UNPKG

agent-contracts-runtime

Version:

Runtime bridge for executing agent-contracts workflows on Agent SDKs

244 lines 8.92 kB
// src/adapters/claude-agent-sdk.ts function buildClaudeHooks(guardrails) { const preToolUseHook = { hooks: [ (input) => { const toolInput = input.tool_input; const toolName = input.tool_name; if (toolName === "Bash" || toolName === "Shell") { const command = toolInput?.command ?? toolInput?.input; if (command) { const result = guardrails.beforeShellExecution({ command }); if (result.permission === "deny") { return { hookEventName: "PreToolUse", permissionDecision: "deny", permissionDecisionReason: result.user_message ?? result.agent_message }; } if (result.additionalContext) { return { hookEventName: "PreToolUse", additionalContext: result.additionalContext }; } } } const filePath = toolInput?.file_path ?? toolInput?.path; if (filePath) { const pathResult = guardrails.preToolUse({ tool_name: toolName, tool_input: { file_path: filePath } }); if (pathResult.permission === "deny") { return { hookEventName: "PreToolUse", permissionDecision: "deny", permissionDecisionReason: pathResult.user_message ?? pathResult.agent_message }; } if (pathResult.additionalContext) { return { hookEventName: "PreToolUse", additionalContext: pathResult.additionalContext }; } } return { hookEventName: "PreToolUse" }; } ] }; return { PreToolUse: [preToolUseHook] }; } var ClaudeAgentSdkAdapter = class _ClaudeAgentSdkAdapter { cwd; model; tools; permissionMode; maxTurns; guardrailHooks; cacheConfig; lastSessionId = null; lastMemoryRef = null; queryFn = null; constructor(config = {}) { this.cwd = config.cwd ?? process.cwd(); this.model = config.model; this.tools = config.tools; this.permissionMode = config.permissionMode ?? "bypassPermissions"; this.maxTurns = config.maxTurns; this.guardrailHooks = config.guardrailHooks; this.cacheConfig = config.cacheConfig ?? { enabled: true }; } // ------------------------------------------------------------------------- // Internal helpers // ------------------------------------------------------------------------- async resolveQueryFn() { if (this.queryFn) return this.queryFn; const sdk = await import("@anthropic-ai/claude-agent-sdk"); this.queryFn = sdk.query; return this.queryFn; } buildOptions(readonly, resume, systemPrompt, agents, allowDynamicWorkflow) { const opts = { cwd: this.cwd, permissionMode: this.permissionMode, allowDangerouslySkipPermissions: this.permissionMode === "bypassPermissions", // SDK isolation: do not load ambient .claude/ settings, skills, or agents // from disk. The runtime injects everything in-code, so disk discovery // would break component isolation and reproducibility. settingSources: [] }; if (this.model) opts.model = this.model; if (this.maxTurns) opts.maxTurns = this.maxTurns; let toolList; if (this.tools) { toolList = Array.isArray(this.tools) ? [...this.tools] : this.tools; } else { toolList = readonly ? ["Read", "Glob", "Grep"] : ["Read", "Glob", "Grep", "Edit", "Write", "Bash"]; } if (agents && agents.length > 0 && Array.isArray(toolList)) { for (const t of ["Task", "Agent"]) { if (!toolList.includes(t)) toolList.push(t); } } if (allowDynamicWorkflow && Array.isArray(toolList)) { if (!toolList.includes("Workflow")) toolList.push("Workflow"); } opts.tools = toolList; if (resume) opts.resume = resume; if (systemPrompt && this.cacheConfig.enabled !== false) { opts.systemPrompt = systemPrompt; } if (agents && agents.length > 0) { const record = {}; for (const a of agents) { record[a.name] = { description: a.description, prompt: a.prompt, ...a.tools ? { tools: a.tools } : {}, ...a.model ? { model: a.model } : {} }; } opts.agents = record; } if (this.guardrailHooks) { opts.hooks = buildClaudeHooks(this.guardrailHooks); } return opts; } async runQuery(prompt, options, onProgress) { const queryFn = await this.resolveQueryFn(); const stream = queryFn({ prompt, options }); let resultText = ""; let sessionId = ""; for await (const message of stream) { if (message.session_id) sessionId = message.session_id; if (message.type === "result") { if (message.subtype === "success") { resultText = message.result; } else if (message.subtype === "error") { throw new Error(`Claude Agent SDK error: ${message.error}`); } } else if (onProgress) { emitProgressFromSdkMessage(message, onProgress, sessionId); } } return { text: resultText, sessionId }; } // ------------------------------------------------------------------------- // SdkAdapter interface // ------------------------------------------------------------------------- async send(prompt, options) { this.lastSessionId = null; this.lastMemoryRef = null; const split = options.splitPrompt; const systemPrompt = split?.system; const userPrompt = split ? split.user : prompt; const opts = this.buildOptions(options.readonly, void 0, systemPrompt, options.agents, options.allowDynamicWorkflow); const { text, sessionId } = await this.runQuery(userPrompt, opts, options.onProgress); this.lastSessionId = sessionId; this.lastMemoryRef = { id: sessionId, provider: "claude-agent-sdk", compat: "claude-agent-sdk@0.2", created_at: (/* @__PURE__ */ new Date()).toISOString() }; return text; } async followUp(message) { if (!this.lastSessionId) { throw new Error("followUp() called before send() \u2014 no active session"); } const opts = this.buildOptions(false, this.lastSessionId); const { text, sessionId } = await this.runQuery(message, opts); this.lastSessionId = sessionId; return text; } async sendExecution(request) { this.lastSessionId = null; this.lastMemoryRef = null; const resume = request.memoryRef?.id; const split = request.splitPrompt; const systemPrompt = split?.system; const userPrompt = split ? split.user : request.prompt; const agents = request.options.agents ?? request.agents; const opts = this.buildOptions(request.options.readonly, resume, systemPrompt, agents, request.options.allowDynamicWorkflow); const { text, sessionId } = await this.runQuery(userPrompt, opts, request.options.onProgress); this.lastSessionId = sessionId; this.lastMemoryRef = { id: sessionId, provider: "claude-agent-sdk", compat: "claude-agent-sdk@0.2", created_at: (/* @__PURE__ */ new Date()).toISOString(), parent_run_id: request.memoryRef?.id }; return text; } getLastMemoryRef() { return this.lastMemoryRef; } isCompatible(compat) { return compat.startsWith("claude-agent-sdk@"); } // ------------------------------------------------------------------------- // Test support // ------------------------------------------------------------------------- /** * Inject a custom query function for testing. * Bypasses the dynamic import of @anthropic-ai/claude-agent-sdk. */ static withQueryFn(queryFn, config) { const adapter = new _ClaudeAgentSdkAdapter(config); adapter.queryFn = queryFn; return adapter; } }; function emitProgressFromSdkMessage(message, onProgress, sessionId) { const sid = message.session_id ?? sessionId; switch (message.type) { case "assistant": { const content = message.message?.content; if (!content) break; for (const block of content) { if (block.type === "tool_use" && block.name) { const input = block.input && typeof block.input === "object" ? block.input : void 0; onProgress({ type: "tool_use", tool_name: block.name, input, session_id: sid }); } else if (block.type === "text" && block.text) { onProgress({ type: "text", message: block.text, session_id: sid }); } } break; } case "tool_progress": if (message.tool_name) { onProgress({ type: "tool_use", tool_name: message.tool_name, session_id: sid }); } break; case "tool_use_summary": if (message.summary) { onProgress({ type: "status", message: message.summary, session_id: sid }); } break; default: break; } } export { ClaudeAgentSdkAdapter }; //# sourceMappingURL=claude-agent-sdk.js.map