diff options
| author | Adam Malczewski <[email protected]> | 2026-05-20 20:40:35 +0900 |
|---|---|---|
| committer | Adam Malczewski <[email protected]> | 2026-05-20 20:40:35 +0900 |
| commit | 8151447758e6826a578363758a755c6cebd1c05f (patch) | |
| tree | 6afa780c28ca6e4622c1ab30238665caaad4371e /packages/core/src/agent | |
| parent | f05099d450748cc7508f8cbde4e6539db2105f6d (diff) | |
| download | dispatch-8151447758e6826a578363758a755c6cebd1c05f.tar.gz dispatch-8151447758e6826a578363758a755c6cebd1c05f.zip | |
feat: claude max oauth support with multi-account switching, reasoning effort, and dynamic model listing
Diffstat (limited to 'packages/core/src/agent')
| -rw-r--r-- | packages/core/src/agent/agent.ts | 61 |
1 files changed, 47 insertions, 14 deletions
diff --git a/packages/core/src/agent/agent.ts b/packages/core/src/agent/agent.ts index 2fd6746..006ee1b 100644 --- a/packages/core/src/agent/agent.ts +++ b/packages/core/src/agent/agent.ts @@ -2,9 +2,10 @@ import type { CoreMessage } from "ai"; import { streamText } from "ai"; import { dirname, isAbsolute, relative, resolve } from "node:path"; import { realpathSync } from "node:fs"; -import { createProvider } from "../llm/provider.js"; +import { createProvider, prefixToolName, unprefixToolName } from "../llm/provider.js"; import { createToolRegistry } from "../tools/registry.js"; import { analyzeCommand } from "../tools/shell-analyze.js"; +import { buildBillingHeaderValue, SYSTEM_IDENTITY } from "../credentials/claude.js"; import type { AgentConfig, AgentEvent, @@ -14,7 +15,7 @@ import type { ToolResult, } from "../types/index.js"; -function toCoreMessages(messages: ChatMessage[]): CoreMessage[] { +function toCoreMessages(messages: ChatMessage[], isAnthropic?: boolean): CoreMessage[] { const result: CoreMessage[] = []; for (const msg of messages) { if (msg.role === "user") { @@ -22,11 +23,13 @@ function toCoreMessages(messages: ChatMessage[]): CoreMessage[] { } else if (msg.role === "assistant") { const parts: Array<{ type: "text"; text: string } | { type: "tool-call"; toolCallId: string; toolName: string; args: Record<string, unknown> }> = [{ type: "text", text: msg.content }]; for (const tc of msg.toolCalls ?? []) { - parts.push({ type: "tool-call", toolCallId: tc.id, toolName: tc.name, args: tc.arguments }); + const toolName = isAnthropic ? prefixToolName(tc.name) : tc.name; + parts.push({ type: "tool-call", toolCallId: tc.id, toolName, args: tc.arguments }); } result.push({ role: "assistant", content: parts }); for (const tr of msg.toolResults ?? []) { - result.push({ role: "tool", content: [{ type: "tool-result", toolCallId: tr.toolCallId, toolName: tr.toolName, result: tr.result }] }); + const toolName = isAnthropic ? prefixToolName(tr.toolName) : tr.toolName; + result.push({ role: "tool", content: [{ type: "tool-result", toolCallId: tr.toolCallId, toolName, result: tr.result }] }); } } } @@ -191,18 +194,36 @@ export class Agent { } } - async *run(userMessage: string): AsyncGenerator<AgentEvent> { + async *run(userMessage: string, options?: { reasoningEffort?: "none" | "low" | "medium" | "high" | "max" }): AsyncGenerator<AgentEvent> { this.status = "running"; yield { type: "status", status: "running" }; this.messages.push({ role: "user", content: userMessage }); const registry = createToolRegistry(this.config.tools); + const isAnthropic = this.config.provider === "anthropic"; const providerFactory = createProvider({ apiKey: this.config.apiKey, baseURL: this.config.baseURL, + provider: this.config.provider, + claudeCredentials: this.config.claudeCredentials, }); + // For Anthropic provider, prefix tool names and build full system prompt + const aiTools = registry.getAISDKTools(); + const tools = isAnthropic + ? Object.fromEntries( + Object.entries(aiTools).map(([name, tool]) => [prefixToolName(name), tool]), + ) + : aiTools; + + // Build system prompt + let systemPrompt = this.config.systemPrompt; + if (isAnthropic) { + const billingHeader = buildBillingHeaderValue(this.messages); + systemPrompt = `${billingHeader}\n${SYSTEM_IDENTITY}\n\n${systemPrompt}`; + } + try { // Track the final assistant message across all steps let finalText = ""; @@ -214,15 +235,25 @@ export class Agent { const stepMessages: ChatMessage[] = [...this.messages]; for (let step = 0; step < MAX_STEPS; step++) { - const result = streamText({ + const effort = options?.reasoningEffort ?? this.config.reasoningEffort ?? "max"; + + // Build stream text options + const streamOptions: Parameters<typeof streamText>[0] = { model: providerFactory(this.config.model), - system: this.config.systemPrompt, - messages: toCoreMessages(stepMessages), - tools: registry.getAISDKTools(), - providerOptions: { - openaiCompatible: { reasoningEffort: "max" }, - }, - }); + system: systemPrompt, + messages: toCoreMessages(stepMessages, isAnthropic), + tools, + }; + + if (isAnthropic && effort !== "none") { + const budgetTokens = effort === "max" ? 16000 : effort === "high" ? 10000 : effort === "medium" ? 5000 : effort === "low" ? 2000 : 0; + streamOptions.providerOptions = { anthropic: { thinking: { type: "enabled" as const, budgetTokens } } }; + streamOptions.maxTokens = budgetTokens + 8000; + } else if (!isAnthropic && effort !== "none") { + streamOptions.providerOptions = { openaiCompatible: { reasoningEffort: effort } }; + } + + const result = streamText(streamOptions); let stepText = ""; const stepToolCalls: ToolCall[] = []; @@ -235,9 +266,11 @@ export class Agent { } else if (event.type === "reasoning") { yield { type: "reasoning-delta", delta: event.textDelta }; } else if (event.type === "tool-call") { + const rawName = event.toolName; + const toolName = isAnthropic ? unprefixToolName(rawName) : rawName; const toolCall: ToolCall = { id: event.toolCallId, - name: event.toolName, + name: toolName, arguments: event.args as Record<string, unknown>, }; stepToolCalls.push(toolCall); |
