skimpyclaw 0.3.14 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +47 -37
- package/dist/__tests__/adapter-types.test.d.ts +4 -0
- package/dist/__tests__/adapter-types.test.js +63 -0
- package/dist/__tests__/anthropic-adapter.test.d.ts +4 -0
- package/dist/__tests__/anthropic-adapter.test.js +264 -0
- package/dist/__tests__/api.test.js +0 -1
- package/dist/__tests__/cli.integration.test.js +2 -4
- package/dist/__tests__/cli.test.js +0 -1
- package/dist/__tests__/code-agents-notifications.test.js +137 -0
- package/dist/__tests__/code-agents-parser.test.js +19 -1
- package/dist/__tests__/code-agents-preflight.test.js +3 -28
- package/dist/__tests__/code-agents-utils.test.js +34 -9
- package/dist/__tests__/code-agents-worktrees.test.js +116 -0
- package/dist/__tests__/codex-adapter.test.js +184 -0
- package/dist/__tests__/codex-auth.test.js +66 -0
- package/dist/__tests__/codex-provider-gating.test.js +35 -0
- package/dist/__tests__/codex-unified-loop.test.js +111 -0
- package/dist/__tests__/config-security.test.js +127 -0
- package/dist/__tests__/config.test.js +23 -0
- package/dist/__tests__/context-manager.test.js +243 -164
- package/dist/__tests__/cron-run.test.js +250 -0
- package/dist/__tests__/cron.test.js +12 -38
- package/dist/__tests__/digests.test.js +67 -0
- package/dist/__tests__/discord-attachments.test.js +211 -0
- package/dist/__tests__/discord-docs.test.d.ts +1 -0
- package/dist/__tests__/discord-docs.test.js +27 -0
- package/dist/__tests__/discord-thread-agents.test.d.ts +1 -0
- package/dist/__tests__/discord-thread-agents.test.js +115 -0
- package/dist/__tests__/discord-thread-context.test.d.ts +1 -0
- package/dist/__tests__/discord-thread-context.test.js +42 -0
- package/dist/__tests__/doctor.formatters.test.js +4 -4
- package/dist/__tests__/doctor.index.test.js +1 -1
- package/dist/__tests__/doctor.runner.test.js +3 -15
- package/dist/__tests__/env-sanitizer.test.d.ts +1 -0
- package/dist/__tests__/env-sanitizer.test.js +45 -0
- package/dist/__tests__/exec-approval.test.js +61 -0
- package/dist/__tests__/fetch-tool.test.d.ts +1 -0
- package/dist/__tests__/fetch-tool.test.js +85 -0
- package/dist/__tests__/gateway-status-auth.test.d.ts +1 -0
- package/dist/__tests__/gateway-status-auth.test.js +72 -0
- package/dist/__tests__/heartbeat.test.js +3 -3
- package/dist/__tests__/interactive-sessions.test.d.ts +1 -0
- package/dist/__tests__/interactive-sessions.test.js +96 -0
- package/dist/__tests__/langfuse.test.js +6 -18
- package/dist/__tests__/model-selection.test.js +3 -4
- package/dist/__tests__/providers-init.test.js +2 -8
- package/dist/__tests__/providers-routing.test.js +1 -1
- package/dist/__tests__/providers-utils.test.js +13 -3
- package/dist/__tests__/sessions.test.js +14 -10
- package/dist/__tests__/setup.test.js +12 -29
- package/dist/__tests__/skills.test.js +10 -7
- package/dist/__tests__/stream-formatter.test.d.ts +1 -0
- package/dist/__tests__/stream-formatter.test.js +114 -0
- package/dist/__tests__/token-efficiency.test.js +131 -15
- package/dist/__tests__/tool-loop.test.d.ts +4 -0
- package/dist/__tests__/tool-loop.test.js +505 -0
- package/dist/__tests__/tools.test.js +101 -276
- package/dist/__tests__/utils.test.d.ts +1 -0
- package/dist/__tests__/utils.test.js +14 -0
- package/dist/__tests__/voice.test.js +21 -0
- package/dist/agent.js +35 -4
- package/dist/api.js +113 -37
- package/dist/channels/discord/attachments.d.ts +50 -0
- package/dist/channels/discord/attachments.js +137 -0
- package/dist/channels/discord/delegation.d.ts +5 -0
- package/dist/channels/discord/delegation.js +136 -0
- package/dist/channels/discord/handlers.js +694 -7
- package/dist/channels/discord/index.d.ts +16 -1
- package/dist/channels/discord/index.js +64 -1
- package/dist/channels/discord/thread-agents.d.ts +54 -0
- package/dist/channels/discord/thread-agents.js +323 -0
- package/dist/channels/discord/threads.d.ts +58 -0
- package/dist/channels/discord/threads.js +192 -0
- package/dist/channels/discord/types.js +4 -2
- package/dist/channels/discord/utils.d.ts +16 -0
- package/dist/channels/discord/utils.js +86 -6
- package/dist/channels/telegram/index.d.ts +1 -1
- package/dist/channels/telegram/types.js +1 -1
- package/dist/channels/telegram/utils.js +9 -3
- package/dist/channels.d.ts +1 -1
- package/dist/cli.js +20 -400
- package/dist/code-agents/executor.d.ts +1 -1
- package/dist/code-agents/executor.js +101 -45
- package/dist/code-agents/index.d.ts +2 -7
- package/dist/code-agents/index.js +111 -80
- package/dist/code-agents/interactive-resume.d.ts +6 -0
- package/dist/code-agents/interactive-resume.js +98 -0
- package/dist/code-agents/interactive-sessions.d.ts +20 -0
- package/dist/code-agents/interactive-sessions.js +132 -0
- package/dist/code-agents/parser.js +5 -1
- package/dist/code-agents/registry.d.ts +7 -1
- package/dist/code-agents/registry.js +11 -23
- package/dist/code-agents/stream-formatter.d.ts +8 -0
- package/dist/code-agents/stream-formatter.js +92 -0
- package/dist/code-agents/types.d.ts +16 -24
- package/dist/code-agents/utils.d.ts +35 -11
- package/dist/code-agents/utils.js +349 -95
- package/dist/code-agents/worktrees.d.ts +37 -0
- package/dist/code-agents/worktrees.js +116 -0
- package/dist/config.d.ts +2 -4
- package/dist/config.js +123 -23
- package/dist/cron.d.ts +1 -6
- package/dist/cron.js +175 -82
- package/dist/dashboard/assets/index-B345aOO-.js +65 -0
- package/dist/dashboard/assets/index-ZWK4dalJ.css +1 -0
- package/dist/dashboard/index.html +2 -2
- package/dist/digests.d.ts +1 -0
- package/dist/digests.js +132 -42
- package/dist/doctor/checks.d.ts +0 -3
- package/dist/doctor/checks.js +1 -108
- package/dist/doctor/runner.js +1 -4
- package/dist/env-sanitizer.d.ts +2 -0
- package/dist/env-sanitizer.js +61 -0
- package/dist/exec-approval.d.ts +11 -1
- package/dist/exec-approval.js +17 -4
- package/dist/gateway.d.ts +3 -1
- package/dist/gateway.js +17 -7
- package/dist/heartbeat.js +1 -6
- package/dist/langfuse.js +3 -29
- package/dist/model-selection.js +3 -1
- package/dist/providers/adapter.d.ts +118 -0
- package/dist/providers/adapter.js +6 -0
- package/dist/providers/adapters/anthropic-adapter.d.ts +22 -0
- package/dist/providers/adapters/anthropic-adapter.js +204 -0
- package/dist/providers/adapters/codex-adapter.d.ts +26 -0
- package/dist/providers/adapters/codex-adapter.js +203 -0
- package/dist/providers/anthropic.d.ts +1 -0
- package/dist/providers/anthropic.js +10 -272
- package/dist/providers/codex.d.ts +21 -0
- package/dist/providers/codex.js +149 -330
- package/dist/providers/content.d.ts +1 -1
- package/dist/providers/content.js +2 -2
- package/dist/providers/context-manager.d.ts +18 -6
- package/dist/providers/context-manager.js +199 -223
- package/dist/providers/index.d.ts +9 -1
- package/dist/providers/index.js +73 -64
- package/dist/providers/loop-utils.d.ts +20 -0
- package/dist/providers/loop-utils.js +30 -0
- package/dist/providers/tool-loop.d.ts +12 -0
- package/dist/providers/tool-loop.js +251 -0
- package/dist/providers/utils.d.ts +19 -3
- package/dist/providers/utils.js +100 -29
- package/dist/secure-store.d.ts +8 -0
- package/dist/secure-store.js +80 -0
- package/dist/service.js +3 -28
- package/dist/sessions.d.ts +3 -0
- package/dist/sessions.js +147 -18
- package/dist/setup-templates.js +13 -25
- package/dist/setup.d.ts +10 -6
- package/dist/setup.js +84 -292
- package/dist/skills.js +3 -11
- package/dist/tools/agent-delegation.d.ts +19 -0
- package/dist/tools/agent-delegation.js +49 -0
- package/dist/tools/bash-tool.js +89 -34
- package/dist/tools/definitions.d.ts +199 -302
- package/dist/tools/definitions.js +70 -123
- package/dist/tools/execute-context.d.ts +13 -4
- package/dist/tools/fetch-tool.js +109 -13
- package/dist/tools/file-tools.js +7 -1
- package/dist/tools.d.ts +7 -7
- package/dist/tools.js +133 -151
- package/dist/types.d.ts +37 -30
- package/dist/utils.js +4 -6
- package/dist/voice.d.ts +1 -1
- package/dist/voice.js +17 -4
- package/package.json +33 -23
- package/templates/TOOLS.md +0 -27
- package/dist/__tests__/audit.test.js +0 -122
- package/dist/__tests__/code-agents-orchestrator.test.js +0 -216
- package/dist/__tests__/code-agents-sandbox.test.js +0 -163
- package/dist/__tests__/orchestrator.test.js +0 -425
- package/dist/__tests__/sandbox-bridge.test.js +0 -116
- package/dist/__tests__/sandbox-manager.test.js +0 -144
- package/dist/__tests__/sandbox-mount-security.test.js +0 -139
- package/dist/__tests__/sandbox-runtime.test.js +0 -176
- package/dist/__tests__/subagent.test.js +0 -240
- package/dist/__tests__/telegram.test.js +0 -42
- package/dist/code-agents/orchestrator.d.ts +0 -29
- package/dist/code-agents/orchestrator.js +0 -694
- package/dist/code-agents/worktree.d.ts +0 -40
- package/dist/code-agents/worktree.js +0 -215
- package/dist/dashboard/assets/index-BoTHPby4.js +0 -65
- package/dist/dashboard/assets/index-D4mufvBg.css +0 -1
- package/dist/dashboard.d.ts +0 -8
- package/dist/dashboard.js +0 -4071
- package/dist/discord.d.ts +0 -8
- package/dist/discord.js +0 -792
- package/dist/mcp-context-a8c.d.ts +0 -13
- package/dist/mcp-context-a8c.js +0 -34
- package/dist/orchestrator.d.ts +0 -15
- package/dist/orchestrator.js +0 -676
- package/dist/providers/openai.d.ts +0 -10
- package/dist/providers/openai.js +0 -355
- package/dist/sandbox/bridge.d.ts +0 -5
- package/dist/sandbox/bridge.js +0 -63
- package/dist/sandbox/index.d.ts +0 -5
- package/dist/sandbox/index.js +0 -4
- package/dist/sandbox/manager.d.ts +0 -7
- package/dist/sandbox/manager.js +0 -100
- package/dist/sandbox/mount-security.d.ts +0 -12
- package/dist/sandbox/mount-security.js +0 -122
- package/dist/sandbox/runtime.d.ts +0 -39
- package/dist/sandbox/runtime.js +0 -192
- package/dist/sandbox-utils.d.ts +0 -6
- package/dist/sandbox-utils.js +0 -36
- package/dist/subagent.d.ts +0 -19
- package/dist/subagent.js +0 -407
- package/dist/telegram.d.ts +0 -2
- package/dist/telegram.js +0 -11
- package/dist/tools/browser-tool.d.ts +0 -3
- package/dist/tools/browser-tool.js +0 -266
- package/sandbox/Dockerfile +0 -40
- /package/dist/__tests__/{audit.test.d.ts → code-agents-notifications.test.d.ts} +0 -0
- /package/dist/__tests__/{code-agents-orchestrator.test.d.ts → code-agents-worktrees.test.d.ts} +0 -0
- /package/dist/__tests__/{code-agents-sandbox.test.d.ts → codex-adapter.test.d.ts} +0 -0
- /package/dist/__tests__/{orchestrator.test.d.ts → codex-auth.test.d.ts} +0 -0
- /package/dist/__tests__/{sandbox-bridge.test.d.ts → codex-provider-gating.test.d.ts} +0 -0
- /package/dist/__tests__/{sandbox-manager.test.d.ts → codex-unified-loop.test.d.ts} +0 -0
- /package/dist/__tests__/{sandbox-mount-security.test.d.ts → config-security.test.d.ts} +0 -0
- /package/dist/__tests__/{sandbox-runtime.test.d.ts → cron-run.test.d.ts} +0 -0
- /package/dist/__tests__/{subagent.test.d.ts → digests.test.d.ts} +0 -0
- /package/dist/__tests__/{telegram.test.d.ts → discord-attachments.test.d.ts} +0 -0
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Anthropic provider adapter for the unified tool loop.
|
|
3
|
+
*/
|
|
4
|
+
import { getAnthropicClient } from '../anthropic.js';
|
|
5
|
+
import { buildSystemParam, addToolCacheBreakpoint, contentToText, stripProvider, buildThinkingConfig } from '../utils.js';
|
|
6
|
+
import { toCostDetails } from '../observability.js';
|
|
7
|
+
import { compactMessages, anthropicFormatHelper } from '../context-manager.js';
|
|
8
|
+
import { buildUsageRecord, recordUsage } from '../../usage.js';
|
|
9
|
+
const NONSTREAMING_TOKEN_LIMIT = 21_333;
|
|
10
|
+
function shouldStreamAnthropicRequest(params) {
|
|
11
|
+
return typeof params.max_tokens === 'number' && params.max_tokens > NONSTREAMING_TOKEN_LIMIT;
|
|
12
|
+
}
|
|
13
|
+
async function createAnthropicMessage(client, params) {
|
|
14
|
+
if (shouldStreamAnthropicRequest(params) && typeof client.messages.stream === 'function') {
|
|
15
|
+
return await client.messages.stream(params).finalMessage();
|
|
16
|
+
}
|
|
17
|
+
return await client.messages.create(params);
|
|
18
|
+
}
|
|
19
|
+
export class AnthropicAdapter {
|
|
20
|
+
name = 'anthropic';
|
|
21
|
+
isAvailable() {
|
|
22
|
+
return getAnthropicClient() !== null;
|
|
23
|
+
}
|
|
24
|
+
async chat(messages, options, config) {
|
|
25
|
+
const client = getAnthropicClient();
|
|
26
|
+
if (!client) {
|
|
27
|
+
throw new Error('Anthropic client not initialized');
|
|
28
|
+
}
|
|
29
|
+
const modelId = stripProvider(options.model);
|
|
30
|
+
const systemMessage = messages.find(m => m.role === 'system');
|
|
31
|
+
const chatMessages = messages
|
|
32
|
+
.filter(m => m.role !== 'system')
|
|
33
|
+
.map(m => ({
|
|
34
|
+
role: m.role,
|
|
35
|
+
content: m.content,
|
|
36
|
+
}));
|
|
37
|
+
const cacheEnabled = config.models?.promptCaching !== false;
|
|
38
|
+
const anthropicParams = {
|
|
39
|
+
model: modelId,
|
|
40
|
+
max_tokens: options.maxTokens || 4096,
|
|
41
|
+
messages: chatMessages,
|
|
42
|
+
};
|
|
43
|
+
const systemParam = buildSystemParam(contentToText(systemMessage?.content || ''), cacheEnabled);
|
|
44
|
+
if (systemParam) {
|
|
45
|
+
anthropicParams.system = systemParam;
|
|
46
|
+
}
|
|
47
|
+
const thinkingConfig = buildThinkingConfig(options.thinking);
|
|
48
|
+
if (thinkingConfig) {
|
|
49
|
+
anthropicParams.thinking = { type: 'enabled', budget_tokens: thinkingConfig.budget };
|
|
50
|
+
anthropicParams.max_tokens = Math.max(anthropicParams.max_tokens, thinkingConfig.maxTokens);
|
|
51
|
+
}
|
|
52
|
+
const response = await createAnthropicMessage(client, anthropicParams);
|
|
53
|
+
const usage = response.usage;
|
|
54
|
+
if (usage?.cache_read_input_tokens > 0 || usage?.cache_creation_input_tokens > 0) {
|
|
55
|
+
console.log(`[cache] read=${usage.cache_read_input_tokens || 0} created=${usage.cache_creation_input_tokens || 0}`);
|
|
56
|
+
}
|
|
57
|
+
this.recordUsage(modelId, {
|
|
58
|
+
inputTokens: usage?.input_tokens ?? 0,
|
|
59
|
+
outputTokens: usage?.output_tokens ?? 0,
|
|
60
|
+
cacheReadTokens: usage?.cache_read_input_tokens,
|
|
61
|
+
cacheCreationTokens: usage?.cache_creation_input_tokens,
|
|
62
|
+
}, 'api');
|
|
63
|
+
const textContent = response.content.find((c) => c.type === 'text');
|
|
64
|
+
return textContent?.text || '';
|
|
65
|
+
}
|
|
66
|
+
buildMessages(messages, options, config) {
|
|
67
|
+
const cacheEnabled = config.models?.promptCaching !== false;
|
|
68
|
+
const systemMessage = messages.find(m => m.role === 'system');
|
|
69
|
+
const systemParam = buildSystemParam(contentToText(systemMessage?.content || ''), cacheEnabled);
|
|
70
|
+
const apiMessages = messages
|
|
71
|
+
.filter(m => m.role !== 'system')
|
|
72
|
+
.map(m => ({ role: m.role, content: m.content }));
|
|
73
|
+
return {
|
|
74
|
+
messages: apiMessages,
|
|
75
|
+
systemParam,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
buildToolDefs(toolDefs, config) {
|
|
79
|
+
const cacheEnabled = config.models?.promptCaching !== false;
|
|
80
|
+
// Make a copy to avoid mutating the original
|
|
81
|
+
const defs = JSON.parse(JSON.stringify(toolDefs));
|
|
82
|
+
if (cacheEnabled) {
|
|
83
|
+
addToolCacheBreakpoint(defs);
|
|
84
|
+
}
|
|
85
|
+
return defs;
|
|
86
|
+
}
|
|
87
|
+
async call(providerMessages, toolDefs, options, config) {
|
|
88
|
+
const client = getAnthropicClient();
|
|
89
|
+
if (!client) {
|
|
90
|
+
throw new Error('Anthropic client not initialized');
|
|
91
|
+
}
|
|
92
|
+
const modelId = stripProvider(options.model);
|
|
93
|
+
const anthropicParams = {
|
|
94
|
+
model: modelId,
|
|
95
|
+
max_tokens: options.maxTokens || 16384,
|
|
96
|
+
messages: providerMessages.messages,
|
|
97
|
+
tools: toolDefs,
|
|
98
|
+
};
|
|
99
|
+
if (providerMessages.systemParam) {
|
|
100
|
+
anthropicParams.system = providerMessages.systemParam;
|
|
101
|
+
}
|
|
102
|
+
// Add thinking if configured
|
|
103
|
+
const thinkingConfig = buildThinkingConfig(options.thinking);
|
|
104
|
+
if (thinkingConfig) {
|
|
105
|
+
anthropicParams.thinking = { type: 'enabled', budget_tokens: thinkingConfig.budget };
|
|
106
|
+
anthropicParams.max_tokens = Math.max(anthropicParams.max_tokens, thinkingConfig.maxTokens);
|
|
107
|
+
}
|
|
108
|
+
const response = await createAnthropicMessage(client, anthropicParams);
|
|
109
|
+
const usage = response.usage;
|
|
110
|
+
// Log cache metrics
|
|
111
|
+
if (usage?.cache_read_input_tokens > 0 || usage?.cache_creation_input_tokens > 0) {
|
|
112
|
+
console.log(`[cache] read=${usage.cache_read_input_tokens || 0} created=${usage.cache_creation_input_tokens || 0}`);
|
|
113
|
+
}
|
|
114
|
+
// Normalize response
|
|
115
|
+
const hasToolCalls = response.stop_reason === 'tool_use';
|
|
116
|
+
const toolCalls = [];
|
|
117
|
+
let textContent = '';
|
|
118
|
+
for (const block of response.content) {
|
|
119
|
+
if (block.type === 'text') {
|
|
120
|
+
textContent += block.text;
|
|
121
|
+
}
|
|
122
|
+
else if (block.type === 'tool_use') {
|
|
123
|
+
toolCalls.push({
|
|
124
|
+
id: block.id,
|
|
125
|
+
name: block.name,
|
|
126
|
+
args: block.input,
|
|
127
|
+
rawArgs: JSON.stringify(block.input),
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
const cost = toCostDetails(modelId, usage) || undefined;
|
|
132
|
+
return {
|
|
133
|
+
hasToolCalls,
|
|
134
|
+
toolCalls,
|
|
135
|
+
textContent,
|
|
136
|
+
usage: {
|
|
137
|
+
inputTokens: usage?.input_tokens ?? 0,
|
|
138
|
+
outputTokens: usage?.output_tokens ?? 0,
|
|
139
|
+
cacheReadTokens: usage?.cache_read_input_tokens,
|
|
140
|
+
cacheCreationTokens: usage?.cache_creation_input_tokens,
|
|
141
|
+
},
|
|
142
|
+
cost,
|
|
143
|
+
rawResponse: response,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
appendAssistantResponse(providerMessages, rawResponse) {
|
|
147
|
+
const response = rawResponse;
|
|
148
|
+
providerMessages.messages.push({ role: 'assistant', content: response.content });
|
|
149
|
+
}
|
|
150
|
+
appendToolResult(providerMessages, toolCallId, result, isError) {
|
|
151
|
+
const toolResult = {
|
|
152
|
+
type: 'tool_result',
|
|
153
|
+
tool_use_id: toolCallId,
|
|
154
|
+
content: result,
|
|
155
|
+
};
|
|
156
|
+
if (isError) {
|
|
157
|
+
toolResult.is_error = true;
|
|
158
|
+
}
|
|
159
|
+
// Anthropic expects tool_result blocks in a user message
|
|
160
|
+
providerMessages.messages.push({ role: 'user', content: [toolResult] });
|
|
161
|
+
}
|
|
162
|
+
appendToolResults(providerMessages, results) {
|
|
163
|
+
// Anthropic expects all tool_result blocks for one turn in a single user message
|
|
164
|
+
const toolResults = results.map(r => {
|
|
165
|
+
const block = {
|
|
166
|
+
type: 'tool_result',
|
|
167
|
+
tool_use_id: r.toolCallId,
|
|
168
|
+
content: r.result,
|
|
169
|
+
};
|
|
170
|
+
if (r.isError) {
|
|
171
|
+
block.is_error = true;
|
|
172
|
+
}
|
|
173
|
+
return block;
|
|
174
|
+
});
|
|
175
|
+
providerMessages.messages.push({ role: 'user', content: toolResults });
|
|
176
|
+
}
|
|
177
|
+
async compactMessages(providerMessages, config, iteration, fullConfig) {
|
|
178
|
+
const result = await compactMessages(providerMessages.messages, anthropicFormatHelper, config, iteration, fullConfig);
|
|
179
|
+
providerMessages.messages = result.messages;
|
|
180
|
+
return result;
|
|
181
|
+
}
|
|
182
|
+
recordUsage(model, usage, trigger, agentId) {
|
|
183
|
+
const u = usage;
|
|
184
|
+
const inputTokens = typeof u?.inputTokens === 'number' ? u.inputTokens : 0;
|
|
185
|
+
const outputTokens = typeof u?.outputTokens === 'number' ? u.outputTokens : 0;
|
|
186
|
+
if (inputTokens === 0 && outputTokens === 0)
|
|
187
|
+
return;
|
|
188
|
+
const cost = toCostDetails(model, { input_tokens: inputTokens, output_tokens: outputTokens });
|
|
189
|
+
const usageRecord = buildUsageRecord({
|
|
190
|
+
model,
|
|
191
|
+
provider: 'anthropic',
|
|
192
|
+
inputTokens,
|
|
193
|
+
outputTokens,
|
|
194
|
+
inputCost: cost?.input ?? 0,
|
|
195
|
+
outputCost: cost?.output ?? 0,
|
|
196
|
+
totalCost: cost?.total ?? 0,
|
|
197
|
+
trigger: trigger || 'api',
|
|
198
|
+
agentId,
|
|
199
|
+
cacheReadTokens: u?.cacheReadTokens,
|
|
200
|
+
cacheCreationTokens: u?.cacheCreationTokens,
|
|
201
|
+
});
|
|
202
|
+
recordUsage(usageRecord);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Codex provider adapter for the unified tool loop.
|
|
3
|
+
*/
|
|
4
|
+
import type { ChatMessage, ChatOptions, Config } from '../../types.js';
|
|
5
|
+
import type { ProviderAdapter, ProviderMessages, NormalizedResponse, CompactionResult } from '../adapter.js';
|
|
6
|
+
import type { ExecuteToolContext } from '../../tools.js';
|
|
7
|
+
export declare class CodexAdapter implements ProviderAdapter {
|
|
8
|
+
readonly name = "codex";
|
|
9
|
+
isAvailable(): boolean;
|
|
10
|
+
chat(messages: ChatMessage[], options: ChatOptions, _config: Config): Promise<string>;
|
|
11
|
+
getToolDefinitionOptions(_toolContext?: ExecuteToolContext, _config?: Config): {
|
|
12
|
+
includeMcp?: boolean;
|
|
13
|
+
};
|
|
14
|
+
buildMessages(messages: ChatMessage[], _options: ChatOptions, _config: Config): ProviderMessages;
|
|
15
|
+
buildToolDefs(toolDefs: any[], _config: Config): any[];
|
|
16
|
+
call(providerMessages: ProviderMessages, toolDefs: any[], options: ChatOptions, _config: Config): Promise<NormalizedResponse>;
|
|
17
|
+
appendAssistantResponse(providerMessages: ProviderMessages, rawResponse: unknown): void;
|
|
18
|
+
appendToolResult(providerMessages: ProviderMessages, toolCallId: string, result: string, _isError?: boolean): void;
|
|
19
|
+
/**
|
|
20
|
+
* Codex sometimes finishes tool execution but omits final text.
|
|
21
|
+
* Re-ask once (without tools) for a user-facing answer from the gathered context.
|
|
22
|
+
*/
|
|
23
|
+
onEmptyFinalResponse(providerMessages: ProviderMessages, _toolDefs: any[], options: ChatOptions, _config: Config): Promise<string | undefined>;
|
|
24
|
+
compactMessages(providerMessages: ProviderMessages, config: any, iteration: number, fullConfig?: Config): Promise<CompactionResult<any>>;
|
|
25
|
+
recordUsage(model: string, usage: unknown, trigger?: string, agentId?: string): void;
|
|
26
|
+
}
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Codex provider adapter for the unified tool loop.
|
|
3
|
+
*/
|
|
4
|
+
import { contentToText, stripProvider } from '../utils.js';
|
|
5
|
+
import { compactMessages, codexFormatHelper } from '../context-manager.js';
|
|
6
|
+
import { toCodexContent, toCodexToolDefinitions } from '../content.js';
|
|
7
|
+
import { toCostDetails } from '../observability.js';
|
|
8
|
+
import { codexFetch, parseCodexSSE, isCodexAvailable, recordCodexUsage } from '../codex.js';
|
|
9
|
+
function codexReasoningEffort(thinking) {
|
|
10
|
+
switch (thinking) {
|
|
11
|
+
case 'low':
|
|
12
|
+
case 'medium':
|
|
13
|
+
case 'high':
|
|
14
|
+
case 'xhigh':
|
|
15
|
+
return thinking;
|
|
16
|
+
default:
|
|
17
|
+
return 'medium';
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
function codexReasoning(options) {
|
|
21
|
+
return { effort: codexReasoningEffort(options.thinking), summary: 'auto' };
|
|
22
|
+
}
|
|
23
|
+
export class CodexAdapter {
|
|
24
|
+
name = 'codex';
|
|
25
|
+
isAvailable() {
|
|
26
|
+
return isCodexAvailable();
|
|
27
|
+
}
|
|
28
|
+
async chat(messages, options, _config) {
|
|
29
|
+
const modelId = stripProvider(options.model);
|
|
30
|
+
let instructions = 'You are a helpful assistant.';
|
|
31
|
+
const input = [];
|
|
32
|
+
for (const m of messages) {
|
|
33
|
+
if (m.role === 'system') {
|
|
34
|
+
instructions = contentToText(m.content);
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
const contentType = m.role === 'assistant' ? 'output_text' : 'input_text';
|
|
38
|
+
input.push({
|
|
39
|
+
type: 'message',
|
|
40
|
+
role: m.role,
|
|
41
|
+
content: toCodexContent(m.content, contentType),
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
const body = {
|
|
45
|
+
model: modelId,
|
|
46
|
+
instructions,
|
|
47
|
+
input,
|
|
48
|
+
store: false,
|
|
49
|
+
stream: true,
|
|
50
|
+
reasoning: codexReasoning(options),
|
|
51
|
+
include: ['reasoning.encrypted_content'],
|
|
52
|
+
};
|
|
53
|
+
const sseText = await codexFetch(body);
|
|
54
|
+
const parsed = parseCodexSSE(sseText);
|
|
55
|
+
this.recordUsage(modelId, {
|
|
56
|
+
inputTokens: parsed.response?.usage?.input_tokens ?? 0,
|
|
57
|
+
outputTokens: parsed.response?.usage?.output_tokens ?? 0,
|
|
58
|
+
cacheReadTokens: parsed.response?.usage?.input_tokens_details?.cached_tokens,
|
|
59
|
+
}, 'api');
|
|
60
|
+
return parsed.outputText || '[No response from Codex]';
|
|
61
|
+
}
|
|
62
|
+
getToolDefinitionOptions(_toolContext, _config) {
|
|
63
|
+
// MCP tools are standard function calls — Codex handles them fine.
|
|
64
|
+
return { includeMcp: true };
|
|
65
|
+
}
|
|
66
|
+
buildMessages(messages, _options, _config) {
|
|
67
|
+
let instructions = 'You are a helpful assistant.';
|
|
68
|
+
const input = [];
|
|
69
|
+
for (const m of messages) {
|
|
70
|
+
if (m.role === 'system') {
|
|
71
|
+
instructions = contentToText(m.content);
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
const contentType = m.role === 'assistant' ? 'output_text' : 'input_text';
|
|
75
|
+
input.push({
|
|
76
|
+
type: 'message',
|
|
77
|
+
role: m.role,
|
|
78
|
+
content: toCodexContent(m.content, contentType),
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
return {
|
|
82
|
+
messages: input,
|
|
83
|
+
systemParam: instructions,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
buildToolDefs(toolDefs, _config) {
|
|
87
|
+
return toCodexToolDefinitions(toolDefs || []);
|
|
88
|
+
}
|
|
89
|
+
async call(providerMessages, toolDefs, options, _config) {
|
|
90
|
+
const modelId = stripProvider(options.model);
|
|
91
|
+
const body = {
|
|
92
|
+
model: modelId,
|
|
93
|
+
instructions: providerMessages.systemParam || 'You are a helpful assistant.',
|
|
94
|
+
input: providerMessages.messages,
|
|
95
|
+
store: false,
|
|
96
|
+
stream: true,
|
|
97
|
+
reasoning: codexReasoning(options),
|
|
98
|
+
include: ['reasoning.encrypted_content'],
|
|
99
|
+
};
|
|
100
|
+
if (toolDefs?.length) {
|
|
101
|
+
body.tools = toolDefs;
|
|
102
|
+
}
|
|
103
|
+
const sseText = await codexFetch(body);
|
|
104
|
+
const parsed = parseCodexSSE(sseText);
|
|
105
|
+
const toolCalls = parsed.functionCalls.map((fc) => {
|
|
106
|
+
const rawArgs = typeof fc.arguments === 'string' ? fc.arguments : '{}';
|
|
107
|
+
let args;
|
|
108
|
+
try {
|
|
109
|
+
args = JSON.parse(rawArgs);
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
args = {};
|
|
113
|
+
}
|
|
114
|
+
return {
|
|
115
|
+
id: fc.callId,
|
|
116
|
+
name: fc.name,
|
|
117
|
+
args,
|
|
118
|
+
rawArgs,
|
|
119
|
+
};
|
|
120
|
+
});
|
|
121
|
+
const usage = parsed.response?.usage;
|
|
122
|
+
const cost = toCostDetails(modelId, usage) || undefined;
|
|
123
|
+
return {
|
|
124
|
+
hasToolCalls: toolCalls.length > 0,
|
|
125
|
+
toolCalls,
|
|
126
|
+
textContent: parsed.outputText || '',
|
|
127
|
+
usage: {
|
|
128
|
+
inputTokens: usage?.input_tokens ?? 0,
|
|
129
|
+
outputTokens: usage?.output_tokens ?? 0,
|
|
130
|
+
cacheReadTokens: usage?.input_tokens_details?.cached_tokens,
|
|
131
|
+
},
|
|
132
|
+
cost,
|
|
133
|
+
rawResponse: parsed.response,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
appendAssistantResponse(providerMessages, rawResponse) {
|
|
137
|
+
const response = rawResponse;
|
|
138
|
+
if (!response?.output || !Array.isArray(response.output))
|
|
139
|
+
return;
|
|
140
|
+
for (const item of response.output) {
|
|
141
|
+
providerMessages.messages.push(item);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
appendToolResult(providerMessages, toolCallId, result, _isError) {
|
|
145
|
+
providerMessages.messages.push({
|
|
146
|
+
type: 'function_call_output',
|
|
147
|
+
call_id: toolCallId,
|
|
148
|
+
output: result,
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Codex sometimes finishes tool execution but omits final text.
|
|
153
|
+
* Re-ask once (without tools) for a user-facing answer from the gathered context.
|
|
154
|
+
*/
|
|
155
|
+
async onEmptyFinalResponse(providerMessages, _toolDefs, options, _config) {
|
|
156
|
+
const modelId = stripProvider(options.model);
|
|
157
|
+
// Build a finalization input: existing messages + nudge
|
|
158
|
+
const finalizeInput = [...providerMessages.messages];
|
|
159
|
+
finalizeInput.push({
|
|
160
|
+
type: 'message',
|
|
161
|
+
role: 'user',
|
|
162
|
+
content: [
|
|
163
|
+
{
|
|
164
|
+
type: 'input_text',
|
|
165
|
+
text: 'Provide the final answer to the user using the tool results above. Do not call tools. Be concise.',
|
|
166
|
+
},
|
|
167
|
+
],
|
|
168
|
+
});
|
|
169
|
+
const body = {
|
|
170
|
+
model: modelId,
|
|
171
|
+
instructions: providerMessages.systemParam || 'You are a helpful assistant.',
|
|
172
|
+
input: finalizeInput,
|
|
173
|
+
store: false,
|
|
174
|
+
stream: true,
|
|
175
|
+
reasoning: codexReasoning(options),
|
|
176
|
+
include: ['reasoning.encrypted_content'],
|
|
177
|
+
};
|
|
178
|
+
console.log('[codex] Finalizing tool run with a text-only follow-up');
|
|
179
|
+
const sseText = await codexFetch(body);
|
|
180
|
+
const parsed = parseCodexSSE(sseText);
|
|
181
|
+
return parsed.outputText?.trim() || undefined;
|
|
182
|
+
}
|
|
183
|
+
async compactMessages(providerMessages, config, iteration, fullConfig) {
|
|
184
|
+
const result = await compactMessages(providerMessages.messages, codexFormatHelper, config, iteration, fullConfig);
|
|
185
|
+
providerMessages.messages = result.messages;
|
|
186
|
+
return result;
|
|
187
|
+
}
|
|
188
|
+
recordUsage(model, usage, trigger, agentId) {
|
|
189
|
+
const u = usage;
|
|
190
|
+
recordCodexUsage({
|
|
191
|
+
model,
|
|
192
|
+
usage: {
|
|
193
|
+
input_tokens: typeof u?.inputTokens === 'number' ? u.inputTokens : 0,
|
|
194
|
+
output_tokens: typeof u?.outputTokens === 'number' ? u.outputTokens : 0,
|
|
195
|
+
input_tokens_details: {
|
|
196
|
+
cached_tokens: typeof u?.cacheReadTokens === 'number' ? u.cacheReadTokens : undefined,
|
|
197
|
+
},
|
|
198
|
+
},
|
|
199
|
+
trigger,
|
|
200
|
+
agentId,
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
}
|
|
@@ -3,5 +3,6 @@ import type { ProviderChatParams, ProviderToolChatParams, ToolChatResult } from
|
|
|
3
3
|
export declare function setAnthropicClient(client: Anthropic | null): void;
|
|
4
4
|
export declare function getAnthropicClient(): Anthropic | null;
|
|
5
5
|
export declare function isAnthropicAvailable(): boolean;
|
|
6
|
+
/** @deprecated Use adapter.chat() via the provider registry instead. */
|
|
6
7
|
export declare function chatAnthropic(params: ProviderChatParams): Promise<string>;
|
|
7
8
|
export declare function chatWithToolsAnthropic(params: ProviderToolChatParams): Promise<ToolChatResult>;
|