@bastani/pi-ai 0.9.43 → 0.9.44-alpha.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 (77) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/dist/api/anthropic-messages.d.ts.map +1 -1
  3. package/dist/api/anthropic-messages.js +45 -58
  4. package/dist/api/anthropic-messages.js.map +1 -1
  5. package/dist/api/azure-openai-responses.d.ts.map +1 -1
  6. package/dist/api/azure-openai-responses.js +0 -6
  7. package/dist/api/azure-openai-responses.js.map +1 -1
  8. package/dist/api/bedrock-converse-stream.d.ts.map +1 -1
  9. package/dist/api/bedrock-converse-stream.js +71 -78
  10. package/dist/api/bedrock-converse-stream.js.map +1 -1
  11. package/dist/api/constrained-sampling.d.ts +11 -1
  12. package/dist/api/constrained-sampling.d.ts.map +1 -1
  13. package/dist/api/constrained-sampling.js +33 -18
  14. package/dist/api/constrained-sampling.js.map +1 -1
  15. package/dist/api/google-generative-ai.d.ts.map +1 -1
  16. package/dist/api/google-generative-ai.js +0 -6
  17. package/dist/api/google-generative-ai.js.map +1 -1
  18. package/dist/api/google-shared.d.ts +2 -1
  19. package/dist/api/google-shared.d.ts.map +1 -1
  20. package/dist/api/google-shared.js +57 -2
  21. package/dist/api/google-shared.js.map +1 -1
  22. package/dist/api/google-vertex.d.ts.map +1 -1
  23. package/dist/api/google-vertex.js +0 -6
  24. package/dist/api/google-vertex.js.map +1 -1
  25. package/dist/api/mistral-conversations.d.ts.map +1 -1
  26. package/dist/api/mistral-conversations.js +17 -44
  27. package/dist/api/mistral-conversations.js.map +1 -1
  28. package/dist/api/openai-codex-responses.d.ts.map +1 -1
  29. package/dist/api/openai-codex-responses.js +0 -5
  30. package/dist/api/openai-codex-responses.js.map +1 -1
  31. package/dist/api/openai-completions.d.ts.map +1 -1
  32. package/dist/api/openai-completions.js +34 -53
  33. package/dist/api/openai-completions.js.map +1 -1
  34. package/dist/api/openai-responses-shared.d.ts.map +1 -1
  35. package/dist/api/openai-responses-shared.js +58 -57
  36. package/dist/api/openai-responses-shared.js.map +1 -1
  37. package/dist/api/openai-responses.d.ts.map +1 -1
  38. package/dist/api/openai-responses.js +0 -6
  39. package/dist/api/openai-responses.js.map +1 -1
  40. package/dist/api/pi-messages.d.ts +2 -0
  41. package/dist/api/pi-messages.d.ts.map +1 -1
  42. package/dist/api/pi-messages.js +15 -9
  43. package/dist/api/pi-messages.js.map +1 -1
  44. package/dist/index.d.ts +1 -0
  45. package/dist/index.d.ts.map +1 -1
  46. package/dist/index.js +1 -0
  47. package/dist/index.js.map +1 -1
  48. package/dist/providers/data/.manifest.json +1 -1
  49. package/dist/providers/data/kimi-coding.json +1 -1
  50. package/dist/providers/data/moonshotai-cn.json +1 -1
  51. package/dist/providers/data/moonshotai.json +1 -1
  52. package/dist/providers/data/opencode-go.json +1 -1
  53. package/dist/providers/data/openrouter.json +1 -1
  54. package/dist/providers/data/vercel-ai-gateway.json +1 -1
  55. package/dist/providers/faux.d.ts.map +1 -1
  56. package/dist/providers/faux.js +7 -2
  57. package/dist/providers/faux.js.map +1 -1
  58. package/dist/providers/radius-config.d.ts.map +1 -1
  59. package/dist/providers/radius-config.js +2 -1
  60. package/dist/providers/radius-config.js.map +1 -1
  61. package/dist/types.js.map +1 -1
  62. package/dist/utils/error-body.d.ts.map +1 -1
  63. package/dist/utils/error-body.js +2 -1
  64. package/dist/utils/error-body.js.map +1 -1
  65. package/dist/utils/streaming-json.d.ts +84 -0
  66. package/dist/utils/streaming-json.d.ts.map +1 -0
  67. package/dist/utils/streaming-json.js +513 -0
  68. package/dist/utils/streaming-json.js.map +1 -0
  69. package/dist/utils/text.d.ts +7 -0
  70. package/dist/utils/text.d.ts.map +1 -1
  71. package/dist/utils/text.js +9 -0
  72. package/dist/utils/text.js.map +1 -1
  73. package/dist/utils/version.d.ts +1 -1
  74. package/dist/utils/version.d.ts.map +1 -1
  75. package/dist/utils/version.js +1 -1
  76. package/dist/utils/version.js.map +1 -1
  77. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -4,6 +4,29 @@ This package is a Bastani fork of `@earendil-works/pi-ai`. Upstream history at t
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [0.9.44-alpha.1] - 2026-10-24
8
+
9
+ ### Breaking Changes
10
+
11
+ - `appendGrammarToolInputJsonDelta()` (`api/constrained-sampling`) takes the input delta instead of the whole input so far and no longer closes the input; close it with the new `closeGrammarToolInputJson()` ([pi #9265](https://github.com/earendil-works/pi/issues/9265))
12
+
13
+ ### Added
14
+
15
+ - Assistant messages from the `pi-messages` API now carry `responseModel`, the concrete model the server reports on its terminal `done` or `error` event, so clients of virtual models can see which model answered ([pi #11265](https://github.com/earendil-works/pi/pull/11265))
16
+ - Added `StreamingJsonParser`, an incremental, tolerant parser for JSON text that arrives in pieces (each piece is read once; snapshots of the partial value never change once returned), and `ToolCallArgumentsStream`, which every provider now uses for streamed tool-call arguments; `StreamingJsonParser.append()` also returns the changes each piece made (`JsonChange`), for writers that keep their own copy ([pi #9265](https://github.com/earendil-works/pi/issues/9265))
17
+
18
+ ### Changed
19
+
20
+ - The faux provider streams partial tool-call arguments, so `toolcall_delta` partials show growing arguments as real providers do ([pi #9265](https://github.com/earendil-works/pi/issues/9265))
21
+
22
+ ### Fixed
23
+
24
+ - Claude models with mid-conversation effort no longer fail with `Invalid effort level` on accounts whose organization caps effort below `high`, even at `low` or `medium` thinking: the fixed top-level effort is now `low`, and the per-message effort markers still set the level for each reply ([pi #10063](https://github.com/earendil-works/pi/issues/10063))
25
+ - The faux provider no longer counts newly cached prompt tokens both as `input` and as `cacheWrite` when it simulates prompt caching for a `sessionId`. Every prompt token now goes to either `cacheRead` or `cacheWrite`, and `input` is 0 ([pi f3fa8bd9](https://github.com/earendil-works/pi/commit/f3fa8bd92430a2bf443681027a01c58586f35464))
26
+ - Gemini tool schemas sent through `parametersJsonSchema` now express string and number `const` values as single-value `enum`s, which Gemini honors (it ignores `const`). Only schema-bearing keywords are rewritten, so literal data under `default`, `examples`, `enum`, `const` and custom keywords, and property names such as `__proto__`, are sent unchanged ([pi 7314819f](https://github.com/earendil-works/pi/commit/7314819f0fe30fa616f32fa3a1fc8d1a856a1446), [pi #11308](https://github.com/earendil-works/pi/issues/11308))
27
+ - Kimi K3.1 Pro and K3.1 Pro Highspeed on Kimi Coding, Moonshot, and OpenCode Go no longer use Kimi K2 request settings. They now send `reasoning_effort` (low/high/max), return earlier reasoning, accept tools added mid-conversation, and report Moonshot rates on Kimi Coding; OpenRouter and Vercel AI Gateway clamp their output limit like Kimi K3. Kimi Coding `k3-256k` now reports Kimi K3 rates ([pi 1de2d7ad](https://github.com/earendil-works/pi/commit/1de2d7adeacfd1b065d9df091997d44c43120ade))
28
+ - Streamed tool-call arguments are no longer re-parsed in full on every delta, which made CPU time and memory grow quadratically with the size of a tool call (a 90 KB `write` on Anthropic peaked at about 140 MB of heap; a 500 KB one took minutes). Every provider and the pi-messages client now read each delta once, and keep their stream state (`partialJson`, `partialArgs`, `index`, `customInput`, `streamIndex`, Bedrock's encrypted reasoning bytes) off the content blocks, so it no longer reaches partial messages or transcripts of failed streams. Grammar (custom) tool input streaming no longer copies the whole input on every delta, and truncated provider error bodies and pi-messages diagnostics no longer keep the whole text they were cut from alive ([pi #9265](https://github.com/earendil-works/pi/issues/9265))
29
+
7
30
  ## [0.9.42] - 2026-10-23
8
31
 
9
32
  ### Added
@@ -1 +1 @@
1
- {"version":3,"file":"anthropic-messages.d.ts","sourceRoot":"","sources":["../../src/api/anthropic-messages.ts"],"names":[],"mappings":"AAAA,OAAO,SAAkD,MAAM,mBAAmB,CAAC;AAiBnF,OAAO,KAAK,EASX,mBAAmB,EAEnB,cAAc,EACd,aAAa,EAOb,MAAM,aAAa,CAAC;AA8KrB,MAAM,MAAM,eAAe,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,OAAO,GAAG,KAAK,CAAC;AAE1E,MAAM,MAAM,wBAAwB,GAAG,YAAY,GAAG,SAAS,CAAC;AAqPhE,MAAM,WAAW,gBAAiB,SAAQ,aAAa;IACtD;;;;;;OAMG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B;;;;OAIG;IACH,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B;;;;;;;;;;;OAWG;IACH,MAAM,CAAC,EAAE,eAAe,CAAC;IACzB;;;;;;;;;;;OAWG;IACH,eAAe,CAAC,EAAE,wBAAwB,CAAC;IAC3C;;;;;OAKG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,GAAG,KAAK,GAAG,MAAM,GAAG;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IACtE;;;;OAIG;IACH,MAAM,CAAC,EAAE,SAAS,CAAC;CACnB;AAsTD,eAAO,MAAM,MAAM,EAAE,cAAc,CAAC,oBAAoB,EAAE,gBAAgB,CAiazE,CAAC;AA4BF,eAAO,MAAM,YAAY,EAAE,cAAc,CAAC,oBAAoB,EAAE,mBAAmB,CAgDlF,CAAC"}
1
+ {"version":3,"file":"anthropic-messages.d.ts","sourceRoot":"","sources":["../../src/api/anthropic-messages.ts"],"names":[],"mappings":"AAAA,OAAO,SAAkD,MAAM,mBAAmB,CAAC;AAiBnF,OAAO,KAAK,EASX,mBAAmB,EAEnB,cAAc,EACd,aAAa,EAKb,MAAM,aAAa,CAAC;AA+KrB,MAAM,MAAM,eAAe,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,OAAO,GAAG,KAAK,CAAC;AAE1E,MAAM,MAAM,wBAAwB,GAAG,YAAY,GAAG,SAAS,CAAC;AAqPhE,MAAM,WAAW,gBAAiB,SAAQ,aAAa;IACtD;;;;;;OAMG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B;;;;OAIG;IACH,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B;;;;;;;;;;;OAWG;IACH,MAAM,CAAC,EAAE,eAAe,CAAC;IACzB;;;;;;;;;;;OAWG;IACH,eAAe,CAAC,EAAE,wBAAwB,CAAC;IAC3C;;;;;OAKG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,GAAG,KAAK,GAAG,MAAM,GAAG;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IACtE;;;;OAIG;IACH,MAAM,CAAC,EAAE,SAAS,CAAC;CACnB;AAsTD,eAAO,MAAM,MAAM,EAAE,cAAc,CAAC,oBAAoB,EAAE,gBAAgB,CAgZzE,CAAC;AA4BF,eAAO,MAAM,YAAY,EAAE,cAAc,CAAC,oBAAoB,EAAE,mBAAmB,CAgDlF,CAAC"}
@@ -7,13 +7,14 @@ import { assertSupportedDocumentMimeType } from "../utils/document-input.js";
7
7
  import { AssistantMessageEventStream } from "../utils/event-stream.js";
8
8
  import { headersToRecord } from "../utils/headers.js";
9
9
  import { getIdentityHeaders } from "../utils/identity.js";
10
- import { parseJsonWithRepair, parseStreamingJson, parseToolCallArguments } from "../utils/json-parse.js";
10
+ import { parseJsonWithRepair } from "../utils/json-parse.js";
11
11
  import { applyOpenRouterReportedCost, isOpenRouterModel, } from "../utils/openrouter-cost.js";
12
12
  import { getProviderEnvValue } from "../utils/provider-env.js";
13
13
  import { retryProviderRequest } from "../utils/provider-retry.js";
14
14
  import { getFetchAdmission } from "../utils/request-admission.js";
15
15
  import { sanitizeSurrogates } from "../utils/sanitize-unicode.js";
16
16
  import { createStreamDeadline, withStreamDeadline } from "../utils/stream-deadline.js";
17
+ import { ToolCallArgumentsStream } from "../utils/streaming-json.js";
17
18
  import { getSystemMessageText, renderSystemMessageUpdate } from "../utils/text.js";
18
19
  import { getCurrentTools, getInitialSystemMessage, resolveTranscript, } from "../utils/transcript.js";
19
20
  import { ANTHROPIC_STRICT_JSON_SCHEMA_PROFILE, getJsonSchemaToolParameters, resolveJsonSchemaStrictSampling, } from "./constrained-sampling.js";
@@ -685,7 +686,12 @@ export const stream = (model, context, options) => {
685
686
  }
686
687
  await options?.onResponse?.({ status: response.status, headers: headersToRecord(response.headers) }, model);
687
688
  stream.push({ type: "start", partial: output });
688
- const blocks = output.content;
689
+ // Stream state per Anthropic block index, kept off the content blocks so it never reaches partials.
690
+ const openBlocks = new Map();
691
+ const openBlock = (anthropicIndex) => {
692
+ const open = openBlocks.get(anthropicIndex);
693
+ return open === undefined ? undefined : { ...open, block: output.content[open.contentIndex] };
694
+ };
689
695
  for await (const event of withStreamDeadline(iterateAnthropicEvents(response, streamDeadline.signal), streamDeadline.deadlineMs, streamDeadline.abort)) {
690
696
  await options?.onProviderStreamEvent?.(event, model);
691
697
  if (event.type === "message_start") {
@@ -715,47 +721,42 @@ export const stream = (model, context, options) => {
715
721
  }
716
722
  else if (event.type === "content_block_start") {
717
723
  if (event.content_block.type === "text") {
718
- const block = {
719
- type: "text",
720
- text: event.content_block.text ?? "",
721
- index: event.index,
722
- };
723
- output.content.push(block);
724
+ output.content.push({ type: "text", text: event.content_block.text ?? "" });
725
+ openBlocks.set(event.index, { contentIndex: output.content.length - 1 });
724
726
  stream.push({ type: "text_start", contentIndex: output.content.length - 1, partial: output });
725
727
  }
726
728
  else if (event.content_block.type === "thinking") {
727
- const block = {
729
+ output.content.push({
728
730
  type: "thinking",
729
731
  thinking: event.content_block.thinking ?? "",
730
732
  thinkingSignature: event.content_block.signature ?? "",
731
- index: event.index,
732
- };
733
- output.content.push(block);
733
+ });
734
+ openBlocks.set(event.index, { contentIndex: output.content.length - 1 });
734
735
  stream.push({ type: "thinking_start", contentIndex: output.content.length - 1, partial: output });
735
736
  }
736
737
  else if (event.content_block.type === "redacted_thinking") {
737
- const block = {
738
+ output.content.push({
738
739
  type: "thinking",
739
740
  thinking: "[Reasoning redacted]",
740
741
  thinkingSignature: event.content_block.data,
741
742
  redacted: true,
742
- index: event.index,
743
- };
744
- output.content.push(block);
743
+ });
744
+ openBlocks.set(event.index, { contentIndex: output.content.length - 1 });
745
745
  stream.push({ type: "thinking_start", contentIndex: output.content.length - 1, partial: output });
746
746
  }
747
747
  else if (event.content_block.type === "tool_use") {
748
- const block = {
748
+ output.content.push({
749
749
  type: "toolCall",
750
750
  id: event.content_block.id,
751
751
  name: isOAuth
752
752
  ? fromClaudeCodeName(event.content_block.name, currentTools)
753
753
  : event.content_block.name,
754
754
  arguments: event.content_block.input ?? {},
755
- partialJson: "",
756
- index: event.index,
757
- };
758
- output.content.push(block);
755
+ });
756
+ openBlocks.set(event.index, {
757
+ contentIndex: output.content.length - 1,
758
+ arguments: new ToolCallArgumentsStream(),
759
+ });
759
760
  stream.push({ type: "toolcall_start", contentIndex: output.content.length - 1, partial: output });
760
761
  }
761
762
  else if (event.content_block.type === "fallback") {
@@ -785,60 +786,52 @@ export const stream = (model, context, options) => {
785
786
  }
786
787
  }
787
788
  else if (event.type === "content_block_delta") {
789
+ const open = openBlock(event.index);
788
790
  if (event.delta.type === "text_delta") {
789
- const index = blocks.findIndex((b) => b.index === event.index);
790
- const block = blocks[index];
791
- if (block && block.type === "text") {
792
- block.text += event.delta.text;
791
+ if (open?.block.type === "text") {
792
+ open.block.text += event.delta.text;
793
793
  stream.push({
794
794
  type: "text_delta",
795
- contentIndex: index,
795
+ contentIndex: open.contentIndex,
796
796
  delta: event.delta.text,
797
797
  partial: output,
798
798
  });
799
799
  }
800
800
  }
801
801
  else if (event.delta.type === "thinking_delta") {
802
- const index = blocks.findIndex((b) => b.index === event.index);
803
- const block = blocks[index];
804
- if (block && block.type === "thinking") {
805
- block.thinking += event.delta.thinking;
802
+ if (open?.block.type === "thinking") {
803
+ open.block.thinking += event.delta.thinking;
806
804
  stream.push({
807
805
  type: "thinking_delta",
808
- contentIndex: index,
806
+ contentIndex: open.contentIndex,
809
807
  delta: event.delta.thinking,
810
808
  partial: output,
811
809
  });
812
810
  }
813
811
  }
814
812
  else if (event.delta.type === "input_json_delta") {
815
- const index = blocks.findIndex((b) => b.index === event.index);
816
- const block = blocks[index];
817
- if (block && block.type === "toolCall") {
818
- block.partialJson += event.delta.partial_json;
819
- block.arguments = parseStreamingJson(block.partialJson);
813
+ if (open?.block.type === "toolCall" && open.arguments !== undefined) {
814
+ open.block.arguments = open.arguments.append(event.delta.partial_json);
820
815
  stream.push({
821
816
  type: "toolcall_delta",
822
- contentIndex: index,
817
+ contentIndex: open.contentIndex,
823
818
  delta: event.delta.partial_json,
824
819
  partial: output,
825
820
  });
826
821
  }
827
822
  }
828
823
  else if (event.delta.type === "signature_delta") {
829
- const index = blocks.findIndex((b) => b.index === event.index);
830
- const block = blocks[index];
831
- if (block && block.type === "thinking") {
832
- block.thinkingSignature = block.thinkingSignature || "";
833
- block.thinkingSignature += event.delta.signature;
824
+ if (open?.block.type === "thinking") {
825
+ open.block.thinkingSignature = open.block.thinkingSignature || "";
826
+ open.block.thinkingSignature += event.delta.signature;
834
827
  }
835
828
  }
836
829
  }
837
830
  else if (event.type === "content_block_stop") {
838
- const index = blocks.findIndex((b) => b.index === event.index);
839
- const block = blocks[index];
840
- if (block) {
841
- delete block.index;
831
+ const open = openBlock(event.index);
832
+ openBlocks.delete(event.index);
833
+ if (open) {
834
+ const { block, contentIndex: index } = open;
842
835
  if (block.type === "text") {
843
836
  stream.push({
844
837
  type: "text_end",
@@ -855,11 +848,8 @@ export const stream = (model, context, options) => {
855
848
  partial: output,
856
849
  });
857
850
  }
858
- else if (block.type === "toolCall") {
859
- Object.assign(block, parseToolCallArguments(block.partialJson));
860
- // Finalize in-place and strip the scratch buffer so replay only
861
- // carries parsed arguments.
862
- delete block.partialJson;
851
+ else if (block.type === "toolCall" && open.arguments !== undefined) {
852
+ Object.assign(block, open.arguments.finish());
863
853
  stream.push({
864
854
  type: "toolcall_end",
865
855
  contentIndex: index,
@@ -948,11 +938,6 @@ export const stream = (model, context, options) => {
948
938
  !output.diagnostics?.some((diagnostic) => diagnostic.type === "anthropic_input_transformations")) {
949
939
  recordInputTransformations(output, inputTransformations);
950
940
  }
951
- for (const block of output.content) {
952
- delete block.index;
953
- // partialJson is only a streaming scratch buffer; never persist it.
954
- delete block.partialJson;
955
- }
956
941
  output.stopReason = options?.signal?.aborted ? "aborted" : "error";
957
942
  output.errorMessage = error instanceof Error ? error.message : JSON.stringify(error);
958
943
  stream.push({ type: "error", reason: output.stopReason, error: output });
@@ -1241,14 +1226,16 @@ function buildParams(model, context, isOAuthToken, options, disablePreferredStri
1241
1226
  }
1242
1227
  }
1243
1228
  // Managed effort models always use adaptive thinking. Their per-turn markers
1244
- // carry the actual effort; the top-level high value deliberately stays stable.
1229
+ // carry the actual effort for every reply.
1245
1230
  if (effortMessages) {
1246
1231
  params.thinking = {
1247
1232
  type: "adaptive",
1248
1233
  display: options?.thinkingDisplay ?? "summarized",
1249
1234
  block_binding: { prefix_mismatch_behavior: "drop_block" },
1250
1235
  };
1251
- params.output_config = { effort: "high" };
1236
+ // The top-level effort is part of the cached prompt, so it stays fixed. It is "low" because
1237
+ // organizations can cap effort and reject requests containing any level above the cap.
1238
+ params.output_config = { effort: "low" };
1252
1239
  }
1253
1240
  else if (model.reasoning) {
1254
1241
  if (options?.thinkingEnabled) {