@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
|
@@ -65,13 +65,16 @@ export function calculateContextTokens(usage) {
|
|
|
65
65
|
function getAssistantUsage(msg) {
|
|
66
66
|
if (msg.role === "assistant" && "usage" in msg) {
|
|
67
67
|
const assistantMsg = msg;
|
|
68
|
-
if (assistantMsg.stopReason !== "aborted" &&
|
|
68
|
+
if (assistantMsg.stopReason !== "aborted" &&
|
|
69
|
+
assistantMsg.stopReason !== "error" &&
|
|
70
|
+
assistantMsg.usage &&
|
|
71
|
+
calculateContextTokens(assistantMsg.usage) > 0) {
|
|
69
72
|
return assistantMsg.usage;
|
|
70
73
|
}
|
|
71
74
|
}
|
|
72
75
|
return undefined;
|
|
73
76
|
}
|
|
74
|
-
/** Return usage from the last
|
|
77
|
+
/** Return usage from the last valid assistant message in session entries. */
|
|
75
78
|
export function getLastAssistantUsage(entries) {
|
|
76
79
|
for (let i = entries.length - 1; i >= 0; i--) {
|
|
77
80
|
const entry = entries[i];
|
|
@@ -180,12 +180,13 @@ export class Session {
|
|
|
180
180
|
});
|
|
181
181
|
}
|
|
182
182
|
async appendSessionName(name) {
|
|
183
|
+
const sanitizedName = name.replace(/[\r\n]+/g, " ").trim();
|
|
183
184
|
return this.appendTypedEntry({
|
|
184
185
|
type: "session_info",
|
|
185
186
|
id: await this.storage.createEntryId(),
|
|
186
187
|
parentId: await this.storage.getLeafId(),
|
|
187
188
|
timestamp: new Date().toISOString(),
|
|
188
|
-
name:
|
|
189
|
+
name: sanitizedName,
|
|
189
190
|
});
|
|
190
191
|
}
|
|
191
192
|
async moveTo(entryId, summary) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ExecutionError, err, ok, toError
|
|
1
|
+
import { ExecutionError, err, ok, toError } from "../types.js";
|
|
2
2
|
import { DEFAULT_MAX_BYTES, truncateTail } from "./truncate.js";
|
|
3
3
|
function toExecutionError(error) {
|
|
4
4
|
if (error instanceof ExecutionError)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@earendil-works/pi-agent-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.80.2",
|
|
4
4
|
"description": "General-purpose agent with transport abstraction, state management, and attachment support",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"prepublishOnly": "npm run clean && npm run build"
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@earendil-works/pi-ai": "^0.
|
|
32
|
+
"@earendil-works/pi-ai": "^0.80.2",
|
|
33
33
|
"ignore": "7.0.5",
|
|
34
34
|
"typebox": "1.1.38",
|
|
35
35
|
"yaml": "2.9.0"
|
|
@@ -5,9 +5,8 @@ import { headersToRecord } from "../utils/headers.js";
|
|
|
5
5
|
import { parseJsonWithRepair, 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 { resolveCloudflareBaseUrl } from "./cloudflare.js";
|
|
9
8
|
import { buildCopilotDynamicHeaders, hasCopilotVisionInput } from "./github-copilot-headers.js";
|
|
10
|
-
import { adjustMaxTokensForThinking, buildBaseOptions } from "./simple-options.js";
|
|
9
|
+
import { adjustMaxTokensForThinking, buildBaseOptions, clampMaxTokensToContext } from "./simple-options.js";
|
|
11
10
|
import { transformMessages } from "./transform-messages.js";
|
|
12
11
|
/**
|
|
13
12
|
* Resolve cache retention preference.
|
|
@@ -108,14 +107,11 @@ function convertContentBlocks(content) {
|
|
|
108
107
|
const FINE_GRAINED_TOOL_STREAMING_BETA = "fine-grained-tool-streaming-2025-05-14";
|
|
109
108
|
const INTERLEAVED_THINKING_BETA = "interleaved-thinking-2025-05-14";
|
|
110
109
|
function getAnthropicCompat(model) {
|
|
111
|
-
// Auto-detect session affinity and cache control support from provider
|
|
112
|
-
const isFireworks = model.provider === "fireworks";
|
|
113
|
-
const isCloudflareAiGatewayAnthropic = model.provider === "cloudflare-ai-gateway" && model.baseUrl.includes("anthropic");
|
|
114
110
|
return {
|
|
115
|
-
supportsEagerToolInputStreaming: model.compat?.supportsEagerToolInputStreaming ??
|
|
116
|
-
supportsLongCacheRetention: model.compat?.supportsLongCacheRetention ??
|
|
117
|
-
sendSessionAffinityHeaders: model.compat?.sendSessionAffinityHeaders ??
|
|
118
|
-
supportsCacheControlOnTools: model.compat?.supportsCacheControlOnTools ??
|
|
111
|
+
supportsEagerToolInputStreaming: model.compat?.supportsEagerToolInputStreaming ?? true,
|
|
112
|
+
supportsLongCacheRetention: model.compat?.supportsLongCacheRetention ?? true,
|
|
113
|
+
sendSessionAffinityHeaders: model.compat?.sendSessionAffinityHeaders ?? false,
|
|
114
|
+
supportsCacheControlOnTools: model.compat?.supportsCacheControlOnTools ?? true,
|
|
119
115
|
supportsTemperature: model.compat?.supportsTemperature ?? true,
|
|
120
116
|
allowEmptySignature: model.compat?.allowEmptySignature ?? false,
|
|
121
117
|
};
|
|
@@ -129,6 +125,26 @@ function mergeHeaders(...headerSources) {
|
|
|
129
125
|
}
|
|
130
126
|
return merged;
|
|
131
127
|
}
|
|
128
|
+
function hasHeader(headers, name) {
|
|
129
|
+
if (!headers)
|
|
130
|
+
return false;
|
|
131
|
+
const expected = name.toLowerCase();
|
|
132
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
133
|
+
if (key.toLowerCase() === expected && value !== null && value.trim().length > 0)
|
|
134
|
+
return true;
|
|
135
|
+
}
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
function assertRequestAuth(provider, apiKey, headers) {
|
|
139
|
+
if (apiKey)
|
|
140
|
+
return;
|
|
141
|
+
if (hasHeader(headers, "authorization") ||
|
|
142
|
+
hasHeader(headers, "x-api-key") ||
|
|
143
|
+
hasHeader(headers, "cf-aig-authorization")) {
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
throw new Error(`No API key for provider: ${provider}`);
|
|
147
|
+
}
|
|
132
148
|
const ANTHROPIC_MESSAGE_EVENTS = new Set([
|
|
133
149
|
"message_start",
|
|
134
150
|
"message_delta",
|
|
@@ -309,9 +325,7 @@ export const stream = (model, context, options) => {
|
|
|
309
325
|
}
|
|
310
326
|
else {
|
|
311
327
|
const apiKey = options?.apiKey;
|
|
312
|
-
|
|
313
|
-
throw new Error(`No API key for provider: ${model.provider}`);
|
|
314
|
-
}
|
|
328
|
+
assertRequestAuth(model.provider, apiKey, options?.headers);
|
|
315
329
|
let copilotDynamicHeaders;
|
|
316
330
|
if (model.provider === "github-copilot") {
|
|
317
331
|
const hasImages = hasCopilotVisionInput(context.messages);
|
|
@@ -322,7 +336,7 @@ export const stream = (model, context, options) => {
|
|
|
322
336
|
}
|
|
323
337
|
const cacheRetention = resolveCacheRetention(options?.cacheRetention, options?.env);
|
|
324
338
|
const cacheSessionId = cacheRetention === "none" ? undefined : options?.sessionId;
|
|
325
|
-
const created = createClient(model, apiKey, options?.interleavedThinking ?? true, shouldUseFineGrainedToolStreamingBeta(model, context), options?.headers, copilotDynamicHeaders, cacheSessionId
|
|
339
|
+
const created = createClient(model, apiKey, options?.interleavedThinking ?? true, shouldUseFineGrainedToolStreamingBeta(model, context), options?.headers, copilotDynamicHeaders, cacheSessionId);
|
|
326
340
|
client = created.client;
|
|
327
341
|
isOAuth = created.isOAuthToken;
|
|
328
342
|
}
|
|
@@ -508,6 +522,14 @@ export const stream = (model, context, options) => {
|
|
|
508
522
|
if (event.usage.cache_creation_input_tokens != null) {
|
|
509
523
|
output.usage.cacheWrite = event.usage.cache_creation_input_tokens;
|
|
510
524
|
}
|
|
525
|
+
// Anthropic reports reasoning tokens in `output_tokens_details.thinking_tokens` on the
|
|
526
|
+
// final message_delta usage (a subset of output_tokens). SDK 0.91.1 omits the field from
|
|
527
|
+
// its Usage type, so read it through a narrow cast. Verified against the live API.
|
|
528
|
+
const thinkingTokens = event.usage
|
|
529
|
+
.output_tokens_details?.thinking_tokens;
|
|
530
|
+
if (thinkingTokens != null) {
|
|
531
|
+
output.usage.reasoning = thinkingTokens;
|
|
532
|
+
}
|
|
511
533
|
// Anthropic doesn't provide total_tokens, compute from components
|
|
512
534
|
output.usage.totalTokens =
|
|
513
535
|
output.usage.input + output.usage.output + output.usage.cacheRead + output.usage.cacheWrite;
|
|
@@ -558,11 +580,8 @@ function mapThinkingLevelToEffort(model, level) {
|
|
|
558
580
|
}
|
|
559
581
|
}
|
|
560
582
|
export const streamSimple = (model, context, options) => {
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
throw new Error(`No API key for provider: ${model.provider}`);
|
|
564
|
-
}
|
|
565
|
-
const base = buildBaseOptions(model, options, apiKey);
|
|
583
|
+
assertRequestAuth(model.provider, options?.apiKey, options?.headers);
|
|
584
|
+
const base = buildBaseOptions(model, context, options, options?.apiKey);
|
|
566
585
|
if (!options?.reasoning) {
|
|
567
586
|
return stream(model, context, { ...base, thinkingEnabled: false });
|
|
568
587
|
}
|
|
@@ -579,17 +598,18 @@ export const streamSimple = (model, context, options) => {
|
|
|
579
598
|
// Undefined means the caller did not request an output cap; let the helper use the model cap.
|
|
580
599
|
// Do not coerce to 0 here, or the thinking budget would become the entire max_tokens value.
|
|
581
600
|
const adjusted = adjustMaxTokensForThinking(base.maxTokens, model.maxTokens, options.reasoning, options.thinkingBudgets);
|
|
601
|
+
const maxTokens = clampMaxTokensToContext(model, context, adjusted.maxTokens);
|
|
582
602
|
return stream(model, context, {
|
|
583
603
|
...base,
|
|
584
|
-
maxTokens
|
|
604
|
+
maxTokens,
|
|
585
605
|
thinkingEnabled: true,
|
|
586
|
-
thinkingBudgetTokens: adjusted.thinkingBudget,
|
|
606
|
+
thinkingBudgetTokens: Math.min(adjusted.thinkingBudget, Math.max(0, maxTokens - 1024)),
|
|
587
607
|
});
|
|
588
608
|
};
|
|
589
609
|
function isOAuthToken(apiKey) {
|
|
590
610
|
return apiKey.includes("sk-ant-oat");
|
|
591
611
|
}
|
|
592
|
-
function createClient(model, apiKey, interleavedThinking, useFineGrainedToolStreamingBeta, optionsHeaders, dynamicHeaders, sessionId
|
|
612
|
+
function createClient(model, apiKey, interleavedThinking, useFineGrainedToolStreamingBeta, optionsHeaders, dynamicHeaders, sessionId) {
|
|
593
613
|
// Adaptive thinking models have interleaved thinking built in, so skip the beta header.
|
|
594
614
|
const needsInterleavedBeta = interleavedThinking && model.compat?.forceAdaptiveThinking !== true;
|
|
595
615
|
const betaFeatures = [];
|
|
@@ -599,28 +619,11 @@ function createClient(model, apiKey, interleavedThinking, useFineGrainedToolStre
|
|
|
599
619
|
if (needsInterleavedBeta) {
|
|
600
620
|
betaFeatures.push(INTERLEAVED_THINKING_BETA);
|
|
601
621
|
}
|
|
602
|
-
if (model.provider === "cloudflare-ai-gateway") {
|
|
603
|
-
const client = new Anthropic({
|
|
604
|
-
apiKey: null,
|
|
605
|
-
authToken: null,
|
|
606
|
-
baseURL: resolveCloudflareBaseUrl(model, env),
|
|
607
|
-
dangerouslyAllowBrowser: true,
|
|
608
|
-
defaultHeaders: mergeHeaders({
|
|
609
|
-
accept: "application/json",
|
|
610
|
-
"anthropic-dangerous-direct-browser-access": "true",
|
|
611
|
-
"cf-aig-authorization": `Bearer ${apiKey}`,
|
|
612
|
-
"x-api-key": null,
|
|
613
|
-
Authorization: null,
|
|
614
|
-
...(betaFeatures.length > 0 ? { "anthropic-beta": betaFeatures.join(",") } : {}),
|
|
615
|
-
}, model.headers, optionsHeaders),
|
|
616
|
-
});
|
|
617
|
-
return { client, isOAuthToken: false };
|
|
618
|
-
}
|
|
619
622
|
// Copilot: Bearer auth, selective betas.
|
|
620
623
|
if (model.provider === "github-copilot") {
|
|
621
624
|
const client = new Anthropic({
|
|
622
625
|
apiKey: null,
|
|
623
|
-
authToken: apiKey,
|
|
626
|
+
authToken: apiKey ?? null,
|
|
624
627
|
baseURL: model.baseUrl,
|
|
625
628
|
dangerouslyAllowBrowser: true,
|
|
626
629
|
defaultHeaders: mergeHeaders({
|
|
@@ -632,7 +635,7 @@ function createClient(model, apiKey, interleavedThinking, useFineGrainedToolStre
|
|
|
632
635
|
return { client, isOAuthToken: false };
|
|
633
636
|
}
|
|
634
637
|
// OAuth: Bearer auth, Claude Code identity headers
|
|
635
|
-
if (isOAuthToken(apiKey)) {
|
|
638
|
+
if (apiKey && isOAuthToken(apiKey)) {
|
|
636
639
|
const client = new Anthropic({
|
|
637
640
|
apiKey: null,
|
|
638
641
|
authToken: apiKey,
|
|
@@ -648,18 +651,19 @@ function createClient(model, apiKey, interleavedThinking, useFineGrainedToolStre
|
|
|
648
651
|
});
|
|
649
652
|
return { client, isOAuthToken: true };
|
|
650
653
|
}
|
|
651
|
-
// API key auth
|
|
654
|
+
// API key or header-owned auth.
|
|
652
655
|
const sessionAffinityHeaders = sessionId && getAnthropicCompat(model).sendSessionAffinityHeaders ? { "x-session-affinity": sessionId } : {};
|
|
656
|
+
const defaultHeaders = mergeHeaders({
|
|
657
|
+
accept: "application/json",
|
|
658
|
+
"anthropic-dangerous-direct-browser-access": "true",
|
|
659
|
+
...(betaFeatures.length > 0 ? { "anthropic-beta": betaFeatures.join(",") } : {}),
|
|
660
|
+
}, sessionAffinityHeaders, model.headers, optionsHeaders);
|
|
653
661
|
const client = new Anthropic({
|
|
654
|
-
apiKey,
|
|
662
|
+
apiKey: apiKey ?? null,
|
|
655
663
|
authToken: null,
|
|
656
664
|
baseURL: model.baseUrl,
|
|
657
665
|
dangerouslyAllowBrowser: true,
|
|
658
|
-
defaultHeaders
|
|
659
|
-
accept: "application/json",
|
|
660
|
-
"anthropic-dangerous-direct-browser-access": "true",
|
|
661
|
-
...(betaFeatures.length > 0 ? { "anthropic-beta": betaFeatures.join(",") } : {}),
|
|
662
|
-
}, sessionAffinityHeaders, model.headers, optionsHeaders),
|
|
666
|
+
defaultHeaders,
|
|
663
667
|
});
|
|
664
668
|
return { client, isOAuthToken: false };
|
|
665
669
|
}
|
|
@@ -120,7 +120,7 @@ export const streamSimple = (model, context, options) => {
|
|
|
120
120
|
if (!apiKey) {
|
|
121
121
|
throw new Error(`No API key for provider: ${model.provider}`);
|
|
122
122
|
}
|
|
123
|
-
const base = buildBaseOptions(model, options, apiKey);
|
|
123
|
+
const base = buildBaseOptions(model, context, options, apiKey);
|
|
124
124
|
const clampedReasoning = options?.reasoning ? clampThinkingLevel(model, options.reasoning) : undefined;
|
|
125
125
|
const reasoningEffort = clampedReasoning === "off" ? undefined : clampedReasoning;
|
|
126
126
|
return stream(model, context, {
|
|
@@ -137,11 +137,17 @@ function normalizeAzureBaseUrl(baseUrl) {
|
|
|
137
137
|
catch {
|
|
138
138
|
throw new Error(`Invalid Azure OpenAI base URL: ${baseUrl}`);
|
|
139
139
|
}
|
|
140
|
-
const isAzureHost = url.hostname.endsWith(".openai.azure.com") ||
|
|
140
|
+
const isAzureHost = url.hostname.endsWith(".openai.azure.com") ||
|
|
141
|
+
url.hostname.endsWith(".cognitiveservices.azure.com") ||
|
|
142
|
+
url.hostname.endsWith(".ai.azure.com");
|
|
141
143
|
const normalizedPath = url.pathname.replace(/\/+$/, "");
|
|
142
144
|
// Ensure Azure hosts have /openai/v1 as base path so the AzureOpenAI SDK
|
|
143
145
|
// can append /deployments/<model>/... and ?api-version=v1 correctly.
|
|
144
|
-
if (isAzureHost &&
|
|
146
|
+
if (isAzureHost &&
|
|
147
|
+
(normalizedPath === "" ||
|
|
148
|
+
normalizedPath === "/" ||
|
|
149
|
+
normalizedPath === "/openai" ||
|
|
150
|
+
normalizedPath === "/openai/v1/responses")) {
|
|
145
151
|
url.pathname = "/openai/v1";
|
|
146
152
|
url.search = "";
|
|
147
153
|
}
|
|
@@ -4,11 +4,12 @@ import { HttpProxyAgent } from "http-proxy-agent";
|
|
|
4
4
|
import { HttpsProxyAgent } from "https-proxy-agent";
|
|
5
5
|
import { calculateCost } from "../models.js";
|
|
6
6
|
import { AssistantMessageEventStream } from "../utils/event-stream.js";
|
|
7
|
+
import { providerHeadersToRecord } from "../utils/headers.js";
|
|
7
8
|
import { parseStreamingJson } from "../utils/json-parse.js";
|
|
8
9
|
import { resolveHttpProxyUrlForTarget } from "../utils/node-http-proxy.js";
|
|
9
10
|
import { getProviderEnvValue } from "../utils/provider-env.js";
|
|
10
11
|
import { sanitizeSurrogates } from "../utils/sanitize-unicode.js";
|
|
11
|
-
import { adjustMaxTokensForThinking, buildBaseOptions, clampReasoning } from "./simple-options.js";
|
|
12
|
+
import { adjustMaxTokensForThinking, buildBaseOptions, clampMaxTokensToContext, clampReasoning, } from "./simple-options.js";
|
|
12
13
|
import { transformMessages } from "./transform-messages.js";
|
|
13
14
|
const EMPTY_TEXT_PLACEHOLDER = "<empty>";
|
|
14
15
|
export const stream = (model, context, options = {}) => {
|
|
@@ -105,8 +106,9 @@ export const stream = (model, context, options = {}) => {
|
|
|
105
106
|
}
|
|
106
107
|
try {
|
|
107
108
|
const client = new BedrockRuntimeClient(config);
|
|
108
|
-
|
|
109
|
-
|
|
109
|
+
const customHeaders = providerHeadersToRecord(options.headers);
|
|
110
|
+
if (customHeaders) {
|
|
111
|
+
addCustomHeadersMiddleware(client, customHeaders);
|
|
110
112
|
}
|
|
111
113
|
const cacheRetention = resolveCacheRetention(options.cacheRetention, options.env);
|
|
112
114
|
const inferenceMaxTokens = options.maxTokens ?? (isAnthropicClaudeModel(model) ? model.maxTokens : undefined);
|
|
@@ -267,7 +269,7 @@ function addCustomHeadersMiddleware(client, headers) {
|
|
|
267
269
|
client.middlewareStack.add(middleware, { step: "build", name: "pi-ai-custom-headers", priority: "low" });
|
|
268
270
|
}
|
|
269
271
|
export const streamSimple = (model, context, options) => {
|
|
270
|
-
const base = buildBaseOptions(model, options, undefined);
|
|
272
|
+
const base = buildBaseOptions(model, context, options, undefined);
|
|
271
273
|
if (!options?.reasoning) {
|
|
272
274
|
return stream(model, context, { ...base, reasoning: undefined });
|
|
273
275
|
}
|
|
@@ -282,13 +284,14 @@ export const streamSimple = (model, context, options) => {
|
|
|
282
284
|
// Undefined means the caller did not request an output cap; let the helper use the model cap.
|
|
283
285
|
// Do not coerce to 0 here, or the thinking budget would become the entire maxTokens value.
|
|
284
286
|
const adjusted = adjustMaxTokensForThinking(base.maxTokens, model.maxTokens, options.reasoning, options.thinkingBudgets);
|
|
287
|
+
const maxTokens = clampMaxTokensToContext(model, context, adjusted.maxTokens);
|
|
285
288
|
return stream(model, context, {
|
|
286
289
|
...base,
|
|
287
|
-
maxTokens
|
|
290
|
+
maxTokens,
|
|
288
291
|
reasoning: options.reasoning,
|
|
289
292
|
thinkingBudgets: {
|
|
290
293
|
...(options.thinkingBudgets || {}),
|
|
291
|
-
[clampReasoning(options.reasoning)]: adjusted.thinkingBudget,
|
|
294
|
+
[clampReasoning(options.reasoning)]: Math.min(adjusted.thinkingBudget, Math.max(0, maxTokens - 1024)),
|
|
292
295
|
},
|
|
293
296
|
});
|
|
294
297
|
}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { getProviderEnvValue } from "../utils/provider-env.js";
|
|
2
1
|
/** Workers AI direct endpoint. */
|
|
3
2
|
export const CLOUDFLARE_WORKERS_AI_BASE_URL = "https://api.cloudflare.com/client/v4/accounts/{CLOUDFLARE_ACCOUNT_ID}/ai/v1";
|
|
4
3
|
/** AI Gateway Unified API. https://developers.cloudflare.com/ai-gateway/usage/unified-api/ */
|
|
@@ -7,21 +6,4 @@ export const CLOUDFLARE_AI_GATEWAY_COMPAT_BASE_URL = "https://gateway.ai.cloudfl
|
|
|
7
6
|
export const CLOUDFLARE_AI_GATEWAY_OPENAI_BASE_URL = "https://gateway.ai.cloudflare.com/v1/{CLOUDFLARE_ACCOUNT_ID}/{CLOUDFLARE_GATEWAY_ID}/openai";
|
|
8
7
|
/** AI Gateway → Anthropic passthrough. */
|
|
9
8
|
export const CLOUDFLARE_AI_GATEWAY_ANTHROPIC_BASE_URL = "https://gateway.ai.cloudflare.com/v1/{CLOUDFLARE_ACCOUNT_ID}/{CLOUDFLARE_GATEWAY_ID}/anthropic";
|
|
10
|
-
export function isCloudflareProvider(provider) {
|
|
11
|
-
return provider === "cloudflare-workers-ai" || provider === "cloudflare-ai-gateway";
|
|
12
|
-
}
|
|
13
|
-
/** Substitute `{VAR}` placeholders in a Cloudflare baseUrl from provider env or process.env. */
|
|
14
|
-
export function resolveCloudflareBaseUrl(model, env) {
|
|
15
|
-
const url = model.baseUrl;
|
|
16
|
-
if (!url.includes("{"))
|
|
17
|
-
return url;
|
|
18
|
-
const baseUrl = url.replace(/\{([A-Z_][A-Z0-9_]*)\}/g, (_match, name) => {
|
|
19
|
-
const value = getProviderEnvValue(name, env);
|
|
20
|
-
if (!value) {
|
|
21
|
-
throw new Error(`${name} is required for provider ${model.provider} but is not set.`);
|
|
22
|
-
}
|
|
23
|
-
return value;
|
|
24
|
-
});
|
|
25
|
-
return baseUrl;
|
|
26
|
-
}
|
|
27
9
|
//# sourceMappingURL=cloudflare.js.map
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { GoogleGenAI, } from "@google/genai";
|
|
2
2
|
import { calculateCost, clampThinkingLevel } from "../models.js";
|
|
3
3
|
import { AssistantMessageEventStream } from "../utils/event-stream.js";
|
|
4
|
+
import { providerHeadersToRecord } from "../utils/headers.js";
|
|
4
5
|
import { sanitizeSurrogates } from "../utils/sanitize-unicode.js";
|
|
5
6
|
import { convertMessages, convertTools, isThinkingPart, mapStopReason, mapToolChoice, retainThoughtSignature, } from "./google-shared.js";
|
|
6
7
|
import { buildBaseOptions } from "./simple-options.js";
|
|
@@ -161,6 +162,7 @@ export const stream = (model, context, options) => {
|
|
|
161
162
|
output: (chunk.usageMetadata.candidatesTokenCount || 0) + (chunk.usageMetadata.thoughtsTokenCount || 0),
|
|
162
163
|
cacheRead: chunk.usageMetadata.cachedContentTokenCount || 0,
|
|
163
164
|
cacheWrite: 0,
|
|
165
|
+
reasoning: chunk.usageMetadata.thoughtsTokenCount || 0,
|
|
164
166
|
totalTokens: chunk.usageMetadata.totalTokenCount || 0,
|
|
165
167
|
cost: {
|
|
166
168
|
input: 0,
|
|
@@ -220,7 +222,7 @@ export const streamSimple = (model, context, options) => {
|
|
|
220
222
|
if (!apiKey) {
|
|
221
223
|
throw new Error(`No API key for provider: ${model.provider}`);
|
|
222
224
|
}
|
|
223
|
-
const base = buildBaseOptions(model, options, apiKey);
|
|
225
|
+
const base = buildBaseOptions(model, context, options, apiKey);
|
|
224
226
|
if (!options?.reasoning) {
|
|
225
227
|
return stream(model, context, { ...base, thinking: { enabled: false } });
|
|
226
228
|
}
|
|
@@ -250,8 +252,9 @@ function createClient(model, apiKey, optionsHeaders) {
|
|
|
250
252
|
httpOptions.baseUrl = model.baseUrl;
|
|
251
253
|
httpOptions.apiVersion = ""; // baseUrl already includes version path, don't append
|
|
252
254
|
}
|
|
253
|
-
|
|
254
|
-
|
|
255
|
+
const headers = providerHeadersToRecord({ ...model.headers, ...optionsHeaders });
|
|
256
|
+
if (headers) {
|
|
257
|
+
httpOptions.headers = headers;
|
|
255
258
|
}
|
|
256
259
|
return new GoogleGenAI({
|
|
257
260
|
apiKey,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { GoogleGenAI, ResourceScope, ThinkingLevel, } from "@google/genai";
|
|
2
2
|
import { calculateCost, clampThinkingLevel } from "../models.js";
|
|
3
3
|
import { AssistantMessageEventStream } from "../utils/event-stream.js";
|
|
4
|
+
import { providerHeadersToRecord } from "../utils/headers.js";
|
|
4
5
|
import { getProviderEnvValue } from "../utils/provider-env.js";
|
|
5
6
|
import { sanitizeSurrogates } from "../utils/sanitize-unicode.js";
|
|
6
7
|
import { convertMessages, convertTools, isThinkingPart, mapStopReason, mapToolChoice, retainThoughtSignature, } from "./google-shared.js";
|
|
@@ -170,6 +171,7 @@ export const stream = (model, context, options) => {
|
|
|
170
171
|
output: (chunk.usageMetadata.candidatesTokenCount || 0) + (chunk.usageMetadata.thoughtsTokenCount || 0),
|
|
171
172
|
cacheRead: chunk.usageMetadata.cachedContentTokenCount || 0,
|
|
172
173
|
cacheWrite: 0,
|
|
174
|
+
reasoning: chunk.usageMetadata.thoughtsTokenCount || 0,
|
|
173
175
|
totalTokens: chunk.usageMetadata.totalTokenCount || 0,
|
|
174
176
|
cost: {
|
|
175
177
|
input: 0,
|
|
@@ -225,7 +227,7 @@ export const stream = (model, context, options) => {
|
|
|
225
227
|
return stream;
|
|
226
228
|
};
|
|
227
229
|
export const streamSimple = (model, context, options) => {
|
|
228
|
-
const base = buildBaseOptions(model, options, undefined);
|
|
230
|
+
const base = buildBaseOptions(model, context, options, undefined);
|
|
229
231
|
if (!options?.reasoning) {
|
|
230
232
|
return stream(model, context, {
|
|
231
233
|
...base,
|
|
@@ -281,8 +283,9 @@ function buildHttpOptions(model, optionsHeaders) {
|
|
|
281
283
|
httpOptions.apiVersion = "";
|
|
282
284
|
}
|
|
283
285
|
}
|
|
284
|
-
|
|
285
|
-
|
|
286
|
+
const headers = providerHeadersToRecord({ ...model.headers, ...optionsHeaders });
|
|
287
|
+
if (headers) {
|
|
288
|
+
httpOptions.headers = headers;
|
|
286
289
|
}
|
|
287
290
|
return Object.keys(httpOptions).length > 0 ? httpOptions : undefined;
|
|
288
291
|
}
|
|
@@ -65,7 +65,7 @@ export const streamSimple = (model, context, options) => {
|
|
|
65
65
|
if (!apiKey) {
|
|
66
66
|
throw new Error(`No API key for provider: ${model.provider}`);
|
|
67
67
|
}
|
|
68
|
-
const base = buildBaseOptions(model, options, apiKey);
|
|
68
|
+
const base = buildBaseOptions(model, context, options, apiKey);
|
|
69
69
|
const clampedReasoning = options?.reasoning ? clampThinkingLevel(model, options.reasoning) : undefined;
|
|
70
70
|
const reasoning = clampedReasoning === "off" ? undefined : clampedReasoning;
|
|
71
71
|
const shouldUseReasoning = model.reasoning && reasoning !== undefined;
|
|
@@ -33,9 +33,6 @@ const JWT_CLAIM_PATH = "https://api.openai.com/auth";
|
|
|
33
33
|
const DEFAULT_MAX_RETRIES = 0;
|
|
34
34
|
const BASE_DELAY_MS = 1000;
|
|
35
35
|
const DEFAULT_MAX_RETRY_DELAY_MS = 60_000;
|
|
36
|
-
// Keep a bounded pre-header timeout so zero-event Codex SSE stalls fail instead of
|
|
37
|
-
// leaving callers stuck on "Working..." indefinitely. See #4945.
|
|
38
|
-
const DEFAULT_SSE_HEADER_TIMEOUT_MS = 20_000;
|
|
39
36
|
const DEFAULT_WEBSOCKET_CONNECT_TIMEOUT_MS = 15_000;
|
|
40
37
|
const CODEX_TOOL_CALL_PROVIDERS = new Set(["openai", "openai-codex", "opencode"]);
|
|
41
38
|
const WEBSOCKET_MESSAGE_TOO_BIG_CLOSE_CODE = 1009;
|
|
@@ -110,19 +107,6 @@ function normalizeTimeoutMs(value) {
|
|
|
110
107
|
}
|
|
111
108
|
return Math.floor(value);
|
|
112
109
|
}
|
|
113
|
-
function createSSEHeaderTimeout() {
|
|
114
|
-
const controller = new AbortController();
|
|
115
|
-
let error;
|
|
116
|
-
const timeout = setTimeout(() => {
|
|
117
|
-
error = new Error(`Codex SSE response headers timed out after ${DEFAULT_SSE_HEADER_TIMEOUT_MS}ms`);
|
|
118
|
-
controller.abort(error);
|
|
119
|
-
}, DEFAULT_SSE_HEADER_TIMEOUT_MS);
|
|
120
|
-
return {
|
|
121
|
-
signal: controller.signal,
|
|
122
|
-
clear: () => clearTimeout(timeout),
|
|
123
|
-
error: () => error,
|
|
124
|
-
};
|
|
125
|
-
}
|
|
126
110
|
// ============================================================================
|
|
127
111
|
// Main Stream Function
|
|
128
112
|
// ============================================================================
|
|
@@ -161,7 +145,7 @@ export const stream = (model, context, options) => {
|
|
|
161
145
|
const sseHeaders = buildSSEHeaders(model.headers, options?.headers, accountId, apiKey, options?.sessionId);
|
|
162
146
|
const websocketHeaders = buildWebSocketHeaders(model.headers, options?.headers, accountId, apiKey, websocketRequestId);
|
|
163
147
|
const bodyJson = JSON.stringify(body);
|
|
164
|
-
const
|
|
148
|
+
const httpTimeoutMs = normalizeTimeoutMs(options?.timeoutMs);
|
|
165
149
|
const websocketConnectTimeoutMs = normalizeTimeoutMs(options?.websocketConnectTimeoutMs);
|
|
166
150
|
const transport = options?.transport || "auto";
|
|
167
151
|
const websocketDisabledForSession = transport !== "sse" && isWebSocketSseFallbackActive(options?.sessionId);
|
|
@@ -176,7 +160,7 @@ export const stream = (model, context, options) => {
|
|
|
176
160
|
try {
|
|
177
161
|
await processWebSocketStream(resolveCodexWebSocketUrl(model.baseUrl), body, websocketHeaders, output, stream, model, () => {
|
|
178
162
|
websocketStarted = true;
|
|
179
|
-
},
|
|
163
|
+
}, httpTimeoutMs, websocketConnectTimeoutMs, options);
|
|
180
164
|
if (options?.signal?.aborted) {
|
|
181
165
|
throw new Error("Request was aborted");
|
|
182
166
|
}
|
|
@@ -223,8 +207,8 @@ export const stream = (model, context, options) => {
|
|
|
223
207
|
throw new Error("Request was aborted");
|
|
224
208
|
}
|
|
225
209
|
try {
|
|
226
|
-
const
|
|
227
|
-
const combinedSignal = combineAbortSignals([options?.signal,
|
|
210
|
+
const headerTimeoutSignal = httpTimeoutMs !== undefined && httpTimeoutMs > 0 ? AbortSignal.timeout(httpTimeoutMs) : undefined;
|
|
211
|
+
const combinedSignal = combineAbortSignals([options?.signal, headerTimeoutSignal]);
|
|
228
212
|
try {
|
|
229
213
|
response = await fetch(resolveCodexUrl(model.baseUrl), {
|
|
230
214
|
method: "POST",
|
|
@@ -234,12 +218,13 @@ export const stream = (model, context, options) => {
|
|
|
234
218
|
});
|
|
235
219
|
}
|
|
236
220
|
catch (error) {
|
|
237
|
-
|
|
238
|
-
|
|
221
|
+
if (headerTimeoutSignal?.aborted && !options?.signal?.aborted) {
|
|
222
|
+
throw new Error(`Codex SSE response headers timed out after ${httpTimeoutMs}ms`);
|
|
223
|
+
}
|
|
224
|
+
throw error;
|
|
239
225
|
}
|
|
240
226
|
finally {
|
|
241
227
|
combinedSignal.cleanup();
|
|
242
|
-
headerTimeout.clear();
|
|
243
228
|
}
|
|
244
229
|
await options?.onResponse?.({ status: response.status, headers: headersToRecord(response.headers) }, model);
|
|
245
230
|
if (response.ok) {
|
|
@@ -312,7 +297,7 @@ export const streamSimple = (model, context, options) => {
|
|
|
312
297
|
if (!apiKey) {
|
|
313
298
|
throw new Error(`No API key for provider: ${model.provider}`);
|
|
314
299
|
}
|
|
315
|
-
const base = buildBaseOptions(model, options, apiKey);
|
|
300
|
+
const base = buildBaseOptions(model, context, options, apiKey);
|
|
316
301
|
const clampedReasoning = options?.reasoning ? clampThinkingLevel(model, options.reasoning) : undefined;
|
|
317
302
|
const reasoningEffort = clampedReasoning === "off" ? undefined : clampedReasoning;
|
|
318
303
|
return stream(model, context, {
|
|
@@ -1164,7 +1149,12 @@ function createCodexRequestId() {
|
|
|
1164
1149
|
function buildBaseCodexHeaders(initHeaders, additionalHeaders, accountId, token) {
|
|
1165
1150
|
const headers = new Headers(initHeaders);
|
|
1166
1151
|
for (const [key, value] of Object.entries(additionalHeaders || {})) {
|
|
1167
|
-
|
|
1152
|
+
if (value === null) {
|
|
1153
|
+
headers.delete(key);
|
|
1154
|
+
}
|
|
1155
|
+
else {
|
|
1156
|
+
headers.set(key, value);
|
|
1157
|
+
}
|
|
1168
1158
|
}
|
|
1169
1159
|
headers.set("Authorization", `Bearer ${token}`);
|
|
1170
1160
|
headers.set("chatgpt-account-id", accountId);
|