wave-agent-sdk 1.0.5 → 1.0.7

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 (202) hide show
  1. package/dist/agent.d.ts +34 -1
  2. package/dist/agent.js +49 -4
  3. package/dist/index.d.ts +3 -1
  4. package/dist/index.js +1 -1
  5. package/dist/managers/aiManager.d.ts +36 -0
  6. package/dist/managers/aiManager.js +240 -11
  7. package/dist/managers/messageManager.d.ts +2 -4
  8. package/dist/managers/messageManager.js +14 -7
  9. package/dist/managers/slashCommandManager.js +28 -0
  10. package/dist/managers/subagentManager.d.ts +0 -2
  11. package/dist/services/aiService.d.ts +2 -3
  12. package/dist/services/aiService.js +4 -18
  13. package/dist/services/configurationService.d.ts +7 -0
  14. package/dist/services/configurationService.js +10 -0
  15. package/dist/telemetry/instrumentation.d.ts +1 -1
  16. package/dist/telemetry/instrumentation.js +57 -28
  17. package/dist/tools/bashTool.js +16 -6
  18. package/dist/tools/grepTool.js +11 -3
  19. package/dist/types/core.d.ts +1 -34
  20. package/dist/types/index.d.ts +4 -0
  21. package/dist/types/messaging.d.ts +2 -0
  22. package/dist/utils/cacheControlUtils.d.ts +5 -27
  23. package/dist/utils/cacheControlUtils.js +12 -73
  24. package/dist/utils/containerSetup.js +0 -3
  25. package/dist/utils/messageOperations.d.ts +2 -1
  26. package/dist/utils/messageOperations.js +2 -1
  27. package/dist/utils/notificationXml.js +5 -0
  28. package/dist/utils/ripgrep.d.ts +4 -4
  29. package/dist/utils/ripgrep.js +4 -27
  30. package/package.json +20 -8
  31. package/bin/rg +0 -79
  32. package/dist/managers/forkedAgentManager.d.ts +0 -50
  33. package/dist/managers/forkedAgentManager.js +0 -130
  34. package/scripts/install_ripgrep.js +0 -111
  35. package/scripts/postinstall.js +0 -38
  36. package/src/agent.ts +0 -1271
  37. package/src/constants/subagents.ts +0 -4
  38. package/src/constants/toolLimits.ts +0 -15
  39. package/src/constants/tools.ts +0 -24
  40. package/src/core/plugin.ts +0 -237
  41. package/src/core/session.ts +0 -9
  42. package/src/index.ts +0 -38
  43. package/src/managers/MemoryRuleManager.ts +0 -198
  44. package/src/managers/aiManager.ts +0 -2429
  45. package/src/managers/backgroundTaskManager.ts +0 -480
  46. package/src/managers/bangManager.ts +0 -119
  47. package/src/managers/cronManager.ts +0 -401
  48. package/src/managers/foregroundTaskManager.ts +0 -30
  49. package/src/managers/forkedAgentManager.ts +0 -216
  50. package/src/managers/hookManager.ts +0 -1089
  51. package/src/managers/liveConfigManager.ts +0 -445
  52. package/src/managers/lspManager.ts +0 -470
  53. package/src/managers/mcpManager.ts +0 -1010
  54. package/src/managers/messageManager.ts +0 -1184
  55. package/src/managers/messageQueue.ts +0 -157
  56. package/src/managers/permissionManager.ts +0 -1140
  57. package/src/managers/planManager.ts +0 -112
  58. package/src/managers/pluginManager.ts +0 -330
  59. package/src/managers/pluginScopeManager.ts +0 -124
  60. package/src/managers/reversionManager.ts +0 -178
  61. package/src/managers/skillManager.ts +0 -578
  62. package/src/managers/slashCommandManager.ts +0 -564
  63. package/src/managers/subagentManager.ts +0 -915
  64. package/src/managers/toolManager.ts +0 -489
  65. package/src/managers/workflowManager.ts +0 -493
  66. package/src/prompts/autoMemory.ts +0 -33
  67. package/src/prompts/autoMemoryExtraction.ts +0 -146
  68. package/src/prompts/index.ts +0 -506
  69. package/src/prompts/planModeReminders.ts +0 -126
  70. package/src/services/GitService.ts +0 -131
  71. package/src/services/MarketplaceService.ts +0 -1052
  72. package/src/services/MemoryRuleService.ts +0 -71
  73. package/src/services/aiService.ts +0 -919
  74. package/src/services/authService.ts +0 -565
  75. package/src/services/autoMemoryService.ts +0 -280
  76. package/src/services/configurationService.ts +0 -1520
  77. package/src/services/fileWatcher.ts +0 -319
  78. package/src/services/hook.ts +0 -348
  79. package/src/services/initializationService.ts +0 -357
  80. package/src/services/interactionService.ts +0 -229
  81. package/src/services/jsonlHandler.ts +0 -295
  82. package/src/services/memory.ts +0 -234
  83. package/src/services/pluginLoader.ts +0 -252
  84. package/src/services/remoteSettingsService.ts +0 -366
  85. package/src/services/reversionService.ts +0 -122
  86. package/src/services/session.ts +0 -917
  87. package/src/services/taskManager.ts +0 -321
  88. package/src/telemetry/events.ts +0 -65
  89. package/src/telemetry/instrumentation.ts +0 -499
  90. package/src/telemetry/sessionTracing.ts +0 -348
  91. package/src/tools/agentTool.ts +0 -322
  92. package/src/tools/askUserQuestion.ts +0 -160
  93. package/src/tools/bashTool.ts +0 -621
  94. package/src/tools/buildTool.ts +0 -61
  95. package/src/tools/cronCreateTool.ts +0 -161
  96. package/src/tools/cronDeleteTool.ts +0 -51
  97. package/src/tools/cronListTool.ts +0 -47
  98. package/src/tools/editTool.ts +0 -313
  99. package/src/tools/enterPlanMode.ts +0 -124
  100. package/src/tools/enterWorktreeTool.ts +0 -190
  101. package/src/tools/exitPlanMode.ts +0 -139
  102. package/src/tools/exitWorktreeTool.ts +0 -236
  103. package/src/tools/globTool.ts +0 -167
  104. package/src/tools/grepTool.ts +0 -399
  105. package/src/tools/lspTool.ts +0 -883
  106. package/src/tools/readTool.ts +0 -426
  107. package/src/tools/skillTool.ts +0 -248
  108. package/src/tools/taskManagementTools.ts +0 -574
  109. package/src/tools/taskStopTool.ts +0 -78
  110. package/src/tools/types.ts +0 -137
  111. package/src/tools/webFetchTool.ts +0 -373
  112. package/src/tools/workflowTool.ts +0 -205
  113. package/src/tools/writeTool.ts +0 -267
  114. package/src/types/agent.ts +0 -128
  115. package/src/types/auth.ts +0 -19
  116. package/src/types/commands.ts +0 -30
  117. package/src/types/config.ts +0 -43
  118. package/src/types/configuration.ts +0 -168
  119. package/src/types/core.ts +0 -105
  120. package/src/types/cron.ts +0 -13
  121. package/src/types/environment.ts +0 -106
  122. package/src/types/fileSearch.ts +0 -4
  123. package/src/types/history.ts +0 -7
  124. package/src/types/hooks.ts +0 -256
  125. package/src/types/index.ts +0 -41
  126. package/src/types/lsp.ts +0 -98
  127. package/src/types/marketplace.ts +0 -71
  128. package/src/types/mcp.ts +0 -43
  129. package/src/types/memoryRule.ts +0 -31
  130. package/src/types/messaging.ts +0 -114
  131. package/src/types/permissions.ts +0 -80
  132. package/src/types/plugins.ts +0 -39
  133. package/src/types/processes.ts +0 -99
  134. package/src/types/reversion.ts +0 -29
  135. package/src/types/session.ts +0 -12
  136. package/src/types/skills.ts +0 -108
  137. package/src/types/tasks.ts +0 -13
  138. package/src/types/telemetry.ts +0 -98
  139. package/src/types/tools.ts +0 -44
  140. package/src/types/workflow.ts +0 -6
  141. package/src/utils/abortUtils.ts +0 -118
  142. package/src/utils/atomicWrite.ts +0 -61
  143. package/src/utils/bashParser.ts +0 -899
  144. package/src/utils/cacheControlUtils.ts +0 -426
  145. package/src/utils/commandArgumentParser.ts +0 -161
  146. package/src/utils/commandPathResolver.ts +0 -88
  147. package/src/utils/configPaths.ts +0 -220
  148. package/src/utils/configValidator.ts +0 -100
  149. package/src/utils/constants.ts +0 -39
  150. package/src/utils/container.ts +0 -92
  151. package/src/utils/containerSetup.ts +0 -371
  152. package/src/utils/convertMessagesForAPI.ts +0 -326
  153. package/src/utils/cronTasks.ts +0 -128
  154. package/src/utils/cronTasksLock.ts +0 -182
  155. package/src/utils/cronToHuman.ts +0 -99
  156. package/src/utils/customCommands.ts +0 -95
  157. package/src/utils/editUtils.ts +0 -20
  158. package/src/utils/fileFormat.ts +0 -40
  159. package/src/utils/fileSearch.ts +0 -151
  160. package/src/utils/fileUtils.ts +0 -223
  161. package/src/utils/gitUtils.ts +0 -315
  162. package/src/utils/globalLogger.ts +0 -128
  163. package/src/utils/groupMessagesByApiRound.ts +0 -121
  164. package/src/utils/hookMatcher.ts +0 -176
  165. package/src/utils/markdownParser.ts +0 -317
  166. package/src/utils/mcpUtils.ts +0 -138
  167. package/src/utils/messageOperations.ts +0 -659
  168. package/src/utils/modelCapabilities.ts +0 -30
  169. package/src/utils/nameGenerator.ts +0 -99
  170. package/src/utils/notificationXml.ts +0 -57
  171. package/src/utils/openaiClient.ts +0 -251
  172. package/src/utils/parseCronExpression.ts +0 -78
  173. package/src/utils/path.ts +0 -72
  174. package/src/utils/pathEncoder.ts +0 -400
  175. package/src/utils/pathSafety.ts +0 -40
  176. package/src/utils/promptHistory.ts +0 -168
  177. package/src/utils/ripgrep.ts +0 -35
  178. package/src/utils/shellResolver.ts +0 -190
  179. package/src/utils/skillParser.ts +0 -246
  180. package/src/utils/stringUtils.ts +0 -156
  181. package/src/utils/subagentParser.ts +0 -279
  182. package/src/utils/taskReminder.ts +0 -96
  183. package/src/utils/tokenCalculation.ts +0 -43
  184. package/src/utils/tokenEstimate.ts +0 -34
  185. package/src/utils/toolResultStorage.ts +0 -117
  186. package/src/utils/worktreeSession.ts +0 -26
  187. package/src/utils/worktreeUtils.ts +0 -786
  188. package/src/workflow/budgetTracker.ts +0 -34
  189. package/src/workflow/concurrencyLimiter.ts +0 -47
  190. package/src/workflow/journal.ts +0 -95
  191. package/src/workflow/progressReporter.ts +0 -141
  192. package/src/workflow/runState.ts +0 -65
  193. package/src/workflow/scriptRuntime.ts +0 -274
  194. package/src/workflow/structuredOutput.ts +0 -123
  195. package/src/workflow/types.ts +0 -101
  196. package/src/workflow/workflowApis.ts +0 -410
  197. package/vendor/ripgrep/linux-aarch64/rg +0 -0
  198. package/vendor/ripgrep/linux-x86_64/rg +0 -0
  199. package/vendor/ripgrep/macos-aarch64/rg +0 -0
  200. package/vendor/ripgrep/macos-x86_64/rg +0 -0
  201. package/vendor/ripgrep/windows-aarch64/rg.exe +0 -0
  202. package/vendor/ripgrep/windows-x86_64/rg.exe +0 -0
@@ -1,919 +0,0 @@
1
- import { ChatCompletionMessageToolCall } from "openai/resources";
2
- import {
3
- ChatCompletionCreateParamsNonStreaming,
4
- ChatCompletionCreateParamsStreaming,
5
- ChatCompletionMessageParam,
6
- ChatCompletionFunctionTool,
7
- ChatCompletionChunk,
8
- } from "openai/resources.js";
9
- import { OpenAIClient } from "../utils/openaiClient.js";
10
- import { logger } from "../utils/globalLogger.js";
11
- import { addOnceAbortListener } from "../utils/abortUtils.js";
12
- import type { GatewayConfig, ModelConfig } from "../types/index.js";
13
- import { ConfigurationError, CONFIG_ERRORS } from "../types/index.js";
14
- import {
15
- transformMessagesForExplicitCache,
16
- extendUsageWithCacheMetrics,
17
- type ClaudeUsage,
18
- type ClaudeChatCompletionContentPartText,
19
- } from "../utils/cacheControlUtils.js";
20
- import { supportsPromptCaching } from "../utils/modelCapabilities.js";
21
-
22
- import * as os from "os";
23
- import * as fs from "fs";
24
- import * as path from "path";
25
-
26
- import {
27
- WEB_CONTENT_SYSTEM_PROMPT,
28
- type SystemPromptBlock,
29
- } from "../prompts/index.js";
30
-
31
- /**
32
- * Interface for debug data saved during 400 errors
33
- */
34
- interface DebugData {
35
- originalMessages: ChatCompletionMessageParam[];
36
- timestamp: string;
37
- model: string;
38
- workdir: string;
39
- sessionId?: string;
40
- gatewayConfig: {
41
- baseURL?: string;
42
- defaultHeaders?: Record<string, string>;
43
- };
44
- processedMessages?: ChatCompletionMessageParam[];
45
- createParams?:
46
- | ChatCompletionCreateParamsNonStreaming
47
- | ChatCompletionCreateParamsStreaming;
48
- tools?: ChatCompletionFunctionTool[];
49
- }
50
-
51
- /**
52
- * Interface for error data saved during 400 errors
53
- */
54
- interface ErrorData {
55
- error: {
56
- message?: string;
57
- status?: number;
58
- type?: string;
59
- code?: string;
60
- body?: unknown;
61
- stack?: string;
62
- };
63
- timestamp: string;
64
- }
65
-
66
- /**
67
- * Use parametersChunk as compact param for better performance
68
- * Instead of parsing JSON, we use the raw chunk for efficient streaming
69
- */
70
-
71
- /**
72
- * OpenAI model configuration type, based on OpenAI parameters but excluding messages
73
- */
74
- type OpenAIModelConfig = Omit<
75
- ChatCompletionCreateParamsNonStreaming,
76
- "messages"
77
- >;
78
-
79
- // Global rate limiter state for 1 QPS
80
- let nextAllowedTime = 0;
81
- const MIN_INTERVAL = 1000; // 1 second for 1 QPS
82
-
83
- /**
84
- * Resets the rate limiter state. Primarily used for testing.
85
- */
86
- export function resetRateLimiter(): void {
87
- nextAllowedTime = 0;
88
- }
89
-
90
- /**
91
- * Acquires a slot for an AI request, ensuring 1 QPS limit.
92
- * @param abortSignal Optional abort signal to cancel waiting
93
- */
94
- async function acquireSlot(abortSignal?: AbortSignal): Promise<void> {
95
- if (abortSignal?.aborted) {
96
- throw new Error("Request was aborted");
97
- }
98
-
99
- const now = Date.now();
100
- const waitTime = Math.max(0, nextAllowedTime - now);
101
-
102
- // Reserve the slot synchronously to ensure global ordering
103
- nextAllowedTime = Math.max(now, nextAllowedTime) + MIN_INTERVAL;
104
-
105
- if (waitTime > 0) {
106
- await new Promise<void>((resolve, reject) => {
107
- const timeout = setTimeout(() => {
108
- cleanup();
109
- resolve();
110
- }, waitTime);
111
-
112
- const cleanup = abortSignal
113
- ? addOnceAbortListener(abortSignal, () => {
114
- clearTimeout(timeout);
115
- reject(new Error("Request was aborted"));
116
- })
117
- : () => {};
118
- });
119
- }
120
- }
121
-
122
- /**
123
- * Get specific configuration parameters based on model name
124
- * @param modelName Model name
125
- * @param baseConfig Base configuration
126
- * @returns Configured model parameters
127
- */
128
- function getModelConfig(
129
- modelName: string,
130
- baseConfig: Partial<OpenAIModelConfig> = {},
131
- ): OpenAIModelConfig {
132
- const config: OpenAIModelConfig = {
133
- model: modelName,
134
- stream: false,
135
- ...baseConfig,
136
- };
137
-
138
- // Handle parameter exclusion: if a parameter is explicitly set to null, remove it.
139
- // This allows users to "unset" default parameters like temperature for models that don't support them.
140
- for (const key in config) {
141
- if (config[key as keyof OpenAIModelConfig] === null) {
142
- delete config[key as keyof OpenAIModelConfig];
143
- }
144
- }
145
-
146
- return config;
147
- }
148
-
149
- /**
150
- * Effective disable-thinking params for a model config. No default: these
151
- * params are only sent when the user explicitly configures
152
- * `models[X].disableThinkingOptions` (an empty object clears them), so a
153
- * gateway that doesn't understand the params is never hit with them.
154
- */
155
- function effectiveDisableThinkingOptions(
156
- modelConfig: ModelConfig,
157
- ): Record<string, unknown> | undefined {
158
- return modelConfig.disableThinkingOptions;
159
- }
160
-
161
- export interface CallAgentOptions {
162
- // Resolved configuration
163
- gatewayConfig: GatewayConfig;
164
- modelConfig: ModelConfig;
165
-
166
- // Existing parameters (preserved)
167
- messages: ChatCompletionMessageParam[];
168
- sessionId?: string;
169
- abortSignal?: AbortSignal;
170
- workdir: string; // Current working directory
171
- tools?: ChatCompletionFunctionTool[]; // Tool configuration
172
- model?: string; // Custom model
173
- systemPrompt?: string | SystemPromptBlock[]; // Custom system prompt (string or structured blocks)
174
- maxTokens?: number; // Maximum output tokens
175
- toolChoice?:
176
- | "auto"
177
- | "none"
178
- | "required"
179
- | { type: "function"; function: { name: string } }; // Force tool selection
180
-
181
- // Force SSE streaming independent of callback presence. Long-running
182
- // non-interactive calls (e.g. the compaction fork) need streaming so a
183
- // slow reasoning model isn't killed by a gateway idle timeout before the
184
- // first byte arrives.
185
- stream?: boolean;
186
-
187
- // NEW: Streaming callbacks
188
- onContentUpdate?: (content: string) => void;
189
- onToolUpdate?: (toolCall: {
190
- id: string;
191
- name: string;
192
- parameters: string;
193
- parametersChunk?: string;
194
- stage?: "start" | "streaming" | "running" | "end";
195
- }) => void;
196
- onReasoningUpdate?: (content: string) => void;
197
-
198
- // Disable-thinking params for fast-model subagent calls (merged into the
199
- // request; never used in the agent loop).
200
- disableThinkingOptions?: Record<string, unknown>;
201
- }
202
-
203
- export interface CallAgentResult {
204
- content?: string;
205
- tool_calls?: ChatCompletionMessageToolCall[];
206
- reasoning_content?: string;
207
- usage?: ClaudeUsage;
208
- finish_reason?:
209
- | "stop"
210
- | "length"
211
- | "tool_calls"
212
- | "content_filter"
213
- | "function_call"
214
- | null;
215
- response_headers?: Record<string, string>;
216
- additionalFields?: Record<string, unknown>;
217
- }
218
-
219
- function validateModelConfig(
220
- modelConfig: ModelConfig,
221
- ): asserts modelConfig is ModelConfig & { model: string; fastModel: string } {
222
- if (!modelConfig.model) {
223
- throw new ConfigurationError(CONFIG_ERRORS.MISSING_MODEL, "model", {
224
- constructor: undefined,
225
- environment: process.env.WAVE_MODEL,
226
- });
227
- }
228
- if (!modelConfig.fastModel) {
229
- throw new ConfigurationError(
230
- CONFIG_ERRORS.MISSING_FAST_MODEL,
231
- "fastModel",
232
- {
233
- constructor: undefined,
234
- environment: process.env.WAVE_FAST_MODEL,
235
- },
236
- );
237
- }
238
- }
239
-
240
- export async function callAgent(
241
- options: CallAgentOptions,
242
- ): Promise<CallAgentResult> {
243
- const {
244
- gatewayConfig,
245
- modelConfig,
246
- messages,
247
- abortSignal,
248
- workdir,
249
- tools,
250
- model,
251
- systemPrompt,
252
- onContentUpdate,
253
- onToolUpdate,
254
- onReasoningUpdate,
255
- disableThinkingOptions,
256
- } = options;
257
-
258
- // Validate model config at call time
259
- validateModelConfig(modelConfig);
260
-
261
- // Apply global 1 QPS rate limit
262
- if (
263
- process.env.NODE_ENV !== "test" ||
264
- modelConfig.model === "rate-limit-test"
265
- ) {
266
- await acquireSlot(abortSignal);
267
- }
268
-
269
- // Declare variables outside try block for error handling access
270
- let openaiMessages: ChatCompletionMessageParam[] | undefined;
271
- let createParams:
272
- | ChatCompletionCreateParamsNonStreaming
273
- | ChatCompletionCreateParamsStreaming
274
- | undefined;
275
- let processedTools: ChatCompletionFunctionTool[] | undefined;
276
-
277
- try {
278
- // Create OpenAI client with injected configuration
279
- const openai = new OpenAIClient({
280
- apiKey: gatewayConfig.apiKey,
281
- baseURL: gatewayConfig.baseURL,
282
- defaultHeaders: gatewayConfig.defaultHeaders,
283
- fetchOptions: gatewayConfig.fetchOptions,
284
- fetch: gatewayConfig.fetch,
285
- });
286
-
287
- // Determine model early (needed for system prompt construction)
288
- const resolvedMaxTokens = options.maxTokens ?? modelConfig.maxTokens;
289
-
290
- // Build system message content
291
- let systemMessage: ChatCompletionMessageParam;
292
- if (Array.isArray(systemPrompt)) {
293
- if (supportsPromptCaching(modelConfig.capabilities)) {
294
- // For Claude models, map blocks to content parts with cache_control on cacheable blocks
295
- const contentParts: ClaudeChatCompletionContentPartText[] =
296
- systemPrompt.map((block) => {
297
- const part: ClaudeChatCompletionContentPartText = {
298
- type: "text",
299
- text: block.text,
300
- };
301
- if (block.cacheable) {
302
- part.cache_control = { type: "ephemeral" };
303
- }
304
- return part;
305
- });
306
- systemMessage = {
307
- role: "system",
308
- content: contentParts,
309
- } as ChatCompletionMessageParam;
310
- } else {
311
- // For non-Claude models, join blocks into a single string
312
- systemMessage = {
313
- role: "system",
314
- content: systemPrompt.map((b) => b.text).join("\n\n"),
315
- };
316
- }
317
- } else {
318
- systemMessage = {
319
- role: "system",
320
- content: systemPrompt || "",
321
- };
322
- }
323
-
324
- // ChatCompletionMessageParam[] is already in OpenAI format, add system prompt to the beginning
325
- openaiMessages = [systemMessage, ...messages];
326
-
327
- processedTools = tools;
328
-
329
- if (supportsPromptCaching(modelConfig.capabilities)) {
330
- openaiMessages = transformMessagesForExplicitCache(
331
- openaiMessages,
332
- modelConfig.capabilities,
333
- );
334
- }
335
-
336
- const openaiModelConfig = getModelConfig(model || modelConfig.model, {
337
- max_tokens: resolvedMaxTokens,
338
- ...(modelConfig.options || {}),
339
- ...(disableThinkingOptions ?? {}),
340
- });
341
-
342
- // Determine if streaming is needed
343
- const isStreaming =
344
- options.stream === true ||
345
- !!(onContentUpdate || onToolUpdate || onReasoningUpdate);
346
-
347
- // Prepare API call parameters
348
- createParams = {
349
- ...openaiModelConfig,
350
- messages: openaiMessages,
351
- stream: isStreaming,
352
- } as
353
- | ChatCompletionCreateParamsNonStreaming
354
- | ChatCompletionCreateParamsStreaming;
355
-
356
- // Only add tools if they exist
357
- if (processedTools && processedTools.length > 0) {
358
- createParams.tools = processedTools;
359
- }
360
-
361
- // Add tool_choice if specified
362
- if (options.toolChoice) {
363
- createParams.tool_choice = options.toolChoice;
364
- }
365
-
366
- if (isStreaming) {
367
- // Handle streaming response
368
- const { data: stream, response } = await openai.chat.completions
369
- .create(createParams as ChatCompletionCreateParamsStreaming, {
370
- signal: abortSignal,
371
- })
372
- .withResponse();
373
-
374
- // Extract response headers
375
- const responseHeaders: Record<string, string> = {};
376
- (response.headers as Headers).forEach((value: string, key: string) => {
377
- responseHeaders[key] = value;
378
- });
379
-
380
- return await processStreamingResponse(
381
- stream,
382
- onContentUpdate,
383
- onToolUpdate,
384
- onReasoningUpdate,
385
- abortSignal,
386
- responseHeaders,
387
- );
388
- } else {
389
- // Handle non-streaming response
390
- const { data: response, response: rawResponse } =
391
- await openai.chat.completions
392
- .create(createParams as ChatCompletionCreateParamsNonStreaming, {
393
- signal: abortSignal,
394
- })
395
- .withResponse();
396
-
397
- // Extract response headers
398
- const responseHeaders: Record<string, string> = {};
399
- (rawResponse.headers as Headers).forEach((value: string, key: string) => {
400
- responseHeaders[key] = value;
401
- });
402
-
403
- const finalMessage = response.choices[0]?.message;
404
- const finishReason = response.choices[0]?.finish_reason || null;
405
-
406
- let totalUsage = response.usage
407
- ? {
408
- prompt_tokens: response.usage.prompt_tokens,
409
- completion_tokens: response.usage.completion_tokens,
410
- total_tokens: response.usage.total_tokens,
411
- }
412
- : undefined;
413
-
414
- // Extend usage with cache metrics (Claude top-level + OpenAI prompt_tokens_details)
415
- if (totalUsage && response.usage) {
416
- totalUsage = extendUsageWithCacheMetrics(
417
- totalUsage,
418
- response.usage as Partial<ClaudeUsage>,
419
- );
420
- }
421
-
422
- const result: CallAgentResult = {};
423
-
424
- if (finalMessage) {
425
- const {
426
- content: finalContent,
427
- tool_calls: finalToolCalls,
428
- reasoning_content: finalReasoningContent,
429
- ...otherFields
430
- } = finalMessage as unknown as {
431
- content?: string;
432
- tool_calls?: ChatCompletionMessageToolCall[];
433
- reasoning_content?: string;
434
- [key: string]: unknown;
435
- };
436
-
437
- if (typeof finalContent === "string" && finalContent.length > 0) {
438
- result.content = finalContent;
439
- }
440
-
441
- if (typeof finalReasoningContent === "string") {
442
- result.reasoning_content = finalReasoningContent;
443
- }
444
-
445
- if (Array.isArray(finalToolCalls) && finalToolCalls.length > 0) {
446
- result.tool_calls = finalToolCalls;
447
- }
448
-
449
- if (Object.keys(otherFields).length > 0) {
450
- const additionalFields: Record<string, unknown> = {};
451
- for (const [key, value] of Object.entries(otherFields)) {
452
- if (value !== undefined && key !== "role") {
453
- additionalFields[key] = value;
454
- }
455
- }
456
- if (Object.keys(additionalFields).length > 0) {
457
- result.additionalFields = additionalFields;
458
- }
459
- }
460
- }
461
-
462
- if (totalUsage) {
463
- result.usage = totalUsage;
464
- }
465
-
466
- if (finishReason) {
467
- result.finish_reason = finishReason;
468
- }
469
-
470
- if (Object.keys(responseHeaders).length > 0) {
471
- result.response_headers = responseHeaders;
472
- }
473
-
474
- return result;
475
- }
476
- } catch (error) {
477
- if ((error as Error).name === "AbortError") {
478
- logger.info("OpenAI request aborted");
479
- throw new Error("Request was aborted");
480
- }
481
-
482
- // Check if it's a 400 error and save messages to temp directory
483
- if (
484
- error &&
485
- typeof error === "object" &&
486
- "status" in error &&
487
- error.status === 400
488
- ) {
489
- try {
490
- // Create temp directory for error debugging
491
- const tempDir = fs.mkdtempSync(
492
- path.join(os.tmpdir(), "callAgent-400-error-"),
493
- );
494
- const messagesFile = path.join(tempDir, "messages.json");
495
- const errorFile = path.join(tempDir, "error.json");
496
-
497
- // Save complete messages to temp file
498
- const debugData: DebugData = {
499
- originalMessages: messages,
500
- timestamp: new Date().toISOString(),
501
- model: model || modelConfig.model,
502
- workdir,
503
- sessionId: options.sessionId,
504
- gatewayConfig: {
505
- baseURL: gatewayConfig.baseURL,
506
- // Don't include apiKey for security
507
- defaultHeaders: gatewayConfig.defaultHeaders,
508
- },
509
- };
510
-
511
- // Add processed messages if they exist
512
- if (typeof openaiMessages !== "undefined") {
513
- debugData.processedMessages = openaiMessages;
514
- }
515
-
516
- // Add create params if they exist
517
- if (typeof createParams !== "undefined") {
518
- debugData.createParams = createParams;
519
- }
520
-
521
- // Add tools if they exist
522
- if (processedTools) {
523
- debugData.tools = processedTools;
524
- }
525
-
526
- fs.writeFileSync(messagesFile, JSON.stringify(debugData, null, 2));
527
-
528
- // Save error details
529
- const errorData: ErrorData = {
530
- error: {
531
- message:
532
- error && typeof error === "object" && "message" in error
533
- ? String(error.message)
534
- : undefined,
535
- status:
536
- error && typeof error === "object" && "status" in error
537
- ? Number(error.status)
538
- : undefined,
539
- type:
540
- error && typeof error === "object" && "type" in error
541
- ? String(error.type)
542
- : undefined,
543
- code:
544
- error && typeof error === "object" && "code" in error
545
- ? String(error.code)
546
- : undefined,
547
- body:
548
- error && typeof error === "object" && "body" in error
549
- ? error.body
550
- : undefined,
551
- stack:
552
- error && typeof error === "object" && "stack" in error
553
- ? String(error.stack)
554
- : undefined,
555
- },
556
- timestamp: new Date().toISOString(),
557
- };
558
-
559
- fs.writeFileSync(errorFile, JSON.stringify(errorData, null, 2));
560
-
561
- logger.error(
562
- "callAgent 400 error occurred. Debug files saved to:",
563
- tempDir,
564
- );
565
- logger.error("Messages file:", messagesFile);
566
- logger.error("Error file:", errorFile);
567
- logger.error("Error details:", error);
568
- } catch (saveError) {
569
- logger.error("Failed to save 400 error debug files:", saveError);
570
- }
571
- }
572
-
573
- logger.error("Failed to call OpenAI:", error);
574
- throw error;
575
- }
576
- }
577
-
578
- /**
579
- * Process streaming response from OpenAI API
580
- * @param stream Async iterator of chat completion chunks
581
- * @param onContentUpdate Callback for content updates
582
- * @param onToolUpdate Callback for tool updates
583
- * @param abortSignal Optional abort signal
584
- * @param responseHeaders Response headers from the initial request
585
- * @returns Final result with accumulated content and tool calls
586
- */
587
- async function processStreamingResponse(
588
- stream: AsyncIterable<ChatCompletionChunk>,
589
- onContentUpdate?: (content: string) => void,
590
- onToolUpdate?: (toolCall: {
591
- id: string;
592
- name: string;
593
- parameters: string;
594
- parametersChunk?: string;
595
- stage?: "start" | "streaming" | "running" | "end";
596
- }) => void,
597
- onReasoningUpdate?: (content: string) => void,
598
- abortSignal?: AbortSignal,
599
- responseHeaders?: Record<string, string>,
600
- ): Promise<CallAgentResult> {
601
- let accumulatedContent = "";
602
- let accumulatedReasoningContent = "";
603
- let hasReasoningContent = false;
604
- const toolCalls: {
605
- id: string;
606
- type: "function";
607
- function: {
608
- name: string;
609
- arguments: string;
610
- };
611
- }[] = [];
612
- const additionalDeltaFields: Record<string, unknown> = {};
613
- let usage: CallAgentResult["usage"] = undefined;
614
- let finishReason: CallAgentResult["finish_reason"] = null;
615
-
616
- try {
617
- for await (const chunk of stream) {
618
- // Check for abort signal
619
- if (abortSignal?.aborted) {
620
- throw new Error("Request was aborted");
621
- }
622
-
623
- // Check for usage information in any chunk
624
- if (chunk.usage) {
625
- let chunkUsage = {
626
- prompt_tokens: chunk.usage.prompt_tokens,
627
- completion_tokens: chunk.usage.completion_tokens,
628
- total_tokens: chunk.usage.total_tokens,
629
- };
630
-
631
- // Extend usage with cache metrics (Claude top-level + OpenAI prompt_tokens_details)
632
- chunkUsage = extendUsageWithCacheMetrics(
633
- chunkUsage,
634
- chunk.usage as Partial<ClaudeUsage>,
635
- );
636
-
637
- usage = chunkUsage;
638
- }
639
-
640
- // Check for finish_reason in the choice
641
- const choice = chunk.choices?.[0];
642
- if (choice?.finish_reason) {
643
- finishReason = choice.finish_reason;
644
- }
645
-
646
- const delta = choice?.delta;
647
- if (!delta) {
648
- continue;
649
- }
650
-
651
- const {
652
- content,
653
- tool_calls: toolCallUpdates,
654
- reasoning_content,
655
- ...deltaMetadata
656
- } = delta as unknown as {
657
- content?: string;
658
- tool_calls?: ChatCompletionChunk.Choice.Delta.ToolCall[];
659
- reasoning_content?: string;
660
- [key: string]: unknown;
661
- };
662
-
663
- if (Object.keys(deltaMetadata).length > 0) {
664
- Object.assign(additionalDeltaFields, deltaMetadata);
665
- }
666
-
667
- if (typeof content === "string" && content.length > 0) {
668
- // Note: OpenAI API already handles UTF-8 character boundaries correctly in streaming,
669
- // ensuring that delta.content always contains complete UTF-8 strings
670
- accumulatedContent += content;
671
- if (onContentUpdate) {
672
- onContentUpdate(accumulatedContent);
673
- }
674
- }
675
-
676
- if (typeof reasoning_content === "string") {
677
- hasReasoningContent = true;
678
- if (reasoning_content.length > 0) {
679
- accumulatedReasoningContent += reasoning_content;
680
- if (onReasoningUpdate) {
681
- onReasoningUpdate(accumulatedReasoningContent);
682
- }
683
- }
684
- }
685
-
686
- if (Array.isArray(toolCallUpdates)) {
687
- for (const rawToolCall of toolCallUpdates) {
688
- const toolCallDelta =
689
- rawToolCall as ChatCompletionChunk.Choice.Delta.ToolCall;
690
-
691
- if (!toolCallDelta.function) {
692
- continue;
693
- }
694
-
695
- const functionDelta = toolCallDelta.function;
696
-
697
- let existingCall;
698
- let isNew = false;
699
-
700
- if (toolCallDelta.id) {
701
- existingCall = toolCalls.find((t) => t.id === toolCallDelta.id);
702
- if (!existingCall) {
703
- existingCall = {
704
- id: toolCallDelta.id,
705
- type: "function" as const,
706
- function: {
707
- name: functionDelta.name || "",
708
- arguments: "",
709
- },
710
- };
711
- toolCalls.push(existingCall);
712
- isNew = true;
713
- }
714
- } else {
715
- existingCall = toolCalls[toolCalls.length - 1];
716
- }
717
-
718
- if (!existingCall) {
719
- continue;
720
- }
721
-
722
- if (functionDelta.name) {
723
- existingCall.function.name = functionDelta.name;
724
- }
725
-
726
- // Emit start stage when a new tool call is created and we have the tool name
727
- if (onToolUpdate && isNew && existingCall.function.name) {
728
- onToolUpdate({
729
- id: existingCall.id,
730
- name: existingCall.function.name,
731
- parameters: "", // Empty parameters for start stage
732
- parametersChunk: "", // Empty chunk for start stage
733
- stage: "start", // New tool call triggers start stage
734
- });
735
- isNew = false; // Prevent duplicate start emissions
736
- }
737
-
738
- if (functionDelta.arguments) {
739
- existingCall.function.arguments += functionDelta.arguments;
740
- }
741
-
742
- // Emit streaming updates for all chunks with actual content (including first chunk)
743
- if (
744
- onToolUpdate &&
745
- existingCall.function.name &&
746
- functionDelta.arguments &&
747
- functionDelta.arguments.length > 0 // Only emit streaming for chunks with actual content
748
- ) {
749
- onToolUpdate({
750
- id: existingCall.id,
751
- name: existingCall.function.name,
752
- parameters: existingCall.function.arguments,
753
- parametersChunk: functionDelta.arguments,
754
- stage: "streaming",
755
- });
756
- }
757
- }
758
- }
759
- }
760
- } catch (error) {
761
- if ((error as Error).message === "Request was aborted") {
762
- throw error;
763
- }
764
- throw error;
765
- }
766
-
767
- // Prepare final result
768
- const result: CallAgentResult = {};
769
-
770
- if (accumulatedContent) {
771
- result.content = accumulatedContent.trim();
772
- }
773
-
774
- if (hasReasoningContent) {
775
- result.reasoning_content = accumulatedReasoningContent.trim();
776
- }
777
-
778
- if (toolCalls.length > 0) {
779
- result.tool_calls = toolCalls;
780
- }
781
-
782
- if (usage) {
783
- result.usage = usage;
784
- }
785
-
786
- if (finishReason) {
787
- result.finish_reason = finishReason;
788
- }
789
-
790
- if (responseHeaders && Object.keys(responseHeaders).length > 0) {
791
- result.response_headers = responseHeaders;
792
- }
793
-
794
- if (Object.keys(additionalDeltaFields).length > 0) {
795
- result.additionalFields = {};
796
- for (const [key, value] of Object.entries(additionalDeltaFields)) {
797
- if (value !== undefined && key !== "role") {
798
- result.additionalFields[key] = value;
799
- }
800
- }
801
- if (Object.keys(result.additionalFields).length === 0) {
802
- delete result.additionalFields;
803
- }
804
- }
805
-
806
- return result;
807
- }
808
-
809
- export interface ProcessWebContentOptions {
810
- // Resolved configuration
811
- gatewayConfig: GatewayConfig;
812
- modelConfig: ModelConfig;
813
-
814
- // Parameters
815
- content: string;
816
- prompt: string;
817
- abortSignal?: AbortSignal;
818
- model?: string;
819
- }
820
-
821
- export interface ProcessWebContentResult {
822
- content: string;
823
- usage?: {
824
- prompt_tokens: number;
825
- completion_tokens: number;
826
- total_tokens: number;
827
- };
828
- }
829
-
830
- export async function processWebContent(
831
- options: ProcessWebContentOptions,
832
- ): Promise<ProcessWebContentResult> {
833
- const { gatewayConfig, modelConfig, content, prompt, abortSignal } = options;
834
-
835
- // Validate model config at call time
836
- validateModelConfig(modelConfig);
837
-
838
- // Apply global 1 QPS rate limit
839
- if (
840
- process.env.NODE_ENV !== "test" ||
841
- modelConfig.model === "rate-limit-test"
842
- ) {
843
- await acquireSlot(abortSignal);
844
- }
845
-
846
- // Create OpenAI client with injected configuration
847
- const openai = new OpenAIClient({
848
- apiKey: gatewayConfig.apiKey,
849
- baseURL: gatewayConfig.baseURL,
850
- defaultHeaders: gatewayConfig.defaultHeaders,
851
- fetchOptions: gatewayConfig.fetchOptions,
852
- fetch: gatewayConfig.fetch,
853
- });
854
-
855
- // When a fast model override is provided, use the fast model's options
856
- // (if configured); otherwise fall back to the agent model's options.
857
- const activeExtraParams = options.model
858
- ? modelConfig.fastModelOptions || {}
859
- : modelConfig.options || {};
860
-
861
- // Disable-thinking params only apply to the fast-model override path;
862
- // the agent-model path is untouched.
863
- const disableThinking = options.model
864
- ? effectiveDisableThinkingOptions(modelConfig)
865
- : undefined;
866
-
867
- const openaiModelConfig = getModelConfig(options.model || modelConfig.model, {
868
- temperature: 0.1,
869
- max_tokens: 4096,
870
- ...activeExtraParams,
871
- ...(disableThinking || {}),
872
- });
873
-
874
- try {
875
- const response = await openai.chat.completions.create(
876
- {
877
- ...openaiModelConfig,
878
- messages: [
879
- {
880
- role: "system",
881
- content: WEB_CONTENT_SYSTEM_PROMPT,
882
- },
883
- {
884
- role: "user",
885
- content: `Web Content:\n\n${content}\n\nUser Prompt: ${prompt}`,
886
- },
887
- ],
888
- },
889
- {
890
- signal: abortSignal,
891
- },
892
- );
893
-
894
- const result = response.choices[0]?.message?.content?.trim();
895
- if (!result) {
896
- throw new Error("Failed to process web content: Empty response from AI");
897
- }
898
- const usage = response.usage
899
- ? {
900
- prompt_tokens: response.usage.prompt_tokens,
901
- completion_tokens: response.usage.completion_tokens,
902
- total_tokens: response.usage.total_tokens,
903
- }
904
- : undefined;
905
-
906
- return {
907
- content: result,
908
- usage,
909
- };
910
- } catch (error) {
911
- if ((error as Error).name === "AbortError") {
912
- logger.info("Web content processing request was aborted");
913
- throw new Error("Web content processing request was aborted");
914
- }
915
- logger.error("Failed to process web content:", error);
916
- throw error;
917
- }
918
- }
919
-