wave-agent-sdk 1.0.4 → 1.0.6
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/dist/agent.d.ts +23 -1
- package/dist/agent.js +34 -4
- package/dist/managers/aiManager.d.ts +36 -0
- package/dist/managers/aiManager.js +240 -8
- package/dist/managers/messageManager.d.ts +1 -3
- package/dist/managers/messageManager.js +19 -9
- package/dist/managers/slashCommandManager.js +28 -0
- package/dist/managers/subagentManager.d.ts +0 -2
- package/dist/services/session.js +2 -9
- package/dist/telemetry/instrumentation.d.ts +1 -1
- package/dist/telemetry/instrumentation.js +57 -28
- package/dist/tools/bashTool.js +16 -6
- package/dist/tools/grepTool.js +11 -3
- package/dist/types/index.d.ts +3 -0
- package/dist/types/messaging.d.ts +2 -0
- package/dist/utils/containerSetup.js +0 -3
- package/dist/utils/messageOperations.d.ts +10 -1
- package/dist/utils/messageOperations.js +19 -1
- package/dist/utils/notificationXml.js +5 -0
- package/dist/utils/ripgrep.d.ts +4 -4
- package/dist/utils/ripgrep.js +4 -27
- package/package.json +20 -8
- package/bin/rg +0 -79
- package/dist/managers/forkedAgentManager.d.ts +0 -50
- package/dist/managers/forkedAgentManager.js +0 -130
- package/scripts/install_ripgrep.js +0 -111
- package/scripts/postinstall.js +0 -38
- package/src/agent.ts +0 -1271
- package/src/constants/subagents.ts +0 -4
- package/src/constants/toolLimits.ts +0 -15
- package/src/constants/tools.ts +0 -24
- package/src/core/plugin.ts +0 -237
- package/src/core/session.ts +0 -9
- package/src/index.ts +0 -38
- package/src/managers/MemoryRuleManager.ts +0 -198
- package/src/managers/aiManager.ts +0 -2429
- package/src/managers/backgroundTaskManager.ts +0 -480
- package/src/managers/bangManager.ts +0 -119
- package/src/managers/cronManager.ts +0 -401
- package/src/managers/foregroundTaskManager.ts +0 -30
- package/src/managers/forkedAgentManager.ts +0 -216
- package/src/managers/hookManager.ts +0 -1089
- package/src/managers/liveConfigManager.ts +0 -445
- package/src/managers/lspManager.ts +0 -470
- package/src/managers/mcpManager.ts +0 -1010
- package/src/managers/messageManager.ts +0 -1180
- package/src/managers/messageQueue.ts +0 -157
- package/src/managers/permissionManager.ts +0 -1140
- package/src/managers/planManager.ts +0 -112
- package/src/managers/pluginManager.ts +0 -330
- package/src/managers/pluginScopeManager.ts +0 -124
- package/src/managers/reversionManager.ts +0 -178
- package/src/managers/skillManager.ts +0 -578
- package/src/managers/slashCommandManager.ts +0 -564
- package/src/managers/subagentManager.ts +0 -915
- package/src/managers/toolManager.ts +0 -489
- package/src/managers/workflowManager.ts +0 -493
- package/src/prompts/autoMemory.ts +0 -33
- package/src/prompts/autoMemoryExtraction.ts +0 -146
- package/src/prompts/index.ts +0 -506
- package/src/prompts/planModeReminders.ts +0 -126
- package/src/services/GitService.ts +0 -131
- package/src/services/MarketplaceService.ts +0 -1052
- package/src/services/MemoryRuleService.ts +0 -71
- package/src/services/aiService.ts +0 -919
- package/src/services/authService.ts +0 -565
- package/src/services/autoMemoryService.ts +0 -280
- package/src/services/configurationService.ts +0 -1520
- package/src/services/fileWatcher.ts +0 -319
- package/src/services/hook.ts +0 -348
- package/src/services/initializationService.ts +0 -357
- package/src/services/interactionService.ts +0 -229
- package/src/services/jsonlHandler.ts +0 -295
- package/src/services/memory.ts +0 -234
- package/src/services/pluginLoader.ts +0 -252
- package/src/services/remoteSettingsService.ts +0 -366
- package/src/services/reversionService.ts +0 -122
- package/src/services/session.ts +0 -922
- package/src/services/taskManager.ts +0 -321
- package/src/telemetry/events.ts +0 -65
- package/src/telemetry/instrumentation.ts +0 -499
- package/src/telemetry/sessionTracing.ts +0 -348
- package/src/tools/agentTool.ts +0 -322
- package/src/tools/askUserQuestion.ts +0 -160
- package/src/tools/bashTool.ts +0 -621
- package/src/tools/buildTool.ts +0 -61
- package/src/tools/cronCreateTool.ts +0 -161
- package/src/tools/cronDeleteTool.ts +0 -51
- package/src/tools/cronListTool.ts +0 -47
- package/src/tools/editTool.ts +0 -313
- package/src/tools/enterPlanMode.ts +0 -124
- package/src/tools/enterWorktreeTool.ts +0 -190
- package/src/tools/exitPlanMode.ts +0 -139
- package/src/tools/exitWorktreeTool.ts +0 -236
- package/src/tools/globTool.ts +0 -167
- package/src/tools/grepTool.ts +0 -399
- package/src/tools/lspTool.ts +0 -883
- package/src/tools/readTool.ts +0 -426
- package/src/tools/skillTool.ts +0 -248
- package/src/tools/taskManagementTools.ts +0 -574
- package/src/tools/taskStopTool.ts +0 -78
- package/src/tools/types.ts +0 -137
- package/src/tools/webFetchTool.ts +0 -373
- package/src/tools/workflowTool.ts +0 -205
- package/src/tools/writeTool.ts +0 -267
- package/src/types/agent.ts +0 -128
- package/src/types/auth.ts +0 -19
- package/src/types/commands.ts +0 -30
- package/src/types/config.ts +0 -43
- package/src/types/configuration.ts +0 -168
- package/src/types/core.ts +0 -105
- package/src/types/cron.ts +0 -13
- package/src/types/environment.ts +0 -106
- package/src/types/fileSearch.ts +0 -4
- package/src/types/history.ts +0 -7
- package/src/types/hooks.ts +0 -256
- package/src/types/index.ts +0 -41
- package/src/types/lsp.ts +0 -98
- package/src/types/marketplace.ts +0 -71
- package/src/types/mcp.ts +0 -43
- package/src/types/memoryRule.ts +0 -31
- package/src/types/messaging.ts +0 -114
- package/src/types/permissions.ts +0 -80
- package/src/types/plugins.ts +0 -39
- package/src/types/processes.ts +0 -99
- package/src/types/reversion.ts +0 -29
- package/src/types/session.ts +0 -12
- package/src/types/skills.ts +0 -108
- package/src/types/tasks.ts +0 -13
- package/src/types/telemetry.ts +0 -98
- package/src/types/tools.ts +0 -44
- package/src/types/workflow.ts +0 -6
- package/src/utils/abortUtils.ts +0 -118
- package/src/utils/atomicWrite.ts +0 -61
- package/src/utils/bashParser.ts +0 -899
- package/src/utils/cacheControlUtils.ts +0 -426
- package/src/utils/commandArgumentParser.ts +0 -161
- package/src/utils/commandPathResolver.ts +0 -88
- package/src/utils/configPaths.ts +0 -220
- package/src/utils/configValidator.ts +0 -100
- package/src/utils/constants.ts +0 -39
- package/src/utils/container.ts +0 -92
- package/src/utils/containerSetup.ts +0 -371
- package/src/utils/convertMessagesForAPI.ts +0 -326
- package/src/utils/cronTasks.ts +0 -128
- package/src/utils/cronTasksLock.ts +0 -182
- package/src/utils/cronToHuman.ts +0 -99
- package/src/utils/customCommands.ts +0 -95
- package/src/utils/editUtils.ts +0 -20
- package/src/utils/fileFormat.ts +0 -40
- package/src/utils/fileSearch.ts +0 -151
- package/src/utils/fileUtils.ts +0 -223
- package/src/utils/gitUtils.ts +0 -315
- package/src/utils/globalLogger.ts +0 -128
- package/src/utils/groupMessagesByApiRound.ts +0 -121
- package/src/utils/hookMatcher.ts +0 -176
- package/src/utils/markdownParser.ts +0 -317
- package/src/utils/mcpUtils.ts +0 -138
- package/src/utils/messageOperations.ts +0 -641
- package/src/utils/modelCapabilities.ts +0 -30
- package/src/utils/nameGenerator.ts +0 -99
- package/src/utils/notificationXml.ts +0 -57
- package/src/utils/openaiClient.ts +0 -251
- package/src/utils/parseCronExpression.ts +0 -78
- package/src/utils/path.ts +0 -72
- package/src/utils/pathEncoder.ts +0 -400
- package/src/utils/pathSafety.ts +0 -40
- package/src/utils/promptHistory.ts +0 -168
- package/src/utils/ripgrep.ts +0 -35
- package/src/utils/shellResolver.ts +0 -190
- package/src/utils/skillParser.ts +0 -246
- package/src/utils/stringUtils.ts +0 -156
- package/src/utils/subagentParser.ts +0 -279
- package/src/utils/taskReminder.ts +0 -96
- package/src/utils/tokenCalculation.ts +0 -43
- package/src/utils/tokenEstimate.ts +0 -34
- package/src/utils/toolResultStorage.ts +0 -117
- package/src/utils/worktreeSession.ts +0 -26
- package/src/utils/worktreeUtils.ts +0 -786
- package/src/workflow/budgetTracker.ts +0 -34
- package/src/workflow/concurrencyLimiter.ts +0 -47
- package/src/workflow/journal.ts +0 -95
- package/src/workflow/progressReporter.ts +0 -141
- package/src/workflow/runState.ts +0 -65
- package/src/workflow/scriptRuntime.ts +0 -274
- package/src/workflow/structuredOutput.ts +0 -123
- package/src/workflow/types.ts +0 -101
- package/src/workflow/workflowApis.ts +0 -410
- package/vendor/ripgrep/linux-aarch64/rg +0 -0
- package/vendor/ripgrep/linux-x86_64/rg +0 -0
- package/vendor/ripgrep/macos-aarch64/rg +0 -0
- package/vendor/ripgrep/macos-x86_64/rg +0 -0
- package/vendor/ripgrep/windows-aarch64/rg.exe +0 -0
- 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
|
-
|