narraeon 0.1.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/node/protocol/modelConnections.js +49 -0
- package/dist/node/protocol/modelUsage.js +39 -0
- package/dist/node/protocol/v1.js +35 -0
- package/dist/node/runtime/FileNativePhysicalClone.js +86 -0
- package/dist/node/runtime/V1Runtime.js +66 -29
- package/dist/node/runtime/model/FileNativeModelAdapters.js +730 -287
- package/dist/node/runtime/model/ModelConnectionStore.js +123 -2
- package/dist/node/runtime/model/ModelHost.js +81 -9
- package/dist/node/runtime/model/ModelHostStream.js +61 -1
- package/dist/node/runtime/model/SettingAuthorStream.js +16 -113
- package/dist/node/runtime/play/FileNativePlayAdvanceStore.js +269 -0
- package/dist/node/runtime/play/FileNativePlayTimelineStore.js +1128 -0
- package/dist/node/runtime/play/PlayCallChain.js +904 -369
- package/dist/node/runtime/play/PlayDocumentTools.js +35 -0
- package/dist/node/runtime/play/PlayFollowupRequests.js +43 -26
- package/dist/node/runtime/prompt/FileNativePromptCompiler.js +50 -35
- package/dist/node/runtime/prompt/FileNativeToolRegistry.js +55 -29
- package/dist/node/runtime/setting/DocumentCandidateSettingImprovement.js +9 -4
- package/dist/node/runtime/world/FileNativeAuthorityV3.js +971 -0
- package/dist/node/runtime/world/FileNativeHistoryProjection.js +39 -0
- package/dist/node/runtime/world/FileNativeWorldStorageMigrator.js +1067 -0
- package/dist/node/runtime/world/FileNativeWorldStore.js +615 -784
- package/dist/node/server/createServer.js +10 -2
- package/dist/web/assets/index-D5VqDvjY.js +1192 -0
- package/dist/web/assets/index-Xa0s35CJ.css +1 -0
- package/dist/web/index.html +2 -2
- package/package.json +1 -1
- package/dist/web/assets/index-CRGTZ9Ak.css +0 -1
- package/dist/web/assets/index-DDx1itzT.js +0 -1192
|
@@ -2,18 +2,50 @@ import { createHash } from "node:crypto";
|
|
|
2
2
|
import { appendFile, chmod, mkdir, stat } from "node:fs/promises";
|
|
3
3
|
import { dirname } from "node:path";
|
|
4
4
|
import { defaultAppLocale, } from "../../protocol/appPreferences.js";
|
|
5
|
-
import {
|
|
5
|
+
import { hasCLIProxyThinkingSuffix, modelReasoningPolicyIssue, } from "../../protocol/modelConnections.js";
|
|
6
|
+
import { ModelHostBindingMismatchError, ModelHostCancelledError, ModelHostContinuationError, ModelHostFailureError, ModelHostOutcomeUnknownError, } from "./ModelHost.js";
|
|
6
7
|
import { renderPromptDeltaMessage, } from "../prompt/FileNativePromptCompiler.js";
|
|
7
8
|
import { aggregateAnthropicSettingStream, aggregateChatSettingStream, } from "./SettingAuthorStream.js";
|
|
8
9
|
import { aggregateAnthropicModelStream, aggregateChatModelStream, aggregateResponsesModelStream, } from "./ModelHostStream.js";
|
|
9
|
-
import { defaultRuntimeToolDefinitionStrategy, } from "../prompt/FileNativeToolRegistry.js";
|
|
10
|
+
import { defaultRuntimeToolDefinitionStrategy, portableRuntimeToolInputSchema, } from "../prompt/FileNativeToolRegistry.js";
|
|
10
11
|
import { AiExchangeCapture, errorDescription, } from "./AiFailureLog.js";
|
|
12
|
+
const providerContinuationCodecVersion = 1;
|
|
13
|
+
function resolveConnection(connection) {
|
|
14
|
+
const resolved = structuredClone({
|
|
15
|
+
...connection,
|
|
16
|
+
dialect: connection.dialect ?? "standard",
|
|
17
|
+
reasoningEffort: connection.reasoningEffort ?? "provider_default",
|
|
18
|
+
reasoningSummary: connection.reasoningSummary ?? "provider_default",
|
|
19
|
+
thinkingMode: connection.thinkingMode ?? "provider_default",
|
|
20
|
+
thinkingBudgetTokens: connection.thinkingBudgetTokens ?? null,
|
|
21
|
+
});
|
|
22
|
+
const issue = modelReasoningPolicyIssue({
|
|
23
|
+
provider: resolved.provider,
|
|
24
|
+
dialect: resolved.dialect,
|
|
25
|
+
modelId: resolved.modelId,
|
|
26
|
+
effort: resolved.reasoningEffort,
|
|
27
|
+
summary: resolved.reasoningSummary,
|
|
28
|
+
thinking: resolved.thinkingMode,
|
|
29
|
+
thinkingBudgetTokens: resolved.thinkingBudgetTokens,
|
|
30
|
+
maxOutputTokens: resolved.maxOutputTokens,
|
|
31
|
+
});
|
|
32
|
+
if (issue !== null)
|
|
33
|
+
throw new Error(issue);
|
|
34
|
+
return resolved;
|
|
35
|
+
}
|
|
36
|
+
function modelCacheStrategy(connection) {
|
|
37
|
+
if (connection.provider === "anthropic_messages")
|
|
38
|
+
return "explicit_anthropic_blocks";
|
|
39
|
+
if (connection.dialect === "cliproxyapi")
|
|
40
|
+
return "explicit_cliproxyapi_message";
|
|
41
|
+
return "provider_managed";
|
|
42
|
+
}
|
|
11
43
|
export class FileNativeModelHost {
|
|
12
44
|
#connection;
|
|
13
45
|
#fetch;
|
|
14
46
|
#failureLog;
|
|
15
47
|
constructor(connection, fetchImplementation = fetch, failureLog) {
|
|
16
|
-
this.#connection =
|
|
48
|
+
this.#connection = resolveConnection(connection);
|
|
17
49
|
this.#fetch = fetchImplementation;
|
|
18
50
|
this.#failureLog = failureLog;
|
|
19
51
|
}
|
|
@@ -24,6 +56,7 @@ export class FileNativeModelHost {
|
|
|
24
56
|
modelId,
|
|
25
57
|
contextWindowTokens,
|
|
26
58
|
maxOutputTokens,
|
|
59
|
+
cacheStrategy: modelCacheStrategy(this.#connection),
|
|
27
60
|
endpointFingerprint: createHash("sha256")
|
|
28
61
|
.update(`${provider}\0${this.#connection.baseUrl}`)
|
|
29
62
|
.digest("hex"),
|
|
@@ -35,75 +68,71 @@ export class FileNativeModelHost {
|
|
|
35
68
|
responseStorage: provider === "openai_responses" ? "disabled" : "provider_default",
|
|
36
69
|
encryptedReasoningContinuation: provider === "openai_responses" ? "included" : "not_requested",
|
|
37
70
|
anthropicVersion: provider === "anthropic_messages" ? "2023-06-01" : null,
|
|
71
|
+
dialect: this.#connection.dialect,
|
|
72
|
+
reasoningEffort: this.#connection.reasoningEffort,
|
|
73
|
+
reasoningSummary: this.#connection.reasoningSummary,
|
|
74
|
+
thinkingMode: this.#connection.thinkingMode,
|
|
75
|
+
thinkingBudgetTokens: this.#connection.thinkingBudgetTokens,
|
|
76
|
+
cachePolicy: modelCacheStrategy(this.#connection),
|
|
77
|
+
providerContinuationCodecVersion,
|
|
38
78
|
}))
|
|
39
79
|
.digest("hex"),
|
|
40
80
|
};
|
|
41
81
|
}
|
|
42
82
|
async exchange(request, observer) {
|
|
83
|
+
assertExchangeCompatibility(this.#connection, request);
|
|
43
84
|
await trace("model_host_exchange", modelHostTrace(request));
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
85
|
+
const signal = observer?.signal;
|
|
86
|
+
if (signalWasAborted(signal))
|
|
87
|
+
throw cancelledModelRequest();
|
|
88
|
+
try {
|
|
89
|
+
if (this.#connection.provider === "chat_completions")
|
|
90
|
+
return await this.#chat(request, observer?.onDelta, signal);
|
|
91
|
+
if (this.#connection.provider === "openai_responses")
|
|
92
|
+
return await this.#responses(request, observer?.onDelta, signal);
|
|
93
|
+
return await this.#anthropic(request, observer?.onDelta, signal);
|
|
94
|
+
}
|
|
95
|
+
catch (error) {
|
|
96
|
+
if (signalWasAborted(signal) &&
|
|
97
|
+
!(error instanceof ModelHostCancelledError))
|
|
98
|
+
throw cancelledModelRequest(error);
|
|
99
|
+
throw error;
|
|
100
|
+
}
|
|
49
101
|
}
|
|
50
|
-
|
|
102
|
+
/** Uses the production encoders and deliberately omits credentials. */
|
|
103
|
+
previewRequest(request) {
|
|
104
|
+
assertExchangeCompatibility(this.#connection, request);
|
|
105
|
+
const provider = this.#connection.provider;
|
|
106
|
+
const body = provider === "chat_completions"
|
|
107
|
+
? chatRequestBody(this.#connection, request)
|
|
108
|
+
: provider === "openai_responses"
|
|
109
|
+
? responsesRequestBody(responsesRequestInput(this.#connection, request))
|
|
110
|
+
: anthropicRequestBody(this.#connection, request);
|
|
111
|
+
return {
|
|
112
|
+
provider,
|
|
113
|
+
method: "POST",
|
|
114
|
+
endpointPath: providerUrl(this.#connection).pathname,
|
|
115
|
+
headerNames: provider === "anthropic_messages"
|
|
116
|
+
? ["Accept", "anthropic-version", "x-api-key", "Content-Type"]
|
|
117
|
+
: ["Accept", "Authorization", "Content-Type"],
|
|
118
|
+
body: cloneProviderValue(body),
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
async #responses(request, onDelta, signal) {
|
|
51
122
|
return openAIResponsesRequest({
|
|
52
|
-
|
|
123
|
+
...responsesRequestInput(this.#connection, request),
|
|
53
124
|
fetchImplementation: this.#fetch,
|
|
54
|
-
input: [
|
|
55
|
-
...request.bootstrap.provider.messages.map(responsesBootstrapMessage),
|
|
56
|
-
...responsesAppend(request),
|
|
57
|
-
],
|
|
58
|
-
tools: providerToolDefinitions(request).map((tool) => ({
|
|
59
|
-
name: tool.name,
|
|
60
|
-
description: tool.description,
|
|
61
|
-
parameters: tool.inputSchema,
|
|
62
|
-
})),
|
|
63
|
-
allowedTools: providerAllowedTools(request),
|
|
64
|
-
toolStrategy: providerToolStrategy(request, this.#connection.provider),
|
|
65
|
-
hasFrozenToolPolicy: hasFrozenToolPolicy(request),
|
|
66
|
-
maxOutputTokens: request.maxOutputTokens,
|
|
67
125
|
tracePrefix: "responses",
|
|
68
126
|
...(this.#failureLog === undefined
|
|
69
127
|
? {}
|
|
70
128
|
: { failureLog: this.#failureLog }),
|
|
71
129
|
diagnosticContext: modelDiagnosticContext(request),
|
|
72
130
|
...(onDelta === undefined ? {} : { onDelta }),
|
|
131
|
+
...(signal === undefined ? {} : { signal }),
|
|
73
132
|
});
|
|
74
133
|
}
|
|
75
|
-
async #chat(request, onDelta) {
|
|
76
|
-
const
|
|
77
|
-
...request.bootstrap.provider.messages,
|
|
78
|
-
...chatAppend(request),
|
|
79
|
-
];
|
|
80
|
-
const tools = providerToolDefinitions(request);
|
|
81
|
-
const allowedTools = providerAllowedTools(request);
|
|
82
|
-
const hasPolicy = hasFrozenToolPolicy(request);
|
|
83
|
-
const body = {
|
|
84
|
-
model: this.#connection.modelId,
|
|
85
|
-
messages,
|
|
86
|
-
...(tools.length === 0
|
|
87
|
-
? {}
|
|
88
|
-
: {
|
|
89
|
-
tools: tools.map((tool) => ({
|
|
90
|
-
type: "function",
|
|
91
|
-
function: {
|
|
92
|
-
name: tool.name,
|
|
93
|
-
description: tool.description,
|
|
94
|
-
parameters: tool.inputSchema,
|
|
95
|
-
},
|
|
96
|
-
})),
|
|
97
|
-
}),
|
|
98
|
-
...(hasPolicy && allowedTools.length === 0
|
|
99
|
-
? { tool_choice: "none" }
|
|
100
|
-
: tools.length === 0
|
|
101
|
-
? {}
|
|
102
|
-
: { tool_choice: "auto" }),
|
|
103
|
-
max_tokens: request.maxOutputTokens,
|
|
104
|
-
stream: true,
|
|
105
|
-
stream_options: { include_usage: true },
|
|
106
|
-
};
|
|
134
|
+
async #chat(request, onDelta, signal) {
|
|
135
|
+
const body = chatRequestBody(this.#connection, request);
|
|
107
136
|
const bodyJson = JSON.stringify(body);
|
|
108
137
|
await trace("chat_request", body);
|
|
109
138
|
const url = providerUrl(this.#connection);
|
|
@@ -118,11 +147,12 @@ export class FileNativeModelHost {
|
|
|
118
147
|
"Content-Type": "application/json",
|
|
119
148
|
},
|
|
120
149
|
body: bodyJson,
|
|
150
|
+
...(signal === undefined ? {} : { signal }),
|
|
121
151
|
}, "Chat Completions"));
|
|
122
152
|
if (!response.ok)
|
|
123
153
|
throw await providerError(response);
|
|
124
154
|
if (isProviderEventStream(response)) {
|
|
125
|
-
const streamed = await providerStreamResult(response, "Chat Completions", (body) => aggregateChatModelStream(body, diagnosticDelta));
|
|
155
|
+
const streamed = await providerStreamResult(response, "Chat Completions", (body) => aggregateChatModelStream(body, diagnosticDelta), signal);
|
|
126
156
|
capture?.setReasoning(streamed.reasoningContent);
|
|
127
157
|
await trace("chat_response", streamed);
|
|
128
158
|
return {
|
|
@@ -135,6 +165,9 @@ export class FileNativeModelHost {
|
|
|
135
165
|
assistantMessage: cloneProviderValue(streamed.assistantMessage),
|
|
136
166
|
},
|
|
137
167
|
usage: normalizeChatUsage(streamed.usage),
|
|
168
|
+
...(streamed.stopReason === undefined
|
|
169
|
+
? {}
|
|
170
|
+
: { stopReason: streamed.stopReason }),
|
|
138
171
|
toolCalls: streamed.toolCalls.map((call) => ({
|
|
139
172
|
id: call.id,
|
|
140
173
|
name: call.name,
|
|
@@ -151,21 +184,14 @@ export class FileNativeModelHost {
|
|
|
151
184
|
const payload = rawPayload;
|
|
152
185
|
await trace("chat_response", payload);
|
|
153
186
|
const message = payload.choices?.[0]?.message;
|
|
187
|
+
const finishReason = payload.choices?.[0]?.finish_reason;
|
|
154
188
|
if (message === undefined)
|
|
155
189
|
throw unknownProviderResponse("Chat Completions");
|
|
156
|
-
if (
|
|
157
|
-
|
|
158
|
-
typeof
|
|
159
|
-
(
|
|
160
|
-
|
|
161
|
-
typeof message.reasoning_content !== "string") ||
|
|
162
|
-
(message.tool_calls !== undefined &&
|
|
163
|
-
(!Array.isArray(message.tool_calls) ||
|
|
164
|
-
message.tool_calls.some((call) => !isRecord(call) ||
|
|
165
|
-
typeof call.id !== "string" ||
|
|
166
|
-
!isRecord(call.function) ||
|
|
167
|
-
typeof call.function.name !== "string" ||
|
|
168
|
-
typeof call.function.arguments !== "string"))))
|
|
190
|
+
if (finishReason !== undefined &&
|
|
191
|
+
finishReason !== null &&
|
|
192
|
+
typeof finishReason !== "string")
|
|
193
|
+
throw unknownProviderResponse("Chat Completions");
|
|
194
|
+
if (!validChatAssistantMessage(message))
|
|
169
195
|
throw unknownProviderResponse("Chat Completions");
|
|
170
196
|
capture?.setReasoning(message.reasoning_content ?? undefined);
|
|
171
197
|
return {
|
|
@@ -178,6 +204,9 @@ export class FileNativeModelHost {
|
|
|
178
204
|
assistantMessage: cloneProviderValue(message),
|
|
179
205
|
},
|
|
180
206
|
usage: normalizeChatUsage(payload.usage),
|
|
207
|
+
...(typeof finishReason === "string"
|
|
208
|
+
? { stopReason: finishReason }
|
|
209
|
+
: {}),
|
|
181
210
|
toolCalls: (message.tool_calls ?? []).map((call) => ({
|
|
182
211
|
id: call.id,
|
|
183
212
|
name: call.function.name,
|
|
@@ -186,27 +215,8 @@ export class FileNativeModelHost {
|
|
|
186
215
|
};
|
|
187
216
|
});
|
|
188
217
|
}
|
|
189
|
-
async #anthropic(request, onDelta) {
|
|
190
|
-
const
|
|
191
|
-
const body = {
|
|
192
|
-
model: this.#connection.modelId,
|
|
193
|
-
system: request.bootstrap.provider.system,
|
|
194
|
-
messages: [
|
|
195
|
-
...request.bootstrap.provider.messages.filter(({ role }) => role !== "system"),
|
|
196
|
-
...anthropicAppend(request),
|
|
197
|
-
],
|
|
198
|
-
...(tools.length === 0
|
|
199
|
-
? {}
|
|
200
|
-
: {
|
|
201
|
-
tools: tools.map((tool) => ({
|
|
202
|
-
name: tool.name,
|
|
203
|
-
description: tool.description,
|
|
204
|
-
input_schema: tool.inputSchema,
|
|
205
|
-
})),
|
|
206
|
-
}),
|
|
207
|
-
max_tokens: request.maxOutputTokens,
|
|
208
|
-
stream: true,
|
|
209
|
-
};
|
|
218
|
+
async #anthropic(request, onDelta, signal) {
|
|
219
|
+
const body = anthropicRequestBody(this.#connection, request);
|
|
210
220
|
const bodyJson = JSON.stringify(body);
|
|
211
221
|
await trace("anthropic_request", body);
|
|
212
222
|
const url = providerUrl(this.#connection);
|
|
@@ -222,11 +232,12 @@ export class FileNativeModelHost {
|
|
|
222
232
|
"Content-Type": "application/json",
|
|
223
233
|
},
|
|
224
234
|
body: bodyJson,
|
|
235
|
+
...(signal === undefined ? {} : { signal }),
|
|
225
236
|
}, "Anthropic Messages"));
|
|
226
237
|
if (!response.ok)
|
|
227
238
|
throw await providerError(response);
|
|
228
239
|
if (isProviderEventStream(response)) {
|
|
229
|
-
const streamed = await providerStreamResult(response, "Anthropic Messages", (body) => aggregateAnthropicModelStream(body, diagnosticDelta));
|
|
240
|
+
const streamed = await providerStreamResult(response, "Anthropic Messages", (body) => aggregateAnthropicModelStream(body, diagnosticDelta), signal);
|
|
230
241
|
capture?.setReasoning(streamed.reasoningContent);
|
|
231
242
|
await trace("anthropic_response", streamed);
|
|
232
243
|
return {
|
|
@@ -253,6 +264,9 @@ export class FileNativeModelHost {
|
|
|
253
264
|
: { stopReason: streamed.stopReason }),
|
|
254
265
|
},
|
|
255
266
|
usage: normalizeAnthropicUsage(streamed.usage),
|
|
267
|
+
...(typeof streamed.stopReason === "string"
|
|
268
|
+
? { stopReason: streamed.stopReason }
|
|
269
|
+
: {}),
|
|
256
270
|
};
|
|
257
271
|
}
|
|
258
272
|
const rawPayload = await providerJson(response, "Anthropic Messages");
|
|
@@ -260,14 +274,7 @@ export class FileNativeModelHost {
|
|
|
260
274
|
throw unknownProviderResponse("Anthropic Messages");
|
|
261
275
|
const payload = rawPayload;
|
|
262
276
|
const content = payload.content;
|
|
263
|
-
if (
|
|
264
|
-
typeof item.type !== "string" ||
|
|
265
|
-
(item.type === "text" && typeof item.text !== "string") ||
|
|
266
|
-
(item.type === "thinking" && typeof item.thinking !== "string") ||
|
|
267
|
-
(item.type === "tool_use" &&
|
|
268
|
-
(typeof item.id !== "string" ||
|
|
269
|
-
typeof item.name !== "string" ||
|
|
270
|
-
!("input" in item)))))
|
|
277
|
+
if (!validAnthropicContent(content))
|
|
271
278
|
throw unknownProviderResponse("Anthropic Messages");
|
|
272
279
|
await trace("anthropic_response", payload);
|
|
273
280
|
const reasoningContent = content
|
|
@@ -298,10 +305,21 @@ export class FileNativeModelHost {
|
|
|
298
305
|
: { stopReason: payload.stop_reason }),
|
|
299
306
|
},
|
|
300
307
|
usage: normalizeAnthropicUsage(payload.usage),
|
|
308
|
+
...(typeof payload.stop_reason === "string"
|
|
309
|
+
? { stopReason: payload.stop_reason }
|
|
310
|
+
: {}),
|
|
301
311
|
};
|
|
302
312
|
});
|
|
303
313
|
}
|
|
304
314
|
}
|
|
315
|
+
function assertExchangeCompatibility(connection, request) {
|
|
316
|
+
if (request.bootstrap.provider.protocol !== connection.provider)
|
|
317
|
+
throw new ModelHostBindingMismatchError("The compiled prompt protocol does not match the selected model host");
|
|
318
|
+
if (request.bootstrap.cache.strategy !== modelCacheStrategy(connection))
|
|
319
|
+
throw new ModelHostBindingMismatchError("The compiled prompt cache policy does not match the selected model host");
|
|
320
|
+
if (request.maxOutputTokens > connection.maxOutputTokens)
|
|
321
|
+
throw new ModelHostBindingMismatchError("The request output limit exceeds the selected model host binding");
|
|
322
|
+
}
|
|
305
323
|
function modelHostTrace(request) {
|
|
306
324
|
return {
|
|
307
325
|
requestId: request.requestId,
|
|
@@ -376,6 +394,8 @@ async function capturedProviderOperation(failureLog, capture, operation) {
|
|
|
376
394
|
return { ...value, diagnostics: capture.snapshot() };
|
|
377
395
|
}
|
|
378
396
|
catch (error) {
|
|
397
|
+
if (error instanceof ModelHostCancelledError)
|
|
398
|
+
throw error;
|
|
379
399
|
if (capture !== undefined && failureLog !== undefined) {
|
|
380
400
|
const exchange = capture.snapshot();
|
|
381
401
|
const failure = {
|
|
@@ -420,7 +440,7 @@ export class FileNativeSettingAuthorProvider {
|
|
|
420
440
|
#locale;
|
|
421
441
|
#exchange = 0;
|
|
422
442
|
constructor(connection, fetchImplementation = fetch, diagnostics) {
|
|
423
|
-
this.#connection =
|
|
443
|
+
this.#connection = resolveConnection(connection);
|
|
424
444
|
this.#fetch = fetchImplementation;
|
|
425
445
|
this.#failureLog = diagnostics?.failureLog;
|
|
426
446
|
this.#operationId = diagnostics?.operationId;
|
|
@@ -441,7 +461,13 @@ export class FileNativeSettingAuthorProvider {
|
|
|
441
461
|
if (this.#connection.provider === "chat_completions") {
|
|
442
462
|
const body = {
|
|
443
463
|
model: this.#connection.modelId,
|
|
444
|
-
|
|
464
|
+
...chatReasoningRequest(this.#connection),
|
|
465
|
+
messages: chatSettingMessages(this.#connection, request.messages),
|
|
466
|
+
...(this.#connection.dialect === "cliproxyapi"
|
|
467
|
+
? {
|
|
468
|
+
prompt_cache_key: settingPromptCacheKey(this.#connection, request.messages, this.#operationId),
|
|
469
|
+
}
|
|
470
|
+
: {}),
|
|
445
471
|
...(tools.length === 0
|
|
446
472
|
? {}
|
|
447
473
|
: {
|
|
@@ -485,6 +511,10 @@ export class FileNativeSettingAuthorProvider {
|
|
|
485
511
|
...(streamed.reasoningContent === ""
|
|
486
512
|
? {}
|
|
487
513
|
: { reasoningContent: streamed.reasoningContent }),
|
|
514
|
+
providerState: {
|
|
515
|
+
protocol: "chat_completions",
|
|
516
|
+
assistantMessage: cloneProviderValue(streamed.assistantMessage),
|
|
517
|
+
},
|
|
488
518
|
toolCalls: streamed.toolCalls.map((call) => ({
|
|
489
519
|
id: call.id,
|
|
490
520
|
name: call.name,
|
|
@@ -498,13 +528,14 @@ export class FileNativeSettingAuthorProvider {
|
|
|
498
528
|
const response = await openAIResponsesRequest({
|
|
499
529
|
connection: this.#connection,
|
|
500
530
|
fetchImplementation: this.#fetch,
|
|
501
|
-
input: request.messages
|
|
531
|
+
input: responsesSettingInput(this.#connection, request.messages),
|
|
502
532
|
tools: tools.map((tool) => ({
|
|
503
533
|
name: tool.name,
|
|
504
534
|
description: tool.description,
|
|
505
535
|
parameters: tool.schema,
|
|
506
536
|
})),
|
|
507
537
|
maxOutputTokens: Math.min(request.maxOutputTokens, this.#connection.maxOutputTokens),
|
|
538
|
+
promptCacheKey: settingPromptCacheKey(this.#connection, request.messages, this.#operationId),
|
|
508
539
|
tracePrefix: "setting_responses",
|
|
509
540
|
...(this.#failureLog === undefined
|
|
510
541
|
? {}
|
|
@@ -515,6 +546,9 @@ export class FileNativeSettingAuthorProvider {
|
|
|
515
546
|
return {
|
|
516
547
|
role: "assistant",
|
|
517
548
|
content: response.text ?? "",
|
|
549
|
+
...(response.reasoningContent === undefined
|
|
550
|
+
? {}
|
|
551
|
+
: { reasoningContent: response.reasoningContent }),
|
|
518
552
|
providerState: response.providerState,
|
|
519
553
|
toolCalls: (response.toolCalls ?? []).map((call) => ({
|
|
520
554
|
...call,
|
|
@@ -528,14 +562,18 @@ export class FileNativeSettingAuthorProvider {
|
|
|
528
562
|
}
|
|
529
563
|
const system = request.messages
|
|
530
564
|
.filter(({ role }) => role === "system")
|
|
531
|
-
.map(({ content }) =>
|
|
532
|
-
|
|
565
|
+
.map(({ content }, index, messages) => ({
|
|
566
|
+
type: "text",
|
|
567
|
+
text: content,
|
|
568
|
+
...(index === messages.length - 1
|
|
569
|
+
? { cache_control: { type: "ephemeral" } }
|
|
570
|
+
: {}),
|
|
571
|
+
}));
|
|
533
572
|
const body = {
|
|
534
573
|
model: this.#connection.modelId,
|
|
574
|
+
...anthropicReasoningRequest(this.#connection),
|
|
535
575
|
...(system.length === 0 ? {} : { system }),
|
|
536
|
-
messages: request.messages
|
|
537
|
-
.filter(({ role }) => role !== "system")
|
|
538
|
-
.map(anthropicSettingMessage),
|
|
576
|
+
messages: anthropicSettingMessages(request.messages),
|
|
539
577
|
...(tools.length === 0
|
|
540
578
|
? {}
|
|
541
579
|
: {
|
|
@@ -576,25 +614,41 @@ export class FileNativeSettingAuthorProvider {
|
|
|
576
614
|
...(streamed.reasoningContent === ""
|
|
577
615
|
? {}
|
|
578
616
|
: { reasoningContent: streamed.reasoningContent }),
|
|
617
|
+
providerState: {
|
|
618
|
+
protocol: "anthropic_messages",
|
|
619
|
+
content: cloneProviderValue(streamed.providerContent),
|
|
620
|
+
...(streamed.responseId === undefined
|
|
621
|
+
? {}
|
|
622
|
+
: { responseId: streamed.responseId }),
|
|
623
|
+
...(streamed.model === undefined ? {} : { model: streamed.model }),
|
|
624
|
+
...(streamed.stopReason === undefined
|
|
625
|
+
? {}
|
|
626
|
+
: { stopReason: streamed.stopReason }),
|
|
627
|
+
},
|
|
579
628
|
toolCalls: streamed.toolCalls.map((call) => ({
|
|
580
629
|
id: call.id,
|
|
581
630
|
name: call.name,
|
|
582
|
-
arguments: asArguments(
|
|
631
|
+
arguments: asArguments(structuredClone(call.arguments)),
|
|
583
632
|
})),
|
|
584
633
|
usage: settingAuthorUsage(normalizeAnthropicUsage(streamed.usage)),
|
|
585
634
|
};
|
|
586
635
|
});
|
|
587
636
|
}
|
|
588
637
|
}
|
|
589
|
-
//
|
|
590
|
-
//
|
|
638
|
+
// Setting authoring uses the same complete usage projection as play. Missing
|
|
639
|
+
// Provider usage stays unavailable instead of being counted as zero.
|
|
591
640
|
function settingAuthorUsage(usage) {
|
|
592
|
-
if (usage
|
|
593
|
-
return
|
|
594
|
-
return {
|
|
595
|
-
inputTokens:
|
|
596
|
-
|
|
597
|
-
|
|
641
|
+
if (usage !== undefined)
|
|
642
|
+
return structuredClone(usage);
|
|
643
|
+
return usageRecord({
|
|
644
|
+
inputTokens: null,
|
|
645
|
+
uncachedInputTokens: null,
|
|
646
|
+
cacheReadTokens: null,
|
|
647
|
+
cacheWriteTokens: null,
|
|
648
|
+
reasoningTokens: null,
|
|
649
|
+
outputTokens: null,
|
|
650
|
+
totalTokens: null,
|
|
651
|
+
});
|
|
598
652
|
}
|
|
599
653
|
function settingToolDefinition(name, locale = defaultAppLocale) {
|
|
600
654
|
const descriptions = settingToolDescriptions[locale];
|
|
@@ -698,6 +752,126 @@ const settingToolDescriptions = {
|
|
|
698
752
|
function items(request) {
|
|
699
753
|
return request.appended;
|
|
700
754
|
}
|
|
755
|
+
function chatRequestBody(connection, request) {
|
|
756
|
+
const tools = providerToolDefinitions(request);
|
|
757
|
+
const allowedTools = providerAllowedTools(request);
|
|
758
|
+
const hasPolicy = hasFrozenToolPolicy(request);
|
|
759
|
+
return {
|
|
760
|
+
model: connection.modelId,
|
|
761
|
+
...chatReasoningRequest(connection),
|
|
762
|
+
messages: [
|
|
763
|
+
...chatBootstrapMessages(connection, request),
|
|
764
|
+
...chatAppend(request),
|
|
765
|
+
],
|
|
766
|
+
...(connection.dialect === "cliproxyapi"
|
|
767
|
+
? { prompt_cache_key: promptCacheKey(connection, request) }
|
|
768
|
+
: {}),
|
|
769
|
+
...(tools.length === 0
|
|
770
|
+
? {}
|
|
771
|
+
: {
|
|
772
|
+
tools: tools.map((tool) => ({
|
|
773
|
+
type: "function",
|
|
774
|
+
function: {
|
|
775
|
+
name: tool.name,
|
|
776
|
+
description: tool.description,
|
|
777
|
+
parameters: tool.inputSchema,
|
|
778
|
+
},
|
|
779
|
+
})),
|
|
780
|
+
}),
|
|
781
|
+
...(hasPolicy && allowedTools.length === 0
|
|
782
|
+
? { tool_choice: "none" }
|
|
783
|
+
: tools.length === 0
|
|
784
|
+
? {}
|
|
785
|
+
: { tool_choice: "auto" }),
|
|
786
|
+
max_tokens: request.maxOutputTokens,
|
|
787
|
+
stream: true,
|
|
788
|
+
stream_options: { include_usage: true },
|
|
789
|
+
};
|
|
790
|
+
}
|
|
791
|
+
function anthropicRequestBody(connection, request) {
|
|
792
|
+
const tools = providerToolDefinitions(request);
|
|
793
|
+
return {
|
|
794
|
+
model: connection.modelId,
|
|
795
|
+
...anthropicReasoningRequest(connection),
|
|
796
|
+
system: request.bootstrap.provider.system,
|
|
797
|
+
messages: [
|
|
798
|
+
...request.bootstrap.provider.messages.filter(({ role }) => role !== "system"),
|
|
799
|
+
...anthropicAppend(request),
|
|
800
|
+
],
|
|
801
|
+
...(tools.length === 0
|
|
802
|
+
? {}
|
|
803
|
+
: {
|
|
804
|
+
tools: tools.map((tool) => ({
|
|
805
|
+
name: tool.name,
|
|
806
|
+
description: tool.description,
|
|
807
|
+
input_schema: tool.inputSchema,
|
|
808
|
+
})),
|
|
809
|
+
}),
|
|
810
|
+
...(hasFrozenToolPolicy(request) &&
|
|
811
|
+
providerAllowedTools(request).length === 0
|
|
812
|
+
? { tool_choice: { type: "none" } }
|
|
813
|
+
: {}),
|
|
814
|
+
max_tokens: request.maxOutputTokens,
|
|
815
|
+
stream: true,
|
|
816
|
+
};
|
|
817
|
+
}
|
|
818
|
+
function responsesRequestInput(connection, request) {
|
|
819
|
+
return {
|
|
820
|
+
connection,
|
|
821
|
+
input: [
|
|
822
|
+
...responsesBootstrapMessages(connection, request),
|
|
823
|
+
...responsesAppend(request),
|
|
824
|
+
],
|
|
825
|
+
tools: providerToolDefinitions(request).map((tool) => ({
|
|
826
|
+
name: tool.name,
|
|
827
|
+
description: tool.description,
|
|
828
|
+
parameters: tool.inputSchema,
|
|
829
|
+
})),
|
|
830
|
+
allowedTools: providerAllowedTools(request),
|
|
831
|
+
toolStrategy: providerToolStrategy(request, connection.provider),
|
|
832
|
+
hasFrozenToolPolicy: hasFrozenToolPolicy(request),
|
|
833
|
+
maxOutputTokens: request.maxOutputTokens,
|
|
834
|
+
promptCacheKey: promptCacheKey(connection, request),
|
|
835
|
+
};
|
|
836
|
+
}
|
|
837
|
+
function responsesRequestBody(input) {
|
|
838
|
+
const allowedTools = input.allowedTools ?? input.tools.map((tool) => tool.name);
|
|
839
|
+
const toolChoice = input.hasFrozenToolPolicy === true && allowedTools.length === 0
|
|
840
|
+
? "none"
|
|
841
|
+
: input.hasFrozenToolPolicy === true &&
|
|
842
|
+
input.toolStrategy === "native_allowed_subset"
|
|
843
|
+
? {
|
|
844
|
+
type: "allowed_tools",
|
|
845
|
+
mode: "auto",
|
|
846
|
+
tools: allowedTools.map((name) => ({ type: "function", name })),
|
|
847
|
+
}
|
|
848
|
+
: "auto";
|
|
849
|
+
return {
|
|
850
|
+
model: input.connection.modelId,
|
|
851
|
+
...responsesReasoningRequest(resolveConnection(input.connection)),
|
|
852
|
+
input: input.input,
|
|
853
|
+
...(input.tools.length === 0
|
|
854
|
+
? input.hasFrozenToolPolicy === true && allowedTools.length === 0
|
|
855
|
+
? { tool_choice: "none" }
|
|
856
|
+
: {}
|
|
857
|
+
: {
|
|
858
|
+
tools: input.tools.map((tool) => ({
|
|
859
|
+
type: "function",
|
|
860
|
+
name: tool.name,
|
|
861
|
+
description: tool.description,
|
|
862
|
+
parameters: tool.parameters,
|
|
863
|
+
})),
|
|
864
|
+
tool_choice: toolChoice,
|
|
865
|
+
}),
|
|
866
|
+
max_output_tokens: input.maxOutputTokens,
|
|
867
|
+
...(input.promptCacheKey === undefined
|
|
868
|
+
? {}
|
|
869
|
+
: { prompt_cache_key: input.promptCacheKey }),
|
|
870
|
+
store: false,
|
|
871
|
+
include: ["reasoning.encrypted_content"],
|
|
872
|
+
stream: true,
|
|
873
|
+
};
|
|
874
|
+
}
|
|
701
875
|
/**
|
|
702
876
|
* A frozen policy is present when the caller supplies the logical operation's
|
|
703
877
|
* complete tool universe. All play lifecycles share this one ModelHost seam.
|
|
@@ -712,7 +886,26 @@ function frozenToolPolicy(request) {
|
|
|
712
886
|
}
|
|
713
887
|
function providerToolDefinitions(request) {
|
|
714
888
|
const policy = frozenToolPolicy(request);
|
|
715
|
-
return policy?.toolUniverse ?? request.tools
|
|
889
|
+
return (policy?.toolUniverse ?? request.tools).map((tool) => {
|
|
890
|
+
if (!hasUnsupportedProviderToolSchemaRoot(tool.inputSchema))
|
|
891
|
+
return tool;
|
|
892
|
+
const portableInputSchema = portableRuntimeToolInputSchema(tool.name);
|
|
893
|
+
if (portableInputSchema === null)
|
|
894
|
+
throw new Error(`Provider tool ${tool.name} uses oneOf, allOf, or anyOf at the input schema root`);
|
|
895
|
+
if (hasUnsupportedProviderToolSchemaRoot(portableInputSchema))
|
|
896
|
+
throw new Error(`Provider tool ${tool.name} has no portable root-object input schema`);
|
|
897
|
+
// Released play contexts freeze their logical tool universe. Migrate only
|
|
898
|
+
// the Provider wire projection of the old root-union schema; Runtime keeps
|
|
899
|
+
// validating the original source-specific contract and the persisted
|
|
900
|
+
// transcript remains untouched.
|
|
901
|
+
return {
|
|
902
|
+
...tool,
|
|
903
|
+
inputSchema: portableInputSchema,
|
|
904
|
+
};
|
|
905
|
+
});
|
|
906
|
+
}
|
|
907
|
+
function hasUnsupportedProviderToolSchemaRoot(schema) {
|
|
908
|
+
return ["oneOf", "allOf", "anyOf"].some((keyword) => Object.prototype.hasOwnProperty.call(schema, keyword));
|
|
716
909
|
}
|
|
717
910
|
function providerAllowedTools(request) {
|
|
718
911
|
const policy = frozenToolPolicy(request);
|
|
@@ -726,82 +919,127 @@ function providerToolStrategy(request, provider) {
|
|
|
726
919
|
return policy.toolStrategy;
|
|
727
920
|
return defaultRuntimeToolDefinitionStrategy(provider);
|
|
728
921
|
}
|
|
922
|
+
function chatBootstrapMessages(connection, request) {
|
|
923
|
+
const messages = request.bootstrap.provider.messages.map((message) => cloneProviderValue(message));
|
|
924
|
+
return connection.dialect === "cliproxyapi"
|
|
925
|
+
? attachCacheControlToLastRole(messages, "system")
|
|
926
|
+
: messages;
|
|
927
|
+
}
|
|
928
|
+
function responsesBootstrapMessages(connection, request) {
|
|
929
|
+
const messages = request.bootstrap.provider.messages.map((message) => responsesBootstrapMessage(message, connection.dialect === "cliproxyapi"));
|
|
930
|
+
return connection.dialect === "cliproxyapi"
|
|
931
|
+
? attachCacheControlToLastRole(messages, "system")
|
|
932
|
+
: messages;
|
|
933
|
+
}
|
|
934
|
+
function attachCacheControlToLastRole(messages, role) {
|
|
935
|
+
const result = cloneProviderValue(messages);
|
|
936
|
+
let target = -1;
|
|
937
|
+
for (const [index, message] of result.entries())
|
|
938
|
+
if (isRecord(message) && message.role === role)
|
|
939
|
+
target = index;
|
|
940
|
+
if (target < 0)
|
|
941
|
+
return result;
|
|
942
|
+
const message = result[target];
|
|
943
|
+
if (!isRecord(message))
|
|
944
|
+
return result;
|
|
945
|
+
result[target] = {
|
|
946
|
+
...message,
|
|
947
|
+
cache_control: { type: "ephemeral" },
|
|
948
|
+
};
|
|
949
|
+
return result;
|
|
950
|
+
}
|
|
951
|
+
function promptCacheKey(connection, request) {
|
|
952
|
+
const stablePrefixScope = request.bootstrap.cache.stablePrefixFingerprint ??
|
|
953
|
+
createHash("sha256")
|
|
954
|
+
.update(JSON.stringify({
|
|
955
|
+
provider: request.bootstrap.provider,
|
|
956
|
+
tools: request.toolUniverse ?? request.tools,
|
|
957
|
+
toolStrategy: request.toolStrategy,
|
|
958
|
+
}))
|
|
959
|
+
.digest("hex");
|
|
960
|
+
// OpenAI uses this as a cache-routing hint, so equal frozen prefixes should
|
|
961
|
+
// share it. CLIProxyAPI additionally treats it as execution-session identity;
|
|
962
|
+
// there it must stay stable within one chain and isolated across chains.
|
|
963
|
+
const scope = connection.dialect === "cliproxyapi"
|
|
964
|
+
? (request.operationId ?? stablePrefixScope)
|
|
965
|
+
: stablePrefixScope;
|
|
966
|
+
return createHash("sha256")
|
|
967
|
+
.update(`${connection.provider}\0${connection.dialect}\0${connection.modelId}\0${scope}`)
|
|
968
|
+
.digest("hex");
|
|
969
|
+
}
|
|
970
|
+
function settingPromptCacheKey(connection, messages, operationId) {
|
|
971
|
+
const firstAssistant = messages.findIndex(({ role }) => role === "assistant");
|
|
972
|
+
const initialPrefix = messages.slice(0, firstAssistant < 0 ? messages.length : firstAssistant);
|
|
973
|
+
const stablePrefixScope = JSON.stringify(initialPrefix);
|
|
974
|
+
const scope = connection.dialect === "cliproxyapi"
|
|
975
|
+
? (operationId ?? stablePrefixScope)
|
|
976
|
+
: stablePrefixScope;
|
|
977
|
+
return createHash("sha256")
|
|
978
|
+
.update(`${connection.provider}\0${connection.dialect}\0${connection.modelId}\0${scope}`)
|
|
979
|
+
.digest("hex");
|
|
980
|
+
}
|
|
729
981
|
function chatAppend(request) {
|
|
730
982
|
return items(request).flatMap((item) => {
|
|
731
983
|
if (item.kind === "player")
|
|
732
|
-
return { role: "user", content: item.text };
|
|
984
|
+
return [{ role: "user", content: item.text }];
|
|
733
985
|
if (item.kind === "prompt_delta")
|
|
734
986
|
return promptDeltaProviderMessages(item.logicalMessages);
|
|
735
987
|
if (item.kind === "tool")
|
|
736
|
-
return
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
988
|
+
return [
|
|
989
|
+
{
|
|
990
|
+
role: "tool",
|
|
991
|
+
tool_call_id: item.toolCallId,
|
|
992
|
+
content: item.markdown,
|
|
993
|
+
},
|
|
994
|
+
];
|
|
741
995
|
if (item.providerState?.protocol === "chat_completions") {
|
|
742
996
|
const message = item.providerState.assistantMessage;
|
|
743
|
-
if (
|
|
997
|
+
if (validChatAssistantMessage(message))
|
|
744
998
|
return [cloneProviderValue(message)];
|
|
745
999
|
}
|
|
746
|
-
|
|
747
|
-
role: "assistant",
|
|
748
|
-
content: item.text,
|
|
749
|
-
...(item.reasoningContent === undefined
|
|
750
|
-
? {}
|
|
751
|
-
: { reasoning_content: item.reasoningContent }),
|
|
752
|
-
...(item.toolCalls.length === 0
|
|
753
|
-
? {}
|
|
754
|
-
: {
|
|
755
|
-
tool_calls: item.toolCalls.map((call) => ({
|
|
756
|
-
id: call.id,
|
|
757
|
-
type: "function",
|
|
758
|
-
function: {
|
|
759
|
-
name: call.name,
|
|
760
|
-
arguments: JSON.stringify(call.arguments),
|
|
761
|
-
},
|
|
762
|
-
})),
|
|
763
|
-
}),
|
|
764
|
-
};
|
|
765
|
-
return assistant;
|
|
1000
|
+
throw continuationError("Chat Completions");
|
|
766
1001
|
});
|
|
767
1002
|
}
|
|
768
1003
|
function anthropicAppend(request) {
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
if (
|
|
773
|
-
return
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
};
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
}
|
|
804
|
-
|
|
1004
|
+
const messages = [];
|
|
1005
|
+
let toolResults = [];
|
|
1006
|
+
const flushToolResults = () => {
|
|
1007
|
+
if (toolResults.length === 0)
|
|
1008
|
+
return;
|
|
1009
|
+
messages.push({ role: "user", content: toolResults });
|
|
1010
|
+
toolResults = [];
|
|
1011
|
+
};
|
|
1012
|
+
for (const item of items(request)) {
|
|
1013
|
+
if (item.kind === "tool") {
|
|
1014
|
+
toolResults.push({
|
|
1015
|
+
type: "tool_result",
|
|
1016
|
+
tool_use_id: item.toolCallId,
|
|
1017
|
+
content: item.markdown,
|
|
1018
|
+
...(item.isError === true ? { is_error: true } : {}),
|
|
1019
|
+
});
|
|
1020
|
+
continue;
|
|
1021
|
+
}
|
|
1022
|
+
flushToolResults();
|
|
1023
|
+
if (item.kind === "player") {
|
|
1024
|
+
messages.push({ role: "user", content: item.text });
|
|
1025
|
+
continue;
|
|
1026
|
+
}
|
|
1027
|
+
if (item.kind === "prompt_delta") {
|
|
1028
|
+
messages.push(...promptDeltaProviderMessages(item.logicalMessages));
|
|
1029
|
+
continue;
|
|
1030
|
+
}
|
|
1031
|
+
if (item.providerState?.protocol === "anthropic_messages" &&
|
|
1032
|
+
validAnthropicContent(item.providerState.content)) {
|
|
1033
|
+
messages.push({
|
|
1034
|
+
role: "assistant",
|
|
1035
|
+
content: cloneProviderValue(item.providerState.content),
|
|
1036
|
+
});
|
|
1037
|
+
continue;
|
|
1038
|
+
}
|
|
1039
|
+
throw continuationError("Anthropic Messages");
|
|
1040
|
+
}
|
|
1041
|
+
flushToolResults();
|
|
1042
|
+
return messages;
|
|
805
1043
|
}
|
|
806
1044
|
function responsesAppend(request) {
|
|
807
1045
|
return items(request).flatMap((item) => {
|
|
@@ -817,9 +1055,10 @@ function responsesAppend(request) {
|
|
|
817
1055
|
output: item.markdown,
|
|
818
1056
|
},
|
|
819
1057
|
];
|
|
820
|
-
if (item.providerState?.protocol === "openai_responses"
|
|
1058
|
+
if (item.providerState?.protocol === "openai_responses" &&
|
|
1059
|
+
validResponsesOutput(item.providerState.output))
|
|
821
1060
|
return cloneResponseOutput(item.providerState.output);
|
|
822
|
-
|
|
1061
|
+
throw continuationError("OpenAI Responses");
|
|
823
1062
|
});
|
|
824
1063
|
}
|
|
825
1064
|
function promptDeltaProviderMessages(messages) {
|
|
@@ -828,6 +1067,77 @@ function promptDeltaProviderMessages(messages) {
|
|
|
828
1067
|
content: renderPromptDeltaMessage(message.role, message.markdown),
|
|
829
1068
|
}));
|
|
830
1069
|
}
|
|
1070
|
+
function chatReasoningRequest(connection) {
|
|
1071
|
+
return {
|
|
1072
|
+
...(connection.reasoningEffort === "provider_default"
|
|
1073
|
+
? {}
|
|
1074
|
+
: { reasoning_effort: connection.reasoningEffort }),
|
|
1075
|
+
...(connection.dialect !== "cliproxyapi" ||
|
|
1076
|
+
connection.reasoningSummary === "provider_default"
|
|
1077
|
+
? {}
|
|
1078
|
+
: {
|
|
1079
|
+
// Chat Completions has no standard summary switch. CLIProxyAPI
|
|
1080
|
+
// deliberately accepts the OpenRouter-compatible visibility bit and
|
|
1081
|
+
// maps it to the target protocol without changing effort.
|
|
1082
|
+
reasoning: {
|
|
1083
|
+
exclude: connection.reasoningSummary === "none",
|
|
1084
|
+
},
|
|
1085
|
+
}),
|
|
1086
|
+
};
|
|
1087
|
+
}
|
|
1088
|
+
function responsesReasoningRequest(connection) {
|
|
1089
|
+
const reasoning = {
|
|
1090
|
+
...(connection.reasoningEffort === "provider_default"
|
|
1091
|
+
? {}
|
|
1092
|
+
: { effort: connection.reasoningEffort }),
|
|
1093
|
+
...(connection.reasoningSummary === "provider_default" ||
|
|
1094
|
+
(connection.reasoningSummary === "none" &&
|
|
1095
|
+
connection.dialect === "standard")
|
|
1096
|
+
? {}
|
|
1097
|
+
: {
|
|
1098
|
+
summary: connection.reasoningSummary === "none"
|
|
1099
|
+
? null
|
|
1100
|
+
: connection.reasoningSummary,
|
|
1101
|
+
}),
|
|
1102
|
+
};
|
|
1103
|
+
return Object.keys(reasoning).length === 0 ? {} : { reasoning };
|
|
1104
|
+
}
|
|
1105
|
+
function anthropicReasoningRequest(connection) {
|
|
1106
|
+
const display = connection.reasoningSummary === "provider_default"
|
|
1107
|
+
? {}
|
|
1108
|
+
: {
|
|
1109
|
+
display: connection.reasoningSummary === "auto" ? "summarized" : "omitted",
|
|
1110
|
+
};
|
|
1111
|
+
const thinking = connection.thinkingMode === "provider_default"
|
|
1112
|
+
? connection.dialect === "cliproxyapi" &&
|
|
1113
|
+
hasCLIProxyThinkingSuffix(connection.modelId) &&
|
|
1114
|
+
connection.reasoningSummary !== "provider_default"
|
|
1115
|
+
? {
|
|
1116
|
+
// CLIProxyAPI extracts visibility from a valid source thinking
|
|
1117
|
+
// block before the model suffix replaces its mode. The suffix is
|
|
1118
|
+
// still the sole mode/effort authority; adaptive is only the
|
|
1119
|
+
// proxy's protocol-valid summary carrier.
|
|
1120
|
+
thinking: { type: "adaptive", ...display },
|
|
1121
|
+
}
|
|
1122
|
+
: {}
|
|
1123
|
+
: connection.thinkingMode === "disabled"
|
|
1124
|
+
? { thinking: { type: "disabled" } }
|
|
1125
|
+
: connection.thinkingMode === "adaptive"
|
|
1126
|
+
? { thinking: { type: "adaptive", ...display } }
|
|
1127
|
+
: {
|
|
1128
|
+
thinking: {
|
|
1129
|
+
type: "enabled",
|
|
1130
|
+
budget_tokens: connection.thinkingBudgetTokens,
|
|
1131
|
+
...display,
|
|
1132
|
+
},
|
|
1133
|
+
};
|
|
1134
|
+
return {
|
|
1135
|
+
...thinking,
|
|
1136
|
+
...(connection.reasoningEffort === "provider_default"
|
|
1137
|
+
? {}
|
|
1138
|
+
: { output_config: { effort: connection.reasoningEffort } }),
|
|
1139
|
+
};
|
|
1140
|
+
}
|
|
831
1141
|
function providerUrl(connection) {
|
|
832
1142
|
const url = new URL(connection.baseUrl);
|
|
833
1143
|
const suffix = connection.provider === "chat_completions"
|
|
@@ -863,6 +1173,12 @@ async function providerError(response) {
|
|
|
863
1173
|
});
|
|
864
1174
|
return new ModelHostFailureError(`Provider request failed: ${response.status} ${text}`);
|
|
865
1175
|
}
|
|
1176
|
+
function chatSettingMessages(connection, messages) {
|
|
1177
|
+
const translated = messages.map(chatSettingMessage);
|
|
1178
|
+
if (connection.dialect !== "cliproxyapi")
|
|
1179
|
+
return translated;
|
|
1180
|
+
return attachCacheControlAtIndex(attachCacheControlToLastRole(translated, "system"), settingPrefixUserIndex(messages));
|
|
1181
|
+
}
|
|
866
1182
|
function chatSettingMessage(message) {
|
|
867
1183
|
if (message.role === "tool")
|
|
868
1184
|
return {
|
|
@@ -870,58 +1186,91 @@ function chatSettingMessage(message) {
|
|
|
870
1186
|
tool_call_id: message.toolCallId,
|
|
871
1187
|
content: message.content,
|
|
872
1188
|
};
|
|
1189
|
+
if (message.role === "assistant") {
|
|
1190
|
+
const state = message.providerState;
|
|
1191
|
+
if (state?.protocol === "chat_completions" &&
|
|
1192
|
+
validChatAssistantMessage(state.assistantMessage))
|
|
1193
|
+
return cloneProviderValue(state.assistantMessage);
|
|
1194
|
+
throw continuationError("Chat Completions setting improvement");
|
|
1195
|
+
}
|
|
873
1196
|
return {
|
|
874
|
-
role: message.role === "
|
|
875
|
-
? "assistant"
|
|
876
|
-
: message.role === "system"
|
|
877
|
-
? "system"
|
|
878
|
-
: "user",
|
|
1197
|
+
role: message.role === "system" ? "system" : "user",
|
|
879
1198
|
content: message.content,
|
|
880
|
-
...(message.role !== "assistant" || message.reasoningContent === undefined
|
|
881
|
-
? {}
|
|
882
|
-
: { reasoning_content: message.reasoningContent }),
|
|
883
|
-
...(message.role !== "assistant" || (message.toolCalls?.length ?? 0) === 0
|
|
884
|
-
? {}
|
|
885
|
-
: {
|
|
886
|
-
tool_calls: message.toolCalls.map((call) => ({
|
|
887
|
-
id: call.id,
|
|
888
|
-
type: "function",
|
|
889
|
-
function: {
|
|
890
|
-
name: call.name,
|
|
891
|
-
arguments: JSON.stringify(call.arguments),
|
|
892
|
-
},
|
|
893
|
-
})),
|
|
894
|
-
}),
|
|
895
1199
|
};
|
|
896
1200
|
}
|
|
897
|
-
function
|
|
898
|
-
|
|
899
|
-
|
|
1201
|
+
function anthropicSettingMessages(source) {
|
|
1202
|
+
const messages = [];
|
|
1203
|
+
const cacheableUserIndex = settingPrefixUserIndex(source);
|
|
1204
|
+
let toolResults = [];
|
|
1205
|
+
const flushToolResults = () => {
|
|
1206
|
+
if (toolResults.length === 0)
|
|
1207
|
+
return;
|
|
1208
|
+
messages.push({ role: "user", content: toolResults });
|
|
1209
|
+
toolResults = [];
|
|
1210
|
+
};
|
|
1211
|
+
for (const [index, message] of source.entries()) {
|
|
1212
|
+
if (message.role === "system")
|
|
1213
|
+
continue;
|
|
1214
|
+
if (message.role === "tool") {
|
|
1215
|
+
toolResults.push({
|
|
1216
|
+
type: "tool_result",
|
|
1217
|
+
tool_use_id: message.toolCallId,
|
|
1218
|
+
content: message.content,
|
|
1219
|
+
...(message.isError === true ? { is_error: true } : {}),
|
|
1220
|
+
});
|
|
1221
|
+
continue;
|
|
1222
|
+
}
|
|
1223
|
+
flushToolResults();
|
|
1224
|
+
if (message.role === "assistant") {
|
|
1225
|
+
if (message.providerState?.protocol !== "anthropic_messages" ||
|
|
1226
|
+
!validAnthropicContent(message.providerState.content))
|
|
1227
|
+
throw continuationError("Anthropic Messages setting improvement");
|
|
1228
|
+
messages.push({
|
|
1229
|
+
role: "assistant",
|
|
1230
|
+
content: cloneProviderValue(message.providerState.content),
|
|
1231
|
+
});
|
|
1232
|
+
continue;
|
|
1233
|
+
}
|
|
1234
|
+
messages.push({
|
|
900
1235
|
role: "user",
|
|
901
|
-
content:
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
1236
|
+
content: index === cacheableUserIndex
|
|
1237
|
+
? [
|
|
1238
|
+
{
|
|
1239
|
+
type: "text",
|
|
1240
|
+
text: message.content,
|
|
1241
|
+
cache_control: { type: "ephemeral" },
|
|
1242
|
+
},
|
|
1243
|
+
]
|
|
1244
|
+
: message.content,
|
|
1245
|
+
});
|
|
1246
|
+
}
|
|
1247
|
+
flushToolResults();
|
|
1248
|
+
return messages;
|
|
1249
|
+
}
|
|
1250
|
+
function responsesSettingInput(connection, messages) {
|
|
1251
|
+
const translated = messages.flatMap(responsesSettingMessage);
|
|
1252
|
+
if (connection.dialect !== "cliproxyapi")
|
|
1253
|
+
return translated;
|
|
1254
|
+
return attachCacheControlAtIndex(attachCacheControlToLastRole(translated, "system"), settingPrefixUserIndex(messages));
|
|
1255
|
+
}
|
|
1256
|
+
function settingPrefixUserIndex(messages) {
|
|
1257
|
+
const firstAssistant = messages.findIndex(({ role }) => role === "assistant");
|
|
1258
|
+
const end = firstAssistant < 0 ? messages.length : firstAssistant;
|
|
1259
|
+
let target = -1;
|
|
1260
|
+
for (let index = 0; index < end; index += 1)
|
|
1261
|
+
if (messages[index]?.role === "user")
|
|
1262
|
+
target = index;
|
|
1263
|
+
return target;
|
|
1264
|
+
}
|
|
1265
|
+
function attachCacheControlAtIndex(messages, index) {
|
|
1266
|
+
const result = cloneProviderValue(messages);
|
|
1267
|
+
if (index < 0 || !isRecord(result[index]))
|
|
1268
|
+
return result;
|
|
1269
|
+
result[index] = {
|
|
1270
|
+
...result[index],
|
|
1271
|
+
cache_control: { type: "ephemeral" },
|
|
1272
|
+
};
|
|
1273
|
+
return result;
|
|
925
1274
|
}
|
|
926
1275
|
function responsesSettingMessage(message) {
|
|
927
1276
|
if (message.role === "tool")
|
|
@@ -933,14 +1282,32 @@ function responsesSettingMessage(message) {
|
|
|
933
1282
|
},
|
|
934
1283
|
];
|
|
935
1284
|
if (message.role === "assistant" &&
|
|
936
|
-
message.providerState?.protocol === "openai_responses"
|
|
1285
|
+
message.providerState?.protocol === "openai_responses" &&
|
|
1286
|
+
validResponsesOutput(message.providerState.output))
|
|
937
1287
|
return cloneResponseOutput(message.providerState.output);
|
|
938
1288
|
if (message.role === "assistant")
|
|
939
|
-
|
|
1289
|
+
throw continuationError("OpenAI Responses setting improvement");
|
|
940
1290
|
return [{ role: message.role, content: message.content }];
|
|
941
1291
|
}
|
|
942
|
-
function responsesBootstrapMessage(message) {
|
|
943
|
-
return {
|
|
1292
|
+
function responsesBootstrapMessage(message, preserveCacheControl = false) {
|
|
1293
|
+
return {
|
|
1294
|
+
role: message.role,
|
|
1295
|
+
content: preserveCacheControl && Array.isArray(message.content)
|
|
1296
|
+
? message.content.flatMap((item) => {
|
|
1297
|
+
if (!isRecord(item) || typeof item.text !== "string")
|
|
1298
|
+
return [];
|
|
1299
|
+
return [
|
|
1300
|
+
{
|
|
1301
|
+
type: "input_text",
|
|
1302
|
+
text: item.text,
|
|
1303
|
+
...(isRecord(item.cache_control)
|
|
1304
|
+
? { cache_control: cloneProviderValue(item.cache_control) }
|
|
1305
|
+
: {}),
|
|
1306
|
+
},
|
|
1307
|
+
];
|
|
1308
|
+
})
|
|
1309
|
+
: providerText(message.content),
|
|
1310
|
+
};
|
|
944
1311
|
}
|
|
945
1312
|
function providerText(content) {
|
|
946
1313
|
if (typeof content === "string")
|
|
@@ -953,50 +1320,71 @@ function providerText(content) {
|
|
|
953
1320
|
}
|
|
954
1321
|
return JSON.stringify(content) ?? "";
|
|
955
1322
|
}
|
|
956
|
-
function
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
1323
|
+
function validChatAssistantMessage(value) {
|
|
1324
|
+
if (!isRecord(value) || value.role !== "assistant")
|
|
1325
|
+
return false;
|
|
1326
|
+
if (value.content !== undefined &&
|
|
1327
|
+
value.content !== null &&
|
|
1328
|
+
typeof value.content !== "string")
|
|
1329
|
+
return false;
|
|
1330
|
+
if (value.reasoning_content !== undefined &&
|
|
1331
|
+
value.reasoning_content !== null &&
|
|
1332
|
+
typeof value.reasoning_content !== "string")
|
|
1333
|
+
return false;
|
|
1334
|
+
return (value.tool_calls === undefined ||
|
|
1335
|
+
(Array.isArray(value.tool_calls) &&
|
|
1336
|
+
value.tool_calls.every((call) => isRecord(call) &&
|
|
1337
|
+
typeof call.id === "string" &&
|
|
1338
|
+
isRecord(call.function) &&
|
|
1339
|
+
typeof call.function.name === "string" &&
|
|
1340
|
+
typeof call.function.arguments === "string")));
|
|
1341
|
+
}
|
|
1342
|
+
function validAnthropicContent(value) {
|
|
1343
|
+
return (Array.isArray(value) &&
|
|
1344
|
+
value.every((item) => {
|
|
1345
|
+
if (!isRecord(item) || typeof item.type !== "string")
|
|
1346
|
+
return false;
|
|
1347
|
+
if (item.type === "text")
|
|
1348
|
+
return typeof item.text === "string";
|
|
1349
|
+
if (item.type === "thinking")
|
|
1350
|
+
return (typeof item.thinking === "string" &&
|
|
1351
|
+
(item.signature === undefined || typeof item.signature === "string"));
|
|
1352
|
+
if (item.type === "redacted_thinking")
|
|
1353
|
+
return typeof item.data === "string";
|
|
1354
|
+
if (item.type === "tool_use")
|
|
1355
|
+
return (typeof item.id === "string" &&
|
|
1356
|
+
typeof item.name === "string" &&
|
|
1357
|
+
isRecord(item.input));
|
|
1358
|
+
return true;
|
|
1359
|
+
}));
|
|
1360
|
+
}
|
|
1361
|
+
function validResponsesOutput(value) {
|
|
1362
|
+
return (Array.isArray(value) &&
|
|
1363
|
+
value.every((item) => {
|
|
1364
|
+
if (!isRecord(item) || typeof item.type !== "string")
|
|
1365
|
+
return false;
|
|
1366
|
+
if (item.type === "message")
|
|
1367
|
+
return Array.isArray(item.content);
|
|
1368
|
+
if (item.type === "function_call")
|
|
1369
|
+
return (typeof item.call_id === "string" &&
|
|
1370
|
+
typeof item.name === "string" &&
|
|
1371
|
+
typeof item.arguments === "string");
|
|
1372
|
+
if (item.type === "reasoning")
|
|
1373
|
+
return ((item.summary === undefined || Array.isArray(item.summary)) &&
|
|
1374
|
+
(item.content === undefined ||
|
|
1375
|
+
item.content === null ||
|
|
1376
|
+
Array.isArray(item.content)) &&
|
|
1377
|
+
(item.encrypted_content === undefined ||
|
|
1378
|
+
item.encrypted_content === null ||
|
|
1379
|
+
typeof item.encrypted_content === "string"));
|
|
1380
|
+
return true;
|
|
1381
|
+
}));
|
|
1382
|
+
}
|
|
1383
|
+
function continuationError(protocol) {
|
|
1384
|
+
return new ModelHostContinuationError(`${protocol} continuation payload is missing or incompatible; start a fresh model context instead of rebuilding it from parsed text, reasoning, or tool calls`);
|
|
966
1385
|
}
|
|
967
1386
|
async function openAIResponsesRequest(input) {
|
|
968
|
-
const
|
|
969
|
-
const toolChoice = input.hasFrozenToolPolicy === true && allowedTools.length === 0
|
|
970
|
-
? "none"
|
|
971
|
-
: input.hasFrozenToolPolicy === true &&
|
|
972
|
-
input.toolStrategy === "native_allowed_subset"
|
|
973
|
-
? {
|
|
974
|
-
type: "allowed_tools",
|
|
975
|
-
mode: "auto",
|
|
976
|
-
tools: allowedTools.map((name) => ({ type: "function", name })),
|
|
977
|
-
}
|
|
978
|
-
: "auto";
|
|
979
|
-
const body = {
|
|
980
|
-
model: input.connection.modelId,
|
|
981
|
-
input: input.input,
|
|
982
|
-
...(input.tools.length === 0
|
|
983
|
-
? input.hasFrozenToolPolicy === true && allowedTools.length === 0
|
|
984
|
-
? { tool_choice: "none" }
|
|
985
|
-
: {}
|
|
986
|
-
: {
|
|
987
|
-
tools: input.tools.map((tool) => ({
|
|
988
|
-
type: "function",
|
|
989
|
-
name: tool.name,
|
|
990
|
-
description: tool.description,
|
|
991
|
-
parameters: tool.parameters,
|
|
992
|
-
})),
|
|
993
|
-
tool_choice: toolChoice,
|
|
994
|
-
}),
|
|
995
|
-
max_output_tokens: input.maxOutputTokens,
|
|
996
|
-
store: false,
|
|
997
|
-
include: ["reasoning.encrypted_content"],
|
|
998
|
-
stream: true,
|
|
999
|
-
};
|
|
1387
|
+
const body = responsesRequestBody(input);
|
|
1000
1388
|
await trace(`${input.tracePrefix}_request`, body);
|
|
1001
1389
|
const bodyJson = JSON.stringify(body);
|
|
1002
1390
|
const url = providerUrl(input.connection);
|
|
@@ -1011,23 +1399,18 @@ async function openAIResponsesRequest(input) {
|
|
|
1011
1399
|
"Content-Type": "application/json",
|
|
1012
1400
|
},
|
|
1013
1401
|
body: bodyJson,
|
|
1402
|
+
...(input.signal === undefined ? {} : { signal: input.signal }),
|
|
1014
1403
|
}, "OpenAI Responses"));
|
|
1015
1404
|
if (!response.ok)
|
|
1016
1405
|
throw await providerError(response);
|
|
1017
1406
|
const payload = isProviderEventStream(response)
|
|
1018
|
-
? await providerStreamResult(response, "Responses API", (body) => aggregateResponsesModelStream(body, diagnosticDelta))
|
|
1407
|
+
? await providerStreamResult(response, "Responses API", (body) => aggregateResponsesModelStream(body, diagnosticDelta), input.signal)
|
|
1019
1408
|
: await providerJson(response, "Responses API");
|
|
1020
1409
|
await trace(`${input.tracePrefix}_response`, payload);
|
|
1021
1410
|
if (!isRecord(payload) || !Array.isArray(payload.output))
|
|
1022
1411
|
throw unknownProviderResponse("Responses API");
|
|
1023
1412
|
const output = cloneResponseOutput(payload.output);
|
|
1024
|
-
if (
|
|
1025
|
-
typeof item.type !== "string" ||
|
|
1026
|
-
(item.type === "message" && !Array.isArray(item.content)) ||
|
|
1027
|
-
(item.type === "function_call" &&
|
|
1028
|
-
(typeof item.call_id !== "string" ||
|
|
1029
|
-
typeof item.name !== "string" ||
|
|
1030
|
-
typeof item.arguments !== "string"))))
|
|
1413
|
+
if (!validResponsesOutput(output))
|
|
1031
1414
|
throw unknownProviderResponse("Responses API");
|
|
1032
1415
|
const text = output
|
|
1033
1416
|
.flatMap((item) => {
|
|
@@ -1042,6 +1425,7 @@ async function openAIResponsesRequest(input) {
|
|
|
1042
1425
|
: []);
|
|
1043
1426
|
})
|
|
1044
1427
|
.join("");
|
|
1428
|
+
const reasoningContent = responsesReturnedReasoning(output);
|
|
1045
1429
|
const toolCalls = output.flatMap((item) => {
|
|
1046
1430
|
if (!isRecord(item) ||
|
|
1047
1431
|
item.type !== "function_call" ||
|
|
@@ -1057,18 +1441,61 @@ async function openAIResponsesRequest(input) {
|
|
|
1057
1441
|
},
|
|
1058
1442
|
];
|
|
1059
1443
|
});
|
|
1444
|
+
const stopReason = responsesStopReason(payload);
|
|
1060
1445
|
return {
|
|
1061
1446
|
...(text.length === 0 ? {} : { text }),
|
|
1447
|
+
...(reasoningContent.length === 0 ? {} : { reasoningContent }),
|
|
1062
1448
|
providerState: {
|
|
1063
1449
|
protocol: "openai_responses",
|
|
1064
1450
|
output,
|
|
1065
1451
|
...(typeof payload.id === "string" ? { responseId: payload.id } : {}),
|
|
1066
1452
|
},
|
|
1067
1453
|
usage: normalizeResponsesUsage(payload.usage),
|
|
1454
|
+
...(stopReason === undefined ? {} : { stopReason }),
|
|
1068
1455
|
toolCalls,
|
|
1069
1456
|
};
|
|
1070
1457
|
});
|
|
1071
1458
|
}
|
|
1459
|
+
function responsesStopReason(payload) {
|
|
1460
|
+
const status = typeof payload.status === "string" && payload.status !== ""
|
|
1461
|
+
? payload.status
|
|
1462
|
+
: undefined;
|
|
1463
|
+
const details = isRecord(payload.incomplete_details)
|
|
1464
|
+
? payload.incomplete_details
|
|
1465
|
+
: undefined;
|
|
1466
|
+
const reason = details !== undefined &&
|
|
1467
|
+
typeof details.reason === "string" &&
|
|
1468
|
+
details.reason !== ""
|
|
1469
|
+
? details.reason
|
|
1470
|
+
: undefined;
|
|
1471
|
+
if (status !== undefined && reason !== undefined)
|
|
1472
|
+
return `${status}:${reason}`;
|
|
1473
|
+
return reason ?? status;
|
|
1474
|
+
}
|
|
1475
|
+
function responsesReturnedReasoning(output) {
|
|
1476
|
+
return output
|
|
1477
|
+
.flatMap((item) => {
|
|
1478
|
+
if (!isRecord(item) || item.type !== "reasoning")
|
|
1479
|
+
return [];
|
|
1480
|
+
const summary = Array.isArray(item.summary)
|
|
1481
|
+
? item.summary.flatMap((part) => isRecord(part) &&
|
|
1482
|
+
part.type === "summary_text" &&
|
|
1483
|
+
typeof part.text === "string"
|
|
1484
|
+
? [part.text]
|
|
1485
|
+
: [])
|
|
1486
|
+
: [];
|
|
1487
|
+
if (summary.length > 0)
|
|
1488
|
+
return summary;
|
|
1489
|
+
return Array.isArray(item.content)
|
|
1490
|
+
? item.content.flatMap((part) => isRecord(part) &&
|
|
1491
|
+
part.type === "reasoning_text" &&
|
|
1492
|
+
typeof part.text === "string"
|
|
1493
|
+
? [part.text]
|
|
1494
|
+
: [])
|
|
1495
|
+
: [];
|
|
1496
|
+
})
|
|
1497
|
+
.join("");
|
|
1498
|
+
}
|
|
1072
1499
|
function cloneResponseOutput(value) {
|
|
1073
1500
|
if (!Array.isArray(value))
|
|
1074
1501
|
throw new Error("Responses output must be an array");
|
|
@@ -1094,7 +1521,8 @@ function normalizeChatUsage(value) {
|
|
|
1094
1521
|
: {};
|
|
1095
1522
|
const inputTokens = providerNumber(usage.prompt_tokens);
|
|
1096
1523
|
const cacheReadTokens = providerNumber(promptDetails.cached_tokens);
|
|
1097
|
-
const cacheWriteTokens = providerNumber(promptDetails.cache_write_tokens)
|
|
1524
|
+
const cacheWriteTokens = providerNumber(promptDetails.cache_write_tokens) ??
|
|
1525
|
+
providerNumber(promptDetails.cached_creation_tokens);
|
|
1098
1526
|
return usageRecord({
|
|
1099
1527
|
inputTokens,
|
|
1100
1528
|
uncachedInputTokens: subtractCachedInput(inputTokens, cacheReadTokens, cacheWriteTokens),
|
|
@@ -1107,6 +1535,9 @@ function normalizeChatUsage(value) {
|
|
|
1107
1535
|
}
|
|
1108
1536
|
function normalizeAnthropicUsage(value) {
|
|
1109
1537
|
const usage = isRecord(value) ? value : {};
|
|
1538
|
+
const outputDetails = isRecord(usage.output_tokens_details)
|
|
1539
|
+
? usage.output_tokens_details
|
|
1540
|
+
: {};
|
|
1110
1541
|
const uncachedInputTokens = providerNumber(usage.input_tokens);
|
|
1111
1542
|
const cacheReadTokens = providerNumber(usage.cache_read_input_tokens);
|
|
1112
1543
|
const cacheWriteTokens = providerNumber(usage.cache_creation_input_tokens);
|
|
@@ -1117,7 +1548,8 @@ function normalizeAnthropicUsage(value) {
|
|
|
1117
1548
|
uncachedInputTokens,
|
|
1118
1549
|
cacheReadTokens,
|
|
1119
1550
|
cacheWriteTokens,
|
|
1120
|
-
reasoningTokens:
|
|
1551
|
+
reasoningTokens: providerNumber(outputDetails.thinking_tokens) ??
|
|
1552
|
+
providerNumber(usage.thinking_tokens),
|
|
1121
1553
|
outputTokens,
|
|
1122
1554
|
totalTokens: inputTokens === null || outputTokens === null
|
|
1123
1555
|
? null
|
|
@@ -1134,7 +1566,8 @@ function normalizeResponsesUsage(value) {
|
|
|
1134
1566
|
: {};
|
|
1135
1567
|
const inputTokens = providerNumber(usage.input_tokens);
|
|
1136
1568
|
const cacheReadTokens = providerNumber(inputDetails.cached_tokens);
|
|
1137
|
-
const cacheWriteTokens = providerNumber(inputDetails.cache_write_tokens)
|
|
1569
|
+
const cacheWriteTokens = providerNumber(inputDetails.cache_write_tokens) ??
|
|
1570
|
+
providerNumber(inputDetails.cached_creation_tokens);
|
|
1138
1571
|
return usageRecord({
|
|
1139
1572
|
inputTokens,
|
|
1140
1573
|
uncachedInputTokens: subtractCachedInput(inputTokens, cacheReadTokens, cacheWriteTokens),
|
|
@@ -1210,13 +1643,15 @@ function isProviderEventStream(response) {
|
|
|
1210
1643
|
?.toLowerCase()
|
|
1211
1644
|
.includes("text/event-stream") === true);
|
|
1212
1645
|
}
|
|
1213
|
-
async function providerStreamResult(response, label, read) {
|
|
1646
|
+
async function providerStreamResult(response, label, read, signal) {
|
|
1214
1647
|
if (response.body === null)
|
|
1215
1648
|
throw new ModelHostOutcomeUnknownError(`${label} was dispatched but its streaming response has no body`);
|
|
1216
1649
|
try {
|
|
1217
1650
|
return await read(response.body);
|
|
1218
1651
|
}
|
|
1219
1652
|
catch (error) {
|
|
1653
|
+
if (signal?.aborted === true)
|
|
1654
|
+
throw cancelledModelRequest(error);
|
|
1220
1655
|
if (error instanceof ModelHostFailureError ||
|
|
1221
1656
|
error instanceof ModelHostOutcomeUnknownError)
|
|
1222
1657
|
throw error;
|
|
@@ -1228,11 +1663,19 @@ async function dispatchProviderRequest(fetchImplementation, url, init, label) {
|
|
|
1228
1663
|
return await fetchImplementation(url, init);
|
|
1229
1664
|
}
|
|
1230
1665
|
catch (error) {
|
|
1666
|
+
if (init.signal?.aborted === true)
|
|
1667
|
+
throw cancelledModelRequest(error);
|
|
1231
1668
|
throw new ModelHostOutcomeUnknownError(`${label} request was dispatched but its outcome is unknown`, {
|
|
1232
1669
|
cause: error,
|
|
1233
1670
|
});
|
|
1234
1671
|
}
|
|
1235
1672
|
}
|
|
1673
|
+
function cancelledModelRequest(cause) {
|
|
1674
|
+
return new ModelHostCancelledError("The player cancelled the dispatched model request; its partial response was not committed and cannot be replayed.", cause === undefined ? undefined : { cause });
|
|
1675
|
+
}
|
|
1676
|
+
function signalWasAborted(signal) {
|
|
1677
|
+
return signal?.aborted === true;
|
|
1678
|
+
}
|
|
1236
1679
|
function unknownProviderResponse(label) {
|
|
1237
1680
|
return new ModelHostOutcomeUnknownError(`${label} was dispatched but its response shape could not be confirmed`);
|
|
1238
1681
|
}
|