UNPKG

@kontextmind/kxm

Version:

KXM local-first multi-agent orchestration and operator dashboard

287 lines (259 loc) • 9.9 kB
// @ts-nocheck — vendored snapshot; host tsconfig is stricter than upstream. import { createRequire } from "node:module"; import type { Context, Model, SimpleStreamOptions } from "./pi-compat.ts"; import { createAssistantMessageEventStream, type AssistantMessageEventStream } from "./pi-compat.ts"; import { claudeCodeModelId, DEFAULT_LONG_CONTEXT, resolveEffort, type LongContextSettings, } from "./models.ts"; import { claudeCodeSettings, loadConfig, longContextFromConfig, type ClaudeBridgeConfig } from "./config.ts"; import { extractUserPrompt, extractUserPromptBlocks } from "./convert.ts"; import { createToolServer } from "./mcp-server.ts"; import { makePromptStream, userMessage } from "./prompt-stream.ts"; import { QueryContext, ctx } from "./query-state.ts"; import { claimCurrentPiStream, consumeQuery, finalizeCurrentStream, markStreamComplete, } from "./stream.ts"; import { extractAllToolResults, MCP_SERVER_NAME, resolveMcpTools, type McpResult, } from "./tools.ts"; export const CC_CHILD_ENV = { ENABLE_CLAUDEAI_MCP_SERVERS: "0", DISABLE_AUTO_COMPACT: "1", } as const; export const CLAUDE_MD_EXCLUDES = ["**/CLAUDE.md", "**/.claude/rules/**"]; const require = createRequire(import.meta.url); const activeQueryContexts = new Set<QueryContext>(); export type QueryHandle = AsyncIterable<unknown> & { interrupt?: () => Promise<unknown> | unknown; close?: () => void; }; export type QueryFactory = (args: { prompt: unknown; options: Record<string, unknown> }) => QueryHandle; export type StreamDeps = { query?: QueryFactory; config?: ClaudeBridgeConfig; longContext?: LongContextSettings; nowCwd?: () => string; }; let lastSessionId: string | undefined; export function resetClaudeBridgeSession(): void { lastSessionId = undefined; activeQueryContexts.clear(); } export function getClaudeBridgeSessionId(): string | undefined { return lastSessionId; } function buildMcpServers(tools: Context["tools"], queryCtx: QueryContext) { if (!tools?.length) return undefined; const mcpTools = tools.map((tool) => ({ name: tool.name, description: tool.description, inputSchema: tool.parameters, handler: async (toolCallId: string) => { if (queryCtx.pendingResults.has(toolCallId)) { const result = queryCtx.pendingResults.get(toolCallId)!; queryCtx.pendingResults.delete(toolCallId); return result; } return new Promise<McpResult>((resolve) => { queryCtx.pendingToolCalls.set(toolCallId, { toolName: tool.name, resolve }); }); }, })); return { [MCP_SERVER_NAME]: createToolServer(MCP_SERVER_NAME, mcpTools) }; } export function deliverToolResults(c: QueryContext, results: McpResult[]): void { for (const result of results) { const id = result.toolCallId; if (id && c.pendingToolCalls.has(id)) { const pending = c.pendingToolCalls.get(id)!; c.pendingToolCalls.delete(id); pending.resolve(result); } else if (id) { c.pendingResults.set(id, result); } } } function contextForToolResults(results: McpResult[]): QueryContext | undefined { for (const result of results) { const id = result.toolCallId; if (!id) continue; for (const queryCtx of activeQueryContexts) { if ( queryCtx.pendingToolCalls.has(id) || queryCtx.pendingResults.has(id) || queryCtx.turnToolCallIds.includes(id) ) { return queryCtx; } } } return undefined; } let cachedQuery: QueryFactory | undefined; function loadAgentSdkQuery(): QueryFactory { if (cachedQuery) return cachedQuery; const mod = require("@anthropic-ai/claude-agent-sdk") as { query: QueryFactory }; cachedQuery = (args) => mod.query(args); return cachedQuery; } export function setAgentSdkQueryForTests(factory: QueryFactory | undefined): void { cachedQuery = factory; } export function streamClaudeBridge( model: Model, context: Context, options?: SimpleStreamOptions, deps: StreamDeps = {}, ): AssistantMessageEventStream { const stream = createAssistantMessageEventStream(); const lastMsg = context.messages[context.messages.length - 1]; const allResults = activeQueryContexts.size > 0 ? extractAllToolResults(context.messages).results : []; const resultCtx = allResults.length > 0 ? contextForToolResults(allResults) : undefined; if (resultCtx) { claimCurrentPiStream(stream, "tool-result", resultCtx); resultCtx.resetTurnState(model); deliverToolResults(resultCtx, allResults); resultCtx.latestCursor = Math.max(resultCtx.latestCursor, context.messages.length); return stream; } if (lastMsg?.role === "toolResult") { const c = new QueryContext(); c.resetTurnState(model); queueMicrotask(() => { stream.push({ type: "done", reason: "stop", message: c.turnOutput }); markStreamComplete(stream); stream.end(); }); return stream; } const queryCtx = ctx().activeQuery !== null ? new QueryContext() : ctx(); const { mcpTools, customToolNameToPi } = resolveMcpTools(context); claimCurrentPiStream(stream, "fresh-query", queryCtx); queryCtx.pendingToolCalls.clear(); queryCtx.pendingResults.clear(); queryCtx.turnToolCallIds = []; queryCtx.resetTurnState(model); const cwd = options?.cwd ?? deps.nowCwd?.() ?? process.cwd(); const config = deps.config ?? loadConfig(cwd); const longContext = deps.longContext ?? longContextFromConfig(config); const cliModel = claudeCodeModelId(model, longContext); const promptBlocks = extractUserPromptBlocks(context.messages); let promptText = extractUserPrompt(context.messages) ?? ""; if (!promptText && !promptBlocks) promptText = "[continue]"; const promptStream = makePromptStream(); void promptStream.push(userMessage(promptBlocks ?? [{ type: "text", text: promptText }])).catch(() => {}); queryCtx.promptStream = promptStream; const mcpServers = buildMcpServers(mcpTools, queryCtx); const effort = resolveEffort(options?.reasoning, model.thinkingLevelMap); const extraArgs: Record<string, string | null> = { model: cliModel }; if (config.provider?.strictMcpConfig !== false) extraArgs["strict-mcp-config"] = null; if (effort) extraArgs["thinking-display"] = "summarized"; const queryOptions: Record<string, unknown> = { cwd, env: { ...process.env, ...CC_CHILD_ENV }, tools: [], permissionMode: "bypassPermissions", includePartialMessages: true, settings: { ...claudeCodeSettings(config.provider), claudeMdExcludes: CLAUDE_MD_EXCLUDES, includeGitInstructions: false, }, systemPrompt: { type: "preset", preset: "claude_code", append: context.systemPrompt, }, extraArgs, ...(effort ? { effort } : {}), ...(mcpServers ? { mcpServers } : {}), ...(lastSessionId ? { resume: lastSessionId } : {}), ...(config.provider?.pathToClaudeCodeExecutable ? { pathToClaudeCodeExecutable: config.provider.pathToClaudeCodeExecutable } : {}), }; const queryFn = deps.query ?? loadAgentSdkQuery(); let wasAborted = false; const sdkQuery = queryFn({ prompt: promptStream.stream, options: queryOptions }); queryCtx.activeQuery = sdkQuery; activeQueryContexts.add(queryCtx); const onAbort = () => { wasAborted = true; promptStream.fail(new Error("Operation aborted")); queryCtx.releasePendingToolCalls("Operation aborted"); void sdkQuery.interrupt?.(); try { sdkQuery.close?.(); } catch { /* ignore */ } }; if (options?.signal) { if (options.signal.aborted) onAbort(); else options.signal.addEventListener("abort", onAbort, { once: true }); } consumeQuery(sdkQuery as AsyncIterable<any>, customToolNameToPi, model, () => wasAborted, queryCtx) .then(({ capturedSessionId }) => { if (wasAborted || options?.signal?.aborted) { if (queryCtx.turnOutput) { queryCtx.turnOutput.stopReason = "aborted"; queryCtx.turnOutput.errorMessage = "Operation aborted"; } const current = queryCtx.currentPiStream; current?.push({ type: "error", reason: "aborted", error: queryCtx.turnOutput }); markStreamComplete(current); current?.end(); queryCtx.currentPiStream = null; return; } if (capturedSessionId) lastSessionId = capturedSessionId; if (queryCtx.activeQuery === sdkQuery) queryCtx.activeQuery = null; finalizeCurrentStream(queryCtx, queryCtx.turnOutput?.stopReason); }) .catch((error) => { promptStream.fail(error instanceof Error ? error : new Error(String(error))); if (queryCtx.turnOutput) { queryCtx.turnOutput.stopReason = options?.signal?.aborted ? "aborted" : "error"; queryCtx.turnOutput.errorMessage ??= error instanceof Error ? error.message : String(error); } if (queryCtx.activeQuery === sdkQuery) { queryCtx.releasePendingToolCalls("Query ended"); queryCtx.activeQuery = null; } const current = queryCtx.currentPiStream; current?.push({ type: "error", reason: queryCtx.turnOutput?.stopReason ?? "error", error: queryCtx.turnOutput, }); markStreamComplete(current); current?.end(); queryCtx.currentPiStream = null; }) .finally(() => { options?.signal?.removeEventListener("abort", onAbort); promptStream.fail(new Error("query ended")); if (queryCtx.promptStream === promptStream) queryCtx.promptStream = null; if (queryCtx.activeQuery === sdkQuery || queryCtx.activeQuery === null) { queryCtx.releasePendingToolCalls("Query ended"); queryCtx.activeQuery = null; activeQueryContexts.delete(queryCtx); } try { sdkQuery.close?.(); } catch { /* ignore */ } }); return stream; } export { DEFAULT_LONG_CONTEXT };