@centerforagenticai/pi-multi-account 0.1.1

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 (124) hide show
  1. package/LICENSE +21 -0
  2. package/NOTICE +29 -0
  3. package/README.md +999 -0
  4. package/config/models/pi-multi-account.v1.json +32 -0
  5. package/config/subscription-plans.v1.json +122 -0
  6. package/package.json +76 -0
  7. package/packages/pi-anthropic-oauth/LICENSE +21 -0
  8. package/packages/pi-anthropic-oauth/package.json +54 -0
  9. package/packages/pi-anthropic-oauth/src/auth.ts +396 -0
  10. package/packages/pi-anthropic-oauth/src/context.ts +116 -0
  11. package/packages/pi-anthropic-oauth/src/convert.ts +303 -0
  12. package/packages/pi-anthropic-oauth/src/index.ts +37 -0
  13. package/packages/pi-anthropic-oauth/src/prompt.ts +137 -0
  14. package/packages/pi-anthropic-oauth/src/stream.ts +476 -0
  15. package/packages/pi-antigravity/LICENSE +21 -0
  16. package/packages/pi-antigravity/package.json +77 -0
  17. package/packages/pi-antigravity/src/auth/index.ts +14 -0
  18. package/packages/pi-antigravity/src/auth/oauth.ts +442 -0
  19. package/packages/pi-antigravity/src/client/client.ts +561 -0
  20. package/packages/pi-antigravity/src/client/index.ts +1 -0
  21. package/packages/pi-antigravity/src/context.ts +110 -0
  22. package/packages/pi-antigravity/src/diagnostics/diagnostics.ts +96 -0
  23. package/packages/pi-antigravity/src/diagnostics/index.ts +1 -0
  24. package/packages/pi-antigravity/src/image/image.ts +336 -0
  25. package/packages/pi-antigravity/src/image/index.ts +1 -0
  26. package/packages/pi-antigravity/src/index.ts +280 -0
  27. package/packages/pi-antigravity/src/models/discovery.ts +154 -0
  28. package/packages/pi-antigravity/src/models/grouping.ts +424 -0
  29. package/packages/pi-antigravity/src/models/index.ts +3 -0
  30. package/packages/pi-antigravity/src/models/models.ts +500 -0
  31. package/packages/pi-antigravity/src/stream/index.ts +1 -0
  32. package/packages/pi-antigravity/src/stream/stream.ts +1478 -0
  33. package/packages/pi-antigravity/src/types/enums.ts +42 -0
  34. package/packages/pi-antigravity/src/types/index.ts +2 -0
  35. package/packages/pi-antigravity/src/types/types.ts +292 -0
  36. package/packages/pi-antigravity/src/usage/index.ts +1 -0
  37. package/packages/pi-antigravity/src/usage/usage.ts +416 -0
  38. package/packages/pi-antigravity/src/utils/http.ts +91 -0
  39. package/packages/pi-antigravity/src/utils/index.ts +3 -0
  40. package/packages/pi-antigravity/src/utils/security.ts +73 -0
  41. package/packages/pi-antigravity/src/utils/util.ts +132 -0
  42. package/scripts/multi-account.mjs +44 -0
  43. package/src/account-labels.ts +223 -0
  44. package/src/account-plan-assignment.ts +340 -0
  45. package/src/account-rate-history.ts +372 -0
  46. package/src/anthropic-adaptive-stream.ts +531 -0
  47. package/src/anthropic-alias-stream.ts +140 -0
  48. package/src/anthropic-context-compat.ts +80 -0
  49. package/src/api-pricing.ts +579 -0
  50. package/src/bounded-file-lines.ts +97 -0
  51. package/src/catalog-rebinding.ts +177 -0
  52. package/src/catalog-registration-probe.ts +111 -0
  53. package/src/codex-adapter.ts +345 -0
  54. package/src/codex-model-defaults.ts +785 -0
  55. package/src/command-completions.ts +404 -0
  56. package/src/commands.ts +2000 -0
  57. package/src/compaction.ts +14 -0
  58. package/src/config.ts +1317 -0
  59. package/src/continuation.ts +569 -0
  60. package/src/cooldowns.ts +110 -0
  61. package/src/cost-digest-store.ts +332 -0
  62. package/src/cost-digest.ts +1044 -0
  63. package/src/cost-history.ts +251 -0
  64. package/src/cost-period-closer.ts +160 -0
  65. package/src/cost-report-json.ts +318 -0
  66. package/src/cost-report-reader.ts +368 -0
  67. package/src/cost-report-render.ts +207 -0
  68. package/src/cost-report.ts +1104 -0
  69. package/src/coverage-attestation.ts +397 -0
  70. package/src/credential-lifecycle.ts +169 -0
  71. package/src/credential-refresh.ts +248 -0
  72. package/src/declaration-notice-marker.ts +238 -0
  73. package/src/diagnostic-store.ts +276 -0
  74. package/src/diagnostics.ts +309 -0
  75. package/src/discovery.ts +471 -0
  76. package/src/duration.ts +13 -0
  77. package/src/error-classification.ts +256 -0
  78. package/src/fuzzy.ts +15 -0
  79. package/src/group-policy.ts +81 -0
  80. package/src/history-store.ts +897 -0
  81. package/src/index.ts +5572 -0
  82. package/src/lifecycle.ts +378 -0
  83. package/src/logical-dispatch.ts +279 -0
  84. package/src/logical-model-selector.ts +254 -0
  85. package/src/logical-model-switcher.ts +430 -0
  86. package/src/logical-provider-attribution.ts +544 -0
  87. package/src/logical-provider.ts +1237 -0
  88. package/src/logical-route-indicator.ts +215 -0
  89. package/src/machine-lease.ts +445 -0
  90. package/src/model-support.ts +66 -0
  91. package/src/models-declaration.ts +1091 -0
  92. package/src/openai-adapter.ts +117 -0
  93. package/src/openrouter-budget.ts +304 -0
  94. package/src/openrouter-fallback.ts +146 -0
  95. package/src/period-boundaries.ts +376 -0
  96. package/src/pi-anthropic-oauth.d.ts +6 -0
  97. package/src/preflight.ts +253 -0
  98. package/src/pricing-cache.ts +235 -0
  99. package/src/project-identity.ts +100 -0
  100. package/src/provider-registration.ts +942 -0
  101. package/src/rate-formula.ts +163 -0
  102. package/src/recovery-engine.ts +853 -0
  103. package/src/recovery-output.ts +837 -0
  104. package/src/recovery-plan.ts +239 -0
  105. package/src/report-range.ts +203 -0
  106. package/src/route-resolver.ts +789 -0
  107. package/src/routing-config-transaction.ts +232 -0
  108. package/src/routing.ts +1163 -0
  109. package/src/runtime-state.ts +630 -0
  110. package/src/session-account-groups.ts +284 -0
  111. package/src/session-restore.ts +287 -0
  112. package/src/shared-usage.ts +1392 -0
  113. package/src/standalone-cli.ts +720 -0
  114. package/src/status-view.ts +578 -0
  115. package/src/subscription-plan-catalog.ts +346 -0
  116. package/src/tier-model-resolver.ts +46 -0
  117. package/src/upstream-anthropic.ts +315 -0
  118. package/src/upstream-antigravity.ts +327 -0
  119. package/src/usage-fetch.ts +1634 -0
  120. package/src/usage.ts +1026 -0
  121. package/src/vendor.ts +87 -0
  122. package/src/warmer.ts +231 -0
  123. package/src/watchdog.ts +219 -0
  124. package/src/window-history.ts +270 -0
@@ -0,0 +1,837 @@
1
+ import type {
2
+ AssistantMessage,
3
+ AssistantMessageEvent,
4
+ AssistantMessageEventStream,
5
+ ToolCall,
6
+ } from "@earendil-works/pi-ai";
7
+
8
+ export interface AcceptedOutputLimits {
9
+ readonly maxBufferedBytes: number;
10
+ readonly maxEvents: number;
11
+ }
12
+
13
+ /**
14
+ * Engineering caps for one private physical attempt.
15
+ *
16
+ * The pinned 0.84.4 catalog and repository fixtures advertise at most 128,000
17
+ * output tokens. The 24 MiB byte cap leaves 64 UTF-8 bytes per advertised token
18
+ * for each of the exact terminal, immutable accepted snapshot, and reconstructed
19
+ * deltas. The 262,144-event cap allows two wire events per advertised token.
20
+ * These are explicit safety margins, not measurements of every possible model
21
+ * response. The adapter counts bytes and events while consuming, but retains
22
+ * neither producer events nor their cumulative mutable `partial` objects. A
23
+ * response outside either engineering cap fails in full.
24
+ */
25
+ export const ACCEPTED_OUTPUT_DEFAULT_LIMITS: AcceptedOutputLimits = Object.freeze({
26
+ maxBufferedBytes: 24 * 1024 * 1024,
27
+ maxEvents: 262_144,
28
+ });
29
+
30
+ // Give async producer finally blocks one bounded second; timeout fails the
31
+ // private attempt without publishing content.
32
+ const TERMINAL_CLEANUP_TIMEOUT_MS = 1_000;
33
+
34
+ export type AcceptedOutputErrorCode =
35
+ | "aborted"
36
+ | "malformed"
37
+ | "overflow"
38
+ | "rejected";
39
+
40
+ const ERROR_MESSAGES: Readonly<Record<AcceptedOutputErrorCode, string>> = Object.freeze({
41
+ aborted: "The candidate output was aborted before acceptance.",
42
+ malformed: "The candidate output did not satisfy the accepted stream contract.",
43
+ overflow: "The candidate output exceeded the accepted output limit.",
44
+ rejected: "The candidate output was rejected before acceptance.",
45
+ });
46
+
47
+ /** A content-free failure safe to expose outside the private attempt. */
48
+ export class AcceptedOutputError extends Error {
49
+ readonly code: AcceptedOutputErrorCode;
50
+
51
+ constructor(code: AcceptedOutputErrorCode) {
52
+ super(ERROR_MESSAGES[code]);
53
+ this.name = "AcceptedOutputError";
54
+ this.code = code;
55
+ }
56
+ }
57
+
58
+ export interface AcceptedOutputOptions extends Partial<AcceptedOutputLimits> {
59
+ readonly signal?: AbortSignal;
60
+ }
61
+
62
+ type AttemptState =
63
+ | { readonly status: "pending" }
64
+ | {
65
+ readonly status: "accepted";
66
+ readonly terminal: AssistantMessage;
67
+ readonly partial: AssistantMessage | undefined;
68
+ }
69
+ | { readonly status: "failed"; readonly error: AcceptedOutputError };
70
+
71
+ function isRecord(value: unknown): value is Record<string, unknown> {
72
+ return typeof value === "object" && value !== null;
73
+ }
74
+
75
+ function isFiniteNumber(value: unknown): value is number {
76
+ return typeof value === "number" && Number.isFinite(value);
77
+ }
78
+
79
+ function isUsage(value: unknown): value is AssistantMessage["usage"] {
80
+ if (!isRecord(value) || !isRecord(value.cost)) return false;
81
+ return (
82
+ isFiniteNumber(value.input) &&
83
+ isFiniteNumber(value.output) &&
84
+ isFiniteNumber(value.cacheRead) &&
85
+ isFiniteNumber(value.cacheWrite) &&
86
+ isFiniteNumber(value.totalTokens) &&
87
+ isFiniteNumber(value.cost.input) &&
88
+ isFiniteNumber(value.cost.output) &&
89
+ isFiniteNumber(value.cost.cacheRead) &&
90
+ isFiniteNumber(value.cost.cacheWrite) &&
91
+ isFiniteNumber(value.cost.total)
92
+ );
93
+ }
94
+
95
+ function isJsonValue(value: unknown, ancestors = new Set<object>()): boolean {
96
+ if (value === null || typeof value === "string" || typeof value === "boolean") return true;
97
+ if (typeof value === "number") return Number.isFinite(value);
98
+ if (typeof value !== "object" || ancestors.has(value)) return false;
99
+ ancestors.add(value);
100
+ const valid = Array.isArray(value)
101
+ ? value.every((item, index) => index in value && isJsonValue(item, ancestors)) &&
102
+ Object.keys(value).length === value.length
103
+ : (Object.getPrototypeOf(value) === Object.prototype || Object.getPrototypeOf(value) === null) &&
104
+ Object.values(value).every((item) => isJsonValue(item, ancestors));
105
+ ancestors.delete(value);
106
+ return valid;
107
+ }
108
+
109
+ function isAssistantMessageEnvelope(value: unknown): value is AssistantMessage {
110
+ return (
111
+ isRecord(value) &&
112
+ value.role === "assistant" &&
113
+ Array.isArray(value.content) &&
114
+ typeof value.api === "string" &&
115
+ typeof value.provider === "string" &&
116
+ typeof value.model === "string" &&
117
+ typeof value.stopReason === "string" &&
118
+ isFiniteNumber(value.timestamp) &&
119
+ isUsage(value.usage)
120
+ );
121
+ }
122
+
123
+ function isAssistantMessage(value: unknown): value is AssistantMessage {
124
+ if (!isAssistantMessageEnvelope(value)) return false;
125
+ return value.content.every((block) => {
126
+ if (!isRecord(block) || typeof block.type !== "string") return false;
127
+ if (block.type === "text") {
128
+ return (
129
+ typeof block.text === "string" &&
130
+ (block.textSignature === undefined || typeof block.textSignature === "string")
131
+ );
132
+ }
133
+ if (block.type === "thinking") {
134
+ return (
135
+ typeof block.thinking === "string" &&
136
+ (block.thinkingSignature === undefined || typeof block.thinkingSignature === "string") &&
137
+ (block.redacted === undefined || typeof block.redacted === "boolean")
138
+ );
139
+ }
140
+ return (
141
+ block.type === "toolCall" &&
142
+ typeof block.id === "string" &&
143
+ typeof block.name === "string" &&
144
+ isRecord(block.arguments) &&
145
+ isJsonValue(block.arguments) &&
146
+ (block.thoughtSignature === undefined || typeof block.thoughtSignature === "string") &&
147
+ (block.namespace === undefined || typeof block.namespace === "string")
148
+ );
149
+ });
150
+ }
151
+
152
+ function isContentIndex(value: unknown): value is number {
153
+ return Number.isSafeInteger(value) && (value as number) >= 0;
154
+ }
155
+
156
+ function isPartialEventEnvelope(value: Record<string, unknown>): boolean {
157
+ return isContentIndex(value.contentIndex) && isAssistantMessageEnvelope(value.partial);
158
+ }
159
+
160
+ /**
161
+ * Checks only the event envelope while consuming. Producer partials are
162
+ * cumulative mutable snapshots, so recursively walking one for every delta
163
+ * would make a growing response quadratic. The authoritative terminal is
164
+ * deeply validated once after producer cleanup and before any clone.
165
+ */
166
+ function isAssistantMessageEventEnvelope(value: unknown): value is AssistantMessageEvent {
167
+ if (!isRecord(value) || typeof value.type !== "string") return false;
168
+ switch (value.type) {
169
+ case "start":
170
+ return isAssistantMessageEnvelope(value.partial);
171
+ case "text_start":
172
+ case "thinking_start":
173
+ case "toolcall_start":
174
+ return isPartialEventEnvelope(value);
175
+ case "text_delta":
176
+ case "thinking_delta":
177
+ case "toolcall_delta":
178
+ return isPartialEventEnvelope(value) && typeof value.delta === "string";
179
+ case "text_end":
180
+ case "thinking_end":
181
+ return isPartialEventEnvelope(value) && typeof value.content === "string";
182
+ case "toolcall_end":
183
+ return (
184
+ isPartialEventEnvelope(value) &&
185
+ isRecord(value.toolCall) &&
186
+ value.toolCall.type === "toolCall" &&
187
+ typeof value.toolCall.id === "string" &&
188
+ typeof value.toolCall.name === "string" &&
189
+ isRecord(value.toolCall.arguments)
190
+ );
191
+ case "done":
192
+ return (
193
+ (value.reason === "stop" ||
194
+ value.reason === "length" ||
195
+ value.reason === "toolUse" ||
196
+ value.reason === "deferred") &&
197
+ isAssistantMessageEnvelope(value.message) &&
198
+ value.message.stopReason === value.reason
199
+ );
200
+ case "error":
201
+ return (
202
+ (value.reason === "error" || value.reason === "aborted") &&
203
+ isAssistantMessageEnvelope(value.error)
204
+ );
205
+ default:
206
+ return false;
207
+ }
208
+ }
209
+
210
+ function assertLimits(options: AcceptedOutputOptions): AcceptedOutputLimits {
211
+ const maxBufferedBytes =
212
+ options.maxBufferedBytes ?? ACCEPTED_OUTPUT_DEFAULT_LIMITS.maxBufferedBytes;
213
+ const maxEvents = options.maxEvents ?? ACCEPTED_OUTPUT_DEFAULT_LIMITS.maxEvents;
214
+ if (
215
+ !Number.isSafeInteger(maxBufferedBytes) ||
216
+ maxBufferedBytes <= 0 ||
217
+ !Number.isSafeInteger(maxEvents) ||
218
+ maxEvents <= 0
219
+ ) {
220
+ throw new TypeError("Accepted output limits must be positive safe integers.");
221
+ }
222
+ return { maxBufferedBytes, maxEvents };
223
+ }
224
+
225
+ function measuredBytes(value: unknown, limit: number): number {
226
+ const ancestors = new Set<object>();
227
+ const visit = (candidate: unknown, total: number): number => {
228
+ if (total > limit) return total;
229
+ if (candidate === null) return total + 4;
230
+ switch (typeof candidate) {
231
+ case "string":
232
+ return total + Buffer.byteLength(candidate, "utf8");
233
+ case "number":
234
+ return total + 16;
235
+ case "boolean":
236
+ return total + 5;
237
+ case "undefined":
238
+ return total;
239
+ case "object": {
240
+ if (ancestors.has(candidate)) throw new AcceptedOutputError("malformed");
241
+ ancestors.add(candidate);
242
+ let measured = total;
243
+ if (Array.isArray(candidate)) {
244
+ for (const item of candidate) {
245
+ measured = visit(item, measured + 1);
246
+ if (measured > limit) break;
247
+ }
248
+ } else {
249
+ for (const [key, item] of Object.entries(candidate)) {
250
+ measured += Buffer.byteLength(key, "utf8") + 1;
251
+ measured = visit(item, measured);
252
+ if (measured > limit) break;
253
+ }
254
+ }
255
+ ancestors.delete(candidate);
256
+ return measured;
257
+ }
258
+ default:
259
+ throw new AcceptedOutputError("malformed");
260
+ }
261
+ };
262
+ return visit(value, 0);
263
+ }
264
+
265
+ function jsonText(value: unknown): string {
266
+ if (value === null) return "null";
267
+ if (typeof value === "string" || typeof value === "boolean") return JSON.stringify(value);
268
+ if (typeof value === "number") return Object.is(value, -0) ? "-0" : String(value);
269
+ if (Array.isArray(value)) {
270
+ const items: string[] = [];
271
+ for (let index = 0; index < value.length; index += 1) {
272
+ if (!(index in value)) throw new AcceptedOutputError("malformed");
273
+ items.push(jsonText(value[index]));
274
+ }
275
+ return `[${items.join(",")}]`;
276
+ }
277
+ if (isRecord(value)) {
278
+ return `{${Object.entries(value)
279
+ .map(([key, item]) => `${JSON.stringify(key)}:${jsonText(item)}`)
280
+ .join(",")}}`;
281
+ }
282
+ throw new AcceptedOutputError("malformed");
283
+ }
284
+
285
+ function jsonStringBytes(value: string): number {
286
+ let bytes = 2;
287
+ for (let index = 0; index < value.length; index += 1) {
288
+ const unit = value.charCodeAt(index);
289
+ if (unit === 0x22 || unit === 0x5c) {
290
+ bytes += 2;
291
+ } else if (unit <= 0x1f) {
292
+ bytes += unit === 0x08 || unit === 0x09 || unit === 0x0a || unit === 0x0c || unit === 0x0d
293
+ ? 2
294
+ : 6;
295
+ } else if (unit >= 0xd800 && unit <= 0xdbff) {
296
+ const following = value.charCodeAt(index + 1);
297
+ if (following >= 0xdc00 && following <= 0xdfff) {
298
+ bytes += 4;
299
+ index += 1;
300
+ } else {
301
+ bytes += 6;
302
+ }
303
+ } else if (unit >= 0xdc00 && unit <= 0xdfff) {
304
+ bytes += 6;
305
+ } else if (unit <= 0x7f) {
306
+ bytes += 1;
307
+ } else if (unit <= 0x7ff) {
308
+ bytes += 2;
309
+ } else {
310
+ bytes += 3;
311
+ }
312
+ }
313
+ return bytes;
314
+ }
315
+
316
+ function jsonTextBytes(value: unknown, limit: number): number {
317
+ if (value === null) return 4;
318
+ if (typeof value === "string") return jsonStringBytes(value);
319
+ if (typeof value === "boolean") return value ? 4 : 5;
320
+ if (typeof value === "number") return Object.is(value, -0) ? 2 : String(value).length;
321
+ let bytes = 2;
322
+ if (Array.isArray(value)) {
323
+ for (let index = 0; index < value.length; index += 1) {
324
+ if (!(index in value)) throw new AcceptedOutputError("malformed");
325
+ if (index > 0) bytes += 1;
326
+ bytes += jsonTextBytes(value[index], limit - bytes);
327
+ if (bytes > limit) break;
328
+ }
329
+ return bytes;
330
+ }
331
+ if (isRecord(value)) {
332
+ let index = 0;
333
+ for (const [key, item] of Object.entries(value)) {
334
+ if (index > 0) bytes += 1;
335
+ bytes += jsonStringBytes(key) + 1;
336
+ bytes += jsonTextBytes(item, limit - bytes);
337
+ if (bytes > limit) break;
338
+ index += 1;
339
+ }
340
+ return bytes;
341
+ }
342
+ throw new AcceptedOutputError("malformed");
343
+ }
344
+
345
+ function deltaBytes(event: AssistantMessageEvent): number {
346
+ switch (event.type) {
347
+ case "text_delta":
348
+ case "thinking_delta":
349
+ case "toolcall_delta":
350
+ return Buffer.byteLength(event.delta, "utf8");
351
+ default:
352
+ return 0;
353
+ }
354
+ }
355
+
356
+ function copyUsage(usage: AssistantMessage["usage"]): AssistantMessage["usage"] {
357
+ return {
358
+ input: usage.input,
359
+ output: usage.output,
360
+ cacheRead: usage.cacheRead,
361
+ cacheWrite: usage.cacheWrite,
362
+ ...(usage.cacheWrite1h === undefined ? {} : { cacheWrite1h: usage.cacheWrite1h }),
363
+ ...(usage.reasoning === undefined ? {} : { reasoning: usage.reasoning }),
364
+ totalTokens: usage.totalTokens,
365
+ cost: {
366
+ input: usage.cost.input,
367
+ output: usage.cost.output,
368
+ cacheRead: usage.cost.cacheRead,
369
+ cacheWrite: usage.cost.cacheWrite,
370
+ total: usage.cost.total,
371
+ },
372
+ };
373
+ }
374
+
375
+ function copyPartialMessage(
376
+ terminal: AssistantMessage,
377
+ content: AssistantMessage["content"],
378
+ ): AssistantMessage {
379
+ return {
380
+ role: "assistant",
381
+ content,
382
+ api: terminal.api,
383
+ provider: terminal.provider,
384
+ model: terminal.model,
385
+ ...(terminal.responseModel === undefined ? {} : { responseModel: terminal.responseModel }),
386
+ ...(terminal.responseId === undefined ? {} : { responseId: terminal.responseId }),
387
+ usage: copyUsage(terminal.usage),
388
+ stopReason: "pending",
389
+ timestamp: terminal.timestamp,
390
+ };
391
+ }
392
+
393
+ function deepFreeze(value: unknown, seen = new Set<object>()): void {
394
+ if (typeof value !== "object" || value === null || seen.has(value)) return;
395
+ seen.add(value);
396
+ for (const child of Object.values(value)) deepFreeze(child, seen);
397
+ Object.freeze(value);
398
+ }
399
+
400
+ function acceptedPartial(terminal: AssistantMessage): AssistantMessage {
401
+ const content = structuredClone(terminal.content);
402
+ const partial = copyPartialMessage(terminal, content);
403
+ deepFreeze(partial);
404
+ return partial;
405
+ }
406
+
407
+ function projectedAcceptedBytes(
408
+ terminal: AssistantMessage,
409
+ limit: number,
410
+ ): number {
411
+ let total = measuredBytes(terminal, limit);
412
+ total += measuredBytes(copyPartialMessage(terminal, terminal.content), limit - total);
413
+ total += measuredBytes(copyPartialMessage(terminal, []), limit - total);
414
+ for (const block of terminal.content) {
415
+ total += block.type === "text"
416
+ ? Buffer.byteLength(block.text, "utf8")
417
+ : block.type === "thinking"
418
+ ? Buffer.byteLength(block.thinking, "utf8")
419
+ : jsonTextBytes(block.arguments, limit - total);
420
+ if (total > limit) break;
421
+ }
422
+ return total;
423
+ }
424
+
425
+ /**
426
+ * Replays a newly constructed accepted sequence. Delta consumers can rebuild
427
+ * every block from the emitted deltas, while snapshot consumers see one stable,
428
+ * immutable accepted snapshot rather than retained mutable producer partials.
429
+ * The terminal event and `result()` retain the exact accepted object.
430
+ */
431
+ async function* acceptedEvents(
432
+ terminal: AssistantMessage,
433
+ partial: AssistantMessage,
434
+ ): AsyncGenerator<AssistantMessageEvent> {
435
+ const start = copyPartialMessage(terminal, []);
436
+ deepFreeze(start);
437
+ yield { type: "start", partial: start };
438
+
439
+ for (let contentIndex = 0; contentIndex < partial.content.length; contentIndex += 1) {
440
+ const accepted = partial.content[contentIndex]!;
441
+ if (accepted.type === "text") {
442
+ yield { type: "text_start", contentIndex, partial };
443
+ yield { type: "text_delta", contentIndex, delta: accepted.text, partial };
444
+ yield {
445
+ type: "text_end",
446
+ contentIndex,
447
+ content: accepted.text,
448
+ partial,
449
+ ...(accepted.textSignature === undefined
450
+ ? {}
451
+ : { contentSignature: accepted.textSignature }),
452
+ } as AssistantMessageEvent;
453
+ continue;
454
+ }
455
+ if (accepted.type === "thinking") {
456
+ yield { type: "thinking_start", contentIndex, partial };
457
+ yield { type: "thinking_delta", contentIndex, delta: accepted.thinking, partial };
458
+ yield {
459
+ type: "thinking_end",
460
+ contentIndex,
461
+ content: accepted.thinking,
462
+ partial,
463
+ ...(accepted.thinkingSignature === undefined
464
+ ? {}
465
+ : { contentSignature: accepted.thinkingSignature }),
466
+ ...(accepted.redacted === undefined ? {} : { redacted: accepted.redacted }),
467
+ } as AssistantMessageEvent;
468
+ continue;
469
+ }
470
+ yield { type: "toolcall_start", contentIndex, partial };
471
+ yield {
472
+ type: "toolcall_delta",
473
+ contentIndex,
474
+ delta: jsonText(accepted.arguments),
475
+ partial,
476
+ };
477
+ yield { type: "toolcall_end", contentIndex, toolCall: accepted, partial };
478
+ }
479
+
480
+ yield {
481
+ type: "done",
482
+ reason: terminal.stopReason as Extract<AssistantMessage["stopReason"], "stop" | "length" | "toolUse" | "deferred">,
483
+ message: terminal,
484
+ };
485
+ }
486
+
487
+ /**
488
+ * Consume one candidate physical attempt privately and publish only an accepted
489
+ * successful terminal. This function never selects or retries an account.
490
+ */
491
+ export function createAcceptedOutputStream(
492
+ upstream: AsyncIterable<unknown> | Promise<AsyncIterable<unknown>>,
493
+ options: AcceptedOutputOptions = {},
494
+ ): AssistantMessageEventStream {
495
+ const limits = assertLimits(options);
496
+ let state: AttemptState = { status: "pending" };
497
+ let claimed = false;
498
+ let iterator: AsyncIterator<unknown> | undefined;
499
+ let sourceReturn: Promise<void> | undefined;
500
+ let resolveResult!: (message: AssistantMessage) => void;
501
+ let rejectResult!: (error: AcceptedOutputError) => void;
502
+ const resultPromise = new Promise<AssistantMessage>((resolve, reject) => {
503
+ resolveResult = resolve;
504
+ rejectResult = reject;
505
+ });
506
+ // A caller may consume iteration only. Mark the independent result branch as
507
+ // observed so a rejected candidate cannot become an unhandled rejection.
508
+ void resultPromise.catch(() => {});
509
+ // Observe the caller-supplied promise synchronously. A pre-aborted signal must
510
+ // not leave an already-rejecting upstream without a rejection handler.
511
+ const upstreamPromise: Promise<AsyncIterable<unknown>> = Promise.resolve(upstream);
512
+ void upstreamPromise.catch(() => {});
513
+
514
+ const fail = (error: AcceptedOutputError): void => {
515
+ if (state.status !== "pending") return;
516
+ state = { status: "failed", error };
517
+ rejectResult(error);
518
+ };
519
+ const accept = (terminal: AssistantMessage, partial: AssistantMessage): void => {
520
+ if (state.status !== "pending") return;
521
+ state = { status: "accepted", terminal, partial };
522
+ resolveResult(terminal);
523
+ };
524
+
525
+ type AbortRace<T> =
526
+ | { readonly kind: "value"; readonly value: T }
527
+ | { readonly kind: "rejected"; readonly error: unknown }
528
+ | { readonly kind: "aborted" };
529
+ type TerminalCleanupRace = AbortRace<void> | { readonly kind: "timed-out" };
530
+
531
+ let abortRequested = false;
532
+ let abortWaiter: (() => void) | undefined;
533
+ const onAbort = (): void => {
534
+ if (abortRequested) return;
535
+ abortRequested = true;
536
+ const wake = abortWaiter;
537
+ abortWaiter = undefined;
538
+ wake?.();
539
+ };
540
+ if (options.signal?.aborted) onAbort();
541
+ else options.signal?.addEventListener("abort", onAbort, { once: true });
542
+
543
+ // A fresh, short-lived abort promise keeps at most one waiter reachable. In
544
+ // contrast, repeatedly chaining from one never-settled promise retains one
545
+ // reaction closure for every producer event until cancellation.
546
+ const raceWithAbort = async <T>(operation: Promise<T>): Promise<AbortRace<T>> => {
547
+ if (abortRequested) return { kind: "aborted" };
548
+ let wake!: () => void;
549
+ const abortForOperation = new Promise<AbortRace<T>>((resolve) => {
550
+ wake = () => resolve({ kind: "aborted" });
551
+ abortWaiter = wake;
552
+ });
553
+ if (abortRequested) wake();
554
+ try {
555
+ return await Promise.race([
556
+ operation.then(
557
+ (value): AbortRace<T> => ({ kind: "value", value }),
558
+ (error: unknown): AbortRace<T> => ({ kind: "rejected", error }),
559
+ ),
560
+ abortForOperation,
561
+ ]);
562
+ } finally {
563
+ if (abortWaiter === wake) abortWaiter = undefined;
564
+ }
565
+ };
566
+
567
+ const raceTerminalCleanup = async (
568
+ operation: Promise<void>,
569
+ ): Promise<TerminalCleanupRace> => {
570
+ if (abortRequested) return { kind: "aborted" };
571
+ let wake!: () => void;
572
+ let cleanupTimer: ReturnType<typeof setTimeout> | undefined;
573
+ const abortForOperation = new Promise<TerminalCleanupRace>((resolve) => {
574
+ wake = () => resolve({ kind: "aborted" });
575
+ abortWaiter = wake;
576
+ });
577
+ const cleanupDeadline = new Promise<TerminalCleanupRace>((resolve) => {
578
+ cleanupTimer = setTimeout(
579
+ () => resolve({ kind: "timed-out" }),
580
+ TERMINAL_CLEANUP_TIMEOUT_MS,
581
+ );
582
+ });
583
+ if (abortRequested) wake();
584
+ try {
585
+ return await Promise.race([
586
+ operation.then(
587
+ (): TerminalCleanupRace => ({ kind: "value", value: undefined }),
588
+ (error: unknown): TerminalCleanupRace => ({ kind: "rejected", error }),
589
+ ),
590
+ abortForOperation,
591
+ cleanupDeadline,
592
+ ]);
593
+ } finally {
594
+ if (cleanupTimer !== undefined) clearTimeout(cleanupTimer);
595
+ if (abortWaiter === wake) abortWaiter = undefined;
596
+ }
597
+ };
598
+
599
+ const releaseSource = (): Promise<void> => {
600
+ if (sourceReturn !== undefined) return sourceReturn;
601
+ const active = iterator;
602
+ if (active === undefined) return Promise.resolve();
603
+ iterator = undefined;
604
+ try {
605
+ const close = active.return;
606
+ sourceReturn = close === undefined
607
+ ? Promise.resolve()
608
+ : Promise.resolve(close.call(active)).then(() => undefined);
609
+ } catch (error) {
610
+ sourceReturn = Promise.reject(error);
611
+ }
612
+ void sourceReturn.catch(() => {});
613
+ return sourceReturn;
614
+ };
615
+ const releaseSourceQuietly = (): void => {
616
+ void releaseSource().catch(() => {});
617
+ };
618
+
619
+ // Opening is started even for a pre-aborted call. If a promised source arrives
620
+ // after cancellation, acquiring and immediately returning its iterator gives
621
+ // the producer one deterministic cleanup path instead of abandoning it.
622
+ const openedPromise = upstreamPromise.then((value) => {
623
+ if (!isRecord(value) || !(Symbol.asyncIterator in value)) {
624
+ throw new AcceptedOutputError("malformed");
625
+ }
626
+ let opened: AsyncIterator<unknown>;
627
+ try {
628
+ opened = value[Symbol.asyncIterator]();
629
+ } catch {
630
+ throw new AcceptedOutputError("malformed");
631
+ }
632
+ iterator = opened;
633
+ if (abortRequested) releaseSourceQuietly();
634
+ return opened;
635
+ });
636
+ void openedPromise.catch(() => {});
637
+
638
+ void (async () => {
639
+ let eventCount = 0;
640
+ let byteCount = 0;
641
+ try {
642
+ if (abortRequested) {
643
+ fail(new AcceptedOutputError("aborted"));
644
+ return;
645
+ }
646
+ const opened = await raceWithAbort(openedPromise);
647
+ if (opened.kind === "aborted" || abortRequested) {
648
+ fail(new AcceptedOutputError("aborted"));
649
+ return;
650
+ }
651
+ if (opened.kind === "rejected") {
652
+ fail(
653
+ opened.error instanceof AcceptedOutputError
654
+ ? opened.error
655
+ : new AcceptedOutputError("rejected"),
656
+ );
657
+ return;
658
+ }
659
+
660
+ while (state.status === "pending") {
661
+ if (abortRequested) {
662
+ releaseSourceQuietly();
663
+ fail(new AcceptedOutputError("aborted"));
664
+ return;
665
+ }
666
+ let nextPromise: Promise<IteratorResult<unknown>>;
667
+ try {
668
+ nextPromise = Promise.resolve(opened.value.next());
669
+ } catch {
670
+ releaseSourceQuietly();
671
+ fail(new AcceptedOutputError("rejected"));
672
+ return;
673
+ }
674
+ const next = await raceWithAbort(nextPromise);
675
+ if (next.kind === "aborted" || abortRequested) {
676
+ releaseSourceQuietly();
677
+ fail(new AcceptedOutputError("aborted"));
678
+ return;
679
+ }
680
+ if (next.kind === "rejected") {
681
+ releaseSourceQuietly();
682
+ fail(new AcceptedOutputError("rejected"));
683
+ return;
684
+ }
685
+ if (next.value.done) {
686
+ fail(new AcceptedOutputError("malformed"));
687
+ return;
688
+ }
689
+ if (!isAssistantMessageEventEnvelope(next.value.value)) {
690
+ releaseSourceQuietly();
691
+ fail(new AcceptedOutputError("malformed"));
692
+ return;
693
+ }
694
+
695
+ const event = next.value.value;
696
+ eventCount += 1;
697
+ if (eventCount > limits.maxEvents) {
698
+ releaseSourceQuietly();
699
+ fail(new AcceptedOutputError("overflow"));
700
+ return;
701
+ }
702
+ byteCount += deltaBytes(event);
703
+ if (byteCount > limits.maxBufferedBytes) {
704
+ releaseSourceQuietly();
705
+ fail(new AcceptedOutputError("overflow"));
706
+ return;
707
+ }
708
+
709
+ if (event.type === "error") {
710
+ releaseSourceQuietly();
711
+ if (!isAssistantMessage(event.error)) {
712
+ fail(new AcceptedOutputError("malformed"));
713
+ return;
714
+ }
715
+ fail(new AcceptedOutputError(event.reason === "aborted" ? "aborted" : "rejected"));
716
+ return;
717
+ }
718
+ if (event.type === "done") {
719
+ const released = await raceTerminalCleanup(releaseSource());
720
+ if (released.kind === "aborted" || abortRequested) {
721
+ fail(new AcceptedOutputError("aborted"));
722
+ return;
723
+ }
724
+ if (released.kind === "rejected" || released.kind === "timed-out") {
725
+ fail(new AcceptedOutputError("rejected"));
726
+ return;
727
+ }
728
+ // Cleanup is allowed to run producer finally blocks. Revalidate and
729
+ // deeply validate the authoritative terminal only now, before sizing
730
+ // or cloning, so replay and exact result cannot diverge.
731
+ if (
732
+ !isAssistantMessageEventEnvelope(event) ||
733
+ event.type !== "done" ||
734
+ !isAssistantMessage(event.message)
735
+ ) {
736
+ fail(new AcceptedOutputError("malformed"));
737
+ return;
738
+ }
739
+ const replayEvents = 2 + event.message.content.length * 3;
740
+ if (replayEvents > limits.maxEvents) {
741
+ fail(new AcceptedOutputError("overflow"));
742
+ return;
743
+ }
744
+ let projectedBytes: number;
745
+ try {
746
+ projectedBytes = projectedAcceptedBytes(
747
+ event.message,
748
+ limits.maxBufferedBytes,
749
+ );
750
+ } catch {
751
+ fail(new AcceptedOutputError("malformed"));
752
+ return;
753
+ }
754
+ if (projectedBytes > limits.maxBufferedBytes) {
755
+ fail(new AcceptedOutputError("overflow"));
756
+ return;
757
+ }
758
+ let partial: AssistantMessage;
759
+ try {
760
+ partial = acceptedPartial(event.message);
761
+ } catch {
762
+ fail(new AcceptedOutputError("malformed"));
763
+ return;
764
+ }
765
+ accept(event.message, partial);
766
+ return;
767
+ }
768
+ }
769
+ } catch (error) {
770
+ releaseSourceQuietly();
771
+ fail(error instanceof AcceptedOutputError ? error : new AcceptedOutputError("malformed"));
772
+ } finally {
773
+ options.signal?.removeEventListener("abort", onAbort);
774
+ iterator = undefined;
775
+ }
776
+ })();
777
+
778
+ const stream = {
779
+ [Symbol.asyncIterator](): AsyncIterator<AssistantMessageEvent> {
780
+ if (claimed) {
781
+ return {
782
+ next: () => Promise.reject(new AcceptedOutputError("malformed")),
783
+ };
784
+ }
785
+ claimed = true;
786
+ let closed = false;
787
+ let replay: AsyncIterator<AssistantMessageEvent> | undefined;
788
+ return {
789
+ async next(): Promise<IteratorResult<AssistantMessageEvent>> {
790
+ if (closed) return { value: undefined, done: true };
791
+ if (replay === undefined) {
792
+ await resultPromise;
793
+ if (closed) return { value: undefined, done: true };
794
+ if (state.status !== "accepted" || state.partial === undefined) {
795
+ throw new AcceptedOutputError("malformed");
796
+ }
797
+ const { terminal, partial } = state;
798
+ replay = acceptedEvents(terminal, partial)[Symbol.asyncIterator]();
799
+ // The replay iterator now owns the sole accepted snapshot. The stream
800
+ // retains only the exact terminal object required by result().
801
+ state = { status: "accepted", terminal, partial: undefined };
802
+ }
803
+ const next = await replay.next();
804
+ if (closed) return { value: undefined, done: true };
805
+ if (next.done) {
806
+ replay = undefined;
807
+ closed = true;
808
+ }
809
+ return next;
810
+ },
811
+ async return(): Promise<IteratorResult<AssistantMessageEvent>> {
812
+ if (closed) return { value: undefined, done: true };
813
+ closed = true;
814
+ onAbort();
815
+ releaseSourceQuietly();
816
+ if (state.status === "accepted" && state.partial !== undefined) {
817
+ state = { status: "accepted", terminal: state.terminal, partial: undefined };
818
+ }
819
+ if (replay?.return !== undefined) {
820
+ try {
821
+ await replay.return();
822
+ } catch {
823
+ // The replay is local and content-free on cleanup failure.
824
+ }
825
+ }
826
+ replay = undefined;
827
+ return { value: undefined, done: true };
828
+ },
829
+ };
830
+ },
831
+ result(): Promise<AssistantMessage> {
832
+ return resultPromise;
833
+ },
834
+ };
835
+
836
+ return stream as AssistantMessageEventStream;
837
+ }