refactor: migrate LLM layer to AI SDK and restructure core - #217
Merged
Conversation
Replace the openai SDK with Vercel AI SDK: - streamText-based streaming (onChunk now streams to the TUI) - messages use AI SDK CoreMessage; responses are GenerateTextResult directly - remove hand-written wire protocol layer (toOpenAIMessage, buildChatCompletionRequest, response parsing) - provider factory registry (ai_provider.ts) dispatches by provider name, with openai-compatible as the default wire path - reasoning_content handled natively by the openai-compatible provider - TokenUsage replaced by AI SDK LanguageModelUsage Restructure core source into domain modules: - llm/ (provider registry, streaming, model profiles) - session/ (runtime, hooks, defaults, compaction, slash commands) - history/ (jsonl sinks, parsers, index, workspace) - prompt/ (system prompt, memory injection) - skills/, mcp/
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
- agent/loop.ts: ReAct loop with session state, usage, permissions, abort handling (session_runtime + session_runtime_helpers merged) - agent/messages.ts: message construction and LLM result normalization - stableStringify stays with the loop (tools/ has its own copy) - remove the 'helpers' bag directory; every module now has a clear name
- features/ holds user-facing capabilities built on contracts: slash commands, file suggestions, history parser/index (resume view) - history/ keeps only the write-side JSONL sink used by the loop - workspace.ts moves to utils/ (shared by skills_admin and history features)
Each feature is a directory with its own index.ts (file_suggestions/, history/, slash/) so module boundaries and exports are explicit.
- createHistoryEvent (loop behavior) moves into agent/loop.ts - JsonlHistorySink joins the features/history module (write + read in one place); the composition root injects it, the loop only knows the HistorySink contract - removes the standalone history/ directory from the core root
The tools package had no build boundary (everything is bundled into the tui dist), no publish target, and core depended on it without declaring it. The agent runtime core (orchestrator/approval/router/runtime) plus the 24 built-in tool implementations now live under core/src/tools/; aliases unified to @memo/core/tools/*.
- tools are now declared with execute wrappers (agent/sdk_tools.ts): approval (white-list → classifier → fingerprint) runs inside execute, UI decision awaited inline; deny returns execution-denied - per-step concurrency gate (agent/step_gate.ts) serializes mutating tools and skips pending tools after a denial - loop consumes llmResult.toolResults instead of orchestrating execution; orchestrator execution implementation removed (types kept) - Tool contract output switched from MCP CallToolResult to MemoToolOutput - behavior notes: action event emitted after step completes; observation meta drops duration_ms; action ids are SDK toolCallIds
- McpClientPool now connects via createMCPClient (http transport config with memo's OAuth provider adapted, stdio via Experimental_StdioMCPTransport) - connection.tools is an AI SDK ToolSet (own execute, JSON-RPC under the hood) - McpToolRegistry derives tools from the tool set; cache store kept - oauth.ts credentials/login CLI untouched; listResourceTemplates confirmed on the SDK client
- CallLLMOptions.tools removed; toolContext presence is the tool enable/disable switch (compaction passes no context) - ToolUseBlock replaced by AI SDK ToolCallPart end-to-end (normalizeLLMResponse returns raw toolCalls) - router.generateToolDefinitions deleted - streamText allowSystemInMessages: system messages are part of the memo history (initial prompt + mid-loop warnings)
- ParsedAssistant.thinking now comes from SDK-separated reasoning (buildThinking think-tag extraction was dead code post-streaming) - api_types TokenUsageSummary aliases AI SDK LanguageModelUsage; history parser drops legacy prompt/completion compatibility reads
ApiEnvelope/ApiError*, WsServerEvent/LiveSessionState/QueuedInputItem, Workspace* and SessionRuntimeBadge were planned web-console API types with zero references (no HTTP/WS server exists). Kept the history-viewing DTOs (SessionListItem/SessionDetail/...) and admin records (SkillRecord/ McpServerRecord) that are actually consumed.
Drop @dqbd/tiktoken in favor of the codex-style byte heuristic (ceil(utf8 bytes / 4)) for prompt size estimation. Exact token counts come from API usage (LanguageModelUsage); the estimator only drives compaction triggers, context overflow checks, and message retention budgets. Also fix api_types.ts missing import and TUI historyParser reading stale LanguageModelUsage field names.
Pass a per-turn thinking override into the LLM call and thread reasoningContent through tool call and final step events. Adds AgentSession.setThinking() for the TUI's thinking toggle.
- TurnCell shows in-progress assistantText while the turn streams - Remove Ctrl+C handlers that Ink's exitOnCtrlC default makes unreachable - Reset the visible timeline when switching tool permission mode so UI stays in sync with the recreated session - Surface --prev session failures on stderr instead of exiting silently - Simplify: drop the dispatch wrapper, stable-header/cwd refs, and as-any backgroundColor hacks; extract a busy/approval guard helper - Guard Ctrl+D exit behind a double-press; hint Tab thinking in footer
Remove SOUL.md loading from system prompt composition and its tests; keep memoHome option since skills discovery still uses it.
Split test and coverage steps so test failures still fail CI while coverage shortfalls only print a warning; set Codecov status checks to informational.
- pass user custom tools through with full definitions (schema, mutating, parallel flags) instead of downgrading to simplified registration - drop tool-description injection into the system prompt; the AI SDK tools schema is the single channel (removes orphaned router helpers) - replace disabled-tool string protocol with a typed MemoToolOutput 'skipped' variant so the loop never matches on message text - export oauth helpers from the main entry; tui imports only from '@memo/core' (no subpath deep imports remain) - move sdk_tools and step_gate under tools/ so llm no longer imports from agent - split loop.ts helpers into utils (usage, errors, serialize, title) and agent/constants; unify the three stableStringify implementations - fix ToolCallPart.name -> toolName usage in the agent loop
- remove unreachable resetActionRepetition/break in the ReAct loop (tool branch always exits via break/continue, so the final-reply condition is always reached with zero tool calls) - close(): split session_end gating (empty sessions skip the event) from sink cleanup, which always runs; hasContent was always true - use a local stepTrace instead of re-indexing steps for observation - calculateUsagePercent: drop promptTokens guard (countMessages is always positive), keep contextWindow guard (floor of 0<config<1 is 0) - ai_stream: drop duplicate empty-registry check already done inside buildSdkTools
Surface model thinking live via onReasoningChunk, promote the streamed trace to the final step thinking, and simplify the step gate to a FIFO shared/exclusive queue.
Remove the slash command, backend detection, task prompt, and docs for the /review GitHub PR review workflow.
- scan user-level ~/.claude, ~/.codex, ~/.agents skills roots - dedup skills by SKILL.md sha256, keeping aliases for active_skills - lazy-load skill bodies via read_skill tool with a budgeted directory - add memo skills list/read CLI commands - fix parsing of YAML block-scalar descriptions (description: >-)
Reference codex's /init design: load the init_agents task prompt and run a single agent turn with a whitelist approval gate that only permits writing <cwd>/AGENTS.md. Idempotent guard skips when AGENTS.md exists. Also disable TUI /init while a turn is running and instruct the model not to overwrite an existing AGENTS.md.
Footer shortcut and /help text showed "Esc Esc cancel", which read as a typo. Clarify the double-press with "Esc×2" in both places.
Implements #221. Resolve composer border, prompt, and cursor color from a pure helper with priority: disabled (gray) > running (yellow) > thinking (amber) > idle (cyan).
Memo now ships a built-in skill-creator skill (SKILL.md plus Node init/validate scripts) that is installed into $MEMO_HOME/skills at session start. Installation is non-destructive: untouched copies are upgraded across releases via a tree-fingerprint marker, user-modified or foreign directories are left alone. Bundled into dist via tsup.
Reference Codex's compaction design: - Cap the compaction request at the trigger threshold and drop the oldest messages when it would exceed it; halve the budget on a failed attempt and retry once before degrading silently. - Keep the tail of long messages when truncating (tool results carry their output at the end). - Feed the compaction model the original system prompt alongside the compaction instructions, and adopt Codex's full summary prefix, so the handoff keeps global constraints in mind. - Parse context_compact events into SessionDetail.compactionSummary and re-inject the latest summary into --prev restored sessions, preserving context that was compacted away. - Replace the byte/4 token estimate with js-tiktoken (cl100k_base, lite build) plus a byte-estimate fallback; structure-aware message counting.
Persist provider, model, thinking mode, context window, and tool permission mode at session start, parse them back from the history log, and restore them in the TUI when resuming a session. Also queue streaming updates so the visible state settles before follow-up renders.
Run format+lint, tests+coverage, and build as separate jobs so they execute concurrently instead of serially. Also add the missing lint step to CI.
Bump CI from node 20 to 22 across test, release, and site deploy workflows, matching the declared engines range, and update the contributing guide.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
概述
用 Vercel AI SDK 替代 openai SDK,并将 core 源码按领域重组。
LLM 层迁移(openai SDK → AI SDK)
streamText,onChunk链路首次真正生效(TUI 打字机效果)ChatMessage直接定义为ModelMessage(parts 结构),手写协议层(toOpenAIMessage、buildChatCompletionRequest、响应解析、parallel_tool_calls注入)全部删除LLMResult(AI SDK 字段子集),删除LLMResponse/ContentBlock/stop_reason/TokenUsage封装;ai_stream.ts从 150 行瘦身到 80 行llm/ai_provider.ts):按 provider.name 分派,默认 openai-compatible(deepseek/任意 base_url 行为不变),anthropic 留接入点,为未来 Responses/Messages API 铺路reasoning_content由 openai-compatible 原生处理(流式 reasoning-delta / 请求侧 reasoning part 回传),零 hackcore 目录重组
runtime/38 个文件按领域拆分:llm/— provider 注册表、流式调用、模型能力session/— ReAct 循环、hooks、默认装配、上下文压缩、slash 命令history/— JSONL sink、解析、索引、workspaceprompt/— 系统提示词、memory 注入skills/、mcp/验证
待实机验证
pnpm start)🤖 Generated with Claude Code