@tyvm/knowhow 0.0.144 → 0.0.146
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/bin/knowhow.js +22 -20
- package/package.json +1 -1
- package/scripts/migrate-usage-json.js +147 -0
- package/src/agents/base/base.ts +65 -10
- package/src/agents/tools/patch.ts +84 -2
- package/src/agents/tools/textSearch.ts +98 -3
- package/src/chat/CliChatService.ts +16 -0
- package/src/chat/modules/AgentModule.ts +159 -29
- package/src/chat/modules/SessionsModule.ts +13 -5
- package/src/chat/types.ts +15 -0
- package/src/chat.ts +11 -0
- package/src/cli.ts +2 -0
- package/src/clients/anthropic.ts +80 -1
- package/src/clients/contextLimits.ts +40 -0
- package/src/clients/fireworks.ts +3 -0
- package/src/clients/gemini.ts +67 -5
- package/src/clients/http.ts +85 -4
- package/src/clients/index.ts +4 -0
- package/src/clients/openai.ts +186 -35
- package/src/clients/pricing/fireworks.ts +50 -1
- package/src/clients/pricing/index.ts +1 -1
- package/src/clients/pricing/models.ts +3 -1
- package/src/clients/pricing/openai.ts +2 -2
- package/src/clients/pricing/qwen.ts +80 -0
- package/src/clients/qwen.ts +16 -0
- package/src/clients/types.ts +99 -8
- package/src/clients/xai.ts +96 -8
- package/src/commands/agent.ts +29 -1
- package/src/commands/services.ts +49 -3
- package/src/index.ts +9 -0
- package/src/plugins/plugins.ts +16 -0
- package/src/processors/Base64ImageDetector.ts +46 -7
- package/src/processors/GemmaToolProcessor.ts +163 -0
- package/src/processors/TokenCompressor.ts +40 -8
- package/src/processors/ToolResponseCache.ts +17 -8
- package/src/processors/index.ts +1 -0
- package/src/processors/tools/boundedToolResponse.ts +49 -0
- package/src/processors/tools/grepToolResponse.ts +164 -38
- package/src/processors/tools/index.ts +2 -0
- package/src/processors/tools/jqToolResponse.ts +40 -10
- package/src/processors/tools/listStoredToolResponses.ts +51 -10
- package/src/processors/tools/tailToolResponse.ts +85 -17
- package/src/services/AgentSyncFs.ts +37 -0
- package/src/services/LazyToolsService.ts +7 -0
- package/src/services/Mcp.ts +9 -2
- package/src/services/RuntimeReloadService.ts +31 -0
- package/src/services/SessionManager.ts +14 -0
- package/src/services/Tools.ts +14 -0
- package/src/services/TracingService.ts +116 -0
- package/src/services/index.ts +8 -0
- package/src/services/modules/computerUse.ts +216 -0
- package/src/services/modules/index.ts +90 -9
- package/src/services/modules/types.ts +31 -1
- package/src/types.ts +3 -0
- package/src/util/Trace.ts +236 -0
- package/tests/clients/contextLimits.test.ts +4 -0
- package/tests/clients/fireworks.test.ts +65 -0
- package/tests/clients/openai.images.test.ts +105 -0
- package/tests/clients/pricing.test.ts +6 -0
- package/tests/clients/reasoningEffort.test.ts +79 -0
- package/tests/compressor/toolResponseCache.test.ts +2 -1
- package/tests/compressor/usageBase64.json +159 -0
- package/tests/compressor/usageBase64.test.ts +175 -0
- package/tests/manual/clients/completions.test.ts +27 -10
- package/tests/patching/fixtures/transcript-mangles/agent-sync-fs-nested-catch.json +9 -0
- package/tests/patching/fixtures/transcript-mangles/automation-functions-crossed.json +9 -0
- package/tests/patching/fixtures/transcript-mangles/fruit-ninja-confirmed-retirement.json +8 -0
- package/tests/patching/patchFileOutput.test.ts +112 -1
- package/tests/patching/transcriptMangleRegression.test.ts +125 -0
- package/tests/processors/Base64ImageDetector.test.ts +36 -1
- package/tests/processors/TokenCompressor.test.ts +17 -3
- package/tests/processors/tools/boundedRetrievalTools.test.ts +68 -0
- package/tests/processors/tools/grepToolResponse.test.ts +88 -1
- package/tests/unit/plugins/pluginLoading.test.ts +31 -0
- package/tests/unit/services/RuntimeReloadService.test.ts +29 -0
- package/ts_build/package.json +1 -1
- package/ts_build/src/agents/base/base.d.ts +8 -1
- package/ts_build/src/agents/base/base.js +37 -3
- package/ts_build/src/agents/base/base.js.map +1 -1
- package/ts_build/src/agents/tools/patch.d.ts +1 -0
- package/ts_build/src/agents/tools/patch.js +35 -2
- package/ts_build/src/agents/tools/patch.js.map +1 -1
- package/ts_build/src/agents/tools/textSearch.js +56 -2
- package/ts_build/src/agents/tools/textSearch.js.map +1 -1
- package/ts_build/src/chat/CliChatService.js +15 -0
- package/ts_build/src/chat/CliChatService.js.map +1 -1
- package/ts_build/src/chat/modules/AgentModule.d.ts +14 -1
- package/ts_build/src/chat/modules/AgentModule.js +86 -24
- package/ts_build/src/chat/modules/AgentModule.js.map +1 -1
- package/ts_build/src/chat/modules/SessionsModule.js +5 -6
- package/ts_build/src/chat/modules/SessionsModule.js.map +1 -1
- package/ts_build/src/chat/types.d.ts +12 -0
- package/ts_build/src/chat.js +7 -0
- package/ts_build/src/chat.js.map +1 -1
- package/ts_build/src/cli.d.ts +2 -1
- package/ts_build/src/cli.js +1 -0
- package/ts_build/src/cli.js.map +1 -1
- package/ts_build/src/clients/anthropic.d.ts +1 -0
- package/ts_build/src/clients/anthropic.js +40 -1
- package/ts_build/src/clients/anthropic.js.map +1 -1
- package/ts_build/src/clients/contextLimits.js +25 -0
- package/ts_build/src/clients/contextLimits.js.map +1 -1
- package/ts_build/src/clients/fireworks.js +1 -0
- package/ts_build/src/clients/fireworks.js.map +1 -1
- package/ts_build/src/clients/gemini.js +56 -4
- package/ts_build/src/clients/gemini.js.map +1 -1
- package/ts_build/src/clients/http.d.ts +2 -0
- package/ts_build/src/clients/http.js +66 -9
- package/ts_build/src/clients/http.js.map +1 -1
- package/ts_build/src/clients/index.d.ts +1 -0
- package/ts_build/src/clients/index.js +4 -0
- package/ts_build/src/clients/index.js.map +1 -1
- package/ts_build/src/clients/openai.d.ts +6 -1
- package/ts_build/src/clients/openai.js +135 -35
- package/ts_build/src/clients/openai.js.map +1 -1
- package/ts_build/src/clients/pricing/fireworks.d.ts +4 -1
- package/ts_build/src/clients/pricing/fireworks.js +33 -1
- package/ts_build/src/clients/pricing/fireworks.js.map +1 -1
- package/ts_build/src/clients/pricing/index.d.ts +1 -1
- package/ts_build/src/clients/pricing/index.js +6 -2
- package/ts_build/src/clients/pricing/index.js.map +1 -1
- package/ts_build/src/clients/pricing/models.d.ts +2 -1
- package/ts_build/src/clients/pricing/models.js +4 -1
- package/ts_build/src/clients/pricing/models.js.map +1 -1
- package/ts_build/src/clients/pricing/openai.js +2 -2
- package/ts_build/src/clients/pricing/openai.js.map +1 -1
- package/ts_build/src/clients/pricing/qwen.d.ts +5 -0
- package/ts_build/src/clients/pricing/qwen.js +30 -0
- package/ts_build/src/clients/pricing/qwen.js.map +1 -0
- package/ts_build/src/clients/qwen.d.ts +4 -0
- package/ts_build/src/clients/qwen.js +15 -0
- package/ts_build/src/clients/qwen.js.map +1 -0
- package/ts_build/src/clients/types.d.ts +16 -1
- package/ts_build/src/clients/types.js +14 -0
- package/ts_build/src/clients/types.js.map +1 -1
- package/ts_build/src/clients/xai.d.ts +2 -0
- package/ts_build/src/clients/xai.js +79 -13
- package/ts_build/src/clients/xai.js.map +1 -1
- package/ts_build/src/commands/agent.js +15 -1
- package/ts_build/src/commands/agent.js.map +1 -1
- package/ts_build/src/commands/services.d.ts +2 -0
- package/ts_build/src/commands/services.js +45 -3
- package/ts_build/src/commands/services.js.map +1 -1
- package/ts_build/src/index.d.ts +2 -0
- package/ts_build/src/index.js +1 -0
- package/ts_build/src/index.js.map +1 -1
- package/ts_build/src/plugins/plugins.d.ts +1 -0
- package/ts_build/src/plugins/plugins.js +14 -0
- package/ts_build/src/plugins/plugins.js.map +1 -1
- package/ts_build/src/processors/Base64ImageDetector.js +22 -3
- package/ts_build/src/processors/Base64ImageDetector.js.map +1 -1
- package/ts_build/src/processors/GemmaToolProcessor.d.ts +14 -0
- package/ts_build/src/processors/GemmaToolProcessor.js +97 -0
- package/ts_build/src/processors/GemmaToolProcessor.js.map +1 -0
- package/ts_build/src/processors/TokenCompressor.js +15 -6
- package/ts_build/src/processors/TokenCompressor.js.map +1 -1
- package/ts_build/src/processors/ToolResponseCache.d.ts +3 -3
- package/ts_build/src/processors/ToolResponseCache.js +8 -8
- package/ts_build/src/processors/ToolResponseCache.js.map +1 -1
- package/ts_build/src/processors/index.d.ts +1 -0
- package/ts_build/src/processors/index.js +3 -1
- package/ts_build/src/processors/index.js.map +1 -1
- package/ts_build/src/processors/tools/boundedToolResponse.d.ts +7 -0
- package/ts_build/src/processors/tools/boundedToolResponse.js +27 -0
- package/ts_build/src/processors/tools/boundedToolResponse.js.map +1 -0
- package/ts_build/src/processors/tools/grepToolResponse.d.ts +4 -0
- package/ts_build/src/processors/tools/grepToolResponse.js +101 -25
- package/ts_build/src/processors/tools/grepToolResponse.js.map +1 -1
- package/ts_build/src/processors/tools/index.d.ts +2 -2
- package/ts_build/src/processors/tools/index.js.map +1 -1
- package/ts_build/src/processors/tools/jqToolResponse.d.ts +5 -1
- package/ts_build/src/processors/tools/jqToolResponse.js +26 -9
- package/ts_build/src/processors/tools/jqToolResponse.js.map +1 -1
- package/ts_build/src/processors/tools/listStoredToolResponses.d.ts +7 -1
- package/ts_build/src/processors/tools/listStoredToolResponses.js +39 -9
- package/ts_build/src/processors/tools/listStoredToolResponses.js.map +1 -1
- package/ts_build/src/processors/tools/tailToolResponse.d.ts +5 -0
- package/ts_build/src/processors/tools/tailToolResponse.js +56 -13
- package/ts_build/src/processors/tools/tailToolResponse.js.map +1 -1
- package/ts_build/src/services/AgentSyncFs.js +17 -0
- package/ts_build/src/services/AgentSyncFs.js.map +1 -1
- package/ts_build/src/services/LazyToolsService.d.ts +1 -0
- package/ts_build/src/services/LazyToolsService.js +5 -0
- package/ts_build/src/services/LazyToolsService.js.map +1 -1
- package/ts_build/src/services/Mcp.js +8 -2
- package/ts_build/src/services/Mcp.js.map +1 -1
- package/ts_build/src/services/RuntimeReloadService.d.ts +12 -0
- package/ts_build/src/services/RuntimeReloadService.js +26 -0
- package/ts_build/src/services/RuntimeReloadService.js.map +1 -0
- package/ts_build/src/services/SessionManager.js +12 -0
- package/ts_build/src/services/SessionManager.js.map +1 -1
- package/ts_build/src/services/Tools.d.ts +3 -0
- package/ts_build/src/services/Tools.js +5 -0
- package/ts_build/src/services/Tools.js.map +1 -1
- package/ts_build/src/services/TracingService.d.ts +16 -0
- package/ts_build/src/services/TracingService.js +35 -0
- package/ts_build/src/services/TracingService.js.map +1 -0
- package/ts_build/src/services/index.d.ts +6 -0
- package/ts_build/src/services/index.js +7 -1
- package/ts_build/src/services/index.js.map +1 -1
- package/ts_build/src/services/modules/computerUse.d.ts +163 -0
- package/ts_build/src/services/modules/computerUse.js +3 -0
- package/ts_build/src/services/modules/computerUse.js.map +1 -0
- package/ts_build/src/services/modules/index.d.ts +6 -0
- package/ts_build/src/services/modules/index.js +49 -6
- package/ts_build/src/services/modules/index.js.map +1 -1
- package/ts_build/src/services/modules/types.d.ts +7 -0
- package/ts_build/src/types.d.ts +3 -0
- package/ts_build/src/types.js.map +1 -1
- package/ts_build/src/util/Trace.d.ts +13 -0
- package/ts_build/src/util/Trace.js +107 -0
- package/ts_build/src/util/Trace.js.map +1 -0
- package/ts_build/src/workers/tools/index.d.ts +1 -1
- package/ts_build/tests/clients/contextLimits.test.js +2 -0
- package/ts_build/tests/clients/contextLimits.test.js.map +1 -1
- package/ts_build/tests/clients/fireworks.test.d.ts +1 -0
- package/ts_build/tests/clients/fireworks.test.js +65 -0
- package/ts_build/tests/clients/fireworks.test.js.map +1 -0
- package/ts_build/tests/clients/openai.images.test.d.ts +1 -0
- package/ts_build/tests/clients/openai.images.test.js +94 -0
- package/ts_build/tests/clients/openai.images.test.js.map +1 -0
- package/ts_build/tests/clients/pricing.test.js +4 -0
- package/ts_build/tests/clients/pricing.test.js.map +1 -1
- package/ts_build/tests/clients/reasoningEffort.test.d.ts +1 -0
- package/ts_build/tests/clients/reasoningEffort.test.js +68 -0
- package/ts_build/tests/clients/reasoningEffort.test.js.map +1 -0
- package/ts_build/tests/compressor/toolResponseCache.test.js +1 -1
- package/ts_build/tests/compressor/toolResponseCache.test.js.map +1 -1
- package/ts_build/tests/compressor/usageBase64.test.d.ts +1 -0
- package/ts_build/tests/compressor/usageBase64.test.js +109 -0
- package/ts_build/tests/compressor/usageBase64.test.js.map +1 -0
- package/ts_build/tests/manual/clients/completions.test.js +19 -6
- package/ts_build/tests/manual/clients/completions.test.js.map +1 -1
- package/ts_build/tests/patching/patchFileOutput.test.js +82 -0
- package/ts_build/tests/patching/patchFileOutput.test.js.map +1 -1
- package/ts_build/tests/patching/transcriptMangleRegression.test.d.ts +1 -0
- package/ts_build/tests/patching/transcriptMangleRegression.test.js +104 -0
- package/ts_build/tests/patching/transcriptMangleRegression.test.js.map +1 -0
- package/ts_build/tests/processors/Base64ImageDetector.test.js +24 -0
- package/ts_build/tests/processors/Base64ImageDetector.test.js.map +1 -1
- package/ts_build/tests/processors/TokenCompressor.test.js +11 -2
- package/ts_build/tests/processors/TokenCompressor.test.js.map +1 -1
- package/ts_build/tests/processors/tools/boundedRetrievalTools.test.d.ts +1 -0
- package/ts_build/tests/processors/tools/boundedRetrievalTools.test.js +57 -0
- package/ts_build/tests/processors/tools/boundedRetrievalTools.test.js.map +1 -0
- package/ts_build/tests/processors/tools/grepToolResponse.test.js +37 -0
- package/ts_build/tests/processors/tools/grepToolResponse.test.js.map +1 -1
- package/ts_build/tests/unit/plugins/pluginLoading.test.js +59 -0
- package/ts_build/tests/unit/plugins/pluginLoading.test.js.map +1 -1
- package/ts_build/tests/unit/services/RuntimeReloadService.test.d.ts +1 -0
- package/ts_build/tests/unit/services/RuntimeReloadService.test.js +26 -0
- package/ts_build/tests/unit/services/RuntimeReloadService.test.js.map +1 -0
- package/x-home.png +0 -0
package/src/clients/gemini.ts
CHANGED
|
@@ -225,17 +225,44 @@ export class GenericGeminiClient implements GenericClient {
|
|
|
225
225
|
msg.tool_calls &&
|
|
226
226
|
msg.tool_calls.length > 0
|
|
227
227
|
) {
|
|
228
|
+
// Look up any captured thoughtSignatures for this assistant message so
|
|
229
|
+
// we can re-attach them to the matching functionCall part (required by
|
|
230
|
+
// Gemini 3 to preserve reasoning continuity across tool-use turns).
|
|
231
|
+
const details = (msg as any)._reasoning_details;
|
|
232
|
+
const sigByCallId: { [id: string]: string } = {};
|
|
233
|
+
const standaloneThoughtSigs: string[] = [];
|
|
234
|
+
if (details && details.provider === "gemini" && Array.isArray(details.items)) {
|
|
235
|
+
for (const item of details.items) {
|
|
236
|
+
if (item?.kind === "functionCall" && item.callId) {
|
|
237
|
+
sigByCallId[item.callId] = item.thoughtSignature;
|
|
238
|
+
} else if (item?.kind === "thoughtSignature" && item.thoughtSignature) {
|
|
239
|
+
standaloneThoughtSigs.push(item.thoughtSignature);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
228
243
|
for (const toolCall of msg.tool_calls) {
|
|
229
|
-
|
|
244
|
+
const callPart: any = {
|
|
230
245
|
functionCall: {
|
|
231
246
|
name: toolCall.function.name,
|
|
232
247
|
// Google expects arguments as a parsed object, not a string
|
|
233
248
|
args: JSON.parse(toolCall.function.arguments || "{}"),
|
|
234
249
|
},
|
|
235
|
-
}
|
|
250
|
+
};
|
|
251
|
+
if (sigByCallId[toolCall.id]) {
|
|
252
|
+
callPart.thoughtSignature = sigByCallId[toolCall.id];
|
|
253
|
+
}
|
|
254
|
+
parts.push(callPart);
|
|
236
255
|
// Store the tool call to potentially link with a future tool response message
|
|
237
256
|
assistantToolCalls[toolCall.id] = toolCall;
|
|
238
257
|
}
|
|
258
|
+
// Re-attach any standalone thought-part signatures onto the leading
|
|
259
|
+
// text part(s) so reasoning-only thoughts are also round-tripped.
|
|
260
|
+
for (let s = 0; s < standaloneThoughtSigs.length; s++) {
|
|
261
|
+
const p: any = parts[s];
|
|
262
|
+
if (p && "text" in p && !p.thoughtSignature) {
|
|
263
|
+
p.thoughtSignature = standaloneThoughtSigs[s];
|
|
264
|
+
}
|
|
265
|
+
}
|
|
239
266
|
}
|
|
240
267
|
|
|
241
268
|
if (parts.length > 0) {
|
|
@@ -407,9 +434,13 @@ export class GenericGeminiClient implements GenericClient {
|
|
|
407
434
|
// Gemini 3.x — use thinkingLevel
|
|
408
435
|
if (GoogleThinkingLevelModels.includes(model)) {
|
|
409
436
|
const levelMap: Record<string, string> = {
|
|
437
|
+
none: "minimal",
|
|
438
|
+
minimal: "minimal",
|
|
410
439
|
low: "low",
|
|
411
440
|
medium: "medium",
|
|
412
441
|
high: "high",
|
|
442
|
+
xhigh: "high",
|
|
443
|
+
max: "high",
|
|
413
444
|
};
|
|
414
445
|
return {
|
|
415
446
|
thinkingLevel: levelMap[effort] ?? "low",
|
|
@@ -420,9 +451,13 @@ export class GenericGeminiClient implements GenericClient {
|
|
|
420
451
|
if (GoogleThinkingBudgetModels.includes(model)) {
|
|
421
452
|
// Map effort to token budget
|
|
422
453
|
const budgetMap: Record<string, number> = {
|
|
454
|
+
none: 0,
|
|
455
|
+
minimal: 512,
|
|
423
456
|
low: 1024,
|
|
424
457
|
medium: 8192,
|
|
425
458
|
high: -1, // dynamic
|
|
459
|
+
xhigh: -1,
|
|
460
|
+
max: -1,
|
|
426
461
|
};
|
|
427
462
|
return {
|
|
428
463
|
thinkingBudget: budgetMap[effort] ?? 1024,
|
|
@@ -499,24 +534,51 @@ export class GenericGeminiClient implements GenericClient {
|
|
|
499
534
|
return { message };
|
|
500
535
|
}
|
|
501
536
|
|
|
537
|
+
// Capture Gemini thoughtSignatures so they can be round-tripped on the
|
|
538
|
+
// next turn. Gemini 3 REQUIRES these to be echoed back on the matching
|
|
539
|
+
// functionCall part or it returns a 400 "Thought signature is not
|
|
540
|
+
// valid" on the 2nd+ tool-calling turn. We key them by tool-call id
|
|
541
|
+
// (and also keep any standalone thought-part signatures in order).
|
|
542
|
+
const reasoningItems: any[] = [];
|
|
543
|
+
|
|
502
544
|
candidate?.content?.parts?.forEach((part) => {
|
|
503
545
|
if ("text" in part && typeof part.text === "string") {
|
|
504
546
|
textContent += part.text; // Concatenate text parts
|
|
547
|
+
if ((part as any).thoughtSignature) {
|
|
548
|
+
reasoningItems.push({
|
|
549
|
+
kind: "thoughtSignature",
|
|
550
|
+
thoughtSignature: (part as any).thoughtSignature,
|
|
551
|
+
});
|
|
552
|
+
}
|
|
505
553
|
} else if ("functionCall" in part && part.functionCall) {
|
|
554
|
+
const callId =
|
|
555
|
+
part.functionCall.id ||
|
|
556
|
+
`fc_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
506
557
|
message.tool_calls.push({
|
|
507
|
-
id:
|
|
508
|
-
part.functionCall.id ||
|
|
509
|
-
`fc_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
|
|
558
|
+
id: callId,
|
|
510
559
|
type: "function",
|
|
511
560
|
function: {
|
|
512
561
|
name: part.functionCall.name,
|
|
513
562
|
arguments: JSON.stringify(part.functionCall.args || {}),
|
|
514
563
|
},
|
|
515
564
|
});
|
|
565
|
+
if ((part as any).thoughtSignature) {
|
|
566
|
+
reasoningItems.push({
|
|
567
|
+
kind: "functionCall",
|
|
568
|
+
callId,
|
|
569
|
+
thoughtSignature: (part as any).thoughtSignature,
|
|
570
|
+
});
|
|
571
|
+
}
|
|
516
572
|
}
|
|
517
573
|
});
|
|
518
574
|
|
|
519
575
|
message.content = textContent || null;
|
|
576
|
+
if (reasoningItems.length > 0) {
|
|
577
|
+
(message as any)._reasoning_details = {
|
|
578
|
+
provider: "gemini",
|
|
579
|
+
items: reasoningItems,
|
|
580
|
+
};
|
|
581
|
+
}
|
|
520
582
|
|
|
521
583
|
return { message };
|
|
522
584
|
}) || []; // Handle case with no candidates
|
package/src/clients/http.ts
CHANGED
|
@@ -92,6 +92,33 @@ export class HttpClient implements GenericClient {
|
|
|
92
92
|
this.pricingMap = pricingMap;
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
+
/**
|
|
96
|
+
* A stable provider id used to tag/read the provider-agnostic
|
|
97
|
+
* `_reasoning_details` slot. Reasoning items are opaque and only meaningful
|
|
98
|
+
* to the provider that produced them, so we key on the client's baseUrl.
|
|
99
|
+
*/
|
|
100
|
+
protected reasoningProviderId(): string {
|
|
101
|
+
return `http:${this.baseUrl}`;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Resolve the provider-native reasoning items (Responses API `reasoning`
|
|
106
|
+
* items with encrypted_content) to re-inject for an assistant message,
|
|
107
|
+
* reading the provider-agnostic `_reasoning_details` slot only when it was
|
|
108
|
+
* produced by this same client.
|
|
109
|
+
*/
|
|
110
|
+
protected getReasoningItems(msg: any): any[] {
|
|
111
|
+
const details = msg?._reasoning_details;
|
|
112
|
+
if (
|
|
113
|
+
details &&
|
|
114
|
+
details.provider === this.reasoningProviderId() &&
|
|
115
|
+
Array.isArray(details.items)
|
|
116
|
+
) {
|
|
117
|
+
return details.items;
|
|
118
|
+
}
|
|
119
|
+
return [];
|
|
120
|
+
}
|
|
121
|
+
|
|
95
122
|
/**
|
|
96
123
|
* Returns the pricing entry for a specific model, or all pricing entries if no model is given.
|
|
97
124
|
* Returns undefined for a specific model if no pricing is known.
|
|
@@ -299,22 +326,44 @@ export class HttpClient implements GenericClient {
|
|
|
299
326
|
.trim() || undefined;
|
|
300
327
|
|
|
301
328
|
// Convert messages to Responses API input format
|
|
329
|
+
// Responses API hard limit: 10,485,760 chars per function_call_output.output
|
|
330
|
+
const MAX_TOOL_OUTPUT = 10_485_000;
|
|
331
|
+
const truncateToolOutput = (s: string) =>
|
|
332
|
+
s.length > MAX_TOOL_OUTPUT
|
|
333
|
+
? s.slice(0, MAX_TOOL_OUTPUT) + "\n\n[TRUNCATED: output exceeded 10MB API limit]"
|
|
334
|
+
: s;
|
|
302
335
|
const input: any[] = nonSystemMessages.map((msg) => {
|
|
303
336
|
if (msg.role === "tool") {
|
|
304
337
|
return {
|
|
305
338
|
type: "function_call_output",
|
|
306
339
|
call_id: msg.tool_call_id,
|
|
307
|
-
output: typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content),
|
|
340
|
+
output: truncateToolOutput(typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content)),
|
|
308
341
|
};
|
|
309
342
|
}
|
|
310
343
|
if (msg.role === "assistant" && msg.tool_calls?.length) {
|
|
311
|
-
|
|
344
|
+
// Re-inject any captured reasoning items (encrypted_content) BEFORE
|
|
345
|
+
// the function_call items so the model preserves its chain-of-thought
|
|
346
|
+
// across tool-use turns instead of re-deriving it each turn.
|
|
347
|
+
const reasoningItems = this.getReasoningItems(msg);
|
|
348
|
+
return [
|
|
349
|
+
...reasoningItems,
|
|
350
|
+
...(msg.tool_calls as any[]).map((tc: any) => ({
|
|
312
351
|
type: "function_call",
|
|
313
352
|
id: tc.id.startsWith("fc") ? tc.id : `fc_${tc.id}`,
|
|
314
353
|
call_id: tc.id,
|
|
315
354
|
name: tc.function.name,
|
|
316
355
|
arguments: tc.function.arguments,
|
|
317
|
-
|
|
356
|
+
})),
|
|
357
|
+
];
|
|
358
|
+
}
|
|
359
|
+
if (msg.role === "assistant" && this.getReasoningItems(msg).length) {
|
|
360
|
+
return [
|
|
361
|
+
...this.getReasoningItems(msg),
|
|
362
|
+
{
|
|
363
|
+
role: msg.role,
|
|
364
|
+
content: typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content),
|
|
365
|
+
},
|
|
366
|
+
];
|
|
318
367
|
}
|
|
319
368
|
return {
|
|
320
369
|
role: msg.role,
|
|
@@ -336,7 +385,18 @@ export class HttpClient implements GenericClient {
|
|
|
336
385
|
...(instructions && { instructions }),
|
|
337
386
|
...(options.max_tokens && { max_output_tokens: options.max_tokens }),
|
|
338
387
|
...(tools?.length && { tools, tool_choice: "auto" }),
|
|
339
|
-
store,
|
|
388
|
+
// Reasoning persistence: store server-side when requested, otherwise
|
|
389
|
+
// ask the provider to return encrypted reasoning content so we can
|
|
390
|
+
// thread the reasoning items back into the next request ourselves.
|
|
391
|
+
store: options.store === true ? true : store,
|
|
392
|
+
...(options.previous_response_id && {
|
|
393
|
+
previous_response_id: options.previous_response_id,
|
|
394
|
+
}),
|
|
395
|
+
...(options.store !== true &&
|
|
396
|
+
options.reasoning_effort &&
|
|
397
|
+
options.reasoning_effort !== "none" && {
|
|
398
|
+
include: ["reasoning.encrypted_content"],
|
|
399
|
+
}),
|
|
340
400
|
...this.extra_body,
|
|
341
401
|
};
|
|
342
402
|
|
|
@@ -368,6 +428,10 @@ export class HttpClient implements GenericClient {
|
|
|
368
428
|
// Collect text content and tool calls from output items
|
|
369
429
|
let textContent: string | null = null;
|
|
370
430
|
const toolCalls: any[] = [];
|
|
431
|
+
// Reasoning items (encrypted_content) to thread back into the next turn,
|
|
432
|
+
// plus a human-readable summary of the model's thinking (if present).
|
|
433
|
+
const reasoningItems: any[] = [];
|
|
434
|
+
let reasoningSummary: string | null = null;
|
|
371
435
|
|
|
372
436
|
for (const item of data.output ?? []) {
|
|
373
437
|
if (item.type === "message") {
|
|
@@ -376,6 +440,15 @@ export class HttpClient implements GenericClient {
|
|
|
376
440
|
textContent = (textContent ?? "") + part.text;
|
|
377
441
|
}
|
|
378
442
|
}
|
|
443
|
+
} else if (item.type === "reasoning") {
|
|
444
|
+
reasoningItems.push(item);
|
|
445
|
+
const summaryParts = item.summary ?? [];
|
|
446
|
+
for (const part of summaryParts) {
|
|
447
|
+
const text = typeof part === "string" ? part : part?.text;
|
|
448
|
+
if (text) {
|
|
449
|
+
reasoningSummary = (reasoningSummary ?? "") + text;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
379
452
|
} else if (item.type === "function_call") {
|
|
380
453
|
toolCalls.push({
|
|
381
454
|
id: item.call_id,
|
|
@@ -392,12 +465,20 @@ export class HttpClient implements GenericClient {
|
|
|
392
465
|
role: "assistant",
|
|
393
466
|
content: textContent,
|
|
394
467
|
...(toolCalls.length > 0 && { tool_calls: toolCalls }),
|
|
468
|
+
...(reasoningSummary && { reasoning_summary: reasoningSummary }),
|
|
469
|
+
...(reasoningItems.length > 0 && {
|
|
470
|
+
_reasoning_details: {
|
|
471
|
+
provider: this.reasoningProviderId(),
|
|
472
|
+
items: reasoningItems,
|
|
473
|
+
},
|
|
474
|
+
}),
|
|
395
475
|
},
|
|
396
476
|
},
|
|
397
477
|
],
|
|
398
478
|
model: data.model ?? options.model,
|
|
399
479
|
usage,
|
|
400
480
|
usd_cost: data.usd_cost ?? this.calculateCost(options.model, usage),
|
|
481
|
+
response_id: data.id,
|
|
401
482
|
};
|
|
402
483
|
});
|
|
403
484
|
}
|
package/src/clients/index.ts
CHANGED
|
@@ -58,6 +58,7 @@ import { GitHubCopilotClient } from "./copilot";
|
|
|
58
58
|
import { GenericLlamaClient } from "./llama";
|
|
59
59
|
import { GenericFireworksClient } from "./fireworks";
|
|
60
60
|
import { GenericMetaClient } from "./meta";
|
|
61
|
+
import { GenericQwenClient } from "./qwen";
|
|
61
62
|
export {
|
|
62
63
|
OpenAiTextPricing,
|
|
63
64
|
AnthropicTextPricing,
|
|
@@ -103,6 +104,7 @@ const BUILT_IN_PROVIDER_REGISTRY: Record<string, ProviderRegistryEntry> = {
|
|
|
103
104
|
llama: { clientClass: GenericLlamaClient },
|
|
104
105
|
fireworks: { clientClass: GenericFireworksClient },
|
|
105
106
|
meta: { clientClass: GenericMetaClient },
|
|
107
|
+
qwen: { clientClass: GenericQwenClient },
|
|
106
108
|
knowhow: {
|
|
107
109
|
createClient: (entry: ModelProvider) => {
|
|
108
110
|
const jwt = loadKnowhowJwt();
|
|
@@ -134,6 +136,7 @@ const DEFAULT_PROVIDERS: ModelProvider[] = [
|
|
|
134
136
|
{ provider: "llama", envKey: "LLAMA_API_KEY" },
|
|
135
137
|
{ provider: "fireworks", envKey: "FIREWORKS_API_KEY" },
|
|
136
138
|
{ provider: "meta", envKey: "META_API_KEY" },
|
|
139
|
+
{ provider: "qwen", envKey: "QWEN_CLOUD_API_KEY" },
|
|
137
140
|
];
|
|
138
141
|
|
|
139
142
|
export class AIClient {
|
|
@@ -1075,3 +1078,4 @@ export * from "./llama";
|
|
|
1075
1078
|
export * from "./copilot";
|
|
1076
1079
|
export * from "./fireworks";
|
|
1077
1080
|
export * from "./meta";
|
|
1081
|
+
export * from "./qwen";
|
package/src/clients/openai.ts
CHANGED
|
@@ -22,13 +22,17 @@ import {
|
|
|
22
22
|
FileUploadResponse,
|
|
23
23
|
FileDownloadOptions,
|
|
24
24
|
FileDownloadResponse,
|
|
25
|
+
Message,
|
|
25
26
|
} from "./types";
|
|
26
27
|
import {
|
|
27
28
|
ChatCompletionMessageParam,
|
|
28
29
|
ChatCompletionToolMessageParam,
|
|
29
30
|
ChatCompletionMessageToolCall,
|
|
30
31
|
} from "openai/resources/chat";
|
|
31
|
-
import {
|
|
32
|
+
import {
|
|
33
|
+
ResponseFunctionToolCall,
|
|
34
|
+
ResponseInputMessageContentList,
|
|
35
|
+
} from "openai/resources/responses/responses";
|
|
32
36
|
|
|
33
37
|
import {
|
|
34
38
|
EmbeddingModels,
|
|
@@ -108,9 +112,10 @@ export class GenericOpenAiClient implements GenericClient {
|
|
|
108
112
|
|
|
109
113
|
resolveReasoningEffort(options: CompletionOptions): "low" | "medium" | "high" {
|
|
110
114
|
const effort = options.reasoning_effort ?? this.detectReasoningEffort(options.messages);
|
|
111
|
-
//
|
|
112
|
-
//
|
|
113
|
-
if (effort === "none") return "low";
|
|
115
|
+
// Chat Completions only accepts low/medium/high. Preserve newer values on
|
|
116
|
+
// the Responses API, but map them to the nearest legacy level here.
|
|
117
|
+
if (effort === "none" || effort === "minimal") return "low";
|
|
118
|
+
if (effort === "xhigh" || effort === "max") return "high";
|
|
114
119
|
return effort;
|
|
115
120
|
}
|
|
116
121
|
|
|
@@ -121,7 +126,9 @@ export class GenericOpenAiClient implements GenericClient {
|
|
|
121
126
|
*/
|
|
122
127
|
resolveReasoningEffortForModel(options: CompletionOptions): string {
|
|
123
128
|
const raw = options.reasoning_effort ?? this.detectReasoningEffort(options.messages);
|
|
124
|
-
|
|
129
|
+
// Anthropic calls its highest level `max`; OpenAI calls it `xhigh`.
|
|
130
|
+
// Accept both provider-neutral CLI spellings.
|
|
131
|
+
const requested = raw === "max" ? "xhigh" : raw;
|
|
125
132
|
const pricing = OpenAiTextPricing[options.model];
|
|
126
133
|
const supportedLevels = pricing?.reasoningLevels;
|
|
127
134
|
if (!supportedLevels || supportedLevels.length === 0) {
|
|
@@ -140,6 +147,75 @@ export class GenericOpenAiClient implements GenericClient {
|
|
|
140
147
|
return options.reasoning_effort === "none";
|
|
141
148
|
}
|
|
142
149
|
|
|
150
|
+
/**
|
|
151
|
+
* OpenAI only permits image parts on user messages. A Chat Completions
|
|
152
|
+
* `tool` message may contain text parts, but not `image_url` parts. Keep the
|
|
153
|
+
* required tool result and attach images in a following user message.
|
|
154
|
+
*/
|
|
155
|
+
toChatCompletionMessages(
|
|
156
|
+
messages: CompletionOptions["messages"]
|
|
157
|
+
): ChatCompletionMessageParam[] {
|
|
158
|
+
return messages.flatMap((msg): ChatCompletionMessageParam[] => {
|
|
159
|
+
if (msg.role !== "tool") return [msg as ChatCompletionMessageParam];
|
|
160
|
+
|
|
161
|
+
const parts = Array.isArray(msg.content) ? msg.content : [];
|
|
162
|
+
const unsupportedPart = parts.find(
|
|
163
|
+
(part) => part.type !== "text" && part.type !== "image_url"
|
|
164
|
+
);
|
|
165
|
+
if (unsupportedPart) {
|
|
166
|
+
throw new Error(
|
|
167
|
+
`OpenAI Chat Completions cannot attach ${unsupportedPart.type} content to a tool result.`
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
const imageParts = parts.filter((part) => part.type === "image_url");
|
|
171
|
+
const textParts = parts.filter((part) => part.type === "text");
|
|
172
|
+
const toolMessage: ChatCompletionToolMessageParam = {
|
|
173
|
+
role: "tool",
|
|
174
|
+
tool_call_id: msg.tool_call_id!,
|
|
175
|
+
content: Array.isArray(msg.content)
|
|
176
|
+
? textParts.length
|
|
177
|
+
? textParts
|
|
178
|
+
: [{
|
|
179
|
+
type: "text",
|
|
180
|
+
text: "Image output attached in the following user message.",
|
|
181
|
+
}]
|
|
182
|
+
: msg.content || "",
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
if (!imageParts.length) return [toolMessage];
|
|
186
|
+
return [
|
|
187
|
+
toolMessage,
|
|
188
|
+
{ role: "user", content: imageParts } as ChatCompletionMessageParam,
|
|
189
|
+
];
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/** Convert the CLI's Chat Completions-style parts to Responses API parts. */
|
|
194
|
+
toResponseContent(
|
|
195
|
+
content: Message["content"]
|
|
196
|
+
): string | ResponseInputMessageContentList {
|
|
197
|
+
if (!Array.isArray(content)) return content || "";
|
|
198
|
+
const result: ResponseInputMessageContentList = [];
|
|
199
|
+
for (const part of content) {
|
|
200
|
+
if (part.type === "text") {
|
|
201
|
+
result.push({ type: "input_text", text: part.text });
|
|
202
|
+
}
|
|
203
|
+
else if (part.type === "image_url") {
|
|
204
|
+
result.push({
|
|
205
|
+
type: "input_image",
|
|
206
|
+
image_url: part.image_url.url,
|
|
207
|
+
detail: part.image_url.detail || "auto",
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
else {
|
|
211
|
+
throw new Error(
|
|
212
|
+
`OpenAI Responses API conversion does not support ${part.type} content.`
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
return result;
|
|
217
|
+
}
|
|
218
|
+
|
|
143
219
|
async createChatCompletion(
|
|
144
220
|
options: CompletionOptions
|
|
145
221
|
): Promise<CompletionResponse> {
|
|
@@ -148,17 +224,7 @@ export class GenericOpenAiClient implements GenericClient {
|
|
|
148
224
|
return this.createChatResponse(options);
|
|
149
225
|
}
|
|
150
226
|
|
|
151
|
-
const openaiMessages = options.messages
|
|
152
|
-
if (msg.role === "tool") {
|
|
153
|
-
return {
|
|
154
|
-
...msg,
|
|
155
|
-
content: msg.content || "",
|
|
156
|
-
role: "tool",
|
|
157
|
-
tool_call_id: msg.tool_call_id,
|
|
158
|
-
} as ChatCompletionToolMessageParam;
|
|
159
|
-
}
|
|
160
|
-
return msg as ChatCompletionMessageParam;
|
|
161
|
-
});
|
|
227
|
+
const openaiMessages = this.toChatCompletionMessages(options.messages);
|
|
162
228
|
|
|
163
229
|
const response = await this.client.chat.completions.create({
|
|
164
230
|
model: options.model,
|
|
@@ -222,17 +288,7 @@ export class GenericOpenAiClient implements GenericClient {
|
|
|
222
288
|
return;
|
|
223
289
|
}
|
|
224
290
|
|
|
225
|
-
const openaiMessages = options.messages
|
|
226
|
-
if (msg.role === "tool") {
|
|
227
|
-
return {
|
|
228
|
-
...msg,
|
|
229
|
-
content: msg.content || "",
|
|
230
|
-
role: "tool",
|
|
231
|
-
tool_call_id: msg.tool_call_id,
|
|
232
|
-
} as ChatCompletionToolMessageParam;
|
|
233
|
-
}
|
|
234
|
-
return msg as ChatCompletionMessageParam;
|
|
235
|
-
});
|
|
291
|
+
const openaiMessages = this.toChatCompletionMessages(options.messages);
|
|
236
292
|
|
|
237
293
|
const stream = await this.client.chat.completions.create({
|
|
238
294
|
model: options.model,
|
|
@@ -307,6 +363,20 @@ export class GenericOpenAiClient implements GenericClient {
|
|
|
307
363
|
}
|
|
308
364
|
}
|
|
309
365
|
|
|
366
|
+
/**
|
|
367
|
+
* Resolve the OpenAI-native reasoning items to re-inject for an assistant
|
|
368
|
+
* message. Reads the provider-agnostic `_reasoning_details` slot (only when
|
|
369
|
+
* it was produced by this provider) and falls back to the deprecated
|
|
370
|
+
* `_reasoning_items` for backward compatibility.
|
|
371
|
+
*/
|
|
372
|
+
private getReasoningItems(msg: any): any[] {
|
|
373
|
+
const details = (msg as any)._reasoning_details;
|
|
374
|
+
if (details && details.provider === "openai" && Array.isArray(details.items)) {
|
|
375
|
+
return details.items;
|
|
376
|
+
}
|
|
377
|
+
return (msg as any)._reasoning_items ?? [];
|
|
378
|
+
}
|
|
379
|
+
|
|
310
380
|
async createChatResponse(
|
|
311
381
|
options: CompletionOptions
|
|
312
382
|
): Promise<CompletionResponse> {
|
|
@@ -324,30 +394,68 @@ export class GenericOpenAiClient implements GenericClient {
|
|
|
324
394
|
|
|
325
395
|
// Convert chat messages to Responses API input items
|
|
326
396
|
// The Responses API accepts: user/assistant/system messages and function_call_output items
|
|
397
|
+
// OpenAI Responses API hard limit: 10,485,760 chars per function_call_output.output
|
|
398
|
+
const MAX_TOOL_OUTPUT = 10_485_000;
|
|
399
|
+
const truncateToolOutput = (s: string) =>
|
|
400
|
+
s.length > MAX_TOOL_OUTPUT
|
|
401
|
+
? s.slice(0, MAX_TOOL_OUTPUT) + "\n\n[TRUNCATED: output exceeded 10MB API limit]"
|
|
402
|
+
: s;
|
|
327
403
|
const input: any[] = nonSystemMessages.map((msg) => {
|
|
328
404
|
if (msg.role === "tool") {
|
|
329
|
-
//
|
|
330
|
-
|
|
405
|
+
// OpenAI function outputs cannot directly contain image content. Send
|
|
406
|
+
// the required function output, then attach images as a user message.
|
|
407
|
+
const parts = Array.isArray(msg.content) ? msg.content : [];
|
|
408
|
+
const imageParts = parts.filter((part) => part.type === "image_url");
|
|
409
|
+
const text = parts
|
|
410
|
+
.filter((part) => part.type === "text")
|
|
411
|
+
.map((part: any) => part.text)
|
|
412
|
+
.join("\n");
|
|
413
|
+
const toolOutput = {
|
|
331
414
|
type: "function_call_output",
|
|
332
415
|
call_id: msg.tool_call_id,
|
|
333
|
-
output: typeof msg.content === "string"
|
|
416
|
+
output: truncateToolOutput(typeof msg.content === "string"
|
|
417
|
+
? msg.content
|
|
418
|
+
: text || "Image output attached in the following user message."),
|
|
334
419
|
};
|
|
420
|
+
if (!imageParts.length) return toolOutput;
|
|
421
|
+
return [
|
|
422
|
+
toolOutput,
|
|
423
|
+
{ role: "user", content: this.toResponseContent(imageParts) },
|
|
424
|
+
];
|
|
335
425
|
}
|
|
336
426
|
if (msg.role === "assistant" && msg.tool_calls?.length) {
|
|
337
|
-
// assistant message with tool calls → function_call items
|
|
338
|
-
|
|
427
|
+
// assistant message with tool calls → function_call items.
|
|
428
|
+
// If the prior turn captured reasoning items (encrypted_content path),
|
|
429
|
+
// re-inject them *before* the function_call items so the model keeps its
|
|
430
|
+
// chain-of-thought instead of re-deriving it from scratch.
|
|
431
|
+
const reasoningItems = this.getReasoningItems(msg);
|
|
432
|
+
return [
|
|
433
|
+
...reasoningItems,
|
|
434
|
+
...msg.tool_calls.map((tc) => ({
|
|
339
435
|
type: "function_call",
|
|
340
436
|
// id must start with 'fc_'; call_id is the original call_ ID used for function_call_output matching
|
|
341
437
|
id: tc.id.startsWith("fc") ? tc.id : `fc_${tc.id}`,
|
|
342
438
|
call_id: tc.id,
|
|
343
439
|
name: tc.function.name,
|
|
344
440
|
arguments: tc.function.arguments,
|
|
345
|
-
|
|
441
|
+
})),
|
|
442
|
+
];
|
|
443
|
+
}
|
|
444
|
+
if (msg.role === "assistant" && this.getReasoningItems(msg).length) {
|
|
445
|
+
// assistant message (no tool calls) that carried reasoning items →
|
|
446
|
+
// re-inject reasoning items, then the visible message content.
|
|
447
|
+
return [
|
|
448
|
+
...this.getReasoningItems(msg),
|
|
449
|
+
{
|
|
450
|
+
role: msg.role,
|
|
451
|
+
content: this.toResponseContent(msg.content),
|
|
452
|
+
},
|
|
453
|
+
];
|
|
346
454
|
}
|
|
347
455
|
// Regular user/assistant message
|
|
348
456
|
return {
|
|
349
457
|
role: msg.role,
|
|
350
|
-
content:
|
|
458
|
+
content: this.toResponseContent(msg.content),
|
|
351
459
|
};
|
|
352
460
|
}).flat();
|
|
353
461
|
|
|
@@ -360,10 +468,29 @@ export class GenericOpenAiClient implements GenericClient {
|
|
|
360
468
|
strict: false,
|
|
361
469
|
}));
|
|
362
470
|
|
|
471
|
+
// Reasoning persistence:
|
|
472
|
+
// - store === true → OpenAI persists the response server-side; the next
|
|
473
|
+
// turn passes previous_response_id to continue the reasoning chain.
|
|
474
|
+
// - store !== true → stateless; ask OpenAI to return encrypted reasoning
|
|
475
|
+
// content so we can thread it back into the next request's input ourselves.
|
|
476
|
+
const store = options.store === true;
|
|
477
|
+
const wantsReasoning =
|
|
478
|
+
OpenAiReasoningModels.includes(options.model) &&
|
|
479
|
+
!this.isReasoningDisabled(options);
|
|
480
|
+
|
|
363
481
|
const response = await this.client.responses.create({
|
|
364
482
|
model: options.model as any,
|
|
365
483
|
input,
|
|
366
484
|
...(instructions && { instructions }),
|
|
485
|
+
...(store && { store: true }),
|
|
486
|
+
...(!store && { store: false }),
|
|
487
|
+
...(options.previous_response_id && {
|
|
488
|
+
previous_response_id: options.previous_response_id,
|
|
489
|
+
}),
|
|
490
|
+
// When stateless, request encrypted reasoning so we can re-inject it next turn.
|
|
491
|
+
...(!store && wantsReasoning && {
|
|
492
|
+
include: ["reasoning.encrypted_content"],
|
|
493
|
+
}),
|
|
367
494
|
// Don't limit max_output_tokens for Responses API - codex truncates tool call arguments when limited
|
|
368
495
|
...(OpenAiReasoningModels.includes(options.model) && !this.isReasoningDisabled(options) && {
|
|
369
496
|
max_output_tokens: Math.max(options.max_tokens || 0, 16000),
|
|
@@ -376,7 +503,6 @@ export class GenericOpenAiClient implements GenericClient {
|
|
|
376
503
|
tools,
|
|
377
504
|
tool_choice: "auto",
|
|
378
505
|
}),
|
|
379
|
-
store: false,
|
|
380
506
|
} as any);
|
|
381
507
|
|
|
382
508
|
// Map Responses API usage to Chat Completions usage format
|
|
@@ -405,6 +531,10 @@ export class GenericOpenAiClient implements GenericClient {
|
|
|
405
531
|
// Collect text content and tool calls from the output items
|
|
406
532
|
let textContent: string | null = null;
|
|
407
533
|
const toolCalls: ChatCompletionMessageToolCall[] = [];
|
|
534
|
+
// Reasoning items (with encrypted_content) to thread back into the next turn,
|
|
535
|
+
// and a human-readable summary of the model's thinking (when requested).
|
|
536
|
+
const reasoningItems: any[] = [];
|
|
537
|
+
let reasoningSummary: string | null = null;
|
|
408
538
|
|
|
409
539
|
for (const item of response.output) {
|
|
410
540
|
if (item.type === "message") {
|
|
@@ -415,6 +545,18 @@ export class GenericOpenAiClient implements GenericClient {
|
|
|
415
545
|
textContent = (textContent ?? "") + part.text;
|
|
416
546
|
}
|
|
417
547
|
}
|
|
548
|
+
} else if (item.type === "reasoning") {
|
|
549
|
+
// ResponseReasoningItem — keep the raw item so we can re-inject it
|
|
550
|
+
// (it carries encrypted_content when include was requested), and pull
|
|
551
|
+
// out any human-readable summary text.
|
|
552
|
+
reasoningItems.push(item);
|
|
553
|
+
const summaryParts = (item as any).summary ?? [];
|
|
554
|
+
for (const part of summaryParts) {
|
|
555
|
+
const text = typeof part === "string" ? part : part?.text;
|
|
556
|
+
if (text) {
|
|
557
|
+
reasoningSummary = (reasoningSummary ?? "") + text;
|
|
558
|
+
}
|
|
559
|
+
}
|
|
418
560
|
} else if (item.type === "function_call") {
|
|
419
561
|
// ResponseFunctionToolCall
|
|
420
562
|
const fc = item as ResponseFunctionToolCall;
|
|
@@ -444,12 +586,21 @@ export class GenericOpenAiClient implements GenericClient {
|
|
|
444
586
|
role: "assistant",
|
|
445
587
|
content: textContent,
|
|
446
588
|
...(toolCalls.length > 0 && { tool_calls: toolCalls }),
|
|
589
|
+
...(reasoningSummary && { reasoning_summary: reasoningSummary }),
|
|
590
|
+
// Stash the raw reasoning items (encrypted_content) so the next call
|
|
591
|
+
// to createChatResponse can re-inject them into the input. We populate
|
|
592
|
+
// both the provider-agnostic slot and the deprecated legacy field.
|
|
593
|
+
...(reasoningItems.length > 0 && {
|
|
594
|
+
_reasoning_items: reasoningItems,
|
|
595
|
+
_reasoning_details: { provider: "openai", items: reasoningItems },
|
|
596
|
+
}),
|
|
447
597
|
},
|
|
448
598
|
},
|
|
449
599
|
],
|
|
450
600
|
model: options.model,
|
|
451
601
|
usage,
|
|
452
602
|
usd_cost: usdCost,
|
|
603
|
+
response_id: (response as any).id,
|
|
453
604
|
};
|
|
454
605
|
}
|
|
455
606
|
|