@psnext/slingcli 2.4.20260601-1 → 2.5.20260602-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 (96) hide show
  1. package/node_modules/@aws-sdk/credential-provider-http/node_modules/@smithy/node-http-handler/package.json +4 -4
  2. package/node_modules/@aws-sdk/credential-provider-node/dist-cjs/index.js +12 -1
  3. package/node_modules/@aws-sdk/credential-provider-node/dist-es/runtime/memoize-chain.js +12 -1
  4. package/node_modules/@aws-sdk/credential-provider-node/package.json +1 -1
  5. package/node_modules/@aws-sdk/nested-clients/node_modules/@smithy/node-http-handler/package.json +4 -4
  6. package/node_modules/@earendil-works/pi-agent-core/dist/harness/agent-harness.js +81 -18
  7. package/node_modules/@earendil-works/pi-agent-core/dist/harness/compaction/branch-summarization.js +1 -0
  8. package/node_modules/@earendil-works/pi-agent-core/dist/harness/compaction/compaction.js +1 -0
  9. package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/session.js +14 -1
  10. package/node_modules/@earendil-works/pi-agent-core/package.json +3 -3
  11. package/node_modules/@earendil-works/pi-ai/dist/models.generated.js +197 -190
  12. package/node_modules/@earendil-works/pi-ai/dist/providers/amazon-bedrock.js +84 -21
  13. package/node_modules/@earendil-works/pi-ai/dist/providers/anthropic.js +24 -14
  14. package/node_modules/@earendil-works/pi-ai/dist/providers/azure-openai-responses.js +5 -9
  15. package/node_modules/@earendil-works/pi-ai/dist/providers/google-vertex.js +1 -1
  16. package/node_modules/@earendil-works/pi-ai/dist/providers/google.js +5 -3
  17. package/node_modules/@earendil-works/pi-ai/dist/providers/images/openrouter.js +2 -3
  18. package/node_modules/@earendil-works/pi-ai/dist/providers/mistral.js +2 -3
  19. package/node_modules/@earendil-works/pi-ai/dist/providers/openai-codex-responses.js +130 -62
  20. package/node_modules/@earendil-works/pi-ai/dist/providers/openai-completions.js +18 -12
  21. package/node_modules/@earendil-works/pi-ai/dist/providers/openai-responses-shared.js +4 -1
  22. package/node_modules/@earendil-works/pi-ai/dist/providers/openai-responses.js +5 -9
  23. package/node_modules/@earendil-works/pi-ai/dist/providers/simple-options.js +1 -0
  24. package/node_modules/@earendil-works/pi-ai/dist/stream.js +14 -2
  25. package/node_modules/@earendil-works/pi-ai/dist/utils/abort-signals.js +34 -0
  26. package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/device-code.js +8 -7
  27. package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/github-copilot.js +1 -1
  28. package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/index.js +1 -1
  29. package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/openai-codex.js +179 -79
  30. package/node_modules/@earendil-works/pi-ai/package.json +2 -2
  31. package/node_modules/@earendil-works/pi-coding-agent/dist/cli/args.js +27 -0
  32. package/node_modules/@earendil-works/pi-coding-agent/dist/config.js +9 -1
  33. package/node_modules/@earendil-works/pi-coding-agent/dist/core/agent-session-services.js +1 -0
  34. package/node_modules/@earendil-works/pi-coding-agent/dist/core/agent-session.js +29 -5
  35. package/node_modules/@earendil-works/pi-coding-agent/dist/core/extensions/runner.js +21 -3
  36. package/node_modules/@earendil-works/pi-coding-agent/dist/core/footer-data-provider.js +29 -1
  37. package/node_modules/@earendil-works/pi-coding-agent/dist/core/model-registry.js +65 -13
  38. package/node_modules/@earendil-works/pi-coding-agent/dist/core/model-resolver.js +1 -1
  39. package/node_modules/@earendil-works/pi-coding-agent/dist/core/resolve-config-value.js +134 -11
  40. package/node_modules/@earendil-works/pi-coding-agent/dist/core/sdk.js +12 -6
  41. package/node_modules/@earendil-works/pi-coding-agent/dist/core/session-manager.js +167 -96
  42. package/node_modules/@earendil-works/pi-coding-agent/dist/core/settings-manager.js +14 -9
  43. package/node_modules/@earendil-works/pi-coding-agent/dist/core/system-prompt.js +0 -3
  44. package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/edit.js +7 -10
  45. package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/ls.js +5 -7
  46. package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/read.js +6 -7
  47. package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/render-utils.js +17 -1
  48. package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/write.js +5 -6
  49. package/node_modules/@earendil-works/pi-coding-agent/dist/index.js +2 -0
  50. package/node_modules/@earendil-works/pi-coding-agent/dist/main.js +69 -16
  51. package/node_modules/@earendil-works/pi-coding-agent/dist/migrations.js +118 -1
  52. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/config-selector.js +1 -1
  53. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/login-dialog.js +2 -4
  54. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/interactive-mode.js +61 -6
  55. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/theme/theme.js +10 -0
  56. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/print-mode.js +1 -0
  57. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/rpc/rpc-mode.js +4 -1
  58. package/node_modules/@earendil-works/pi-coding-agent/dist/utils/deprecation.js +13 -0
  59. package/node_modules/@earendil-works/pi-coding-agent/dist/utils/json.js +7 -0
  60. package/node_modules/@earendil-works/pi-coding-agent/npm-shrinkwrap.json +71 -56
  61. package/node_modules/@earendil-works/pi-coding-agent/package.json +7 -7
  62. package/node_modules/@earendil-works/pi-tui/dist/components/editor.js +9 -53
  63. package/node_modules/@earendil-works/pi-tui/dist/components/input.js +6 -54
  64. package/node_modules/@earendil-works/pi-tui/dist/terminal-image.js +34 -7
  65. package/node_modules/@earendil-works/pi-tui/dist/terminal.js +172 -37
  66. package/node_modules/@earendil-works/pi-tui/dist/tui.js +166 -22
  67. package/node_modules/@earendil-works/pi-tui/dist/utils.js +8 -3
  68. package/node_modules/@earendil-works/pi-tui/dist/word-navigation.js +96 -0
  69. package/node_modules/@earendil-works/pi-tui/package.json +2 -2
  70. package/node_modules/@mariozechner/clipboard/Cargo.toml +3 -3
  71. package/node_modules/@mariozechner/clipboard/index.d.ts +34 -20
  72. package/node_modules/@mariozechner/clipboard/index.js +546 -257
  73. package/node_modules/@mariozechner/clipboard/package.json +14 -14
  74. package/node_modules/@mariozechner/clipboard/src/lib.rs +4 -9
  75. package/node_modules/@mariozechner/clipboard-linux-x64-gnu/clipboard.linux-x64-gnu.node +0 -0
  76. package/node_modules/@mariozechner/clipboard-linux-x64-gnu/package.json +2 -2
  77. package/node_modules/@smithy/core/dist-cjs/submodules/retry/index.browser.js +39 -22
  78. package/node_modules/@smithy/core/dist-cjs/submodules/retry/index.js +39 -22
  79. package/node_modules/@smithy/core/dist-es/submodules/retry/middleware-retry/configurations.js +19 -6
  80. package/node_modules/@smithy/core/dist-es/submodules/retry/middleware-retry/retryMiddleware.js +4 -5
  81. package/node_modules/@smithy/core/dist-es/submodules/retry/service-error-classification/constants.js +1 -1
  82. package/node_modules/@smithy/core/dist-es/submodules/retry/util-retry/ConfiguredRetryStrategy.js +4 -5
  83. package/node_modules/@smithy/core/dist-es/submodules/retry/util-retry/DefaultRetryToken.js +3 -0
  84. package/node_modules/@smithy/core/dist-es/submodules/retry/util-retry/StandardRetryStrategy.js +9 -5
  85. package/node_modules/@smithy/core/dist-types/submodules/retry/middleware-retry/configurations.d.ts +4 -1
  86. package/node_modules/@smithy/core/dist-types/submodules/retry/util-retry/ConfiguredRetryStrategy.d.ts +1 -2
  87. package/node_modules/@smithy/core/dist-types/submodules/retry/util-retry/DefaultRetryToken.d.ts +3 -0
  88. package/node_modules/@smithy/core/dist-types/submodules/retry/util-retry/StandardRetryStrategy.d.ts +1 -1
  89. package/node_modules/@smithy/core/package.json +2 -2
  90. package/node_modules/@smithy/credential-provider-imds/package.json +3 -3
  91. package/node_modules/@smithy/fetch-http-handler/package.json +4 -4
  92. package/node_modules/@smithy/signature-v4/package.json +3 -3
  93. package/node_modules/@smithy/types/dist-types/retry.d.ts +25 -0
  94. package/node_modules/@smithy/types/package.json +1 -1
  95. package/package.json +6 -6
  96. package/slingshot/index.js +231 -230
@@ -7,6 +7,7 @@ import { createHttpProxyAgentsForTarget } from "../utils/node-http-proxy.js";
7
7
  import { sanitizeSurrogates } from "../utils/sanitize-unicode.js";
8
8
  import { adjustMaxTokensForThinking, buildBaseOptions, clampReasoning } from "./simple-options.js";
9
9
  import { transformMessages } from "./transform-messages.js";
10
+ const EMPTY_TEXT_PLACEHOLDER = "<empty>";
10
11
  export const streamBedrock = (model, context, options = {}) => {
11
12
  const stream = new AssistantMessageEventStream();
12
13
  (async () => {
@@ -89,6 +90,9 @@ export const streamBedrock = (model, context, options = {}) => {
89
90
  }
90
91
  try {
91
92
  const client = new BedrockRuntimeClient(config);
93
+ if (options.headers && Object.keys(options.headers).length > 0) {
94
+ addCustomHeadersMiddleware(client, options.headers);
95
+ }
92
96
  const cacheRetention = resolveCacheRetention(options.cacheRetention);
93
97
  const inferenceMaxTokens = options.maxTokens ?? (isAnthropicClaudeModel(model) ? model.maxTokens : undefined);
94
98
  let commandInput = {
@@ -205,6 +209,39 @@ function formatBedrockError(error) {
205
209
  }
206
210
  return message;
207
211
  }
212
+ /**
213
+ * Header keys that must never be overwritten by caller-supplied headers.
214
+ * `host` and `x-amz-*` participate in the SigV4 canonical request; `authorization`
215
+ * is owned by SigV4 or the bearer-token path (config.token + authSchemePreference).
216
+ * Compared case-insensitively (caller key is lower-cased before lookup).
217
+ */
218
+ const RESERVED_HEADER_EXACT = new Set(["authorization", "host"]);
219
+ function isReservedHeader(key) {
220
+ const lower = key.toLowerCase();
221
+ return lower.startsWith("x-amz-") || RESERVED_HEADER_EXACT.has(lower);
222
+ }
223
+ /**
224
+ * Attach caller-supplied headers to the outgoing Bedrock request via a Smithy
225
+ * `build`-step middleware. The `build` step runs after request serialisation but
226
+ * before SigV4 signing, so injected headers are covered by the signature. Reserved
227
+ * SigV4 / auth headers (`x-amz-*`, `authorization`, `host`) are silently skipped;
228
+ * all other caller headers override any existing same-named header on the request.
229
+ */
230
+ function addCustomHeadersMiddleware(client, headers) {
231
+ const middleware = (next) => async (args) => {
232
+ const request = args.request;
233
+ if (request && typeof request === "object" && "headers" in request) {
234
+ const requestHeaders = request.headers;
235
+ for (const [key, value] of Object.entries(headers)) {
236
+ if (!isReservedHeader(key)) {
237
+ requestHeaders[key] = value;
238
+ }
239
+ }
240
+ }
241
+ return next(args);
242
+ };
243
+ client.middlewareStack.add(middleware, { step: "build", name: "pi-ai-custom-headers", priority: "low" });
244
+ }
208
245
  export const streamSimpleBedrock = (model, context, options) => {
209
246
  const base = buildBaseOptions(model, options, undefined);
210
247
  if (!options?.reasoning) {
@@ -350,11 +387,11 @@ function getModelMatchCandidates(modelId, modelName) {
350
387
  }
351
388
  function supportsAdaptiveThinking(modelId, modelName) {
352
389
  const candidates = getModelMatchCandidates(modelId, modelName);
353
- return candidates.some((s) => s.includes("opus-4-6") || s.includes("opus-4-7") || s.includes("sonnet-4-6"));
390
+ return candidates.some((s) => s.includes("opus-4-6") || s.includes("opus-4-7") || s.includes("opus-4-8") || s.includes("sonnet-4-6"));
354
391
  }
355
392
  function supportsNativeXhighEffort(model) {
356
393
  const candidates = getModelMatchCandidates(model.id, model.name);
357
- return candidates.some((s) => s.includes("opus-4-7"));
394
+ return candidates.some((s) => s.includes("opus-4-7") || s.includes("opus-4-8"));
358
395
  }
359
396
  function mapThinkingLevelToEffort(model, level) {
360
397
  if (level === "xhigh" && supportsNativeXhighEffort(model))
@@ -461,6 +498,29 @@ function normalizeToolCallId(id) {
461
498
  const sanitized = id.replace(/[^a-zA-Z0-9_-]/g, "_");
462
499
  return sanitized.length > 64 ? sanitized.slice(0, 64) : sanitized;
463
500
  }
501
+ function createNonBlankTextBlock(text) {
502
+ const sanitized = sanitizeSurrogates(text);
503
+ return sanitized.trim().length === 0 ? undefined : { text: sanitized };
504
+ }
505
+ function createRequiredTextBlock(text) {
506
+ return createNonBlankTextBlock(text) ?? { text: EMPTY_TEXT_PLACEHOLDER };
507
+ }
508
+ function convertToolResultContent(content) {
509
+ const result = [];
510
+ for (const c of content) {
511
+ if (c.type === "image") {
512
+ result.push({ image: createImageBlock(c.mimeType, c.data) });
513
+ }
514
+ else {
515
+ const textBlock = createNonBlankTextBlock(c.text);
516
+ if (textBlock)
517
+ result.push(textBlock);
518
+ }
519
+ }
520
+ if (result.length === 0)
521
+ result.push({ text: EMPTY_TEXT_PLACEHOLDER });
522
+ return result;
523
+ }
464
524
  function convertMessages(context, model, cacheRetention) {
465
525
  const result = [];
466
526
  const transformedMessages = transformMessages(context.messages, model, normalizeToolCallId);
@@ -470,14 +530,17 @@ function convertMessages(context, model, cacheRetention) {
470
530
  case "user": {
471
531
  const content = [];
472
532
  if (typeof m.content === "string") {
473
- content.push({ text: sanitizeSurrogates(m.content) });
533
+ content.push(createRequiredTextBlock(m.content));
474
534
  }
475
535
  else {
476
536
  for (const c of m.content) {
477
537
  switch (c.type) {
478
- case "text":
479
- content.push({ text: sanitizeSurrogates(c.text) });
538
+ case "text": {
539
+ const textBlock = createNonBlankTextBlock(c.text);
540
+ if (textBlock)
541
+ content.push(textBlock);
480
542
  break;
543
+ }
481
544
  case "image":
482
545
  content.push({ image: createImageBlock(c.mimeType, c.data) });
483
546
  break;
@@ -485,9 +548,9 @@ function convertMessages(context, model, cacheRetention) {
485
548
  continue;
486
549
  }
487
550
  }
551
+ if (content.length === 0)
552
+ content.push({ text: EMPTY_TEXT_PLACEHOLDER });
488
553
  }
489
- if (content.length === 0)
490
- continue;
491
554
  result.push({
492
555
  role: ConversationRole.USER,
493
556
  content,
@@ -503,20 +566,23 @@ function convertMessages(context, model, cacheRetention) {
503
566
  const contentBlocks = [];
504
567
  for (const c of m.content) {
505
568
  switch (c.type) {
506
- case "text":
569
+ case "text": {
507
570
  // Skip empty text blocks
508
- if (c.text.trim().length === 0)
571
+ const textBlock = createNonBlankTextBlock(c.text);
572
+ if (!textBlock)
509
573
  continue;
510
- contentBlocks.push({ text: sanitizeSurrogates(c.text) });
574
+ contentBlocks.push(textBlock);
511
575
  break;
576
+ }
512
577
  case "toolCall":
513
578
  contentBlocks.push({
514
579
  toolUse: { toolUseId: c.id, name: c.name, input: c.arguments },
515
580
  });
516
581
  break;
517
- case "thinking":
582
+ case "thinking": {
518
583
  // Skip empty thinking blocks
519
- if (c.thinking.trim().length === 0)
584
+ const thinking = sanitizeSurrogates(c.thinking);
585
+ if (thinking.trim().length === 0)
520
586
  continue;
521
587
  // Only Anthropic models support the signature field in reasoningText.
522
588
  // For other models, we omit the signature to avoid errors like:
@@ -526,13 +592,13 @@ function convertMessages(context, model, cacheRetention) {
526
592
  // persisted message lacks a signature, Bedrock rejects the replayed
527
593
  // reasoning block. Fall back to plain text, matching Anthropic.
528
594
  if (!c.thinkingSignature || c.thinkingSignature.trim().length === 0) {
529
- contentBlocks.push({ text: sanitizeSurrogates(c.thinking) });
595
+ contentBlocks.push({ text: thinking });
530
596
  }
531
597
  else {
532
598
  contentBlocks.push({
533
599
  reasoningContent: {
534
600
  reasoningText: {
535
- text: sanitizeSurrogates(c.thinking),
601
+ text: thinking,
536
602
  signature: c.thinkingSignature,
537
603
  },
538
604
  },
@@ -542,11 +608,12 @@ function convertMessages(context, model, cacheRetention) {
542
608
  else {
543
609
  contentBlocks.push({
544
610
  reasoningContent: {
545
- reasoningText: { text: sanitizeSurrogates(c.thinking) },
611
+ reasoningText: { text: thinking },
546
612
  },
547
613
  });
548
614
  }
549
615
  break;
616
+ }
550
617
  default:
551
618
  continue;
552
619
  }
@@ -569,9 +636,7 @@ function convertMessages(context, model, cacheRetention) {
569
636
  toolResults.push({
570
637
  toolResult: {
571
638
  toolUseId: m.toolCallId,
572
- content: m.content.map((c) => c.type === "image"
573
- ? { image: createImageBlock(c.mimeType, c.data) }
574
- : { text: sanitizeSurrogates(c.text) }),
639
+ content: convertToolResultContent(m.content),
575
640
  status: m.isError ? ToolResultStatus.ERROR : ToolResultStatus.SUCCESS,
576
641
  },
577
642
  });
@@ -582,9 +647,7 @@ function convertMessages(context, model, cacheRetention) {
582
647
  toolResults.push({
583
648
  toolResult: {
584
649
  toolUseId: nextMsg.toolCallId,
585
- content: nextMsg.content.map((c) => c.type === "image"
586
- ? { image: createImageBlock(c.mimeType, c.data) }
587
- : { text: sanitizeSurrogates(c.text) }),
650
+ content: convertToolResultContent(nextMsg.content),
588
651
  status: nextMsg.isError ? ToolResultStatus.ERROR : ToolResultStatus.SUCCESS,
589
652
  },
590
653
  });
@@ -1,5 +1,4 @@
1
1
  import Anthropic from "@anthropic-ai/sdk";
2
- import { getEnvApiKey } from "../env-api-keys.js";
3
2
  import { calculateCost } from "../models.js";
4
3
  import { AssistantMessageEventStream } from "../utils/event-stream.js";
5
4
  import { headersToRecord } from "../utils/headers.js";
@@ -116,6 +115,8 @@ function getAnthropicCompat(model) {
116
115
  supportsLongCacheRetention: model.compat?.supportsLongCacheRetention ?? !isFireworks,
117
116
  sendSessionAffinityHeaders: model.compat?.sendSessionAffinityHeaders ?? !!(isFireworks || isCloudflareAiGatewayAnthropic),
118
117
  supportsCacheControlOnTools: model.compat?.supportsCacheControlOnTools ?? !isFireworks,
118
+ supportsTemperature: model.compat?.supportsTemperature ?? true,
119
+ allowEmptySignature: model.compat?.allowEmptySignature ?? false,
119
120
  };
120
121
  }
121
122
  function mergeHeaders(...headerSources) {
@@ -306,7 +307,10 @@ export const streamAnthropic = (model, context, options) => {
306
307
  isOAuth = false;
307
308
  }
308
309
  else {
309
- const apiKey = options?.apiKey ?? getEnvApiKey(model.provider) ?? "";
310
+ const apiKey = options?.apiKey;
311
+ if (!apiKey) {
312
+ throw new Error(`No API key for provider: ${model.provider}`);
313
+ }
310
314
  let copilotDynamicHeaders;
311
315
  if (model.provider === "github-copilot") {
312
316
  const hasImages = hasCopilotVisionInput(context.messages);
@@ -548,7 +552,7 @@ function mapThinkingLevelToEffort(model, level) {
548
552
  }
549
553
  }
550
554
  export const streamSimpleAnthropic = (model, context, options) => {
551
- const apiKey = options?.apiKey || getEnvApiKey(model.provider);
555
+ const apiKey = options?.apiKey;
552
556
  if (!apiKey) {
553
557
  throw new Error(`No API key for provider: ${model.provider}`);
554
558
  }
@@ -655,9 +659,10 @@ function createClient(model, apiKey, interleavedThinking, useFineGrainedToolStre
655
659
  }
656
660
  function buildParams(model, context, isOAuthToken, options) {
657
661
  const { cacheControl } = getCacheControl(model, options?.cacheRetention);
662
+ const compat = getAnthropicCompat(model);
658
663
  const params = {
659
664
  model: model.id,
660
- messages: convertMessages(context.messages, model, isOAuthToken, cacheControl),
665
+ messages: convertMessages(context.messages, model, isOAuthToken, cacheControl, compat.allowEmptySignature),
661
666
  max_tokens: options?.maxTokens ?? model.maxTokens,
662
667
  stream: true,
663
668
  };
@@ -688,12 +693,11 @@ function buildParams(model, context, isOAuthToken, options) {
688
693
  },
689
694
  ];
690
695
  }
691
- // Temperature is incompatible with extended thinking (adaptive or budget-based).
692
- if (options?.temperature !== undefined && !options?.thinkingEnabled) {
696
+ // Temperature is incompatible with extended thinking and unsupported on Claude Opus 4.7+.
697
+ if (options?.temperature !== undefined && !options?.thinkingEnabled && compat.supportsTemperature) {
693
698
  params.temperature = options.temperature;
694
699
  }
695
700
  if (context.tools && context.tools.length > 0) {
696
- const compat = getAnthropicCompat(model);
697
701
  params.tools = convertTools(context.tools, isOAuthToken, compat.supportsEagerToolInputStreaming, compat.supportsCacheControlOnTools ? cacheControl : undefined);
698
702
  }
699
703
  // Configure thinking mode: adaptive, budget-based, or explicitly disabled.
@@ -746,7 +750,7 @@ function buildParams(model, context, isOAuthToken, options) {
746
750
  function normalizeToolCallId(id) {
747
751
  return id.replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 64);
748
752
  }
749
- function convertMessages(messages, model, isOAuthToken, cacheControl) {
753
+ function convertMessages(messages, model, isOAuthToken, cacheControl, allowEmptySignature = false) {
750
754
  const params = [];
751
755
  // Transform messages for cross-provider compatibility
752
756
  const transformedMessages = transformMessages(messages, model, normalizeToolCallId);
@@ -817,13 +821,19 @@ function convertMessages(messages, model, isOAuthToken, cacheControl) {
817
821
  if (block.thinking.trim().length === 0)
818
822
  continue;
819
823
  // If thinking signature is missing/empty (e.g., from aborted stream),
820
- // convert to plain text block without <thinking> tags to avoid API rejection
821
- // and prevent Claude from mimicking the tags in responses
824
+ // convert to plain text for Anthropic. Some compatible providers emit
825
+ // and accept empty signatures, so let marked models preserve the block.
822
826
  if (!block.thinkingSignature || block.thinkingSignature.trim().length === 0) {
823
- blocks.push({
824
- type: "text",
825
- text: sanitizeSurrogates(block.thinking),
826
- });
827
+ blocks.push(allowEmptySignature
828
+ ? {
829
+ type: "thinking",
830
+ thinking: sanitizeSurrogates(block.thinking),
831
+ signature: "",
832
+ }
833
+ : {
834
+ type: "text",
835
+ text: sanitizeSurrogates(block.thinking),
836
+ });
827
837
  }
828
838
  else {
829
839
  blocks.push({
@@ -1,5 +1,4 @@
1
1
  import { AzureOpenAI } from "openai";
2
- import { getEnvApiKey } from "../env-api-keys.js";
3
2
  import { clampThinkingLevel } from "../models.js";
4
3
  import { AssistantMessageEventStream } from "../utils/event-stream.js";
5
4
  import { headersToRecord } from "../utils/headers.js";
@@ -73,7 +72,10 @@ export const streamAzureOpenAIResponses = (model, context, options) => {
73
72
  };
74
73
  try {
75
74
  // Create Azure OpenAI client
76
- const apiKey = options?.apiKey || getEnvApiKey(model.provider) || "";
75
+ const apiKey = options?.apiKey;
76
+ if (!apiKey) {
77
+ throw new Error(`No API key for provider: ${model.provider}`);
78
+ }
77
79
  const client = createClient(model, apiKey, options);
78
80
  let params = buildParams(model, context, options, deploymentName);
79
81
  const nextParams = await options?.onPayload?.(params, model);
@@ -113,7 +115,7 @@ export const streamAzureOpenAIResponses = (model, context, options) => {
113
115
  return stream;
114
116
  };
115
117
  export const streamSimpleAzureOpenAIResponses = (model, context, options) => {
116
- const apiKey = options?.apiKey || getEnvApiKey(model.provider);
118
+ const apiKey = options?.apiKey;
117
119
  if (!apiKey) {
118
120
  throw new Error(`No API key for provider: ${model.provider}`);
119
121
  }
@@ -167,12 +169,6 @@ function resolveAzureConfig(model, options) {
167
169
  };
168
170
  }
169
171
  function createClient(model, apiKey, options) {
170
- if (!apiKey) {
171
- if (!process.env.AZURE_OPENAI_API_KEY) {
172
- throw new Error("Azure OpenAI API key is required. Set AZURE_OPENAI_API_KEY environment variable or pass it as an argument.");
173
- }
174
- apiKey = process.env.AZURE_OPENAI_API_KEY;
175
- }
176
172
  const headers = { ...model.headers };
177
173
  if (options?.headers) {
178
174
  Object.assign(headers, options.headers);
@@ -300,7 +300,7 @@ function baseUrlIncludesApiVersion(baseUrl) {
300
300
  }
301
301
  }
302
302
  function resolveApiKey(options) {
303
- const apiKey = options?.apiKey?.trim() || process.env.GOOGLE_CLOUD_API_KEY?.trim();
303
+ const apiKey = options?.apiKey?.trim();
304
304
  if (!apiKey || apiKey === GCP_VERTEX_CREDENTIALS_MARKER || isPlaceholderApiKey(apiKey)) {
305
305
  return undefined;
306
306
  }
@@ -1,5 +1,4 @@
1
1
  import { GoogleGenAI, } from "@google/genai";
2
- import { getEnvApiKey } from "../env-api-keys.js";
3
2
  import { calculateCost, clampThinkingLevel } from "../models.js";
4
3
  import { AssistantMessageEventStream } from "../utils/event-stream.js";
5
4
  import { sanitizeSurrogates } from "../utils/sanitize-unicode.js";
@@ -28,7 +27,10 @@ export const streamGoogle = (model, context, options) => {
28
27
  timestamp: Date.now(),
29
28
  };
30
29
  try {
31
- const apiKey = options?.apiKey || getEnvApiKey(model.provider) || "";
30
+ const apiKey = options?.apiKey;
31
+ if (!apiKey) {
32
+ throw new Error(`No API key for provider: ${model.provider}`);
33
+ }
32
34
  const client = createClient(model, apiKey, options?.headers);
33
35
  let params = buildParams(model, context, options);
34
36
  const nextParams = await options?.onPayload?.(params, model);
@@ -214,7 +216,7 @@ export const streamGoogle = (model, context, options) => {
214
216
  return stream;
215
217
  };
216
218
  export const streamSimpleGoogle = (model, context, options) => {
217
- const apiKey = options?.apiKey || getEnvApiKey(model.provider);
219
+ const apiKey = options?.apiKey;
218
220
  if (!apiKey) {
219
221
  throw new Error(`No API key for provider: ${model.provider}`);
220
222
  }
@@ -1,5 +1,4 @@
1
1
  import OpenAI from "openai";
2
- import { getEnvApiKey } from "../../env-api-keys.js";
3
2
  import { headersToRecord } from "../../utils/headers.js";
4
3
  import { sanitizeSurrogates } from "../../utils/sanitize-unicode.js";
5
4
  export const generateImagesOpenRouter = async (model, context, options) => {
@@ -12,9 +11,9 @@ export const generateImagesOpenRouter = async (model, context, options) => {
12
11
  timestamp: Date.now(),
13
12
  };
14
13
  try {
15
- const apiKey = options?.apiKey || getEnvApiKey(model.provider);
14
+ const apiKey = options?.apiKey;
16
15
  if (!apiKey) {
17
- throw new Error(`No API key available for provider: ${model.provider}`);
16
+ throw new Error(`No API key for provider: ${model.provider}`);
18
17
  }
19
18
  const client = createClient(model, apiKey, options?.headers);
20
19
  let params = buildParams(model, context);
@@ -1,5 +1,4 @@
1
1
  import { Mistral } from "@mistralai/mistralai";
2
- import { getEnvApiKey } from "../env-api-keys.js";
3
2
  import { calculateCost, clampThinkingLevel } from "../models.js";
4
3
  import { AssistantMessageEventStream } from "../utils/event-stream.js";
5
4
  import { shortHash } from "../utils/hash.js";
@@ -17,7 +16,7 @@ export const streamMistral = (model, context, options) => {
17
16
  (async () => {
18
17
  const output = createOutput(model);
19
18
  try {
20
- const apiKey = options?.apiKey || getEnvApiKey(model.provider);
19
+ const apiKey = options?.apiKey;
21
20
  if (!apiKey) {
22
21
  throw new Error(`No API key for provider: ${model.provider}`);
23
22
  }
@@ -62,7 +61,7 @@ export const streamMistral = (model, context, options) => {
62
61
  * Maps provider-agnostic `SimpleStreamOptions` to Mistral options.
63
62
  */
64
63
  export const streamSimpleMistral = (model, context, options) => {
65
- const apiKey = options?.apiKey || getEnvApiKey(model.provider);
64
+ const apiKey = options?.apiKey;
66
65
  if (!apiKey) {
67
66
  throw new Error(`No API key for provider: ${model.provider}`);
68
67
  }