@code-yeongyu/senpi-ai 2026.9.12 → 2026.9.13

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 (110) hide show
  1. package/README.md +39 -4
  2. package/dist/api/anthropic-messages.d.ts.map +1 -1
  3. package/dist/api/anthropic-messages.js +8 -1
  4. package/dist/api/anthropic-messages.js.map +1 -1
  5. package/dist/api/cloudflare-ai-binding.d.ts +77 -0
  6. package/dist/api/cloudflare-ai-binding.d.ts.map +1 -0
  7. package/dist/api/cloudflare-ai-binding.js +72 -0
  8. package/dist/api/cloudflare-ai-binding.js.map +1 -0
  9. package/dist/api/cursor-agent/measure.d.ts +11 -0
  10. package/dist/api/cursor-agent/measure.d.ts.map +1 -1
  11. package/dist/api/cursor-agent/measure.js +18 -0
  12. package/dist/api/cursor-agent/measure.js.map +1 -1
  13. package/dist/api/cursor-agent.d.ts +1 -1
  14. package/dist/api/cursor-agent.d.ts.map +1 -1
  15. package/dist/api/cursor-agent.js +5 -1
  16. package/dist/api/cursor-agent.js.map +1 -1
  17. package/dist/api/mistral-conversations.js +4 -1
  18. package/dist/api/mistral-conversations.js.map +1 -1
  19. package/dist/api/openai-codex-responses.d.ts.map +1 -1
  20. package/dist/api/openai-codex-responses.js +6 -3
  21. package/dist/api/openai-codex-responses.js.map +1 -1
  22. package/dist/api/openai-responses-shared.d.ts.map +1 -1
  23. package/dist/api/openai-responses-shared.js +4 -1
  24. package/dist/api/openai-responses-shared.js.map +1 -1
  25. package/dist/api/openai-responses.d.ts.map +1 -1
  26. package/dist/api/openai-responses.js +13 -3
  27. package/dist/api/openai-responses.js.map +1 -1
  28. package/dist/cursor/context-limit-store.d.ts +32 -0
  29. package/dist/cursor/context-limit-store.d.ts.map +1 -0
  30. package/dist/cursor/context-limit-store.js +62 -0
  31. package/dist/cursor/context-limit-store.js.map +1 -0
  32. package/dist/cursor/store-migration.d.ts.map +1 -1
  33. package/dist/cursor/store-migration.js +2 -1
  34. package/dist/cursor/store-migration.js.map +1 -1
  35. package/dist/index.d.ts +2 -1
  36. package/dist/index.d.ts.map +1 -1
  37. package/dist/index.js +2 -1
  38. package/dist/index.js.map +1 -1
  39. package/dist/models.d.ts +1 -0
  40. package/dist/models.d.ts.map +1 -1
  41. package/dist/models.js +5 -2
  42. package/dist/models.js.map +1 -1
  43. package/dist/openai-responses-compat.d.ts +1 -1
  44. package/dist/openai-responses-compat.d.ts.map +1 -1
  45. package/dist/openai-responses-compat.js.map +1 -1
  46. package/dist/providers/cloudflare-ai-gateway.d.ts.map +1 -1
  47. package/dist/providers/cloudflare-ai-gateway.js +4 -0
  48. package/dist/providers/cloudflare-ai-gateway.js.map +1 -1
  49. package/dist/providers/cursor.d.ts.map +1 -1
  50. package/dist/providers/cursor.js +2 -1
  51. package/dist/providers/cursor.js.map +1 -1
  52. package/dist/providers/data/.manifest.json +1 -1
  53. package/dist/providers/data/azure-openai-responses.json +1 -1
  54. package/dist/providers/data/baseten.json +1 -1
  55. package/dist/providers/data/deepseek.json +1 -1
  56. package/dist/providers/data/fireworks.json +1 -1
  57. package/dist/providers/data/github-copilot.json +1 -1
  58. package/dist/providers/data/openai-codex.json +1 -1
  59. package/dist/providers/data/openrouter.json +1 -1
  60. package/dist/providers/data/qwen-token-plan-cn.json +1 -1
  61. package/dist/providers/data/qwen-token-plan-individual.json +1 -1
  62. package/dist/providers/data/qwen-token-plan.json +1 -1
  63. package/dist/providers/data/vercel-ai-gateway.json +1 -1
  64. package/dist/providers/data/xai.json +1 -1
  65. package/dist/providers/faux.js +5 -5
  66. package/dist/providers/faux.js.map +1 -1
  67. package/dist/providers/opencode-go.d.ts.map +1 -1
  68. package/dist/providers/opencode-go.js +4 -3
  69. package/dist/providers/opencode-go.js.map +1 -1
  70. package/dist/providers/opencode-headers.d.ts +4 -0
  71. package/dist/providers/opencode-headers.d.ts.map +1 -0
  72. package/dist/providers/opencode-headers.js +22 -0
  73. package/dist/providers/opencode-headers.js.map +1 -0
  74. package/dist/providers/opencode.d.ts.map +1 -1
  75. package/dist/providers/opencode.js +5 -4
  76. package/dist/providers/opencode.js.map +1 -1
  77. package/dist/types.d.ts +16 -6
  78. package/dist/types.d.ts.map +1 -1
  79. package/dist/types.js.map +1 -1
  80. package/dist/utils/assistant-message-frame.d.ts +77 -0
  81. package/dist/utils/assistant-message-frame.d.ts.map +1 -0
  82. package/dist/utils/assistant-message-frame.js +427 -0
  83. package/dist/utils/assistant-message-frame.js.map +1 -0
  84. package/dist/utils/cursor-context-limit.d.ts +12 -0
  85. package/dist/utils/cursor-context-limit.d.ts.map +1 -0
  86. package/dist/utils/cursor-context-limit.js +93 -0
  87. package/dist/utils/cursor-context-limit.js.map +1 -0
  88. package/dist/utils/event-stream.d.ts.map +1 -1
  89. package/dist/utils/event-stream.js +25 -5
  90. package/dist/utils/event-stream.js.map +1 -1
  91. package/dist/utils/prompt-cache-ttl.d.ts +1 -1
  92. package/dist/utils/prompt-cache-ttl.d.ts.map +1 -1
  93. package/dist/utils/prompt-cache-ttl.js +5 -1
  94. package/dist/utils/prompt-cache-ttl.js.map +1 -1
  95. package/dist/utils/retry-profile/types.d.ts +10 -1
  96. package/dist/utils/retry-profile/types.d.ts.map +1 -1
  97. package/dist/utils/retry-profile/types.js.map +1 -1
  98. package/dist/utils/retry.d.ts +13 -2
  99. package/dist/utils/retry.d.ts.map +1 -1
  100. package/dist/utils/retry.js +18 -7
  101. package/dist/utils/retry.js.map +1 -1
  102. package/dist/utils/uuid.d.ts +2 -2
  103. package/dist/utils/uuid.d.ts.map +1 -1
  104. package/dist/utils/uuid.js +36 -31
  105. package/dist/utils/uuid.js.map +1 -1
  106. package/package.json +6 -6
  107. package/dist/api/cloudflare-gateway-binding.d.ts +0 -69
  108. package/dist/api/cloudflare-gateway-binding.d.ts.map +0 -1
  109. package/dist/api/cloudflare-gateway-binding.js +0 -159
  110. package/dist/api/cloudflare-gateway-binding.js.map +0 -1
package/README.md CHANGED
@@ -26,6 +26,7 @@ Unified LLM API with provider collections, automatic auth resolution, token and
26
26
  - [Streaming Tool Calls with Partial JSON](#streaming-tool-calls-with-partial-json)
27
27
  - [Validating Tool Arguments](#validating-tool-arguments)
28
28
  - [Complete Event Reference](#complete-event-reference)
29
+ - [Compact Assistant Message Frames](#compact-assistant-message-frames)
29
30
  - [Image Input](#image-input)
30
31
  - [Image Generation](#image-generation)
31
32
  - [Thinking/Reasoning](#thinkingreasoning)
@@ -662,6 +663,10 @@ for await (const event of s) {
662
663
 
663
664
  ### Complete Event Reference
664
665
 
666
+ Successful generation follows `start → updates* → done`. A failure after generation starts follows `start → updates* → error`. Request setup may fail before generation starts, in which case the stream contains only `error`; `done` and update events are invalid before `start`. Direct API `streamSimple()` calls throw synchronously when request auth is missing.
667
+
668
+ Every non-terminal event's `partial` is the shared live response-so-far helper. It is intentionally not an event-time snapshot: providers may mutate the same message and content blocks as generation advances, including while older events wait in the stream queue. Inspect it when handling an event instead of retaining it as historical state. Text and ordinary thinking blocks are empty when their `*_start` event is emitted and grow only through matching `*_delta` events until the authoritative `*_end`; redacted thinking may be complete at start and emit no deltas. Tool-call arguments at `toolcall_start` are provider-specific; `toolcall_delta` carries subsequent JSON updates.
669
+
665
670
  All streaming events emitted during assistant message generation:
666
671
 
667
672
  | Event Type | Description | Key Properties |
@@ -675,12 +680,40 @@ All streaming events emitted during assistant message generation:
675
680
  | `thinking_end` | Thinking block complete | `content`: Full thinking, `contentIndex`: Position |
676
681
  | `toolcall_start` | Tool call begins | `contentIndex`: Position in content array |
677
682
  | `toolcall_delta` | Tool arguments streaming | `delta`: JSON chunk, `partial.content[contentIndex].arguments`: Partial parsed args |
678
- | `toolcall_end` | Tool call complete | `toolCall`: Complete validated tool call with `id`, `name`, `arguments` |
683
+ | `toolcall_end` | Tool call complete | `toolCall`: Complete, but not schema-validated, tool call with `id`, `name`, `arguments` |
679
684
  | `done` | Stream complete | `reason`: Stop reason ("stop", "length", "toolUse"), `message`: Final assistant message |
680
685
  | `error` | Error occurred | `reason`: Error type ("error" or "aborted"), `error`: AssistantMessage with partial content |
681
686
 
682
687
  Streaming events for different content blocks are not guaranteed to be contiguous. Providers may emit deltas for text, thinking, and tool calls in the same upstream chunk, and pi may surface corresponding events interleaved, for example `text_start`, `text_delta`, `toolcall_start`, `text_delta`, `toolcall_delta`. Consumers must use `contentIndex` to associate each delta/end event with its block and must not assume that a block's `*_start`/`*_delta`/`*_end` sequence is uninterrupted by events for other blocks.
683
688
 
689
+ ### Compact Assistant Message Frames
690
+
691
+ `AssistantMessageFrameEncoder` converts one stream into compact, persistable `AssistantMessageFrame` values. Create one encoder per stream and feed it every event in order. The encoder understands that `partial` is live: a block-start event consumed after the provider has already queued later deltas snapshots the current block once, and covered queued text/thinking deltas produce no duplicate frame. It retains only per-open-block counters plus, temporarily, the raw prefix needed to synchronize an already-advanced tool call. It never clones the growing full partial per token.
692
+
693
+ The start frame contains message metadata with empty content. Text and thinking frames store each generated character at most once before the authoritative end frame. Tool calls that were already advanced when their start event was consumed use one compact JSON checkpoint before ordinary deltas resume. Terminal `done` and `error` events produce no frame because final message settlement is separate. A pre-generation `error` therefore produces no frames.
694
+
695
+ `reduceAssistantMessageFrames()` is the canonical pure reducer. It reconstructs text, thinking, and tool-call arguments, including interleaved blocks identified by `contentIndex`, and rejects malformed sequences. It performs a single pass over the iterable and returns `undefined` when there is no start frame. End frames replace blocks with the provider's authoritative completed content and metadata. The reducer does not validate tool arguments against a TypeBox schema; call `validateToolCall` before execution.
696
+
697
+ ```typescript
698
+ import {
699
+ AssistantMessageFrameEncoder,
700
+ reduceAssistantMessageFrames,
701
+ type AssistantMessageFrame,
702
+ } from '@earendil-works/pi-ai';
703
+
704
+ const encoder = new AssistantMessageFrameEncoder();
705
+ const frames: AssistantMessageFrame[] = [];
706
+ for await (const event of s) {
707
+ const frame = encoder.encode(event);
708
+ if (frame) frames.push(frame);
709
+ }
710
+
711
+ const reconstructedPartial = reduceAssistantMessageFrames(frames);
712
+ const finalMessage = await s.result(); // Persist terminal settlement separately.
713
+ ```
714
+
715
+ An encoder rejects duplicate starts, updates before start, `done` before start, events after a terminal event, duplicate block starts, and block-kind mismatches. An `error` before start is valid and returns no frame.
716
+
684
717
  ## Image Input
685
718
 
686
719
  Models with vision capabilities can process images. You can check if a model supports images via the `input` property. If you pass images to a non-vision model, they are silently ignored.
@@ -905,7 +938,7 @@ Every `AssistantMessage` includes a `stopReason` field that indicates how the ge
905
938
 
906
939
  ## Error Handling
907
940
 
908
- Request failures never throw out of the stream functions: when a request ends with an error (including aborts and tool call validation errors), the streaming API emits an error event and the final message carries the details:
941
+ Request failures after a stream is returned never throw: when a request ends with an error (including aborts and tool call validation errors), the streaming API emits an error event and the final message carries the details. Setup failures may emit `error` without `start`; failures after generation begins emit `start`, any observed updates, then `error`. Direct API `streamSimple()` calls throw synchronously when request auth is missing:
909
942
 
910
943
  ```typescript
911
944
  // In streaming
@@ -927,7 +960,7 @@ if (message.stopReason === 'error' || message.stopReason === 'aborted') {
927
960
  }
928
961
  ```
929
962
 
930
- Auth failures (no key configured, OAuth refresh failed, unknown provider) surface the same way: as a stream error with `stopReason: "error"`.
963
+ When using a provider collection, auth failures (OAuth refresh failed, unknown provider) surface as a stream error with `stopReason: "error"`. Direct API `streamSimple()` calls instead throw synchronously when their required auth is absent.
931
964
 
932
965
  ### Aborting Requests
933
966
 
@@ -1187,7 +1220,7 @@ interface OpenAICompletionsCompat {
1187
1220
  supportsUsageInStreaming?: boolean; // Whether provider supports `stream_options: { include_usage: true }` (default: true)
1188
1221
  supportsStrictMode?: boolean; // Whether provider supports `strict` in tool definitions (default: true)
1189
1222
  supportsOpenAIGrammarTools?: boolean; // Whether to emit OpenAI custom Lark/regex grammar tools; false falls back to normal function tools (default: false; the generated catalog enables it for capable models)
1190
- sendSessionAffinityHeaders?: boolean; // Send session-affinity data from `sessionId` (default: false)
1223
+ sendSessionAffinityHeaders?: boolean; // Send session-affinity data from `sessionId` (default: true for OpenRouter, false otherwise)
1191
1224
  sessionAffinityFormat?: 'openai' | 'openai-nosession' | 'openrouter'; // Format for session affinity: 'openai' uses `prompt_cache_key`, `session_id`, `x-client-request-id`, and `x-session-affinity`; 'openai-nosession' uses `prompt_cache_key`, `x-client-request-id`, and `x-session-affinity`; 'openrouter' uses `x-session-id` (default: auto-detected)
1192
1225
  maxTokensField?: 'max_completion_tokens' | 'max_tokens'; // Which field name to use (default: max_completion_tokens)
1193
1226
  requiresToolResultName?: boolean; // Whether tool results require the `name` field (default: false)
@@ -1213,6 +1246,8 @@ interface OpenAIResponsesCompat {
1213
1246
  }
1214
1247
  ```
1215
1248
 
1249
+ OpenRouter requests send `x-session-id` from `sessionId` when prompt caching is enabled. Chat Completions and Anthropic Messages both auto-detect OpenRouter endpoints unless `sendSessionAffinityHeaders` is explicitly false. On Anthropic-compatible models, `sessionAffinityFormat: "openrouter"` selects `x-session-id`; when unset, the existing `x-session-affinity` format is used. Explicit request headers take precedence over generated headers.
1250
+
1216
1251
  If `compat` is not set, the library falls back to URL-based detection. If `compat` is partially set, unspecified fields use the detected defaults. This is useful for:
1217
1252
 
1218
1253
  - **LiteLLM proxies**: May not support `store` field
@@ -1 +1 @@
1
- {"version":3,"file":"anthropic-messages.d.ts","sourceRoot":"","sources":["../../src/api/anthropic-messages.ts"],"names":[],"mappings":"AAAA,OAAO,SAAS,MAAM,mBAAmB,CAAC;AAC1C,OAAO,KAAK,EAMX,+BAA+B,EAK/B,MAAM,uDAAuD,CAAC;AAG/D,OAAO,KAAK,EACX,wBAAwB,EAKxB,OAAO,EAGP,KAAK,EAGL,mBAAmB,EAEnB,cAAc,EACd,aAAa,EAGb,IAAI,EAIJ,MAAM,aAAa,CAAC;AAsCrB,OAAO,EAAE,kBAAkB,EAAE,MAAM,8BAA8B,CAAC;AA0ElE,eAAO,MAAM,gBAAgB,SAAU,MAAM,WAC2B,CAAC;AACzE,eAAO,MAAM,kBAAkB,SAAU,MAAM,UAAU,IAAI,EAAE,WAS9D,CAAC;AA4EF,MAAM,MAAM,eAAe,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,OAAO,GAAG,KAAK,CAAC;AAE1E,MAAM,MAAM,wBAAwB,GAAG,YAAY,GAAG,SAAS,CAAC;AA+FhE,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,wEAAwE;IACxE,gBAAgB,CAAC,EAAE,wBAAwB,CAAC;IAC5C;;;;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;AAmvBD,eAAO,MAAM,MAAM,EAAE,cAAc,CAAC,oBAAoB,EAAE,gBAAgB,CAudzE,CAAC;AAyFF,eAAO,MAAM,YAAY,EAAE,cAAc,CAAC,oBAAoB,EAAE,mBAAmB,CA+ClF,CAAC;AA6IF,wBAAgB,mCAAmC,CAClD,KAAK,EAAE,KAAK,CAAC,oBAAoB,CAAC,EAClC,OAAO,EAAE,OAAO,EAChB,OAAO,CAAC,EAAE,gBAAgB,GACxB,+BAA+B,CAkBjC"}
1
+ {"version":3,"file":"anthropic-messages.d.ts","sourceRoot":"","sources":["../../src/api/anthropic-messages.ts"],"names":[],"mappings":"AAAA,OAAO,SAAS,MAAM,mBAAmB,CAAC;AAC1C,OAAO,KAAK,EAMX,+BAA+B,EAK/B,MAAM,uDAAuD,CAAC;AAG/D,OAAO,KAAK,EACX,wBAAwB,EAKxB,OAAO,EAGP,KAAK,EAGL,mBAAmB,EAEnB,cAAc,EACd,aAAa,EAGb,IAAI,EAIJ,MAAM,aAAa,CAAC;AAsCrB,OAAO,EAAE,kBAAkB,EAAE,MAAM,8BAA8B,CAAC;AA0ElE,eAAO,MAAM,gBAAgB,SAAU,MAAM,WAC2B,CAAC;AACzE,eAAO,MAAM,kBAAkB,SAAU,MAAM,UAAU,IAAI,EAAE,WAS9D,CAAC;AA4EF,MAAM,MAAM,eAAe,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,OAAO,GAAG,KAAK,CAAC;AAE1E,MAAM,MAAM,wBAAwB,GAAG,YAAY,GAAG,SAAS,CAAC;AA+FhE,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,wEAAwE;IACxE,gBAAgB,CAAC,EAAE,wBAAwB,CAAC;IAC5C;;;;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;AAmvBD,eAAO,MAAM,MAAM,EAAE,cAAc,CAAC,oBAAoB,EAAE,gBAAgB,CAudzE,CAAC;AAyFF,eAAO,MAAM,YAAY,EAAE,cAAc,CAAC,oBAAoB,EAAE,mBAAmB,CA+ClF,CAAC;AAmJF,wBAAgB,mCAAmC,CAClD,KAAK,EAAE,KAAK,CAAC,oBAAoB,CAAC,EAClC,OAAO,EAAE,OAAO,EAChB,OAAO,CAAC,EAAE,gBAAgB,GACxB,+BAA+B,CAkBjC"}
@@ -1432,7 +1432,14 @@ function createClient(model, apiKey, interleavedThinking, useFineGrainedToolStre
1432
1432
  return { client, isOAuthToken: true };
1433
1433
  }
1434
1434
  // API key auth
1435
- const sessionAffinityHeaders = sessionId && getAnthropicCompat(model).sendSessionAffinityHeaders ? { "x-session-affinity": sessionId } : {};
1435
+ const affinityCompat = getAnthropicCompat(model);
1436
+ const sessionAffinityHeaders = {};
1437
+ if (sessionId && affinityCompat.sendSessionAffinityHeaders) {
1438
+ // OpenRouter routes prompt-cache affinity through its own header name and
1439
+ // rejects x-session-affinity (earendil-works/pi#9102).
1440
+ const header = affinityCompat.sessionAffinityFormat === "openrouter" ? "x-session-id" : "x-session-affinity";
1441
+ sessionAffinityHeaders[header] = sessionId;
1442
+ }
1436
1443
  const client = new Anthropic({
1437
1444
  apiKey: apiKey ?? null,
1438
1445
  authToken: null,