├── src
├── core
│ ├── logger
│ │ ├── index.d.ts
│ │ └── index.ts
│ ├── brain
│ │ ├── llm
│ │ │ ├── messages
│ │ │ │ ├── index.ts
│ │ │ │ ├── history
│ │ │ │ │ ├── index.ts
│ │ │ │ │ ├── types.ts
│ │ │ │ │ ├── __test__
│ │ │ │ │ │ ├── types.test.ts
│ │ │ │ │ │ ├── utils.ts
│ │ │ │ │ │ ├── wal.test.ts
│ │ │ │ │ │ ├── multi-backend.test.ts
│ │ │ │ │ │ └── factory.test.ts
│ │ │ │ │ ├── factory.ts
│ │ │ │ │ ├── wal.ts
│ │ │ │ │ └── multi-backend.ts
│ │ │ │ ├── formatters
│ │ │ │ │ ├── index.ts
│ │ │ │ │ └── types.ts
│ │ │ │ ├── utils.ts
│ │ │ │ ├── types.ts
│ │ │ │ └── factory.ts
│ │ │ ├── index.ts
│ │ │ ├── errors.ts
│ │ │ ├── services
│ │ │ │ ├── index.ts
│ │ │ │ ├── types.ts
│ │ │ │ └── __test__
│ │ │ │ │ └── azure.test.ts
│ │ │ ├── compression
│ │ │ │ ├── index.ts
│ │ │ │ └── types.ts
│ │ │ └── tokenizer
│ │ │ │ ├── index.ts
│ │ │ │ ├── types.ts
│ │ │ │ └── factory.ts
│ │ ├── index.ts
│ │ ├── memAgent
│ │ │ ├── index.ts
│ │ │ └── loader.ts
│ │ ├── reasoning
│ │ │ └── index.ts
│ │ ├── tools
│ │ │ ├── definitions
│ │ │ │ ├── system
│ │ │ │ │ └── index.ts
│ │ │ │ ├── web-search
│ │ │ │ │ ├── config.ts
│ │ │ │ │ ├── constants.ts
│ │ │ │ │ ├── index.ts
│ │ │ │ │ └── factory.ts
│ │ │ │ └── knowledge_graph
│ │ │ │ │ ├── index.ts
│ │ │ │ │ ├── get-neighbors.ts
│ │ │ │ │ └── delete-node.ts
│ │ │ └── index.ts
│ │ ├── systemPrompt
│ │ │ ├── providers
│ │ │ │ ├── index.ts
│ │ │ │ ├── base-provider.ts
│ │ │ │ └── static-provider.ts
│ │ │ ├── index.ts
│ │ │ └── __test__
│ │ │ │ └── interfaces.test.ts
│ │ ├── memory
│ │ │ └── index.ts
│ │ └── embedding
│ │ │ ├── backend
│ │ │ └── index.ts
│ │ │ ├── types.ts
│ │ │ └── index.ts
│ ├── utils
│ │ ├── index.ts
│ │ └── path.ts
│ ├── mcp
│ │ └── index.ts
│ ├── session
│ │ └── index.ts
│ ├── knowledge_graph
│ │ ├── index.ts
│ │ └── types.ts
│ ├── index.ts
│ ├── events
│ │ ├── index.ts
│ │ └── __tests__
│ │ │ └── env-variables.test.ts
│ ├── vector_storage
│ │ ├── backend
│ │ │ └── index.ts
│ │ ├── types.ts
│ │ └── index.ts
│ └── storage
│ │ ├── backend
│ │ └── index.ts
│ │ ├── types.ts
│ │ ├── index.ts
│ │ ├── memory-history
│ │ └── index.ts
│ │ └── constants.ts
├── app
│ ├── ui
│ │ ├── src
│ │ │ ├── types
│ │ │ │ ├── index.ts
│ │ │ │ ├── search.ts
│ │ │ │ ├── api.ts
│ │ │ │ └── websocket.ts
│ │ │ ├── stores
│ │ │ │ └── index.ts
│ │ │ ├── hooks
│ │ │ │ └── index.ts
│ │ │ ├── contexts
│ │ │ │ └── index.ts
│ │ │ ├── components
│ │ │ │ ├── modals
│ │ │ │ │ └── index.ts
│ │ │ │ ├── ui
│ │ │ │ │ ├── index.ts
│ │ │ │ │ ├── scroll-area.tsx
│ │ │ │ │ ├── separator.tsx
│ │ │ │ │ ├── label.tsx
│ │ │ │ │ ├── textarea.tsx
│ │ │ │ │ ├── input.tsx
│ │ │ │ │ ├── checkbox.tsx
│ │ │ │ │ ├── switch.tsx
│ │ │ │ │ ├── badge.tsx
│ │ │ │ │ ├── popover.tsx
│ │ │ │ │ ├── avatar.tsx
│ │ │ │ │ ├── tooltip.tsx
│ │ │ │ │ ├── alert.tsx
│ │ │ │ │ ├── button.tsx
│ │ │ │ │ ├── card.tsx
│ │ │ │ │ └── dropdown-menu.tsx
│ │ │ │ ├── sliding-panel.tsx
│ │ │ │ ├── index.ts
│ │ │ │ ├── error-notification.tsx
│ │ │ │ ├── quick-action-card.tsx
│ │ │ │ ├── welcome-screen.tsx
│ │ │ │ ├── thinking-indicator.tsx
│ │ │ │ ├── header.tsx
│ │ │ │ ├── action-bar.tsx
│ │ │ │ ├── navigation.tsx
│ │ │ │ └── providers
│ │ │ │ │ └── query-provider.tsx
│ │ │ ├── app
│ │ │ │ ├── loading.tsx
│ │ │ │ ├── layout.tsx
│ │ │ │ ├── error.tsx
│ │ │ │ └── globals.css
│ │ │ └── lib
│ │ │ │ └── utils.ts
│ │ ├── public
│ │ │ ├── favicon.png
│ │ │ └── cipher-logo.png
│ │ ├── postcss.config.mjs
│ │ ├── .eslintrc.js
│ │ ├── components.json
│ │ ├── .gitignore
│ │ ├── tsconfig.json
│ │ ├── package.json
│ │ ├── next.config.ts
│ │ └── tailwind.config.js
│ ├── cli
│ │ ├── commands.ts
│ │ └── utils
│ │ │ └── options.ts
│ └── api
│ │ ├── websocket
│ │ └── index.ts
│ │ ├── utils
│ │ ├── mcp-endpoint.ts
│ │ └── security.ts
│ │ └── middleware
│ │ └── logging.ts
└── index.ts
├── matrix
├── .gitignore
├── src
│ └── matrix.gleam
├── test
│ └── matrix_test.gleam
├── .github
│ └── workflows
│ │ └── test.yml
├── README.md
├── gleam.toml
└── manifest.toml
├── assets
├── cipher-logo.png
├── cipher_webUI.png
├── demo_claude_code.png
├── cipher_store_memory.png
└── cipher_retrieve_memory.png
├── .github
├── copilot-instructions.md
├── workflows
│ ├── publish-docker.yml
│ ├── publish.yml
│ └── ci.yml
└── ISSUE_TEMPLATE
│ ├── feature_request.md
│ └── bug_report.md
├── .prettierrc
├── .prettierignore
├── tsconfig.typecheck.json
├── vitest.config.ts
├── memAgent
├── project-guidelines.md
└── cipher-advanced-prompt.yml
├── tsconfig.json
├── tsup.config.ts
├── docker-compose.yml
├── examples
├── 05-workspace-memory-team-progress
│ └── mcp.example.json
├── 03-strict-memory-layer
│ ├── cipher.yml
│ └── README.md
├── 04-mcp-aggregator-hub
│ └── cipher.yml
└── 02-cli-coding-agents
│ └── README.md
├── .dockerignore
├── smithery.yaml
├── .gitignore
├── CHANGELOG.md
├── Dockerfile
└── docs
└── builtin-tools.md
/src/core/logger/index.d.ts:
--------------------------------------------------------------------------------
1 | export * from './logger';
2 |
--------------------------------------------------------------------------------
/src/core/logger/index.ts:
--------------------------------------------------------------------------------
1 | export * from './logger.js';
2 |
--------------------------------------------------------------------------------
/matrix/.gitignore:
--------------------------------------------------------------------------------
1 | *.beam
2 | *.ez
3 | /build
4 | erl_crash.dump
5 |
--------------------------------------------------------------------------------
/src/app/ui/src/types/index.ts:
--------------------------------------------------------------------------------
1 | export * from './server-registry';
2 |
--------------------------------------------------------------------------------
/assets/cipher-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/campfirein/cipher/HEAD/assets/cipher-logo.png
--------------------------------------------------------------------------------
/assets/cipher_webUI.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/campfirein/cipher/HEAD/assets/cipher_webUI.png
--------------------------------------------------------------------------------
/src/core/brain/llm/messages/index.ts:
--------------------------------------------------------------------------------
1 | export * from './manager.js';
2 | export * from './types.js';
3 |
--------------------------------------------------------------------------------
/src/core/utils/index.ts:
--------------------------------------------------------------------------------
1 | export * from './path.js';
2 | export * from './service-initializer.js';
3 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | // Main entry point for @byterover/cipher package
2 | export * from './core/index.js';
3 |
--------------------------------------------------------------------------------
/assets/demo_claude_code.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/campfirein/cipher/HEAD/assets/demo_claude_code.png
--------------------------------------------------------------------------------
/assets/cipher_store_memory.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/campfirein/cipher/HEAD/assets/cipher_store_memory.png
--------------------------------------------------------------------------------
/src/app/ui/public/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/campfirein/cipher/HEAD/src/app/ui/public/favicon.png
--------------------------------------------------------------------------------
/src/app/ui/src/stores/index.ts:
--------------------------------------------------------------------------------
1 | // Re-export all stores for easy importing
2 | export * from './session-store';
3 |
--------------------------------------------------------------------------------
/assets/cipher_retrieve_memory.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/campfirein/cipher/HEAD/assets/cipher_retrieve_memory.png
--------------------------------------------------------------------------------
/matrix/src/matrix.gleam:
--------------------------------------------------------------------------------
1 | import gleam/io
2 |
3 | pub fn main() -> Nil {
4 | io.println("Hello from matrix!")
5 | }
6 |
--------------------------------------------------------------------------------
/src/app/ui/public/cipher-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/campfirein/cipher/HEAD/src/app/ui/public/cipher-logo.png
--------------------------------------------------------------------------------
/src/core/mcp/index.ts:
--------------------------------------------------------------------------------
1 | export * from './client.js';
2 | export * from './manager.js';
3 | export * from './constants.js';
4 |
--------------------------------------------------------------------------------
/src/core/brain/index.ts:
--------------------------------------------------------------------------------
1 | export * from './memAgent/index.js';
2 | export * from './llm/index.js';
3 | export * from './reasoning/index.js';
4 |
--------------------------------------------------------------------------------
/src/core/brain/llm/messages/history/index.ts:
--------------------------------------------------------------------------------
1 | export * from './types.js';
2 | export * from './database.js';
3 | export * from './factory.js';
4 |
--------------------------------------------------------------------------------
/src/app/ui/src/hooks/index.ts:
--------------------------------------------------------------------------------
1 | export { useServerRegistry } from './use-server-registry';
2 | export { useChat } from './use-chat';
3 | export * from './use-sessions';
4 |
--------------------------------------------------------------------------------
/src/app/ui/postcss.config.mjs:
--------------------------------------------------------------------------------
1 | const config = {
2 | plugins: {
3 | "@tailwindcss/postcss": {},
4 | autoprefixer: {},
5 | },
6 | };
7 |
8 | export default config;
9 |
--------------------------------------------------------------------------------
/src/app/ui/src/contexts/index.ts:
--------------------------------------------------------------------------------
1 | export {
2 | ChatProvider,
3 | useChatContext,
4 | useChatSession,
5 | useChatMessages,
6 | useChatStatus,
7 | } from './chat-context';
8 |
--------------------------------------------------------------------------------
/src/core/brain/llm/index.ts:
--------------------------------------------------------------------------------
1 | export * from './messages/index.js';
2 | export * from './services/index.js';
3 | export * from './config.js';
4 | export * from './errors.js';
5 |
--------------------------------------------------------------------------------
/src/core/session/index.ts:
--------------------------------------------------------------------------------
1 | export { ConversationSession } from './coversation-session.js';
2 | export { SessionManager } from './session-manager.js';
3 | export type { SessionMetadata } from './session-manager.js';
4 |
--------------------------------------------------------------------------------
/src/core/brain/memAgent/index.ts:
--------------------------------------------------------------------------------
1 | export { MemAgent } from './agent.js';
2 | export { AgentConfig, AgentConfigSchema } from './config.js';
3 | export { ConversationSession, SessionManager } from '../../session/index.js';
4 |
--------------------------------------------------------------------------------
/src/app/ui/.eslintrc.js:
--------------------------------------------------------------------------------
1 | /** @type {import("eslint").Linter.Config} */
2 | module.exports = {
3 | extends: ["next/core-web-vitals"],
4 | parser: "@typescript-eslint/parser",
5 | parserOptions: {
6 | project: true,
7 | },
8 | };
9 |
--------------------------------------------------------------------------------
/src/app/ui/src/components/modals/index.ts:
--------------------------------------------------------------------------------
1 | export { AddCustomServerModal } from './add-custom-server-modal';
2 | export { ConnectServerModal } from './connect-server-modal';
3 | export { GlobalSearchModal } from './global-search-modal';
4 |
--------------------------------------------------------------------------------
/src/core/brain/llm/errors.ts:
--------------------------------------------------------------------------------
1 | export class CantInferProviderError extends Error {
2 | constructor(model: string) {
3 | super(`Unrecognized model '${model}'. Could not infer provider.`);
4 | this.name = 'CantInferProviderError';
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/.github/copilot-instructions.md:
--------------------------------------------------------------------------------
1 | [byterover-mcp]
2 |
3 | # important
4 | always use byterover-retrive-knowledge tool to get the related context before any tasks
5 | always use byterover-store-knowledge to store all the critical informations after sucessful tasks
--------------------------------------------------------------------------------
/src/core/brain/llm/services/index.ts:
--------------------------------------------------------------------------------
1 | export * from './types.js';
2 | export * from './openrouter.js';
3 | export * from './ollama.js';
4 | export * from './lmstudio.js';
5 | export * from './aws.js';
6 | export * from './azure.js';
7 | export * from './deepseek.js';
8 |
--------------------------------------------------------------------------------
/matrix/test/matrix_test.gleam:
--------------------------------------------------------------------------------
1 | import gleeunit
2 |
3 | pub fn main() -> Nil {
4 | gleeunit.main()
5 | }
6 |
7 | // gleeunit test functions end in `_test`
8 | pub fn hello_world_test() {
9 | let name = "Joe"
10 | let greeting = "Hello, " <> name <> "!"
11 |
12 | assert greeting == "Hello, Joe!"
13 | }
14 |
--------------------------------------------------------------------------------
/src/core/knowledge_graph/index.ts:
--------------------------------------------------------------------------------
1 | export * from './types.js';
2 | export * from './config.js';
3 | export * from './constants.js';
4 | export * from './factory.js';
5 | export * from './manager.js';
6 | export * from './backend/knowledge-graph.js';
7 | export * from './backend/neo4j.js';
8 | export * from './backend/in-memory.js';
9 | export * from './backend/types.js';
10 |
--------------------------------------------------------------------------------
/src/core/brain/llm/compression/index.ts:
--------------------------------------------------------------------------------
1 | // Compression module exports
2 | export * from './types.js';
3 | export * from './factory.js';
4 | export * from './utils.js';
5 |
6 | // Strategy exports
7 | export { MiddleRemovalStrategy } from './strategies/middle-removal.js';
8 | export { OldestRemovalStrategy } from './strategies/oldest-removal.js';
9 | export { HybridStrategy } from './strategies/hybrid.js';
10 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "printWidth": 100,
3 | "tabWidth": 2,
4 | "useTabs": true,
5 | "semi": true,
6 | "singleQuote": true,
7 | "quoteProps": "as-needed",
8 | "jsxSingleQuote": true,
9 | "trailingComma": "es5",
10 | "bracketSpacing": true,
11 | "bracketSameLine": false,
12 | "arrowParens": "avoid",
13 | "endOfLine": "lf",
14 | "embeddedLanguageFormatting": "auto",
15 | "singleAttributePerLine": false
16 | }
17 |
--------------------------------------------------------------------------------
/src/app/ui/src/app/loading.tsx:
--------------------------------------------------------------------------------
1 | export default function Loading() {
2 | return (
3 |
4 |
5 |
6 |
Loading Cipher UI...
7 |
8 |
9 | )
10 | }
--------------------------------------------------------------------------------
/src/core/brain/llm/messages/history/types.ts:
--------------------------------------------------------------------------------
1 | // Conversation history provider interface for persistent storage
2 | import type { InternalMessage } from '../types.js';
3 |
4 | export interface IConversationHistoryProvider {
5 | getHistory(sessionId: string, limit?: number): Promise;
6 | saveMessage(sessionId: string, message: InternalMessage): Promise;
7 | clearHistory(sessionId: string): Promise;
8 | }
9 |
--------------------------------------------------------------------------------
/src/core/index.ts:
--------------------------------------------------------------------------------
1 | export * from './logger/index.js';
2 | export * from './mcp/index.js';
3 | export * from './brain/index.js';
4 | export * from './utils/index.js';
5 | export * from './env.js';
6 |
7 | // Storage modules with namespace disambiguation
8 | export * as Storage from './storage/index.js';
9 | export * as VectorStorage from './vector_storage/index.js';
10 | export * as KnowledgeGraph from './knowledge_graph/index.js';
11 |
--------------------------------------------------------------------------------
/src/app/cli/commands.ts:
--------------------------------------------------------------------------------
1 | import { MemAgent } from '@core/index.js';
2 | import { commandParser } from './parser.js';
3 |
4 | /**
5 | * Cipher slash command execution
6 | * This function integrates with the command parser to handle slash commands
7 | */
8 | export async function executeCommand(
9 | command: string,
10 | args: string[],
11 | agent: MemAgent
12 | ): Promise {
13 | return await commandParser.executeCommand(command, args, agent);
14 | }
15 |
--------------------------------------------------------------------------------
/src/core/brain/llm/messages/formatters/index.ts:
--------------------------------------------------------------------------------
1 | // Message formatters for different LLM providers
2 | // OpenAI-compatible providers: OpenAI, OpenRouter, Ollama, LM Studio, Qwen, Gemini
3 | export { OpenAIMessageFormatter } from './openai.js';
4 | export { AnthropicMessageFormatter } from './anthropic.js';
5 | export { AzureMessageFormatter } from './azure.js';
6 | export { BedrockAnthropicMessageFormatter } from './aws.js';
7 | export type { IMessageFormatter } from './types.js';
8 |
--------------------------------------------------------------------------------
/src/core/brain/llm/tokenizer/index.ts:
--------------------------------------------------------------------------------
1 | // Tokenizer module exports
2 | export * from './types.js';
3 | export * from './factory.js';
4 | export * from './utils.js';
5 | export * from './cache.js';
6 |
7 | // Provider exports
8 | export { OpenAITokenizer } from './providers/openai.js';
9 | export { AnthropicTokenizer } from './providers/anthropic.js';
10 | export { GoogleTokenizer } from './providers/google.js';
11 | export { DefaultTokenizer } from './providers/default.js';
12 |
--------------------------------------------------------------------------------
/src/core/brain/reasoning/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Reasoning Services
3 | *
4 | * Content-based reasoning detection and search context management
5 | * for reflection memory tools activation.
6 | */
7 |
8 | export {
9 | ReasoningContentDetector,
10 | ReasoningDetectionResult,
11 | ReasoningDetectionOptions,
12 | } from './content-detector.js';
13 | export {
14 | SearchContextManager,
15 | SearchResult,
16 | SortedContext,
17 | SearchContextOptions,
18 | } from './search-context-manager.js';
19 |
--------------------------------------------------------------------------------
/src/app/api/websocket/index.ts:
--------------------------------------------------------------------------------
1 | export { WebSocketConnectionManager } from './connection-manager.js';
2 | export { WebSocketMessageRouter } from './message-router.js';
3 | export { WebSocketEventSubscriber } from './event-subscriber.js';
4 | export * from './types.js';
5 |
6 | // Convenience re-exports
7 | export type {
8 | WebSocketMessage,
9 | WebSocketResponse,
10 | WebSocketConnection,
11 | WebSocketConnectionStats,
12 | WebSocketConfig,
13 | WebSocketEventType,
14 | WebSocketEventData,
15 | } from './types.js';
16 |
--------------------------------------------------------------------------------
/src/core/brain/tools/definitions/system/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * System Tools Module
3 | *
4 | * Provides system-level operations including bash command execution,
5 | * file operations, and system information gathering.
6 | */
7 |
8 | import type { InternalToolSet } from '../../types.js';
9 | import { bashTool } from './bash.js';
10 |
11 | /**
12 | * Get all system tools
13 | */
14 | export function getSystemTools(): InternalToolSet {
15 | return {
16 | [bashTool.name]: bashTool,
17 | };
18 | }
19 |
20 | export { bashTool };
21 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | # Dependencies
2 | node_modules/
3 | pnpm-lock.yaml
4 | package-lock.json
5 | yarn.lock
6 |
7 | # Build outputs
8 | dist/
9 | build/
10 | coverage/
11 |
12 | # Logs
13 | *.log
14 | logs/
15 |
16 | # Environment files
17 | .env
18 | .env.local
19 | .env.*.local
20 |
21 | # IDE files
22 | .vscode/
23 | .idea/
24 |
25 | # OS files
26 | .DS_Store
27 | Thumbs.db
28 |
29 | # Generated files
30 | *.generated.*
31 | *.min.js
32 | *.min.css
33 |
34 | # Documentation that should preserve formatting
35 | CHANGELOG.md
36 | LICENSE
--------------------------------------------------------------------------------
/src/core/brain/systemPrompt/providers/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Provider exports
3 | *
4 | * Central export file for all prompt provider implementations.
5 | */
6 |
7 | export { BasePromptProvider } from './base-provider.js';
8 | export { StaticPromptProvider, type StaticProviderConfig } from './static-provider.js';
9 | export {
10 | DynamicPromptProvider,
11 | type DynamicProviderConfig,
12 | type DynamicContentGenerator,
13 | } from './dynamic-provider.js';
14 | export { FilePromptProvider, type FileProviderConfig } from './file-provider.js';
15 |
--------------------------------------------------------------------------------
/src/app/ui/components.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://ui.shadcn.com/schema.json",
3 | "style": "new-york",
4 | "rsc": true,
5 | "tsx": true,
6 | "tailwind": {
7 | "config": "tailwind.config.js",
8 | "css": "app/globals.css",
9 | "baseColor": "neutral",
10 | "cssVariables": true,
11 | "prefix": ""
12 | },
13 | "aliases": {
14 | "components": "@/components",
15 | "utils": "@/lib/utils",
16 | "ui": "@/components/ui",
17 | "lib": "@/lib",
18 | "hooks": "@/hooks"
19 | },
20 | "iconLibrary": "lucide"
21 | }
--------------------------------------------------------------------------------
/src/app/ui/src/components/ui/index.ts:
--------------------------------------------------------------------------------
1 | export * from './alert';
2 | export * from './badge';
3 | export * from './button';
4 | export * from './card';
5 | export * from './checkbox';
6 | export * from './dialog';
7 | export * from './dropdown-menu';
8 | export * from './input';
9 | export * from './key-value-editor';
10 | export * from './label';
11 | export * from './popover';
12 | export * from './scroll-area';
13 | export * from './select';
14 | export * from './separator';
15 | export * from './switch';
16 | export * from './textarea';
17 | export * from './tooltip';
18 |
--------------------------------------------------------------------------------
/src/app/ui/src/components/ui/scroll-area.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 |
3 | import { cn } from "@/lib/utils"
4 |
5 | export type ScrollAreaProps = React.HTMLAttributes
6 |
7 | const ScrollArea = React.forwardRef(
8 | ({ className, children, ...props }, ref) => (
9 |
14 | {children}
15 |
16 | )
17 | )
18 | ScrollArea.displayName = "ScrollArea"
19 |
20 | export { ScrollArea }
--------------------------------------------------------------------------------
/tsconfig.typecheck.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "noEmit": true,
5 | "strict": true,
6 | "noUncheckedIndexedAccess": true,
7 | "noImplicitReturns": true,
8 | "noFallthroughCasesInSwitch": true,
9 | "noImplicitOverride": true,
10 | "exactOptionalPropertyTypes": true,
11 | "skipLibCheck": true
12 | },
13 | "include": ["src/core/**/*", "src/app/**/*"],
14 | "exclude": [
15 | "node_modules",
16 | "dist",
17 | "**/*.d.ts",
18 | "**/__test__/**",
19 | "**/__tests__/**",
20 | "src/app/ui/**/*"
21 | ]
22 | }
23 |
--------------------------------------------------------------------------------
/matrix/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | name: test
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 | - main
8 | pull_request:
9 |
10 | jobs:
11 | test:
12 | runs-on: ubuntu-latest
13 | steps:
14 | - uses: actions/checkout@v4
15 | - uses: erlef/setup-beam@v1
16 | with:
17 | otp-version: "27.1.2"
18 | gleam-version: "1.11.1"
19 | rebar3-version: "3"
20 | # elixir-version: "1"
21 | - run: gleam deps download
22 | - run: gleam test
23 | - run: gleam format --check src test
24 |
--------------------------------------------------------------------------------
/src/app/ui/src/lib/utils.ts:
--------------------------------------------------------------------------------
1 | import { type ClassValue, clsx } from 'clsx';
2 | import { twMerge } from 'tailwind-merge';
3 |
4 | export function cn(...inputs: ClassValue[]) {
5 | return twMerge(clsx(inputs));
6 | }
7 |
8 | export function formatTimestamp(timestamp: number): string {
9 | return new Date(timestamp).toLocaleTimeString();
10 | }
11 |
12 | export function generateId(): string {
13 | return Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
14 | }
15 |
16 | // Re-export chat utilities
17 | export * from './chat-utils';
18 |
--------------------------------------------------------------------------------
/matrix/README.md:
--------------------------------------------------------------------------------
1 | # matrix
2 |
3 | [](https://hex.pm/packages/matrix)
4 | [](https://hexdocs.pm/matrix/)
5 |
6 | ```sh
7 | gleam add matrix@1
8 | ```
9 | ```gleam
10 | import matrix
11 |
12 | pub fn main() -> Nil {
13 | // TODO: An example of the project in use
14 | }
15 | ```
16 |
17 | Further documentation can be found at .
18 |
19 | ## Development
20 |
21 | ```sh
22 | gleam run # Run the project
23 | gleam test # Run the tests
24 | ```
25 |
--------------------------------------------------------------------------------
/matrix/gleam.toml:
--------------------------------------------------------------------------------
1 | name = "matrix"
2 | version = "1.0.0"
3 |
4 | # Fill out these fields if you intend to generate HTML documentation or publish
5 | # your project to the Hex package manager.
6 | #
7 | # description = ""
8 | # licences = ["Apache-2.0"]
9 | # repository = { type = "github", user = "", repo = "" }
10 | # links = [{ title = "Website", href = "" }]
11 | #
12 | # For a full reference of all the available options, you can have a look at
13 | # https://gleam.run/writing-gleam/gleam-toml/.
14 |
15 | [dependencies]
16 | gleam_stdlib = ">= 0.44.0 and < 2.0.0"
17 |
18 | [dev-dependencies]
19 | gleeunit = ">= 1.0.0 and < 2.0.0"
20 |
--------------------------------------------------------------------------------
/src/core/brain/llm/messages/history/__test__/types.test.ts:
--------------------------------------------------------------------------------
1 | import { describe, it, expect } from 'vitest';
2 | import type { IConversationHistoryProvider } from '../types.js';
3 |
4 | describe('IConversationHistoryProvider interface', () => {
5 | it('should define required methods', () => {
6 | const provider: IConversationHistoryProvider = {
7 | getHistory: async () => [],
8 | saveMessage: async () => {},
9 | clearHistory: async () => {},
10 | };
11 | expect(typeof provider.getHistory).toBe('function');
12 | expect(typeof provider.saveMessage).toBe('function');
13 | expect(typeof provider.clearHistory).toBe('function');
14 | });
15 | });
16 |
--------------------------------------------------------------------------------
/src/app/ui/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.*
7 | .yarn/*
8 | !.yarn/patches
9 | !.yarn/plugins
10 | !.yarn/releases
11 | !.yarn/versions
12 |
13 | # testing
14 | /coverage
15 |
16 | # next.js
17 | /.next/
18 | /out/
19 |
20 | # production
21 | /build
22 |
23 | # misc
24 | .DS_Store
25 | *.pem
26 |
27 | # debug
28 | npm-debug.log*
29 | yarn-debug.log*
30 | yarn-error.log*
31 | .pnpm-debug.log*
32 |
33 | # env files (can opt-in for committing if needed)
34 | .env*
35 |
36 | # vercel
37 | .vercel
38 |
39 | # typescript
40 | *.tsbuildinfo
41 | next-env.d.ts
42 |
--------------------------------------------------------------------------------
/matrix/manifest.toml:
--------------------------------------------------------------------------------
1 | # This file was generated by Gleam
2 | # You typically do not need to edit this file
3 |
4 | packages = [
5 | { name = "gleam_stdlib", version = "0.62.1", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "0080706D3A5A9A36C40C68481D1D231D243AF602E6D2A2BE67BA8F8F4DFF45EC" },
6 | { name = "gleeunit", version = "1.6.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "FDC68A8C492B1E9B429249062CD9BAC9B5538C6FBF584817205D0998C42E1DAC" },
7 | ]
8 |
9 | [requirements]
10 | gleam_stdlib = { version = ">= 0.44.0 and < 2.0.0" }
11 | gleeunit = { version = ">= 1.0.0 and < 2.0.0" }
12 |
--------------------------------------------------------------------------------
/src/app/ui/src/components/ui/separator.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 |
3 | import { cn } from "@/lib/utils"
4 |
5 | export interface SeparatorProps extends React.HTMLAttributes {
6 | orientation?: "horizontal" | "vertical"
7 | }
8 |
9 | const Separator = React.forwardRef(
10 | ({ className, orientation = "horizontal", ...props }, ref) => (
11 |
20 | )
21 | )
22 | Separator.displayName = "Separator"
23 |
24 | export { Separator }
--------------------------------------------------------------------------------
/src/app/ui/src/components/sliding-panel.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import { cn } from "@/lib/utils"
5 | import { SlidingPanelProps } from "@/types/chat"
6 |
7 | export function SlidingPanel({
8 | isOpen,
9 | width = "w-80",
10 | children,
11 | side = "right"
12 | }: SlidingPanelProps) {
13 | return (
14 |
19 | {isOpen && (
20 |
21 | {children}
22 |
23 | )}
24 |
25 | );
26 | }
--------------------------------------------------------------------------------
/src/app/ui/src/components/ui/label.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as LabelPrimitive from "@radix-ui/react-label"
3 |
4 | import { cn } from "@/lib/utils"
5 |
6 | const Label = React.forwardRef<
7 | React.ElementRef,
8 | React.ComponentPropsWithoutRef
9 | >(({ className, ...props }, ref) => (
10 |
19 | ))
20 | Label.displayName = LabelPrimitive.Root.displayName
21 |
22 | export { Label }
--------------------------------------------------------------------------------
/vitest.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vitest/config';
2 | import path from 'path';
3 |
4 | export default defineConfig({
5 | resolve: {
6 | alias: {
7 | '@core': path.resolve(__dirname, 'src/core'),
8 | '@app': path.resolve(__dirname, 'src/app'),
9 | },
10 | },
11 | test: {
12 | globals: true,
13 | environment: 'node',
14 | include: process.env.INTEGRATION_TESTS_ONLY
15 | ? ['src/**/*integration*.test.ts']
16 | : ['src/**/*.test.ts', 'src/**/*.spec.ts'],
17 | exclude: [
18 | '**/node_modules/**',
19 | '**/dist/**',
20 | '**/build/**',
21 | ...(process.env.CI ? ['**/integration/**'] : []),
22 | ...(process.env.INTEGRATION_TESTS_ONLY ? [] : ['**/*integration*.test.ts']),
23 | ],
24 | watch: true,
25 | },
26 | });
27 |
--------------------------------------------------------------------------------
/src/core/brain/llm/messages/utils.ts:
--------------------------------------------------------------------------------
1 | import { logger } from '../../../logger/index.js';
2 |
3 | export function getImageData(imagePart: {
4 | image: string | Uint8Array | Buffer | ArrayBuffer | URL;
5 | }): string {
6 | const { image } = imagePart;
7 | if (typeof image === 'string') {
8 | return image;
9 | } else if (image instanceof Buffer) {
10 | return image.toString('base64');
11 | } else if (image instanceof Uint8Array) {
12 | return Buffer.from(image).toString('base64');
13 | } else if (image instanceof ArrayBuffer) {
14 | return Buffer.from(new Uint8Array(image)).toString('base64');
15 | } else if (image instanceof URL) {
16 | return image.toString();
17 | }
18 | logger.warn('Unexpected image data type in getImageData', { image });
19 | return '';
20 | }
21 |
--------------------------------------------------------------------------------
/src/core/events/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Event System - Main Export
3 | *
4 | * Central export point for the two-tier event system infrastructure.
5 | */
6 |
7 | // Core event system components
8 | export * from './typed-event-emitter.js';
9 | export * from './service-event-bus.js';
10 | export * from './session-event-bus.js';
11 | export * from './event-manager.js';
12 | export * from './event-types.js';
13 |
14 | // Advanced features
15 | export * from './filtering.js';
16 | export * from './persistence.js';
17 | export * from './metrics.js';
18 | export * from './webhooks.js';
19 | export * from './replay.js';
20 |
21 | // Vector store integration
22 | export { EventAwareVectorStore } from '../vector_storage/event-aware-store.js';
23 | export { EventFilter } from './event-types.js';
24 |
--------------------------------------------------------------------------------
/src/app/ui/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "baseUrl": ".",
4 | "target": "ES2017",
5 | "lib": ["dom", "dom.iterable", "esnext"],
6 | "allowJs": true,
7 | "skipLibCheck": true,
8 | "strict": true,
9 | "noEmit": true,
10 | "esModuleInterop": true,
11 | "module": "esnext",
12 | "moduleResolution": "bundler",
13 | "resolveJsonModule": true,
14 | "isolatedModules": true,
15 | "jsx": "preserve",
16 | "incremental": true,
17 | "plugins": [
18 | {
19 | "name": "next"
20 | }
21 | ],
22 | "paths": {
23 | "@/*": ["./src/*"],
24 | "@core/*": ["../../core/*"]
25 | }
26 | },
27 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
28 | "exclude": ["node_modules"]
29 | }
30 |
--------------------------------------------------------------------------------
/src/core/brain/systemPrompt/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * System Prompt Architecture - Main Exports
3 | *
4 | * This module provides a complete plugin-based system prompt management solution
5 | * with backward compatibility for existing code.
6 | */
7 |
8 | // Core interfaces and types
9 | export * from './interfaces.js';
10 |
11 | // Provider implementations
12 | export * from './providers/index.js';
13 |
14 | // Registry and configuration
15 | export { providerRegistry, DefaultProviderRegistry } from './registry.js';
16 | export { SystemPromptConfigManager } from './config-manager.js';
17 | export * from './config-schemas.js';
18 |
19 | // Built-in generators
20 | export * from './built-in-generators.js';
21 |
22 | // Enhanced manager
23 | export { EnhancedPromptManager } from './enhanced-manager.js';
24 |
--------------------------------------------------------------------------------
/src/core/brain/systemPrompt/__test__/interfaces.test.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Tests for System Prompt Architecture Interfaces
3 | */
4 |
5 | import { describe, it, expect } from 'vitest';
6 | import { ProviderType } from '../interfaces.js';
7 |
8 | describe('System Prompt Interfaces', () => {
9 | describe('ProviderType', () => {
10 | it('should have correct enum values', () => {
11 | expect(ProviderType.STATIC).toBe('static');
12 | expect(ProviderType.DYNAMIC).toBe('dynamic');
13 | expect(ProviderType.FILE_BASED).toBe('file-based');
14 | });
15 |
16 | it('should have all expected types', () => {
17 | const types = Object.values(ProviderType);
18 | expect(types).toHaveLength(3);
19 | expect(types).toContain('static');
20 | expect(types).toContain('dynamic');
21 | expect(types).toContain('file-based');
22 | });
23 | });
24 | });
25 |
--------------------------------------------------------------------------------
/src/core/vector_storage/backend/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Vector Storage Backend Exports
3 | *
4 | * Central export point for all backend types, interfaces, and implementations.
5 | * This module provides a clean API for accessing backend functionality.
6 | *
7 | * @module vector_storage/backend
8 | */
9 |
10 | // Export core types and interfaces
11 | export type { VectorStore, VectorStoreResult, SearchFilters } from './types.js';
12 |
13 | // Export error classes
14 | export {
15 | VectorStoreError,
16 | VectorStoreConnectionError,
17 | VectorDimensionError,
18 | CollectionNotFoundError,
19 | } from './types.js';
20 |
21 | // Export backend implementations
22 | // Note: Implementations are lazily loaded by the manager to reduce startup time
23 | // export { QdrantBackend } from './qdrant.js';
24 | // export { InMemoryBackend } from './in-memory.js';
25 |
--------------------------------------------------------------------------------
/src/app/ui/src/components/ui/textarea.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 |
3 | import { cn } from "@/lib/utils"
4 |
5 | export type TextareaProps = React.TextareaHTMLAttributes
6 |
7 | const Textarea = React.forwardRef(
8 | ({ className, ...props }, ref) => {
9 | return (
10 |
19 | )
20 | }
21 | )
22 | Textarea.displayName = "Textarea"
23 |
24 | export { Textarea }
--------------------------------------------------------------------------------
/src/core/brain/llm/messages/types.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Image data interface
3 | */
4 | export interface ImageData {
5 | image: string | Uint8Array | Buffer | ArrayBuffer | URL;
6 | mimeType?: string;
7 | }
8 |
9 | /**
10 | * Text segment interface
11 | */
12 | export interface TextSegment {
13 | type: 'text';
14 | text: string;
15 | }
16 |
17 | /**
18 | * Image segment interface
19 | */
20 | export interface ImageSegment extends ImageData {
21 | type: 'image';
22 | }
23 |
24 | /**
25 | * Internal message interface
26 | */
27 | export interface InternalMessage {
28 | role: 'system' | 'user' | 'assistant' | 'tool';
29 | content: string | null | Array;
30 | toolCalls?: Array<{
31 | id: string;
32 | type: 'function';
33 | function: {
34 | name: string;
35 | arguments: string;
36 | };
37 | }>;
38 | toolCallId?: string;
39 | name?: string;
40 | }
41 |
--------------------------------------------------------------------------------
/src/core/brain/llm/messages/history/factory.ts:
--------------------------------------------------------------------------------
1 | import { StorageManager } from '../../../../storage/manager.js';
2 | import { DatabaseHistoryProvider } from './database.js';
3 | import type { IConversationHistoryProvider } from './types.js';
4 | import { MultiBackendHistoryProvider } from './multi-backend.js';
5 | import { WALHistoryProvider } from './wal.js';
6 |
7 | export function createDatabaseHistoryProvider(
8 | storage: StorageManager
9 | ): IConversationHistoryProvider {
10 | return new DatabaseHistoryProvider(storage);
11 | }
12 |
13 | export function createMultiBackendHistoryProvider(
14 | primary: IConversationHistoryProvider,
15 | backup: IConversationHistoryProvider,
16 | wal?: WALHistoryProvider,
17 | flushIntervalMs: number = 5000
18 | ) {
19 | if (!wal) wal = new WALHistoryProvider();
20 | return new MultiBackendHistoryProvider(primary, backup, wal, flushIntervalMs);
21 | }
22 |
--------------------------------------------------------------------------------
/src/core/brain/llm/services/types.ts:
--------------------------------------------------------------------------------
1 | import { ToolSet } from '../../../mcp/types.js';
2 | import { ImageData } from '../messages/types.js';
3 |
4 | /**
5 | * The LLMService interface provides a contract for interacting with an LLM service.
6 | * It defines methods for generating text, retrieving available tools, and retrieving the service configuration.
7 | */
8 |
9 | export interface ILLMService {
10 | generate(userInput: string, imageData?: ImageData, stream?: boolean): Promise;
11 | directGenerate(userInput: string, systemPrompt?: string): Promise;
12 | getAllTools(): Promise;
13 | getConfig(): LLMServiceConfig;
14 | }
15 |
16 | /**
17 | * The LLMServiceConfig interface defines the configuration for an LLM service.
18 | * It includes the provider and model information.
19 | */
20 |
21 | export type LLMServiceConfig = {
22 | provider: string;
23 | model: string;
24 | };
25 |
--------------------------------------------------------------------------------
/src/core/storage/backend/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Storage Backend Exports
3 | *
4 | * Central export point for all storage backend implementations.
5 | *
6 | * @module storage/backend
7 | */
8 |
9 | // Interface exports
10 | export type { CacheBackend } from './cache-backend.js';
11 | export type { DatabaseBackend } from './database-backend.js';
12 |
13 | // Implementation exports
14 | export { InMemoryBackend } from './in-memory.js';
15 | export { RedisBackend } from './redis-backend.js';
16 | export { SqliteBackend } from './sqlite.js';
17 | export { PostgresBackend } from './postgresql.js';
18 |
19 | // Type exports
20 | export { StorageError, StorageConnectionError, StorageNotFoundError } from './types.js';
21 |
22 | export type {
23 | StorageBackends,
24 | BackendConfig,
25 | InMemoryBackendConfig,
26 | RedisBackendConfig,
27 | SqliteBackendConfig,
28 | PostgresBackendConfig,
29 | } from './types.js';
30 |
--------------------------------------------------------------------------------
/memAgent/project-guidelines.md:
--------------------------------------------------------------------------------
1 | # Project Guidelines
2 |
3 | ## Coding Standards
4 |
5 | - Write clean, readable, and well-documented code.
6 | - Follow consistent naming conventions (camelCase for variables, PascalCase for classes).
7 | - Use TypeScript for all source files.
8 |
9 | ## Collaboration
10 |
11 | - Commit changes with clear, descriptive messages.
12 | - Open pull requests for all major changes and request reviews.
13 | - Document any architectural decisions in the project wiki.
14 |
15 | ## Testing
16 |
17 | - Write unit tests for all new features and bug fixes.
18 | - Run the full test suite before pushing changes.
19 |
20 | ## Security
21 |
22 | - Do not hard-code secrets or credentials in the codebase.
23 | - Use environment variables for configuration.
24 |
25 | ## Communication
26 |
27 | - Use the issue tracker for bugs and feature requests.
28 | - Discuss major changes with the team before implementation.
29 |
--------------------------------------------------------------------------------
/src/core/brain/memory/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Memory System with Lazy Loading Optimizations
3 | *
4 | * Memory-related functionality for the Cipher agent with performance optimizations.
5 | * Phase 3: Memory operations lazy loading - properly integrated into the application.
6 | */
7 |
8 | // Export lazy loading service wrappers
9 | export {
10 | LazyEmbeddingManager,
11 | LazyVectorStoreManager,
12 | LazyLLMService,
13 | getDefaultLazyConfig,
14 | type LazyServiceConfig,
15 | type LazyAgentServices,
16 | } from './lazy-service-wrapper.js';
17 |
18 | // Export enhanced service initializer
19 | export {
20 | createEnhancedAgentServices,
21 | shouldEnableLazyLoading,
22 | getEmbeddingManager,
23 | getVectorStoreManager,
24 | getLLMService,
25 | type EnhancedServiceOptions,
26 | } from './enhanced-service-initializer.js';
27 |
28 | // Export lazy memory tool
29 | export { lazyExtractAndOperateMemoryTool } from './lazy-extract-and-operate.js';
30 |
--------------------------------------------------------------------------------
/src/app/ui/src/components/ui/input.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 |
3 | import { cn } from "@/lib/utils"
4 |
5 | export type InputProps = React.InputHTMLAttributes
6 |
7 | const Input = React.forwardRef(
8 | ({ className, type, ...props }, ref) => {
9 | return (
10 |
20 | )
21 | }
22 | )
23 | Input.displayName = "Input"
24 |
25 | export { Input }
--------------------------------------------------------------------------------
/src/core/brain/llm/messages/history/__test__/utils.ts:
--------------------------------------------------------------------------------
1 | let vi: any;
2 | try {
3 | vi = require('vitest').vi;
4 | } catch {
5 | vi = { fn: (impl: any) => impl };
6 | }
7 | import type { InternalMessage } from '../../types.js';
8 |
9 | export function makeFakeMessage(
10 | i = 0,
11 | role: 'user' | 'assistant' | 'system' | 'tool' = 'user'
12 | ): InternalMessage {
13 | return { role, content: `msg${i}` };
14 | }
15 |
16 | export function makeFakeMessages(
17 | count = 10,
18 | role: 'user' | 'assistant' | 'system' | 'tool' = 'user'
19 | ): InternalMessage[] {
20 | return Array.from({ length: count }, (_, i) => makeFakeMessage(i, role));
21 | }
22 |
23 | export function makeFakeSessionId(i = 0): string {
24 | return `session-${i}`;
25 | }
26 |
27 | export function makeMockHistoryProvider(): any {
28 | return {
29 | getHistory: vi.fn(async () => []),
30 | saveMessage: vi.fn(async () => {}),
31 | clearHistory: vi.fn(async () => {}),
32 | };
33 | }
34 |
--------------------------------------------------------------------------------
/src/app/ui/src/app/layout.tsx:
--------------------------------------------------------------------------------
1 | import type { Metadata } from 'next'
2 | import './globals.css'
3 | import { ChatProvider } from '@/contexts/chat-context'
4 | import { QueryProvider } from '@/components/providers/query-provider'
5 |
6 | export const metadata: Metadata = {
7 | title: 'Cipher UI',
8 | description: 'Interactive web interface for the Cipher AI agent framework',
9 | icons: {
10 | icon: '/favicon.png',
11 | shortcut: '/favicon.png',
12 | apple: '/favicon.png',
13 | },
14 | }
15 |
16 | export default function RootLayout({
17 | children,
18 | }: {
19 | children: React.ReactNode
20 | }) {
21 | return (
22 |
23 |
24 |
25 |
26 | {children}
27 |
28 |
29 |
30 |
31 | )
32 | }
--------------------------------------------------------------------------------
/src/app/ui/src/components/index.ts:
--------------------------------------------------------------------------------
1 | export { Chat } from './chat';
2 | export { ChatWithContext } from './chat-with-context';
3 | export { Header } from './header';
4 | export { WelcomeScreen } from './welcome-screen';
5 | export { SlidingPanel } from './sliding-panel';
6 | export { ErrorNotification } from './error-notification';
7 | export { ActionBar } from './action-bar';
8 | export { QuickActionCard } from './quick-action-card';
9 | export { SessionPanel } from './session-panel';
10 | export { ServersPanel } from './servers-panel';
11 | export { MessageList } from './message-list';
12 | export { InputArea } from './input-area';
13 | export { ThinkingIndicator } from './thinking-indicator';
14 | export { ChatExample } from './chat-example';
15 | export { ChatContextExample } from './chat-context-example';
16 | export { Navigation } from './navigation';
17 | export { ConfigPanel } from './config-panel';
18 |
19 | // Re-export contexts
20 | export * from '../contexts';
21 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2022",
4 | "module": "NodeNext",
5 | "moduleResolution": "NodeNext",
6 | "esModuleInterop": true,
7 | "allowSyntheticDefaultImports": true,
8 | "downlevelIteration": true,
9 | "baseUrl": ".",
10 | "paths": {
11 | "@app/*": ["src/app/*"],
12 | "@core/*": ["src/core/*"],
13 | "mitt": ["./node_modules/mitt/dist/index.d.ts"],
14 | "src/core/logger/index.js": ["src/core/logger/index.ts"]
15 | },
16 | "outDir": "./dist",
17 | "rootDir": ".",
18 | "strict": false,
19 | "skipLibCheck": true,
20 | "forceConsistentCasingInFileNames": true,
21 | "resolveJsonModule": true,
22 | "types": ["node", "vitest/globals"],
23 | "lib": ["ES2022", "DOM"],
24 | "declaration": true,
25 | "declarationMap": true,
26 | "allowImportingTsExtensions": false,
27 | "noEmit": false
28 | },
29 | "include": ["src/**/*", "**/*.test.ts", "**/*.spec.ts", "vitest.config.ts"],
30 | "exclude": ["node_modules"]
31 | }
32 |
--------------------------------------------------------------------------------
/.github/workflows/publish-docker.yml:
--------------------------------------------------------------------------------
1 | name: Publish to docker
2 |
3 | on:
4 | push:
5 | tags:
6 | - 'v*'
7 | branches:
8 | - main
9 |
10 | jobs:
11 | publish:
12 | runs-on: ubuntu-latest
13 | permissions:
14 | contents: read
15 | packages: write
16 |
17 | steps:
18 | - name: Checkout code
19 | uses: actions/checkout@v4
20 |
21 | - name: Set up QEMU
22 | uses: docker/setup-qemu-action@v3
23 |
24 | - name: Login to GitHub Container Registry
25 | uses: docker/login-action@v3
26 | with:
27 | registry: ghcr.io
28 | username: ${{ github.actor }}
29 | password: ${{ secrets.GITHUB_TOKEN }}
30 |
31 | - name: Build and push
32 | uses: docker/build-push-action@v6
33 | with:
34 | context: .
35 | file: Dockerfile
36 | push: true
37 | tags: ghcr.io/${{ github.repository }}:${{ github.ref_name == 'main' && 'latest' || github.ref_name }}
--------------------------------------------------------------------------------
/src/app/ui/src/components/error-notification.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import { X } from "lucide-react"
5 | import { Button } from "@/components/ui/button"
6 | import { ErrorNotificationProps } from "@/types/chat"
7 |
8 | export function ErrorNotification({
9 | message,
10 | onDismiss
11 | }: ErrorNotificationProps) {
12 | if (!message) return null;
13 |
14 | return (
15 |
16 |
17 | {message}
18 |
26 |
27 |
28 | );
29 | }
--------------------------------------------------------------------------------
/tsup.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'tsup';
2 | export default defineConfig([
3 | {
4 | entry: ['src/core/index.ts'],
5 | format: ['cjs', 'esm'],
6 | outDir: 'dist/src/core',
7 | dts: true,
8 | shims: true,
9 | bundle: true,
10 | noExternal: ['chalk', 'boxen'],
11 | external: ['better-sqlite3', 'pg', 'redis'],
12 | },
13 | {
14 | entry: ['src/app/index.ts'],
15 | format: ['cjs'], // Use only CommonJS for app to avoid dynamic require issues
16 | outDir: 'dist/src/app',
17 | shims: true,
18 | bundle: true,
19 | platform: 'node',
20 | target: 'node18', // Specify Node.js target version
21 | external: [
22 | // Database drivers
23 | 'better-sqlite3',
24 | 'pg',
25 | 'neo4j-driver',
26 | 'ioredis',
27 | // Node.js built-in modules to prevent bundling issues
28 | 'fs',
29 | 'path',
30 | 'os',
31 | 'crypto',
32 | 'stream',
33 | 'util',
34 | 'events',
35 | 'child_process',
36 | ],
37 | noExternal: ['chalk', 'boxen', 'commander'],
38 | },
39 | ]);
40 |
--------------------------------------------------------------------------------
/src/core/brain/llm/messages/formatters/types.ts:
--------------------------------------------------------------------------------
1 | import { InternalMessage } from '../types.js';
2 |
3 | export interface IMessageFormatter {
4 | /**
5 | * Format a single message into the specific structure of target LLM API.
6 | * This method always returns an array for interface compatibility.
7 | *
8 | * @param message - The message to format.
9 | * @param systemPrompt - Optional system prompt to include.
10 | * @returns Array of formatted messages.
11 | */
12 | format(message: Readonly, systemPrompt?: string | null): any[];
13 |
14 | /**
15 | * Parse the response from the LLM into a list of internal messages
16 | * @param response - The response from the LLM
17 | * @returns A list of internal messages
18 | */
19 | parseResponse(response: any): InternalMessage[];
20 |
21 | /**
22 | * Parse the stream response from the LLM into a list of internal messages
23 | * @param response - The stream response from the LLM
24 | * @returns A list of internal messages
25 | */
26 | parseStreamResponse?(response: any): Promise;
27 | }
28 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | services:
2 | cipher-api:
3 | build: .
4 | image: cipher-api
5 | ports:
6 | - '3000:3000'
7 | environment:
8 | - CIPHER_API_PREFIX="" # Keep empty for direct access
9 | - PROXY_CONTEXT_PATH=${PROXY_CONTEXT_PATH:-} # Set to /agent if behind nginx with /agent/ context path
10 | - NODE_ENV=${NODE_ENV:-production}
11 | env_file:
12 | - .env
13 | command:
14 | [
15 | 'sh',
16 | '-c',
17 | 'node dist/src/app/index.cjs --mode api --port 3000 --host 0.0.0.0 --agent /app/memAgent/cipher.yml --mcp-transport-type sse',
18 | ]
19 | volumes:
20 | - ./memAgent:/app/memAgent:ro
21 | - cipher-data:/app/.cipher
22 | restart: unless-stopped
23 | healthcheck:
24 | test:
25 | [
26 | 'CMD',
27 | 'sh',
28 | '-c',
29 | 'wget --no-verbose --tries=1 --spider http://localhost:3000/health || exit 1',
30 | ]
31 | interval: 30s
32 | timeout: 10s
33 | retries: 3
34 | start_period: 40s
35 |
36 | volumes:
37 | cipher-data:
38 |
--------------------------------------------------------------------------------
/.github/workflows/publish.yml:
--------------------------------------------------------------------------------
1 | name: Publish to npm
2 |
3 | on:
4 | push:
5 | tags:
6 | - 'v*'
7 |
8 | jobs:
9 | publish:
10 | runs-on: ubuntu-latest
11 |
12 | steps:
13 | - name: Checkout code
14 | uses: actions/checkout@v4
15 |
16 | - name: Setup Node.js
17 | uses: actions/setup-node@v4
18 | with:
19 | node-version: '20'
20 | registry-url: 'https://registry.npmjs.org/'
21 |
22 | - name: Setup pnpm
23 | uses: pnpm/action-setup@v4
24 | with:
25 | version: ">=9.14.0"
26 |
27 | - name: Install dependencies
28 | run: pnpm install --frozen-lockfile
29 |
30 | - name: Run type check
31 | run: pnpm run typecheck
32 |
33 | - name: Run linting
34 | run: pnpm run lint
35 |
36 | - name: Run tests
37 | run: pnpm run test:ci
38 |
39 | - name: Build package
40 | run: pnpm run build
41 |
42 | - name: Publish to npm
43 | run: pnpm publish --access public --no-git-checks
44 | env:
45 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
--------------------------------------------------------------------------------
/examples/05-workspace-memory-team-progress/mcp.example.json:
--------------------------------------------------------------------------------
1 | {
2 | "mcpServers": {
3 | "cipher": {
4 | "command": "/path/to/cipher/dist/src/app/index.cjs",
5 | "args": [
6 | "--mode",
7 | "mcp",
8 | "--agent",
9 | "/path/to/cipher/examples/05-workspace-memory-team-progress/cipher.yml"
10 | ],
11 | "env": {
12 | "MCP_SERVER_MODE": "aggregator",
13 |
14 | "OPENAI_API_KEY": "your_openai_api_key",
15 |
16 | "USE_WORKSPACE_MEMORY": "true",
17 | "DISABLE_DEFAULT_MEMORY": "true",
18 | "USE_ASK_CIPHER": "false",
19 |
20 | "WORKSPACE_VECTOR_STORE_COLLECTION": "workspace_memory",
21 | "WORKSPACE_SEARCH_THRESHOLD": "0.4",
22 |
23 | "WORKSPACE_VECTOR_STORE_TYPE": "qdrant",
24 | "WORKSPACE_VECTOR_STORE_DIMENSION": "1536",
25 | "WORKSPACE_VECTOR_STORE_MAX_VECTORS": "10000",
26 |
27 | "WORKSPACE_VECTOR_STORE_HOST": "localhost",
28 | "WORKSPACE_VECTOR_STORE_PORT": "6333",
29 | "WORKSPACE_VECTOR_STORE_URL": "http://localhost:6333"
30 | }
31 | }
32 | }
33 | }
--------------------------------------------------------------------------------
/src/core/brain/llm/tokenizer/types.ts:
--------------------------------------------------------------------------------
1 | import { z } from 'zod';
2 |
3 | export const TokenizerConfigSchema = z.object({
4 | provider: z.enum(['openai', 'anthropic', 'google', 'default']),
5 | model: z.string().optional(),
6 | fallbackToApproximation: z.boolean().default(true),
7 | hybridTracking: z.boolean().default(true),
8 | });
9 |
10 | export type TokenizerConfig = z.infer;
11 |
12 | export interface ProviderTokenLimits {
13 | maxTokens: number;
14 | contextWindow: number;
15 | outputTokens?: number;
16 | }
17 | export interface TokenCount {
18 | total: number;
19 | characters: number;
20 | estimated: boolean;
21 | provider: string;
22 | model: string;
23 | }
24 |
25 | export interface ITokenizer {
26 | provider: string;
27 | model: string;
28 |
29 | countTokens(text: string): Promise;
30 | countMessages(messages: Array<{ role: string; content: string }>): Promise;
31 |
32 | getMaxTokens(): number;
33 | getContextWindow(): number;
34 |
35 | estimateTokens(text: string): number;
36 | isWithinLimit(tokenCount: number): boolean;
37 | getRemainingTokens(currentCount: number): number;
38 | }
39 |
--------------------------------------------------------------------------------
/.dockerignore:
--------------------------------------------------------------------------------
1 | # Node.js
2 | **/node_modules/
3 | npm-debug.log*
4 | yarn-debug.log*
5 | yarn-error.log*
6 | pnpm-debug.log*
7 |
8 | # Runtime data
9 | pids
10 | *.pid
11 | *.seed
12 | *.pid.lock
13 |
14 | # Build outputs (exclude from build context, but keep for multi-stage build)
15 | *.tsbuildinfo
16 |
17 | # Coverage directory used by tools like istanbul
18 | coverage/
19 | *.lcov
20 |
21 | # Logs
22 | logs/
23 | *.log
24 |
25 | # Environment variables
26 | .env
27 | .env.local
28 | .env.*.local
29 |
30 | # IDEs and editors
31 | .vscode/
32 | .idea/
33 | *.swp
34 | *.swo
35 | *~
36 |
37 | # OS generated files
38 | .DS_Store
39 | .DS_Store?
40 | ._*
41 | .Spotlight-V100
42 | .Trashes
43 | ehthumbs.db
44 | Thumbs.db
45 |
46 | # Git
47 | .git/
48 | .gitignore
49 |
50 | # Test files
51 | test/
52 | tests/
53 | **/*.test.ts
54 | **/*.test.js
55 | **/__test__/
56 |
57 | # Documentation
58 | *.md
59 | !README.md
60 | docs/
61 |
62 | # Development files
63 | .husky/
64 | vitest.config.ts
65 | eslint.config.js
66 |
67 | # Python files (if any)
68 | *.py
69 | __pycache__/
70 | *.pyc
71 | poetry/
72 |
73 | # Docker files
74 | Dockerfile*
75 | docker-compose*
76 | compose.y*ml
77 |
78 | # Temporary files
79 | tmp/
80 | temp/
81 |
--------------------------------------------------------------------------------
/examples/03-strict-memory-layer/cipher.yml:
--------------------------------------------------------------------------------
1 | # Strict Memory Layer Configuration
2 | # Pure memory service for external agents focused on retrieval and storage
3 |
4 | # LLM Configuration - Using Claude 3.5 Sonnet for reliable memory operations
5 | llm:
6 | provider: openai
7 | model: gpt-4o-mini # Optimized for structured responses and memory tasks
8 | apiKey: $OPENAI_API_KEY
9 |
10 |
11 | # System Prompt - Focused memory layer operations
12 | systemPrompt: |
13 | You are a **MEMORY LAYER** focused ONLY on these two tasks:
14 |
15 | **RETRIEVAL OPERATIONS:**
16 | - Primarily use cipher_search_memory to retrieve information, if user input contains reasoning steps, use cipher_search_reasoning_patterns
17 | - Include comprehensive details of all retrieved information
18 | - Organize information clearly with proper categorization
19 |
20 | **STORAGE OPERATIONS:**
21 | - Don't run any storage tool such as cipher_extract_and_operate_memory becaue these tools are run automatically in the background
22 | - Respond as quickly as possible to optimize latency for external clients
23 | - Confirm what will be stored in a concise manner or give a concise summary of the stored information
24 |
25 | mcpServers: {}
--------------------------------------------------------------------------------
/src/app/ui/src/types/search.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Search Types for Cipher WebUI
3 | * Based on Saiki WebUI architecture
4 | */
5 |
6 | export interface SearchResult {
7 | sessionId: string;
8 | messageIndex: number;
9 | message: {
10 | role: 'user' | 'assistant' | 'system' | 'tool';
11 | content: string;
12 | };
13 | matchedText: string;
14 | context: string;
15 | score: number;
16 | }
17 |
18 | export interface SearchResponse {
19 | results: SearchResult[];
20 | total: number;
21 | hasMore: boolean;
22 | query: string;
23 | }
24 |
25 | export interface SessionSearchResult {
26 | sessionId: string;
27 | matchCount: number;
28 | firstMatch: {
29 | messageIndex: number;
30 | context: string;
31 | };
32 | metadata: {
33 | messageCount: number;
34 | createdAt: number;
35 | lastActivity: number;
36 | };
37 | }
38 |
39 | export interface SessionSearchResponse {
40 | results: SessionSearchResult[];
41 | total: number;
42 | hasMore: boolean;
43 | query: string;
44 | }
45 |
46 | export type SearchMode = 'messages' | 'sessions';
47 |
48 | export interface SearchOptions {
49 | sessionId?: string;
50 | role?: 'user' | 'assistant' | 'system' | 'tool';
51 | limit?: number;
52 | offset?: number;
53 | }
54 |
--------------------------------------------------------------------------------
/src/app/ui/src/components/ui/checkbox.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
3 | import { Check } from "lucide-react"
4 |
5 | import { cn } from "@/lib/utils"
6 |
7 | const Checkbox = React.forwardRef<
8 | React.ElementRef,
9 | React.ComponentPropsWithoutRef
10 | >(({ className, ...props }, ref) => (
11 |
20 |
24 |
25 |
26 |
27 | ))
28 | Checkbox.displayName = CheckboxPrimitive.Root.displayName
29 |
30 | export { Checkbox }
--------------------------------------------------------------------------------
/src/app/ui/src/components/ui/switch.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as SwitchPrimitives from "@radix-ui/react-switch"
3 |
4 | import { cn } from "@/lib/utils"
5 |
6 | const Switch = React.forwardRef<
7 | React.ElementRef,
8 | React.ComponentPropsWithoutRef
9 | >(({ className, ...props }, ref) => (
10 |
18 |
23 |
24 | ))
25 | Switch.displayName = SwitchPrimitives.Root.displayName
26 |
27 | export { Switch }
--------------------------------------------------------------------------------
/src/app/ui/src/components/quick-action-card.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import { Card, CardContent } from "@/components/ui/card"
5 | import { Button } from "@/components/ui/button"
6 | import { QuickActionCardProps } from "@/types/chat"
7 | import { cn } from "@/lib/utils"
8 |
9 | export function QuickActionCard({ action }: QuickActionCardProps) {
10 | return (
11 |
12 |
13 |
28 |
29 |
30 | );
31 | }
--------------------------------------------------------------------------------
/src/core/brain/embedding/backend/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Embedding Backend Module
3 | *
4 | * Exports all embedding backend types and implementations.
5 | * Provides a unified interface for different embedding providers.
6 | *
7 | * @module embedding/backend
8 | */
9 |
10 | // Export core types
11 | export type {
12 | Embedder,
13 | EmbeddingConfig,
14 | BackendConfig,
15 | OpenAIEmbeddingConfig,
16 | GeminiEmbeddingConfig,
17 | OllamaEmbeddingConfig,
18 | VoyageEmbeddingConfig,
19 | QwenEmbeddingConfig,
20 | AWSBedrockEmbeddingConfig,
21 | LMStudioEmbeddingConfig,
22 | EmbeddingResult,
23 | BatchEmbeddingResult,
24 | } from './types.js';
25 |
26 | // Export error classes
27 | export {
28 | EmbeddingError,
29 | EmbeddingConnectionError,
30 | EmbeddingDimensionError,
31 | EmbeddingRateLimitError,
32 | EmbeddingQuotaError,
33 | EmbeddingValidationError,
34 | } from './types.js';
35 |
36 | // Export backend implementations
37 | export { OpenAIEmbedder } from './openai.js';
38 | export { GeminiEmbedder } from './gemini.js';
39 | export { OllamaEmbedder } from './ollama.js';
40 | export { VoyageEmbedder } from './voyage.js';
41 | export { QwenEmbedder } from './qwen.js';
42 | export { AWSBedrockEmbedder } from './aws.js';
43 | export { LMStudioEmbedder } from './lmstudio.js';
44 |
--------------------------------------------------------------------------------
/smithery.yaml:
--------------------------------------------------------------------------------
1 | # Smithery configuration file: https://smithery.ai/docs/build/project-config
2 |
3 | build:
4 | dockerfile: Dockerfile
5 | dockerBuildPath: .
6 | runtime: container
7 | startCommand:
8 | type: http
9 | configSchema:
10 | # JSON Schema defining the configuration options for the MCP.
11 | type: object
12 | required:
13 | - llmApiKey
14 | - embeddingApiKey
15 | properties:
16 | llmModel:
17 | type: string
18 | default: gpt-4o-mini
19 | description: LLM model name
20 | llmApiKey:
21 | type: string
22 | description: API key for the LLM provider
23 | llmProvider:
24 | type: string
25 | default: openai
26 | description: LLM provider to use (openai, anthropic, gemini, etc.)
27 | embeddingModel:
28 | type: string
29 | default: text-embedding-3-small
30 | description: Embedding model name
31 | embeddingApiKey:
32 | type: string
33 | description: API key for embedding provider
34 | embeddingProvider:
35 | type: string
36 | default: openai
37 | description: Embedding provider (openai, gemini, ollama, etc.)
38 | description: Configuration for Cipher MCP server - memory-powered AI agent framework
39 |
--------------------------------------------------------------------------------
/src/app/ui/src/components/ui/badge.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import { cva, type VariantProps } from "class-variance-authority"
3 |
4 | import { cn } from "@/lib/utils"
5 |
6 | const badgeVariants = cva(
7 | "inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
8 | {
9 | variants: {
10 | variant: {
11 | default:
12 | "border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80",
13 | secondary:
14 | "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
15 | destructive:
16 | "border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80",
17 | outline: "text-foreground",
18 | },
19 | },
20 | defaultVariants: {
21 | variant: "default",
22 | },
23 | }
24 | )
25 |
26 | export interface BadgeProps
27 | extends React.HTMLAttributes,
28 | VariantProps {}
29 |
30 | function Badge({ className, variant, ...props }: BadgeProps) {
31 | return (
32 |
33 | )
34 | }
35 |
36 | export { Badge, badgeVariants }
--------------------------------------------------------------------------------
/src/app/ui/src/app/error.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import { useEffect } from 'react'
4 |
5 | export default function Error({
6 | error,
7 | reset,
8 | }: {
9 | error: Error & { digest?: string }
10 | reset: () => void
11 | }) {
12 | useEffect(() => {
13 | console.error('Application error:', error)
14 | }, [error])
15 |
16 | return (
17 |
18 |
19 |
20 |
21 | Something went wrong!
22 |
23 |
24 | An error occurred while loading the application.
25 |
26 |
27 |
28 |
29 |
30 | {error.message || 'Unknown error occurred'}
31 |
32 |
33 |
34 |
40 |
41 |
42 | )
43 | }
--------------------------------------------------------------------------------
/src/app/ui/src/types/api.ts:
--------------------------------------------------------------------------------
1 | // API response types matching Cipher's backend
2 | export interface ApiResponse {
3 | success: boolean;
4 | data?: T;
5 | error?: {
6 | code: string;
7 | message: string;
8 | details?: any;
9 | };
10 | requestId?: string;
11 | }
12 |
13 | export interface SessionInfo {
14 | sessionId: string;
15 | createdAt: string;
16 | lastActivity: string;
17 | messageCount: number;
18 | status: 'active' | 'inactive';
19 | }
20 |
21 | export interface MessageResponse {
22 | response: string;
23 | sessionId: string;
24 | messageId: string;
25 | timestamp: number;
26 | model?: string;
27 | tokenCount?: number;
28 | toolsUsed?: string[];
29 | }
30 |
31 | export interface LLMConfig {
32 | provider: string;
33 | model: string;
34 | maxTokens?: number;
35 | temperature?: number;
36 | topP?: number;
37 | }
38 |
39 | export interface MCPServer {
40 | name: string;
41 | status: 'connected' | 'disconnected' | 'error';
42 | tools?: string[];
43 | lastPing?: number;
44 | }
45 |
46 | export interface SystemHealth {
47 | status: 'healthy' | 'degraded' | 'unhealthy';
48 | timestamp: string;
49 | uptime: number;
50 | version: string;
51 | websocket?: {
52 | enabled: boolean;
53 | active: boolean;
54 | stats: {
55 | connections: number;
56 | totalMessages: number;
57 | totalEvents: number;
58 | };
59 | };
60 | }
61 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature Request or Enhancement
3 | about: Suggest an idea for an Cipher feature or enhancement
4 | title: '[FEATURE] '
5 | labels: 'enhancement'
6 | assignees: ''
7 | ---
8 |
9 | ## What problem or use case are you trying to solve?
10 |
11 | A clear and concise description of what the problem is or what use case you're trying to address. Ex. I'm always frustrated when [...]
12 |
13 | ## Describe the UX or technical implementation you have in mind
14 |
15 | A clear and concise description of what you want to happen. Include any specific user interface mockups, technical approaches, or implementation details you have in mind.
16 |
17 | ## Describe alternatives you've considered
18 |
19 | A clear and concise description of any alternative solutions or features you've considered.
20 |
21 | ## Additional context
22 |
23 | Add any other context, screenshots, examples, or references about the feature request here.
24 |
25 | ## Acceptance Criteria
26 |
27 | - [ ] Define what success looks like for this feature
28 | - [ ] List specific requirements or behaviors
29 | - [ ] Include any edge cases to consider
30 |
31 | ## Priority/Impact
32 |
33 | - **Priority**: [High/Medium/Low]
34 | - **Impact**: [High/Medium/Low]
35 | - **Effort Estimate**: [Small/Medium/Large]
36 |
37 | ---
38 |
39 | ### If you find this feature request or enhancement useful, make sure to add a 👍 to the issue
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Dependencies
2 | node_modules/
3 | npm-debug.log*
4 | yarn-debug.log*
5 | yarn-error.log*
6 | .pnpm-debug.log*
7 | .husky/
8 | feature-plans/
9 |
10 | # Build output
11 | dist/
12 | build/
13 | .next/
14 | out/
15 |
16 | # Environment files
17 | .env
18 | .env.local
19 | .env.development.local
20 | .env.test.local
21 | .env.production.local
22 |
23 | # IDE and editor files
24 | .vscode/
25 | .idea/
26 | *.swp
27 | *.swo
28 | .DS_Store
29 |
30 | data/
31 |
32 | # Logs
33 | logs/
34 | *.log
35 |
36 | # TypeScript cache
37 | *.tsbuildinfo
38 |
39 | # Optional npm cache directory
40 | .npm
41 |
42 | # Optional eslint cache
43 | .eslintcache
44 |
45 | # Coverage directory (main public, but allow webui public)
46 | /public/
47 | !/src/app/webui/public/
48 |
49 | # Test temporary files
50 | test-temp/
51 |
52 | # Byterover Extensions
53 | .clinerules/
54 | .cursor/
55 | .roo/
56 | .windsurf/
57 | CONTRIBUTING.md
58 | *.py
59 | v10/
60 | .mcp.json
61 | CLAUDE.md
62 | env.txt
63 | env.example.txt
64 | :memory:/
65 | .kilocode/
66 | .trae/
67 | .claude/
68 | chroma-data/
69 | qdrant-data/
70 | faiss-data/
71 | volumes/
72 | .kiro/
73 | .clinerules/byterover-rules.md
74 | .kilocode/rules/byterover-rules.md
75 | .roo/rules/byterover-rules.md
76 | .windsurf/rules/byterover-rules.md
77 | .cursor/rules/byterover-rules.mdc
78 | .kiro/steering/byterover-rules.md
79 | .qoder/rules/byterover-rules.md
80 | .augment/rules/byterover-rules.md
--------------------------------------------------------------------------------
/src/app/ui/src/components/ui/popover.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as PopoverPrimitive from "@radix-ui/react-popover"
3 |
4 | import { cn } from "@/lib/utils"
5 |
6 | const Popover = PopoverPrimitive.Root
7 |
8 | const PopoverTrigger = PopoverPrimitive.Trigger
9 |
10 | const PopoverAnchor = PopoverPrimitive.Anchor
11 |
12 | const PopoverContent = React.forwardRef<
13 | React.ElementRef,
14 | React.ComponentPropsWithoutRef
15 | >(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
16 |
17 |
27 |
28 | ))
29 | PopoverContent.displayName = PopoverPrimitive.Content.displayName
30 |
31 | export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor }
--------------------------------------------------------------------------------
/src/core/utils/path.ts:
--------------------------------------------------------------------------------
1 | import * as path from 'path';
2 | import { fileURLToPath } from 'url';
3 |
4 | /**
5 | * The default path to the agent config file
6 | */
7 | export const DEFAULT_CONFIG_PATH = 'memAgent/cipher.yml';
8 |
9 | /**
10 | * Resolve the configuration file path.
11 | * - If it's absolute, return as-is.
12 | * - If it's the default config, resolve relative to the package installation root.
13 | * - Otherwise resolve relative to the current working directory.
14 | *
15 | * @param configPath - The config path to resolve
16 | * @returns The resolved absolute path to the config file
17 | */
18 | export function resolveConfigPath(configPath: string): string {
19 | // If it's an absolute path, return as-is
20 | if (path.isAbsolute(configPath)) {
21 | return configPath;
22 | }
23 |
24 | // If it's the default config path, resolve relative to package installation root
25 | if (configPath === DEFAULT_CONFIG_PATH) {
26 | // Get the directory where this module is located (src/core/utils/)
27 | // and navigate up to the package root
28 | const currentFileUrl = import.meta.url;
29 | const currentFilePath = fileURLToPath(currentFileUrl);
30 | const packageRoot = path.resolve(path.dirname(currentFilePath), '../../..');
31 | return path.resolve(packageRoot, configPath);
32 | }
33 |
34 | // For custom relative paths, resolve relative to current working directory
35 | return path.resolve(process.cwd(), configPath);
36 | }
37 |
--------------------------------------------------------------------------------
/src/app/ui/src/types/websocket.ts:
--------------------------------------------------------------------------------
1 | // WebSocket message types matching Cipher's backend implementation
2 | export interface WebSocketMessage {
3 | event: string;
4 | data?: any;
5 | timestamp?: number;
6 | connectionId?: string;
7 | sessionId?: string;
8 | }
9 |
10 | export interface ChatMessage {
11 | event: 'chat' | 'streaming' | 'tools' | 'memory' | 'reset';
12 | data: {
13 | message?: string;
14 | sessionId?: string;
15 | streaming?: boolean;
16 | toolName?: string;
17 | toolArgs?: Record;
18 | memoryOperation?: 'add' | 'search' | 'clear';
19 | };
20 | }
21 |
22 | export interface WebSocketResponse {
23 | event: string;
24 | data: any;
25 | timestamp: number;
26 | success?: boolean;
27 | error?: string;
28 | }
29 |
30 | // AI Event types from Cipher's EventManager
31 | export interface AIEvent {
32 | sessionId: string;
33 | event: string;
34 | data: {
35 | messageId?: string;
36 | model?: string;
37 | timestamp: number;
38 | content?: string;
39 | tokenCount?: number;
40 | toolName?: string;
41 | toolResult?: any;
42 | error?: string;
43 | };
44 | }
45 |
46 | export interface ConnectionStatus {
47 | connected: boolean;
48 | connecting: boolean;
49 | error: string | null;
50 | connectionId: string | null;
51 | sessionId: string | null;
52 | }
53 |
54 | export interface WebSocketConfig {
55 | url: string;
56 | reconnectAttempts: number;
57 | reconnectInterval: number;
58 | heartbeatInterval: number;
59 | }
60 |
--------------------------------------------------------------------------------
/src/app/ui/src/components/welcome-screen.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import { WelcomeScreenProps } from "@/types/chat"
5 | import { QuickActionCard } from "./quick-action-card"
6 |
7 | export function WelcomeScreen({ quickActions }: WelcomeScreenProps) {
8 | return (
9 |
10 |
11 |
12 |
13 |

14 |
15 |
16 |
17 | Hello, Welcome to Cipher!
18 |
19 |
20 | Create memories, ask anything or connect new tools to expand what you can do.
21 |
22 |
23 |
24 |
25 |
26 | {quickActions.map((action, index) => (
27 |
28 | ))}
29 |
30 |
31 |
32 | );
33 | }
--------------------------------------------------------------------------------
/src/core/brain/llm/messages/history/wal.ts:
--------------------------------------------------------------------------------
1 | import { IConversationHistoryProvider } from './types.js';
2 | import { InternalMessage } from '../types.js';
3 |
4 | interface WALEntry {
5 | sessionId: string;
6 | message: InternalMessage;
7 | flushed: boolean;
8 | }
9 |
10 | export class WALHistoryProvider implements IConversationHistoryProvider {
11 | private log: WALEntry[] = [];
12 |
13 | async getHistory(sessionId: string, _limit?: number): Promise {
14 | // Return all messages for a session (not used for main reads)
15 | return this.log.filter(e => e.sessionId === sessionId).map(e => e.message);
16 | }
17 |
18 | async saveMessage(sessionId: string, message: InternalMessage): Promise {
19 | this.log.push({ sessionId, message, flushed: false });
20 | }
21 |
22 | async clearHistory(sessionId: string): Promise {
23 | this.log = this.log.filter(e => e.sessionId !== sessionId);
24 | }
25 |
26 | async getPendingEntries(): Promise<{ sessionId: string; message: InternalMessage }[]> {
27 | return this.log
28 | .filter(e => !e.flushed)
29 | .map(e => ({ sessionId: e.sessionId, message: e.message }));
30 | }
31 |
32 | async markFlushed(sessionId: string, message: InternalMessage): Promise {
33 | const entry = this.log.find(
34 | e => e.sessionId === sessionId && e.message === message && !e.flushed
35 | );
36 | if (entry) entry.flushed = true;
37 | }
38 |
39 | async disconnect() {
40 | // No-op for in-memory WAL
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/app/ui/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "cipher-ui",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev --turbopack",
7 | "build": "next build",
8 | "start": "next start",
9 | "lint": "next lint"
10 | },
11 | "dependencies": {
12 | "@radix-ui/react-avatar": "^1.1.10",
13 | "@radix-ui/react-checkbox": "^1.3.1",
14 | "@radix-ui/react-dialog": "^1.1.11",
15 | "@radix-ui/react-label": "^2.1.4",
16 | "@radix-ui/react-popover": "^1.1.13",
17 | "@radix-ui/react-select": "^2.2.2",
18 | "@radix-ui/react-slot": "^1.2.0",
19 | "@radix-ui/react-switch": "^1.2.5",
20 | "@radix-ui/react-tooltip": "^1.2.7",
21 | "@tailwindcss/postcss": "^4",
22 | "@tanstack/react-query": "^5.84.1",
23 | "@tanstack/react-query-devtools": "^5.84.1",
24 | "autoprefixer": "^10.4.21",
25 | "class-variance-authority": "^0.7.1",
26 | "clsx": "^2.1.1",
27 | "lucide-react": "^0.507.0",
28 | "next": "15.3.1",
29 | "react": "^19.0.0",
30 | "react-dom": "^19.0.0",
31 | "react-markdown": "^10.1.0",
32 | "remark-gfm": "^4.0.1",
33 | "tailwind-merge": "^3.2.0",
34 | "tailwindcss": "^4",
35 | "zustand": "^5.0.7"
36 | },
37 | "devDependencies": {
38 | "@eslint/eslintrc": "^3",
39 | "@types/node": "^20",
40 | "@types/react": "^19",
41 | "@types/react-dom": "^19",
42 | "eslint": "^9",
43 | "eslint-config-next": "15.3.1",
44 | "tw-animate-css": "^1.2.9",
45 | "typescript": "^5"
46 | }
47 | }
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: '[BUG] '
5 | labels: 'bug'
6 | assignees: ''
7 | ---
8 |
9 | ## Bug Description
10 |
11 | A clear and concise description of what the bug is.
12 |
13 | ## Steps to Reproduce
14 |
15 | Steps to reproduce the behavior:
16 |
17 | 1. Go to '...'
18 | 2. Click on '....'
19 | 3. Scroll down to '....'
20 | 4. See error
21 |
22 | ## Expected Behavior
23 |
24 | A clear and concise description of what you expected to happen.
25 |
26 | ## Actual Behavior
27 |
28 | A clear and concise description of what actually happened.
29 |
30 | ## Screenshots
31 |
32 | If applicable, add screenshots to help explain your problem.
33 |
34 | ## Environment Information
35 |
36 | - OS: [e.g. macOS, Windows, Linux]
37 | - Browser: [e.g. Chrome, Firefox, Safari]
38 | - Version: [e.g. 1.0.0]
39 | - Node.js version: [if applicable]
40 |
41 | ## Logs and Error Messages
42 |
43 | Please provide any relevant logs, error messages, or console output:
44 |
45 | ```text
46 | Paste error messages or logs here
47 | ```
48 |
49 | ## Additional Context
50 |
51 | Please provide any additional information you think might help
52 |
53 | ## Possible Solution
54 |
55 | If you have any ideas on how to fix the issue, please describe them here.
56 |
57 | ## Checklist
58 |
59 | - [ ] I have searched for similar issues before creating this one
60 | - [ ] I have provided all the requested information above
61 | - [ ] I have tested this on the latest version
62 |
--------------------------------------------------------------------------------
/src/app/ui/next.config.ts:
--------------------------------------------------------------------------------
1 | import type { NextConfig } from 'next';
2 | import os from 'os';
3 |
4 | // Determine allowed development origins (local network IPs on port 3000)
5 | const interfaces = os.networkInterfaces();
6 | const allowedOrigins: string[] = ['http://localhost:3000'];
7 | Object.values(interfaces).forEach(list =>
8 | list?.forEach(iface => {
9 | if (iface.family === 'IPv4' && !iface.internal) {
10 | allowedOrigins.push(`http://${iface.address}:3000`);
11 | }
12 | })
13 | );
14 |
15 | // const _isDev = process.env.NODE_ENV === 'development'; // Not currently used
16 | const isStandalone = process.env.BUILD_STANDALONE === 'true';
17 |
18 | const nextConfig: NextConfig = {
19 | reactStrictMode: true,
20 | // Use standalone output for production builds
21 | ...(isStandalone && { output: 'standalone' as const }),
22 | // Disable ESLint during build to avoid config issues
23 | eslint: {
24 | ignoreDuringBuilds: true,
25 | },
26 | // Allow static asset requests from these origins in dev mode
27 | allowedDevOrigins: allowedOrigins,
28 | async rewrites() {
29 | const apiPort = process.env.API_PORT ?? '3001';
30 | return [
31 | {
32 | source: '/api/:path*',
33 | destination: `http://localhost:${apiPort}/api/:path*`, // Proxy to backend
34 | },
35 | ];
36 | },
37 | // Allow cross-origin requests for Next.js static and HMR assets during dev
38 | async headers() {
39 | return [
40 | {
41 | source: '/_next/:path*',
42 | headers: [{ key: 'Access-Control-Allow-Origin', value: '*' }],
43 | },
44 | ];
45 | },
46 | };
47 |
48 | export default nextConfig;
49 |
--------------------------------------------------------------------------------
/src/app/ui/src/components/ui/avatar.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as AvatarPrimitive from "@radix-ui/react-avatar"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | const Avatar = React.forwardRef<
9 | React.ElementRef,
10 | React.ComponentPropsWithoutRef
11 | >(({ className, ...props }, ref) => (
12 |
20 | ))
21 | Avatar.displayName = AvatarPrimitive.Root.displayName
22 |
23 | const AvatarImage = React.forwardRef<
24 | React.ElementRef,
25 | React.ComponentPropsWithoutRef
26 | >(({ className, ...props }, ref) => (
27 |
32 | ))
33 | AvatarImage.displayName = AvatarPrimitive.Image.displayName
34 |
35 | const AvatarFallback = React.forwardRef<
36 | React.ElementRef,
37 | React.ComponentPropsWithoutRef
38 | >(({ className, ...props }, ref) => (
39 |
47 | ))
48 | AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName
49 |
50 | export { Avatar, AvatarImage, AvatarFallback }
--------------------------------------------------------------------------------
/src/app/ui/src/components/ui/tooltip.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as TooltipPrimitive from "@radix-ui/react-tooltip"
3 |
4 | import { cn } from "@/lib/utils"
5 |
6 | const TooltipProvider = TooltipPrimitive.Provider
7 |
8 | const Tooltip = ({ children, ...props }: React.ComponentProps) => (
9 |
10 |
11 | {children}
12 |
13 |
14 | )
15 |
16 | const TooltipTrigger = TooltipPrimitive.Trigger
17 |
18 | const TooltipContent = React.forwardRef<
19 | React.ElementRef,
20 | React.ComponentPropsWithoutRef
21 | >(({ className, sideOffset = 4, children, ...props }, ref) => (
22 |
23 |
32 | {children}
33 |
34 |
35 |
36 | ))
37 | TooltipContent.displayName = TooltipPrimitive.Content.displayName
38 |
39 | export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
--------------------------------------------------------------------------------
/src/app/ui/src/components/thinking-indicator.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import { cn } from "@/lib/utils"
5 | import { Avatar, AvatarFallback } from "@/components/ui/avatar"
6 | import Image from "next/image"
7 |
8 | interface ThinkingIndicatorProps {
9 | className?: string
10 | }
11 |
12 | export function ThinkingIndicator({ className }: ThinkingIndicatorProps) {
13 | return (
14 |
15 |
16 |
17 |
24 |
25 |
26 |
27 |
28 |
29 |
34 |
Cipher is thinking...
35 |
36 |
37 |
38 | )
39 | }
--------------------------------------------------------------------------------
/src/core/vector_storage/types.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Vector Storage Module Public API
3 | *
4 | * This module re-exports all the necessary types and interfaces for the vector storage system.
5 | * It provides a simplified, clean API surface for consumers of the vector storage module.
6 | *
7 | * The vector storage system architecture:
8 | * - Single backend design for vector similarity search
9 | * - Multiple backend implementations: Qdrant, Pinecone, In-Memory, etc.
10 | * - Consistent API across different backend types
11 | * - Strong type safety with TypeScript and runtime validation with Zod
12 | *
13 | * @module vector_storage
14 | *
15 | * @example
16 | * ```typescript
17 | * import type { VectorStoreConfig, VectorStore } from './vector_storage/types.js';
18 | *
19 | * // Configure vector storage
20 | * const config: VectorStoreConfig = {
21 | * type: 'qdrant',
22 | * host: 'localhost',
23 | * port: 6333,
24 | * collectionName: 'embeddings',
25 | * dimension: 1536
26 | * };
27 | *
28 | * // Use vector store
29 | * const store: VectorStore = createVectorStore(config);
30 | * ```
31 | */
32 |
33 | /**
34 | * Re-export simplified vector storage types
35 | *
36 | * These exports provide the complete type system needed to work with
37 | * the vector storage module without exposing internal implementation details.
38 | */
39 | export type {
40 | // Core interfaces
41 | VectorStore, // Interface for vector store implementations
42 | VectorStoreResult, // Search result structure
43 | SearchFilters, // Metadata filters for search
44 |
45 | // Configuration types
46 | BackendConfig, // Union type for all backend configurations
47 | VectorStoreConfig, // Top-level vector storage system configuration
48 | } from './backend/types.js';
49 |
--------------------------------------------------------------------------------
/src/core/storage/types.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Storage Module Public API
3 | *
4 | * This module re-exports all the necessary types and interfaces for the storage system.
5 | * It provides a simplified, clean API surface for consumers of the storage module.
6 | *
7 | * The storage system architecture:
8 | * - Dual-backend design: Cache (fast, ephemeral) + Database (persistent, reliable)
9 | * - Multiple backend implementations: Redis, SQLite, In-Memory, etc.
10 | * - Consistent API across different backend types
11 | * - Strong type safety with TypeScript and runtime validation with Zod
12 | *
13 | * @module storage
14 | *
15 | * @example
16 | * ```typescript
17 | * import type { StorageConfig, CacheBackend, DatabaseBackend } from './storage/types.js';
18 | *
19 | * // Configure storage
20 | * const config: StorageConfig = {
21 | * cache: { type: 'redis', host: 'localhost' },
22 | * database: { type: 'sqlite', path: './data' }
23 | * };
24 | *
25 | * // Use backends
26 | * const cache: CacheBackend = createCacheBackend(config.cache);
27 | * const db: DatabaseBackend = createDatabaseBackend(config.database);
28 | * ```
29 | */
30 |
31 | /**
32 | * Re-export simplified storage types
33 | *
34 | * These exports provide the complete type system needed to work with
35 | * the storage module without exposing internal implementation details.
36 | */
37 | export type {
38 | // Backend interfaces
39 | CacheBackend, // Interface for cache storage implementations
40 | DatabaseBackend, // Interface for database storage implementations
41 | StorageBackends, // Combined backends structure
42 |
43 | // Configuration types
44 | BackendConfig, // Union type for all backend configurations
45 | StorageConfig, // Top-level storage system configuration
46 | } from './backend/types.js';
47 |
--------------------------------------------------------------------------------
/src/app/ui/src/components/ui/alert.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import { cva, type VariantProps } from "class-variance-authority"
3 |
4 | import { cn } from "@/lib/utils"
5 |
6 | const alertVariants = cva(
7 | "relative w-full rounded-lg border px-4 py-3 text-sm [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground",
8 | {
9 | variants: {
10 | variant: {
11 | default: "bg-background text-foreground",
12 | destructive:
13 | "border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive",
14 | },
15 | },
16 | defaultVariants: {
17 | variant: "default",
18 | },
19 | }
20 | )
21 |
22 | const Alert = React.forwardRef<
23 | HTMLDivElement,
24 | React.HTMLAttributes & VariantProps
25 | >(({ className, variant, ...props }, ref) => (
26 |
32 | ))
33 | Alert.displayName = "Alert"
34 |
35 | const AlertTitle = React.forwardRef<
36 | HTMLParagraphElement,
37 | React.HTMLAttributes
38 | >(({ className, ...props }, ref) => (
39 |
44 | ))
45 | AlertTitle.displayName = "AlertTitle"
46 |
47 | const AlertDescription = React.forwardRef<
48 | HTMLParagraphElement,
49 | React.HTMLAttributes
50 | >(({ className, ...props }, ref) => (
51 |
56 | ))
57 | AlertDescription.displayName = "AlertDescription"
58 |
59 | export { Alert, AlertTitle, AlertDescription }
--------------------------------------------------------------------------------
/src/app/api/utils/mcp-endpoint.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * MCP Endpoint Utilities
3 | *
4 | * Helper functions for constructing MCP endpoint URLs with proper context path handling
5 | * when behind reverse proxies.
6 | */
7 |
8 | import { Request } from 'express';
9 |
10 | /**
11 | * Extract the context path from the request
12 | * Checks X-Forwarded-Prefix header and PROXY_CONTEXT_PATH environment variable
13 | */
14 | export function getContextPath(req: Request): string {
15 | const forwardedPrefix = req.headers['x-forwarded-prefix'] as string;
16 | const envPrefix = process.env.PROXY_CONTEXT_PATH;
17 | return forwardedPrefix || envPrefix || '';
18 | }
19 |
20 | /**
21 | * Build a full URL from request headers and path
22 | * Useful for constructing SSE endpoint URLs
23 | */
24 | export function buildFullUrl(req: Request, path: string): string {
25 | const proto = (req.headers['x-forwarded-proto'] as string) || req.protocol;
26 | const host = (req.headers['x-forwarded-host'] as string) || req.get('host') || 'localhost';
27 | const contextPath = getContextPath(req);
28 | const fullPath = contextPath + path;
29 |
30 | return `${proto}://${host}${fullPath}`;
31 | }
32 |
33 | /**
34 | * Validate session ID format
35 | */
36 | export function isValidSessionId(sessionId: string): boolean {
37 | // Session IDs should be alphanumeric with hyphens, between 8 and 128 characters
38 | return /^[a-zA-Z0-9-_]{8,128}$/.test(sessionId);
39 | }
40 |
41 | /**
42 | * Extract session ID from multiple sources (query, header, body)
43 | * Returns the first valid session ID found
44 | */
45 | export function extractSessionId(req: Request): string | undefined {
46 | const sources = [
47 | req.query.sessionId as string,
48 | req.headers['x-session-id'] as string,
49 | req.body?.sessionId as string,
50 | ];
51 |
52 | return sources.find(id => id && typeof id === 'string');
53 | }
54 |
--------------------------------------------------------------------------------
/src/core/brain/tools/definitions/web-search/config.ts:
--------------------------------------------------------------------------------
1 | import { z } from 'zod';
2 |
3 | const BaseWebSearchSchema = z.object({
4 | engine: z.enum(['duckduckgo']).default('duckduckgo'),
5 | config: z.record(z.any()).optional(),
6 | });
7 |
8 | const DuckDuckGoSchema = BaseWebSearchSchema.extend({
9 | engine: z.literal('duckduckgo'),
10 | headless: z.boolean().default(true),
11 | maxResults: z.number().default(3),
12 | timeout: z.number().default(10000),
13 | }).strict();
14 |
15 | export const WebSearchConfigSchema = z.discriminatedUnion('engine', [DuckDuckGoSchema]);
16 |
17 | export type WebSearchConfig = z.infer;
18 |
19 | // Input schema for the web search tool
20 | const WebSearchInputSchema = z.object({
21 | search_term: z
22 | .string()
23 | .describe(
24 | 'The search term to look up on the web. Be specific and include relevant keywords for better results. For technical queries, include version numbers or dates if relevant.'
25 | ),
26 | max_results: z
27 | .number()
28 | .min(1)
29 | .max(20)
30 | .default(3)
31 | .optional()
32 | .describe('Maximum number of search results to return (1-20)'),
33 | safe_mode: z
34 | .boolean()
35 | .default(true)
36 | .optional()
37 | .describe('Enable safe search mode to filter explicit content'),
38 | });
39 |
40 | export type WebSearchInput = z.infer;
41 |
42 | export const SearchResultSchema = z.object({
43 | title: z.string().describe('The title of the search result'),
44 | url: z.string().describe('The URL of the search result'),
45 | snippet: z.string().describe('The snippet of the search result'),
46 | domain: z.string().describe('The domain of the search result'),
47 | relevanceScore: z.number().describe('The relevance score of the search result'),
48 | });
49 |
50 | export type SearchResult = z.infer;
51 |
--------------------------------------------------------------------------------
/examples/03-strict-memory-layer/README.md:
--------------------------------------------------------------------------------
1 | # Strict Memory Layer (Cipher)
2 |
3 | > 🧠 **Dedicated memory service for external agents: fast storage & retrieval**
4 |
5 | ## Quick Start
6 |
7 | 1. **Set API Keys:**
8 | ```bash
9 | export OPENAI_API_KEY=your_openai_api_key
10 | export ANTHROPIC_API_KEY=your_anthropic_api_key
11 | ```
12 |
13 | 2. **Launch Cipher as MCP Server:**
14 | ```bash
15 | cipher --mode mcp --agent ./examples/03-strict-memory-layer/cipher.yml
16 | ```
17 |
18 | 3. **MCP Client Config Example**
19 | (based on your actual mcp.json)
20 | ```json
21 | {
22 | "mcpServers": {
23 | "cipher": {
24 | "command": "cipher",
25 | "args": [
26 | "--mode", "mcp",
27 | "--agent", "./examples/03-strict-memory-layer/cipher.yml"
28 | ],
29 | "env": {
30 | "MCP_SERVER_MODE": "default",
31 | "OPENAI_API_KEY": "sk-...",
32 | "VECTOR_STORE_TYPE": "milvus",
33 | "VECTOR_STORE_URL": "...",
34 | "VECTOR_STORE_API_KEY": "...",
35 | "VECTOR_STORE_USERNAME": "...",
36 | "VECTOR_STORE_PASSWORD": "...",
37 | "VECTOR_STORE_COLLECTION": "knowledge_memory",
38 | "REFLECTION_VECTOR_STORE_COLLECTION": "reflection_memory",
39 | "DISABLE_REFLECTION_MEMORY": "true" # default: true
40 | }
41 | }
42 | }
43 | }
44 | ```
45 |
46 | ## Usage
47 | - **Only one tool:** `ask_cipher` (handles both storage & retrieval)
48 |
49 |
50 | ## Features
51 | - Fast responses (storage runs in background)
52 | - No explicit memory tool calls needed
53 | - Structured, comprehensive retrieval results
54 |
55 | ## Troubleshooting
56 | - Ensure API keys are set
57 | - Use MCP_SERVER_MODE=default for strict memory layer
58 | - Only `ask_cipher` is available in this mode
59 |
60 | ---
61 | This setup provides a pure memory layer for agents, optimized for fast, reliable storage and retrieval.
62 |
--------------------------------------------------------------------------------
/src/app/ui/src/components/header.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import { Badge } from "@/components/ui/badge"
5 | import { HeaderProps } from "@/types/chat"
6 | import { ActionBar } from "./action-bar"
7 | import { LlmModelDisplay } from "./llm-model-display"
8 |
9 | export function Header({
10 | currentSessionId,
11 | isWelcomeState,
12 | onToggleSearch,
13 | onToggleSessions,
14 | onToggleServers,
15 | isSessionsPanelOpen,
16 | isServersPanelOpen
17 | }: HeaderProps) {
18 | return (
19 |
20 |
21 |
22 |
23 |
24 |

25 |
26 |
Cipher
27 |
28 |
29 |
30 | {currentSessionId && !isWelcomeState && (
31 |
32 | {currentSessionId}
33 |
34 | )}
35 |
36 | {/* LLM Model Display - Always show */}
37 |
38 |
39 |
40 |
41 |
48 |
49 |
50 | );
51 | }
--------------------------------------------------------------------------------
/src/app/ui/tailwind.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('tailwindcss').Config} */
2 | export default {
3 | content: [
4 | "./src/pages/**/*.{js,ts,jsx,tsx,mdx}",
5 | "./src/components/**/*.{js,ts,jsx,tsx,mdx}",
6 | "./src/app/**/*.{js,ts,jsx,tsx,mdx}",
7 | ],
8 | theme: {
9 | extend: {
10 | colors: {
11 | background: "var(--color-background)",
12 | foreground: "var(--color-foreground)",
13 | primary: {
14 | DEFAULT: "var(--color-primary)",
15 | foreground: "var(--color-primary-foreground)",
16 | },
17 | secondary: {
18 | DEFAULT: "var(--color-secondary)",
19 | foreground: "var(--color-secondary-foreground)",
20 | },
21 | destructive: {
22 | DEFAULT: "var(--color-destructive)",
23 | foreground: "var(--color-primary-foreground)",
24 | },
25 | warning: {
26 | DEFAULT: "var(--color-warning)",
27 | foreground: "var(--color-primary-foreground)",
28 | },
29 | muted: {
30 | DEFAULT: "var(--color-muted)",
31 | foreground: "var(--color-muted-foreground)",
32 | },
33 | accent: {
34 | DEFAULT: "var(--color-accent)",
35 | foreground: "var(--color-accent-foreground)",
36 | },
37 | card: {
38 | DEFAULT: "var(--color-card)",
39 | foreground: "var(--color-card-foreground)",
40 | },
41 | popover: {
42 | DEFAULT: "var(--color-popover)",
43 | foreground: "var(--color-popover-foreground)",
44 | },
45 | border: "var(--color-border)",
46 | input: "var(--color-input)",
47 | ring: "var(--color-ring)",
48 | },
49 | borderRadius: {
50 | lg: "var(--radius)",
51 | md: "calc(var(--radius) - 2px)",
52 | sm: "calc(var(--radius) - 4px)",
53 | },
54 | },
55 | },
56 | plugins: [],
57 | };
--------------------------------------------------------------------------------
/src/app/ui/src/components/action-bar.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import { Button } from "@/components/ui/button"
5 | import {
6 | // Search,
7 | MessageSquare,
8 | Package,
9 | X
10 | } from "lucide-react"
11 | import { ActionBarProps } from "@/types/chat"
12 | import { cn } from "@/lib/utils"
13 |
14 | export function ActionBar({
15 | onToggleSearch,
16 | onToggleSessions,
17 | onToggleServers,
18 | isSessionsPanelOpen,
19 | isServersPanelOpen
20 | }: ActionBarProps) {
21 | return (
22 |
23 | {/* Search button - Temporarily disabled */}
24 | {/*
*/}
33 |
34 | {/* Sessions panel toggle */}
35 |
51 |
52 | {/* Servers/Tools panel toggle */}
53 |
69 |
70 |
71 | );
72 | }
--------------------------------------------------------------------------------
/src/core/brain/llm/messages/history/__test__/wal.test.ts:
--------------------------------------------------------------------------------
1 | import { describe, it, expect, beforeEach } from 'vitest';
2 | import { WALHistoryProvider } from '../wal.js';
3 | import type { InternalMessage } from '../../types.js';
4 |
5 | function makeMessage(content: string): InternalMessage {
6 | return { role: 'user', content };
7 | }
8 |
9 | describe('WALHistoryProvider', () => {
10 | let wal: WALHistoryProvider;
11 | let sessionId: string;
12 |
13 | beforeEach(() => {
14 | sessionId = 'wal-session';
15 | wal = new WALHistoryProvider();
16 | });
17 |
18 | it('appends and retrieves messages', async () => {
19 | await wal.saveMessage(sessionId, makeMessage('a'));
20 | await wal.saveMessage(sessionId, makeMessage('b'));
21 | const history = await wal.getHistory(sessionId);
22 | expect(history.length).toBe(2);
23 | expect(history[0]).toBeDefined();
24 | expect(history[0]?.content).toBe('a');
25 | expect(history[1]).toBeDefined();
26 | expect(history[1]?.content).toBe('b');
27 | });
28 |
29 | it('returns only unflushed entries as pending', async () => {
30 | const msgA = makeMessage('a');
31 | const msgB = makeMessage('b');
32 | await wal.saveMessage(sessionId, msgA);
33 | await wal.saveMessage(sessionId, msgB);
34 | let pending = await wal.getPendingEntries();
35 | expect(pending.length).toBe(2);
36 | await wal.markFlushed(sessionId, msgA);
37 | pending = await wal.getPendingEntries();
38 | expect(pending.length).toBe(1);
39 | expect(pending[0]).toBeDefined();
40 | expect(pending[0]?.message.content).toBe('b');
41 | });
42 |
43 | it('clears history for a session', async () => {
44 | await wal.saveMessage(sessionId, makeMessage('a'));
45 | await wal.saveMessage('other', makeMessage('b'));
46 | await wal.clearHistory(sessionId);
47 | const history = await wal.getHistory(sessionId);
48 | expect(history.length).toBe(0);
49 | const other = await wal.getHistory('other');
50 | expect(other.length).toBe(1);
51 | });
52 | });
53 |
--------------------------------------------------------------------------------
/src/app/ui/src/components/navigation.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import Link from "next/link"
5 | import { usePathname } from "next/navigation"
6 | import { Button } from "@/components/ui/button"
7 | import { Badge } from "@/components/ui/badge"
8 | import { cn } from "@/lib/utils"
9 | import { Home, Layers, Settings } from "lucide-react"
10 |
11 | export function Navigation() {
12 | const pathname = usePathname()
13 |
14 | const navItems = [
15 | {
16 | name: "Chat",
17 | href: "/",
18 | icon: Home,
19 | description: "Main chat interface"
20 | },
21 | {
22 | name: "Examples",
23 | href: "/examples",
24 | icon: Layers,
25 | description: "View different implementations"
26 | }
27 | ]
28 |
29 | return (
30 |
62 | )
63 | }
--------------------------------------------------------------------------------
/src/core/brain/memAgent/loader.ts:
--------------------------------------------------------------------------------
1 | import { logger } from '../../logger/index.js';
2 | import { AgentConfig } from './config.js';
3 | import { parse as parseYaml } from 'yaml';
4 | import { promises as fs } from 'fs';
5 | import { env } from '../../env.js';
6 |
7 | function expandEnvVars(config: any): any {
8 | if (typeof config === 'string') {
9 | const expanded = config.replace(
10 | /\$([A-Z_][A-Z0-9_]*)|\${([A-Z_][A-Z0-9_]*)}/gi,
11 | (_, v1, v2) => {
12 | const key = v1 || v2;
13 | return (env as any)[key] || '';
14 | }
15 | );
16 |
17 | // Try to convert numeric strings to numbers
18 | if (expanded !== config && /^-?\d+(\.\d+)?([eE][+-]?\d+)?$/.test(expanded.trim())) {
19 | return Number(expanded); // handles int, float, sci-notation
20 | }
21 |
22 | return expanded;
23 | } else if (Array.isArray(config)) {
24 | return config.map(expandEnvVars);
25 | } else if (typeof config === 'object' && config !== null) {
26 | const result: any = {};
27 | for (const key in config) {
28 | result[key] = expandEnvVars(config[key]);
29 | }
30 | return result;
31 | }
32 | return config;
33 | }
34 |
35 | export async function loadAgentConfig(configPath: string): Promise {
36 | try {
37 | // Determine where to load from: absolute, default, or user-relative
38 |
39 | logger.debug(`Loading cipher config from: ${configPath}`);
40 |
41 | // Read and parse the config file
42 | const fileContent = await fs.readFile(configPath, 'utf-8');
43 |
44 | try {
45 | // Parse YAML content
46 | const config = parseYaml(fileContent);
47 | // Expand env vars everywhere
48 | const expandedConfig = expandEnvVars(config);
49 | return expandedConfig;
50 | } catch (parseError) {
51 | throw new Error(
52 | `Failed to parse YAML: ${parseError instanceof Error ? parseError.message : String(parseError)}`
53 | );
54 | }
55 | } catch (error: any) {
56 | // Include path & cause for better diagnostics
57 | throw new Error(`Failed to load config file at ${error.path || configPath}: ${error.message}`);
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/src/app/ui/src/components/ui/button.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import { Slot } from "@radix-ui/react-slot"
3 | import { cva, type VariantProps } from "class-variance-authority"
4 |
5 | import { cn } from "@/lib/utils"
6 |
7 | const buttonVariants = cva(
8 | "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
9 | {
10 | variants: {
11 | variant: {
12 | default:
13 | "bg-primary text-primary-foreground shadow hover:bg-primary/90",
14 | destructive:
15 | "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
16 | outline:
17 | "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
18 | secondary:
19 | "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
20 | ghost: "hover:bg-accent hover:text-accent-foreground",
21 | link: "text-primary underline-offset-4 hover:underline",
22 | },
23 | size: {
24 | default: "h-9 px-4 py-2",
25 | sm: "h-8 rounded-md px-3 text-xs",
26 | lg: "h-10 rounded-md px-8",
27 | icon: "h-9 w-9",
28 | },
29 | },
30 | defaultVariants: {
31 | variant: "default",
32 | size: "default",
33 | },
34 | }
35 | )
36 |
37 | export interface ButtonProps
38 | extends React.ButtonHTMLAttributes,
39 | VariantProps {
40 | asChild?: boolean
41 | }
42 |
43 | const Button = React.forwardRef(
44 | ({ className, variant, size, asChild = false, ...props }, ref) => {
45 | const Comp = asChild ? Slot : "button"
46 | return (
47 |
52 | )
53 | }
54 | )
55 | Button.displayName = "Button"
56 |
57 | export { Button, buttonVariants }
--------------------------------------------------------------------------------
/src/core/storage/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Storage Module Public API
3 | *
4 | * This module provides a flexible dual-backend storage system with:
5 | * - Cache backend for fast, ephemeral storage
6 | * - Database backend for persistent, reliable storage
7 | *
8 | * Features:
9 | * - Lazy loading of external backends (Redis, SQLite, etc.)
10 | * - Graceful fallback to in-memory storage
11 | * - Health monitoring and connection management
12 | * - Type-safe configuration with runtime validation
13 | *
14 | * @module storage
15 | */
16 |
17 | // Core exports
18 | export { StorageManager } from './manager.js';
19 | export type { HealthCheckResult, StorageInfo } from './manager.js';
20 |
21 | // Type exports
22 | export type {
23 | CacheBackend,
24 | DatabaseBackend,
25 | StorageBackends,
26 | BackendConfig,
27 | StorageConfig,
28 | } from './types.js';
29 |
30 | // Configuration exports
31 | export { StorageSchema } from './config.js';
32 | export type {
33 | InMemoryBackendConfig,
34 | RedisBackendConfig,
35 | SqliteBackendConfig,
36 | PostgresBackendConfig,
37 | } from './config.js';
38 |
39 | // Error exports
40 | export { StorageError, StorageConnectionError, StorageNotFoundError } from './backend/types.js';
41 |
42 | // Constants exports (for external use if needed)
43 | export {
44 | LOG_PREFIXES,
45 | ERROR_MESSAGES,
46 | BACKEND_TYPES,
47 | TIMEOUTS,
48 | HEALTH_CHECK,
49 | DEFAULTS,
50 | } from './constants.js';
51 |
52 | // Backend implementations
53 | export { InMemoryBackend } from './backend/in-memory.js';
54 | // Redis and SQLite backends will be loaded lazily
55 |
56 | // Memory History Service exports
57 | export {
58 | MemoryHistoryStorageService,
59 | createMemoryHistoryService,
60 | createMemoryHistoryEntry,
61 | } from './memory-history/index.js';
62 | export type {
63 | MemoryHistoryEntry,
64 | MemoryHistoryService,
65 | HistoryFilters,
66 | QueryOptions,
67 | OperationStats,
68 | MemoryOperation,
69 | } from './memory-history/index.js';
70 |
71 | // Factory functions
72 | export {
73 | createStorageBackends,
74 | createDefaultStorage,
75 | createStorageFromEnv,
76 | isStorageFactory,
77 | type StorageFactory,
78 | } from './factory.js';
79 |
--------------------------------------------------------------------------------
/src/core/brain/tools/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Internal Tools Module
3 | *
4 | * This module provides internal tools for the Cipher agent, including
5 | * memory management, session control, and system operations.
6 | *
7 | * @example
8 | * ```typescript
9 | * import { InternalToolManager, InternalToolRegistry } from '@core/brain/tools';
10 | *
11 | * const manager = new InternalToolManager();
12 | * await manager.initialize();
13 | *
14 | * // Register a tool
15 | * const result = manager.registerTool(myTool);
16 | *
17 | * // Execute a tool
18 | * const output = await manager.executeTool('cipher_my_tool', args);
19 | * ```
20 | */
21 |
22 | // Core types
23 | export type {
24 | InternalTool,
25 | InternalToolSet,
26 | InternalToolCategory,
27 | InternalToolHandler,
28 | InternalToolManagerConfig,
29 | InternalToolContext,
30 | ToolExecutionStats,
31 | ToolRegistrationResult,
32 | IInternalToolManager,
33 | } from './types.js';
34 |
35 | // Constants and utilities
36 | export { INTERNAL_TOOL_PREFIX, createInternalToolName, isInternalToolName } from './types.js';
37 |
38 | // Core classes
39 | export { InternalToolRegistry } from './registry.js';
40 | export { InternalToolManager } from './manager.js';
41 | export { UnifiedToolManager } from './unified-tool-manager.js';
42 | export type { UnifiedToolManagerConfig, CombinedToolSet } from './unified-tool-manager.js';
43 |
44 | // Import types for use in functions
45 | import type { InternalToolCategory, InternalToolManagerConfig } from './types.js';
46 | import { InternalToolManager } from './manager.js';
47 | import { InternalToolRegistry } from './registry.js';
48 |
49 | // Version and metadata
50 | export const INTERNAL_TOOLS_VERSION = '1.0.0';
51 | export const SUPPORTED_CATEGORIES: InternalToolCategory[] = ['memory', 'session', 'system'];
52 |
53 | /**
54 | * Create a new internal tool manager with optional configuration
55 | */
56 | export function createInternalToolManager(config?: InternalToolManagerConfig): InternalToolManager {
57 | return new InternalToolManager(config);
58 | }
59 |
60 | /**
61 | * Get the singleton registry instance
62 | */
63 | export function getInternalToolRegistry(): InternalToolRegistry {
64 | return InternalToolRegistry.getInstance();
65 | }
66 |
--------------------------------------------------------------------------------
/src/core/brain/tools/definitions/web-search/constants.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Web Search Module Constants
3 | *
4 | * Central location for all web search-related constants including
5 | * error messages, log prefixes, timeouts, and configuration defaults.
6 | *
7 | * @module web-search/constants
8 | */
9 |
10 | /**
11 | * Log prefixes for consistent logging across the web search module
12 | */
13 | export const LOG_PREFIXES = {
14 | MANAGER: '[WebSearch:Manager]',
15 | PROVIDER: '[WebSearch:Provider]',
16 | DUCKDUCKGO: '[WebSearch:DuckDuckGo]',
17 | FACTORY: '[WebSearch:Factory]',
18 | TOOL: '[WebSearch:Tool]',
19 | EXTRACTOR: '[WebSearch:Extractor]',
20 | } as const;
21 |
22 | /**
23 | * Error messages for the web search module
24 | */
25 | export const ERROR_MESSAGES = {
26 | PROVIDER_NOT_FOUND: 'Web search provider not found',
27 | PROVIDER_DISABLED: 'Web search provider is disabled',
28 | SEARCH_FAILED: 'Web search operation failed',
29 | CONNECTION_FAILED: 'Failed to connect to web search provider',
30 | INVALID_CONFIG: 'Invalid web search configuration',
31 | TIMEOUT_EXCEEDED: 'Web search operation timed out',
32 | RATE_LIMITED: 'Web search rate limit exceeded',
33 | CONTENT_EXTRACTION_FAILED: 'Failed to extract content from search result',
34 | } as const;
35 |
36 | /**
37 | * Timeout constants for web search operations
38 | */
39 | export const TIMEOUTS = {
40 | DEFAULT_SEARCH: 10000, // 10 seconds
41 | PROVIDER_CONNECTION: 5000, // 5 seconds
42 | CONTENT_EXTRACTION: 15000, // 15 seconds
43 | PAGE_LOAD: 30000, // 30 seconds for Puppeteer
44 | } as const;
45 |
46 | /**
47 | * Content extraction limits
48 | */
49 | export const EXTRACTION_LIMITS = {
50 | MAX_TEXT_LENGTH: 2000,
51 | MAX_HEADINGS: 10,
52 | MAX_KEY_FACTS: 5,
53 | MAX_PARAGRAPHS: 20,
54 | MAX_LIST_ITEMS: 15,
55 | } as const;
56 |
57 | /**
58 | * Search engine specific constants
59 | */
60 | export const SEARCH_ENGINES = {
61 | DUCKDUCKGO: 'duckduckgo',
62 | } as const;
63 |
64 | /**
65 | * Content type classifications
66 | */
67 | export const CONTENT_TYPES = {
68 | DOCUMENTATION: 'documentation',
69 | TUTORIAL: 'tutorial',
70 | ARTICLE: 'article',
71 | REFERENCE: 'reference',
72 | FORUM: 'forum',
73 | NEWS: 'news',
74 | OTHER: 'other',
75 | } as const;
76 |
--------------------------------------------------------------------------------
/examples/04-mcp-aggregator-hub/cipher.yml:
--------------------------------------------------------------------------------
1 | # MCP Aggregator Hub Configuration
2 |
3 | # LLM Configuration - Using Claude 3.5 Sonnet for balanced performance
4 | llm:
5 | provider: openai
6 | model: gpt-4.1-mini
7 | apiKey: $OPENAI_API_KEY
8 | maxIterations: 50
9 |
10 |
11 | # System Prompt - Optimized for MCP aggregator functionality
12 | systemPrompt: |
13 | You are a **Development Assistant** with access to multiple MCP tools for enhanced capabilities.
14 |
15 | Use available tools proactively to provide comprehensive assistance:
16 | - Research and gather current information
17 | - Analyze code for security and quality
18 | - Plan and structure development tasks
19 | - Learn from interactions for better future responses
20 |
21 | Deliver thorough, secure, and well-researched development assistance.
22 |
23 | # MCP Servers Configuration - Comprehensive development toolkit
24 | mcpServers:
25 | # Exa Search - Advanced web research and search
26 | exa:
27 | type: stdio
28 | command: npx
29 | args:
30 | - -y
31 | - "exa-mcp-server"
32 | env:
33 | EXA_API_KEY: $EXA_API_KEY
34 |
35 | # Context7 - Up-to-date documentation access (remote server)
36 | context7:
37 | type: "streamable-http"
38 | url: "https://mcp.context7.com/mcp"
39 | enabled: true
40 |
41 |
42 | # Context7 - local server (alternative - currently disabled)
43 | # context7:
44 | # type: stdio
45 | # command: npx
46 | # args:
47 | # - -y
48 | # - "@upstash/context7-mcp"
49 |
50 | # TaskMaster - AI-powered task management (for IDEs with API keys)
51 | taskmaster:
52 | type: stdio
53 | command: npx
54 | args:
55 | - -y
56 | - "--package=task-master-ai" # If the mcp server's not working, remove this
57 | - "task-master-ai"
58 | enabled: false # Explicitly disabled
59 | env:
60 | OPENAI_API_KEY: $OPENAI_API_KEY
61 | # Beside OPENAI, you can other providers' keys as well, see more here: https://github.com/eyaltoledano/claude-task-master?tab=readme-ov-file
62 |
63 |
64 | # Semgrep - Security vulnerability scanning
65 | semgrep:
66 | type: "streamable-http"
67 | url: "https://mcp.semgrep.ai/mcp/"
68 | enabled: true
69 |
70 |
--------------------------------------------------------------------------------
/src/app/cli/utils/options.ts:
--------------------------------------------------------------------------------
1 | import { z } from 'zod';
2 | import { logger } from '@core/index.js';
3 |
4 | export function validateCliOptions(opts: any): void {
5 | // Remove debug logging for CLI validation to reduce noise
6 | const cliOptionShape = z.object({
7 | verbose: z.boolean().optional().default(true),
8 | mode: z
9 | .enum(['cli', 'mcp', 'api', 'ui'], {
10 | errorMap: () => ({ message: 'Mode must be either cli, mcp, api, or ui' }),
11 | })
12 | .optional()
13 | .default('cli'),
14 | strict: z.boolean().optional().default(false),
15 | newSession: z.union([z.boolean(), z.string()]).optional(),
16 | port: z.string().optional(),
17 | uiPort: z.string().optional(),
18 | host: z.string().optional(),
19 | mcpTransportType: z
20 | .enum(['stdio', 'sse', 'streamable-http'], {
21 | errorMap: () => ({ message: 'MCP transport type must be stdio, sse, or streamable-http' }),
22 | })
23 | .optional()
24 | .default('stdio'),
25 | mcpPort: z.string().optional(),
26 | mcpHost: z.string().optional(),
27 | mcpDnsRebindingProtection: z.boolean().optional(),
28 | });
29 |
30 | const cliOptionSchema = cliOptionShape;
31 |
32 | const result = cliOptionSchema.safeParse({
33 | verbose: opts.verbose,
34 | mode: opts.mode,
35 | strict: opts.strict,
36 | newSession: opts.newSession,
37 | port: opts.port,
38 | uiPort: opts.uiPort,
39 | host: opts.host,
40 | mcpTransportType: opts.mcpTransportType,
41 | mcpPort: opts.mcpPort,
42 | mcpHost: opts.mcpHost,
43 | mcpDnsRebindingProtection: opts.mcpDnsRebindingProtection,
44 | });
45 |
46 | if (!result.success) {
47 | throw result.error;
48 | }
49 |
50 | // CLI options validated - no logging to reduce noise
51 | }
52 |
53 | export function handleCliOptionsError(error: unknown): never {
54 | if (error instanceof z.ZodError) {
55 | logger.error('Invalid command-line options detected:');
56 | error.errors.forEach(err => {
57 | const fieldName = err.path.join('.') || 'Unknown Option';
58 | logger.error(`- Option '${fieldName}': ${err.message}`);
59 | });
60 | logger.error('Please check your command-line arguments or run with --help for usage details.');
61 | } else {
62 | logger.error(
63 | `Validation error: ${error instanceof Error ? error.message : JSON.stringify(error)}`
64 | );
65 | }
66 | process.exit(1);
67 | }
68 |
--------------------------------------------------------------------------------
/src/app/ui/src/components/ui/card.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 |
3 | import { cn } from "@/lib/utils"
4 |
5 | const Card = React.forwardRef<
6 | HTMLDivElement,
7 | React.HTMLAttributes
8 | >(({ className, ...props }, ref) => (
9 |
18 | ))
19 | Card.displayName = "Card"
20 |
21 | const CardHeader = React.forwardRef<
22 | HTMLDivElement,
23 | React.HTMLAttributes
24 | >(({ className, ...props }, ref) => (
25 |
31 | ))
32 | CardHeader.displayName = "CardHeader"
33 |
34 | const CardTitle = React.forwardRef<
35 | HTMLParagraphElement,
36 | React.HTMLAttributes
37 | >(({ className, ...props }, ref) => (
38 |
43 | ))
44 | CardTitle.displayName = "CardTitle"
45 |
46 | const CardDescription = React.forwardRef<
47 | HTMLParagraphElement,
48 | React.HTMLAttributes
49 | >(({ className, ...props }, ref) => (
50 |
55 | ))
56 | CardDescription.displayName = "CardDescription"
57 |
58 | const CardContent = React.forwardRef<
59 | HTMLDivElement,
60 | React.HTMLAttributes
61 | >(({ className, ...props }, ref) => (
62 |
68 | ))
69 | CardContent.displayName = "CardContent"
70 |
71 | const CardFooter = React.forwardRef<
72 | HTMLDivElement,
73 | React.HTMLAttributes
74 | >(({ className, ...props }, ref) => (
75 |
81 | ))
82 | CardFooter.displayName = "CardFooter"
83 |
84 | export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
--------------------------------------------------------------------------------
/src/app/api/middleware/logging.ts:
--------------------------------------------------------------------------------
1 | import { Request, Response, NextFunction } from 'express';
2 | import { v4 as uuidv4 } from 'uuid';
3 | import { logger } from '@core/logger/index.js';
4 |
5 | // Extend Express Request interface to include requestId
6 | declare global {
7 | namespace Express {
8 | interface Request {
9 | requestId: string;
10 | startTime: number;
11 | }
12 | }
13 | }
14 |
15 | /**
16 | * Request ID middleware - adds unique request ID to each request
17 | */
18 | export function requestIdMiddleware(req: Request, res: Response, next: NextFunction): void {
19 | req.requestId = uuidv4();
20 | req.startTime = Date.now();
21 |
22 | // Add request ID to response headers
23 | res.setHeader('X-Request-ID', req.requestId);
24 |
25 | next();
26 | }
27 |
28 | /**
29 | * Request logging middleware - logs incoming requests and responses
30 | */
31 | export function requestLoggingMiddleware(req: Request, res: Response, next: NextFunction): void {
32 | const { method, url, ip, headers } = req;
33 | const userAgent = headers['user-agent'] || 'unknown';
34 |
35 | // Log incoming request
36 | logger.info('API Request', {
37 | requestId: req.requestId,
38 | method,
39 | url,
40 | ip,
41 | userAgent,
42 | contentType: headers['content-type'],
43 | });
44 |
45 | // Override res.end to log response
46 | const originalEnd = res.end;
47 | res.end = function (chunk?: any, encoding?: any): any {
48 | const duration = Date.now() - req.startTime;
49 | const { statusCode } = res;
50 |
51 | // Log response
52 | logger.info('API Response', {
53 | requestId: req.requestId,
54 | method,
55 | url,
56 | statusCode,
57 | duration: `${duration}ms`,
58 | responseSize: res.get('content-length') || 'unknown',
59 | });
60 |
61 | // Call original end method
62 | return originalEnd.call(this, chunk, encoding);
63 | };
64 |
65 | next();
66 | }
67 |
68 | /**
69 | * Error logging middleware - logs errors with request context
70 | */
71 | export function errorLoggingMiddleware(
72 | err: Error,
73 | req: Request,
74 | res: Response,
75 | next: NextFunction
76 | ): void {
77 | logger.error('API Error', {
78 | requestId: req.requestId,
79 | method: req.method,
80 | url: req.url,
81 | error: err.message,
82 | stack: err.stack,
83 | userAgent: req.headers['user-agent'],
84 | });
85 |
86 | next(err);
87 | }
88 |
--------------------------------------------------------------------------------
/src/core/brain/tools/definitions/web-search/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Web Search Tools Module
3 | *
4 | * Provides web search capabilities for the Cipher AI system using various search engines.
5 | * Currently supports DuckDuckGo with Puppeteer-based scraping.
6 | *
7 | * @module WebSearch
8 | */
9 |
10 | // Core components
11 | export { WebSearchManager } from './manager.js';
12 | export { BaseProvider } from './engine/base.js';
13 | export { DuckDuckGoPuppeteerProvider } from './engine/duckduckgo.js';
14 |
15 | // Configuration and factory
16 | export { WebSearchConfig, WebSearchConfigSchema } from './config.js';
17 | export {
18 | createWebSearchProvider,
19 | getWebSearchConfigFromEnv,
20 | createWebSearchProviderFromEnv,
21 | } from './factory.js';
22 |
23 | // Constants
24 | export {
25 | LOG_PREFIXES,
26 | ERROR_MESSAGES,
27 | TIMEOUTS,
28 | EXTRACTION_LIMITS,
29 | SEARCH_ENGINES,
30 | CONTENT_TYPES,
31 | } from './constants.js';
32 |
33 | // Types
34 | export type {
35 | SearchOptions,
36 | SearchResult,
37 | SearchResponse,
38 | ExtractedContent,
39 | ProviderConfig,
40 | InternalSearchResult,
41 | } from './types.js';
42 |
43 | // Tool integration
44 | export { webSearchTool, getWebSearchTools, cleanupWebSearch } from './web-search-tool.js';
45 |
46 | /**
47 | * Check if web search is available based on environment configuration
48 | */
49 | export function isWebSearchAvailable(): boolean {
50 | try {
51 | // Check if required environment variables or configs are available
52 | const searchEngine = process.env.WEB_SEARCH_ENGINE || 'duckduckgo';
53 | return searchEngine === 'duckduckgo'; // Currently only DuckDuckGo is supported
54 | } catch {
55 | return false;
56 | }
57 | }
58 |
59 | /**
60 | * Get web search configuration summary for debugging
61 | */
62 | export function getWebSearchInfo() {
63 | return {
64 | availableEngines: ['duckduckgo'],
65 | defaultEngine: process.env.WEB_SEARCH_ENGINE || 'duckduckgo',
66 | isAvailable: isWebSearchAvailable(),
67 | version: '1.0.0',
68 | };
69 | }
70 |
71 | import type { InternalToolSet } from '../../types.js';
72 | import { getWebSearchTools } from './web-search-tool.js';
73 |
74 | /**
75 | * Get all system tools
76 | */
77 | export function getSystemTools(): InternalToolSet {
78 | const webSearchTools = getWebSearchTools();
79 |
80 | return {
81 | ...webSearchTools,
82 | };
83 | }
84 |
--------------------------------------------------------------------------------
/src/core/brain/systemPrompt/providers/base-provider.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Base abstract class for all prompt providers
3 | *
4 | * This provides common functionality and enforces the interface contract
5 | * for all prompt provider implementations.
6 | */
7 |
8 | import { PromptProvider, ProviderType, ProviderContext } from '../interfaces.js';
9 |
10 | export abstract class BasePromptProvider implements PromptProvider {
11 | public readonly id: string;
12 | public readonly name: string;
13 | public readonly type: ProviderType;
14 | public readonly priority: number;
15 | public enabled: boolean;
16 |
17 | protected config: Record = {};
18 | protected initialized: boolean = false;
19 |
20 | constructor(
21 | id: string,
22 | name: string,
23 | type: ProviderType,
24 | priority: number,
25 | enabled: boolean = true
26 | ) {
27 | this.id = id;
28 | this.name = name;
29 | this.type = type;
30 | this.priority = priority;
31 | this.enabled = enabled;
32 | }
33 |
34 | /**
35 | * Abstract method that must be implemented by concrete providers
36 | */
37 | public abstract generateContent(context: ProviderContext): Promise;
38 |
39 | /**
40 | * Default validation - can be overridden by concrete providers
41 | */
42 | public validateConfig(config: Record): boolean {
43 | // Basic validation - check if config is an object
44 | return typeof config === 'object' && config !== null;
45 | }
46 |
47 | /**
48 | * Initialize the provider with configuration
49 | */
50 | public async initialize(config: Record): Promise {
51 | if (!this.validateConfig(config)) {
52 | throw new Error(`Invalid configuration for provider ${this.id}`);
53 | }
54 |
55 | this.config = { ...config };
56 | this.initialized = true;
57 | }
58 |
59 | /**
60 | * Default cleanup - can be overridden by concrete providers
61 | */
62 | public async destroy(): Promise {
63 | this.config = {};
64 | this.initialized = false;
65 | }
66 |
67 | /**
68 | * Check if provider is initialized
69 | */
70 | protected ensureInitialized(): void {
71 | if (!this.initialized) {
72 | throw new Error(`Provider ${this.id} is not initialized`);
73 | }
74 | }
75 |
76 | /**
77 | * Check if provider is enabled and initialized
78 | */
79 | protected canGenerate(): boolean {
80 | return this.enabled && this.initialized;
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/src/core/vector_storage/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Vector Storage Module
3 | *
4 | * High-performance vector storage and similarity search for embeddings.
5 | * Supports multiple backends with a unified API.
6 | *
7 | * Features:
8 | * - Multiple backend support (Qdrant, In-Memory, etc.)
9 | * - Similarity search with metadata filtering
10 | * - Batch operations for efficient indexing
11 | * - Type-safe configuration with runtime validation
12 | * - Graceful fallback to in-memory storage
13 | *
14 | * @module vector_storage
15 | *
16 | * @example
17 | * ```typescript
18 | * import { createVectorStore } from './vector_storage';
19 | *
20 | * // Create a vector store
21 | * const { store, manager } = await createVectorStore({
22 | * type: 'qdrant',
23 | * host: 'localhost',
24 | * port: 6333,
25 | * collectionName: 'documents',
26 | * dimension: 1536
27 | * });
28 | *
29 | * // Index vectors
30 | * await store.insert(
31 | * [embedding1, embedding2],
32 | * ['doc1', 'doc2'],
33 | * [{ title: 'Doc 1' }, { title: 'Doc 2' }]
34 | * );
35 | *
36 | * // Search for similar vectors
37 | * const results = await store.search(queryEmbedding, 5);
38 | *
39 | * // Cleanup
40 | * await manager.disconnect();
41 | * ```
42 | */
43 |
44 | // Export types
45 | export type {
46 | VectorStore,
47 | VectorStoreResult,
48 | SearchFilters,
49 | VectorStoreConfig,
50 | BackendConfig,
51 | } from './types.js';
52 |
53 | // Export error classes
54 | export {
55 | VectorStoreError,
56 | VectorStoreConnectionError,
57 | VectorDimensionError,
58 | CollectionNotFoundError,
59 | } from './backend/types.js';
60 |
61 | // Export factory functions
62 | export {
63 | createVectorStore,
64 | createDefaultVectorStore,
65 | createVectorStoreFromEnv,
66 | createDualCollectionVectorStoreFromEnv,
67 | createMultiCollectionVectorStoreFromEnv,
68 | getVectorStoreConfigFromEnv,
69 | createWorkspaceVectorStoreFromEnv,
70 | getWorkspaceVectorStoreConfigFromEnv,
71 | isVectorStoreFactory,
72 | type VectorStoreFactory,
73 | type DualCollectionVectorFactory,
74 | type MultiCollectionVectorFactory,
75 | } from './factory.js';
76 |
77 | // Export managers
78 | export { VectorStoreManager, type HealthCheckResult, type VectorStoreInfo } from './manager.js';
79 | export { DualCollectionVectorManager, type CollectionType } from './dual-collection-manager.js';
80 |
81 | // Export constants for external use
82 | export { BACKEND_TYPES, DEFAULTS, DISTANCE_METRICS } from './constants.js';
83 |
--------------------------------------------------------------------------------
/memAgent/cipher-advanced-prompt.yml:
--------------------------------------------------------------------------------
1 | # Advanced System Prompt Providers
2 |
3 | providers:
4 | - name: built-in-memory-search
5 | type: static
6 | priority: 100
7 | enabled: true
8 | config:
9 | content: |
10 | Use the memory search tool to retrieve facts, code, or context from previous interactions. Always search memory before answering if relevant.
11 |
12 | - name: built-in-reasoning-patterns
13 | type: static
14 | priority: 90
15 | enabled: true
16 | config:
17 | content: |
18 | Use the reasoning patterns tool to find and apply problem-solving strategies from past sessions when the input contains reasoning steps.
19 |
20 | - name: built-in-knowledge-graph
21 | type: static
22 | priority: 80
23 | enabled: true
24 | config:
25 | content: |
26 | Use the knowledge graph tools to manage, search, and relate entities and code concepts. Add nodes, search, and extract entities as needed.
27 |
28 | - name: built-in-efficiency-guidelines
29 | type: static
30 | priority: 70
31 | enabled: true
32 | config:
33 | content: |
34 | Follow efficiency guidelines: avoid redundant searches, batch related queries, and use tools strategically.
35 |
36 | - name: built-in-automatic-tools
37 | type: static
38 | priority: 60
39 | enabled: true
40 | config:
41 | content: |
42 | Some tools run automatically in the background (e.g., memory extraction, reasoning evaluation). Manual invocation is not required.
43 |
44 | # Dynamic providers (LLM-driven, not loaded at startup)
45 | - name: summary
46 | type: dynamic
47 | priority: 50
48 | enabled: true
49 | config:
50 | generator: summary
51 | history: all # or N for most recent N messages
52 |
53 | - name: rules
54 | type: dynamic
55 | priority: 49
56 | enabled: true
57 | config:
58 | generator: rules
59 | history: all
60 |
61 | - name: error-detection
62 | type: dynamic
63 | priority: 48
64 | enabled: true
65 | config:
66 | generator: error-detection
67 | history: all
68 |
69 | # Example file-based provider
70 | - name: project-guidelines
71 | type: file-based
72 | priority: 40
73 | enabled: true
74 | config:
75 | filePath: ./memAgent/project-guidelines.md
76 | summarize: false
77 |
78 | settings:
79 | maxGenerationTime: 10000
80 | failOnProviderError: false
81 | contentSeparator: "\n\n---\n\n"
--------------------------------------------------------------------------------
/src/core/brain/tools/definitions/web-search/factory.ts:
--------------------------------------------------------------------------------
1 | import { createLogger } from '../../../../logger/index.js';
2 | import { env } from '../../../../env.js';
3 | import { DuckDuckGoPuppeteerProvider } from './engine/duckduckgo.js';
4 | import { BaseProvider } from './engine/base.js';
5 | import { WebSearchConfig } from './config.js';
6 |
7 | const logger = createLogger({ level: env.CIPHER_LOG_LEVEL });
8 |
9 | export function createWebSearchProvider(
10 | searchEngine: string,
11 | searchEngineConfig?: any
12 | ): BaseProvider | null {
13 | if (searchEngine === 'duckduckgo') {
14 | return new DuckDuckGoPuppeteerProvider(searchEngineConfig);
15 | }
16 | logger.error('Unknown web search engine', { searchEngine });
17 | return null;
18 | }
19 |
20 | export function getDefaultWebSearchConfig(): WebSearchConfig {
21 | return {
22 | engine: 'duckduckgo' as const,
23 | headless: true,
24 | maxResults: 2,
25 | timeout: 10000,
26 | config: {
27 | timeout: 10000,
28 | maxRetries: 2,
29 | rateLimit: {
30 | requestsPerMinute: 10,
31 | burstLimit: 3,
32 | },
33 | },
34 | };
35 | }
36 |
37 | export async function getWebSearchConfigFromEnv(
38 | agentConfig?: any
39 | ): Promise {
40 | // Start with default configuration
41 | const defaultConfig = getDefaultWebSearchConfig();
42 |
43 | // Override with environment variables
44 | const searchEngine = env.WEB_SEARCH_ENGINE;
45 | const searchEngineConfig = agentConfig?.webSearch?.[searchEngine];
46 |
47 | if (searchEngine === 'duckduckgo') {
48 | return {
49 | engine: 'duckduckgo' as const,
50 | headless: true,
51 | maxResults: env.WEB_SEARCH_MAX_RESULTS, // Use env var
52 | timeout: defaultConfig.timeout,
53 | config: {
54 | ...defaultConfig.config,
55 | ...searchEngineConfig, // Allow agent config to override
56 | rateLimit: {
57 | requestsPerMinute: env.WEB_SEARCH_RATE_LIMIT, // Use env var
58 | burstLimit: defaultConfig.config?.rateLimit?.burstLimit || 3,
59 | ...(searchEngineConfig?.rateLimit || {}), // Allow agent config to override
60 | },
61 | },
62 | };
63 | }
64 |
65 | logger.warn('Unknown web search engine', { searchEngine });
66 | return null;
67 | }
68 |
69 | export async function createWebSearchProviderFromEnv(
70 | searchToolConfig?: any
71 | ): Promise {
72 | const config = await getWebSearchConfigFromEnv(searchToolConfig);
73 | if (!config) {
74 | return null;
75 | }
76 |
77 | return createWebSearchProvider(config.engine, config.config);
78 | }
79 |
--------------------------------------------------------------------------------
/src/app/ui/src/components/ui/dropdown-menu.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import { Popover, PopoverContent, PopoverTrigger } from "./popover"
3 |
4 | import { cn } from "@/lib/utils"
5 |
6 | const DropdownMenu = ({ children, ...props }: React.ComponentProps) => (
7 | {children}
8 | )
9 |
10 | const DropdownMenuTrigger = PopoverTrigger
11 |
12 | const DropdownMenuContent = React.forwardRef<
13 | React.ElementRef,
14 | React.ComponentPropsWithoutRef
15 | >(({ className, align = "end", sideOffset = 4, ...props }, ref) => (
16 |
23 | ))
24 | DropdownMenuContent.displayName = "DropdownMenuContent"
25 |
26 | const DropdownMenuItem = React.forwardRef<
27 | HTMLDivElement,
28 | React.HTMLAttributes & {
29 | disabled?: boolean
30 | }
31 | >(({ className, disabled, ...props }, ref) => (
32 |
42 | ))
43 | DropdownMenuItem.displayName = "DropdownMenuItem"
44 |
45 | const DropdownMenuSeparator = React.forwardRef<
46 | HTMLDivElement,
47 | React.HTMLAttributes
48 | >(({ className, ...props }, ref) => (
49 |
54 | ))
55 | DropdownMenuSeparator.displayName = "DropdownMenuSeparator"
56 |
57 | const DropdownMenuLabel = React.forwardRef<
58 | HTMLDivElement,
59 | React.HTMLAttributes
60 | >(({ className, ...props }, ref) => (
61 |
66 | ))
67 | DropdownMenuLabel.displayName = "DropdownMenuLabel"
68 |
69 | export {
70 | DropdownMenu,
71 | DropdownMenuTrigger,
72 | DropdownMenuContent,
73 | DropdownMenuItem,
74 | DropdownMenuSeparator,
75 | DropdownMenuLabel,
76 | }
--------------------------------------------------------------------------------
/src/core/brain/tools/definitions/knowledge_graph/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Knowledge Graph Tools Module
3 | *
4 | * This module exports all knowledge graph-related internal tools for the Cipher agent.
5 | * These tools handle graph operations, entity management, and knowledge graph queries.
6 | */
7 |
8 | import type { InternalToolSet } from '../../types.js';
9 |
10 | // Export all knowledge graph tools with dynamic imports
11 | export { addNodeTool } from './add-node.js';
12 | export { addEdgeTool } from './add-edge.js';
13 | export { searchGraphTool } from './search-graph.js';
14 | export { getNeighborsTool } from './get-neighbors.js';
15 | export { extractEntitiesTool } from './extract-entities.js';
16 | export { updateNodeTool } from './update-node.js';
17 | export { deleteNodeTool } from './delete-node.js';
18 | export { queryGraphTool } from './query-graph.js';
19 |
20 | // Export new intelligent knowledge graph tools
21 | export { intelligentProcessorTool } from './intelligent-processor.js';
22 | export { enhancedSearchTool } from './enhanced-search.js';
23 | export { relationshipManagerTool } from './relationship-manager.js';
24 |
25 | /**
26 | * Get all knowledge graph tools as a tool set
27 | */
28 | export async function getKnowledgeGraphTools(): Promise {
29 | const [
30 | { addNodeTool },
31 | { addEdgeTool },
32 | { searchGraphTool },
33 | { getNeighborsTool },
34 | { extractEntitiesTool },
35 | { updateNodeTool },
36 | { deleteNodeTool },
37 | { queryGraphTool },
38 | { intelligentProcessorTool },
39 | { enhancedSearchTool },
40 | { relationshipManagerTool },
41 | ] = await Promise.all([
42 | import('./add-node.js'),
43 | import('./add-edge.js'),
44 | import('./search-graph.js'),
45 | import('./get-neighbors.js'),
46 | import('./extract-entities.js'),
47 | import('./update-node.js'),
48 | import('./delete-node.js'),
49 | import('./query-graph.js'),
50 | import('./intelligent-processor.js'),
51 | import('./enhanced-search.js'),
52 | import('./relationship-manager.js'),
53 | ]);
54 |
55 | return {
56 | [addNodeTool.name]: addNodeTool,
57 | [addEdgeTool.name]: addEdgeTool,
58 | [searchGraphTool.name]: searchGraphTool,
59 | [getNeighborsTool.name]: getNeighborsTool,
60 | [extractEntitiesTool.name]: extractEntitiesTool,
61 | [updateNodeTool.name]: updateNodeTool,
62 | [deleteNodeTool.name]: deleteNodeTool,
63 | [queryGraphTool.name]: queryGraphTool,
64 | [intelligentProcessorTool.name]: intelligentProcessorTool,
65 | [enhancedSearchTool.name]: enhancedSearchTool,
66 | [relationshipManagerTool.name]: relationshipManagerTool,
67 | };
68 | }
69 |
--------------------------------------------------------------------------------
/src/core/knowledge_graph/types.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Knowledge Graph Module Public API
3 | *
4 | * This module re-exports all the necessary types and interfaces for the knowledge graph system.
5 | * It provides a simplified, clean API surface for consumers of the knowledge graph module.
6 | *
7 | * The knowledge graph system architecture:
8 | * - Single backend design for graph operations and traversal
9 | * - Multiple backend implementations: Neo4j, In-Memory, etc.
10 | * - Consistent API across different backend types
11 | * - Strong type safety with TypeScript and runtime validation with Zod
12 | *
13 | * @module knowledge_graph
14 | *
15 | * @example
16 | * ```typescript
17 | * import type { KnowledgeGraphConfig, KnowledgeGraph } from './knowledge_graph/types.js';
18 | *
19 | * // Configure knowledge graph
20 | * const config: KnowledgeGraphConfig = {
21 | * type: 'neo4j',
22 | * host: 'localhost',
23 | * port: 7687,
24 | * username: 'neo4j',
25 | * password: 'password',
26 | * database: 'neo4j'
27 | * };
28 | *
29 | * // Use knowledge graph
30 | * const graph: KnowledgeGraph = createKnowledgeGraph(config);
31 | * ```
32 | */
33 |
34 | /**
35 | * Re-export simplified knowledge graph types
36 | *
37 | * These exports provide the complete type system needed to work with
38 | * the knowledge graph module without exposing internal implementation details.
39 | */
40 | export type {
41 | // Core interfaces
42 | KnowledgeGraph, // Interface for knowledge graph implementations
43 | GraphNode, // Node structure in the graph
44 | GraphEdge, // Edge/relationship structure in the graph
45 | GraphQuery, // Query structure for graph operations
46 | GraphResult, // Search/query result structure
47 | NodeFilters, // Metadata filters for node search
48 | EdgeFilters, // Metadata filters for edge search
49 |
50 | // Configuration types
51 | BackendConfig, // Union type for all backend configurations
52 | KnowledgeGraphConfig, // Top-level knowledge graph system configuration
53 | } from './backend/types.js';
54 |
55 | /**
56 | * Re-export configuration schemas
57 | */
58 | export {
59 | // Configuration parsers and validators
60 | parseKnowledgeGraphConfig,
61 | validateKnowledgeGraphConfig,
62 | } from './config.js';
63 |
64 | /**
65 | * Re-export factory functions
66 | */
67 | export {
68 | createKnowledgeGraph,
69 | createDefaultKnowledgeGraph,
70 | createKnowledgeGraphFromEnv,
71 | } from './factory.js';
72 |
73 | /**
74 | * Re-export manager
75 | */
76 | export { KnowledgeGraphManager } from './manager.js';
77 |
78 | /**
79 | * Re-export constants
80 | */
81 | export { BACKEND_TYPES, DEFAULTS, ERROR_MESSAGES } from './constants.js';
82 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | ## [0.3.0] - 2025-01-27
4 |
5 | ### 🚀 Features
6 | - Provided Full Supports for SSE and Streamable-HTTP Transports and Refactored README [#193](https://github.com/campfirein/cipher/pull/193)
7 | - Optimize PayLoad and Introduce New WorkSpace Environment Variables [#195](https://github.com/campfirein/cipher/pull/195)
8 | - Added ChromaDB Backend. [#197](https://github.com/campfirein/cipher/pull/197)
9 | - Adjusted Default values for Vector Stores and Adjust Docs [#225](https://github.com/campfirein/cipher/pull/200)
10 | - Added Pinecone Backend. [#202](https://github.com/campfirein/cipher/pull/202)
11 | - Added ChromaDB Pgvector Backend. [#205](https://github.com/campfirein/cipher/pull/205)
12 | - Added FAISS Backend. [#217](https://github.com/campfirein/cipher/pull/217)
13 | - Added Redis Backend. [#218](https://github.com/campfirein/cipher/pull/218)
14 | - Added Weaviate Backend. [#225](https://github.com/campfirein/cipher/pull/225)
15 |
16 |
17 | ### 🐛 Bug Fixes
18 | - Fixed AWS LLM provider not recognized at startup. [#212](https://github.com/campfirein/cipher/pull/212)
19 | - Fixed Streamable-HTTP MCP transport + Tool Panel payloads. [#214](https://github.com/campfirein/cipher/pull/214)
20 | -
21 |
22 | ### 📝 Documentation
23 | - Refactored README and provided full docs in [docs](./docs/) [#193](https://github.com/campfirein/cipher/pull/193)
24 |
25 | ## [0.2.2] - 2025-08-08
26 |
27 | ### 🚀 Features
28 | - Provided Full Supports for SSE and Streamable-HTTP Transports and Refactored README [#193](https://github.com/campfirein/cipher/pull/193)
29 | - Optimize PayLoad and Introduce New WorkSpace Environment Variables [#195](https://github.com/campfirein/cipher/pull/195)
30 | - Added ChromaDB Backend. [#197](https://github.com/campfirein/cipher/pull/197)
31 | - Adjusted Default values for Vector Stores and Adjust Docs [#225](https://github.com/campfirein/cipher/pull/200)
32 | - Added Pinecone Backend. [#202](https://github.com/campfirein/cipher/pull/202)
33 | - Added ChromaDB Pgvector Backend. [#205](https://github.com/campfirein/cipher/pull/205)
34 | - Added FAISS Backend. [#217](https://github.com/campfirein/cipher/pull/217)
35 | - Added Redis Backend. [#218](https://github.com/campfirein/cipher/pull/218)
36 | - Added Weaviate Backend. [#225](https://github.com/campfirein/cipher/pull/225)
37 |
38 |
39 | ### 🐛 Bug Fixes
40 | - Fixed AWS LLM provider not recognized at startup. [#212](https://github.com/campfirein/cipher/pull/212)
41 | - Fixed Streamable-HTTP MCP transport + Tool Panel payloads. [#214](https://github.com/campfirein/cipher/pull/214)
42 | -
43 |
44 | ### 📝 Documentation
45 | - Refactored README and provided full docs in [docs](./docs/) [#193](https://github.com/campfirein/cipher/pull/193)
--------------------------------------------------------------------------------
/src/core/brain/systemPrompt/providers/static-provider.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Static Prompt Provider
3 | *
4 | * Provides static content that doesn't change based on runtime context.
5 | * Useful for fixed instructions, disclaimers, or constant prompt segments.
6 | */
7 |
8 | import { ProviderType, ProviderContext } from '../interfaces.js';
9 | import { BasePromptProvider } from './base-provider.js';
10 |
11 | export interface StaticProviderConfig {
12 | /** The static content to provide */
13 | content: string;
14 | /** Optional template variables to replace in content */
15 | variables?: Record;
16 | }
17 |
18 | export class StaticPromptProvider extends BasePromptProvider {
19 | private content: string = '';
20 | private variables: Record = {};
21 |
22 | constructor(id: string, name: string, priority: number, enabled: boolean = true) {
23 | super(id, name, ProviderType.STATIC, priority, enabled);
24 | }
25 |
26 | public override validateConfig(config: Record): boolean {
27 | if (!super.validateConfig(config)) {
28 | return false;
29 | }
30 |
31 | const typedConfig = config as StaticProviderConfig;
32 |
33 | // Content is required and must be a string
34 | if (typeof typedConfig.content !== 'string') {
35 | return false;
36 | }
37 |
38 | // Variables are optional but must be a record if provided
39 | if (typedConfig.variables !== undefined) {
40 | if (typeof typedConfig.variables !== 'object' || typedConfig.variables === null) {
41 | return false;
42 | }
43 |
44 | // Check that all variable values are strings
45 | for (const [key, value] of Object.entries(typedConfig.variables)) {
46 | if (typeof key !== 'string' || typeof value !== 'string') {
47 | return false;
48 | }
49 | }
50 | }
51 |
52 | return true;
53 | }
54 |
55 | public override async initialize(config: Record): Promise {
56 | await super.initialize(config);
57 |
58 | const typedConfig = config as StaticProviderConfig;
59 | this.content = typedConfig.content;
60 | this.variables = typedConfig.variables || {};
61 | }
62 |
63 | public async generateContent(_context: ProviderContext): Promise {
64 | this.ensureInitialized();
65 |
66 | if (!this.canGenerate()) {
67 | return '';
68 | }
69 |
70 | // Replace template variables if any exist
71 | let result = this.content;
72 |
73 | for (const [key, value] of Object.entries(this.variables)) {
74 | const placeholder = `{{${key}}}`;
75 | result = result.replace(new RegExp(placeholder, 'g'), value);
76 | }
77 |
78 | return result;
79 | }
80 |
81 | public override async destroy(): Promise {
82 | await super.destroy();
83 | this.content = '';
84 | this.variables = {};
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/src/app/ui/src/components/providers/query-provider.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import React from 'react'
4 | import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
5 | import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
6 |
7 | // Create a client optimized for immediate UI updates
8 | function makeQueryClient() {
9 | return new QueryClient({
10 | defaultOptions: {
11 | queries: {
12 | // Balance between performance and freshness for session data
13 | staleTime: 30 * 1000, // 30 seconds - fresh enough for real-time feel
14 | gcTime: 5 * 60 * 1000, // 5 minutes garbage collection
15 | refetchOnWindowFocus: false, // Prevent unnecessary refetches
16 | refetchOnMount: true, // Always fetch on mount for consistency
17 | retry: (failureCount, error: any) => {
18 | // Don't retry on 4xx errors except 408 (timeout) and 429 (rate limit)
19 | if (error?.status >= 400 && error?.status < 500 &&
20 | error?.status !== 408 && error?.status !== 429) {
21 | return false
22 | }
23 | // Retry up to 3 times for other errors
24 | return failureCount < 3
25 | },
26 | retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 30000),
27 | },
28 | mutations: {
29 | // Optimistic UI updates with proper error handling
30 | retry: (failureCount, error: any) => {
31 | // Don't retry mutations on client errors
32 | if (error?.status >= 400 && error?.status < 500) {
33 | return false
34 | }
35 | // Retry mutations once for server errors
36 | return failureCount < 1
37 | },
38 | // Enable network mode for offline resilience
39 | networkMode: 'offlineFirst',
40 | },
41 | },
42 | })
43 | }
44 |
45 | let browserQueryClient: QueryClient | undefined = undefined
46 |
47 | function getQueryClient() {
48 | if (typeof window === 'undefined') {
49 | // Server: always make a new query client
50 | return makeQueryClient()
51 | } else {
52 | // Browser: make a new query client if we don't already have one
53 | if (!browserQueryClient) browserQueryClient = makeQueryClient()
54 | return browserQueryClient
55 | }
56 | }
57 |
58 | interface QueryProviderProps {
59 | children: React.ReactNode
60 | }
61 |
62 | export function QueryProvider({ children }: QueryProviderProps) {
63 | const queryClient = getQueryClient()
64 |
65 | return (
66 |
67 | {children}
68 | {process.env.NODE_ENV === 'development' && (
69 |
73 | )}
74 |
75 | )
76 | }
--------------------------------------------------------------------------------
/src/core/storage/memory-history/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Memory History Storage Module
3 | *
4 | * Main export point for the memory history storage service.
5 | * Provides tracking and audit trails for memory operations.
6 | *
7 | * @module storage/memory-history
8 | */
9 |
10 | // Core exports
11 | export { MemoryHistoryStorageService } from './service.js';
12 |
13 | // Type exports
14 | export type {
15 | MemoryHistoryEntry,
16 | MemoryHistoryService,
17 | HistoryFilters,
18 | QueryOptions,
19 | OperationStats,
20 | MemoryOperation,
21 | } from './types.js';
22 |
23 | // Import types for internal use
24 | import type { MemoryHistoryEntry } from './types.js';
25 | import { MemoryHistoryStorageService } from './service.js';
26 |
27 | // Schema exports
28 | export { SQLITE_SCHEMA, POSTGRESQL_SCHEMA, MIGRATIONS, QueryBuilder } from './schema.js';
29 | export type { SchemaManager, SchemaMigration } from './schema.js';
30 |
31 | /**
32 | * Create a new memory history service instance
33 | *
34 | * @returns A new MemoryHistoryStorageService instance
35 | *
36 | * @example
37 | * ```typescript
38 | * import { createMemoryHistoryService } from './storage/memory-history';
39 | *
40 | * const historyService = createMemoryHistoryService();
41 | * await historyService.connect();
42 | *
43 | * await historyService.recordOperation({
44 | * id: 'op-123',
45 | * projectId: 'project-1',
46 | * memoryId: 'mem-456',
47 | * name: 'Add knowledge about React hooks',
48 | * tags: ['react', 'hooks', 'javascript'],
49 | * operation: 'ADD',
50 | * timestamp: new Date().toISOString(),
51 | * metadata: { source: 'cli' },
52 | * success: true
53 | * });
54 | * ```
55 | */
56 | export function createMemoryHistoryService(): MemoryHistoryStorageService {
57 | return new MemoryHistoryStorageService();
58 | }
59 |
60 | /**
61 | * Helper function to create a memory history entry
62 | *
63 | * @param params - Partial entry parameters
64 | * @returns Complete memory history entry with generated ID and timestamp
65 | *
66 | * @example
67 | * ```typescript
68 | * import { createMemoryHistoryEntry } from './storage/memory-history';
69 | *
70 | * const entry = createMemoryHistoryEntry({
71 | * projectId: 'project-1',
72 | * memoryId: 'mem-456',
73 | * name: 'Search for React patterns',
74 | * operation: 'SEARCH',
75 | * tags: ['react', 'patterns'],
76 | * success: true,
77 | * metadata: { query: 'react hooks patterns' }
78 | * });
79 | * ```
80 | */
81 | export function createMemoryHistoryEntry(
82 | params: Omit & {
83 | id?: string;
84 | timestamp?: string;
85 | }
86 | ): MemoryHistoryEntry {
87 | return {
88 | id: params.id || `mh_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
89 | timestamp: params.timestamp || new Date().toISOString(),
90 | ...params,
91 | } as MemoryHistoryEntry;
92 | }
93 |
--------------------------------------------------------------------------------
/src/core/events/__tests__/env-variables.test.ts:
--------------------------------------------------------------------------------
1 | import { describe, test, expect, beforeEach, afterEach } from 'vitest';
2 | import { EventManager } from '../event-manager.js';
3 | // import { EventPersistenceConfig } from '../persistence.js';
4 |
5 | describe('Event Persistence Environment Variables', () => {
6 | const originalEnv = { ...process.env };
7 |
8 | beforeEach(() => {
9 | // Reset environment variables before each test
10 | process.env = { ...originalEnv };
11 | });
12 |
13 | afterEach(() => {
14 | // Restore original environment variables
15 | process.env = originalEnv;
16 | });
17 |
18 | test('should enable persistence when EVENT_PERSISTENCE_ENABLED=true', () => {
19 | process.env.EVENT_PERSISTENCE_ENABLED = 'true';
20 | process.env.EVENT_PERSISTENCE_PATH = '/custom/events/path';
21 |
22 | const eventManager = new EventManager({
23 | enablePersistence: true,
24 | eventPersistenceConfig: {
25 | enabled: false, // This should be overridden by env var
26 | },
27 | });
28 |
29 | // The EventManager should be created with persistence enabled
30 | // We can't directly test the internal state, but we can verify it doesn't throw
31 | expect(eventManager).toBeDefined();
32 | });
33 |
34 | test('should use custom path when EVENT_PERSISTENCE_PATH is set', () => {
35 | process.env.EVENT_PERSISTENCE_ENABLED = 'true';
36 | process.env.EVENT_PERSISTENCE_PATH = '/custom/events/path';
37 |
38 | const eventManager = new EventManager({
39 | enablePersistence: true,
40 | eventPersistenceConfig: {
41 | enabled: true,
42 | filePath: '/default/path', // This should be overridden by env var
43 | },
44 | });
45 |
46 | expect(eventManager).toBeDefined();
47 | });
48 |
49 | test('should disable persistence when EVENT_PERSISTENCE_ENABLED=false', () => {
50 | process.env.EVENT_PERSISTENCE_ENABLED = 'false';
51 |
52 | const eventManager = new EventManager({
53 | enablePersistence: false,
54 | eventPersistenceConfig: {
55 | enabled: true, // This should be overridden by env var
56 | },
57 | });
58 |
59 | expect(eventManager).toBeDefined();
60 | });
61 |
62 | test('should use default path when EVENT_PERSISTENCE_PATH is not set', () => {
63 | process.env.EVENT_PERSISTENCE_ENABLED = 'true';
64 | // Don't set EVENT_PERSISTENCE_PATH
65 |
66 | const eventManager = new EventManager({
67 | enablePersistence: true,
68 | eventPersistenceConfig: {
69 | enabled: true,
70 | filePath: '/default/path',
71 | },
72 | });
73 |
74 | expect(eventManager).toBeDefined();
75 | });
76 |
77 | test('should handle invalid EVENT_PERSISTENCE_ENABLED values', () => {
78 | process.env.EVENT_PERSISTENCE_ENABLED = 'invalid';
79 | process.env.EVENT_PERSISTENCE_PATH = '/custom/path';
80 |
81 | const eventManager = new EventManager({
82 | enablePersistence: false, // Should default to false for invalid values
83 | });
84 |
85 | expect(eventManager).toBeDefined();
86 | });
87 | });
88 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | ################################################################################
2 | # Build stage - includes dev dependencies
3 |
4 | ################################################################################
5 | # Build stage - optimized for smaller final image
6 | FROM node:20.18.1-alpine AS builder
7 |
8 | # Build arguments
9 | ARG BUILD_UI=false
10 | ARG NODE_VERSION=20.18.1
11 |
12 | # Install build dependencies for native modules
13 | RUN apk add --no-cache \
14 | python3 \
15 | make \
16 | g++ \
17 | && rm -rf /var/cache/apk/*
18 |
19 | WORKDIR /app
20 |
21 | # Copy package files first for better caching
22 | COPY package*.json pnpm-lock.yaml ./
23 |
24 | # Install pnpm
25 | RUN npm install -g pnpm@9.14.0
26 |
27 | # Install dependencies
28 | RUN pnpm install --frozen-lockfile
29 |
30 | # Copy source and build
31 | COPY . .
32 | # Use conditional build based on BUILD_UI arg
33 | RUN if [ "$BUILD_UI" = "true" ]; then pnpm run build; else pnpm run build:no-ui; fi
34 |
35 | # Clean up and prepare production node_modules
36 | RUN pnpm prune --prod && \
37 | pnpm store prune && \
38 | rm -rf /root/.npm /tmp/* /usr/lib/node_modules/npm/man /usr/lib/node_modules/npm/doc /usr/lib/node_modules/npm/html /usr/lib/node_modules/npm/scripts
39 |
40 | ################################################################################
41 | # Production stage - minimal Alpine
42 | FROM node:20.18.1-alpine AS production
43 |
44 | WORKDIR /app
45 |
46 | # Create non-root user
47 | RUN addgroup -g 1001 -S cipher && adduser -S cipher -u 1001
48 |
49 | # Create .cipher directory with proper permissions for database
50 | RUN mkdir -p /app/.cipher/database && \
51 | chown -R cipher:cipher /app/.cipher
52 |
53 | # Copy only essential production files
54 | COPY --from=builder --chown=cipher:cipher /app/dist ./dist
55 | COPY --from=builder --chown=cipher:cipher /app/node_modules ./node_modules
56 | COPY --from=builder --chown=cipher:cipher /app/package.json ./
57 | COPY --from=builder --chown=cipher:cipher /app/memAgent ./memAgent
58 |
59 | # Create a minimal .env file for Docker (environment variables will be passed via docker)
60 | RUN echo "# Docker environment - variables passed via docker run" > .env
61 |
62 | # Environment variables
63 | ENV NODE_ENV=production \
64 | PORT=3000 \
65 | CONFIG_FILE=/app/memAgent/cipher.yml
66 |
67 | # Switch to non-root user
68 | USER cipher
69 |
70 | # Health check
71 | HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
72 | CMD node -e "const http = require('http'); const req = http.request({host:'localhost',port:process.env.PORT||3000,path:'/health'}, (res) => process.exit(res.statusCode === 200 ? 0 : 1)); req.on('error', () => process.exit(1)); req.end();"
73 |
74 | # Single port for deployment platform
75 | EXPOSE $PORT
76 |
77 | # API server mode: REST APIs on single port
78 | CMD ["sh", "-c", "node dist/src/app/index.cjs --mode api --port $PORT --host 0.0.0.0 --agent $CONFIG_FILE"]
--------------------------------------------------------------------------------
/src/app/ui/src/app/globals.css:
--------------------------------------------------------------------------------
1 | @import "tailwindcss";
2 |
3 | :root {
4 | font-family: Inter, sans-serif;
5 | background: var(--color-background);
6 | background-color: var(--color-background);
7 | }
8 |
9 | @theme {
10 | /* Base colors */
11 | --color-background: #0F0F0F;
12 | --color-foreground: #fafafa;
13 |
14 | /* Primary colors */
15 | --color-primary: #099250;
16 | --color-primary-foreground: #fff;
17 |
18 | /* Status colors */
19 | --color-destructive: #D92D20;
20 | --color-destructive-foreground: #fff;
21 | --color-warning: #CA8504;
22 | --color-warning-foreground: #fff;
23 | --color-success: #099250;
24 | --color-success-foreground: #fff;
25 |
26 | /* Surface colors */
27 | --color-secondary: #1a1a1a;
28 | --color-secondary-foreground: #fafafa;
29 | --color-muted: #262626;
30 | --color-muted-foreground: #a1a1aa;
31 | --color-accent: #262626;
32 | --color-accent-foreground: #fafafa;
33 |
34 | /* UI element colors */
35 | --color-card: #0a0a0a;
36 | --color-card-foreground: #fafafa;
37 | --color-popover: #0a0a0a;
38 | --color-popover-foreground: #fafafa;
39 | --color-border: #262626;
40 | --color-input: #262626;
41 | --color-ring: #099250;
42 |
43 | /* Glass effect variables */
44 | --color-glass-bg: rgba(255, 255, 255, 0.05);
45 | --color-glass-border: rgba(255, 255, 255, 0.1);
46 |
47 | /* Spacing and sizing */
48 | --radius: 0.5rem;
49 | --radius-sm: 0.25rem;
50 | --radius-lg: 0.75rem;
51 |
52 | /* Typography */
53 | --font-sans: Inter, ui-sans-serif, system-ui, sans-serif;
54 | --font-mono: "JetBrains Mono", ui-monospace, monospace;
55 | }
56 |
57 | * {
58 | border-color: var(--color-border);
59 | }
60 |
61 | body {
62 | color: var(--color-foreground);
63 | background: var(--color-background);
64 | font-family: var(--font-sans);
65 | }
66 |
67 | /* Glass effect utilities */
68 | .glass {
69 | background: var(--color-glass-bg);
70 | backdrop-filter: blur(12px);
71 | border: 1px solid var(--color-glass-border);
72 | }
73 |
74 | /* Smooth transitions */
75 | .transition-smooth {
76 | transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
77 | }
78 |
79 | /* Custom scrollbar */
80 | .scrollbar-thin::-webkit-scrollbar {
81 | width: 6px;
82 | }
83 |
84 | .scrollbar-thin::-webkit-scrollbar-track {
85 | background: var(--color-muted);
86 | border-radius: var(--radius-sm);
87 | }
88 |
89 | .scrollbar-thin::-webkit-scrollbar-thumb {
90 | background: var(--color-border);
91 | border-radius: var(--radius-sm);
92 | }
93 |
94 | .scrollbar-thin::-webkit-scrollbar-thumb:hover {
95 | background: var(--color-muted-foreground);
96 | }
97 |
98 | /* Focus styles */
99 | .focus-visible:focus-visible {
100 | outline: 2px solid var(--color-ring);
101 | outline-offset: 2px;
102 | }
--------------------------------------------------------------------------------
/src/core/brain/llm/tokenizer/factory.ts:
--------------------------------------------------------------------------------
1 | import { ITokenizer, TokenizerConfigSchema, TokenizerConfig } from './types.js';
2 | import { OpenAITokenizer } from './providers/openai.js';
3 | import { AnthropicTokenizer } from './providers/anthropic.js';
4 | import { GoogleTokenizer } from './providers/google.js';
5 | import { DefaultTokenizer } from './providers/default.js';
6 | import { logger } from '../../../logger/index.js';
7 | import { getTokenizerCache } from './cache.js';
8 |
9 | /**
10 | * Create a tokenizer instance based on provider configuration
11 | * Uses caching to avoid redundant tokenizer creation
12 | */
13 | export function createTokenizer(config: TokenizerConfig): ITokenizer {
14 | const validatedConfig = TokenizerConfigSchema.parse(config);
15 |
16 | // Use cache to get or create tokenizer
17 | const cache = getTokenizerCache();
18 | return cache.get(validatedConfig, config => {
19 | logger.debug('Creating tokenizer', {
20 | provider: config.provider,
21 | model: config.model,
22 | });
23 |
24 | switch (config.provider) {
25 | case 'openai':
26 | return new OpenAITokenizer(config);
27 | case 'anthropic':
28 | return new AnthropicTokenizer(config);
29 | case 'google':
30 | return new GoogleTokenizer(config);
31 | case 'default':
32 | default:
33 | logger.warn('Using default tokenizer, token counting may be less accurate');
34 | return new DefaultTokenizer(config);
35 | }
36 | });
37 | }
38 |
39 | /**
40 | * Get recommended tokenizer config for a given model
41 | */
42 | export function getTokenizerConfigForModel(model: string): TokenizerConfig {
43 | if (model.startsWith('gpt-') || model.startsWith('o1-') || model.includes('openai')) {
44 | return {
45 | provider: 'openai',
46 | model,
47 | fallbackToApproximation: true,
48 | hybridTracking: true,
49 | };
50 | }
51 |
52 | if (model.startsWith('claude-') || model.includes('anthropic')) {
53 | return {
54 | provider: 'anthropic',
55 | model,
56 | fallbackToApproximation: true,
57 | hybridTracking: true,
58 | };
59 | }
60 |
61 | if (model.startsWith('gemini-') || model.includes('google')) {
62 | return {
63 | provider: 'google',
64 | model,
65 | fallbackToApproximation: true,
66 | hybridTracking: true,
67 | };
68 | }
69 |
70 | if (model.startsWith('qwen') || model.includes('qwen')) {
71 | return {
72 | provider: 'openai', // Qwen uses OpenAI-compatible tokenization
73 | model,
74 | fallbackToApproximation: true,
75 | hybridTracking: true,
76 | };
77 | }
78 |
79 | // LM Studio models use OpenAI-compatible tokenization
80 | if (model.includes('lmstudio') || model.includes('llama') || model.includes('mistral')) {
81 | return {
82 | provider: 'openai', // LM Studio uses OpenAI-compatible tokenization
83 | model,
84 | fallbackToApproximation: true,
85 | hybridTracking: true,
86 | };
87 | }
88 |
89 | return {
90 | provider: 'default',
91 | model,
92 | fallbackToApproximation: true,
93 | hybridTracking: false,
94 | };
95 | }
96 |
--------------------------------------------------------------------------------
/examples/02-cli-coding-agents/README.md:
--------------------------------------------------------------------------------
1 | # CLI Coding Agents
2 |
3 | > 🖥️ **Memory-powered CLI development with Claude Code and Gemini CLI**
4 |
5 | ## Overview
6 |
7 | This configuration demonstrates Cipher as a memory layer for Claude Code and Gemini CLI. Unlike traditional CLI tools that lose context between sessions, this setup provides persistent memory that grows with your development workflow.
8 |
9 | **Key Benefits:**
10 | - **Persistent memory** across CLI sessions
11 | - **Project-aware assistance** that remembers your codebase
12 | - **Cross-session learning** that builds on previous interactions
13 |
14 | ## Prerequisites
15 |
16 | **Required API Keys:**
17 |
18 | 1. **Anthropic API Key** - Get from [console.anthropic.com](https://console.anthropic.com)
19 | 2. **OpenAI API Key** - Get from [platform.openai.com](https://platform.openai.com) (required for embeddings)
20 | 3. **Google AI API Key** (optional) - Get from [Google AI Studio](https://aistudio.google.com)
21 |
22 | ## Setup
23 |
24 | ### 1. Environment Setup
25 |
26 | Set your API keys:
27 | ```bash
28 | export ANTHROPIC_API_KEY=your_anthropic_api_key
29 | export OPENAI_API_KEY=your_openai_api_key
30 | export GOOGLE_AI_API_KEY=your_google_ai_api_key # optional
31 | ```
32 |
33 | ### 2. Claude Code Configuration
34 |
35 | Create or edit `.mcp.json` in your project root:
36 |
37 | ```json
38 | {
39 | "mcpServers": {
40 | "cipher": {
41 | "command": "cipher",
42 | "args": ["--mode", "mcp", "--agent", "./cipher.yml"],
43 | "env": {
44 | "ANTHROPIC_API_KEY": "your_anthropic_api_key",
45 | "OPENAI_API_KEY": "your_openai_api_key"
46 | }
47 | }
48 | }
49 | }
50 | ```
51 |
52 | ### 3. Gemini CLI Configuration
53 |
54 | Add to your Gemini CLI `settings.json`:
55 | ```json
56 | {
57 | "mcpServers": {
58 | "cipher": {
59 | "command": "cipher",
60 | "args": ["--mode", "mcp", "--agent", "./cipher.yml"],
61 | "env": {
62 | "ANTHROPIC_API_KEY": "your_anthropic_api_key",
63 | "OPENAI_API_KEY": "your_openai_api_key"
64 | }
65 | }
66 | }
67 | }
68 | ```
69 |
70 | ### 4. Test the Setup
71 |
72 | **Claude Code:**
73 | ```bash
74 | claude
75 | > Analyze my project structure and remember the patterns
76 | ```
77 |
78 | **Gemini CLI:**
79 | ```bash
80 | gemini "Use cipher to analyze my codebase"
81 | ```
82 |
83 | ## Usage Examples
84 |
85 | **Project Analysis:**
86 | ```bash
87 | claude> Analyze my project structure and remember the patterns
88 | ```
89 |
90 | **Cross-Session Learning:**
91 | ```bash
92 | # Session 1
93 | claude> Help me debug this React performance issue
94 |
95 | # Session 2 (later)
96 | claude> Apply the optimization techniques we discussed yesterday
97 | ```
98 |
99 | **Code Review:**
100 | ```bash
101 | claude> Review my authentication changes using the security patterns you've learned
102 | ```
103 |
104 | ---
105 |
106 | This setup provides persistent memory for your CLI development workflow, making your coding agents smarter with every interaction.
107 |
--------------------------------------------------------------------------------
/src/core/brain/embedding/types.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Embedding Module Public API Types
3 | *
4 | * This module re-exports all the necessary types and interfaces for the embedding system.
5 | * It provides a simplified, clean API surface for consumers of the embedding module.
6 | *
7 | * The embedding system architecture:
8 | * - Multiple provider support (OpenAI, future: Anthropic, Cohere, etc.)
9 | * - Consistent API across different providers
10 | * - Strong type safety with TypeScript and runtime validation with Zod
11 | * - Factory pattern for creating embedders
12 | * - Manager pattern for lifecycle management
13 | *
14 | * @module embedding/types
15 | *
16 | * @example
17 | * ```typescript
18 | * import type { Embedder, EmbeddingConfig } from './embedding/types.js';
19 | *
20 | * // Configure embedder
21 | * const config: EmbeddingConfig = {
22 | * type: 'openai',
23 | * apiKey: process.env.OPENAI_API_KEY,
24 | * model: 'text-embedding-3-small'
25 | * };
26 | *
27 | * // Use embedder
28 | * const embedder = await createEmbedder(config);
29 | * const embedding = await embedder.embed('Hello world');
30 | * ```
31 | */
32 |
33 | /**
34 | * Re-export core embedding types
35 | *
36 | * These exports provide the complete type system needed to work with
37 | * the embedding module without exposing internal implementation details.
38 | */
39 | export type {
40 | // Core interfaces
41 | Embedder, // Interface for embedding providers
42 | EmbeddingConfig, // Base configuration interface
43 | OpenAIEmbeddingConfig, // OpenAI-specific configuration
44 | BackendConfig, // Union type for all provider configurations
45 |
46 | // Result types
47 | EmbeddingResult, // Single embedding result with metadata
48 | BatchEmbeddingResult, // Batch embedding result with metadata
49 | } from './backend/types.js';
50 |
51 | /**
52 | * Re-export error classes
53 | *
54 | * Comprehensive error hierarchy for embedding operations.
55 | */
56 | export {
57 | EmbeddingError, // Base error class
58 | EmbeddingConnectionError, // Connection-related errors
59 | EmbeddingDimensionError, // Dimension mismatch errors
60 | EmbeddingRateLimitError, // Rate limiting errors
61 | EmbeddingQuotaError, // Quota exceeded errors
62 | EmbeddingValidationError, // Input validation errors
63 | } from './backend/types.js';
64 |
65 | /**
66 | * Re-export factory types
67 | *
68 | * Types related to embedding factory functionality.
69 | */
70 | export type {
71 | EmbeddingFactory, // Factory interface for creating embedders
72 | } from './factory.js';
73 |
74 | /**
75 | * Re-export manager types
76 | *
77 | * Types related to embedding lifecycle management.
78 | */
79 | export type {
80 | HealthCheckResult, // Health check result structure
81 | EmbedderInfo, // Information about embedder instances
82 | EmbeddingStats, // Statistics about embedding operations
83 | } from './manager.js';
84 |
85 | /**
86 | * Re-export configuration types and utilities
87 | *
88 | * Configuration validation and parsing utilities.
89 | */
90 | export type {
91 | EmbeddingEnvConfig, // Environment-based configuration
92 | } from './config.js';
93 |
--------------------------------------------------------------------------------
/src/core/brain/llm/messages/factory.ts:
--------------------------------------------------------------------------------
1 | import { LLMConfig, LLMConfigSchema } from '../config.js';
2 | import { OpenAIMessageFormatter } from './formatters/openai.js';
3 | import { AnthropicMessageFormatter } from './formatters/anthropic.js';
4 | import { AzureMessageFormatter } from './formatters/azure.js';
5 | import { IMessageFormatter } from './formatters/types.js';
6 | import { ContextManager } from './manager.js';
7 | import { logger } from '../../../logger/index.js';
8 | import { EnhancedPromptManager } from '../../systemPrompt/enhanced-manager.js';
9 | import { IConversationHistoryProvider } from './history/types.js';
10 |
11 | function getFormatter(provider: string): IMessageFormatter {
12 | const normalizedProvider = provider.toLowerCase();
13 | let formatter: IMessageFormatter;
14 | switch (normalizedProvider) {
15 | case 'openai':
16 | case 'openrouter':
17 | case 'ollama':
18 | case 'lmstudio':
19 | case 'qwen':
20 | case 'gemini':
21 | case 'deepseek':
22 | formatter = new OpenAIMessageFormatter();
23 | break;
24 | case 'azure':
25 | formatter = new AzureMessageFormatter();
26 | break;
27 | case 'anthropic':
28 | case 'aws':
29 | formatter = new AnthropicMessageFormatter();
30 | break;
31 | default:
32 | throw new Error(
33 | `Unsupported provider: ${provider}. Supported providers: openai, anthropic, openrouter, ollama, lmstudio, qwen, aws, azure, gemini, deepseek`
34 | );
35 | }
36 | return formatter;
37 | }
38 |
39 | /**
40 | * Creates a new ContextManager instance with the appropriate formatter for the specified LLM config
41 | * @param config - The LLM configuration
42 | * @param promptManager - The prompt manager
43 | * @param historyProvider - Optional conversation history provider
44 | * @param sessionId - Optional session ID for history isolation
45 | * @returns A new ContextManager instance
46 | * @throws Error if the config is invalid or the provider is unsupported
47 | */
48 | export function createContextManager(
49 | config: LLMConfig,
50 | promptManager: EnhancedPromptManager,
51 | historyProvider?: IConversationHistoryProvider,
52 | sessionId?: string
53 | ): ContextManager {
54 | try {
55 | LLMConfigSchema.parse(config);
56 | } catch (error) {
57 | logger.error('Invalid LLM configuration provided to createContextManager', {
58 | config,
59 | error: error instanceof Error ? error.message : String(error),
60 | validationIssues: error instanceof Error && 'issues' in error ? error.issues : undefined,
61 | });
62 | throw new Error(
63 | `Invalid LLM configuration: ${error instanceof Error ? error.message : String(error)}`
64 | );
65 | }
66 | const { provider, model } = config;
67 | try {
68 | const formatter = getFormatter(provider);
69 | logger.debug('Created context manager', {
70 | provider: provider.toLowerCase(),
71 | model: model.toLowerCase(),
72 | formatterType: formatter.constructor.name,
73 | });
74 | return new ContextManager(formatter, promptManager, historyProvider, sessionId);
75 | } catch (error) {
76 | logger.error('Failed to create context manager', {
77 | provider,
78 | model,
79 | error: error instanceof Error ? error.message : String(error),
80 | });
81 | throw error;
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/src/core/storage/constants.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Storage Module Constants
3 | *
4 | * Central location for all storage-related constants including
5 | * error messages, log prefixes, timeouts, and configuration defaults.
6 | *
7 | * @module storage/constants
8 | */
9 |
10 | /**
11 | * Log prefixes for consistent logging across the storage module
12 | */
13 | export const LOG_PREFIXES = {
14 | MANAGER: '[StorageManager]',
15 | CACHE: '[StorageManager:Cache]',
16 | DATABASE: '[StorageManager:Database]',
17 | HEALTH: '[StorageManager:Health]',
18 | FACTORY: '[StorageFactory]',
19 | BACKEND: '[StorageBackend]',
20 | } as const;
21 |
22 | /**
23 | * Error messages for the storage module
24 | */
25 | export const ERROR_MESSAGES = {
26 | // Connection errors
27 | CACHE_CONNECTION_FAILED: 'Failed to connect to cache backend',
28 | DATABASE_CONNECTION_FAILED: 'Failed to connect to database backend',
29 | ALREADY_CONNECTED: 'Storage manager is already connected',
30 | NOT_CONNECTED: 'Storage manager is not connected',
31 |
32 | // Backend errors
33 | BACKEND_NOT_FOUND: 'Storage backend not found',
34 | INVALID_BACKEND_TYPE: 'Invalid backend type specified',
35 | MODULE_LOAD_FAILED: 'Failed to load backend module',
36 |
37 | // Operation errors
38 | HEALTH_CHECK_FAILED: 'Health check failed',
39 | OPERATION_TIMEOUT: 'Storage operation timed out',
40 | SERIALIZATION_ERROR: 'Failed to serialize/deserialize data',
41 |
42 | // Configuration errors
43 | INVALID_CONFIG: 'Invalid storage configuration',
44 | MISSING_REQUIRED_CONFIG: 'Missing required configuration',
45 | } as const;
46 |
47 | /**
48 | * Storage operation timeouts (in milliseconds)
49 | */
50 | export const TIMEOUTS = {
51 | CONNECTION: 10000, // 10 seconds
52 | HEALTH_CHECK: 5000, // 5 seconds
53 | OPERATION: 30000, // 30 seconds
54 | SHUTDOWN: 5000, // 5 seconds
55 | } as const;
56 |
57 | /**
58 | * Health check constants
59 | */
60 | export const HEALTH_CHECK = {
61 | KEY: 'storage_manager_health_check',
62 | VALUE: 'ok',
63 | TTL_SECONDS: 10,
64 | } as const;
65 |
66 | /**
67 | * Backend type identifiers
68 | */
69 | export const BACKEND_TYPES = {
70 | // Cache backends
71 | REDIS: 'redis',
72 | MEMCACHED: 'memcached',
73 | IN_MEMORY: 'in-memory',
74 |
75 | // Database backends
76 | SQLITE: 'sqlite',
77 | POSTGRES: 'postgres',
78 | MYSQL: 'mysql',
79 | } as const;
80 |
81 | /**
82 | * Default configuration values
83 | */
84 | export const DEFAULTS = {
85 | MAX_RETRIES: 3,
86 | RETRY_DELAY: 1000, // 1 second
87 | CACHE_TTL: 3600, // 1 hour in seconds
88 | MAX_CONNECTIONS: 10,
89 | IDLE_TIMEOUT: 30000, // 30 seconds
90 | } as const;
91 |
92 | /**
93 | * Storage metrics event names
94 | */
95 | export const METRICS_EVENTS = {
96 | CONNECTION_ATTEMPT: 'storage.connection.attempt',
97 | CONNECTION_SUCCESS: 'storage.connection.success',
98 | CONNECTION_FAILURE: 'storage.connection.failure',
99 | OPERATION_START: 'storage.operation.start',
100 | OPERATION_SUCCESS: 'storage.operation.success',
101 | OPERATION_FAILURE: 'storage.operation.failure',
102 | HEALTH_CHECK: 'storage.health.check',
103 | FALLBACK_USED: 'storage.fallback.used',
104 | } as const;
105 |
--------------------------------------------------------------------------------
/src/core/brain/llm/messages/history/__test__/multi-backend.test.ts:
--------------------------------------------------------------------------------
1 | import { describe, it, expect, beforeEach, vi } from 'vitest';
2 | import { MultiBackendHistoryProvider } from '../multi-backend.js';
3 | import { WALHistoryProvider } from '../wal.js';
4 | import type { IConversationHistoryProvider } from '../types.js';
5 | import type { InternalMessage } from '../../types.js';
6 |
7 | function makeMessage(content: string): InternalMessage {
8 | return { role: 'user', content };
9 | }
10 |
11 | describe('MultiBackendHistoryProvider', () => {
12 | let primary: IConversationHistoryProvider;
13 | let backup: IConversationHistoryProvider;
14 | let wal: WALHistoryProvider;
15 | let provider: MultiBackendHistoryProvider;
16 | let sessionId: string;
17 |
18 | beforeEach(() => {
19 | sessionId = 'test-session';
20 | wal = new WALHistoryProvider();
21 | primary = {
22 | getHistory: vi.fn(async () => []),
23 | saveMessage: vi.fn(async () => {}),
24 | clearHistory: vi.fn(async () => {}),
25 | };
26 | backup = {
27 | getHistory: vi.fn(async () => []),
28 | saveMessage: vi.fn(async () => {}),
29 | clearHistory: vi.fn(async () => {}),
30 | };
31 | provider = new MultiBackendHistoryProvider(primary, backup, wal, 100);
32 | });
33 |
34 | it('writes to primary and WAL synchronously', async () => {
35 | const msg = makeMessage('hello');
36 | await provider.saveMessage(sessionId, msg);
37 | expect((primary.saveMessage as any).mock.calls.length).toBe(1);
38 | expect(wal.getHistory(sessionId).then(h => h.length)).resolves.toBe(1);
39 | });
40 |
41 | it('throws if primary write fails', async () => {
42 | (primary.saveMessage as any).mockRejectedValueOnce(new Error('fail'));
43 | await expect(provider.saveMessage(sessionId, makeMessage('fail'))).rejects.toThrow('fail');
44 | });
45 |
46 | it('throws if WAL write fails', async () => {
47 | wal.saveMessage = vi.fn(async () => {
48 | throw new Error('wal fail');
49 | });
50 | await expect(provider.saveMessage(sessionId, makeMessage('fail'))).rejects.toThrow('wal fail');
51 | });
52 |
53 | it('flushes WAL to backup asynchronously', async () => {
54 | const msg = makeMessage('to-backup');
55 | await provider.saveMessage(sessionId, msg);
56 | // Simulate WAL flush interval
57 | await new Promise(res => setTimeout(res, 200));
58 | expect((backup.saveMessage as any).mock.calls.length).toBeGreaterThan(0);
59 | });
60 |
61 | it('retries backup write if it fails', async () => {
62 | const msg = makeMessage('retry');
63 | let fail = true;
64 | backup.saveMessage = vi.fn(async () => {
65 | if (fail) {
66 | fail = false;
67 | throw new Error('fail');
68 | }
69 | });
70 | await provider.saveMessage(sessionId, msg);
71 | await new Promise(res => setTimeout(res, 300));
72 | expect((backup.saveMessage as any).mock.calls.length).toBeGreaterThan(1);
73 | });
74 |
75 | it('getHistory falls back to backup if primary fails', async () => {
76 | (primary.getHistory as any).mockRejectedValueOnce(new Error('fail'));
77 | (backup.getHistory as any).mockResolvedValueOnce([makeMessage('backup')]);
78 | const result = await provider.getHistory(sessionId);
79 | expect(result[0]).toBeDefined();
80 | expect(result[0]?.content).toBe('backup');
81 | });
82 | });
83 |
--------------------------------------------------------------------------------
/src/core/brain/embedding/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Embedding Module
3 | *
4 | * High-performance text embedding system supporting multiple providers.
5 | * Provides a unified API for generating embeddings with comprehensive
6 | * error handling, retry logic, and lifecycle management.
7 | *
8 | * Features:
9 | * - Multiple provider support (OpenAI, future: Anthropic, Cohere, etc.)
10 | * - Batch operations for efficient processing
11 | * - Type-safe configuration with runtime validation
12 | * - Comprehensive error handling and retry logic
13 | * - Health monitoring and statistics collection
14 | * - Graceful fallback and connection management
15 | *
16 | * @module embedding
17 | *
18 | * @example
19 | * ```typescript
20 | * import { createEmbedder, EmbeddingManager } from './embedding';
21 | *
22 | * // Create a single embedder
23 | * const embedder = await createEmbedder({
24 | * type: 'openai',
25 | * apiKey: process.env.OPENAI_API_KEY,
26 | * model: 'text-embedding-3-small'
27 | * });
28 | *
29 | * // Generate embeddings
30 | * const embedding = await embedder.embed('Hello world');
31 | * const embeddings = await embedder.embedBatch(['Hello', 'World']);
32 | *
33 | * // Use embedding manager for multiple embedders
34 | * const manager = new EmbeddingManager();
35 | * const { embedder: managedEmbedder } = await manager.createEmbedder({
36 | * type: 'openai',
37 | * apiKey: process.env.OPENAI_API_KEY
38 | * });
39 | *
40 | * // Health monitoring
41 | * manager.startHealthChecks();
42 | * const healthResults = await manager.checkAllHealth();
43 | *
44 | * // Cleanup
45 | * await manager.disconnect();
46 | * ```
47 | */
48 |
49 | // Export types
50 | export type {
51 | Embedder,
52 | EmbeddingConfig,
53 | OpenAIEmbeddingConfig,
54 | BackendConfig,
55 | EmbeddingResult,
56 | BatchEmbeddingResult,
57 | HealthCheckResult,
58 | EmbedderInfo,
59 | EmbeddingStats,
60 | EmbeddingEnvConfig,
61 | } from './types.js';
62 |
63 | // Export error classes
64 | export {
65 | EmbeddingError,
66 | EmbeddingConnectionError,
67 | EmbeddingDimensionError,
68 | EmbeddingRateLimitError,
69 | EmbeddingQuotaError,
70 | EmbeddingValidationError,
71 | } from './types.js';
72 |
73 | // Export factory functions
74 | export {
75 | createEmbedder,
76 | createEmbedderFromEnv,
77 | getSupportedProviders,
78 | isProviderSupported,
79 | validateEmbeddingConfiguration,
80 | EMBEDDING_FACTORIES,
81 | type EmbeddingFactory,
82 | } from './factory.js';
83 |
84 | // Export manager
85 | export { EmbeddingManager, SessionEmbeddingState } from './manager.js';
86 |
87 | // Export configuration utilities
88 | export {
89 | parseEmbeddingConfig,
90 | parseEmbeddingConfigFromEnv,
91 | validateEmbeddingConfig,
92 | EmbeddingConfigSchema,
93 | } from './config.js';
94 |
95 | // Export constants for external use
96 | export {
97 | PROVIDER_TYPES,
98 | OPENAI_MODELS,
99 | MODEL_DIMENSIONS,
100 | DEFAULTS,
101 | VALIDATION_LIMITS,
102 | } from './constants.js';
103 |
104 | // Export utilities
105 | export {
106 | getEmbeddingConfigFromEnv,
107 | isEmbeddingConfigAvailable,
108 | getEmbeddingConfigSummary,
109 | validateEmbeddingEnv,
110 | analyzeProviderConfiguration,
111 | } from './utils.js';
112 |
--------------------------------------------------------------------------------
/docs/builtin-tools.md:
--------------------------------------------------------------------------------
1 | # Built-in Tools in Cipher
2 |
3 | This page summarizes the built-in tools that ship with Cipher, grouped by category. It shows each tool’s purpose at a glance and how it’s typically used. Some tools are internal-only; others are agent-accessible.
4 |
5 | Notes
6 | - Some tools depend on embeddings. If embeddings are disabled or unavailable, those tools are skipped automatically.
7 | - Workspace Memory tools require `USE_WORKSPACE_MEMORY=true`.
8 | - Knowledge Graph tools require `KNOWLEDGE_GRAPH_ENABLED=true`.
9 |
10 | ## Memory Tools
11 | - `cipher_extract_and_operate_memory`:
12 | - Extracts knowledge from interactions and immediately applies ADD/UPDATE/DELETE/NONE as one atomic operation. Embedding-dependent.
13 | - `cipher_memory_search`:
14 | - Semantic search over stored knowledge to retrieve relevant facts/code patterns. Embedding-dependent.
15 | - `cipher_store_reasoning_memory`:
16 | - Stores high-quality reasoning traces for future analysis (append-only reflection memory). Embedding-dependent.
17 |
18 | ## Reasoning (Reflection) Tools
19 | - `cipher_extract_reasoning_steps` (internal):
20 | - Extracts structured reasoning steps from user input (explicit and implicit patterns).
21 | - `cipher_evaluate_reasoning` (internal):
22 | - Evaluates a reasoning trace for quality and generates improvement suggestions.
23 | - `cipher_search_reasoning_patterns` (agent-accessible):
24 | - Searches reflection memory for relevant reasoning patterns; supports optional query refinement.
25 |
26 | ## Workspace Memory Tools (team context)
27 | - `cipher_workspace_search`:
28 | - Searches team/project workspace memory for progress, bugs, PR summaries, and collaboration context. Embedding-dependent.
29 | - `cipher_workspace_store`:
30 | - Background tool capturing team and project signals into workspace memory. Embedding-dependent.
31 |
32 | ## Knowledge Graph Tools
33 | - `cipher_add_node`, `cipher_update_node`, `cipher_delete_node`:
34 | - Manage entities (nodes) in the knowledge graph.
35 | - `cipher_add_edge`:
36 | - Create relationships between entities.
37 | - `cipher_search_graph`, `cipher_enhanced_search`:
38 | - Search the graph with basic and enhanced strategies.
39 | - `cipher_get_neighbors`:
40 | - Retrieve related entities around a node.
41 | - `cipher_extract_entities`:
42 | - Extract entities for graph insertion from text.
43 | - `cipher_query_graph`:
44 | - Run graph queries and retrieve structured results.
45 | - `cipher_relationship_manager`:
46 | - Higher-level relationship operations and maintenance.
47 |
48 | ## System Tools
49 | - `cipher_bash` (agent-accessible):
50 | - Execute bash commands. Supports one-off or persistent sessions with working dir and timeout controls.
51 |
52 | ## Operational Notes
53 | - Embedding-dependent tools are automatically excluded in chat-only mode or when embeddings are disabled.
54 | - Workspace tools are included only when `USE_WORKSPACE_MEMORY=true` (and can disable default memory with `DISABLE_DEFAULT_MEMORY=true`).
55 | - Knowledge Graph tools are included only when `KNOWLEDGE_GRAPH_ENABLED=true`.
56 |
57 | For setup and environment flags, see:
58 | - [Configuration](./configuration.md)
59 | - [Workspace Memory](./workspace-memory.md)
60 | - [Vector Stores](./vector-stores.md)
61 | - [Embedding Configuration](./embedding-configuration.md)
62 |
63 |
64 |
--------------------------------------------------------------------------------
/src/core/brain/llm/compression/types.ts:
--------------------------------------------------------------------------------
1 | import { z } from 'zod';
2 | import { InternalMessage } from '../messages/types.js';
3 |
4 | /**
5 | * Configuration schema for compression settings
6 | */
7 | export const CompressionConfigSchema = z.object({
8 | strategy: z.enum(['middle-removal', 'oldest-removal', 'hybrid']),
9 | maxTokens: z.number().positive(),
10 | warningThreshold: z.number().min(0).max(1).default(0.8),
11 | compressionThreshold: z.number().min(0).max(1).default(0.9),
12 | preserveStart: z.number().min(1).default(4),
13 | preserveEnd: z.number().min(1).default(5),
14 | minMessagesToKeep: z.number().min(1).default(4),
15 | });
16 |
17 | export type CompressionConfig = z.infer;
18 |
19 | /**
20 | * Enhanced message interface with compression metadata
21 | */
22 | export interface EnhancedInternalMessage extends InternalMessage {
23 | priority?: 'critical' | 'high' | 'normal' | 'low';
24 | preserveInCompression?: boolean;
25 | tokenCount?: number;
26 | timestamp?: number;
27 | messageId?: string;
28 | }
29 |
30 | /**
31 | * Compression result interface
32 | */
33 | export interface CompressionResult {
34 | compressedMessages: EnhancedInternalMessage[];
35 | removedMessages: EnhancedInternalMessage[];
36 | originalTokenCount: number;
37 | compressedTokenCount: number;
38 | compressionRatio: number;
39 | strategy: string;
40 | timestamp: number;
41 | }
42 |
43 | /**
44 | * Compression statistics for monitoring
45 | */
46 | export interface CompressionStats {
47 | totalCompressions: number;
48 | averageCompressionRatio: number;
49 | messagesRemoved: number;
50 | tokensRemoved: number;
51 | lastCompressionTime: number;
52 | }
53 |
54 | /**
55 | * Compression strategy interface
56 | */
57 | export interface ICompressionStrategy {
58 | readonly name: string;
59 | readonly config: CompressionConfig;
60 |
61 | /**
62 | * Compress messages according to strategy
63 | */
64 | compress(
65 | messages: EnhancedInternalMessage[],
66 | currentTokenCount: number,
67 | targetTokenCount: number
68 | ): Promise;
69 |
70 | /**
71 | * Check if compression is needed
72 | */
73 | shouldCompress(currentTokenCount: number): boolean;
74 |
75 | /**
76 | * Get compression level (0-1) based on current usage
77 | */
78 | getCompressionLevel(currentTokenCount: number): number;
79 |
80 | /**
81 | * Validate that the compression preserves essential messages
82 | */
83 | validateCompression(result: CompressionResult): boolean;
84 | }
85 |
86 | /**
87 | * Message priority levels
88 | */
89 | export enum MessagePriority {
90 | CRITICAL = 'critical',
91 | HIGH = 'high',
92 | NORMAL = 'normal',
93 | LOW = 'low',
94 | }
95 |
96 | /**
97 | * Compression levels based on token usage
98 | */
99 | export enum CompressionLevel {
100 | NONE = 0,
101 | WARNING = 1,
102 | SOFT = 2,
103 | HARD = 3,
104 | EMERGENCY = 4,
105 | }
106 |
107 | /**
108 | * Compression event types for monitoring
109 | */
110 | export interface CompressionEvent {
111 | type: 'compression_started' | 'compression_completed' | 'compression_failed';
112 | timestamp: number;
113 | strategy: string;
114 | beforeTokens: number;
115 | afterTokens?: number;
116 | messagesRemoved?: number;
117 | error?: string;
118 | }
119 |
--------------------------------------------------------------------------------
/src/app/api/utils/security.ts:
--------------------------------------------------------------------------------
1 | // Security utilities for API server
2 |
3 | // Sensitive field patterns that should be redacted
4 | const SENSITIVE_PATTERNS = [
5 | /api[_-]?key/i,
6 | /secret/i,
7 | /token/i,
8 | /password/i,
9 | /auth/i,
10 | /credential/i,
11 | /private[_-]?key/i,
12 | ];
13 |
14 | // Environment variables that should be redacted
15 | const SENSITIVE_ENV_VARS = [
16 | 'OPENAI_API_KEY',
17 | 'ANTHROPIC_API_KEY',
18 | 'OPENROUTER_API_KEY',
19 | 'DATABASE_URL',
20 | 'REDIS_URL',
21 | 'QDRANT_API_KEY',
22 | 'MILVUS_TOKEN',
23 | ];
24 |
25 | /**
26 | * Redacts sensitive information from any object
27 | */
28 | export function redactSensitiveData(obj: any): any {
29 | if (typeof obj !== 'object' || obj === null) {
30 | return obj;
31 | }
32 |
33 | if (Array.isArray(obj)) {
34 | return obj.map(item => redactSensitiveData(item));
35 | }
36 |
37 | const redacted: any = {};
38 |
39 | for (const [key, value] of Object.entries(obj)) {
40 | // Check if key matches sensitive patterns
41 | const isSensitive = SENSITIVE_PATTERNS.some(pattern => pattern.test(key));
42 |
43 | if (isSensitive && typeof value === 'string') {
44 | redacted[key] = maskValue(value);
45 | } else if (typeof value === 'object') {
46 | redacted[key] = redactSensitiveData(value);
47 | } else {
48 | redacted[key] = value;
49 | }
50 | }
51 |
52 | return redacted;
53 | }
54 |
55 | /**
56 | * Masks a string value, showing only first and last few characters
57 | */
58 | function maskValue(value: string): string {
59 | if (!value || value.length <= 8) {
60 | return '***';
61 | }
62 |
63 | const start = value.slice(0, 4);
64 | const end = value.slice(-4);
65 | const middle = '*'.repeat(Math.max(4, value.length - 8));
66 |
67 | return `${start}${middle}${end}`;
68 | }
69 |
70 | /**
71 | * Redacts sensitive environment variables
72 | */
73 | export function redactEnvironmentVars(
74 | env: Record
75 | ): Record {
76 | const redacted: Record = {};
77 |
78 | for (const [key, value] of Object.entries(env)) {
79 | if (SENSITIVE_ENV_VARS.includes(key) && value) {
80 | redacted[key] = maskValue(value);
81 | } else {
82 | redacted[key] = value;
83 | }
84 | }
85 |
86 | return redacted;
87 | }
88 |
89 | /**
90 | * Sanitizes user input to prevent injection attacks
91 | */
92 | export function sanitizeInput(input: string): string {
93 | if (typeof input !== 'string') {
94 | return input;
95 | }
96 |
97 | // Remove null bytes and control characters except newlines and tabs
98 | // eslint-disable-next-line no-control-regex
99 | return input.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, '');
100 | }
101 |
102 | /**
103 | * Validates that a string is a valid UUID
104 | */
105 | export function isValidUUID(uuid: string): boolean {
106 | const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
107 | return uuidRegex.test(uuid);
108 | }
109 |
110 | /**
111 | * Validates session ID format (allows UUID or custom format)
112 | */
113 | export function isValidSessionId(sessionId: string): boolean {
114 | // Allow UUID format or alphanumeric with hyphens/underscores (max 50 chars)
115 | return isValidUUID(sessionId) || /^[a-zA-Z0-9_-]{1,50}$/.test(sessionId);
116 | }
117 |
--------------------------------------------------------------------------------
/src/core/brain/llm/messages/history/multi-backend.ts:
--------------------------------------------------------------------------------
1 | import { IConversationHistoryProvider } from './types.js';
2 | import { InternalMessage } from '../types.js';
3 | import { WALHistoryProvider } from './wal.js';
4 |
5 | export class MultiBackendHistoryProvider implements IConversationHistoryProvider {
6 | private walFlushInterval: NodeJS.Timeout | null = null;
7 |
8 | constructor(
9 | private primary: IConversationHistoryProvider,
10 | private backup: IConversationHistoryProvider,
11 | private wal: WALHistoryProvider,
12 | private flushIntervalMs: number = 5000 // configurable
13 | ) {
14 | this.startWALFlushWorker();
15 | }
16 |
17 | async getHistory(sessionId: string, limit?: number): Promise {
18 | try {
19 | return await this.primary.getHistory(sessionId, limit);
20 | } catch (err) {
21 | console.error('[MultiBackend] Primary getHistory failed, falling back to backup:', err);
22 | return await this.backup.getHistory(sessionId, limit);
23 | }
24 | }
25 |
26 | async saveMessage(sessionId: string, message: InternalMessage): Promise {
27 | try {
28 | await this.primary.saveMessage(sessionId, message);
29 | } catch (err) {
30 | console.error('[MultiBackend] Primary saveMessage failed:', err);
31 | throw err; // Do not proceed if primary fails
32 | }
33 | try {
34 | await this.wal.saveMessage(sessionId, message);
35 | } catch (err) {
36 | console.error('[MultiBackend] WAL saveMessage failed:', err);
37 | throw err; // WAL is critical for durability
38 | }
39 | // WAL will be flushed to backup asynchronously
40 | }
41 |
42 | async clearHistory(sessionId: string): Promise {
43 | try {
44 | await this.primary.clearHistory(sessionId);
45 | } catch (err) {
46 | console.error('[MultiBackend] Primary clearHistory failed:', err);
47 | }
48 | try {
49 | await this.backup.clearHistory(sessionId);
50 | } catch (err) {
51 | console.error('[MultiBackend] Backup clearHistory failed:', err);
52 | }
53 | try {
54 | await this.wal.clearHistory(sessionId);
55 | } catch (err) {
56 | console.error('[MultiBackend] WAL clearHistory failed:', err);
57 | }
58 | }
59 |
60 | private startWALFlushWorker() {
61 | if (this.walFlushInterval) return;
62 | this.walFlushInterval = setInterval(async () => {
63 | try {
64 | const pending = await this.wal.getPendingEntries();
65 | for (const { sessionId, message } of pending) {
66 | try {
67 | await this.backup.saveMessage(sessionId, message);
68 | await this.wal.markFlushed(sessionId, message);
69 | } catch (err) {
70 | console.error('[MultiBackend] Backup saveMessage failed during WAL flush:', err);
71 | // Do not mark as flushed, will retry
72 | }
73 | }
74 | } catch (err) {
75 | console.error('[MultiBackend] WAL flush worker error:', err);
76 | }
77 | }, this.flushIntervalMs);
78 | }
79 |
80 | async disconnect() {
81 | if (this.walFlushInterval) {
82 | clearInterval(this.walFlushInterval);
83 | this.walFlushInterval = null;
84 | }
85 | if (typeof (this.primary as any).disconnect === 'function') {
86 | await (this.primary as any).disconnect();
87 | }
88 | if (typeof (this.backup as any).disconnect === 'function') {
89 | await (this.backup as any).disconnect();
90 | }
91 | if (typeof (this.wal as any).disconnect === 'function') {
92 | await (this.wal as any).disconnect();
93 | }
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: Code Quality Check
2 |
3 | on:
4 | pull_request:
5 | branches: [main]
6 | push:
7 | branches: [main]
8 |
9 | jobs:
10 | lint:
11 | name: 'ESLint'
12 | runs-on: ubuntu-latest
13 | steps:
14 | - name: Checkout code
15 | uses: actions/checkout@v4
16 |
17 | - name: Setup pnpm
18 | uses: pnpm/action-setup@v4
19 | with:
20 | version: ">=9.14.0"
21 |
22 | - name: Set up Node.js
23 | uses: actions/setup-node@v4
24 | with:
25 | node-version: '20'
26 | cache: 'pnpm'
27 |
28 | - name: Install dependencies
29 | run: pnpm install --frozen-lockfile
30 |
31 | - name: Run ESLint
32 | run: pnpm run lint
33 | env:
34 | CI: true
35 |
36 | typecheck:
37 | name: 'TypeScript'
38 | runs-on: ubuntu-latest
39 | steps:
40 | - name: Checkout code
41 | uses: actions/checkout@v4
42 |
43 | - name: Setup pnpm
44 | uses: pnpm/action-setup@v4
45 | with:
46 | version: ">=9.14.0"
47 |
48 | - name: Set up Node.js
49 | uses: actions/setup-node@v4
50 | with:
51 | node-version: '20'
52 | cache: 'pnpm'
53 |
54 | - name: Install dependencies
55 | run: pnpm install --frozen-lockfile
56 |
57 | - name: Run TypeScript check
58 | run: pnpm run typecheck
59 |
60 | format-check:
61 | name: 'Format Check'
62 | runs-on: ubuntu-latest
63 | steps:
64 | - name: Checkout code
65 | uses: actions/checkout@v4
66 |
67 | - name: Setup pnpm
68 | uses: pnpm/action-setup@v4
69 | with:
70 | version: ">=9.14.0"
71 |
72 | - name: Set up Node.js
73 | uses: actions/setup-node@v4
74 | with:
75 | node-version: '20'
76 | cache: 'pnpm'
77 |
78 | - name: Install dependencies
79 | run: pnpm install --frozen-lockfile
80 |
81 | - name: Check formatting
82 | run: pnpm run format:check
83 |
84 | test:
85 | name: 'Tests'
86 | runs-on: ubuntu-latest
87 | steps:
88 | - name: Checkout code
89 | uses: actions/checkout@v4
90 |
91 | - name: Setup pnpm
92 | uses: pnpm/action-setup@v4
93 | with:
94 | version: ">=9.14.0"
95 |
96 | - name: Set up Node.js
97 | uses: actions/setup-node@v4
98 | with:
99 | node-version: '20'
100 | cache: 'pnpm'
101 |
102 | - name: Install dependencies
103 | run: pnpm install --frozen-lockfile
104 |
105 | - name: Run tests
106 | run: pnpm run test:ci
107 | env:
108 | CI: true
109 |
110 | build:
111 | name: 'Build'
112 | runs-on: ubuntu-latest
113 | steps:
114 | - name: Checkout code
115 | uses: actions/checkout@v4
116 |
117 | - name: Setup pnpm
118 | uses: pnpm/action-setup@v4
119 | with:
120 | version: ">=9.14.0"
121 |
122 | - name: Set up Node.js
123 | uses: actions/setup-node@v4
124 | with:
125 | node-version: '20'
126 | cache: 'pnpm'
127 |
128 | - name: Install dependencies
129 | run: pnpm install --frozen-lockfile
130 |
131 | - name: Build project
132 | run: pnpm run build
133 |
--------------------------------------------------------------------------------
/src/core/brain/tools/definitions/knowledge_graph/get-neighbors.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Get Neighbors Tool
3 | *
4 | * Finds neighboring nodes in the knowledge graph with direction and type filtering.
5 | * Supports filtering by edge types and limiting the number of results.
6 | */
7 |
8 | import { InternalTool } from '../../types.js';
9 |
10 | /**
11 | * Input schema for the get neighbors tool
12 | */
13 | interface GetNeighborsInput {
14 | /** ID of the node to get neighbors for */
15 | nodeId: string;
16 | /** Direction of relationships ('in', 'out', 'both') */
17 | direction?: 'in' | 'out' | 'both';
18 | /** Optional edge types to filter by */
19 | edgeTypes?: string[];
20 | /** Maximum number of neighbors to return */
21 | limit?: number;
22 | }
23 |
24 | /**
25 | * Get neighbors of a node in the knowledge graph
26 | *
27 | * @param args - Search parameters
28 | * @param context - Execution context with services
29 | * @returns List of neighbor nodes with their connecting edges
30 | */
31 | async function getNeighborsHandler(
32 | args: GetNeighborsInput,
33 | context?: any
34 | ): Promise<{ success: boolean; message: string; neighbors?: Array<{ node: any; edge: any }> }> {
35 | try {
36 | // Validate input
37 | if (!args.nodeId || typeof args.nodeId !== 'string') {
38 | return {
39 | success: false,
40 | message: 'Node ID must be a non-empty string',
41 | };
42 | }
43 | const direction = args.direction || 'both';
44 | const limit = args.limit || 10;
45 | const kgManager = context?.services?.knowledgeGraphManager;
46 | if (!kgManager) {
47 | return {
48 | success: false,
49 | message: 'KnowledgeGraphManager not available in context.services',
50 | };
51 | }
52 | const graph = kgManager.getGraph();
53 | if (!graph) {
54 | return {
55 | success: false,
56 | message: 'Knowledge graph backend is not connected',
57 | };
58 | }
59 | const neighbors = await graph.getNeighbors(args.nodeId, direction, args.edgeTypes, limit);
60 | return {
61 | success: true,
62 | message: 'Neighbors retrieved',
63 | neighbors,
64 | };
65 | } catch (error) {
66 | return {
67 | success: false,
68 | message: `Failed to get neighbors: ${(error as Error).message}`,
69 | };
70 | }
71 | }
72 |
73 | /**
74 | * Get Neighbors Tool Definition
75 | */
76 | export const getNeighborsTool: InternalTool = {
77 | name: 'get_neighbors',
78 | description: 'Get neighboring nodes in the knowledge graph with optional filtering',
79 | category: 'knowledge_graph',
80 | internal: true,
81 | handler: getNeighborsHandler,
82 | parameters: {
83 | type: 'object',
84 | properties: {
85 | nodeId: {
86 | type: 'string',
87 | description: 'ID of the node to get neighbors for',
88 | },
89 | direction: {
90 | type: 'string',
91 | enum: ['in', 'out', 'both'],
92 | description: 'Direction of relationships to traverse',
93 | default: 'both',
94 | },
95 | edgeTypes: {
96 | type: 'array',
97 | items: { type: 'string' },
98 | description: 'Optional edge types to filter by (e.g., ["DEPENDS_ON", "CALLS"])',
99 | },
100 | limit: {
101 | type: 'number',
102 | description: 'Maximum number of neighbors to return',
103 | minimum: 1,
104 | maximum: 100,
105 | default: 10,
106 | },
107 | },
108 | required: ['nodeId'],
109 | },
110 | };
111 |
--------------------------------------------------------------------------------
/src/core/brain/tools/definitions/knowledge_graph/delete-node.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Delete Node Tool
3 | *
4 | * Deletes a node and its relationships from the knowledge graph.
5 | */
6 |
7 | import { InternalTool, InternalToolContext } from '../../types.js';
8 | import { logger } from '../../../../logger/index.js';
9 |
10 | export const deleteNodeTool: InternalTool = {
11 | name: 'delete_node',
12 | category: 'knowledge_graph',
13 | internal: true,
14 | description: 'Delete a node and its relationships from the knowledge graph.',
15 | version: '1.0.0',
16 | parameters: {
17 | type: 'object',
18 | properties: {
19 | id: {
20 | type: 'string',
21 | description: 'Node ID to delete',
22 | },
23 | },
24 | required: ['id'],
25 | },
26 | handler: async (
27 | args: { id: string },
28 | context?: InternalToolContext
29 | ): Promise<{
30 | success: boolean;
31 | message: string;
32 | nodeId?: string;
33 | timestamp: string;
34 | }> => {
35 | try {
36 | logger.info('DeleteNode: Processing delete request', {
37 | nodeId: args.id,
38 | });
39 |
40 | // Validate input
41 | if (!args.id || typeof args.id !== 'string' || args.id.trim().length === 0) {
42 | return {
43 | success: false,
44 | message: 'Node ID must be a non-empty string',
45 | timestamp: new Date().toISOString(),
46 | };
47 | }
48 |
49 | const kgManager = context?.services?.knowledgeGraphManager;
50 | if (!kgManager) {
51 | return {
52 | success: false,
53 | message: 'KnowledgeGraphManager not available in context.services',
54 | nodeId: args.id,
55 | timestamp: new Date().toISOString(),
56 | };
57 | }
58 |
59 | const graph = kgManager.getGraph();
60 | if (!graph) {
61 | return {
62 | success: false,
63 | message: 'Knowledge graph backend is not connected',
64 | nodeId: args.id,
65 | timestamp: new Date().toISOString(),
66 | };
67 | }
68 |
69 | // Check if node exists before deletion (optional, but provides better error messages)
70 | const existingNode = await graph.getNode(args.id.trim());
71 | if (!existingNode) {
72 | logger.warn('DeleteNode: Attempting to delete non-existent node', {
73 | nodeId: args.id,
74 | });
75 | // Still return success since the desired state (node not existing) is achieved
76 | return {
77 | success: true,
78 | message: `Node '${args.id}' does not exist (already deleted or never existed)`,
79 | nodeId: args.id,
80 | timestamp: new Date().toISOString(),
81 | };
82 | }
83 |
84 | // Perform deletion
85 | await graph.deleteNode(args.id.trim());
86 |
87 | logger.info('DeleteNode: Node deleted successfully', {
88 | nodeId: args.id,
89 | nodeLabels: existingNode.labels,
90 | });
91 |
92 | return {
93 | success: true,
94 | nodeId: args.id,
95 | message: 'Node deleted from knowledge graph',
96 | timestamp: new Date().toISOString(),
97 | };
98 | } catch (error) {
99 | const errorMessage = error instanceof Error ? error.message : String(error);
100 | logger.error('DeleteNode: Failed to delete node', {
101 | error: errorMessage,
102 | nodeId: args.id,
103 | });
104 |
105 | return {
106 | success: false,
107 | nodeId: args.id,
108 | message: `Failed to delete node: ${errorMessage}`,
109 | timestamp: new Date().toISOString(),
110 | };
111 | }
112 | },
113 | };
114 |
--------------------------------------------------------------------------------
/src/core/brain/llm/messages/history/__test__/factory.test.ts:
--------------------------------------------------------------------------------
1 | import { describe, it, expect } from 'vitest';
2 | import { StorageManager } from '../../../../../storage/manager.js';
3 | import { createDatabaseHistoryProvider } from '../factory.js';
4 | import { DatabaseHistoryProvider } from '../database.js';
5 |
6 | describe('createDatabaseHistoryProvider', () => {
7 | it('should create a DatabaseHistoryProvider instance for in-memory backend', async () => {
8 | const config = {
9 | cache: { type: 'in-memory' as const },
10 | database: { type: 'in-memory' as const },
11 | };
12 | const storageManager = new StorageManager(config);
13 | await storageManager.connect();
14 | const provider = createDatabaseHistoryProvider(storageManager);
15 | expect(provider).toBeInstanceOf(DatabaseHistoryProvider);
16 | });
17 | it('should create a DatabaseHistoryProvider instance for in-memory backend', async () => {
18 | const config = {
19 | cache: { type: 'in-memory' as const },
20 | database: { type: 'in-memory' as const },
21 | };
22 | const storageManager = new StorageManager(config);
23 | await storageManager.connect();
24 | const provider = createDatabaseHistoryProvider(storageManager);
25 | expect(provider).toBeInstanceOf(DatabaseHistoryProvider);
26 | });
27 |
28 | it('should create a DatabaseHistoryProvider instance for SQLite backend', async () => {
29 | let hasSqlite = true;
30 | try {
31 | require('better-sqlite3');
32 | } catch {
33 | hasSqlite = false;
34 | }
35 | if (!hasSqlite) {
36 | console.warn('Skipping SQLite test: better-sqlite3 not installed');
37 | return;
38 | }
39 | const config = {
40 | cache: { type: 'in-memory' as const },
41 | database: { type: 'sqlite' as const, path: ':memory:' },
42 | };
43 | const storageManager = new StorageManager(config);
44 | await storageManager.connect();
45 | const provider = createDatabaseHistoryProvider(storageManager);
46 | expect(provider).toBeInstanceOf(DatabaseHistoryProvider);
47 | });
48 |
49 | it('should create a DatabaseHistoryProvider instance for PostgreSQL backend', async () => {
50 | let hasPg = true;
51 | try {
52 | require('pg');
53 | } catch {
54 | hasPg = false;
55 | }
56 | if (!hasPg) {
57 | console.warn('Skipping PostgreSQL test: pg not installed');
58 | return;
59 | }
60 | const config = {
61 | cache: { type: 'in-memory' as const },
62 | database: {
63 | type: 'postgres' as const,
64 | url: 'postgres://testuser:testpass@localhost:5432/testdb',
65 | },
66 | };
67 | const storageManager = new StorageManager(config);
68 | try {
69 | await storageManager.connect();
70 | } catch (err) {
71 | console.warn('Skipping PostgreSQL test: could not connect to database', err);
72 | return;
73 | }
74 | const provider = createDatabaseHistoryProvider(storageManager);
75 | expect(provider).toBeInstanceOf(DatabaseHistoryProvider);
76 | }, 20000);
77 |
78 | it('should throw for misconfigured backend', async () => {
79 | const config = { cache: { type: 'in-memory' as const }, database: { type: 'unknown' as any } };
80 | // StorageManager constructor should throw synchronously for invalid config
81 | expect(() => new StorageManager(config)).toThrow(/Invalid backend type/);
82 | });
83 | it('should throw for misconfigured backend', async () => {
84 | const config = { cache: { type: 'in-memory' as const }, database: { type: 'unknown' as any } };
85 | // StorageManager constructor should throw synchronously for invalid config
86 | expect(() => new StorageManager(config)).toThrow(/Invalid backend type/);
87 | });
88 | });
89 |
--------------------------------------------------------------------------------
/src/core/brain/llm/services/__test__/azure.test.ts:
--------------------------------------------------------------------------------
1 | import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
2 | import { AzureService } from '../azure.js';
3 | import { MCPManager } from '../../../../mcp/manager.js';
4 | import { ContextManager } from '../../messages/manager.js';
5 | import { UnifiedToolManager } from '../../../tools/unified-tool-manager.js';
6 | import { OpenAIClient, AzureKeyCredential } from '@azure/openai';
7 |
8 | vi.mock('@azure/openai', () => ({
9 | OpenAIClient: vi.fn(),
10 | AzureKeyCredential: vi.fn((key: string) => ({ key })),
11 | }));
12 |
13 | describe('AzureService', () => {
14 | let azureService: AzureService;
15 | let mockMcpManager: MCPManager;
16 | let mockContextManager: ContextManager;
17 | let mockToolManager: UnifiedToolManager;
18 | let mockOpenAIClient: any;
19 |
20 | beforeEach(() => {
21 | vi.clearAllMocks();
22 | process.env.AZURE_OPENAI_ENDPOINT = 'https://test.openai.azure.com';
23 | process.env.AZURE_OPENAI_API_KEY = 'test-api-key';
24 | mockMcpManager = {} as MCPManager;
25 | mockContextManager = {
26 | addUserMessage: vi.fn(),
27 | addAssistantMessage: vi.fn(),
28 | getAllFormattedMessages: vi.fn().mockResolvedValue([]),
29 | getFormattedMessage: vi.fn().mockResolvedValue([]),
30 | getSystemPrompt: vi.fn().mockResolvedValue('Test system prompt'),
31 | } as unknown as ContextManager;
32 | mockToolManager = {
33 | getToolsForProvider: vi.fn().mockResolvedValue([]),
34 | getAllTools: vi.fn().mockResolvedValue({}),
35 | executeTool: vi.fn().mockResolvedValue('tool result'),
36 | } as unknown as UnifiedToolManager;
37 | mockOpenAIClient = { getChatCompletions: vi.fn(), streamChatCompletions: vi.fn() };
38 | (OpenAIClient as any).mockImplementation(() => mockOpenAIClient);
39 | (AzureKeyCredential as any).mockImplementation((key: string) => ({ key }));
40 | azureService = new AzureService(
41 | 'gpt-4-deployment',
42 | mockMcpManager,
43 | mockContextManager,
44 | mockToolManager,
45 | 10,
46 | { endpoint: 'https://test.openai.azure.com' }
47 | );
48 | });
49 |
50 | afterEach(() => {
51 | delete process.env.AZURE_OPENAI_ENDPOINT;
52 | delete process.env.AZURE_OPENAI_API_KEY;
53 | });
54 |
55 | describe('constructor', () => {
56 | it('should initialize with provided configuration', () => {
57 | expect(OpenAIClient).toHaveBeenCalledWith(
58 | 'https://test.openai.azure.com',
59 | expect.objectContaining({ key: 'test-api-key' })
60 | );
61 | });
62 | });
63 |
64 | describe('generate', () => {
65 | it('should call addUserMessage and return string', async () => {
66 | mockOpenAIClient.getChatCompletions.mockResolvedValue({
67 | choices: [{ message: { content: 'response' } }],
68 | });
69 | // Patch getAIResponse to return a compatible response
70 | (azureService as any).getAIResponse = vi
71 | .fn()
72 | .mockResolvedValue({ choices: [{ message: { content: 'response' } }] });
73 | const result = await azureService.generate('hello');
74 | expect(mockContextManager.addUserMessage).toHaveBeenCalledWith('hello', undefined);
75 | expect(typeof result).toBe('string');
76 | expect(result).toBe('response');
77 | });
78 | });
79 |
80 | describe('directGenerate', () => {
81 | it('should return string response', async () => {
82 | // Patch directGenerate to simulate a response
83 | (azureService as any).directGenerate = vi.fn().mockResolvedValue('direct response');
84 | const result = await azureService.directGenerate('hi');
85 | expect(typeof result).toBe('string');
86 | expect(result).toBe('direct response');
87 | });
88 | });
89 | });
90 |
--------------------------------------------------------------------------------