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.
Files changed (222) hide show
  1. package/README.md +47 -37
  2. package/dist/__tests__/adapter-types.test.d.ts +4 -0
  3. package/dist/__tests__/adapter-types.test.js +63 -0
  4. package/dist/__tests__/anthropic-adapter.test.d.ts +4 -0
  5. package/dist/__tests__/anthropic-adapter.test.js +264 -0
  6. package/dist/__tests__/api.test.js +0 -1
  7. package/dist/__tests__/cli.integration.test.js +2 -4
  8. package/dist/__tests__/cli.test.js +0 -1
  9. package/dist/__tests__/code-agents-notifications.test.js +137 -0
  10. package/dist/__tests__/code-agents-parser.test.js +19 -1
  11. package/dist/__tests__/code-agents-preflight.test.js +3 -28
  12. package/dist/__tests__/code-agents-utils.test.js +34 -9
  13. package/dist/__tests__/code-agents-worktrees.test.js +116 -0
  14. package/dist/__tests__/codex-adapter.test.js +184 -0
  15. package/dist/__tests__/codex-auth.test.js +66 -0
  16. package/dist/__tests__/codex-provider-gating.test.js +35 -0
  17. package/dist/__tests__/codex-unified-loop.test.js +111 -0
  18. package/dist/__tests__/config-security.test.js +127 -0
  19. package/dist/__tests__/config.test.js +23 -0
  20. package/dist/__tests__/context-manager.test.js +243 -164
  21. package/dist/__tests__/cron-run.test.js +250 -0
  22. package/dist/__tests__/cron.test.js +12 -38
  23. package/dist/__tests__/digests.test.js +67 -0
  24. package/dist/__tests__/discord-attachments.test.js +211 -0
  25. package/dist/__tests__/discord-docs.test.d.ts +1 -0
  26. package/dist/__tests__/discord-docs.test.js +27 -0
  27. package/dist/__tests__/discord-thread-agents.test.d.ts +1 -0
  28. package/dist/__tests__/discord-thread-agents.test.js +115 -0
  29. package/dist/__tests__/discord-thread-context.test.d.ts +1 -0
  30. package/dist/__tests__/discord-thread-context.test.js +42 -0
  31. package/dist/__tests__/doctor.formatters.test.js +4 -4
  32. package/dist/__tests__/doctor.index.test.js +1 -1
  33. package/dist/__tests__/doctor.runner.test.js +3 -15
  34. package/dist/__tests__/env-sanitizer.test.d.ts +1 -0
  35. package/dist/__tests__/env-sanitizer.test.js +45 -0
  36. package/dist/__tests__/exec-approval.test.js +61 -0
  37. package/dist/__tests__/fetch-tool.test.d.ts +1 -0
  38. package/dist/__tests__/fetch-tool.test.js +85 -0
  39. package/dist/__tests__/gateway-status-auth.test.d.ts +1 -0
  40. package/dist/__tests__/gateway-status-auth.test.js +72 -0
  41. package/dist/__tests__/heartbeat.test.js +3 -3
  42. package/dist/__tests__/interactive-sessions.test.d.ts +1 -0
  43. package/dist/__tests__/interactive-sessions.test.js +96 -0
  44. package/dist/__tests__/langfuse.test.js +6 -18
  45. package/dist/__tests__/model-selection.test.js +3 -4
  46. package/dist/__tests__/providers-init.test.js +2 -8
  47. package/dist/__tests__/providers-routing.test.js +1 -1
  48. package/dist/__tests__/providers-utils.test.js +13 -3
  49. package/dist/__tests__/sessions.test.js +14 -10
  50. package/dist/__tests__/setup.test.js +12 -29
  51. package/dist/__tests__/skills.test.js +10 -7
  52. package/dist/__tests__/stream-formatter.test.d.ts +1 -0
  53. package/dist/__tests__/stream-formatter.test.js +114 -0
  54. package/dist/__tests__/token-efficiency.test.js +131 -15
  55. package/dist/__tests__/tool-loop.test.d.ts +4 -0
  56. package/dist/__tests__/tool-loop.test.js +505 -0
  57. package/dist/__tests__/tools.test.js +101 -276
  58. package/dist/__tests__/utils.test.d.ts +1 -0
  59. package/dist/__tests__/utils.test.js +14 -0
  60. package/dist/__tests__/voice.test.js +21 -0
  61. package/dist/agent.js +35 -4
  62. package/dist/api.js +113 -37
  63. package/dist/channels/discord/attachments.d.ts +50 -0
  64. package/dist/channels/discord/attachments.js +137 -0
  65. package/dist/channels/discord/delegation.d.ts +5 -0
  66. package/dist/channels/discord/delegation.js +136 -0
  67. package/dist/channels/discord/handlers.js +694 -7
  68. package/dist/channels/discord/index.d.ts +16 -1
  69. package/dist/channels/discord/index.js +64 -1
  70. package/dist/channels/discord/thread-agents.d.ts +54 -0
  71. package/dist/channels/discord/thread-agents.js +323 -0
  72. package/dist/channels/discord/threads.d.ts +58 -0
  73. package/dist/channels/discord/threads.js +192 -0
  74. package/dist/channels/discord/types.js +4 -2
  75. package/dist/channels/discord/utils.d.ts +16 -0
  76. package/dist/channels/discord/utils.js +86 -6
  77. package/dist/channels/telegram/index.d.ts +1 -1
  78. package/dist/channels/telegram/types.js +1 -1
  79. package/dist/channels/telegram/utils.js +9 -3
  80. package/dist/channels.d.ts +1 -1
  81. package/dist/cli.js +20 -400
  82. package/dist/code-agents/executor.d.ts +1 -1
  83. package/dist/code-agents/executor.js +101 -45
  84. package/dist/code-agents/index.d.ts +2 -7
  85. package/dist/code-agents/index.js +111 -80
  86. package/dist/code-agents/interactive-resume.d.ts +6 -0
  87. package/dist/code-agents/interactive-resume.js +98 -0
  88. package/dist/code-agents/interactive-sessions.d.ts +20 -0
  89. package/dist/code-agents/interactive-sessions.js +132 -0
  90. package/dist/code-agents/parser.js +5 -1
  91. package/dist/code-agents/registry.d.ts +7 -1
  92. package/dist/code-agents/registry.js +11 -23
  93. package/dist/code-agents/stream-formatter.d.ts +8 -0
  94. package/dist/code-agents/stream-formatter.js +92 -0
  95. package/dist/code-agents/types.d.ts +16 -24
  96. package/dist/code-agents/utils.d.ts +35 -11
  97. package/dist/code-agents/utils.js +349 -95
  98. package/dist/code-agents/worktrees.d.ts +37 -0
  99. package/dist/code-agents/worktrees.js +116 -0
  100. package/dist/config.d.ts +2 -4
  101. package/dist/config.js +123 -23
  102. package/dist/cron.d.ts +1 -6
  103. package/dist/cron.js +175 -82
  104. package/dist/dashboard/assets/index-B345aOO-.js +65 -0
  105. package/dist/dashboard/assets/index-ZWK4dalJ.css +1 -0
  106. package/dist/dashboard/index.html +2 -2
  107. package/dist/digests.d.ts +1 -0
  108. package/dist/digests.js +132 -42
  109. package/dist/doctor/checks.d.ts +0 -3
  110. package/dist/doctor/checks.js +1 -108
  111. package/dist/doctor/runner.js +1 -4
  112. package/dist/env-sanitizer.d.ts +2 -0
  113. package/dist/env-sanitizer.js +61 -0
  114. package/dist/exec-approval.d.ts +11 -1
  115. package/dist/exec-approval.js +17 -4
  116. package/dist/gateway.d.ts +3 -1
  117. package/dist/gateway.js +17 -7
  118. package/dist/heartbeat.js +1 -6
  119. package/dist/langfuse.js +3 -29
  120. package/dist/model-selection.js +3 -1
  121. package/dist/providers/adapter.d.ts +118 -0
  122. package/dist/providers/adapter.js +6 -0
  123. package/dist/providers/adapters/anthropic-adapter.d.ts +22 -0
  124. package/dist/providers/adapters/anthropic-adapter.js +204 -0
  125. package/dist/providers/adapters/codex-adapter.d.ts +26 -0
  126. package/dist/providers/adapters/codex-adapter.js +203 -0
  127. package/dist/providers/anthropic.d.ts +1 -0
  128. package/dist/providers/anthropic.js +10 -272
  129. package/dist/providers/codex.d.ts +21 -0
  130. package/dist/providers/codex.js +149 -330
  131. package/dist/providers/content.d.ts +1 -1
  132. package/dist/providers/content.js +2 -2
  133. package/dist/providers/context-manager.d.ts +18 -6
  134. package/dist/providers/context-manager.js +199 -223
  135. package/dist/providers/index.d.ts +9 -1
  136. package/dist/providers/index.js +73 -64
  137. package/dist/providers/loop-utils.d.ts +20 -0
  138. package/dist/providers/loop-utils.js +30 -0
  139. package/dist/providers/tool-loop.d.ts +12 -0
  140. package/dist/providers/tool-loop.js +251 -0
  141. package/dist/providers/utils.d.ts +19 -3
  142. package/dist/providers/utils.js +100 -29
  143. package/dist/secure-store.d.ts +8 -0
  144. package/dist/secure-store.js +80 -0
  145. package/dist/service.js +3 -28
  146. package/dist/sessions.d.ts +3 -0
  147. package/dist/sessions.js +147 -18
  148. package/dist/setup-templates.js +13 -25
  149. package/dist/setup.d.ts +10 -6
  150. package/dist/setup.js +84 -292
  151. package/dist/skills.js +3 -11
  152. package/dist/tools/agent-delegation.d.ts +19 -0
  153. package/dist/tools/agent-delegation.js +49 -0
  154. package/dist/tools/bash-tool.js +89 -34
  155. package/dist/tools/definitions.d.ts +199 -302
  156. package/dist/tools/definitions.js +70 -123
  157. package/dist/tools/execute-context.d.ts +13 -4
  158. package/dist/tools/fetch-tool.js +109 -13
  159. package/dist/tools/file-tools.js +7 -1
  160. package/dist/tools.d.ts +7 -7
  161. package/dist/tools.js +133 -151
  162. package/dist/types.d.ts +37 -30
  163. package/dist/utils.js +4 -6
  164. package/dist/voice.d.ts +1 -1
  165. package/dist/voice.js +17 -4
  166. package/package.json +33 -23
  167. package/templates/TOOLS.md +0 -27
  168. package/dist/__tests__/audit.test.js +0 -122
  169. package/dist/__tests__/code-agents-orchestrator.test.js +0 -216
  170. package/dist/__tests__/code-agents-sandbox.test.js +0 -163
  171. package/dist/__tests__/orchestrator.test.js +0 -425
  172. package/dist/__tests__/sandbox-bridge.test.js +0 -116
  173. package/dist/__tests__/sandbox-manager.test.js +0 -144
  174. package/dist/__tests__/sandbox-mount-security.test.js +0 -139
  175. package/dist/__tests__/sandbox-runtime.test.js +0 -176
  176. package/dist/__tests__/subagent.test.js +0 -240
  177. package/dist/__tests__/telegram.test.js +0 -42
  178. package/dist/code-agents/orchestrator.d.ts +0 -29
  179. package/dist/code-agents/orchestrator.js +0 -694
  180. package/dist/code-agents/worktree.d.ts +0 -40
  181. package/dist/code-agents/worktree.js +0 -215
  182. package/dist/dashboard/assets/index-BoTHPby4.js +0 -65
  183. package/dist/dashboard/assets/index-D4mufvBg.css +0 -1
  184. package/dist/dashboard.d.ts +0 -8
  185. package/dist/dashboard.js +0 -4071
  186. package/dist/discord.d.ts +0 -8
  187. package/dist/discord.js +0 -792
  188. package/dist/mcp-context-a8c.d.ts +0 -13
  189. package/dist/mcp-context-a8c.js +0 -34
  190. package/dist/orchestrator.d.ts +0 -15
  191. package/dist/orchestrator.js +0 -676
  192. package/dist/providers/openai.d.ts +0 -10
  193. package/dist/providers/openai.js +0 -355
  194. package/dist/sandbox/bridge.d.ts +0 -5
  195. package/dist/sandbox/bridge.js +0 -63
  196. package/dist/sandbox/index.d.ts +0 -5
  197. package/dist/sandbox/index.js +0 -4
  198. package/dist/sandbox/manager.d.ts +0 -7
  199. package/dist/sandbox/manager.js +0 -100
  200. package/dist/sandbox/mount-security.d.ts +0 -12
  201. package/dist/sandbox/mount-security.js +0 -122
  202. package/dist/sandbox/runtime.d.ts +0 -39
  203. package/dist/sandbox/runtime.js +0 -192
  204. package/dist/sandbox-utils.d.ts +0 -6
  205. package/dist/sandbox-utils.js +0 -36
  206. package/dist/subagent.d.ts +0 -19
  207. package/dist/subagent.js +0 -407
  208. package/dist/telegram.d.ts +0 -2
  209. package/dist/telegram.js +0 -11
  210. package/dist/tools/browser-tool.d.ts +0 -3
  211. package/dist/tools/browser-tool.js +0 -266
  212. package/sandbox/Dockerfile +0 -40
  213. /package/dist/__tests__/{audit.test.d.ts → code-agents-notifications.test.d.ts} +0 -0
  214. /package/dist/__tests__/{code-agents-orchestrator.test.d.ts → code-agents-worktrees.test.d.ts} +0 -0
  215. /package/dist/__tests__/{code-agents-sandbox.test.d.ts → codex-adapter.test.d.ts} +0 -0
  216. /package/dist/__tests__/{orchestrator.test.d.ts → codex-auth.test.d.ts} +0 -0
  217. /package/dist/__tests__/{sandbox-bridge.test.d.ts → codex-provider-gating.test.d.ts} +0 -0
  218. /package/dist/__tests__/{sandbox-manager.test.d.ts → codex-unified-loop.test.d.ts} +0 -0
  219. /package/dist/__tests__/{sandbox-mount-security.test.d.ts → config-security.test.d.ts} +0 -0
  220. /package/dist/__tests__/{sandbox-runtime.test.d.ts → cron-run.test.d.ts} +0 -0
  221. /package/dist/__tests__/{subagent.test.d.ts → digests.test.d.ts} +0 -0
  222. /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>;