@earendil-works/pi-ai 0.84.2 → 0.84.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -3
- package/dist/api/anthropic-messages.d.ts.map +1 -1
- package/dist/api/anthropic-messages.js +34 -19
- package/dist/api/anthropic-messages.js.map +1 -1
- package/dist/api/azure-openai-responses.d.ts +2 -0
- package/dist/api/azure-openai-responses.d.ts.map +1 -1
- package/dist/api/azure-openai-responses.js +9 -2
- package/dist/api/azure-openai-responses.js.map +1 -1
- package/dist/api/bedrock-converse-stream.d.ts.map +1 -1
- package/dist/api/bedrock-converse-stream.js +131 -7
- package/dist/api/bedrock-converse-stream.js.map +1 -1
- package/dist/api/google-generative-ai.d.ts +2 -2
- package/dist/api/google-generative-ai.d.ts.map +1 -1
- package/dist/api/google-generative-ai.js +17 -13
- package/dist/api/google-generative-ai.js.map +1 -1
- package/dist/api/google-shared.d.ts +5 -2
- package/dist/api/google-shared.d.ts.map +1 -1
- package/dist/api/google-shared.js +16 -0
- package/dist/api/google-shared.js.map +1 -1
- package/dist/api/google-vertex.d.ts +2 -2
- package/dist/api/google-vertex.d.ts.map +1 -1
- package/dist/api/google-vertex.js +15 -11
- package/dist/api/google-vertex.js.map +1 -1
- package/dist/api/mistral-conversations.d.ts.map +1 -1
- package/dist/api/mistral-conversations.js +6 -1
- package/dist/api/mistral-conversations.js.map +1 -1
- package/dist/api/openai-codex-responses.d.ts.map +1 -1
- package/dist/api/openai-codex-responses.js +4 -1
- package/dist/api/openai-codex-responses.js.map +1 -1
- package/dist/api/openai-completions.d.ts +3 -2
- package/dist/api/openai-completions.d.ts.map +1 -1
- package/dist/api/openai-completions.js +128 -86
- package/dist/api/openai-completions.js.map +1 -1
- package/dist/api/openai-responses.d.ts.map +1 -1
- package/dist/api/openai-responses.js +6 -2
- package/dist/api/openai-responses.js.map +1 -1
- package/dist/api/pi-messages.d.ts.map +1 -1
- package/dist/api/pi-messages.js +1 -1
- package/dist/api/pi-messages.js.map +1 -1
- package/dist/api/simple-options.d.ts +4 -0
- package/dist/api/simple-options.d.ts.map +1 -1
- package/dist/api/simple-options.js +17 -10
- package/dist/api/simple-options.js.map +1 -1
- package/dist/auth/oauth/device-code.d.ts +1 -0
- package/dist/auth/oauth/device-code.d.ts.map +1 -1
- package/dist/auth/oauth/device-code.js +1 -1
- package/dist/auth/oauth/device-code.js.map +1 -1
- package/dist/auth/oauth/github-copilot.d.ts.map +1 -1
- package/dist/auth/oauth/github-copilot.js +98 -34
- package/dist/auth/oauth/github-copilot.js.map +1 -1
- package/dist/auth/oauth/kimi-coding.d.ts.map +1 -1
- package/dist/auth/oauth/kimi-coding.js +1 -14
- package/dist/auth/oauth/kimi-coding.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/providers/data/.manifest.json +1 -1
- package/dist/providers/data/amazon-bedrock.json +1 -1
- package/dist/providers/data/anthropic.json +1 -1
- package/dist/providers/data/baseten.json +1 -1
- package/dist/providers/data/cerebras.json +1 -1
- package/dist/providers/data/cloudflare-ai-gateway.json +1 -1
- package/dist/providers/data/cloudflare-workers-ai.json +1 -1
- package/dist/providers/data/github-copilot.json +1 -1
- package/dist/providers/data/google-vertex.json +1 -1
- package/dist/providers/data/google.json +1 -1
- package/dist/providers/data/huggingface.json +1 -1
- package/dist/providers/data/nvidia.json +1 -1
- package/dist/providers/data/opencode-go.json +1 -1
- package/dist/providers/data/opencode.json +1 -1
- package/dist/providers/data/openrouter.json +1 -1
- package/dist/providers/data/qwen-token-plan-cn.json +1 -1
- package/dist/providers/data/qwen-token-plan-individual.json +1 -1
- package/dist/providers/data/qwen-token-plan.json +1 -1
- package/dist/providers/data/together.json +1 -1
- package/dist/providers/data/vercel-ai-gateway.json +1 -1
- package/dist/providers/data/xai.json +1 -1
- package/dist/providers/data/xiaomi-token-plan-ams.json +1 -1
- package/dist/providers/data/xiaomi-token-plan-cn.json +1 -1
- package/dist/providers/data/xiaomi-token-plan-sgp.json +1 -1
- package/dist/providers/data/xiaomi.json +1 -1
- package/dist/providers/data/zai-coding-cn.json +1 -1
- package/dist/providers/data/zai.json +1 -1
- package/dist/providers/xai.d.ts +1 -1
- package/dist/providers/xai.d.ts.map +1 -1
- package/dist/providers/xai.js +1 -5
- package/dist/providers/xai.js.map +1 -1
- package/dist/types.d.ts +29 -4
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/utils/sleep.d.ts +2 -0
- package/dist/utils/sleep.d.ts.map +1 -0
- package/dist/utils/sleep.js +15 -0
- package/dist/utils/sleep.js.map +1 -0
- package/package.json +3 -4
|
@@ -5,13 +5,14 @@ import { AssistantMessageEventStream } from "../utils/event-stream.js";
|
|
|
5
5
|
import { shortHash } from "../utils/hash.js";
|
|
6
6
|
import { headersToRecord } from "../utils/headers.js";
|
|
7
7
|
import { parseStreamingJson } from "../utils/json-parse.js";
|
|
8
|
+
import { getPiUserAgent } from "../utils/pi-user-agent.js";
|
|
8
9
|
import { getProviderEnvValue } from "../utils/provider-env.js";
|
|
9
10
|
import { retryProviderRequest } from "../utils/provider-retry.js";
|
|
10
11
|
import { sanitizeSurrogates } from "../utils/sanitize-unicode.js";
|
|
11
12
|
import { appendGrammarToolInputJsonDelta, createGrammarToolInputProperties, getGrammarToolInput, getJsonSchemaToolParameters, resolveGrammarConstrainedSampling, resolveJsonSchemaStrictSampling, } from "./constrained-sampling.js";
|
|
12
13
|
import { buildCopilotDynamicHeaders, hasCopilotVisionInput } from "./github-copilot-headers.js";
|
|
13
14
|
import { clampOpenAIPromptCacheKey } from "./openai-prompt-cache.js";
|
|
14
|
-
import { buildBaseOptions,
|
|
15
|
+
import { buildBaseOptions, clampThinkingBudgetToAnswerRoom, thinkingBudgetForLevel } from "./simple-options.js";
|
|
15
16
|
import { transformMessages } from "./transform-messages.js";
|
|
16
17
|
/**
|
|
17
18
|
* Check if conversation messages contain tool calls or tool results.
|
|
@@ -79,16 +80,61 @@ function isToolCallBlock(block) {
|
|
|
79
80
|
function isImageContentBlock(block) {
|
|
80
81
|
return block.type === "image";
|
|
81
82
|
}
|
|
82
|
-
function
|
|
83
|
-
|
|
83
|
+
function isReasoningDetailObject(detail) {
|
|
84
|
+
return typeof detail === "object" && detail !== null && !Array.isArray(detail);
|
|
85
|
+
}
|
|
86
|
+
function hasValidCommonReasoningDetailFields(candidate) {
|
|
87
|
+
return ((candidate.id === undefined || candidate.id === null || typeof candidate.id === "string") &&
|
|
88
|
+
(candidate.format === undefined || typeof candidate.format === "string") &&
|
|
89
|
+
(candidate.index === undefined || typeof candidate.index === "number"));
|
|
90
|
+
}
|
|
91
|
+
function isOpenAIReasoningDetail(detail) {
|
|
92
|
+
if (!isReasoningDetailObject(detail) || !hasValidCommonReasoningDetailFields(detail)) {
|
|
84
93
|
return false;
|
|
85
94
|
}
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
95
|
+
switch (detail.type) {
|
|
96
|
+
case "reasoning.summary":
|
|
97
|
+
return typeof detail.summary === "string";
|
|
98
|
+
case "reasoning.encrypted":
|
|
99
|
+
return typeof detail.data === "string";
|
|
100
|
+
case "reasoning.text":
|
|
101
|
+
return (typeof detail.text === "string" &&
|
|
102
|
+
(detail.signature === undefined || detail.signature === null || typeof detail.signature === "string"));
|
|
103
|
+
default:
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
function parseOpenAIReasoningDetails(signature) {
|
|
108
|
+
if (!signature)
|
|
109
|
+
return undefined;
|
|
110
|
+
try {
|
|
111
|
+
const parsed = JSON.parse(signature);
|
|
112
|
+
return Array.isArray(parsed) && parsed.length > 0 && parsed.every(isOpenAIReasoningDetail) ? parsed : undefined;
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
return undefined;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
function parseLegacyEncryptedReasoningDetail(signature) {
|
|
119
|
+
if (!signature)
|
|
120
|
+
return undefined;
|
|
121
|
+
try {
|
|
122
|
+
const parsed = JSON.parse(signature);
|
|
123
|
+
return isOpenAIReasoningDetail(parsed) &&
|
|
124
|
+
parsed.type === "reasoning.encrypted" &&
|
|
125
|
+
typeof parsed.id === "string" &&
|
|
126
|
+
parsed.id.length > 0 &&
|
|
127
|
+
parsed.data.length > 0
|
|
128
|
+
? parsed
|
|
129
|
+
: undefined;
|
|
130
|
+
}
|
|
131
|
+
catch {
|
|
132
|
+
return undefined;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
const OPENAI_COMPLETIONS_REASONING_FIELDS = ["reasoning", "reasoning_content", "reasoning_text"];
|
|
136
|
+
function isOpenAICompletionsReasoningField(field) {
|
|
137
|
+
return OPENAI_COMPLETIONS_REASONING_FIELDS.includes(field);
|
|
92
138
|
}
|
|
93
139
|
function resolveCacheRetention(cacheRetention, env) {
|
|
94
140
|
if (cacheRetention) {
|
|
@@ -148,7 +194,6 @@ export const stream = (model, context, options) => {
|
|
|
148
194
|
let hasFinishReason = false;
|
|
149
195
|
const toolCallBlocksByIndex = new Map();
|
|
150
196
|
const toolCallBlocksById = new Map();
|
|
151
|
-
const pendingReasoningDetailsByToolCallId = new Map();
|
|
152
197
|
const blocks = output.content;
|
|
153
198
|
const getContentIndex = (block) => blocks.indexOf(block);
|
|
154
199
|
const getCustomToolCallInput = (block) => {
|
|
@@ -235,16 +280,6 @@ export const stream = (model, context, options) => {
|
|
|
235
280
|
}
|
|
236
281
|
return thinkingBlock;
|
|
237
282
|
};
|
|
238
|
-
const applyPendingReasoningDetail = (block) => {
|
|
239
|
-
if (!block.id) {
|
|
240
|
-
return;
|
|
241
|
-
}
|
|
242
|
-
const pendingReasoningDetail = pendingReasoningDetailsByToolCallId.get(block.id);
|
|
243
|
-
if (pendingReasoningDetail) {
|
|
244
|
-
block.thoughtSignature = pendingReasoningDetail;
|
|
245
|
-
pendingReasoningDetailsByToolCallId.delete(block.id);
|
|
246
|
-
}
|
|
247
|
-
};
|
|
248
283
|
const ensureToolCallBlock = (toolCall) => {
|
|
249
284
|
const streamIndex = typeof toolCall.index === "number" ? toolCall.index : undefined;
|
|
250
285
|
const name = toolCall.function?.name ?? toolCall.custom?.name ?? "";
|
|
@@ -300,7 +335,6 @@ export const stream = (model, context, options) => {
|
|
|
300
335
|
};
|
|
301
336
|
delete block.partialArgs;
|
|
302
337
|
}
|
|
303
|
-
applyPendingReasoningDetail(block);
|
|
304
338
|
return block;
|
|
305
339
|
};
|
|
306
340
|
for await (const chunk of openaiStream) {
|
|
@@ -407,16 +441,14 @@ export const stream = (model, context, options) => {
|
|
|
407
441
|
const reasoningDetails = choice.delta.reasoning_details;
|
|
408
442
|
if (Array.isArray(reasoningDetails)) {
|
|
409
443
|
for (const detail of reasoningDetails) {
|
|
410
|
-
if (
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
}
|
|
419
|
-
}
|
|
444
|
+
if (!isOpenAIReasoningDetail(detail))
|
|
445
|
+
continue;
|
|
446
|
+
const block = ensureThinkingBlock("");
|
|
447
|
+
const preservedDetails = parseOpenAIReasoningDetails(block.thinkingSignature) ?? [];
|
|
448
|
+
preservedDetails.push(detail);
|
|
449
|
+
// Keep provider replay data in the existing signature slot. OpenRouter
|
|
450
|
+
// requires the complete reasoning_details sequence in its original order.
|
|
451
|
+
block.thinkingSignature = JSON.stringify(preservedDetails);
|
|
420
452
|
}
|
|
421
453
|
}
|
|
422
454
|
}
|
|
@@ -468,19 +500,20 @@ export const stream = (model, context, options) => {
|
|
|
468
500
|
};
|
|
469
501
|
export const streamSimple = (model, context, options) => {
|
|
470
502
|
getClientApiKey(model.provider, options?.apiKey, options?.headers);
|
|
471
|
-
const base =
|
|
503
|
+
const base = {
|
|
504
|
+
...buildBaseOptions(model, context, options, options?.apiKey),
|
|
505
|
+
toolChoice: options?.toolChoice,
|
|
506
|
+
};
|
|
472
507
|
const clampedReasoning = options?.reasoning ? clampThinkingLevel(model, options.reasoning) : undefined;
|
|
473
508
|
const reasoningEffort = clampedReasoning === "off" ? undefined : clampedReasoning;
|
|
474
|
-
const toolChoice = options?.toolChoice;
|
|
475
509
|
return stream(model, context, {
|
|
476
510
|
...base,
|
|
477
511
|
reasoningEffort,
|
|
478
|
-
toolChoice,
|
|
479
512
|
thinkingBudgets: options?.thinkingBudgets,
|
|
480
513
|
});
|
|
481
514
|
};
|
|
482
515
|
function createClient(model, context, apiKey, optionsHeaders, fetch, sessionId, compat = getCompat(model)) {
|
|
483
|
-
const headers = { ...model.headers };
|
|
516
|
+
const headers = { "User-Agent": getPiUserAgent(), ...model.headers };
|
|
484
517
|
if (model.provider === "github-copilot") {
|
|
485
518
|
const hasImages = hasCopilotVisionInput(context.messages);
|
|
486
519
|
const copilotHeaders = buildCopilotDynamicHeaders({
|
|
@@ -561,6 +594,8 @@ function buildParams(model, context, options, compat = getCompat(model), cacheRe
|
|
|
561
594
|
if (options?.toolChoice) {
|
|
562
595
|
params.tool_choice = options.toolChoice;
|
|
563
596
|
}
|
|
597
|
+
const thinkingTokenBudgetField = resolveThinkingTokenBudgetField(compat);
|
|
598
|
+
const thinkingBudget = resolveClampedThinkingBudget(model, options, params);
|
|
564
599
|
if (compat.thinkingFormat === "zai" && model.reasoning) {
|
|
565
600
|
const zaiParams = params;
|
|
566
601
|
zaiParams.thinking = options?.reasoningEffort ? { type: "enabled", clear_thinking: false } : { type: "disabled" };
|
|
@@ -588,14 +623,14 @@ function buildParams(model, context, options, compat = getCompat(model), cacheRe
|
|
|
588
623
|
};
|
|
589
624
|
}
|
|
590
625
|
else if (compat.thinkingFormat === "chat-template" && model.reasoning) {
|
|
591
|
-
const chatTemplateKwargs = buildChatTemplateValues(model, options, compat.chatTemplateKwargs);
|
|
626
|
+
const chatTemplateKwargs = buildChatTemplateValues(model, options, compat.chatTemplateKwargs, thinkingBudget);
|
|
592
627
|
if (chatTemplateKwargs) {
|
|
593
628
|
params.chat_template_kwargs = chatTemplateKwargs;
|
|
594
629
|
}
|
|
595
630
|
}
|
|
596
631
|
else if (compat.thinkingFormat === "baseten" && model.reasoning) {
|
|
597
632
|
const basetenParams = params;
|
|
598
|
-
const chatTemplateArgs = buildChatTemplateValues(model, options, compat.chatTemplateArgs);
|
|
633
|
+
const chatTemplateArgs = buildChatTemplateValues(model, options, compat.chatTemplateArgs, thinkingBudget);
|
|
599
634
|
if (chatTemplateArgs) {
|
|
600
635
|
basetenParams.chat_template_args = chatTemplateArgs;
|
|
601
636
|
}
|
|
@@ -664,25 +699,12 @@ function buildParams(model, context, options, compat = getCompat(model), cacheRe
|
|
|
664
699
|
params.reasoning_effort = offValue;
|
|
665
700
|
}
|
|
666
701
|
}
|
|
667
|
-
//
|
|
668
|
-
//
|
|
669
|
-
//
|
|
670
|
-
//
|
|
671
|
-
if (
|
|
672
|
-
|
|
673
|
-
const budgets = {
|
|
674
|
-
minimal: 1024,
|
|
675
|
-
low: 2048,
|
|
676
|
-
medium: 8192,
|
|
677
|
-
high: 16384,
|
|
678
|
-
...options.thinkingBudgets,
|
|
679
|
-
};
|
|
680
|
-
const ceiling = params.max_tokens ?? params.max_completion_tokens ?? model.maxTokens;
|
|
681
|
-
// Always leave room for the answer, otherwise the budget recreates the bug it prevents.
|
|
682
|
-
const budget = Math.min(budgets[level], Math.max(0, ceiling - MIN_ANSWER_TOKENS));
|
|
683
|
-
if (budget > 0) {
|
|
684
|
-
params.thinking_token_budget = budget;
|
|
685
|
-
}
|
|
702
|
+
// Cap reasoning with a top-level budget field. Independent of thinkingFormat: the
|
|
703
|
+
// same server can serve zai, qwen or chat-template models. Reasoning and the answer
|
|
704
|
+
// share max_tokens here, so an uncapped reasoning phase can consume the whole
|
|
705
|
+
// response and leave no answer and no tool call.
|
|
706
|
+
if (thinkingTokenBudgetField && thinkingBudget !== undefined) {
|
|
707
|
+
Object.assign(params, { [thinkingTokenBudgetField]: thinkingBudget });
|
|
686
708
|
}
|
|
687
709
|
// OpenRouter provider routing preferences
|
|
688
710
|
if (model.compat?.openRouterRouting) {
|
|
@@ -706,17 +728,31 @@ function buildParams(model, context, options, compat = getCompat(model), cacheRe
|
|
|
706
728
|
}
|
|
707
729
|
return params;
|
|
708
730
|
}
|
|
709
|
-
function
|
|
731
|
+
function resolveThinkingTokenBudgetField(compat) {
|
|
732
|
+
if (compat.thinkingTokenBudgetField)
|
|
733
|
+
return compat.thinkingTokenBudgetField;
|
|
734
|
+
if (compat.supportsThinkingTokenBudget)
|
|
735
|
+
return "thinking_token_budget";
|
|
736
|
+
return undefined;
|
|
737
|
+
}
|
|
738
|
+
function resolveClampedThinkingBudget(model, options, params) {
|
|
739
|
+
if (!options?.reasoningEffort || !model.reasoning)
|
|
740
|
+
return undefined;
|
|
741
|
+
const ceiling = params.max_tokens ?? params.max_completion_tokens ?? model.maxTokens;
|
|
742
|
+
const budget = clampThinkingBudgetToAnswerRoom(thinkingBudgetForLevel(options.reasoningEffort, options.thinkingBudgets), ceiling);
|
|
743
|
+
return budget > 0 ? budget : undefined;
|
|
744
|
+
}
|
|
745
|
+
function buildChatTemplateValues(model, options, values, thinkingBudget) {
|
|
710
746
|
const resolvedValues = {};
|
|
711
747
|
for (const [key, value] of Object.entries(values)) {
|
|
712
|
-
const resolved = resolveChatTemplateKwargValue(model, options, value);
|
|
748
|
+
const resolved = resolveChatTemplateKwargValue(model, options, value, thinkingBudget);
|
|
713
749
|
if (resolved !== undefined) {
|
|
714
750
|
resolvedValues[key] = resolved;
|
|
715
751
|
}
|
|
716
752
|
}
|
|
717
753
|
return Object.keys(resolvedValues).length > 0 ? resolvedValues : undefined;
|
|
718
754
|
}
|
|
719
|
-
function resolveChatTemplateKwargValue(model, options, value) {
|
|
755
|
+
function resolveChatTemplateKwargValue(model, options, value, thinkingBudget) {
|
|
720
756
|
if (typeof value !== "object" || value === null) {
|
|
721
757
|
return value;
|
|
722
758
|
}
|
|
@@ -727,6 +763,9 @@ function resolveChatTemplateKwargValue(model, options, value) {
|
|
|
727
763
|
if (value.$var === "thinking.enabled") {
|
|
728
764
|
return !!reasoningEffort;
|
|
729
765
|
}
|
|
766
|
+
if (value.$var === "thinking.budget") {
|
|
767
|
+
return thinkingBudget;
|
|
768
|
+
}
|
|
730
769
|
const mappedValue = reasoningEffort ? model.thinkingLevelMap?.[reasoningEffort] : model.thinkingLevelMap?.off;
|
|
731
770
|
return mappedValue === undefined ? reasoningEffort : typeof mappedValue === "string" ? mappedValue : undefined;
|
|
732
771
|
}
|
|
@@ -894,9 +933,16 @@ export function convertMessages(model, context, compat, options) {
|
|
|
894
933
|
text: sanitizeSurrogates(block.text),
|
|
895
934
|
}));
|
|
896
935
|
const assistantText = assistantTextParts.map((part) => part.text).join("");
|
|
897
|
-
const
|
|
898
|
-
|
|
899
|
-
|
|
936
|
+
const thinkingBlocks = msg.content.filter(isThinkingContentBlock);
|
|
937
|
+
const toolCalls = msg.content.filter(isToolCallBlock);
|
|
938
|
+
const signedReasoningDetails = thinkingBlocks
|
|
939
|
+
.map((block) => parseOpenAIReasoningDetails(block.thinkingSignature))
|
|
940
|
+
.find((details) => details !== undefined);
|
|
941
|
+
const legacyReasoningDetails = toolCalls
|
|
942
|
+
.map((toolCall) => parseLegacyEncryptedReasoningDetail(toolCall.thoughtSignature))
|
|
943
|
+
.filter((detail) => detail !== undefined);
|
|
944
|
+
const preservedReasoningDetails = signedReasoningDetails ?? (legacyReasoningDetails.length > 0 ? legacyReasoningDetails : undefined);
|
|
945
|
+
const nonEmptyThinkingBlocks = thinkingBlocks.filter((block) => block.thinking.trim().length > 0);
|
|
900
946
|
if (nonEmptyThinkingBlocks.length > 0) {
|
|
901
947
|
if (compat.requiresThinkingAsText) {
|
|
902
948
|
// Convert thinking blocks to plain text (no tags to avoid model mimicking them)
|
|
@@ -914,13 +960,16 @@ export function convertMessages(model, context, compat, options) {
|
|
|
914
960
|
if (assistantText.length > 0) {
|
|
915
961
|
assistantMsg.content = assistantText;
|
|
916
962
|
}
|
|
917
|
-
//
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
signature =
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
963
|
+
// reasoning_details is the structured alternative to a raw reasoning field.
|
|
964
|
+
if (!preservedReasoningDetails) {
|
|
965
|
+
// Use the signature from the first thinking block if available (for llama.cpp server + gpt-oss)
|
|
966
|
+
let signature = nonEmptyThinkingBlocks[0].thinkingSignature;
|
|
967
|
+
if (model.provider === "opencode-go" && signature === "reasoning") {
|
|
968
|
+
signature = "reasoning_content";
|
|
969
|
+
}
|
|
970
|
+
if (signature && isOpenAICompletionsReasoningField(signature)) {
|
|
971
|
+
assistantMsg[signature] = nonEmptyThinkingBlocks.map((block) => block.thinking).join("\n");
|
|
972
|
+
}
|
|
924
973
|
}
|
|
925
974
|
}
|
|
926
975
|
}
|
|
@@ -932,7 +981,6 @@ export function convertMessages(model, context, compat, options) {
|
|
|
932
981
|
// output, producing recursive nesting like [{'type':'text','text':'[{...}]'}].
|
|
933
982
|
assistantMsg.content = assistantText;
|
|
934
983
|
}
|
|
935
|
-
const toolCalls = msg.content.filter(isToolCallBlock);
|
|
936
984
|
if (toolCalls.length > 0) {
|
|
937
985
|
assistantMsg.tool_calls = toolCalls.map((tc) => {
|
|
938
986
|
const customInputProperty = options?.grammarToolInputProperties?.get(tc.name);
|
|
@@ -955,20 +1003,9 @@ export function convertMessages(model, context, compat, options) {
|
|
|
955
1003
|
},
|
|
956
1004
|
};
|
|
957
1005
|
});
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
try {
|
|
962
|
-
return JSON.parse(tc.thoughtSignature);
|
|
963
|
-
}
|
|
964
|
-
catch {
|
|
965
|
-
return null;
|
|
966
|
-
}
|
|
967
|
-
})
|
|
968
|
-
.filter(Boolean);
|
|
969
|
-
if (reasoningDetails.length > 0) {
|
|
970
|
-
assistantMsg.reasoning_details = reasoningDetails;
|
|
971
|
-
}
|
|
1006
|
+
}
|
|
1007
|
+
if (preservedReasoningDetails) {
|
|
1008
|
+
assistantMsg.reasoning_details = preservedReasoningDetails;
|
|
972
1009
|
}
|
|
973
1010
|
if (compat.requiresReasoningContentOnAssistantMessages &&
|
|
974
1011
|
model.reasoning &&
|
|
@@ -1105,10 +1142,13 @@ function convertTools(tools, compat) {
|
|
|
1105
1142
|
}
|
|
1106
1143
|
function parseChunkUsage(rawUsage, model) {
|
|
1107
1144
|
const promptTokens = rawUsage.prompt_tokens || 0;
|
|
1108
|
-
const cacheReadTokens = rawUsage.prompt_tokens_details?.cached_tokens ?? rawUsage.prompt_cache_hit_tokens ?? 0;
|
|
1145
|
+
const cacheReadTokens = rawUsage.prompt_tokens_details?.cached_tokens ?? rawUsage.prompt_cache_hit_tokens ?? rawUsage.cached_tokens ?? 0;
|
|
1109
1146
|
const cacheWriteTokens = rawUsage.prompt_tokens_details?.cache_write_tokens || 0;
|
|
1110
1147
|
// Follow documented OpenAI/OpenRouter semantics: cached_tokens is cache-read
|
|
1111
|
-
// tokens (hits).
|
|
1148
|
+
// tokens (hits). Providers disagree on placement: OpenAI/OpenRouter use
|
|
1149
|
+
// prompt_tokens_details.cached_tokens, DeepSeek uses prompt_cache_hit_tokens,
|
|
1150
|
+
// and Kimi documents top-level usage.cached_tokens on the final usage chunk.
|
|
1151
|
+
// OpenAI does not document or emit cache_write_tokens, but
|
|
1112
1152
|
// OpenRouter-compatible providers can include it as a separate write count.
|
|
1113
1153
|
// OpenRouter's own provider/tests affirm the separate mapping:
|
|
1114
1154
|
// https://github.com/OpenRouterTeam/ai-sdk-provider/pull/409
|
|
@@ -1227,6 +1267,7 @@ function detectCompat(model) {
|
|
|
1227
1267
|
chatTemplateArgs: {},
|
|
1228
1268
|
zaiToolStream: false,
|
|
1229
1269
|
supportsThinkingTokenBudget: false,
|
|
1270
|
+
thinkingTokenBudgetField: undefined,
|
|
1230
1271
|
supportsStrictMode: !isMoonshot && !isTogether && !isCloudflareAiGateway && !isNvidia,
|
|
1231
1272
|
supportsOpenAIGrammarTools: false,
|
|
1232
1273
|
cacheControlFormat,
|
|
@@ -1267,6 +1308,7 @@ function getCompat(model) {
|
|
|
1267
1308
|
chatTemplateArgs: model.compat.chatTemplateArgs ?? detected.chatTemplateArgs,
|
|
1268
1309
|
zaiToolStream: model.compat.zaiToolStream ?? detected.zaiToolStream,
|
|
1269
1310
|
supportsThinkingTokenBudget: model.compat.supportsThinkingTokenBudget ?? detected.supportsThinkingTokenBudget,
|
|
1311
|
+
thinkingTokenBudgetField: model.compat.thinkingTokenBudgetField ?? detected.thinkingTokenBudgetField,
|
|
1270
1312
|
supportsStrictMode: model.compat.supportsStrictMode ?? detected.supportsStrictMode,
|
|
1271
1313
|
supportsOpenAIGrammarTools: model.compat.supportsOpenAIGrammarTools ?? detected.supportsOpenAIGrammarTools,
|
|
1272
1314
|
cacheControlFormat: model.compat.cacheControlFormat ?? detected.cacheControlFormat,
|