@psnext/slingcli 2.5.20260617-1 → 2.5.20260617-2

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.
Files changed (64) hide show
  1. package/node_modules/@earendil-works/pi-agent-core/package.json +2 -2
  2. package/node_modules/@earendil-works/pi-ai/dist/env-api-keys.js +21 -57
  3. package/node_modules/@earendil-works/pi-ai/dist/models.generated.js +116 -155
  4. package/node_modules/@earendil-works/pi-ai/dist/models.js +4 -1
  5. package/node_modules/@earendil-works/pi-ai/dist/providers/amazon-bedrock.js +52 -34
  6. package/node_modules/@earendil-works/pi-ai/dist/providers/anthropic.js +11 -9
  7. package/node_modules/@earendil-works/pi-ai/dist/providers/azure-openai-responses.js +7 -4
  8. package/node_modules/@earendil-works/pi-ai/dist/providers/cloudflare.js +4 -3
  9. package/node_modules/@earendil-works/pi-ai/dist/providers/google-vertex.js +15 -5
  10. package/node_modules/@earendil-works/pi-ai/dist/providers/google.js +2 -1
  11. package/node_modules/@earendil-works/pi-ai/dist/providers/openai-codex-responses.js +18 -17
  12. package/node_modules/@earendil-works/pi-ai/dist/providers/openai-completions.js +21 -8
  13. package/node_modules/@earendil-works/pi-ai/dist/providers/openai-responses-shared.js +2 -1
  14. package/node_modules/@earendil-works/pi-ai/dist/providers/openai-responses.js +8 -7
  15. package/node_modules/@earendil-works/pi-ai/dist/providers/simple-options.js +1 -0
  16. package/node_modules/@earendil-works/pi-ai/dist/stream.js +1 -1
  17. package/node_modules/@earendil-works/pi-ai/dist/utils/node-http-proxy.js +17 -22
  18. package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/anthropic.js +2 -1
  19. package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/openai-codex.js +3 -2
  20. package/node_modules/@earendil-works/pi-ai/dist/utils/provider-env.js +44 -0
  21. package/node_modules/@earendil-works/pi-ai/package.json +1 -1
  22. package/node_modules/@earendil-works/pi-coding-agent/dist/bun/restore-sandbox-env.js +4 -0
  23. package/node_modules/@earendil-works/pi-coding-agent/dist/cli/args.js +1 -1
  24. package/node_modules/@earendil-works/pi-coding-agent/dist/core/agent-session.js +10 -7
  25. package/node_modules/@earendil-works/pi-coding-agent/dist/core/auth-storage.js +8 -1
  26. package/node_modules/@earendil-works/pi-coding-agent/dist/core/compaction/branch-summarization.js +2 -2
  27. package/node_modules/@earendil-works/pi-coding-agent/dist/core/compaction/compaction.js +10 -10
  28. package/node_modules/@earendil-works/pi-coding-agent/dist/core/export-html/vendor/marked.min.js +75 -3
  29. package/node_modules/@earendil-works/pi-coding-agent/dist/core/http-dispatcher.js +17 -1
  30. package/node_modules/@earendil-works/pi-coding-agent/dist/core/model-registry.js +8 -4
  31. package/node_modules/@earendil-works/pi-coding-agent/dist/core/project-trust.js +1 -1
  32. package/node_modules/@earendil-works/pi-coding-agent/dist/core/provider-attribution.js +10 -0
  33. package/node_modules/@earendil-works/pi-coding-agent/dist/core/resolve-config-value.js +19 -19
  34. package/node_modules/@earendil-works/pi-coding-agent/dist/core/sdk.js +2 -0
  35. package/node_modules/@earendil-works/pi-coding-agent/dist/core/system-prompt.js +6 -6
  36. package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/bash.js +4 -0
  37. package/node_modules/@earendil-works/pi-coding-agent/dist/main.js +17 -5
  38. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/footer.js +4 -0
  39. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/login-dialog.js +0 -1
  40. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/session-selector.js +0 -5
  41. package/node_modules/@earendil-works/pi-coding-agent/dist/package-manager-cli.js +6 -6
  42. package/node_modules/@earendil-works/pi-coding-agent/dist/utils/child-process.js +23 -5
  43. package/node_modules/@earendil-works/pi-coding-agent/npm-shrinkwrap.json +17 -17
  44. package/node_modules/@earendil-works/pi-coding-agent/package.json +4 -4
  45. package/node_modules/@earendil-works/pi-tui/dist/components/editor.js +5 -1
  46. package/node_modules/@earendil-works/pi-tui/package.json +2 -2
  47. package/node_modules/marked/README.md +13 -4
  48. package/node_modules/marked/bin/main.js +16 -14
  49. package/node_modules/marked/bin/marked.js +2 -1
  50. package/node_modules/marked/lib/marked.d.ts +157 -125
  51. package/node_modules/marked/lib/marked.esm.js +67 -2179
  52. package/node_modules/marked/lib/marked.esm.js.map +3 -3
  53. package/node_modules/marked/lib/marked.umd.js +67 -2201
  54. package/node_modules/marked/lib/marked.umd.js.map +3 -3
  55. package/node_modules/marked/man/marked.1 +4 -2
  56. package/node_modules/marked/man/marked.1.md +2 -1
  57. package/node_modules/marked/package.json +27 -35
  58. package/package.json +6 -6
  59. package/slingshot/index.js +253 -253
  60. package/node_modules/marked/lib/marked.cjs +0 -2211
  61. package/node_modules/marked/lib/marked.cjs.map +0 -7
  62. package/node_modules/marked/lib/marked.d.cts +0 -728
  63. package/node_modules/marked/marked.min.js +0 -69
  64. /package/node_modules/marked/{LICENSE.md → LICENSE} +0 -0
@@ -20,10 +20,13 @@ export function getModels(provider) {
20
20
  return models ? Array.from(models.values()) : [];
21
21
  }
22
22
  export function calculateCost(model, usage) {
23
+ // Anthropic charges 2x base input for 1h cache writes.
24
+ const longWrite = usage.cacheWrite1h ?? 0;
25
+ const shortWrite = usage.cacheWrite - longWrite;
23
26
  usage.cost.input = (model.cost.input / 1000000) * usage.input;
24
27
  usage.cost.output = (model.cost.output / 1000000) * usage.output;
25
28
  usage.cost.cacheRead = (model.cost.cacheRead / 1000000) * usage.cacheRead;
26
- usage.cost.cacheWrite = (model.cost.cacheWrite / 1000000) * usage.cacheWrite;
29
+ usage.cost.cacheWrite = (model.cost.cacheWrite * shortWrite + model.cost.input * 2 * longWrite) / 1000000;
27
30
  usage.cost.total = usage.cost.input + usage.cost.output + usage.cost.cacheRead + usage.cost.cacheWrite;
28
31
  return usage.cost;
29
32
  }
@@ -1,9 +1,12 @@
1
1
  import { BedrockRuntimeClient, BedrockRuntimeServiceException, StopReason as BedrockStopReason, CachePointType, CacheTTL, ConversationRole, ConverseStreamCommand, ImageFormat, ToolResultStatus, } from "@aws-sdk/client-bedrock-runtime";
2
2
  import { NodeHttpHandler } from "@smithy/node-http-handler";
3
+ import { HttpProxyAgent } from "http-proxy-agent";
4
+ import { HttpsProxyAgent } from "https-proxy-agent";
3
5
  import { calculateCost } from "../models.js";
4
6
  import { AssistantMessageEventStream } from "../utils/event-stream.js";
5
7
  import { parseStreamingJson } from "../utils/json-parse.js";
6
- import { createHttpProxyAgentsForTarget } from "../utils/node-http-proxy.js";
8
+ import { resolveHttpProxyUrlForTarget } from "../utils/node-http-proxy.js";
9
+ import { getProviderEnvValue } from "../utils/provider-env.js";
7
10
  import { sanitizeSurrogates } from "../utils/sanitize-unicode.js";
8
11
  import { adjustMaxTokensForThinking, buildBaseOptions, clampReasoning } from "./simple-options.js";
9
12
  import { transformMessages } from "./transform-messages.js";
@@ -30,21 +33,22 @@ export const streamBedrock = (model, context, options = {}) => {
30
33
  };
31
34
  const blocks = output.content;
32
35
  const config = {
33
- profile: options.profile,
36
+ profile: options.profile || getProviderEnvValue("AWS_PROFILE", options.env),
34
37
  };
35
38
  const configuredRegion = getConfiguredBedrockRegion(options);
36
- const hasConfiguredProfile = hasConfiguredBedrockProfile();
39
+ const hasAmbientConfiguredProfile = Boolean(getProviderEnvValue("AWS_PROFILE"));
37
40
  const endpointRegion = getStandardBedrockEndpointRegion(model.baseUrl);
38
- const useExplicitEndpoint = shouldUseExplicitBedrockEndpoint(model.baseUrl, configuredRegion, hasConfiguredProfile);
39
- // Only pin standard AWS Bedrock runtime endpoints when no region/profile is configured.
41
+ const useExplicitEndpoint = shouldUseExplicitBedrockEndpoint(model.baseUrl, configuredRegion, hasAmbientConfiguredProfile);
42
+ // Only pin standard AWS Bedrock runtime endpoints when no region or ambient AWS_PROFILE is configured.
40
43
  // This preserves custom endpoints (VPC/proxy) from #3402 without forcing built-in
41
44
  // catalog defaults such as us-east-1 to override AWS_REGION/AWS_PROFILE.
42
45
  if (useExplicitEndpoint) {
43
46
  config.endpoint = model.baseUrl;
44
47
  }
45
48
  // Resolve bearer token for Bedrock API key auth.
46
- const bearerToken = options.bearerToken || process.env.AWS_BEARER_TOKEN_BEDROCK || undefined;
47
- const useBearerToken = bearerToken !== undefined && process.env.AWS_BEDROCK_SKIP_AUTH !== "1";
49
+ const skipAuth = getProviderEnvValue("AWS_BEDROCK_SKIP_AUTH", options.env) === "1";
50
+ const bearerToken = options.bearerToken || getProviderEnvValue("AWS_BEARER_TOKEN_BEDROCK", options.env) || undefined;
51
+ const useBearerToken = bearerToken !== undefined && !skipAuth;
48
52
  // in Node.js/Bun environment only
49
53
  if (typeof process !== "undefined" && (process.versions?.node || process.versions?.bun)) {
50
54
  // Region resolution: ARN-embedded > explicit option > env vars > SDK default chain.
@@ -60,24 +64,31 @@ export const streamBedrock = (model, context, options = {}) => {
60
64
  else if (endpointRegion && useExplicitEndpoint) {
61
65
  config.region = endpointRegion;
62
66
  }
63
- else if (!hasConfiguredProfile) {
67
+ else if (!hasAmbientConfiguredProfile) {
64
68
  config.region = "us-east-1";
65
69
  }
66
70
  // Support proxies that don't need authentication
67
- if (process.env.AWS_BEDROCK_SKIP_AUTH === "1") {
71
+ if (skipAuth) {
68
72
  config.credentials = {
69
73
  accessKeyId: "dummy-access-key",
70
74
  secretAccessKey: "dummy-secret-key",
71
75
  };
72
76
  }
73
- const proxyAgents = createHttpProxyAgentsForTarget(model.baseUrl);
74
- if (proxyAgents) {
77
+ const credentials = getConfiguredBedrockCredentials(options.env);
78
+ if (!skipAuth && credentials) {
79
+ config.credentials = credentials;
80
+ }
81
+ const proxyUrl = resolveHttpProxyUrlForTarget(model.baseUrl, options.env);
82
+ if (proxyUrl) {
75
83
  // Bedrock runtime uses NodeHttp2Handler by default since v3.798.0, which is based
76
84
  // on `http2` module and has no support for http agent.
77
85
  // Use NodeHttpHandler to support HTTP(S) proxy agents.
78
- config.requestHandler = new NodeHttpHandler(proxyAgents);
86
+ config.requestHandler = new NodeHttpHandler({
87
+ httpAgent: new HttpProxyAgent(proxyUrl),
88
+ httpsAgent: new HttpsProxyAgent(proxyUrl),
89
+ });
79
90
  }
80
- else if (process.env.AWS_BEDROCK_FORCE_HTTP1 === "1") {
91
+ else if (getProviderEnvValue("AWS_BEDROCK_FORCE_HTTP1", options.env) === "1") {
81
92
  // Some custom endpoints require HTTP/1.1 instead of HTTP/2
82
93
  config.requestHandler = new NodeHttpHandler();
83
94
  }
@@ -97,12 +108,12 @@ export const streamBedrock = (model, context, options = {}) => {
97
108
  if (options.headers && Object.keys(options.headers).length > 0) {
98
109
  addCustomHeadersMiddleware(client, options.headers);
99
110
  }
100
- const cacheRetention = resolveCacheRetention(options.cacheRetention);
111
+ const cacheRetention = resolveCacheRetention(options.cacheRetention, options.env);
101
112
  const inferenceMaxTokens = options.maxTokens ?? (isAnthropicClaudeModel(model) ? model.maxTokens : undefined);
102
113
  let commandInput = {
103
114
  modelId: model.id,
104
- messages: convertMessages(context, model, cacheRetention),
105
- system: buildSystemPrompt(context.systemPrompt, model, cacheRetention),
115
+ messages: convertMessages(context, model, cacheRetention, options.env),
116
+ system: buildSystemPrompt(context.systemPrompt, model, cacheRetention, options.env),
106
117
  inferenceConfig: {
107
118
  ...(inferenceMaxTokens !== undefined && { maxTokens: inferenceMaxTokens }),
108
119
  ...(options.temperature !== undefined && { temperature: options.temperature }),
@@ -432,11 +443,11 @@ function mapThinkingLevelToEffort(model, level) {
432
443
  * Resolve cache retention preference.
433
444
  * Defaults to "short" and uses PI_CACHE_RETENTION for backward compatibility.
434
445
  */
435
- function resolveCacheRetention(cacheRetention) {
446
+ function resolveCacheRetention(cacheRetention, env) {
436
447
  if (cacheRetention) {
437
448
  return cacheRetention;
438
449
  }
439
- if (typeof process !== "undefined" && process.env.PI_CACHE_RETENTION === "long") {
450
+ if (getProviderEnvValue("PI_CACHE_RETENTION", env) === "long") {
440
451
  return "long";
441
452
  }
442
453
  return "short";
@@ -467,13 +478,13 @@ function isAnthropicClaudeModel(model) {
467
478
  * As a last resort, set AWS_BEDROCK_FORCE_CACHE=1 to enable cache points.
468
479
  * Amazon Nova models have automatic caching and don't need explicit cache points.
469
480
  */
470
- function supportsPromptCaching(model) {
481
+ function supportsPromptCaching(model, env) {
471
482
  const candidates = getModelMatchCandidates(model.id, model.name);
472
483
  const hasClaudeRef = candidates.some((s) => s.includes("claude"));
473
484
  if (!hasClaudeRef) {
474
485
  // Application inference profiles don't contain the model name in the ARN.
475
486
  // Allow users to force cache points via environment variable.
476
- if (typeof process !== "undefined" && process.env.AWS_BEDROCK_FORCE_CACHE === "1")
487
+ if (getProviderEnvValue("AWS_BEDROCK_FORCE_CACHE", env) === "1")
477
488
  return true;
478
489
  return false;
479
490
  }
@@ -499,12 +510,12 @@ function supportsPromptCaching(model) {
499
510
  function supportsThinkingSignature(model) {
500
511
  return isAnthropicClaudeModel(model);
501
512
  }
502
- function buildSystemPrompt(systemPrompt, model, cacheRetention) {
513
+ function buildSystemPrompt(systemPrompt, model, cacheRetention, env) {
503
514
  if (!systemPrompt)
504
515
  return undefined;
505
516
  const blocks = [{ text: sanitizeSurrogates(systemPrompt) }];
506
517
  // Add cache point for supported Claude models when caching is enabled
507
- if (cacheRetention !== "none" && supportsPromptCaching(model)) {
518
+ if (cacheRetention !== "none" && supportsPromptCaching(model, env)) {
508
519
  blocks.push({
509
520
  cachePoint: { type: CachePointType.DEFAULT, ...(cacheRetention === "long" ? { ttl: CacheTTL.ONE_HOUR } : {}) },
510
521
  });
@@ -538,7 +549,7 @@ function convertToolResultContent(content) {
538
549
  result.push({ text: EMPTY_TEXT_PLACEHOLDER });
539
550
  return result;
540
551
  }
541
- function convertMessages(context, model, cacheRetention) {
552
+ function convertMessages(context, model, cacheRetention, env) {
542
553
  const result = [];
543
554
  const transformedMessages = transformMessages(context.messages, model, normalizeToolCallId);
544
555
  for (let i = 0; i < transformedMessages.length; i++) {
@@ -683,7 +694,7 @@ function convertMessages(context, model, cacheRetention) {
683
694
  }
684
695
  }
685
696
  // Add cache point to the last user message for supported Claude models when caching is enabled
686
- if (cacheRetention !== "none" && supportsPromptCaching(model) && result.length > 0) {
697
+ if (cacheRetention !== "none" && supportsPromptCaching(model, env) && result.length > 0) {
687
698
  const lastMessage = result[result.length - 1];
688
699
  if (lastMessage.role === ConversationRole.USER && lastMessage.content) {
689
700
  lastMessage.content.push({
@@ -736,16 +747,23 @@ function mapStopReason(reason) {
736
747
  }
737
748
  }
738
749
  function getConfiguredBedrockRegion(options) {
739
- if (typeof process === "undefined") {
740
- return options.region;
741
- }
742
- return options.region || process.env.AWS_REGION || process.env.AWS_DEFAULT_REGION || undefined;
750
+ return (options.region ||
751
+ getProviderEnvValue("AWS_REGION", options.env) ||
752
+ getProviderEnvValue("AWS_DEFAULT_REGION", options.env) ||
753
+ undefined);
743
754
  }
744
- function hasConfiguredBedrockProfile() {
745
- if (typeof process === "undefined") {
746
- return false;
755
+ function getConfiguredBedrockCredentials(env) {
756
+ const accessKeyId = getProviderEnvValue("AWS_ACCESS_KEY_ID", env);
757
+ const secretAccessKey = getProviderEnvValue("AWS_SECRET_ACCESS_KEY", env);
758
+ if (!accessKeyId || !secretAccessKey) {
759
+ return undefined;
747
760
  }
748
- return Boolean(process.env.AWS_PROFILE);
761
+ const sessionToken = getProviderEnvValue("AWS_SESSION_TOKEN", env);
762
+ return {
763
+ accessKeyId,
764
+ secretAccessKey,
765
+ ...(sessionToken ? { sessionToken } : {}),
766
+ };
749
767
  }
750
768
  function getStandardBedrockEndpointRegion(baseUrl) {
751
769
  if (!baseUrl) {
@@ -760,12 +778,12 @@ function getStandardBedrockEndpointRegion(baseUrl) {
760
778
  return undefined;
761
779
  }
762
780
  }
763
- function shouldUseExplicitBedrockEndpoint(baseUrl, configuredRegion, hasConfiguredProfile) {
781
+ function shouldUseExplicitBedrockEndpoint(baseUrl, configuredRegion, hasAmbientConfiguredProfile) {
764
782
  const endpointRegion = getStandardBedrockEndpointRegion(baseUrl);
765
783
  if (!endpointRegion) {
766
784
  return true;
767
785
  }
768
- return !configuredRegion && !hasConfiguredProfile;
786
+ return !configuredRegion && !hasAmbientConfiguredProfile;
769
787
  }
770
788
  function isGovCloudBedrockTarget(model, options) {
771
789
  const region = getConfiguredBedrockRegion(options);
@@ -3,6 +3,7 @@ import { calculateCost } from "../models.js";
3
3
  import { AssistantMessageEventStream } from "../utils/event-stream.js";
4
4
  import { headersToRecord } from "../utils/headers.js";
5
5
  import { parseJsonWithRepair, parseStreamingJson } from "../utils/json-parse.js";
6
+ import { getProviderEnvValue } from "../utils/provider-env.js";
6
7
  import { sanitizeSurrogates } from "../utils/sanitize-unicode.js";
7
8
  import { resolveCloudflareBaseUrl } from "./cloudflare.js";
8
9
  import { buildCopilotDynamicHeaders, hasCopilotVisionInput } from "./github-copilot-headers.js";
@@ -12,17 +13,17 @@ import { transformMessages } from "./transform-messages.js";
12
13
  * Resolve cache retention preference.
13
14
  * Defaults to "short" and uses PI_CACHE_RETENTION for backward compatibility.
14
15
  */
15
- function resolveCacheRetention(cacheRetention) {
16
+ function resolveCacheRetention(cacheRetention, env) {
16
17
  if (cacheRetention) {
17
18
  return cacheRetention;
18
19
  }
19
- if (typeof process !== "undefined" && process.env.PI_CACHE_RETENTION === "long") {
20
+ if (getProviderEnvValue("PI_CACHE_RETENTION", env) === "long") {
20
21
  return "long";
21
22
  }
22
23
  return "short";
23
24
  }
24
- function getCacheControl(model, cacheRetention) {
25
- const retention = resolveCacheRetention(cacheRetention);
25
+ function getCacheControl(model, cacheRetention, env) {
26
+ const retention = resolveCacheRetention(cacheRetention, env);
26
27
  if (retention === "none") {
27
28
  return { retention };
28
29
  }
@@ -319,9 +320,9 @@ export const streamAnthropic = (model, context, options) => {
319
320
  hasImages,
320
321
  });
321
322
  }
322
- const cacheRetention = options?.cacheRetention ?? resolveCacheRetention();
323
+ const cacheRetention = resolveCacheRetention(options?.cacheRetention, options?.env);
323
324
  const cacheSessionId = cacheRetention === "none" ? undefined : options?.sessionId;
324
- const created = createClient(model, apiKey, options?.interleavedThinking ?? true, shouldUseFineGrainedToolStreamingBeta(model, context), options?.headers, copilotDynamicHeaders, cacheSessionId);
325
+ const created = createClient(model, apiKey, options?.interleavedThinking ?? true, shouldUseFineGrainedToolStreamingBeta(model, context), options?.headers, copilotDynamicHeaders, cacheSessionId, options?.env);
325
326
  client = created.client;
326
327
  isOAuth = created.isOAuthToken;
327
328
  }
@@ -348,6 +349,7 @@ export const streamAnthropic = (model, context, options) => {
348
349
  output.usage.output = event.message.usage.output_tokens || 0;
349
350
  output.usage.cacheRead = event.message.usage.cache_read_input_tokens || 0;
350
351
  output.usage.cacheWrite = event.message.usage.cache_creation_input_tokens || 0;
352
+ output.usage.cacheWrite1h = event.message.usage.cache_creation?.ephemeral_1h_input_tokens || 0;
351
353
  // Anthropic doesn't provide total_tokens, compute from components
352
354
  output.usage.totalTokens =
353
355
  output.usage.input + output.usage.output + output.usage.cacheRead + output.usage.cacheWrite;
@@ -587,7 +589,7 @@ export const streamSimpleAnthropic = (model, context, options) => {
587
589
  function isOAuthToken(apiKey) {
588
590
  return apiKey.includes("sk-ant-oat");
589
591
  }
590
- function createClient(model, apiKey, interleavedThinking, useFineGrainedToolStreamingBeta, optionsHeaders, dynamicHeaders, sessionId) {
592
+ function createClient(model, apiKey, interleavedThinking, useFineGrainedToolStreamingBeta, optionsHeaders, dynamicHeaders, sessionId, env) {
591
593
  // Adaptive thinking models have interleaved thinking built in, so skip the beta header.
592
594
  const needsInterleavedBeta = interleavedThinking && model.compat?.forceAdaptiveThinking !== true;
593
595
  const betaFeatures = [];
@@ -601,7 +603,7 @@ function createClient(model, apiKey, interleavedThinking, useFineGrainedToolStre
601
603
  const client = new Anthropic({
602
604
  apiKey: null,
603
605
  authToken: null,
604
- baseURL: resolveCloudflareBaseUrl(model),
606
+ baseURL: resolveCloudflareBaseUrl(model, env),
605
607
  dangerouslyAllowBrowser: true,
606
608
  defaultHeaders: mergeHeaders({
607
609
  accept: "application/json",
@@ -662,7 +664,7 @@ function createClient(model, apiKey, interleavedThinking, useFineGrainedToolStre
662
664
  return { client, isOAuthToken: false };
663
665
  }
664
666
  function buildParams(model, context, isOAuthToken, options) {
665
- const { cacheControl } = getCacheControl(model, options?.cacheRetention);
667
+ const { cacheControl } = getCacheControl(model, options?.cacheRetention, options?.env);
666
668
  const compat = getAnthropicCompat(model);
667
669
  const params = {
668
670
  model: model.id,
@@ -2,6 +2,7 @@ import { AzureOpenAI } from "openai";
2
2
  import { clampThinkingLevel } from "../models.js";
3
3
  import { AssistantMessageEventStream } from "../utils/event-stream.js";
4
4
  import { headersToRecord } from "../utils/headers.js";
5
+ import { getProviderEnvValue } from "../utils/provider-env.js";
5
6
  import { clampOpenAIPromptCacheKey } from "./openai-prompt-cache.js";
6
7
  import { convertResponsesMessages, convertResponsesTools, processResponsesStream } from "./openai-responses-shared.js";
7
8
  import { buildBaseOptions } from "./simple-options.js";
@@ -26,7 +27,7 @@ function resolveDeploymentName(model, options) {
26
27
  if (options?.azureDeploymentName) {
27
28
  return options.azureDeploymentName;
28
29
  }
29
- const mappedDeployment = parseDeploymentNameMap(process.env.AZURE_OPENAI_DEPLOYMENT_NAME_MAP).get(model.id);
30
+ const mappedDeployment = parseDeploymentNameMap(getProviderEnvValue("AZURE_OPENAI_DEPLOYMENT_NAME_MAP", options?.env)).get(model.id);
30
31
  return mappedDeployment || model.id;
31
32
  }
32
33
  function formatAzureOpenAIError(error) {
@@ -150,9 +151,11 @@ function buildDefaultBaseUrl(resourceName) {
150
151
  return `https://${resourceName}.openai.azure.com/openai/v1`;
151
152
  }
152
153
  function resolveAzureConfig(model, options) {
153
- const apiVersion = options?.azureApiVersion || process.env.AZURE_OPENAI_API_VERSION || DEFAULT_AZURE_API_VERSION;
154
- const baseUrl = options?.azureBaseUrl?.trim() || process.env.AZURE_OPENAI_BASE_URL?.trim() || undefined;
155
- const resourceName = options?.azureResourceName || process.env.AZURE_OPENAI_RESOURCE_NAME;
154
+ const apiVersion = options?.azureApiVersion ||
155
+ getProviderEnvValue("AZURE_OPENAI_API_VERSION", options?.env) ||
156
+ DEFAULT_AZURE_API_VERSION;
157
+ const baseUrl = options?.azureBaseUrl?.trim() || getProviderEnvValue("AZURE_OPENAI_BASE_URL", options?.env)?.trim() || undefined;
158
+ const resourceName = options?.azureResourceName || getProviderEnvValue("AZURE_OPENAI_RESOURCE_NAME", options?.env);
156
159
  let resolvedBaseUrl = baseUrl;
157
160
  if (!resolvedBaseUrl && resourceName) {
158
161
  resolvedBaseUrl = buildDefaultBaseUrl(resourceName);
@@ -1,3 +1,4 @@
1
+ import { getProviderEnvValue } from "../utils/provider-env.js";
1
2
  /** Workers AI direct endpoint. */
2
3
  export const CLOUDFLARE_WORKERS_AI_BASE_URL = "https://api.cloudflare.com/client/v4/accounts/{CLOUDFLARE_ACCOUNT_ID}/ai/v1";
3
4
  /** AI Gateway Unified API. https://developers.cloudflare.com/ai-gateway/usage/unified-api/ */
@@ -9,13 +10,13 @@ export const CLOUDFLARE_AI_GATEWAY_ANTHROPIC_BASE_URL = "https://gateway.ai.clou
9
10
  export function isCloudflareProvider(provider) {
10
11
  return provider === "cloudflare-workers-ai" || provider === "cloudflare-ai-gateway";
11
12
  }
12
- /** Substitute `{VAR}` placeholders in a Cloudflare baseUrl from process.env. */
13
- export function resolveCloudflareBaseUrl(model) {
13
+ /** Substitute `{VAR}` placeholders in a Cloudflare baseUrl from provider env or process.env. */
14
+ export function resolveCloudflareBaseUrl(model, env) {
14
15
  const url = model.baseUrl;
15
16
  if (!url.includes("{"))
16
17
  return url;
17
18
  const baseUrl = url.replace(/\{([A-Z_][A-Z0-9_]*)\}/g, (_match, name) => {
18
- const value = process.env[name];
19
+ const value = getProviderEnvValue(name, env);
19
20
  if (!value) {
20
21
  throw new Error(`${name} is required for provider ${model.provider} but is not set.`);
21
22
  }
@@ -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 { getProviderEnvValue } from "../utils/provider-env.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";
@@ -40,7 +41,7 @@ export const streamGoogleVertex = (model, context, options) => {
40
41
  // Create the client using either a Vertex API key, if provided, or ADC with project and location
41
42
  const client = apiKey
42
43
  ? createClientWithApiKey(model, apiKey, options?.headers)
43
- : createClient(model, resolveProject(options), resolveLocation(options), options?.headers);
44
+ : createClient(model, resolveProject(options), resolveLocation(options), options?.headers, options?.env);
44
45
  let params = buildParams(model, context, options);
45
46
  const nextParams = await options?.onPayload?.(params, model);
46
47
  if (nextParams !== undefined) {
@@ -251,12 +252,14 @@ export const streamSimpleGoogleVertex = (model, context, options) => {
251
252
  },
252
253
  });
253
254
  };
254
- function createClient(model, project, location, optionsHeaders) {
255
+ function createClient(model, project, location, optionsHeaders, env) {
256
+ const googleAuthOptions = buildGoogleAuthOptions(env);
255
257
  return new GoogleGenAI({
256
258
  vertexai: true,
257
259
  project,
258
260
  location,
259
261
  apiVersion: API_VERSION,
262
+ ...(googleAuthOptions ? { googleAuthOptions } : {}),
260
263
  httpOptions: buildHttpOptions(model, optionsHeaders),
261
264
  });
262
265
  }
@@ -299,6 +302,10 @@ function baseUrlIncludesApiVersion(baseUrl) {
299
302
  return /(?:^|\/)v\d+(?:beta\d*)?(?:\/|$)/.test(baseUrl);
300
303
  }
301
304
  }
305
+ function buildGoogleAuthOptions(env) {
306
+ const keyFilename = getProviderEnvValue("GOOGLE_APPLICATION_CREDENTIALS", env);
307
+ return keyFilename ? { keyFilename } : undefined;
308
+ }
302
309
  function resolveApiKey(options) {
303
310
  const apiKey = options?.apiKey?.trim();
304
311
  if (!apiKey || apiKey === GCP_VERTEX_CREDENTIALS_MARKER || isPlaceholderApiKey(apiKey)) {
@@ -310,14 +317,16 @@ function isPlaceholderApiKey(apiKey) {
310
317
  return /^<[^>]+>$/.test(apiKey);
311
318
  }
312
319
  function resolveProject(options) {
313
- const project = options?.project || process.env.GOOGLE_CLOUD_PROJECT || process.env.GCLOUD_PROJECT;
320
+ const project = options?.project ||
321
+ getProviderEnvValue("GOOGLE_CLOUD_PROJECT", options?.env) ||
322
+ getProviderEnvValue("GCLOUD_PROJECT", options?.env);
314
323
  if (!project) {
315
324
  throw new Error("Vertex AI requires a project ID. Set GOOGLE_CLOUD_PROJECT/GCLOUD_PROJECT or pass project in options.");
316
325
  }
317
326
  return project;
318
327
  }
319
328
  function resolveLocation(options) {
320
- const location = options?.location || process.env.GOOGLE_CLOUD_LOCATION;
329
+ const location = options?.location || getProviderEnvValue("GOOGLE_CLOUD_LOCATION", options?.env);
321
330
  if (!location) {
322
331
  throw new Error("Vertex AI requires a location. Set GOOGLE_CLOUD_LOCATION or pass location in options.");
323
332
  }
@@ -377,7 +386,8 @@ function isGemini3ProModel(model) {
377
386
  return /gemini-3(?:\.\d+)?-pro/.test(model.id.toLowerCase());
378
387
  }
379
388
  function isGemini3FlashModel(model) {
380
- return /gemini-3(?:\.\d+)?-flash/.test(model.id.toLowerCase());
389
+ const id = model.id.toLowerCase();
390
+ return /gemini-3(?:\.\d+)?-flash/.test(id) || id === "gemini-flash-latest" || id === "gemini-flash-lite-latest";
381
391
  }
382
392
  function getDisabledThinkingConfig(model) {
383
393
  // Google docs: Gemini 3.1 Pro cannot disable thinking, and Gemini 3 Flash / Flash-Lite
@@ -316,7 +316,8 @@ function isGemini3ProModel(model) {
316
316
  return /gemini-3(?:\.\d+)?-pro/.test(model.id.toLowerCase());
317
317
  }
318
318
  function isGemini3FlashModel(model) {
319
- return /gemini-3(?:\.\d+)?-flash/.test(model.id.toLowerCase());
319
+ const id = model.id.toLowerCase();
320
+ return /gemini-3(?:\.\d+)?-flash/.test(id) || id === "gemini-flash-latest" || id === "gemini-flash-lite-latest";
320
321
  }
321
322
  function getDisabledThinkingConfig(model) {
322
323
  // Google docs: Gemini 3.1 Pro cannot disable thinking, and Gemini 3 Flash / Flash-Lite
@@ -21,6 +21,7 @@ import { combineAbortSignals } from "../utils/abort-signals.js";
21
21
  import { appendAssistantMessageDiagnostic, createAssistantMessageDiagnostic, formatThrownValue, } from "../utils/diagnostics.js";
22
22
  import { AssistantMessageEventStream } from "../utils/event-stream.js";
23
23
  import { headersToRecord } from "../utils/headers.js";
24
+ import { resolveHttpProxyUrlForTarget } from "../utils/node-http-proxy.js";
24
25
  import { clampOpenAIPromptCacheKey } from "./openai-prompt-cache.js";
25
26
  import { convertResponsesMessages, convertResponsesTools, processResponsesStream } from "./openai-responses-shared.js";
26
27
  import { buildBaseOptions } from "./simple-options.js";
@@ -598,16 +599,13 @@ function recordWebSocketFailure(sessionId, error) {
598
599
  stats.websocketFallbackActive = true;
599
600
  }
600
601
  let _cachedWebsocket = null;
601
- async function getWebSocketConstructor() {
602
- if (_cachedWebsocket)
602
+ async function getWebSocketConstructor(env) {
603
+ if (!env && _cachedWebsocket)
603
604
  return _cachedWebsocket;
604
605
  // bun doesn't respect http proxy envs, ref: https://github.com/oven-sh/bun/issues/15489
605
606
  // TODO: remove this when bun supports proxy envs in websocket.
606
- if (process?.versions?.bun &&
607
- (process.env.HTTP_PROXY || process.env.HTTPS_PROXY || process.env.http_proxy || process.env.https_proxy)) {
608
- const m = await dynamicImport("proxy-from-env");
609
- const getProxyForUrl = m.getProxyForUrl;
610
- _cachedWebsocket = class extends WebSocket {
607
+ if (typeof process !== "undefined" && process.versions?.bun) {
608
+ const WebSocketWithProxy = class extends WebSocket {
611
609
  constructor(url, options) {
612
610
  let _opts = {};
613
611
  if (Array.isArray(options) || typeof options === "string") {
@@ -616,11 +614,14 @@ async function getWebSocketConstructor() {
616
614
  else {
617
615
  _opts = { ...options };
618
616
  }
619
- const proxy = getProxyForUrl(url.toString().replace(/^wss:/, "https:").replace(/^ws:/, "http:"));
620
- super(url, { ..._opts, ...(proxy ? { proxy } : {}) });
617
+ const proxyUrl = resolveHttpProxyUrlForTarget(url.toString().replace(/^wss:/, "https:").replace(/^ws:/, "http:"), env);
618
+ super(url, { ..._opts, ...(proxyUrl ? { proxy: proxyUrl.toString() } : {}) });
621
619
  }
622
620
  };
623
- return _cachedWebsocket;
621
+ if (!env) {
622
+ _cachedWebsocket = WebSocketWithProxy;
623
+ }
624
+ return WebSocketWithProxy;
624
625
  }
625
626
  const ctor = globalThis.WebSocket;
626
627
  if (typeof ctor !== "function")
@@ -665,8 +666,8 @@ function scheduleSessionWebSocketExpiry(sessionId, entry) {
665
666
  websocketSessionCache.delete(sessionId);
666
667
  }, SESSION_WEBSOCKET_CACHE_TTL_MS);
667
668
  }
668
- async function connectWebSocket(url, headers, signal, connectTimeoutMs = DEFAULT_WEBSOCKET_CONNECT_TIMEOUT_MS) {
669
- const WebSocketCtor = await getWebSocketConstructor();
669
+ async function connectWebSocket(url, headers, signal, connectTimeoutMs = DEFAULT_WEBSOCKET_CONNECT_TIMEOUT_MS, env) {
670
+ const WebSocketCtor = await getWebSocketConstructor(env);
670
671
  if (!WebSocketCtor) {
671
672
  throw new Error("WebSocket transport is not available in this runtime");
672
673
  }
@@ -733,9 +734,9 @@ async function connectWebSocket(url, headers, signal, connectTimeoutMs = DEFAULT
733
734
  }
734
735
  });
735
736
  }
736
- async function acquireWebSocket(url, headers, sessionId, signal, connectTimeoutMs) {
737
+ async function acquireWebSocket(url, headers, sessionId, signal, connectTimeoutMs, env) {
737
738
  if (!sessionId) {
738
- const socket = await connectWebSocket(url, headers, signal, connectTimeoutMs);
739
+ const socket = await connectWebSocket(url, headers, signal, connectTimeoutMs, env);
739
740
  return {
740
741
  socket,
741
742
  reused: false,
@@ -766,7 +767,7 @@ async function acquireWebSocket(url, headers, sessionId, signal, connectTimeoutM
766
767
  };
767
768
  }
768
769
  if (cached.busy) {
769
- const socket = await connectWebSocket(url, headers, signal, connectTimeoutMs);
770
+ const socket = await connectWebSocket(url, headers, signal, connectTimeoutMs, env);
770
771
  return {
771
772
  socket,
772
773
  reused: false,
@@ -780,7 +781,7 @@ async function acquireWebSocket(url, headers, sessionId, signal, connectTimeoutM
780
781
  websocketSessionCache.delete(sessionId);
781
782
  }
782
783
  }
783
- const socket = await connectWebSocket(url, headers, signal, connectTimeoutMs);
784
+ const socket = await connectWebSocket(url, headers, signal, connectTimeoutMs, env);
784
785
  const entry = { socket, busy: true };
785
786
  websocketSessionCache.set(sessionId, entry);
786
787
  return {
@@ -1020,7 +1021,7 @@ async function* startWebSocketOutputOnFirstEvent(events, output, stream, onStart
1020
1021
  }
1021
1022
  }
1022
1023
  async function processWebSocketStream(url, body, headers, output, stream, model, onStart, idleTimeoutMs, websocketConnectTimeoutMs, options) {
1023
- const { socket, entry, reused, release } = await acquireWebSocket(url, headers, options?.sessionId, options?.signal, websocketConnectTimeoutMs);
1024
+ const { socket, entry, reused, release } = await acquireWebSocket(url, headers, options?.sessionId, options?.signal, websocketConnectTimeoutMs, options?.env);
1024
1025
  let keepConnection = true;
1025
1026
  const useCachedContext = options?.transport === "websocket-cached" || options?.transport === "auto";
1026
1027
  // ChatGPT Codex Responses rejects `store: true` ("Store must be set to false").
@@ -3,6 +3,7 @@ import { calculateCost, clampThinkingLevel } from "../models.js";
3
3
  import { AssistantMessageEventStream } from "../utils/event-stream.js";
4
4
  import { headersToRecord } from "../utils/headers.js";
5
5
  import { parseStreamingJson } from "../utils/json-parse.js";
6
+ import { getProviderEnvValue } from "../utils/provider-env.js";
6
7
  import { sanitizeSurrogates } from "../utils/sanitize-unicode.js";
7
8
  import { isCloudflareProvider, resolveCloudflareBaseUrl } from "./cloudflare.js";
8
9
  import { buildCopilotDynamicHeaders, hasCopilotVisionInput } from "./github-copilot-headers.js";
@@ -39,11 +40,11 @@ function isToolCallBlock(block) {
39
40
  function isImageContentBlock(block) {
40
41
  return block.type === "image";
41
42
  }
42
- function resolveCacheRetention(cacheRetention) {
43
+ function resolveCacheRetention(cacheRetention, env) {
43
44
  if (cacheRetention) {
44
45
  return cacheRetention;
45
46
  }
46
- if (typeof process !== "undefined" && process.env.PI_CACHE_RETENTION === "long") {
47
+ if (getProviderEnvValue("PI_CACHE_RETENTION", env) === "long") {
47
48
  return "long";
48
49
  }
49
50
  return "short";
@@ -74,9 +75,9 @@ export const streamOpenAICompletions = (model, context, options) => {
74
75
  throw new Error(`No API key for provider: ${model.provider}`);
75
76
  }
76
77
  const compat = getCompat(model);
77
- const cacheRetention = resolveCacheRetention(options?.cacheRetention);
78
+ const cacheRetention = resolveCacheRetention(options?.cacheRetention, options?.env);
78
79
  const cacheSessionId = cacheRetention === "none" ? undefined : options?.sessionId;
79
- const client = createClient(model, context, apiKey, options?.headers, cacheSessionId, compat);
80
+ const client = createClient(model, context, apiKey, options?.headers, cacheSessionId, compat, options?.env);
80
81
  let params = buildParams(model, context, options, compat, cacheRetention);
81
82
  const nextParams = await options?.onPayload?.(params, model);
82
83
  if (nextParams !== undefined) {
@@ -351,7 +352,7 @@ export const streamSimpleOpenAICompletions = (model, context, options) => {
351
352
  toolChoice,
352
353
  });
353
354
  };
354
- function createClient(model, context, apiKey, optionsHeaders, sessionId, compat = getCompat(model)) {
355
+ function createClient(model, context, apiKey, optionsHeaders, sessionId, compat = getCompat(model), env) {
355
356
  const headers = { ...model.headers };
356
357
  if (model.provider === "github-copilot") {
357
358
  const hasImages = hasCopilotVisionInput(context.messages);
@@ -379,12 +380,12 @@ function createClient(model, context, apiKey, optionsHeaders, sessionId, compat
379
380
  : headers;
380
381
  return new OpenAI({
381
382
  apiKey,
382
- baseURL: isCloudflareProvider(model.provider) ? resolveCloudflareBaseUrl(model) : model.baseUrl,
383
+ baseURL: isCloudflareProvider(model.provider) ? resolveCloudflareBaseUrl(model, env) : model.baseUrl,
383
384
  dangerouslyAllowBrowser: true,
384
385
  defaultHeaders,
385
386
  });
386
387
  }
387
- function buildParams(model, context, options, compat = getCompat(model), cacheRetention = resolveCacheRetention(options?.cacheRetention)) {
388
+ function buildParams(model, context, options, compat = getCompat(model), cacheRetention = resolveCacheRetention(options?.cacheRetention, options?.env)) {
388
389
  const messages = convertMessages(model, context, compat);
389
390
  const cacheControl = getCompatCacheControl(compat, cacheRetention);
390
391
  const params = {
@@ -433,6 +434,13 @@ function buildParams(model, context, options, compat = getCompat(model), cacheRe
433
434
  if (compat.thinkingFormat === "zai" && model.reasoning) {
434
435
  const zaiParams = params;
435
436
  zaiParams.thinking = { type: options?.reasoningEffort ? "enabled" : "disabled" };
437
+ if (options?.reasoningEffort && compat.supportsReasoningEffort) {
438
+ const mappedEffort = model.thinkingLevelMap?.[options.reasoningEffort];
439
+ const effort = mappedEffort === undefined ? options.reasoningEffort : mappedEffort;
440
+ if (typeof effort === "string") {
441
+ zaiParams.reasoning_effort = effort;
442
+ }
443
+ }
436
444
  }
437
445
  else if (compat.thinkingFormat === "qwen" && model.reasoning) {
438
446
  params.enable_thinking = !!options?.reasoningEffort;
@@ -444,7 +452,12 @@ function buildParams(model, context, options, compat = getCompat(model), cacheRe
444
452
  };
445
453
  }
446
454
  else if (compat.thinkingFormat === "deepseek" && model.reasoning) {
447
- params.thinking = { type: options?.reasoningEffort ? "enabled" : "disabled" };
455
+ if (options?.reasoningEffort) {
456
+ params.thinking = { type: "enabled" };
457
+ }
458
+ else if (model.thinkingLevelMap?.off !== null) {
459
+ params.thinking = { type: "disabled" };
460
+ }
448
461
  if (options?.reasoningEffort && compat.supportsReasoningEffort) {
449
462
  params.reasoning_effort =
450
463
  model.thinkingLevelMap?.[options.reasoningEffort] ?? options.reasoningEffort;
@@ -393,7 +393,8 @@ export async function processResponsesStream(openaiStream, output, stream, model
393
393
  currentBlock = null;
394
394
  }
395
395
  else if (item.type === "message" && currentBlock?.type === "text") {
396
- currentBlock.text = item.content.map((c) => (c.type === "output_text" ? c.text : c.refusal)).join("");
396
+ currentBlock.text =
397
+ item.content?.map((c) => (c.type === "output_text" ? c.text : c.refusal)).join("") || "";
397
398
  currentBlock.textSignature = encodeTextSignatureV1(item.id, item.phase ?? undefined);
398
399
  stream.push({
399
400
  type: "text_end",