@bastani/pi-ai 0.9.14 → 0.9.15

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 (113) hide show
  1. package/CHANGELOG.md +72 -0
  2. package/NOTICE.md +4 -5
  3. package/README.md +6 -4
  4. package/dist/api/anthropic-messages.d.ts.map +1 -1
  5. package/dist/api/anthropic-messages.js +55 -25
  6. package/dist/api/anthropic-messages.js.map +1 -1
  7. package/dist/api/azure-openai-responses.d.ts +2 -0
  8. package/dist/api/azure-openai-responses.d.ts.map +1 -1
  9. package/dist/api/azure-openai-responses.js +9 -2
  10. package/dist/api/azure-openai-responses.js.map +1 -1
  11. package/dist/api/bedrock-converse-stream.d.ts.map +1 -1
  12. package/dist/api/bedrock-converse-stream.js +132 -8
  13. package/dist/api/bedrock-converse-stream.js.map +1 -1
  14. package/dist/api/github-copilot-headers.d.ts +22 -1
  15. package/dist/api/github-copilot-headers.d.ts.map +1 -1
  16. package/dist/api/github-copilot-headers.js +30 -0
  17. package/dist/api/github-copilot-headers.js.map +1 -1
  18. package/dist/api/google-generative-ai.d.ts +2 -2
  19. package/dist/api/google-generative-ai.d.ts.map +1 -1
  20. package/dist/api/google-generative-ai.js +28 -18
  21. package/dist/api/google-generative-ai.js.map +1 -1
  22. package/dist/api/google-shared.d.ts +5 -2
  23. package/dist/api/google-shared.d.ts.map +1 -1
  24. package/dist/api/google-shared.js +16 -0
  25. package/dist/api/google-shared.js.map +1 -1
  26. package/dist/api/google-vertex.d.ts +2 -2
  27. package/dist/api/google-vertex.d.ts.map +1 -1
  28. package/dist/api/google-vertex.js +15 -11
  29. package/dist/api/google-vertex.js.map +1 -1
  30. package/dist/api/mistral-conversations.d.ts.map +1 -1
  31. package/dist/api/mistral-conversations.js +15 -5
  32. package/dist/api/mistral-conversations.js.map +1 -1
  33. package/dist/api/openai-codex-responses.d.ts.map +1 -1
  34. package/dist/api/openai-codex-responses.js +15 -6
  35. package/dist/api/openai-codex-responses.js.map +1 -1
  36. package/dist/api/openai-completions.d.ts +3 -2
  37. package/dist/api/openai-completions.d.ts.map +1 -1
  38. package/dist/api/openai-completions.js +142 -94
  39. package/dist/api/openai-completions.js.map +1 -1
  40. package/dist/api/openai-responses.d.ts.map +1 -1
  41. package/dist/api/openai-responses.js +20 -10
  42. package/dist/api/openai-responses.js.map +1 -1
  43. package/dist/api/pi-messages.d.ts.map +1 -1
  44. package/dist/api/pi-messages.js +23 -5
  45. package/dist/api/pi-messages.js.map +1 -1
  46. package/dist/api/simple-options.d.ts +4 -0
  47. package/dist/api/simple-options.d.ts.map +1 -1
  48. package/dist/api/simple-options.js +18 -10
  49. package/dist/api/simple-options.js.map +1 -1
  50. package/dist/auth/oauth/device-code.d.ts +1 -0
  51. package/dist/auth/oauth/device-code.d.ts.map +1 -1
  52. package/dist/auth/oauth/device-code.js +1 -1
  53. package/dist/auth/oauth/device-code.js.map +1 -1
  54. package/dist/auth/oauth/github-copilot.d.ts.map +1 -1
  55. package/dist/auth/oauth/github-copilot.js +98 -34
  56. package/dist/auth/oauth/github-copilot.js.map +1 -1
  57. package/dist/auth/oauth/kimi-coding.d.ts.map +1 -1
  58. package/dist/auth/oauth/kimi-coding.js +1 -14
  59. package/dist/auth/oauth/kimi-coding.js.map +1 -1
  60. package/dist/index.d.ts +2 -1
  61. package/dist/index.d.ts.map +1 -1
  62. package/dist/index.js +1 -0
  63. package/dist/index.js.map +1 -1
  64. package/dist/providers/data/.manifest.json +1 -1
  65. package/dist/providers/data/amazon-bedrock.json +1 -1
  66. package/dist/providers/data/anthropic.json +1 -1
  67. package/dist/providers/data/baseten.json +1 -1
  68. package/dist/providers/data/cerebras.json +1 -1
  69. package/dist/providers/data/cloudflare-ai-gateway.json +1 -1
  70. package/dist/providers/data/cloudflare-workers-ai.json +1 -1
  71. package/dist/providers/data/github-copilot.json +1 -1
  72. package/dist/providers/data/google-vertex.json +1 -1
  73. package/dist/providers/data/google.json +1 -1
  74. package/dist/providers/data/huggingface.json +1 -1
  75. package/dist/providers/data/nvidia.json +1 -1
  76. package/dist/providers/data/opencode-go.json +1 -1
  77. package/dist/providers/data/opencode.json +1 -1
  78. package/dist/providers/data/openrouter.json +1 -1
  79. package/dist/providers/data/qwen-token-plan-cn.json +1 -1
  80. package/dist/providers/data/qwen-token-plan-individual.json +1 -1
  81. package/dist/providers/data/qwen-token-plan.json +1 -1
  82. package/dist/providers/data/together.json +1 -1
  83. package/dist/providers/data/vercel-ai-gateway.json +1 -1
  84. package/dist/providers/data/xai.json +1 -1
  85. package/dist/providers/data/xiaomi-token-plan-ams.json +1 -1
  86. package/dist/providers/data/xiaomi-token-plan-cn.json +1 -1
  87. package/dist/providers/data/xiaomi-token-plan-sgp.json +1 -1
  88. package/dist/providers/data/xiaomi.json +1 -1
  89. package/dist/providers/data/zai-coding-cn.json +1 -1
  90. package/dist/providers/data/zai.json +1 -1
  91. package/dist/providers/github-copilot-env.d.ts +36 -0
  92. package/dist/providers/github-copilot-env.d.ts.map +1 -0
  93. package/dist/providers/github-copilot-env.js +94 -0
  94. package/dist/providers/github-copilot-env.js.map +1 -0
  95. package/dist/providers/xai.d.ts +1 -1
  96. package/dist/providers/xai.d.ts.map +1 -1
  97. package/dist/providers/xai.js +1 -5
  98. package/dist/providers/xai.js.map +1 -1
  99. package/dist/types.d.ts +43 -5
  100. package/dist/types.d.ts.map +1 -1
  101. package/dist/types.js.map +1 -1
  102. package/dist/utils/retry.d.ts.map +1 -1
  103. package/dist/utils/retry.js +14 -0
  104. package/dist/utils/retry.js.map +1 -1
  105. package/dist/utils/sleep.d.ts +2 -0
  106. package/dist/utils/sleep.d.ts.map +1 -0
  107. package/dist/utils/sleep.js +15 -0
  108. package/dist/utils/sleep.js.map +1 -0
  109. package/dist/utils/stream-deadline.d.ts +74 -0
  110. package/dist/utils/stream-deadline.d.ts.map +1 -0
  111. package/dist/utils/stream-deadline.js +196 -0
  112. package/dist/utils/stream-deadline.js.map +1 -0
  113. package/package.json +118 -120
@@ -8,8 +8,9 @@ import { getPiUserAgent } from "../utils/pi-user-agent.js";
8
8
  import { getProviderEnvValue } from "../utils/provider-env.js";
9
9
  import { retryProviderRequest } from "../utils/provider-retry.js";
10
10
  import { sanitizeSurrogates } from "../utils/sanitize-unicode.js";
11
+ import { createStreamDeadline, withStreamDeadline } from "../utils/stream-deadline.js";
11
12
  import { getJsonSchemaToolParameters, resolveJsonSchemaStrictSampling } from "./constrained-sampling.js";
12
- import { buildCopilotDynamicHeaders, hasCopilotVisionInput } from "./github-copilot-headers.js";
13
+ import { buildCopilotDynamicHeaders, hasCopilotVisionInput, preserveCopilotIntegrationHeader, } from "./github-copilot-headers.js";
13
14
  import { adjustMaxTokensForThinking, buildBaseOptions, clampMaxTokensToContext } from "./simple-options.js";
14
15
  import { transformMessages } from "./transform-messages.js";
15
16
  /**
@@ -110,6 +111,10 @@ function convertContentBlocks(content) {
110
111
  }
111
112
  const FINE_GRAINED_TOOL_STREAMING_BETA = "fine-grained-tool-streaming-2025-05-14";
112
113
  const INTERLEAVED_THINKING_BETA = "interleaved-thinking-2025-05-14";
114
+ const SERVER_SIDE_FALLBACK_BETA = "server-side-fallback-2026-07-01";
115
+ function shouldUseServerSideFallbackBeta(model) {
116
+ return (model.compat?.allowedFallbackModels?.length ?? 0) > 0;
117
+ }
113
118
  function getAnthropicCompat(model) {
114
119
  return {
115
120
  supportsEagerToolInputStreaming: model.compat?.supportsEagerToolInputStreaming ?? true,
@@ -146,16 +151,8 @@ function mergeHeaders(...headerSources) {
146
151
  }
147
152
  return merged;
148
153
  }
149
- function mergeClientHeaders(model, ...headerSources) {
150
- const merged = mergeHeaders(...headerSources);
151
- if (model.provider === "kimi-coding") {
152
- for (const name of Object.keys(merged)) {
153
- if (name.toLowerCase() === "user-agent")
154
- delete merged[name];
155
- }
156
- merged["User-Agent"] = getPiUserAgent();
157
- }
158
- return merged;
154
+ function mergeClientHeaders(...headerSources) {
155
+ return mergeHeaders({ "User-Agent": getPiUserAgent() }, ...headerSources);
159
156
  }
160
157
  function hasHeader(headers, name) {
161
158
  if (!headers)
@@ -251,6 +248,10 @@ async function* iterateSseMessages(body, signal) {
251
248
  const decoder = new TextDecoder();
252
249
  const state = { event: null, data: [], raw: [] };
253
250
  let buffer = "";
251
+ const onAbort = () => {
252
+ void reader.cancel().catch(() => { });
253
+ };
254
+ signal?.addEventListener("abort", onAbort, { once: true });
254
255
  try {
255
256
  while (true) {
256
257
  if (signal?.aborted) {
@@ -293,6 +294,11 @@ async function* iterateSseMessages(body, signal) {
293
294
  }
294
295
  }
295
296
  finally {
297
+ signal?.removeEventListener("abort", onAbort);
298
+ try {
299
+ await reader.cancel();
300
+ }
301
+ catch { }
296
302
  reader.releaseLock();
297
303
  }
298
304
  }
@@ -348,9 +354,11 @@ export const stream = (model, context, options) => {
348
354
  stopReason: "pending",
349
355
  timestamp: Date.now(),
350
356
  };
357
+ const streamDeadline = createStreamDeadline(options?.streamDeadlineMs, options?.signal);
351
358
  try {
352
359
  let client;
353
360
  let isOAuth;
361
+ let usageModel = model;
354
362
  if (options?.client) {
355
363
  client = options.client;
356
364
  isOAuth = false;
@@ -361,14 +369,15 @@ export const stream = (model, context, options) => {
361
369
  let copilotDynamicHeaders;
362
370
  if (model.provider === "github-copilot") {
363
371
  const hasImages = hasCopilotVisionInput(context.messages);
364
- copilotDynamicHeaders = buildCopilotDynamicHeaders({
372
+ copilotDynamicHeaders = preserveCopilotIntegrationHeader(model.headers, buildCopilotDynamicHeaders({
365
373
  messages: context.messages,
366
374
  hasImages,
367
- });
375
+ apiKey,
376
+ }));
368
377
  }
369
378
  const cacheRetention = resolveCacheRetention(options?.cacheRetention, options?.env);
370
379
  const cacheSessionId = cacheRetention === "none" ? undefined : options?.sessionId;
371
- const created = createClient(model, apiKey, options?.interleavedThinking ?? true, shouldUseFineGrainedToolStreamingBeta(model, context), options?.headers, options?.fetch, copilotDynamicHeaders, cacheSessionId);
380
+ const created = createClient(model, apiKey, options?.interleavedThinking ?? true, shouldUseFineGrainedToolStreamingBeta(model, context), shouldUseServerSideFallbackBeta(model), options?.headers, options?.fetch, copilotDynamicHeaders, cacheSessionId);
372
381
  client = created.client;
373
382
  isOAuth = created.isOAuthToken;
374
383
  }
@@ -378,21 +387,26 @@ export const stream = (model, context, options) => {
378
387
  params = nextParams;
379
388
  }
380
389
  const requestOptions = {
381
- ...(options?.signal ? { signal: options.signal } : {}),
390
+ ...(streamDeadline.signal ? { signal: streamDeadline.signal } : {}),
382
391
  ...(options?.timeoutMs !== undefined ? { timeout: options.timeoutMs } : {}),
383
392
  maxRetries: 0,
384
393
  };
385
394
  const response = await retryProviderRequest(() => client.messages.create({ ...params, stream: true }, requestOptions).asResponse(), {
386
395
  maxRetries: options?.maxRetries,
387
396
  maxRetryDelayMs: options?.maxRetryDelayMs,
388
- signal: options?.signal,
397
+ signal: streamDeadline.signal,
389
398
  });
390
399
  await options?.onResponse?.({ status: response.status, headers: headersToRecord(response.headers) }, model);
391
400
  stream.push({ type: "start", partial: output });
392
401
  const blocks = output.content;
393
- for await (const event of iterateAnthropicEvents(response, options?.signal)) {
402
+ for await (const event of withStreamDeadline(iterateAnthropicEvents(response, streamDeadline.signal), streamDeadline.deadlineMs, streamDeadline.abort)) {
394
403
  if (event.type === "message_start") {
395
404
  output.responseId = event.message.id;
405
+ output.model = event.message.model;
406
+ const fallbackCost = output.model === model.id
407
+ ? undefined
408
+ : model.compat?.allowedFallbackModels?.find((fallback) => fallback.provider === model.provider && fallback.model === output.model)?.cost;
409
+ usageModel = fallbackCost ? { ...model, id: output.model, cost: fallbackCost } : model;
396
410
  // Capture initial token usage from message_start event
397
411
  // This ensures we have input token counts even if the stream is aborted early
398
412
  output.usage.input = event.message.usage.input_tokens || 0;
@@ -403,7 +417,7 @@ export const stream = (model, context, options) => {
403
417
  // Anthropic doesn't provide total_tokens, compute from components
404
418
  output.usage.totalTokens =
405
419
  output.usage.input + output.usage.output + output.usage.cacheRead + output.usage.cacheWrite;
406
- calculateCost(model, output.usage);
420
+ calculateCost(usageModel, output.usage);
407
421
  }
408
422
  else if (event.type === "content_block_start") {
409
423
  if (event.content_block.type === "text") {
@@ -572,7 +586,7 @@ export const stream = (model, context, options) => {
572
586
  // Anthropic doesn't provide total_tokens, compute from components
573
587
  output.usage.totalTokens =
574
588
  output.usage.input + output.usage.output + output.usage.cacheRead + output.usage.cacheWrite;
575
- calculateCost(model, output.usage);
589
+ calculateCost(usageModel, output.usage);
576
590
  }
577
591
  }
578
592
  if (options?.signal?.aborted) {
@@ -598,6 +612,9 @@ export const stream = (model, context, options) => {
598
612
  stream.push({ type: "error", reason: output.stopReason, error: output });
599
613
  stream.end();
600
614
  }
615
+ finally {
616
+ streamDeadline.cleanup();
617
+ }
601
618
  })();
602
619
  return stream;
603
620
  };
@@ -624,9 +641,15 @@ function mapThinkingLevelToEffort(model, level) {
624
641
  }
625
642
  export const streamSimple = (model, context, options) => {
626
643
  assertRequestAuth(model.provider, options?.apiKey, options?.headers);
627
- const base = buildBaseOptions(model, context, options, options?.apiKey);
644
+ const base = {
645
+ ...buildBaseOptions(model, context, options, options?.apiKey),
646
+ toolChoice: options?.toolChoice,
647
+ };
628
648
  if (!options?.reasoning) {
629
- return stream(model, context, { ...base, thinkingEnabled: false });
649
+ return stream(model, context, {
650
+ ...base,
651
+ thinkingEnabled: false,
652
+ });
630
653
  }
631
654
  // For models with adaptive thinking: use an effort level.
632
655
  // For older models: use budget-based thinking.
@@ -652,7 +675,7 @@ export const streamSimple = (model, context, options) => {
652
675
  function isOAuthToken(apiKey) {
653
676
  return apiKey.includes("sk-ant-oat");
654
677
  }
655
- function createClient(model, apiKey, interleavedThinking, useFineGrainedToolStreamingBeta, optionsHeaders, fetch, dynamicHeaders, sessionId) {
678
+ function createClient(model, apiKey, interleavedThinking, useFineGrainedToolStreamingBeta, useServerSideFallbackBeta, optionsHeaders, fetch, dynamicHeaders, sessionId) {
656
679
  // Adaptive thinking models have interleaved thinking built in, so skip the beta header.
657
680
  const needsInterleavedBeta = interleavedThinking && model.compat?.forceAdaptiveThinking !== true;
658
681
  const betaFeatures = [];
@@ -662,6 +685,9 @@ function createClient(model, apiKey, interleavedThinking, useFineGrainedToolStre
662
685
  if (needsInterleavedBeta) {
663
686
  betaFeatures.push(INTERLEAVED_THINKING_BETA);
664
687
  }
688
+ if (useServerSideFallbackBeta) {
689
+ betaFeatures.push(SERVER_SIDE_FALLBACK_BETA);
690
+ }
665
691
  // Copilot: Bearer auth, selective betas.
666
692
  if (model.provider === "github-copilot") {
667
693
  const client = new Anthropic({
@@ -670,7 +696,7 @@ function createClient(model, apiKey, interleavedThinking, useFineGrainedToolStre
670
696
  baseURL: model.baseUrl,
671
697
  dangerouslyAllowBrowser: true,
672
698
  fetch,
673
- defaultHeaders: mergeClientHeaders(model, {
699
+ defaultHeaders: mergeClientHeaders({
674
700
  accept: "application/json",
675
701
  "anthropic-dangerous-direct-browser-access": "true",
676
702
  ...(betaFeatures.length > 0 ? { "anthropic-beta": betaFeatures.join(",") } : {}),
@@ -686,7 +712,7 @@ function createClient(model, apiKey, interleavedThinking, useFineGrainedToolStre
686
712
  baseURL: model.baseUrl,
687
713
  dangerouslyAllowBrowser: true,
688
714
  fetch,
689
- defaultHeaders: mergeClientHeaders(model, {
715
+ defaultHeaders: mergeClientHeaders({
690
716
  accept: "application/json",
691
717
  "anthropic-dangerous-direct-browser-access": "true",
692
718
  "anthropic-beta": ["claude-code-20250219", "oauth-2025-04-20", ...betaFeatures].join(","),
@@ -698,7 +724,7 @@ function createClient(model, apiKey, interleavedThinking, useFineGrainedToolStre
698
724
  }
699
725
  // API key or header-owned auth.
700
726
  const sessionAffinityHeaders = sessionId && getAnthropicCompat(model).sendSessionAffinityHeaders ? { "x-session-affinity": sessionId } : {};
701
- const defaultHeaders = mergeClientHeaders(model, {
727
+ const defaultHeaders = mergeClientHeaders({
702
728
  accept: "application/json",
703
729
  "anthropic-dangerous-direct-browser-access": "true",
704
730
  ...(betaFeatures.length > 0 ? { "anthropic-beta": betaFeatures.join(",") } : {}),
@@ -813,6 +839,10 @@ function buildParams(model, context, isOAuthToken, options) {
813
839
  params.tool_choice = options.toolChoice;
814
840
  }
815
841
  }
842
+ const allowedFallbackModels = model.compat?.allowedFallbackModels;
843
+ if (allowedFallbackModels && allowedFallbackModels.length > 0) {
844
+ params.fallbacks = allowedFallbackModels.map((fallback) => ({ model: fallback.model }));
845
+ }
816
846
  return params;
817
847
  }
818
848
  // Normalize tool call IDs to match Anthropic's required pattern and length