@psnext/slingcli 2.5.20260623-1 → 2.5.20260629-1
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/node_modules/@earendil-works/pi-agent-core/dist/harness/compaction/compaction.js +5 -2
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/session.js +2 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/shell-output.js +1 -1
- package/node_modules/@earendil-works/pi-agent-core/package.json +2 -2
- package/node_modules/@earendil-works/pi-ai/dist/api/anthropic-messages.js +51 -47
- package/node_modules/@earendil-works/pi-ai/dist/api/azure-openai-responses.js +9 -3
- package/node_modules/@earendil-works/pi-ai/dist/api/bedrock-converse-stream.js +9 -6
- package/node_modules/@earendil-works/pi-ai/dist/api/cloudflare.js +0 -18
- package/node_modules/@earendil-works/pi-ai/dist/api/google-generative-ai.js +6 -3
- package/node_modules/@earendil-works/pi-ai/dist/api/google-vertex.js +6 -3
- package/node_modules/@earendil-works/pi-ai/dist/api/mistral-conversations.js +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/openai-codex-responses.js +15 -25
- package/node_modules/@earendil-works/pi-ai/dist/api/openai-completions.js +30 -26
- package/node_modules/@earendil-works/pi-ai/dist/api/openai-responses-shared.js +189 -204
- package/node_modules/@earendil-works/pi-ai/dist/api/openai-responses.js +24 -21
- package/node_modules/@earendil-works/pi-ai/dist/api/openrouter-images.js +2 -5
- package/node_modules/@earendil-works/pi-ai/dist/api/simple-options.js +11 -2
- package/node_modules/@earendil-works/pi-ai/dist/auth/helpers.js +2 -2
- package/node_modules/@earendil-works/pi-ai/dist/auth/resolve.js +19 -4
- package/node_modules/@earendil-works/pi-ai/dist/compat.js +81 -7
- package/node_modules/@earendil-works/pi-ai/dist/image-models.generated.js +45 -0
- package/node_modules/@earendil-works/pi-ai/dist/images-models.js +7 -3
- package/node_modules/@earendil-works/pi-ai/dist/index.js +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/legacy-api-aliases.js +49 -0
- package/node_modules/@earendil-works/pi-ai/dist/models.js +7 -3
- package/node_modules/@earendil-works/pi-ai/dist/providers/amazon-bedrock.models.js +25 -8
- package/node_modules/@earendil-works/pi-ai/dist/providers/ant-ling.models.js +3 -3
- package/node_modules/@earendil-works/pi-ai/dist/providers/anthropic.models.js +0 -34
- package/node_modules/@earendil-works/pi-ai/dist/providers/cerebras.models.js +2 -0
- package/node_modules/@earendil-works/pi-ai/dist/providers/cloudflare-ai-gateway.js +2 -2
- package/node_modules/@earendil-works/pi-ai/dist/providers/cloudflare-ai-gateway.models.js +21 -9
- package/node_modules/@earendil-works/pi-ai/dist/providers/cloudflare-auth.js +85 -0
- package/node_modules/@earendil-works/pi-ai/dist/providers/cloudflare-workers-ai.js +2 -2
- package/node_modules/@earendil-works/pi-ai/dist/providers/cloudflare-workers-ai.models.js +13 -13
- package/node_modules/@earendil-works/pi-ai/dist/providers/deepseek.models.js +2 -2
- package/node_modules/@earendil-works/pi-ai/dist/providers/faux.js +1 -20
- package/node_modules/@earendil-works/pi-ai/dist/providers/fireworks.models.js +19 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/huggingface.models.js +72 -0
- package/node_modules/@earendil-works/pi-ai/dist/providers/minimax-cn.models.js +4 -4
- package/node_modules/@earendil-works/pi-ai/dist/providers/minimax.models.js +4 -4
- package/node_modules/@earendil-works/pi-ai/dist/providers/nvidia.models.js +19 -0
- package/node_modules/@earendil-works/pi-ai/dist/providers/opencode-go.models.js +11 -11
- package/node_modules/@earendil-works/pi-ai/dist/providers/opencode.models.js +15 -15
- package/node_modules/@earendil-works/pi-ai/dist/providers/openrouter.models.js +310 -39
- package/node_modules/@earendil-works/pi-ai/dist/providers/together.models.js +37 -18
- package/node_modules/@earendil-works/pi-ai/dist/providers/vercel-ai-gateway.models.js +349 -77
- package/node_modules/@earendil-works/pi-ai/dist/providers/xai.models.js +7 -0
- package/node_modules/@earendil-works/pi-ai/dist/providers/zai-coding-cn.models.js +6 -6
- package/node_modules/@earendil-works/pi-ai/dist/providers/zai.models.js +6 -6
- package/node_modules/@earendil-works/pi-ai/dist/utils/estimate.js +95 -0
- package/node_modules/@earendil-works/pi-ai/dist/utils/headers.js +10 -0
- package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/openai-codex.js +2 -2
- package/node_modules/@earendil-works/pi-ai/dist/utils/retry.js +86 -0
- package/node_modules/@earendil-works/pi-ai/package.json +1 -33
- package/node_modules/@earendil-works/pi-coding-agent/dist/cli/file-processor.js +13 -25
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/agent-session.js +15 -30
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/compaction/compaction.js +6 -3
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/extensions/loader.js +5 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/model-registry.js +2 -4
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/model-resolver.js +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/resource-loader.js +2 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/session-manager.js +9 -5
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/settings-manager.js +11 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/timings.js +24 -14
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/read.js +12 -25
- package/node_modules/@earendil-works/pi-coding-agent/dist/main.js +20 -4
- package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/assistant-message.js +9 -9
- package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/extension-editor.js +12 -3
- package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/session-selector.js +14 -3
- package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/user-message.js +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/interactive-mode.js +37 -25
- package/node_modules/@earendil-works/pi-coding-agent/dist/modes/rpc/rpc-client.js +14 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/modes/rpc/rpc-mode.js +16 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/rpc-entry.js +10 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/utils/image-convert.js +21 -15
- package/node_modules/@earendil-works/pi-coding-agent/dist/utils/image-process.js +83 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/utils/mime.js +41 -0
- package/node_modules/@earendil-works/pi-coding-agent/npm-shrinkwrap.json +12 -12
- package/node_modules/@earendil-works/pi-coding-agent/package.json +8 -5
- package/node_modules/@earendil-works/pi-orchestrator/dist/cli.js +136 -0
- package/node_modules/@earendil-works/pi-orchestrator/dist/config.js +55 -0
- package/node_modules/@earendil-works/pi-orchestrator/dist/handler.js +105 -0
- package/node_modules/@earendil-works/pi-orchestrator/dist/index.js +11 -0
- package/node_modules/@earendil-works/pi-orchestrator/dist/ipc/client.js +56 -0
- package/node_modules/@earendil-works/pi-orchestrator/dist/ipc/protocol.js +12 -0
- package/node_modules/@earendil-works/pi-orchestrator/dist/ipc/server.js +145 -0
- package/node_modules/@earendil-works/pi-orchestrator/dist/radius.js +365 -0
- package/node_modules/@earendil-works/pi-orchestrator/dist/rpc-process.js +168 -0
- package/node_modules/@earendil-works/pi-orchestrator/dist/serve.js +70 -0
- package/node_modules/@earendil-works/pi-orchestrator/dist/storage.js +58 -0
- package/node_modules/@earendil-works/pi-orchestrator/dist/supervisor.js +290 -0
- package/node_modules/@earendil-works/pi-orchestrator/dist/types.js +2 -0
- package/node_modules/@earendil-works/pi-orchestrator/package.json +45 -0
- package/node_modules/@earendil-works/pi-tui/dist/components/markdown.js +3 -0
- package/node_modules/@earendil-works/pi-tui/package.json +1 -1
- package/node_modules/@types/node/README.md +1 -1
- package/node_modules/@types/node/diagnostics_channel.d.ts +17 -20
- package/node_modules/@types/node/package.json +2 -2
- package/node_modules/brace-expansion/dist/commonjs/index.js +24 -14
- package/node_modules/brace-expansion/dist/commonjs/index.js.map +1 -1
- package/node_modules/brace-expansion/dist/esm/index.js +24 -14
- package/node_modules/brace-expansion/dist/esm/index.js.map +1 -1
- package/node_modules/brace-expansion/package.json +2 -2
- package/node_modules/google-auth-library/build/src/auth/googleauth.js +1 -1
- package/node_modules/google-auth-library/package.json +4 -4
- package/package.json +7 -6
- package/slingshot/index.js +302 -304
- package/node_modules/@earendil-works/pi-ai/dist/api-registry.js +0 -44
|
@@ -5,7 +5,6 @@ import { headersToRecord } from "../utils/headers.js";
|
|
|
5
5
|
import { parseStreamingJson } from "../utils/json-parse.js";
|
|
6
6
|
import { getProviderEnvValue } from "../utils/provider-env.js";
|
|
7
7
|
import { sanitizeSurrogates } from "../utils/sanitize-unicode.js";
|
|
8
|
-
import { isCloudflareProvider, resolveCloudflareBaseUrl } from "./cloudflare.js";
|
|
9
8
|
import { buildCopilotDynamicHeaders, hasCopilotVisionInput } from "./github-copilot-headers.js";
|
|
10
9
|
import { clampOpenAIPromptCacheKey } from "./openai-prompt-cache.js";
|
|
11
10
|
import { buildBaseOptions } from "./simple-options.js";
|
|
@@ -15,6 +14,23 @@ import { transformMessages } from "./transform-messages.js";
|
|
|
15
14
|
* This is needed because Anthropic (via proxy) requires the tools param
|
|
16
15
|
* to be present when messages include tool_calls or tool role messages.
|
|
17
16
|
*/
|
|
17
|
+
function hasHeader(headers, name) {
|
|
18
|
+
if (!headers)
|
|
19
|
+
return false;
|
|
20
|
+
const expected = name.toLowerCase();
|
|
21
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
22
|
+
if (key.toLowerCase() === expected && value !== null && value.trim().length > 0)
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
function getClientApiKey(provider, apiKey, headers) {
|
|
28
|
+
if (apiKey)
|
|
29
|
+
return apiKey;
|
|
30
|
+
if (hasHeader(headers, "authorization") || hasHeader(headers, "cf-aig-authorization"))
|
|
31
|
+
return "unused";
|
|
32
|
+
throw new Error(`No API key for provider: ${provider}`);
|
|
33
|
+
}
|
|
18
34
|
function hasToolHistory(messages) {
|
|
19
35
|
for (const msg of messages) {
|
|
20
36
|
if (msg.role === "toolResult") {
|
|
@@ -81,14 +97,11 @@ export const stream = (model, context, options) => {
|
|
|
81
97
|
timestamp: Date.now(),
|
|
82
98
|
};
|
|
83
99
|
try {
|
|
84
|
-
const apiKey = options?.apiKey;
|
|
85
|
-
if (!apiKey) {
|
|
86
|
-
throw new Error(`No API key for provider: ${model.provider}`);
|
|
87
|
-
}
|
|
100
|
+
const apiKey = getClientApiKey(model.provider, options?.apiKey, options?.headers);
|
|
88
101
|
const compat = getCompat(model);
|
|
89
102
|
const cacheRetention = resolveCacheRetention(options?.cacheRetention, options?.env);
|
|
90
103
|
const cacheSessionId = cacheRetention === "none" ? undefined : options?.sessionId;
|
|
91
|
-
const client = createClient(model, context, apiKey, options?.headers, cacheSessionId, compat
|
|
104
|
+
const client = createClient(model, context, apiKey, options?.headers, cacheSessionId, compat);
|
|
92
105
|
let params = buildParams(model, context, options, compat, cacheRetention);
|
|
93
106
|
const nextParams = await options?.onPayload?.(params, model);
|
|
94
107
|
if (nextParams !== undefined) {
|
|
@@ -365,11 +378,8 @@ export const stream = (model, context, options) => {
|
|
|
365
378
|
return stream;
|
|
366
379
|
};
|
|
367
380
|
export const streamSimple = (model, context, options) => {
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
throw new Error(`No API key for provider: ${model.provider}`);
|
|
371
|
-
}
|
|
372
|
-
const base = buildBaseOptions(model, options, apiKey);
|
|
381
|
+
getClientApiKey(model.provider, options?.apiKey, options?.headers);
|
|
382
|
+
const base = buildBaseOptions(model, context, options, options?.apiKey);
|
|
373
383
|
const clampedReasoning = options?.reasoning ? clampThinkingLevel(model, options.reasoning) : undefined;
|
|
374
384
|
const reasoningEffort = clampedReasoning === "off" ? undefined : clampedReasoning;
|
|
375
385
|
const toolChoice = options?.toolChoice;
|
|
@@ -379,7 +389,7 @@ export const streamSimple = (model, context, options) => {
|
|
|
379
389
|
toolChoice,
|
|
380
390
|
});
|
|
381
391
|
};
|
|
382
|
-
function createClient(model, context, apiKey, optionsHeaders, sessionId, compat = getCompat(model)
|
|
392
|
+
function createClient(model, context, apiKey, optionsHeaders, sessionId, compat = getCompat(model)) {
|
|
383
393
|
const headers = { ...model.headers };
|
|
384
394
|
if (model.provider === "github-copilot") {
|
|
385
395
|
const hasImages = hasCopilotVisionInput(context.messages);
|
|
@@ -398,18 +408,11 @@ function createClient(model, context, apiKey, optionsHeaders, sessionId, compat
|
|
|
398
408
|
if (optionsHeaders) {
|
|
399
409
|
Object.assign(headers, optionsHeaders);
|
|
400
410
|
}
|
|
401
|
-
const defaultHeaders = model.provider === "cloudflare-ai-gateway"
|
|
402
|
-
? {
|
|
403
|
-
...headers,
|
|
404
|
-
Authorization: headers.Authorization ?? null,
|
|
405
|
-
"cf-aig-authorization": `Bearer ${apiKey}`,
|
|
406
|
-
}
|
|
407
|
-
: headers;
|
|
408
411
|
return new OpenAI({
|
|
409
412
|
apiKey,
|
|
410
|
-
baseURL:
|
|
413
|
+
baseURL: model.baseUrl,
|
|
411
414
|
dangerouslyAllowBrowser: true,
|
|
412
|
-
defaultHeaders,
|
|
415
|
+
defaultHeaders: headers,
|
|
413
416
|
});
|
|
414
417
|
}
|
|
415
418
|
function buildParams(model, context, options, compat = getCompat(model), cacheRetention = resolveCacheRetention(options?.cacheRetention, options?.env)) {
|
|
@@ -545,7 +548,7 @@ function buildParams(model, context, options, compat = getCompat(model), cacheRe
|
|
|
545
548
|
params.provider = model.compat.openRouterRouting;
|
|
546
549
|
}
|
|
547
550
|
// Vercel AI Gateway provider routing preferences
|
|
548
|
-
if (model.
|
|
551
|
+
if (model.compat?.vercelGatewayRouting) {
|
|
549
552
|
const routing = model.compat.vercelGatewayRouting;
|
|
550
553
|
if (routing.only || routing.order) {
|
|
551
554
|
const gatewayOptions = {};
|
|
@@ -913,6 +916,7 @@ function parseChunkUsage(rawUsage, model) {
|
|
|
913
916
|
output: outputTokens,
|
|
914
917
|
cacheRead: cacheReadTokens,
|
|
915
918
|
cacheWrite: cacheWriteTokens,
|
|
919
|
+
reasoning: rawUsage.completion_tokens_details?.reasoning_tokens || 0,
|
|
916
920
|
totalTokens: input + outputTokens + cacheReadTokens + cacheWriteTokens,
|
|
917
921
|
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
|
|
918
922
|
};
|
|
@@ -943,9 +947,9 @@ function mapStopReason(reason) {
|
|
|
943
947
|
}
|
|
944
948
|
}
|
|
945
949
|
/**
|
|
946
|
-
*
|
|
947
|
-
*
|
|
948
|
-
*
|
|
950
|
+
* Auto-detect compatibility settings from provider name and baseUrl.
|
|
951
|
+
* Used as the base when model.compat is not set; explicit model.compat
|
|
952
|
+
* entries override these detected values.
|
|
949
953
|
*/
|
|
950
954
|
function detectCompat(model) {
|
|
951
955
|
const provider = model.provider;
|
|
@@ -1018,7 +1022,7 @@ function detectCompat(model) {
|
|
|
1018
1022
|
}
|
|
1019
1023
|
/**
|
|
1020
1024
|
* Get resolved compatibility settings for a model.
|
|
1021
|
-
*
|
|
1025
|
+
* Auto-detects from provider/URL then overrides with explicit model.compat.
|
|
1022
1026
|
*/
|
|
1023
1027
|
function getCompat(model) {
|
|
1024
1028
|
const detected = detectCompat(model);
|
|
@@ -214,254 +214,236 @@ export function convertResponsesTools(tools, options) {
|
|
|
214
214
|
strict,
|
|
215
215
|
}));
|
|
216
216
|
}
|
|
217
|
-
// =============================================================================
|
|
218
|
-
// Stream processing
|
|
219
|
-
// =============================================================================
|
|
220
217
|
export async function processResponsesStream(openaiStream, output, stream, model, options) {
|
|
221
|
-
let
|
|
222
|
-
|
|
223
|
-
const
|
|
224
|
-
|
|
218
|
+
let sawTerminalResponseEvent = false;
|
|
219
|
+
const outputSlots = new Map();
|
|
220
|
+
const getSlot = (outputIndex, type) => {
|
|
221
|
+
const slot = outputSlots.get(outputIndex);
|
|
222
|
+
return slot?.type === type ? slot : undefined;
|
|
223
|
+
};
|
|
224
|
+
const createSlot = (outputIndex, item) => {
|
|
225
|
+
if (item.type === "reasoning") {
|
|
226
|
+
const block = { type: "thinking", thinking: "" };
|
|
227
|
+
output.content.push(block);
|
|
228
|
+
const slot = {
|
|
229
|
+
type: "thinking",
|
|
230
|
+
block,
|
|
231
|
+
contentIndex: output.content.length - 1,
|
|
232
|
+
};
|
|
233
|
+
outputSlots.set(outputIndex, slot);
|
|
234
|
+
stream.push({ type: "thinking_start", contentIndex: slot.contentIndex, partial: output });
|
|
235
|
+
return slot;
|
|
236
|
+
}
|
|
237
|
+
if (item.type === "message") {
|
|
238
|
+
const block = { type: "text", text: "" };
|
|
239
|
+
output.content.push(block);
|
|
240
|
+
const slot = { type: "text", block, contentIndex: output.content.length - 1 };
|
|
241
|
+
outputSlots.set(outputIndex, slot);
|
|
242
|
+
stream.push({ type: "text_start", contentIndex: slot.contentIndex, partial: output });
|
|
243
|
+
return slot;
|
|
244
|
+
}
|
|
245
|
+
if (item.type === "function_call") {
|
|
246
|
+
const block = {
|
|
247
|
+
type: "toolCall",
|
|
248
|
+
id: `${item.call_id}|${item.id}`,
|
|
249
|
+
name: item.name,
|
|
250
|
+
arguments: {},
|
|
251
|
+
partialJson: item.arguments || "",
|
|
252
|
+
};
|
|
253
|
+
output.content.push(block);
|
|
254
|
+
const slot = {
|
|
255
|
+
type: "toolCall",
|
|
256
|
+
block,
|
|
257
|
+
contentIndex: output.content.length - 1,
|
|
258
|
+
};
|
|
259
|
+
outputSlots.set(outputIndex, slot);
|
|
260
|
+
stream.push({ type: "toolcall_start", contentIndex: slot.contentIndex, partial: output });
|
|
261
|
+
return slot;
|
|
262
|
+
}
|
|
263
|
+
return undefined;
|
|
264
|
+
};
|
|
265
|
+
const getOrCreateSlot = (outputIndex, item) => {
|
|
266
|
+
return outputSlots.get(outputIndex) ?? createSlot(outputIndex, item);
|
|
267
|
+
};
|
|
268
|
+
const finalizeResponse = (response) => {
|
|
269
|
+
sawTerminalResponseEvent = true;
|
|
270
|
+
if (response?.id) {
|
|
271
|
+
output.responseId = response.id;
|
|
272
|
+
}
|
|
273
|
+
if (response?.usage) {
|
|
274
|
+
const cachedTokens = response.usage.input_tokens_details?.cached_tokens || 0;
|
|
275
|
+
output.usage = {
|
|
276
|
+
// OpenAI includes cached tokens in input_tokens, so subtract to get non-cached input
|
|
277
|
+
input: (response.usage.input_tokens || 0) - cachedTokens,
|
|
278
|
+
output: response.usage.output_tokens || 0,
|
|
279
|
+
cacheRead: cachedTokens,
|
|
280
|
+
cacheWrite: 0,
|
|
281
|
+
reasoning: response.usage.output_tokens_details?.reasoning_tokens || 0,
|
|
282
|
+
totalTokens: response.usage.total_tokens || 0,
|
|
283
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
calculateCost(model, output.usage);
|
|
287
|
+
if (options?.applyServiceTierPricing) {
|
|
288
|
+
const serviceTier = options.resolveServiceTier
|
|
289
|
+
? options.resolveServiceTier(response?.service_tier, options.serviceTier)
|
|
290
|
+
: (response?.service_tier ?? options.serviceTier);
|
|
291
|
+
options.applyServiceTierPricing(output.usage, serviceTier);
|
|
292
|
+
}
|
|
293
|
+
// Map status to stop reason
|
|
294
|
+
output.stopReason = mapStopReason(response?.status);
|
|
295
|
+
if (output.content.some((b) => b.type === "toolCall") && output.stopReason === "stop") {
|
|
296
|
+
output.stopReason = "toolUse";
|
|
297
|
+
}
|
|
298
|
+
};
|
|
225
299
|
for await (const event of openaiStream) {
|
|
226
300
|
if (event.type === "response.created") {
|
|
227
301
|
output.responseId = event.response.id;
|
|
228
302
|
}
|
|
229
303
|
else if (event.type === "response.output_item.added") {
|
|
230
|
-
|
|
231
|
-
if (item.type === "reasoning") {
|
|
232
|
-
currentItem = item;
|
|
233
|
-
currentBlock = { type: "thinking", thinking: "" };
|
|
234
|
-
output.content.push(currentBlock);
|
|
235
|
-
stream.push({ type: "thinking_start", contentIndex: blockIndex(), partial: output });
|
|
236
|
-
}
|
|
237
|
-
else if (item.type === "message") {
|
|
238
|
-
currentItem = item;
|
|
239
|
-
currentBlock = { type: "text", text: "" };
|
|
240
|
-
output.content.push(currentBlock);
|
|
241
|
-
stream.push({ type: "text_start", contentIndex: blockIndex(), partial: output });
|
|
242
|
-
}
|
|
243
|
-
else if (item.type === "function_call") {
|
|
244
|
-
currentItem = item;
|
|
245
|
-
currentBlock = {
|
|
246
|
-
type: "toolCall",
|
|
247
|
-
id: `${item.call_id}|${item.id}`,
|
|
248
|
-
name: item.name,
|
|
249
|
-
arguments: {},
|
|
250
|
-
partialJson: item.arguments || "",
|
|
251
|
-
};
|
|
252
|
-
output.content.push(currentBlock);
|
|
253
|
-
stream.push({ type: "toolcall_start", contentIndex: blockIndex(), partial: output });
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
else if (event.type === "response.reasoning_summary_part.added") {
|
|
257
|
-
if (currentItem && currentItem.type === "reasoning") {
|
|
258
|
-
currentItem.summary = currentItem.summary || [];
|
|
259
|
-
currentItem.summary.push(event.part);
|
|
260
|
-
}
|
|
304
|
+
createSlot(event.output_index, event.item);
|
|
261
305
|
}
|
|
262
306
|
else if (event.type === "response.reasoning_summary_text.delta") {
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
partial: output,
|
|
274
|
-
});
|
|
275
|
-
}
|
|
276
|
-
}
|
|
307
|
+
const slot = getSlot(event.output_index, "thinking");
|
|
308
|
+
if (!slot)
|
|
309
|
+
continue;
|
|
310
|
+
slot.block.thinking += event.delta;
|
|
311
|
+
stream.push({
|
|
312
|
+
type: "thinking_delta",
|
|
313
|
+
contentIndex: slot.contentIndex,
|
|
314
|
+
delta: event.delta,
|
|
315
|
+
partial: output,
|
|
316
|
+
});
|
|
277
317
|
}
|
|
278
318
|
else if (event.type === "response.reasoning_summary_part.done") {
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
partial: output,
|
|
290
|
-
});
|
|
291
|
-
}
|
|
292
|
-
}
|
|
319
|
+
const slot = getSlot(event.output_index, "thinking");
|
|
320
|
+
if (!slot)
|
|
321
|
+
continue;
|
|
322
|
+
slot.block.thinking += "\n\n";
|
|
323
|
+
stream.push({
|
|
324
|
+
type: "thinking_delta",
|
|
325
|
+
contentIndex: slot.contentIndex,
|
|
326
|
+
delta: "\n\n",
|
|
327
|
+
partial: output,
|
|
328
|
+
});
|
|
293
329
|
}
|
|
294
330
|
else if (event.type === "response.reasoning_text.delta") {
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
else if (event.type === "response.content_part.added") {
|
|
306
|
-
if (currentItem?.type === "message") {
|
|
307
|
-
currentItem.content = currentItem.content || [];
|
|
308
|
-
// Filter out ReasoningText, only accept output_text and refusal
|
|
309
|
-
if (event.part.type === "output_text" || event.part.type === "refusal") {
|
|
310
|
-
currentItem.content.push(event.part);
|
|
311
|
-
}
|
|
312
|
-
}
|
|
331
|
+
const slot = getSlot(event.output_index, "thinking");
|
|
332
|
+
if (!slot)
|
|
333
|
+
continue;
|
|
334
|
+
slot.block.thinking += event.delta;
|
|
335
|
+
stream.push({
|
|
336
|
+
type: "thinking_delta",
|
|
337
|
+
contentIndex: slot.contentIndex,
|
|
338
|
+
delta: event.delta,
|
|
339
|
+
partial: output,
|
|
340
|
+
});
|
|
313
341
|
}
|
|
314
342
|
else if (event.type === "response.output_text.delta") {
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
contentIndex: blockIndex(),
|
|
326
|
-
delta: event.delta,
|
|
327
|
-
partial: output,
|
|
328
|
-
});
|
|
329
|
-
}
|
|
330
|
-
}
|
|
343
|
+
const slot = getSlot(event.output_index, "text");
|
|
344
|
+
if (!slot)
|
|
345
|
+
continue;
|
|
346
|
+
slot.block.text += event.delta;
|
|
347
|
+
stream.push({
|
|
348
|
+
type: "text_delta",
|
|
349
|
+
contentIndex: slot.contentIndex,
|
|
350
|
+
delta: event.delta,
|
|
351
|
+
partial: output,
|
|
352
|
+
});
|
|
331
353
|
}
|
|
332
354
|
else if (event.type === "response.refusal.delta") {
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
contentIndex: blockIndex(),
|
|
344
|
-
delta: event.delta,
|
|
345
|
-
partial: output,
|
|
346
|
-
});
|
|
347
|
-
}
|
|
348
|
-
}
|
|
355
|
+
const slot = getSlot(event.output_index, "text");
|
|
356
|
+
if (!slot)
|
|
357
|
+
continue;
|
|
358
|
+
slot.block.text += event.delta;
|
|
359
|
+
stream.push({
|
|
360
|
+
type: "text_delta",
|
|
361
|
+
contentIndex: slot.contentIndex,
|
|
362
|
+
delta: event.delta,
|
|
363
|
+
partial: output,
|
|
364
|
+
});
|
|
349
365
|
}
|
|
350
366
|
else if (event.type === "response.function_call_arguments.delta") {
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
367
|
+
const slot = getSlot(event.output_index, "toolCall");
|
|
368
|
+
if (!slot)
|
|
369
|
+
continue;
|
|
370
|
+
slot.block.partialJson += event.delta;
|
|
371
|
+
slot.block.arguments = parseStreamingJson(slot.block.partialJson);
|
|
372
|
+
stream.push({
|
|
373
|
+
type: "toolcall_delta",
|
|
374
|
+
contentIndex: slot.contentIndex,
|
|
375
|
+
delta: event.delta,
|
|
376
|
+
partial: output,
|
|
377
|
+
});
|
|
361
378
|
}
|
|
362
379
|
else if (event.type === "response.function_call_arguments.done") {
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
380
|
+
const slot = getSlot(event.output_index, "toolCall");
|
|
381
|
+
if (!slot)
|
|
382
|
+
continue;
|
|
383
|
+
const previousPartialJson = slot.block.partialJson;
|
|
384
|
+
slot.block.partialJson = event.arguments;
|
|
385
|
+
slot.block.arguments = parseStreamingJson(slot.block.partialJson);
|
|
386
|
+
if (event.arguments.startsWith(previousPartialJson)) {
|
|
387
|
+
const delta = event.arguments.slice(previousPartialJson.length);
|
|
388
|
+
if (delta.length > 0) {
|
|
389
|
+
stream.push({
|
|
390
|
+
type: "toolcall_delta",
|
|
391
|
+
contentIndex: slot.contentIndex,
|
|
392
|
+
delta,
|
|
393
|
+
partial: output,
|
|
394
|
+
});
|
|
377
395
|
}
|
|
378
396
|
}
|
|
379
397
|
}
|
|
380
398
|
else if (event.type === "response.output_item.done") {
|
|
381
399
|
const item = event.item;
|
|
382
|
-
|
|
400
|
+
const slot = getOrCreateSlot(event.output_index, item);
|
|
401
|
+
if (item.type === "reasoning" && slot?.type === "thinking") {
|
|
383
402
|
const summaryText = item.summary?.map((s) => s.text).join("\n\n") || "";
|
|
384
403
|
const contentText = item.content?.map((c) => c.text).join("\n\n") || "";
|
|
385
|
-
|
|
386
|
-
|
|
404
|
+
slot.block.thinking = summaryText || contentText || slot.block.thinking;
|
|
405
|
+
slot.block.thinkingSignature = JSON.stringify(item);
|
|
387
406
|
stream.push({
|
|
388
407
|
type: "thinking_end",
|
|
389
|
-
contentIndex:
|
|
390
|
-
content:
|
|
408
|
+
contentIndex: slot.contentIndex,
|
|
409
|
+
content: slot.block.thinking,
|
|
391
410
|
partial: output,
|
|
392
411
|
});
|
|
393
|
-
|
|
412
|
+
outputSlots.delete(event.output_index);
|
|
394
413
|
}
|
|
395
|
-
else if (item.type === "message" &&
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
currentBlock.textSignature = encodeTextSignatureV1(item.id, item.phase ?? undefined);
|
|
414
|
+
else if (item.type === "message" && slot?.type === "text") {
|
|
415
|
+
slot.block.text = item.content?.map((c) => (c.type === "output_text" ? c.text : c.refusal)).join("") || "";
|
|
416
|
+
slot.block.textSignature = encodeTextSignatureV1(item.id, item.phase ?? undefined);
|
|
399
417
|
stream.push({
|
|
400
418
|
type: "text_end",
|
|
401
|
-
contentIndex:
|
|
402
|
-
content:
|
|
419
|
+
contentIndex: slot.contentIndex,
|
|
420
|
+
content: slot.block.text,
|
|
403
421
|
partial: output,
|
|
404
422
|
});
|
|
405
|
-
|
|
423
|
+
outputSlots.delete(event.output_index);
|
|
406
424
|
}
|
|
407
|
-
else if (item.type === "function_call") {
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
else {
|
|
420
|
-
toolCall = {
|
|
421
|
-
type: "toolCall",
|
|
422
|
-
id: `${item.call_id}|${item.id}`,
|
|
423
|
-
name: item.name,
|
|
424
|
-
arguments: args,
|
|
425
|
-
};
|
|
426
|
-
}
|
|
427
|
-
currentBlock = null;
|
|
428
|
-
stream.push({ type: "toolcall_end", contentIndex: blockIndex(), toolCall, partial: output });
|
|
425
|
+
else if (item.type === "function_call" && slot?.type === "toolCall") {
|
|
426
|
+
slot.block.arguments = parseStreamingJson(item.arguments || slot.block.partialJson || "{}");
|
|
427
|
+
// Finalize in-place and strip the scratch buffer so replay only
|
|
428
|
+
// carries parsed arguments.
|
|
429
|
+
delete slot.block.partialJson;
|
|
430
|
+
stream.push({
|
|
431
|
+
type: "toolcall_end",
|
|
432
|
+
contentIndex: slot.contentIndex,
|
|
433
|
+
toolCall: slot.block,
|
|
434
|
+
partial: output,
|
|
435
|
+
});
|
|
436
|
+
outputSlots.delete(event.output_index);
|
|
429
437
|
}
|
|
430
438
|
}
|
|
431
|
-
else if (event.type === "response.completed") {
|
|
432
|
-
|
|
433
|
-
if (response?.id) {
|
|
434
|
-
output.responseId = response.id;
|
|
435
|
-
}
|
|
436
|
-
if (response?.usage) {
|
|
437
|
-
const cachedTokens = response.usage.input_tokens_details?.cached_tokens || 0;
|
|
438
|
-
output.usage = {
|
|
439
|
-
// OpenAI includes cached tokens in input_tokens, so subtract to get non-cached input
|
|
440
|
-
input: (response.usage.input_tokens || 0) - cachedTokens,
|
|
441
|
-
output: response.usage.output_tokens || 0,
|
|
442
|
-
cacheRead: cachedTokens,
|
|
443
|
-
cacheWrite: 0,
|
|
444
|
-
totalTokens: response.usage.total_tokens || 0,
|
|
445
|
-
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
|
|
446
|
-
};
|
|
447
|
-
}
|
|
448
|
-
calculateCost(model, output.usage);
|
|
449
|
-
if (options?.applyServiceTierPricing) {
|
|
450
|
-
const serviceTier = options.resolveServiceTier
|
|
451
|
-
? options.resolveServiceTier(response?.service_tier, options.serviceTier)
|
|
452
|
-
: (response?.service_tier ?? options.serviceTier);
|
|
453
|
-
options.applyServiceTierPricing(output.usage, serviceTier);
|
|
454
|
-
}
|
|
455
|
-
// Map status to stop reason
|
|
456
|
-
output.stopReason = mapStopReason(response?.status);
|
|
457
|
-
if (output.content.some((b) => b.type === "toolCall") && output.stopReason === "stop") {
|
|
458
|
-
output.stopReason = "toolUse";
|
|
459
|
-
}
|
|
439
|
+
else if (event.type === "response.completed" || event.type === "response.incomplete") {
|
|
440
|
+
finalizeResponse(event.response);
|
|
460
441
|
}
|
|
461
442
|
else if (event.type === "error") {
|
|
462
443
|
throw new Error(`Error Code ${event.code}: ${event.message}` || "Unknown error");
|
|
463
444
|
}
|
|
464
445
|
else if (event.type === "response.failed") {
|
|
446
|
+
sawTerminalResponseEvent = true;
|
|
465
447
|
const error = event.response?.error;
|
|
466
448
|
const details = event.response?.incomplete_details;
|
|
467
449
|
const msg = error
|
|
@@ -472,6 +454,9 @@ export async function processResponsesStream(openaiStream, output, stream, model
|
|
|
472
454
|
throw new Error(msg);
|
|
473
455
|
}
|
|
474
456
|
}
|
|
457
|
+
if (!sawTerminalResponseEvent) {
|
|
458
|
+
throw new Error("OpenAI Responses stream ended before a terminal response event");
|
|
459
|
+
}
|
|
475
460
|
}
|
|
476
461
|
function mapStopReason(status) {
|
|
477
462
|
if (!status)
|