@earendil-works/pi-ai 0.84.1 → 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 +40 -12
- 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 +147 -11
- package/dist/api/bedrock-converse-stream.js.map +1 -1
- package/dist/api/cloudflare-gateway-binding.d.ts +69 -0
- package/dist/api/cloudflare-gateway-binding.d.ts.map +1 -0
- package/dist/api/cloudflare-gateway-binding.js +159 -0
- package/dist/api/cloudflare-gateway-binding.js.map +1 -0
- package/dist/api/constrained-sampling.d.ts +3 -0
- package/dist/api/constrained-sampling.d.ts.map +1 -1
- package/dist/api/constrained-sampling.js +125 -3
- package/dist/api/constrained-sampling.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 +24 -16
- package/dist/api/google-generative-ai.js.map +1 -1
- package/dist/api/google-shared.d.ts +6 -3
- package/dist/api/google-shared.d.ts.map +1 -1
- package/dist/api/google-shared.js +29 -9
- 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 +22 -14
- package/dist/api/google-vertex.js.map +1 -1
- package/dist/api/mistral-conversations.d.ts +1 -1
- package/dist/api/mistral-conversations.d.ts.map +1 -1
- package/dist/api/mistral-conversations.js +216 -43
- 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 +19 -15
- 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 +133 -90
- package/dist/api/openai-completions.js.map +1 -1
- package/dist/api/openai-responses-shared.d.ts +1 -0
- package/dist/api/openai-responses-shared.d.ts.map +1 -1
- package/dist/api/openai-responses-shared.js +29 -8
- package/dist/api/openai-responses-shared.js.map +1 -1
- package/dist/api/openai-responses.d.ts.map +1 -1
- package/dist/api/openai-responses.js +14 -3
- 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 +99 -32
- 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/image-models.generated.d.ts +45 -0
- package/dist/image-models.generated.d.ts.map +1 -1
- package/dist/image-models.generated.js +45 -0
- package/dist/image-models.generated.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/deepseek.json +1 -1
- package/dist/providers/data/fireworks.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/kimi-coding.json +1 -1
- package/dist/providers/data/nvidia.json +1 -1
- package/dist/providers/data/openai-codex.json +1 -1
- package/dist/providers/data/openai.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 +38 -4
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/utils/pi-user-agent.d.ts +2 -0
- package/dist/utils/pi-user-agent.d.ts.map +1 -0
- package/dist/utils/pi-user-agent.js +12 -0
- package/dist/utils/pi-user-agent.js.map +1 -0
- package/dist/utils/retry.d.ts.map +1 -1
- package/dist/utils/retry.js +1 -0
- package/dist/utils/retry.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/dist/utils/validation.d.ts.map +1 -1
- package/dist/utils/validation.js +34 -0
- package/dist/utils/validation.js.map +1 -1
- package/package.json +4 -6
|
@@ -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
|
-
import { appendGrammarToolInputJsonDelta, createGrammarToolInputProperties, getGrammarToolInput, resolveGrammarConstrainedSampling, resolveJsonSchemaStrictSampling, } from "./constrained-sampling.js";
|
|
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 &&
|
|
@@ -1096,7 +1133,7 @@ function convertTools(tools, compat) {
|
|
|
1096
1133
|
function: {
|
|
1097
1134
|
name: tool.name,
|
|
1098
1135
|
description: tool.description,
|
|
1099
|
-
parameters: tool
|
|
1136
|
+
parameters: getJsonSchemaToolParameters(tool, strict),
|
|
1100
1137
|
// Only include strict if provider supports it. Some reject unknown fields.
|
|
1101
1138
|
...(compat.supportsStrictMode !== false && { strict: strict ?? false }),
|
|
1102
1139
|
},
|
|
@@ -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
|
|
@@ -1172,6 +1212,7 @@ function detectCompat(model) {
|
|
|
1172
1212
|
const isCloudflareAiGateway = provider === "cloudflare-ai-gateway" || baseUrl.includes("gateway.ai.cloudflare.com");
|
|
1173
1213
|
const isNvidia = provider === "nvidia" || baseUrl.includes("integrate.api.nvidia.com");
|
|
1174
1214
|
const isAntLing = provider === "ant-ling" || baseUrl.includes("api.ant-ling.com");
|
|
1215
|
+
const isDeepSeek = provider === "deepseek" || baseUrl.toLowerCase().includes("deepseek.com");
|
|
1175
1216
|
const isNonStandard = isNvidia ||
|
|
1176
1217
|
provider === "cerebras" ||
|
|
1177
1218
|
baseUrl.includes("cerebras.ai") ||
|
|
@@ -1179,7 +1220,7 @@ function detectCompat(model) {
|
|
|
1179
1220
|
baseUrl.includes("api.x.ai") ||
|
|
1180
1221
|
isTogether ||
|
|
1181
1222
|
baseUrl.includes("chutes.ai") ||
|
|
1182
|
-
|
|
1223
|
+
isDeepSeek ||
|
|
1183
1224
|
isZai ||
|
|
1184
1225
|
isMoonshot ||
|
|
1185
1226
|
provider === "opencode" ||
|
|
@@ -1188,6 +1229,7 @@ function detectCompat(model) {
|
|
|
1188
1229
|
isCloudflareAiGateway ||
|
|
1189
1230
|
isAntLing;
|
|
1190
1231
|
const useMaxTokens = baseUrl.includes("chutes.ai") ||
|
|
1232
|
+
isDeepSeek ||
|
|
1191
1233
|
isMoonshot ||
|
|
1192
1234
|
isCloudflareAiGateway ||
|
|
1193
1235
|
isTogether ||
|
|
@@ -1195,7 +1237,6 @@ function detectCompat(model) {
|
|
|
1195
1237
|
isAntLing ||
|
|
1196
1238
|
isZai;
|
|
1197
1239
|
const isGrok = provider === "xai" || baseUrl.includes("api.x.ai");
|
|
1198
|
-
const isDeepSeek = provider === "deepseek" || baseUrl.includes("deepseek.com");
|
|
1199
1240
|
const isOpenRouterDeveloperRoleModel = isOpenRouter && (model.id.startsWith("anthropic/") || model.id.startsWith("openai/"));
|
|
1200
1241
|
const cacheControlFormat = provider === "openrouter" && model.id.startsWith("anthropic/") ? "anthropic" : undefined;
|
|
1201
1242
|
return {
|
|
@@ -1226,6 +1267,7 @@ function detectCompat(model) {
|
|
|
1226
1267
|
chatTemplateArgs: {},
|
|
1227
1268
|
zaiToolStream: false,
|
|
1228
1269
|
supportsThinkingTokenBudget: false,
|
|
1270
|
+
thinkingTokenBudgetField: undefined,
|
|
1229
1271
|
supportsStrictMode: !isMoonshot && !isTogether && !isCloudflareAiGateway && !isNvidia,
|
|
1230
1272
|
supportsOpenAIGrammarTools: false,
|
|
1231
1273
|
cacheControlFormat,
|
|
@@ -1266,6 +1308,7 @@ function getCompat(model) {
|
|
|
1266
1308
|
chatTemplateArgs: model.compat.chatTemplateArgs ?? detected.chatTemplateArgs,
|
|
1267
1309
|
zaiToolStream: model.compat.zaiToolStream ?? detected.zaiToolStream,
|
|
1268
1310
|
supportsThinkingTokenBudget: model.compat.supportsThinkingTokenBudget ?? detected.supportsThinkingTokenBudget,
|
|
1311
|
+
thinkingTokenBudgetField: model.compat.thinkingTokenBudgetField ?? detected.thinkingTokenBudgetField,
|
|
1269
1312
|
supportsStrictMode: model.compat.supportsStrictMode ?? detected.supportsStrictMode,
|
|
1270
1313
|
supportsOpenAIGrammarTools: model.compat.supportsOpenAIGrammarTools ?? detected.supportsOpenAIGrammarTools,
|
|
1271
1314
|
cacheControlFormat: model.compat.cacheControlFormat ?? detected.cacheControlFormat,
|