@reinsjs/lowering-fetch 0.2.0

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.
package/dist/index.js ADDED
@@ -0,0 +1,2678 @@
1
+ // src/usage.ts
2
+ function costOf(usage, cost) {
3
+ if (!cost) return void 0;
4
+ const perToken = (n, price) => (n ?? 0) * price / 1e6;
5
+ return perToken(usage.input, cost.input) + perToken(usage.output, cost.output) + perToken(usage.cacheRead, cost.cacheRead) + perToken(usage.cacheWrite, cost.cacheWrite);
6
+ }
7
+
8
+ // src/anthropic/from-stream.ts
9
+ var REDACTED_THINKING_TEXT = "[Reasoning redacted]";
10
+ function str(v) {
11
+ return typeof v === "string" ? v : void 0;
12
+ }
13
+ function num(v) {
14
+ return typeof v === "number" && Number.isFinite(v) ? v : void 0;
15
+ }
16
+ function parseInput(json, fallback) {
17
+ if (json.trim() === "") return typeof fallback === "object" && fallback !== null ? fallback : {};
18
+ try {
19
+ return JSON.parse(json);
20
+ } catch {
21
+ return json;
22
+ }
23
+ }
24
+ var AnthropicAssembly = class {
25
+ blocks = /* @__PURE__ */ new Map();
26
+ start;
27
+ delta;
28
+ responseModel;
29
+ stopReason;
30
+ stopDetails;
31
+ error;
32
+ sawStop = false;
33
+ drafts(origin) {
34
+ const out = [];
35
+ for (const [, b] of [...this.blocks.entries()].sort((x, y) => x[0] - y[0])) {
36
+ const replay = { ...origin };
37
+ switch (b.kind) {
38
+ case "thinking":
39
+ if (b.thinking.length === 0 && b.signature.length === 0) break;
40
+ if (b.signature.length > 0) replay.thinkingSignature = b.signature;
41
+ out.push({ type: "core.model_thinking", actor: "model", payload: { text: b.thinking }, replay });
42
+ break;
43
+ case "redacted_thinking":
44
+ replay.thinkingSignature = b.data;
45
+ replay.redacted = true;
46
+ out.push({
47
+ type: "core.model_thinking",
48
+ actor: "model",
49
+ payload: { text: REDACTED_THINKING_TEXT },
50
+ replay
51
+ });
52
+ break;
53
+ case "text":
54
+ if (b.text.length > 0)
55
+ out.push({ type: "core.model_text", actor: "model", payload: { text: b.text }, replay });
56
+ break;
57
+ case "tool_use":
58
+ out.push({
59
+ type: "core.tool_call",
60
+ actor: "model",
61
+ payload: { toolCallId: b.id, name: b.name, args: parseInput(b.json, b.input) },
62
+ replay
63
+ });
64
+ break;
65
+ case "other":
66
+ break;
67
+ }
68
+ }
69
+ return out;
70
+ }
71
+ hasToolUse() {
72
+ for (const b of this.blocks.values()) if (b.kind === "tool_use") return true;
73
+ return false;
74
+ }
75
+ };
76
+ function usageOf(start, delta) {
77
+ const pick = (k) => num(delta?.[k]) ?? num(start?.[k]);
78
+ const usage = { input: pick("input_tokens") ?? 0, output: pick("output_tokens") ?? 0 };
79
+ const read = pick("cache_read_input_tokens");
80
+ const write = pick("cache_creation_input_tokens");
81
+ if (read) usage.cacheRead = read;
82
+ if (write) usage.cacheWrite = write;
83
+ return usage;
84
+ }
85
+ function apply(ev, a, ctx) {
86
+ switch (ev.type) {
87
+ case "message_start": {
88
+ const model = str(ev.message?.model);
89
+ if (model) a.responseModel = model;
90
+ if (ev.message?.usage) a.start = ev.message.usage;
91
+ break;
92
+ }
93
+ case "content_block_start": {
94
+ const index = num(ev.index);
95
+ const cb = ev.content_block;
96
+ if (index === void 0 || !cb) break;
97
+ const type = str(cb.type) ?? "unknown";
98
+ switch (type) {
99
+ case "text":
100
+ a.blocks.set(index, { kind: "text", text: str(cb.text) ?? "" });
101
+ break;
102
+ case "thinking":
103
+ a.blocks.set(index, {
104
+ kind: "thinking",
105
+ thinking: str(cb.thinking) ?? "",
106
+ signature: str(cb.signature) ?? ""
107
+ });
108
+ break;
109
+ case "redacted_thinking":
110
+ a.blocks.set(index, { kind: "redacted_thinking", data: str(cb.data) ?? "" });
111
+ break;
112
+ case "tool_use":
113
+ a.blocks.set(index, {
114
+ kind: "tool_use",
115
+ id: str(cb.id) ?? "",
116
+ name: str(cb.name) ?? "",
117
+ input: cb.input,
118
+ json: ""
119
+ });
120
+ break;
121
+ default:
122
+ a.blocks.set(index, { kind: "other", type });
123
+ }
124
+ break;
125
+ }
126
+ case "content_block_delta": {
127
+ const index = num(ev.index);
128
+ const d = ev.delta;
129
+ if (index === void 0 || !d) break;
130
+ const b = a.blocks.get(index);
131
+ if (!b) break;
132
+ switch (d.type) {
133
+ case "text_delta": {
134
+ const t = str(d.text);
135
+ if (b.kind === "text" && t) {
136
+ b.text += t;
137
+ ctx.onDelta?.({ kind: "text", index, delta: t });
138
+ }
139
+ break;
140
+ }
141
+ case "thinking_delta": {
142
+ const t = str(d.thinking);
143
+ if (b.kind === "thinking" && t) {
144
+ b.thinking += t;
145
+ ctx.onDelta?.({ kind: "thinking", index, delta: t });
146
+ }
147
+ break;
148
+ }
149
+ case "signature_delta": {
150
+ const s = str(d.signature);
151
+ if (b.kind === "thinking" && s) b.signature += s;
152
+ break;
153
+ }
154
+ case "input_json_delta": {
155
+ const j = str(d.partial_json);
156
+ if (b.kind === "tool_use" && j) {
157
+ b.json += j;
158
+ ctx.onDelta?.({ kind: "tool_args", index, delta: j });
159
+ }
160
+ break;
161
+ }
162
+ default:
163
+ break;
164
+ }
165
+ break;
166
+ }
167
+ case "message_delta": {
168
+ const reason = str(ev.delta?.stop_reason);
169
+ if (reason) a.stopReason = reason;
170
+ if (ev.delta?.stop_details && typeof ev.delta.stop_details === "object")
171
+ a.stopDetails = ev.delta.stop_details;
172
+ if (ev.usage) a.delta = { ...a.delta, ...ev.usage };
173
+ break;
174
+ }
175
+ case "message_stop":
176
+ a.sawStop = true;
177
+ break;
178
+ case "error":
179
+ a.error = { type: str(ev.error?.type) ?? "error", message: str(ev.error?.message) ?? "" };
180
+ break;
181
+ default:
182
+ break;
183
+ }
184
+ }
185
+ function outcomeOf(a, cost) {
186
+ const usage = usageOf(a.start, a.delta);
187
+ const out = { stopReason: "stop", usage };
188
+ const c = costOf(usage, cost);
189
+ if (c !== void 0) out.costUsd = c;
190
+ if (a.responseModel) out.responseModel = a.responseModel;
191
+ if (a.error) {
192
+ out.stopReason = "error";
193
+ out.errorMessage = `${a.error.type}: ${a.error.message}`;
194
+ return out;
195
+ }
196
+ if (a.hasToolUse()) {
197
+ out.stopReason = "toolUse";
198
+ return out;
199
+ }
200
+ switch (a.stopReason) {
201
+ case "end_turn":
202
+ case "stop_sequence":
203
+ case "pause_turn":
204
+ case "tool_use":
205
+ break;
206
+ case "max_tokens":
207
+ case "model_context_window_exceeded":
208
+ out.stopReason = "length";
209
+ break;
210
+ case "refusal": {
211
+ out.stopReason = "error";
212
+ const category = str(a.stopDetails?.category);
213
+ const explanation = str(a.stopDetails?.explanation);
214
+ out.errorMessage = `stop_reason=refusal${category ? ` (${category})` : ""}: the provider's classifier refused to answer${explanation ? `: ${explanation}` : ""}`;
215
+ break;
216
+ }
217
+ case void 0:
218
+ if (!a.sawStop) {
219
+ out.stopReason = "error";
220
+ out.errorMessage = "stream ended before message_stop";
221
+ }
222
+ break;
223
+ default:
224
+ break;
225
+ }
226
+ return out;
227
+ }
228
+ async function* consumeAnthropicStream(input, ctx = {}) {
229
+ const a = new AnthropicAssembly();
230
+ try {
231
+ for await (const msg of input.messages) {
232
+ let ev;
233
+ try {
234
+ ev = JSON.parse(msg.data);
235
+ } catch {
236
+ continue;
237
+ }
238
+ apply(ev, a, ctx);
239
+ if (a.error) break;
240
+ }
241
+ } catch (err) {
242
+ yield* a.drafts(input.origin);
243
+ const base = outcomeOf(a, input.cost);
244
+ if (input.signal?.aborted) return { ...base, stopReason: "aborted", errorMessage: "aborted by host" };
245
+ if (input.timedOut?.())
246
+ return {
247
+ ...base,
248
+ stopReason: "error",
249
+ errorMessage: `request timed out after ${input.timeoutMs ?? "?"}ms (stream ended before message_stop)`
250
+ };
251
+ return { ...base, stopReason: "error", errorMessage: errorText(err) };
252
+ }
253
+ yield* a.drafts(input.origin);
254
+ return outcomeOf(a, input.cost);
255
+ }
256
+ function errorText(err) {
257
+ if (err instanceof Error) {
258
+ const cause = err.cause;
259
+ const code = typeof cause?.code === "string" ? ` (${cause.code})` : "";
260
+ return `${err.message}${code}`;
261
+ }
262
+ return String(err);
263
+ }
264
+
265
+ // src/chat/loss-matrix.ts
266
+ var exact = (landing, note, when) => {
267
+ const spec = { kind: "exact", landing };
268
+ if (note) spec.note = note;
269
+ if (when) spec.when = when;
270
+ return spec;
271
+ };
272
+ var lossy = (landing, note, when) => when ? { kind: "lossy", landing, note, when } : { kind: "lossy", landing, note };
273
+ var dropped = (landing, note, when) => when ? { kind: "dropped", landing, note, when } : { kind: "dropped", landing, note };
274
+ var NOT_SENT = {
275
+ "core.approval_request": [
276
+ dropped("none", "the approval outcome reaches the model through tool_result(isError)")
277
+ ],
278
+ "core.approval_decision": [dropped("none", "same as above")],
279
+ "core.run_paused": [dropped("none", "a run record; the perception module distills it into a system_note")],
280
+ "core.run_resumed": [dropped("none", "same as above")],
281
+ "core.budget_usage": [dropped("none", "same as above")],
282
+ "core.memory_op": [
283
+ dropped(
284
+ "none",
285
+ "an audit trail of memory reads and writes; the model already learned of them from the tool result"
286
+ )
287
+ ],
288
+ "core.handoff": [
289
+ dropped(
290
+ "none",
291
+ "this session ends at the handoff; the new session's first user message carries the triggerMessage"
292
+ )
293
+ ],
294
+ "core.tools_bound": [
295
+ dropped(
296
+ "none",
297
+ "a snapshot of the tool table; the loop announces changes to it in a separate system_note"
298
+ )
299
+ ],
300
+ "core.error": [
301
+ dropped(
302
+ "none",
303
+ "provider errors are handled by the loop; tool errors already live in tool_result.isError"
304
+ )
305
+ ],
306
+ "ext.*": [
307
+ dropped(
308
+ "none",
309
+ "host extension events have no general landing; translate the ones the model must see into core events in the projection layer"
310
+ )
311
+ ]
312
+ };
313
+ var CHAT_LOSS_MATRIX = {
314
+ "core.user_message": [
315
+ exact("user"),
316
+ lossy(
317
+ "user",
318
+ "moved after that batch of tool results (the order changes), untrusted content escaped, or images replaced with placeholder text because the model takes none",
319
+ "the user speaks up before all tool results are in / the content holds a </untrusted / it carries images the model does not take"
320
+ )
321
+ ],
322
+ "core.model_text": [
323
+ exact("assistant-content"),
324
+ lossy(
325
+ "merged-text",
326
+ "multiple text segments of one turn are merged into a single string (assistant.content can only be a string)",
327
+ "the turn holds more than one text segment"
328
+ )
329
+ ],
330
+ "core.model_thinking": [
331
+ exact(
332
+ "reasoning_content",
333
+ "the DeepSeek dialect: the thinking text is filled back in verbatim",
334
+ "the model has chat.reasoningContent on and the source is the same family"
335
+ ),
336
+ dropped(
337
+ "none",
338
+ "Chat Completions has no landing for replaying thinking (no signature, no encrypted item), or the source is another family"
339
+ )
340
+ ],
341
+ "core.tool_call": [exact("tool_calls", "arguments is a JSON string")],
342
+ "core.tool_result": [
343
+ exact(
344
+ "tool",
345
+ "a role:tool message right after the assistant that carries the tool_calls; tool reference parts are expanded into text (Chat has no deferred-loading landing)"
346
+ ),
347
+ lossy(
348
+ "tool",
349
+ "isError is expressed with a [tool error] prefix (a tool message has no error flag), or untrusted content is escaped"
350
+ ),
351
+ lossy(
352
+ "tool-text-only",
353
+ "a tool message takes text only, so images are replaced with placeholder text",
354
+ "the result carries images"
355
+ )
356
+ ],
357
+ "core.system_note": [
358
+ exact(
359
+ "system",
360
+ "a mid-conversation system message",
361
+ "the upstream accepts mid-conversation system (the default)"
362
+ ),
363
+ lossy(
364
+ "system",
365
+ "a mid-conversation system message, with the early close inside untrusted content escaped",
366
+ "a note the host marked untrusted holds a </untrusted"
367
+ ),
368
+ lossy(
369
+ "user-role",
370
+ "wrapped in a <system_note> tag and sent with the user role",
371
+ "the model declares midConversationSystem: false"
372
+ )
373
+ ],
374
+ "core.compaction": [
375
+ lossy(
376
+ "user-text",
377
+ "the summary is rendered as user-role text, so the model cannot tell it apart from the user's own words"
378
+ )
379
+ ],
380
+ ...NOT_SENT
381
+ };
382
+
383
+ // src/anthropic/loss-matrix.ts
384
+ var exact2 = (landing, note, when) => {
385
+ const spec = { kind: "exact", landing };
386
+ if (note) spec.note = note;
387
+ if (when) spec.when = when;
388
+ return spec;
389
+ };
390
+ var lossy2 = (landing, note, when) => when ? { kind: "lossy", landing, note, when } : { kind: "lossy", landing, note };
391
+ var dropped2 = (landing, note, when) => when ? { kind: "dropped", landing, note, when } : { kind: "dropped", landing, note };
392
+ var ANTHROPIC_LOSS_MATRIX = {
393
+ "core.user_message": [
394
+ exact2("user"),
395
+ lossy2(
396
+ "user",
397
+ "moved after that batch of tool results (the order changes), untrusted content escaped, or images replaced with placeholder text because the model takes none",
398
+ "the user speaks up before all tool results are in / the content holds a </untrusted / it carries images the model does not take"
399
+ ),
400
+ dropped2(
401
+ "none",
402
+ "the content is empty and Anthropic rejects empty text blocks",
403
+ "the user message has no non-empty content"
404
+ )
405
+ ],
406
+ "core.model_text": [
407
+ exact2(
408
+ "assistant-text",
409
+ "a text block on the assistant; each text segment of a turn becomes its own block"
410
+ ),
411
+ dropped2(
412
+ "none",
413
+ "empty text is not sent (the provider rejects empty text blocks)",
414
+ "the text is an empty string"
415
+ )
416
+ ],
417
+ "core.model_thinking": [
418
+ exact2(
419
+ "thinking-block",
420
+ "replayed verbatim with its signature",
421
+ "the replay has a thinkingSignature and comes from the same source (provider + api + model)"
422
+ ),
423
+ exact2(
424
+ "redacted-thinking",
425
+ "redacted_thinking is replayed verbatim through its data",
426
+ "replay.redacted is true"
427
+ ),
428
+ dropped2(
429
+ "none",
430
+ "thinking with no signature (a broken stream), from another family, or from another model of the same family (a signature is only valid for the model that produced it) is rejected by the provider, so it is not replayed, and not downgraded to text either"
431
+ )
432
+ ],
433
+ "core.tool_call": [
434
+ exact2("tool_use"),
435
+ lossy2("wrapped-args", "non-object arguments are wrapped as { value }", "tool_call.args is not an object")
436
+ ],
437
+ "core.tool_result": [
438
+ exact2(
439
+ "tool_result",
440
+ "in the user message right after the assistant that holds the tool_use; is_error is passed through; tool reference parts are expanded into text when the native capability is missing or the result is not trusted as system"
441
+ ),
442
+ lossy2(
443
+ "tool_result",
444
+ "untrusted content is escaped, or images are replaced with placeholder text because the model takes none, or the referenced tool is absent from this request's tool table and its definition is expanded into text"
445
+ ),
446
+ exact2(
447
+ "tool-reference",
448
+ "the result holds nothing but tool reference parts: a tool_reference block goes into the tool_result and the provider expands it in place into the full definition (the tool table stays byte-identical)",
449
+ "the model supports defer_loading, the result is trusted as system, and every referenced tool is in this request's tool table"
450
+ ),
451
+ lossy2(
452
+ "tool-reference",
453
+ "the reference block goes into the tool_result and the result's text segments move to a text block right after it in the same user message (a reference inside a tool_result cannot be mixed with text)",
454
+ "the result holds both reference parts and text segments (explanatory text, untrusted markers)"
455
+ )
456
+ ],
457
+ "core.system_note": [
458
+ exact2(
459
+ "system",
460
+ "a mid-conversation system message carrying the text; it is placed before the next assistant message, or at the very end",
461
+ "the model family supports mid-conversation system (Fable 5.x / Mythos 5.x / Opus 5 / Opus 4.8, or the host says so)"
462
+ ),
463
+ lossy2(
464
+ "system",
465
+ "a mid-conversation system message, with the early close inside untrusted content escaped",
466
+ "a note the host marked untrusted holds a </untrusted"
467
+ ),
468
+ lossy2(
469
+ "user-role",
470
+ "wrapped in a <system_note> tag and sent with the user role",
471
+ "the model family does not support mid-conversation system, or the placement rules forbid it (the previous message is an assistant, or the note would be first)"
472
+ )
473
+ ],
474
+ "core.compaction": [
475
+ lossy2(
476
+ "user-text",
477
+ "the summary is rendered as user-role text, so the model cannot tell it apart from the user's own words"
478
+ )
479
+ ],
480
+ ...NOT_SENT
481
+ };
482
+
483
+ // src/anthropic/to-request.ts
484
+ import {
485
+ renderToolReference
486
+ } from "@reinsjs/core";
487
+
488
+ // src/ir.ts
489
+ import {
490
+ markUntrusted,
491
+ markUntrustedText,
492
+ needsUntrustedMark,
493
+ untrustedSourceOf
494
+ } from "@reinsjs/core";
495
+ var DEFERRED_NOTE = "moved after that batch of tool results (tool results must immediately follow their call)";
496
+ var ESCAPED_NOTE = "an early-closing </untrusted inside untrusted content was escaped";
497
+ var NOT_SENT_NOTE = "operational event, not sent (the default projection already filters it)";
498
+ function originOf(replay, fallback) {
499
+ const r = replay ?? {};
500
+ return {
501
+ provider: typeof r.provider === "string" ? r.provider : fallback.provider,
502
+ api: typeof r.api === "string" ? r.api : fallback.api,
503
+ model: typeof r.model === "string" ? r.model : fallback.model
504
+ };
505
+ }
506
+ function sameOrigin(a, b) {
507
+ return a.provider === b.provider && a.api === b.api && a.model === b.model;
508
+ }
509
+ function foreignOrigin(a, target) {
510
+ return a.provider !== target.provider || a.api !== target.api;
511
+ }
512
+ function eventsToIr(input) {
513
+ const { target } = input;
514
+ const trustMarkers = input.trustMarkers !== false;
515
+ const items = [];
516
+ let group = null;
517
+ const awaiting = /* @__PURE__ */ new Set();
518
+ const deferred = [];
519
+ const content = (e, parts) => {
520
+ if (!trustMarkers || !needsUntrustedMark(e)) return { parts: [...parts], escaped: false };
521
+ return markUntrusted(parts, untrustedSourceOf(e));
522
+ };
523
+ const text = (e, s) => {
524
+ if (!trustMarkers || !needsUntrustedMark(e)) return { text: s, escaped: false };
525
+ return markUntrustedText(s, untrustedSourceOf(e));
526
+ };
527
+ const release = () => {
528
+ for (const d of deferred) items.push(d);
529
+ deferred.length = 0;
530
+ };
531
+ const place = (item) => {
532
+ if (awaiting.size > 0) {
533
+ deferred.push({ ...item, deferred: true });
534
+ return;
535
+ }
536
+ items.push(item);
537
+ };
538
+ const flush = () => {
539
+ if (!group) return;
540
+ items.push({ kind: "assistant", origin: group.origin, blocks: group.blocks, at: group.at });
541
+ for (const b of group.blocks) if (b.type === "tool_call") awaiting.add(b.id);
542
+ group = null;
543
+ };
544
+ const settle = () => {
545
+ awaiting.clear();
546
+ release();
547
+ };
548
+ const assistant = (e, origin) => {
549
+ if (group && !sameOrigin(group.origin, origin)) flush();
550
+ if (!group) {
551
+ settle();
552
+ group = { origin, blocks: [], at: e.at };
553
+ }
554
+ return group;
555
+ };
556
+ for (const raw of input.events) {
557
+ const e = raw;
558
+ switch (e.type) {
559
+ case "core.user_message": {
560
+ flush();
561
+ const c = content(e, e.payload.content);
562
+ place({ kind: "user", event: e, parts: c.parts, escaped: c.escaped, deferred: false });
563
+ break;
564
+ }
565
+ case "core.model_thinking": {
566
+ assistant(e, originOf(e.replay, target)).blocks.push({
567
+ type: "thinking",
568
+ event: e,
569
+ text: e.payload.text,
570
+ replay: e.replay ?? {}
571
+ });
572
+ break;
573
+ }
574
+ case "core.model_text": {
575
+ assistant(e, originOf(e.replay, target)).blocks.push({
576
+ type: "text",
577
+ event: e,
578
+ text: e.payload.text,
579
+ replay: e.replay ?? {}
580
+ });
581
+ break;
582
+ }
583
+ case "core.tool_call": {
584
+ assistant(e, originOf(e.replay, target)).blocks.push({
585
+ type: "tool_call",
586
+ event: e,
587
+ id: e.payload.toolCallId,
588
+ name: e.payload.name,
589
+ args: e.payload.args,
590
+ replay: e.replay ?? {}
591
+ });
592
+ break;
593
+ }
594
+ case "core.tool_result": {
595
+ flush();
596
+ const c = content(e, e.payload.content);
597
+ items.push({
598
+ kind: "tool_result",
599
+ event: e,
600
+ toolCallId: e.payload.toolCallId,
601
+ name: e.payload.name,
602
+ parts: c.parts,
603
+ isError: e.payload.isError,
604
+ escaped: c.escaped
605
+ });
606
+ awaiting.delete(e.payload.toolCallId);
607
+ if (awaiting.size === 0) release();
608
+ break;
609
+ }
610
+ case "core.system_note": {
611
+ flush();
612
+ const t = text(e, e.payload.text);
613
+ place({
614
+ kind: "system_note",
615
+ event: e,
616
+ noteKind: e.payload.kind,
617
+ text: t.text,
618
+ escaped: t.escaped,
619
+ deferred: false
620
+ });
621
+ break;
622
+ }
623
+ case "core.compaction": {
624
+ flush();
625
+ const t = text(e, e.payload.summary);
626
+ place({ kind: "compaction", event: e, text: t.text, escaped: t.escaped, deferred: false });
627
+ break;
628
+ }
629
+ case "core.approval_request":
630
+ case "core.approval_decision":
631
+ case "core.run_paused":
632
+ case "core.run_resumed":
633
+ case "core.budget_usage":
634
+ case "core.memory_op":
635
+ case "core.handoff":
636
+ case "core.tools_bound":
637
+ case "core.error":
638
+ items.push({ kind: "dropped", event: e, note: NOT_SENT_NOTE });
639
+ break;
640
+ default:
641
+ items.push({ kind: "dropped", event: raw, note: `no general landing for ${raw.type}` });
642
+ }
643
+ }
644
+ flush();
645
+ settle();
646
+ return items;
647
+ }
648
+ function orderLandings(events, landings) {
649
+ const index = /* @__PURE__ */ new Map();
650
+ for (const [i, e] of events.entries()) index.set(e.id, i);
651
+ return [...landings].sort((a, b) => (index.get(a.eventId) ?? 0) - (index.get(b.eventId) ?? 0));
652
+ }
653
+
654
+ // src/notes.ts
655
+ function framedSystemNote(kind, text) {
656
+ return `<system_note kind="${kind}">
657
+ ${text}
658
+ </system_note>`;
659
+ }
660
+ function framedSummary(summary) {
661
+ return `[Summary of earlier conversation]
662
+ ${summary}`;
663
+ }
664
+
665
+ // src/anthropic/to-request.ts
666
+ var MAX_ANTHROPIC_BREAKPOINTS = 4;
667
+ var IMAGE_OMITTED = "[image omitted: this model does not accept images]";
668
+ var EMPTY_NOTE = "the content is empty and Anthropic rejects empty text blocks, so the whole message is not sent";
669
+ var MOVED_NOTE = "moved to just after the next user message (a system message must follow a user message, and be followed by an assistant message or the end)";
670
+ var REF_TEXT_ASIDE_NOTE = "the result's text segments move to a text block after this batch of tool_result in the same user message (a reference inside a tool_result cannot be mixed with text, and tool_result must come first in a user message)";
671
+ var REF_UNTRUSTED_NOTE = "a tool reference in an untrusted result does not take the native landing (untrusted content must not light up tools for the model), so it is expanded into text";
672
+ var REF_UNBOUND_NOTE = "the referenced tool is absent from this request's tool table (the provider validates references across the whole history and answers 400 if one is missing), so the definition is expanded into text";
673
+ function land(out, e, kind, landing, ...notes) {
674
+ const note = notes.filter((n) => Boolean(n)).join("; ");
675
+ out.push(
676
+ note ? { eventId: e.id, type: e.type, kind, landing, note } : { eventId: e.id, type: e.type, kind, landing }
677
+ );
678
+ }
679
+ function isPlainObject(v) {
680
+ return typeof v === "object" && v !== null && !Array.isArray(v);
681
+ }
682
+ function blocksOf(parts, images) {
683
+ let imagesDropped = false;
684
+ const blocks = [];
685
+ for (const p of parts) {
686
+ if (p.type === "text") {
687
+ if (p.text.length > 0) blocks.push({ type: "text", text: p.text });
688
+ } else if (p.type === "tool_reference") {
689
+ blocks.push({ type: "text", text: renderToolReference(p) });
690
+ } else if (images) {
691
+ blocks.push({ type: "image", source: { type: "base64", media_type: p.mime, data: p.data } });
692
+ } else {
693
+ imagesDropped = true;
694
+ blocks.push({ type: "text", text: IMAGE_OMITTED });
695
+ }
696
+ }
697
+ return { blocks, imagesDropped };
698
+ }
699
+ function encodeAnthropicRequest(input) {
700
+ const { model, capabilities } = input;
701
+ const target = { provider: model.provider, api: model.api, model: model.id };
702
+ const dialect = model.anthropic ?? {};
703
+ const boundNames = new Set((input.tools ?? []).map((t) => t.name));
704
+ const messages = [];
705
+ const landings = [];
706
+ let userRun = null;
707
+ let asides = [];
708
+ const pendingNotes = [];
709
+ const openUser = () => {
710
+ if (!userRun) userRun = [];
711
+ for (const n of pendingNotes) n.moved = true;
712
+ return userRun;
713
+ };
714
+ const closeUser = () => {
715
+ if (userRun && asides.length > 0) {
716
+ const lastResult = userRun.map((b) => b.type).lastIndexOf("tool_result");
717
+ userRun.splice(lastResult + 1, 0, ...asides);
718
+ asides = [];
719
+ }
720
+ if (userRun && userRun.length > 0) messages.push({ role: "user", content: userRun });
721
+ userRun = null;
722
+ };
723
+ const flushNotes = () => {
724
+ closeUser();
725
+ if (pendingNotes.length === 0) return;
726
+ const last = messages[messages.length - 1];
727
+ if (last && last.role !== "assistant") {
728
+ for (const { item, moved } of pendingNotes) {
729
+ messages.push({ role: "system", content: [{ type: "text", text: item.text }] });
730
+ land(
731
+ landings,
732
+ item.event,
733
+ item.escaped ? "lossy" : "exact",
734
+ "system",
735
+ item.escaped ? ESCAPED_NOTE : void 0,
736
+ item.deferred ? DEFERRED_NOTE : void 0,
737
+ moved ? MOVED_NOTE : void 0
738
+ );
739
+ }
740
+ } else {
741
+ const why = last ? "the previous message is an assistant, and a system message must follow a user message" : "a system message cannot come first";
742
+ messages.push({
743
+ role: "user",
744
+ content: pendingNotes.map(({ item }) => ({
745
+ type: "text",
746
+ text: framedSystemNote(item.noteKind, item.text)
747
+ }))
748
+ });
749
+ for (const { item } of pendingNotes) {
750
+ land(
751
+ landings,
752
+ item.event,
753
+ "lossy",
754
+ "user-role",
755
+ `${why}, so it is wrapped in a <system_note> tag and sent with the user role`,
756
+ item.escaped ? ESCAPED_NOTE : void 0,
757
+ item.deferred ? DEFERRED_NOTE : void 0
758
+ );
759
+ }
760
+ }
761
+ pendingNotes.length = 0;
762
+ };
763
+ for (const item of input.ir) {
764
+ switch (item.kind) {
765
+ case "user": {
766
+ const c = blocksOf(item.parts, capabilities.images);
767
+ if (c.blocks.length === 0) {
768
+ land(landings, item.event, "dropped", "none", EMPTY_NOTE);
769
+ break;
770
+ }
771
+ openUser().push(...c.blocks);
772
+ const lossy4 = item.deferred || item.escaped || c.imagesDropped;
773
+ land(
774
+ landings,
775
+ item.event,
776
+ lossy4 ? "lossy" : "exact",
777
+ "user",
778
+ item.deferred ? `the user message sits between a tool call and its results; ${DEFERRED_NOTE}` : void 0,
779
+ item.escaped ? ESCAPED_NOTE : void 0,
780
+ c.imagesDropped ? "the model takes no images, so they are replaced with placeholder text" : void 0
781
+ );
782
+ break;
783
+ }
784
+ case "tool_result": {
785
+ const refs = item.parts.filter(
786
+ (p) => p.type === "tool_reference"
787
+ );
788
+ const block = { type: "tool_result", tool_use_id: item.toolCallId };
789
+ if (item.isError) block.is_error = true;
790
+ const trusted = item.event.trust === "system";
791
+ if (refs.length > 0 && capabilities.deferredTools && trusted && refs.every((r) => boundNames.has(r.name))) {
792
+ block.content = refs.map((r) => ({ type: "tool_reference", tool_name: r.name }));
793
+ const rest = blocksOf(
794
+ item.parts.filter((p) => p.type !== "tool_reference"),
795
+ capabilities.images
796
+ );
797
+ openUser().push(block);
798
+ asides.push(...rest.blocks);
799
+ const aside = rest.blocks.length > 0;
800
+ land(
801
+ landings,
802
+ item.event,
803
+ aside || item.escaped || rest.imagesDropped ? "lossy" : "exact",
804
+ "tool-reference",
805
+ aside ? REF_TEXT_ASIDE_NOTE : void 0,
806
+ rest.imagesDropped ? "the model takes no images, so they are replaced with placeholder text" : void 0,
807
+ item.escaped ? ESCAPED_NOTE : void 0
808
+ );
809
+ break;
810
+ }
811
+ const c = blocksOf(item.parts, capabilities.images);
812
+ if (c.blocks.length > 0) block.content = c.blocks;
813
+ openUser().push(block);
814
+ const unbound = refs.length > 0 && capabilities.deferredTools && trusted;
815
+ const untrustedRefs = refs.length > 0 && capabilities.deferredTools && !trusted;
816
+ const lossy4 = item.escaped || c.imagesDropped || unbound;
817
+ land(
818
+ landings,
819
+ item.event,
820
+ lossy4 ? "lossy" : "exact",
821
+ "tool_result",
822
+ unbound ? REF_UNBOUND_NOTE : void 0,
823
+ untrustedRefs ? REF_UNTRUSTED_NOTE : void 0,
824
+ c.imagesDropped ? "the model takes no images, so they are replaced with placeholder text" : void 0,
825
+ item.escaped ? ESCAPED_NOTE : void 0
826
+ );
827
+ break;
828
+ }
829
+ case "compaction": {
830
+ openUser().push({ type: "text", text: framedSummary(item.text) });
831
+ land(
832
+ landings,
833
+ item.event,
834
+ "lossy",
835
+ "user-text",
836
+ "the summary is rendered as user-role text",
837
+ item.escaped ? ESCAPED_NOTE : void 0,
838
+ item.deferred ? DEFERRED_NOTE : void 0
839
+ );
840
+ break;
841
+ }
842
+ case "system_note": {
843
+ if (capabilities.midConversationSystem) {
844
+ pendingNotes.push({ item, moved: false });
845
+ } else {
846
+ openUser().push({ type: "text", text: framedSystemNote(item.noteKind, item.text) });
847
+ land(
848
+ landings,
849
+ item.event,
850
+ "lossy",
851
+ "user-role",
852
+ "the model does not support mid-conversation system, so it is wrapped in a <system_note> tag and sent with the user role",
853
+ item.escaped ? ESCAPED_NOTE : void 0,
854
+ item.deferred ? DEFERRED_NOTE : void 0
855
+ );
856
+ }
857
+ break;
858
+ }
859
+ case "assistant": {
860
+ const content = assistantBlocks(item.blocks, item.origin, target, landings);
861
+ if (content.length === 0) break;
862
+ flushNotes();
863
+ messages.push({ role: "assistant", content });
864
+ break;
865
+ }
866
+ case "dropped":
867
+ land(landings, item.event, "dropped", "none", item.note);
868
+ break;
869
+ }
870
+ }
871
+ flushNotes();
872
+ const ro = input.requestOptions ?? {};
873
+ const { system: _system, tools: _tools, ...passthrough } = ro;
874
+ const body = {
875
+ ...passthrough,
876
+ model: model.id,
877
+ max_tokens: typeof ro.max_tokens === "number" ? ro.max_tokens : model.maxOutputTokens,
878
+ messages,
879
+ stream: true
880
+ };
881
+ if (input.systemPrompt) body.system = [{ type: "text", text: input.systemPrompt }];
882
+ if (input.tools && input.tools.length > 0) {
883
+ const tools = encodeTools(input.tools, capabilities.deferredTools);
884
+ if (tools.length > 0) body.tools = tools;
885
+ }
886
+ if (dialect.cacheBreakpoints !== false) placeCacheBreakpoints(body, dialect);
887
+ return { body, landings: orderLandings(input.events, landings) };
888
+ }
889
+ function assistantBlocks(blocks, origin, target, landings) {
890
+ const out = [];
891
+ const foreign = foreignOrigin(origin, target);
892
+ for (const b of blocks) {
893
+ switch (b.type) {
894
+ case "text":
895
+ if (b.text.length === 0) {
896
+ land(landings, b.event, "dropped", "none", EMPTY_NOTE);
897
+ break;
898
+ }
899
+ out.push({ type: "text", text: b.text });
900
+ land(landings, b.event, "exact", "assistant-text");
901
+ break;
902
+ case "thinking": {
903
+ const signature = typeof b.replay.thinkingSignature === "string" ? b.replay.thinkingSignature : "";
904
+ if (foreign) {
905
+ land(
906
+ landings,
907
+ b.event,
908
+ "dropped",
909
+ "none",
910
+ `thinking from ${origin.provider}/${origin.api} has no signature of its own, so it is not replayed`
911
+ );
912
+ break;
913
+ }
914
+ if (signature.length === 0) {
915
+ land(
916
+ landings,
917
+ b.event,
918
+ "dropped",
919
+ "none",
920
+ "thinking with no signature (a broken stream) is rejected by the provider, so it is not replayed"
921
+ );
922
+ break;
923
+ }
924
+ if (origin.model !== target.model) {
925
+ land(
926
+ landings,
927
+ b.event,
928
+ "dropped",
929
+ "none",
930
+ `the signature comes from ${origin.model}, but this request targets ${target.model}; a signature is only valid for the model that produced it, so the block is not replayed`
931
+ );
932
+ break;
933
+ }
934
+ if (b.replay.redacted === true) {
935
+ out.push({ type: "redacted_thinking", data: signature });
936
+ land(
937
+ landings,
938
+ b.event,
939
+ "exact",
940
+ "redacted-thinking",
941
+ "redacted_thinking is replayed verbatim through its data"
942
+ );
943
+ } else {
944
+ out.push({ type: "thinking", thinking: b.text, signature });
945
+ land(landings, b.event, "exact", "thinking-block");
946
+ }
947
+ break;
948
+ }
949
+ case "tool_call": {
950
+ const wrapped = !isPlainObject(b.args);
951
+ out.push({
952
+ type: "tool_use",
953
+ id: b.id,
954
+ name: b.name,
955
+ input: wrapped ? { value: b.args } : b.args
956
+ });
957
+ if (wrapped)
958
+ land(
959
+ landings,
960
+ b.event,
961
+ "lossy",
962
+ "wrapped-args",
963
+ "non-object arguments are wrapped as { value } (tool_use.input must be an object)"
964
+ );
965
+ else land(landings, b.event, "exact", "tool_use");
966
+ break;
967
+ }
968
+ }
969
+ }
970
+ return out;
971
+ }
972
+ function encodeTools(specs, deferredTools) {
973
+ const shown = deferredTools ? specs : specs.filter((t) => t.deferLoading !== true);
974
+ const allDeferred = deferredTools && shown.length > 0 && shown.every((t) => t.deferLoading === true);
975
+ return shown.map((t) => {
976
+ const tool = { name: t.name, description: t.description, input_schema: t.inputSchema };
977
+ if (deferredTools && t.deferLoading === true && !allDeferred) tool.defer_loading = true;
978
+ return tool;
979
+ });
980
+ }
981
+ function countBlockBreakpoints(body) {
982
+ const has = (b) => typeof b === "object" && b !== null && "cache_control" in b;
983
+ let n = 0;
984
+ for (const s of body.system ?? []) if (has(s)) n++;
985
+ for (const t of body.tools ?? []) if (has(t)) n++;
986
+ for (const m of body.messages) for (const b of m.content) if (has(b)) n++;
987
+ return n;
988
+ }
989
+ function placeCacheBreakpoints(body, dialect) {
990
+ const cc = dialect.cacheTtl === "1h" ? { type: "ephemeral", ttl: "1h" } : { type: "ephemeral" };
991
+ const budget = () => MAX_ANTHROPIC_BREAKPOINTS - countBlockBreakpoints(body) - (body.cache_control ? 1 : 0);
992
+ const lastSystem = body.system?.[body.system.length - 1];
993
+ if (lastSystem && budget() > 0) lastSystem.cache_control = cc;
994
+ const lastTool = [...body.tools ?? []].reverse().find((t) => t.defer_loading !== true);
995
+ if (lastTool && budget() > 0) lastTool.cache_control = cc;
996
+ const last = body.messages[body.messages.length - 1];
997
+ if (!last) return;
998
+ const markLastUser = (m) => {
999
+ if (m?.role !== "user") return;
1000
+ const block = m.content[m.content.length - 1];
1001
+ if (block && budget() > 0) block.cache_control = cc;
1002
+ };
1003
+ if (last.role === "user") {
1004
+ markLastUser(last);
1005
+ return;
1006
+ }
1007
+ if (last.role !== "system") return;
1008
+ switch (dialect.midSystemCacheBreakpoint ?? "automatic") {
1009
+ case "automatic":
1010
+ if (body.cache_control === void 0 && budget() > 0) body.cache_control = cc;
1011
+ break;
1012
+ case "previous-user":
1013
+ markLastUser([...body.messages].reverse().find((m) => m.role === "user"));
1014
+ break;
1015
+ case "drop":
1016
+ break;
1017
+ }
1018
+ }
1019
+
1020
+ // src/capabilities.ts
1021
+ var ANTHROPIC_MID_SYSTEM = /^claude-(fable-5|mythos-5|opus-5|opus-4-8)(-|$)/;
1022
+ var ANTHROPIC_TASK_BUDGET = /^claude-(opus-5|fable-5-1)(-|$)/;
1023
+ function capabilitiesOf(model) {
1024
+ const base = {
1025
+ api: model.api,
1026
+ parallelTools: true,
1027
+ images: model.images ?? false,
1028
+ contextWindow: model.contextWindow,
1029
+ maxOutputTokens: model.maxOutputTokens,
1030
+ taskBudget: false,
1031
+ // 只有 Anthropic 线有 defer_loading / tool_reference 的原生落点(L1);其余线引用段展开成文本、deferLoading 的工具不发
1032
+ deferredTools: false
1033
+ };
1034
+ switch (model.api) {
1035
+ case "openai-chat":
1036
+ return {
1037
+ ...base,
1038
+ // Chat 的 system 消息可出现在任意位置(OpenAI 官方与 DeepSeek 实测到达),宿主可对特殊上游关掉
1039
+ midConversationSystem: model.midConversationSystem ?? true,
1040
+ // 官方 Chat 没有 thinking 回放位;只有 DeepSeek 方言的 reasoning_content 能把思考原样回填
1041
+ thinkingReplay: model.reasoning && model.chat?.reasoningContent === true
1042
+ };
1043
+ case "anthropic-messages":
1044
+ return {
1045
+ ...base,
1046
+ midConversationSystem: model.midConversationSystem ?? ANTHROPIC_MID_SYSTEM.test(model.id),
1047
+ // 带 signature 的 thinking 块可原样回放(F0 A7b 实测接受、伪造签名 400)
1048
+ thinkingReplay: model.reasoning,
1049
+ taskBudget: ANTHROPIC_TASK_BUDGET.test(model.id),
1050
+ // 官方模型 Haiku 4.5 起都支持(L1 spike 实测 Haiku 4.5 / Opus 5);第三方兼容上游由宿主声明
1051
+ deferredTools: model.anthropic?.deferredTools ?? model.provider === "anthropic"
1052
+ };
1053
+ case "openai-responses":
1054
+ return {
1055
+ ...base,
1056
+ // input 项里 developer / system 消息可出现在任意位置(F0 R3 实测中途 developer 到达),无归位需求
1057
+ midConversationSystem: model.midConversationSystem ?? true,
1058
+ // reasoning 项以 encrypted_content 原样回放(F0 R3 接受、伪造 400);关掉 encryptedReasoning 就没有可回放的东西
1059
+ thinkingReplay: model.reasoning && model.responses?.encryptedReasoning !== false
1060
+ };
1061
+ default:
1062
+ return { ...base, midConversationSystem: model.midConversationSystem ?? false, thinkingReplay: false };
1063
+ }
1064
+ }
1065
+
1066
+ // src/chat/from-stream.ts
1067
+ var ChatAssembly = class {
1068
+ reasoning = "";
1069
+ text = "";
1070
+ calls = /* @__PURE__ */ new Map();
1071
+ finish;
1072
+ usage;
1073
+ responseModel;
1074
+ /** onDelta 用的块序号:按首次出现顺序分配 */
1075
+ blockIndex = /* @__PURE__ */ new Map();
1076
+ indexOf(key) {
1077
+ let i = this.blockIndex.get(key);
1078
+ if (i === void 0) {
1079
+ i = this.blockIndex.size;
1080
+ this.blockIndex.set(key, i);
1081
+ }
1082
+ return i;
1083
+ }
1084
+ drafts(origin) {
1085
+ const out = [];
1086
+ const replay = { ...origin };
1087
+ if (this.reasoning.length > 0)
1088
+ out.push({ type: "core.model_thinking", actor: "model", payload: { text: this.reasoning }, replay });
1089
+ if (this.text.length > 0)
1090
+ out.push({ type: "core.model_text", actor: "model", payload: { text: this.text }, replay });
1091
+ for (const [, c] of [...this.calls.entries()].sort((a, b) => a[0] - b[0])) {
1092
+ out.push({
1093
+ type: "core.tool_call",
1094
+ actor: "model",
1095
+ payload: { toolCallId: c.id, name: c.name, args: parseArguments(c.args) },
1096
+ replay
1097
+ });
1098
+ }
1099
+ return out;
1100
+ }
1101
+ };
1102
+ function parseArguments(raw) {
1103
+ if (raw.trim() === "") return {};
1104
+ try {
1105
+ return JSON.parse(raw);
1106
+ } catch {
1107
+ return raw;
1108
+ }
1109
+ }
1110
+ function num2(v) {
1111
+ return typeof v === "number" && Number.isFinite(v) ? v : void 0;
1112
+ }
1113
+ function usageOf2(u) {
1114
+ const prompt = num2(u?.prompt_tokens) ?? 0;
1115
+ const cached = num2(u?.prompt_tokens_details?.cached_tokens) ?? num2(u?.prompt_cache_hit_tokens) ?? 0;
1116
+ const usage = { input: Math.max(0, prompt - cached), output: num2(u?.completion_tokens) ?? 0 };
1117
+ if (cached > 0) usage.cacheRead = cached;
1118
+ return usage;
1119
+ }
1120
+ function apply2(chunk, a, ctx) {
1121
+ if (typeof chunk.model === "string" && chunk.model !== "") a.responseModel = chunk.model;
1122
+ if (chunk.usage && typeof chunk.usage === "object") a.usage = chunk.usage;
1123
+ const choice = chunk.choices?.[0];
1124
+ if (!choice) return;
1125
+ const d = choice.delta;
1126
+ if (d) {
1127
+ if (typeof d.reasoning_content === "string" && d.reasoning_content !== "") {
1128
+ a.reasoning += d.reasoning_content;
1129
+ ctx.onDelta?.({ kind: "thinking", index: a.indexOf("thinking"), delta: d.reasoning_content });
1130
+ }
1131
+ if (typeof d.content === "string" && d.content !== "") {
1132
+ a.text += d.content;
1133
+ ctx.onDelta?.({ kind: "text", index: a.indexOf("text"), delta: d.content });
1134
+ }
1135
+ for (const tc of d.tool_calls ?? []) {
1136
+ const index = num2(tc.index) ?? 0;
1137
+ let call = a.calls.get(index);
1138
+ if (!call) {
1139
+ call = { id: "", name: "", args: "" };
1140
+ a.calls.set(index, call);
1141
+ }
1142
+ if (typeof tc.id === "string" && tc.id !== "") call.id = tc.id;
1143
+ if (typeof tc.function?.name === "string" && tc.function.name !== "") call.name = tc.function.name;
1144
+ if (typeof tc.function?.arguments === "string" && tc.function.arguments !== "") {
1145
+ call.args += tc.function.arguments;
1146
+ ctx.onDelta?.({ kind: "tool_args", index: a.indexOf(`tool:${index}`), delta: tc.function.arguments });
1147
+ }
1148
+ }
1149
+ }
1150
+ if (typeof choice.finish_reason === "string") a.finish = choice.finish_reason;
1151
+ }
1152
+ function outcomeOf2(a, cost, sawDone) {
1153
+ const usage = usageOf2(a.usage);
1154
+ const out = { stopReason: "stop", usage };
1155
+ const c = costOf(usage, cost);
1156
+ if (c !== void 0) out.costUsd = c;
1157
+ if (a.responseModel) out.responseModel = a.responseModel;
1158
+ if (a.calls.size > 0) out.stopReason = "toolUse";
1159
+ else {
1160
+ switch (a.finish) {
1161
+ case "stop":
1162
+ case "tool_calls":
1163
+ break;
1164
+ case "length":
1165
+ out.stopReason = "length";
1166
+ break;
1167
+ case "content_filter":
1168
+ out.stopReason = "error";
1169
+ out.errorMessage = "finish_reason=content_filter: the provider's content filter cut this output short";
1170
+ break;
1171
+ case "insufficient_system_resource":
1172
+ out.stopReason = "error";
1173
+ out.errorMessage = "finish_reason=insufficient_system_resource (service temporarily unavailable)";
1174
+ break;
1175
+ case "aborted":
1176
+ out.stopReason = "error";
1177
+ out.errorMessage = "finish_reason=aborted: the server aborted this generation";
1178
+ break;
1179
+ case void 0:
1180
+ if (!sawDone || a.text.length === 0) {
1181
+ out.stopReason = "error";
1182
+ out.errorMessage = "stream ended before finish_reason";
1183
+ }
1184
+ break;
1185
+ default:
1186
+ break;
1187
+ }
1188
+ }
1189
+ return out;
1190
+ }
1191
+ async function* consumeChatStream(input, ctx = {}) {
1192
+ const a = new ChatAssembly();
1193
+ let sawDone = false;
1194
+ try {
1195
+ for await (const msg of input.messages) {
1196
+ if (msg.data === "[DONE]") {
1197
+ sawDone = true;
1198
+ break;
1199
+ }
1200
+ let chunk;
1201
+ try {
1202
+ chunk = JSON.parse(msg.data);
1203
+ } catch {
1204
+ continue;
1205
+ }
1206
+ apply2(chunk, a, ctx);
1207
+ }
1208
+ } catch (err) {
1209
+ yield* a.drafts(input.origin);
1210
+ const base = outcomeOf2(a, input.cost, false);
1211
+ if (input.signal?.aborted) return { ...base, stopReason: "aborted", errorMessage: "aborted by host" };
1212
+ if (input.timedOut?.())
1213
+ return {
1214
+ ...base,
1215
+ stopReason: "error",
1216
+ errorMessage: `request timed out after ${input.timeoutMs ?? "?"}ms (stream ended before finish_reason)`
1217
+ };
1218
+ return { ...base, stopReason: "error", errorMessage: errorText2(err) };
1219
+ }
1220
+ yield* a.drafts(input.origin);
1221
+ return outcomeOf2(a, input.cost, sawDone);
1222
+ }
1223
+ function errorText2(err) {
1224
+ if (err instanceof Error) {
1225
+ const cause = err.cause;
1226
+ const code = typeof cause?.code === "string" ? ` (${cause.code})` : "";
1227
+ return `${err.message}${code}`;
1228
+ }
1229
+ return String(err);
1230
+ }
1231
+
1232
+ // src/chat/to-request.ts
1233
+ import {
1234
+ renderToolReference as renderToolReference2
1235
+ } from "@reinsjs/core";
1236
+ var IMAGE_OMITTED2 = "[image omitted: this model does not accept images]";
1237
+ var TOOL_IMAGE_OMITTED = "[image omitted: tool messages carry text only]";
1238
+ var ERROR_PREFIX = "[tool error]\n";
1239
+ function land2(out, e, kind, landing, ...notes) {
1240
+ const note = notes.filter((n) => Boolean(n)).join("; ");
1241
+ out.push(
1242
+ note ? { eventId: e.id, type: e.type, kind, landing, note } : { eventId: e.id, type: e.type, kind, landing }
1243
+ );
1244
+ }
1245
+ function userContent(parts, images) {
1246
+ let imagesDropped = false;
1247
+ const out = parts.map((p) => {
1248
+ if (p.type === "text") return { type: "text", text: p.text };
1249
+ if (p.type === "tool_reference") return { type: "text", text: renderToolReference2(p) };
1250
+ if (!images) {
1251
+ imagesDropped = true;
1252
+ return { type: "text", text: IMAGE_OMITTED2 };
1253
+ }
1254
+ return { type: "image_url", image_url: { url: `data:${p.mime};base64,${p.data}` } };
1255
+ });
1256
+ if (out.length === 1 && out[0]?.type === "text") return { content: out[0].text, imagesDropped };
1257
+ return { content: out, imagesDropped };
1258
+ }
1259
+ function toolContent(parts) {
1260
+ let imagesDropped = false;
1261
+ const text = parts.map((p) => {
1262
+ if (p.type === "text") return p.text;
1263
+ if (p.type === "tool_reference") return renderToolReference2(p);
1264
+ imagesDropped = true;
1265
+ return TOOL_IMAGE_OMITTED;
1266
+ }).join("\n");
1267
+ return { text, imagesDropped };
1268
+ }
1269
+ function argumentsOf(args) {
1270
+ return typeof args === "string" ? args : JSON.stringify(args ?? {});
1271
+ }
1272
+ function encodeChatRequest(input) {
1273
+ const { model, capabilities } = input;
1274
+ const target = { provider: model.provider, api: model.api, model: model.id };
1275
+ const reasoningContent = model.chat?.reasoningContent === true;
1276
+ const messages = [];
1277
+ const landings = [];
1278
+ if (input.systemPrompt) messages.push({ role: "system", content: input.systemPrompt });
1279
+ for (const item of input.ir) {
1280
+ switch (item.kind) {
1281
+ case "user": {
1282
+ const c = userContent(item.parts, capabilities.images);
1283
+ messages.push({ role: "user", content: c.content });
1284
+ const lossy4 = item.deferred || item.escaped || c.imagesDropped;
1285
+ land2(
1286
+ landings,
1287
+ item.event,
1288
+ lossy4 ? "lossy" : "exact",
1289
+ "user",
1290
+ item.deferred ? `the user message sits between a tool call and its results; ${DEFERRED_NOTE}` : void 0,
1291
+ item.escaped ? ESCAPED_NOTE : void 0,
1292
+ c.imagesDropped ? "the model takes no images, so they are replaced with placeholder text" : void 0
1293
+ );
1294
+ break;
1295
+ }
1296
+ case "assistant": {
1297
+ messages.push(assistantMessage(item.blocks, item.origin, target, reasoningContent, landings));
1298
+ break;
1299
+ }
1300
+ case "tool_result": {
1301
+ const c = toolContent(item.parts);
1302
+ messages.push({
1303
+ role: "tool",
1304
+ tool_call_id: item.toolCallId,
1305
+ content: item.isError ? ERROR_PREFIX + c.text : c.text
1306
+ });
1307
+ const lossy4 = item.escaped || item.isError || c.imagesDropped;
1308
+ land2(
1309
+ landings,
1310
+ item.event,
1311
+ lossy4 ? "lossy" : "exact",
1312
+ c.imagesDropped ? "tool-text-only" : "tool",
1313
+ item.isError ? "a Chat tool message has no error flag, so isError is expressed with a [tool error] prefix" : void 0,
1314
+ c.imagesDropped ? "a tool message takes text only, so images are replaced with placeholder text" : void 0,
1315
+ item.escaped ? ESCAPED_NOTE : void 0
1316
+ );
1317
+ break;
1318
+ }
1319
+ case "system_note": {
1320
+ if (capabilities.midConversationSystem) {
1321
+ messages.push({ role: "system", content: item.text });
1322
+ land2(
1323
+ landings,
1324
+ item.event,
1325
+ item.escaped ? "lossy" : "exact",
1326
+ "system",
1327
+ item.escaped ? ESCAPED_NOTE : void 0,
1328
+ item.deferred ? DEFERRED_NOTE : void 0
1329
+ );
1330
+ } else {
1331
+ messages.push({ role: "user", content: framedSystemNote(item.noteKind, item.text) });
1332
+ land2(
1333
+ landings,
1334
+ item.event,
1335
+ "lossy",
1336
+ "user-role",
1337
+ "the upstream does not accept mid-conversation system, so it is wrapped in a <system_note> tag and sent with the user role",
1338
+ item.escaped ? ESCAPED_NOTE : void 0,
1339
+ item.deferred ? DEFERRED_NOTE : void 0
1340
+ );
1341
+ }
1342
+ break;
1343
+ }
1344
+ case "compaction": {
1345
+ messages.push({ role: "user", content: framedSummary(item.text) });
1346
+ land2(
1347
+ landings,
1348
+ item.event,
1349
+ "lossy",
1350
+ "user-text",
1351
+ "the summary is rendered as user-role text",
1352
+ item.escaped ? ESCAPED_NOTE : void 0,
1353
+ item.deferred ? DEFERRED_NOTE : void 0
1354
+ );
1355
+ break;
1356
+ }
1357
+ case "dropped":
1358
+ land2(landings, item.event, "dropped", "none", item.note);
1359
+ break;
1360
+ }
1361
+ }
1362
+ const { tools: _tools, ...passthrough } = input.requestOptions ?? {};
1363
+ const shownTools = (input.tools ?? []).filter((t) => t.deferLoading !== true);
1364
+ const body = {
1365
+ ...passthrough,
1366
+ model: model.id,
1367
+ messages,
1368
+ ...shownTools.length > 0 ? {
1369
+ tools: shownTools.map(
1370
+ (t) => ({
1371
+ type: "function",
1372
+ function: { name: t.name, description: t.description, parameters: t.inputSchema }
1373
+ })
1374
+ )
1375
+ } : {},
1376
+ stream: true,
1377
+ stream_options: { include_usage: true }
1378
+ };
1379
+ return { body, landings: orderLandings(input.events, landings) };
1380
+ }
1381
+ function assistantMessage(blocks, origin, target, reasoningContent, landings) {
1382
+ const texts = blocks.filter((b) => b.type === "text");
1383
+ const thinkings = blocks.filter((b) => b.type === "thinking");
1384
+ const calls = blocks.filter((b) => b.type === "tool_call");
1385
+ const foreign = foreignOrigin(origin, target);
1386
+ for (const t of texts) {
1387
+ if (texts.length > 1)
1388
+ land2(
1389
+ landings,
1390
+ t.event,
1391
+ "lossy",
1392
+ "merged-text",
1393
+ "multiple text segments of one turn are merged into a single string"
1394
+ );
1395
+ else land2(landings, t.event, "exact", "assistant-content");
1396
+ }
1397
+ const replayable = reasoningContent && !foreign;
1398
+ for (const t of thinkings) {
1399
+ if (replayable) land2(landings, t.event, "exact", "reasoning_content");
1400
+ else if (reasoningContent)
1401
+ land2(
1402
+ landings,
1403
+ t.event,
1404
+ "dropped",
1405
+ "none",
1406
+ `thinking from ${origin.provider}/${origin.api} is not filled back in for a model of another family`
1407
+ );
1408
+ else
1409
+ land2(
1410
+ landings,
1411
+ t.event,
1412
+ "dropped",
1413
+ "none",
1414
+ "Chat Completions has no landing for replaying thinking (no signature, no encrypted item)"
1415
+ );
1416
+ }
1417
+ for (const c of calls) land2(landings, c.event, "exact", "tool_calls");
1418
+ const msg = {
1419
+ role: "assistant",
1420
+ content: texts.length > 0 ? texts.map((t) => t.text).join("\n\n") : null
1421
+ };
1422
+ if (calls.length > 0) {
1423
+ msg.tool_calls = calls.map((c) => ({
1424
+ id: c.id,
1425
+ type: "function",
1426
+ function: { name: c.name, arguments: argumentsOf(c.args) }
1427
+ }));
1428
+ }
1429
+ if (reasoningContent) msg.reasoning_content = replayable ? thinkings.map((t) => t.text).join("\n") : "";
1430
+ return msg;
1431
+ }
1432
+
1433
+ // src/lowering.ts
1434
+ import {
1435
+ LoweringError as LoweringError2
1436
+ } from "@reinsjs/core";
1437
+
1438
+ // src/http.ts
1439
+ var HttpError = class extends Error {
1440
+ constructor(status, headers, body, url) {
1441
+ super(`${status} ${body}`);
1442
+ this.status = status;
1443
+ this.headers = headers;
1444
+ this.body = body;
1445
+ this.url = url;
1446
+ }
1447
+ status;
1448
+ headers;
1449
+ body;
1450
+ url;
1451
+ name = "HttpError";
1452
+ };
1453
+ var DEFAULT_TIMEOUT_MS = 6e5;
1454
+ function requestSignals(hostSignal, timeoutMs) {
1455
+ const timeout = AbortSignal.timeout(timeoutMs);
1456
+ const signal = hostSignal ? AbortSignal.any([hostSignal, timeout]) : timeout;
1457
+ return { signal, timedOut: () => timeout.aborted && !hostSignal?.aborted };
1458
+ }
1459
+ async function postJson(input) {
1460
+ const doFetch = input.fetch ?? globalThis.fetch;
1461
+ const res = await doFetch(input.url, {
1462
+ method: "POST",
1463
+ headers: { "content-type": "application/json", accept: "text/event-stream", ...input.headers },
1464
+ body: JSON.stringify(input.body),
1465
+ signal: input.signal
1466
+ });
1467
+ if (!res.ok) {
1468
+ const text = await res.text().catch(() => "");
1469
+ throw new HttpError(res.status, res.headers, text, input.url);
1470
+ }
1471
+ return res;
1472
+ }
1473
+
1474
+ // src/models.ts
1475
+ import { LoweringError } from "@reinsjs/core";
1476
+ var SUPPORTED_APIS = /* @__PURE__ */ new Set([
1477
+ "openai-chat",
1478
+ "anthropic-messages",
1479
+ "openai-responses"
1480
+ ]);
1481
+ var DEEPSEEK_BASE = "https://api.deepseek.com";
1482
+ var ANTHROPIC_BASE = "https://api.anthropic.com/v1";
1483
+ var anthropicModel = (id, cost, extra = {}) => ({
1484
+ provider: "anthropic",
1485
+ id,
1486
+ api: "anthropic-messages",
1487
+ baseUrl: ANTHROPIC_BASE,
1488
+ contextWindow: 1e6,
1489
+ maxOutputTokens: 128e3,
1490
+ reasoning: true,
1491
+ images: true,
1492
+ cost,
1493
+ ...extra
1494
+ });
1495
+ var HAIKU_4_5 = anthropicModel(
1496
+ "claude-haiku-4-5",
1497
+ { input: 1, output: 5, cacheRead: 0.1, cacheWrite: 1.25 },
1498
+ { contextWindow: 2e5, maxOutputTokens: 64e3 }
1499
+ );
1500
+ var OPENAI_BASE = "https://api.openai.com/v1";
1501
+ var DEEPSEEK_FLASH = {
1502
+ provider: "deepseek",
1503
+ id: "deepseek-flash",
1504
+ api: "openai-chat",
1505
+ baseUrl: DEEPSEEK_BASE,
1506
+ contextWindow: 1e6,
1507
+ maxOutputTokens: 384e3,
1508
+ reasoning: true,
1509
+ images: true,
1510
+ cost: { input: 0.3, output: 1.2, cacheRead: 6e-3, cacheWrite: 0 },
1511
+ chat: { reasoningContent: true }
1512
+ };
1513
+ var responsesModel = (id, cost, extra) => ({
1514
+ provider: "openai",
1515
+ id,
1516
+ api: "openai-responses",
1517
+ baseUrl: OPENAI_BASE,
1518
+ images: true,
1519
+ cost,
1520
+ ...extra
1521
+ });
1522
+ var GPT5 = { reasoning: true, contextWindow: 4e5, maxOutputTokens: 128e3 };
1523
+ var GPT5_LARGE = { reasoning: true, contextWindow: 272e3, maxOutputTokens: 128e3 };
1524
+ var O_SERIES = { reasoning: true, contextWindow: 2e5, maxOutputTokens: 1e5 };
1525
+ var BUILTIN_MODELS = [
1526
+ anthropicModel("claude-fable-5-1", { input: 10, output: 50, cacheRead: 0.25, cacheWrite: 12.5 }),
1527
+ anthropicModel("claude-opus-5", { input: 5, output: 25, cacheRead: 0.5, cacheWrite: 6.25 }),
1528
+ anthropicModel("claude-opus-4-8", { input: 5, output: 25, cacheRead: 0.5, cacheWrite: 6.25 }),
1529
+ anthropicModel("claude-sonnet-5", { input: 2, output: 10, cacheRead: 0.2, cacheWrite: 2.5 }),
1530
+ HAIKU_4_5,
1531
+ // 带日期后缀的正式 id 与短名同一份定义
1532
+ { ...HAIKU_4_5, id: "claude-haiku-4-5-20251001" },
1533
+ DEEPSEEK_FLASH,
1534
+ // 别名:模型列表里只有 deepseek-flash,请求带 deepseek-v4-flash 照样接受、响应报 deepseek-flash
1535
+ { ...DEEPSEEK_FLASH, id: "deepseek-v4-flash" },
1536
+ {
1537
+ provider: "deepseek",
1538
+ id: "deepseek-v4-pro",
1539
+ api: "openai-chat",
1540
+ baseUrl: DEEPSEEK_BASE,
1541
+ contextWindow: 1e6,
1542
+ maxOutputTokens: 384e3,
1543
+ reasoning: true,
1544
+ images: true,
1545
+ cost: { input: 1.32, output: 3.96, cacheRead: 0.044, cacheWrite: 0 },
1546
+ chat: { reasoningContent: true }
1547
+ },
1548
+ // ---- OpenAI Responses 线:OpenAI 官方的主协议,同 id 无协议解析时先取它;Chat 条目在后面
1549
+ responsesModel("gpt-5.5", { input: 5, output: 30, cacheRead: 0.5, cacheWrite: 0 }, GPT5_LARGE),
1550
+ responsesModel("gpt-5.4", { input: 2.5, output: 15, cacheRead: 0.25, cacheWrite: 0 }, GPT5_LARGE),
1551
+ responsesModel("gpt-5.4-mini", { input: 0.75, output: 4.5, cacheRead: 0.075, cacheWrite: 0 }, GPT5),
1552
+ responsesModel("gpt-5.2", { input: 1.75, output: 14, cacheRead: 0.175, cacheWrite: 0 }, GPT5),
1553
+ responsesModel("gpt-5.1", { input: 1.25, output: 10, cacheRead: 0.125, cacheWrite: 0 }, GPT5),
1554
+ responsesModel("gpt-5", { input: 1.25, output: 10, cacheRead: 0.125, cacheWrite: 0 }, GPT5),
1555
+ responsesModel("gpt-5-mini", { input: 0.25, output: 2, cacheRead: 0.025, cacheWrite: 0 }, GPT5),
1556
+ responsesModel("gpt-5-nano", { input: 0.05, output: 0.4, cacheRead: 5e-3, cacheWrite: 0 }, GPT5),
1557
+ responsesModel("o3", { input: 2, output: 8, cacheRead: 0.5, cacheWrite: 0 }, O_SERIES),
1558
+ responsesModel("o4-mini", { input: 1.1, output: 4.4, cacheRead: 0.275, cacheWrite: 0 }, O_SERIES),
1559
+ responsesModel(
1560
+ "gpt-4.1",
1561
+ { input: 2, output: 8, cacheRead: 0.5, cacheWrite: 0 },
1562
+ { reasoning: false, contextWindow: 1047576, maxOutputTokens: 32768 }
1563
+ ),
1564
+ responsesModel(
1565
+ "gpt-4.1-mini",
1566
+ { input: 0.4, output: 1.6, cacheRead: 0.1, cacheWrite: 0 },
1567
+ { reasoning: false, contextWindow: 1047576, maxOutputTokens: 32768 }
1568
+ ),
1569
+ responsesModel(
1570
+ "gpt-4o-mini",
1571
+ { input: 0.15, output: 0.6, cacheRead: 0.075, cacheWrite: 0 },
1572
+ { reasoning: false, contextWindow: 128e3, maxOutputTokens: 16384 }
1573
+ ),
1574
+ // ---- OpenAI Chat Completions 线的同款型号(openaiChat() 按协议精确取;无协议解析时上面的 Responses 条目优先)
1575
+ {
1576
+ provider: "openai",
1577
+ id: "gpt-4o-mini",
1578
+ api: "openai-chat",
1579
+ baseUrl: OPENAI_BASE,
1580
+ contextWindow: 128e3,
1581
+ maxOutputTokens: 16384,
1582
+ reasoning: false,
1583
+ images: true,
1584
+ cost: { input: 0.15, output: 0.6, cacheRead: 0.075, cacheWrite: 0 }
1585
+ },
1586
+ {
1587
+ provider: "openai",
1588
+ id: "gpt-4.1",
1589
+ api: "openai-chat",
1590
+ baseUrl: OPENAI_BASE,
1591
+ contextWindow: 1047576,
1592
+ maxOutputTokens: 32768,
1593
+ reasoning: false,
1594
+ images: true,
1595
+ cost: { input: 2, output: 8, cacheRead: 0.5, cacheWrite: 0 }
1596
+ },
1597
+ {
1598
+ provider: "openai",
1599
+ id: "gpt-4.1-mini",
1600
+ api: "openai-chat",
1601
+ baseUrl: OPENAI_BASE,
1602
+ contextWindow: 1047576,
1603
+ maxOutputTokens: 32768,
1604
+ reasoning: false,
1605
+ images: true,
1606
+ cost: { input: 0.4, output: 1.6, cacheRead: 0.1, cacheWrite: 0 }
1607
+ },
1608
+ {
1609
+ provider: "openai",
1610
+ id: "gpt-5-mini",
1611
+ api: "openai-chat",
1612
+ baseUrl: OPENAI_BASE,
1613
+ contextWindow: 4e5,
1614
+ maxOutputTokens: 128e3,
1615
+ reasoning: true,
1616
+ images: true,
1617
+ cost: { input: 0.25, output: 2, cacheRead: 0.025, cacheWrite: 0 }
1618
+ }
1619
+ ];
1620
+ function findBuiltin(provider, id, api) {
1621
+ return BUILTIN_MODELS.find(
1622
+ (m) => m.provider === provider && m.id === id && (api === void 0 || m.api === api)
1623
+ );
1624
+ }
1625
+ function resolveModel(ref, extra = []) {
1626
+ const model = extra.find((m) => m.provider === ref.provider && m.id === ref.id) ?? findBuiltin(ref.provider, ref.id);
1627
+ if (!model) {
1628
+ throw new LoweringError(
1629
+ "unsupported_model",
1630
+ `unknown model ${ref.provider}/${ref.id}; the built-in table is a minimal set, declare the rest through the models option (or use a factory function)`,
1631
+ { provider: ref.provider, id: ref.id }
1632
+ );
1633
+ }
1634
+ if (!SUPPORTED_APIS.has(model.api)) {
1635
+ throw new LoweringError(
1636
+ "unsupported_api",
1637
+ `model ${ref.provider}/${ref.id} speaks ${model.api}, but this implementation currently supports only ${[...SUPPORTED_APIS].join(" / ")}`,
1638
+ { api: model.api }
1639
+ );
1640
+ }
1641
+ return model;
1642
+ }
1643
+ function endpointOf(model) {
1644
+ const base = model.baseUrl.replace(/\/+$/, "");
1645
+ switch (model.api) {
1646
+ case "openai-chat":
1647
+ return `${base}/chat/completions`;
1648
+ case "anthropic-messages":
1649
+ return `${base}/messages`;
1650
+ case "openai-responses":
1651
+ return `${base}/responses`;
1652
+ }
1653
+ }
1654
+
1655
+ // src/responses/from-stream.ts
1656
+ function str2(v) {
1657
+ return typeof v === "string" ? v : void 0;
1658
+ }
1659
+ function num3(v) {
1660
+ return typeof v === "number" && Number.isFinite(v) ? v : void 0;
1661
+ }
1662
+ function parseArguments2(raw) {
1663
+ if (raw.trim() === "") return {};
1664
+ try {
1665
+ return JSON.parse(raw);
1666
+ } catch {
1667
+ return raw;
1668
+ }
1669
+ }
1670
+ function reasoningText(item, fallback) {
1671
+ if (!item) return fallback;
1672
+ const summary = (item.summary ?? []).map((s) => str2(s.text) ?? "").filter((t) => t.length > 0);
1673
+ if (summary.length > 0) return summary.join("\n\n");
1674
+ const content = (item.content ?? []).map((c) => str2(c.text) ?? "").filter((t) => t.length > 0);
1675
+ return content.length > 0 ? content.join("\n\n") : fallback;
1676
+ }
1677
+ function messageText(item, fallback) {
1678
+ if (!item?.content) return fallback;
1679
+ return item.content.map((c) => (c.type === "refusal" ? str2(c.refusal) : str2(c.text)) ?? "").join("");
1680
+ }
1681
+ var ResponsesAssembly = class {
1682
+ slots = /* @__PURE__ */ new Map();
1683
+ responseId;
1684
+ responseModel;
1685
+ status;
1686
+ incompleteReason;
1687
+ usage;
1688
+ error;
1689
+ sawTerminal = false;
1690
+ slotFor(index, item) {
1691
+ let slot = this.slots.get(index);
1692
+ if (slot) return slot;
1693
+ const type = str2(item.type) ?? "unknown";
1694
+ switch (type) {
1695
+ case "reasoning":
1696
+ slot = { kind: "reasoning", id: str2(item.id) ?? "", summary: "", item: void 0 };
1697
+ break;
1698
+ case "message":
1699
+ slot = { kind: "message", id: str2(item.id) ?? "", text: "", phase: str2(item.phase) };
1700
+ break;
1701
+ case "function_call":
1702
+ slot = {
1703
+ kind: "function_call",
1704
+ id: str2(item.id) ?? "",
1705
+ callId: str2(item.call_id) ?? "",
1706
+ name: str2(item.name) ?? "",
1707
+ args: str2(item.arguments) ?? ""
1708
+ };
1709
+ break;
1710
+ default:
1711
+ slot = { kind: "other", type };
1712
+ }
1713
+ this.slots.set(index, slot);
1714
+ return slot;
1715
+ }
1716
+ /** 项收尾:以厂商给的最终形状为准(reasoning 带 encrypted_content、message 带 id / phase、function_call 带完整 arguments) */
1717
+ finish(index, item) {
1718
+ const slot = this.slotFor(index, item);
1719
+ switch (slot.kind) {
1720
+ case "reasoning":
1721
+ slot.item = item;
1722
+ slot.id = str2(item.id) ?? slot.id;
1723
+ slot.summary = reasoningText(item, slot.summary);
1724
+ break;
1725
+ case "message":
1726
+ slot.id = str2(item.id) ?? slot.id;
1727
+ slot.phase = str2(item.phase) ?? slot.phase;
1728
+ slot.text = messageText(item, slot.text);
1729
+ break;
1730
+ case "function_call":
1731
+ slot.id = str2(item.id) ?? slot.id;
1732
+ slot.callId = str2(item.call_id) ?? slot.callId;
1733
+ slot.name = str2(item.name) ?? slot.name;
1734
+ slot.args = str2(item.arguments) ?? slot.args;
1735
+ break;
1736
+ case "other":
1737
+ break;
1738
+ }
1739
+ }
1740
+ /**
1741
+ * Azure 等上游可能只在 response.completed 的 output 里给 encrypted_content(output_item.done 里没有),
1742
+ * 按 id 回填,让 store:false 的多轮回放不断(pi-ai 同一处置)。
1743
+ */
1744
+ backfill(output) {
1745
+ if (!output) return;
1746
+ for (const item of output) {
1747
+ if (item.type !== "reasoning" || typeof item.encrypted_content !== "string") continue;
1748
+ for (const slot of this.slots.values()) {
1749
+ if (slot.kind !== "reasoning" || slot.id !== item.id) continue;
1750
+ if (!slot.item) slot.item = item;
1751
+ else if (typeof slot.item.encrypted_content !== "string")
1752
+ slot.item = { ...slot.item, encrypted_content: item.encrypted_content };
1753
+ }
1754
+ }
1755
+ }
1756
+ hasFunctionCall() {
1757
+ for (const s of this.slots.values()) if (s.kind === "function_call") return true;
1758
+ return false;
1759
+ }
1760
+ drafts(origin) {
1761
+ const out = [];
1762
+ for (const [, s] of [...this.slots.entries()].sort((x, y) => x[0] - y[0])) {
1763
+ const replay = { ...origin };
1764
+ switch (s.kind) {
1765
+ case "reasoning": {
1766
+ const encrypted = typeof s.item?.encrypted_content === "string" && s.item.encrypted_content.length > 0;
1767
+ if (!encrypted && s.summary.length === 0) break;
1768
+ if (encrypted) replay.thinkingSignature = JSON.stringify(s.item);
1769
+ out.push({ type: "core.model_thinking", actor: "model", payload: { text: s.summary }, replay });
1770
+ break;
1771
+ }
1772
+ case "message":
1773
+ if (s.text.length === 0) break;
1774
+ if (s.id) replay.textSignature = s.id;
1775
+ if (s.phase) replay.phase = s.phase;
1776
+ out.push({ type: "core.model_text", actor: "model", payload: { text: s.text }, replay });
1777
+ break;
1778
+ case "function_call":
1779
+ if (s.id) replay.itemId = s.id;
1780
+ out.push({
1781
+ type: "core.tool_call",
1782
+ actor: "model",
1783
+ payload: { toolCallId: s.callId, name: s.name, args: parseArguments2(s.args) },
1784
+ replay
1785
+ });
1786
+ break;
1787
+ case "other":
1788
+ break;
1789
+ }
1790
+ }
1791
+ return out;
1792
+ }
1793
+ };
1794
+ function usageOf3(u) {
1795
+ const total = num3(u?.input_tokens) ?? 0;
1796
+ const cached = num3(u?.input_tokens_details?.cached_tokens) ?? 0;
1797
+ const written = num3(u?.input_tokens_details?.cache_write_tokens) ?? 0;
1798
+ const usage = {
1799
+ input: Math.max(0, total - cached - written),
1800
+ output: num3(u?.output_tokens) ?? 0
1801
+ };
1802
+ if (cached > 0) usage.cacheRead = cached;
1803
+ if (written > 0) usage.cacheWrite = written;
1804
+ return usage;
1805
+ }
1806
+ function apply3(ev, a, ctx) {
1807
+ const type = str2(ev.type);
1808
+ const index = num3(ev.output_index);
1809
+ const slot = index === void 0 ? void 0 : a.slots.get(index);
1810
+ switch (type) {
1811
+ case "response.created":
1812
+ case "response.in_progress": {
1813
+ const id = str2(ev.response?.id);
1814
+ const model = str2(ev.response?.model);
1815
+ if (id) a.responseId = id;
1816
+ if (model) a.responseModel = model;
1817
+ break;
1818
+ }
1819
+ case "response.output_item.added":
1820
+ if (index !== void 0 && ev.item) a.slotFor(index, ev.item);
1821
+ break;
1822
+ case "response.reasoning_summary_part.added":
1823
+ if (slot?.kind === "reasoning" && slot.summary.length > 0 && index !== void 0) {
1824
+ slot.summary += "\n\n";
1825
+ ctx.onDelta?.({ kind: "thinking", index, delta: "\n\n" });
1826
+ }
1827
+ break;
1828
+ case "response.reasoning_summary_text.delta":
1829
+ case "response.reasoning_text.delta": {
1830
+ const d = str2(ev.delta);
1831
+ if (slot?.kind === "reasoning" && d && index !== void 0) {
1832
+ slot.summary += d;
1833
+ ctx.onDelta?.({ kind: "thinking", index, delta: d });
1834
+ }
1835
+ break;
1836
+ }
1837
+ case "response.output_text.delta":
1838
+ case "response.refusal.delta": {
1839
+ const d = str2(ev.delta);
1840
+ if (slot?.kind === "message" && d && index !== void 0) {
1841
+ slot.text += d;
1842
+ ctx.onDelta?.({ kind: "text", index, delta: d });
1843
+ }
1844
+ break;
1845
+ }
1846
+ case "response.function_call_arguments.delta": {
1847
+ const d = str2(ev.delta);
1848
+ if (slot?.kind === "function_call" && d && index !== void 0) {
1849
+ slot.args += d;
1850
+ ctx.onDelta?.({ kind: "tool_args", index, delta: d });
1851
+ }
1852
+ break;
1853
+ }
1854
+ case "response.function_call_arguments.done": {
1855
+ const full = str2(ev.arguments);
1856
+ if (slot?.kind === "function_call" && full !== void 0) slot.args = full;
1857
+ break;
1858
+ }
1859
+ case "response.output_item.done":
1860
+ if (index !== void 0 && ev.item) a.finish(index, ev.item);
1861
+ break;
1862
+ case "response.completed":
1863
+ case "response.incomplete": {
1864
+ a.sawTerminal = true;
1865
+ const r = ev.response;
1866
+ a.status = str2(r?.status) ?? (type === "response.completed" ? "completed" : "incomplete");
1867
+ a.incompleteReason = str2(r?.incomplete_details?.reason);
1868
+ if (r?.usage) a.usage = r.usage;
1869
+ const model = str2(r?.model);
1870
+ if (model) a.responseModel = model;
1871
+ a.backfill(r?.output);
1872
+ break;
1873
+ }
1874
+ case "response.failed": {
1875
+ a.sawTerminal = true;
1876
+ a.status = "failed";
1877
+ const e = ev.response?.error;
1878
+ a.error = {
1879
+ code: str2(e?.code) ?? "failed",
1880
+ message: str2(e?.message) ?? "response.failed carries no error detail"
1881
+ };
1882
+ if (ev.response?.usage) a.usage = ev.response.usage;
1883
+ break;
1884
+ }
1885
+ case "error":
1886
+ a.error = { code: str2(ev.code) ?? "error", message: str2(ev.message) ?? "" };
1887
+ break;
1888
+ default:
1889
+ break;
1890
+ }
1891
+ }
1892
+ function outcomeOf3(a, cost) {
1893
+ const usage = usageOf3(a.usage);
1894
+ const out = { stopReason: "stop", usage };
1895
+ const c = costOf(usage, cost);
1896
+ if (c !== void 0) out.costUsd = c;
1897
+ if (a.responseModel) out.responseModel = a.responseModel;
1898
+ if (a.error) {
1899
+ out.stopReason = "error";
1900
+ out.errorMessage = `${a.error.code}: ${a.error.message}`;
1901
+ return out;
1902
+ }
1903
+ if (!a.sawTerminal) {
1904
+ out.stopReason = "error";
1905
+ out.errorMessage = "stream ended before response.completed";
1906
+ return out;
1907
+ }
1908
+ if (a.hasFunctionCall()) {
1909
+ out.stopReason = "toolUse";
1910
+ return out;
1911
+ }
1912
+ switch (a.status) {
1913
+ case "completed":
1914
+ break;
1915
+ case "incomplete":
1916
+ if (a.incompleteReason === "max_output_tokens") out.stopReason = "length";
1917
+ else {
1918
+ out.stopReason = "error";
1919
+ out.errorMessage = a.incompleteReason === "content_filter" ? "status=incomplete (content_filter): the provider's content filter cut this output short" : `status=incomplete${a.incompleteReason ? ` (${a.incompleteReason})` : ""}: the provider did not finish`;
1920
+ }
1921
+ break;
1922
+ case "cancelled":
1923
+ case "failed":
1924
+ out.stopReason = "error";
1925
+ out.errorMessage = `status=${a.status}`;
1926
+ break;
1927
+ default:
1928
+ break;
1929
+ }
1930
+ return out;
1931
+ }
1932
+ async function* consumeResponsesStream(input, ctx = {}) {
1933
+ const a = new ResponsesAssembly();
1934
+ try {
1935
+ for await (const msg of input.messages) {
1936
+ if (msg.data === "[DONE]") break;
1937
+ let ev;
1938
+ try {
1939
+ ev = JSON.parse(msg.data);
1940
+ } catch {
1941
+ continue;
1942
+ }
1943
+ apply3(ev, a, ctx);
1944
+ if (a.error) break;
1945
+ }
1946
+ } catch (err) {
1947
+ yield* a.drafts(input.origin);
1948
+ const base = outcomeOf3(a, input.cost);
1949
+ if (input.signal?.aborted) return { ...base, stopReason: "aborted", errorMessage: "aborted by host" };
1950
+ if (input.timedOut?.())
1951
+ return {
1952
+ ...base,
1953
+ stopReason: "error",
1954
+ errorMessage: `request timed out after ${input.timeoutMs ?? "?"}ms (stream ended before response.completed)`
1955
+ };
1956
+ return { ...base, stopReason: "error", errorMessage: errorText3(err) };
1957
+ }
1958
+ yield* a.drafts(input.origin);
1959
+ return outcomeOf3(a, input.cost);
1960
+ }
1961
+ function errorText3(err) {
1962
+ if (err instanceof Error) {
1963
+ const cause = err.cause;
1964
+ const code = typeof cause?.code === "string" ? ` (${cause.code})` : "";
1965
+ return `${err.message}${code}`;
1966
+ }
1967
+ return String(err);
1968
+ }
1969
+
1970
+ // src/responses/to-request.ts
1971
+ import {
1972
+ renderToolReference as renderToolReference3
1973
+ } from "@reinsjs/core";
1974
+ var IMAGE_OMITTED3 = "[image omitted: this model does not accept images]";
1975
+ var ERROR_PREFIX2 = "[tool error]\n";
1976
+ var EMPTY_NOTE2 = "the content is empty, so no empty message is sent";
1977
+ var ENCRYPTED_INCLUDE = "reasoning.encrypted_content";
1978
+ function land3(out, e, kind, landing, ...notes) {
1979
+ const note = notes.filter((n) => Boolean(n)).join("; ");
1980
+ out.push(
1981
+ note ? { eventId: e.id, type: e.type, kind, landing, note } : { eventId: e.id, type: e.type, kind, landing }
1982
+ );
1983
+ }
1984
+ function contentOf(parts, images) {
1985
+ let imagesDropped = false;
1986
+ const content = [];
1987
+ for (const p of parts) {
1988
+ if (p.type === "text") {
1989
+ if (p.text.length > 0) content.push({ type: "input_text", text: p.text });
1990
+ } else if (p.type === "tool_reference") {
1991
+ content.push({ type: "input_text", text: renderToolReference3(p) });
1992
+ } else if (images) {
1993
+ content.push({ type: "input_image", image_url: `data:${p.mime};base64,${p.data}`, detail: "auto" });
1994
+ } else {
1995
+ imagesDropped = true;
1996
+ content.push({ type: "input_text", text: IMAGE_OMITTED3 });
1997
+ }
1998
+ }
1999
+ return { content, imagesDropped };
2000
+ }
2001
+ function argumentsOf2(args) {
2002
+ return typeof args === "string" ? args : JSON.stringify(args ?? {});
2003
+ }
2004
+ function textItemId(replay) {
2005
+ const sig = replay.textSignature;
2006
+ if (typeof sig !== "string" || sig.length === 0) return {};
2007
+ const ownPhase = typeof replay.phase === "string" && replay.phase.length > 0 ? { phase: replay.phase } : {};
2008
+ if (sig.startsWith("{")) {
2009
+ try {
2010
+ const parsed = JSON.parse(sig);
2011
+ if (typeof parsed.id === "string" && parsed.id.length > 0) {
2012
+ return typeof parsed.phase === "string" ? { id: parsed.id, phase: parsed.phase } : { id: parsed.id };
2013
+ }
2014
+ } catch {
2015
+ }
2016
+ }
2017
+ return sig.length > 64 ? {} : { id: sig, ...ownPhase };
2018
+ }
2019
+ function reasoningItemOf(replay) {
2020
+ const sig = replay.thinkingSignature;
2021
+ if (typeof sig !== "string" || !sig.startsWith("{")) return void 0;
2022
+ try {
2023
+ const item = JSON.parse(sig);
2024
+ if (typeof item.encrypted_content !== "string" || item.encrypted_content.length === 0) return void 0;
2025
+ return item;
2026
+ } catch {
2027
+ return void 0;
2028
+ }
2029
+ }
2030
+ function encodeResponsesRequest(input) {
2031
+ const { model, capabilities } = input;
2032
+ const target = { provider: model.provider, api: model.api, model: model.id };
2033
+ const dialect = model.responses ?? {};
2034
+ const systemRole = dialect.systemRole ?? (model.reasoning ? "developer" : "system");
2035
+ const items = [];
2036
+ const landings = [];
2037
+ let textCounter = 0;
2038
+ if (input.systemPrompt)
2039
+ items.push({ role: systemRole, content: [{ type: "input_text", text: input.systemPrompt }] });
2040
+ for (const item of input.ir) {
2041
+ switch (item.kind) {
2042
+ case "user": {
2043
+ const c = contentOf(item.parts, capabilities.images);
2044
+ if (c.content.length === 0) {
2045
+ land3(landings, item.event, "dropped", "none", EMPTY_NOTE2);
2046
+ break;
2047
+ }
2048
+ items.push({ role: "user", content: c.content });
2049
+ const lossy4 = item.deferred || item.escaped || c.imagesDropped;
2050
+ land3(
2051
+ landings,
2052
+ item.event,
2053
+ lossy4 ? "lossy" : "exact",
2054
+ "user",
2055
+ item.deferred ? `the user message sits between a tool call and its results; ${DEFERRED_NOTE}` : void 0,
2056
+ item.escaped ? ESCAPED_NOTE : void 0,
2057
+ c.imagesDropped ? "the model takes no images, so they are replaced with placeholder text" : void 0
2058
+ );
2059
+ break;
2060
+ }
2061
+ case "tool_result": {
2062
+ const c = contentOf(item.parts, capabilities.images);
2063
+ const hasImage = c.content.some((b) => b.type === "input_image");
2064
+ const text = c.content.filter((b) => b.type === "input_text").map((b) => b.text).join("\n");
2065
+ const prefixed = item.isError ? ERROR_PREFIX2 + text : text;
2066
+ const output = hasImage ? [
2067
+ ...prefixed.length > 0 ? [{ type: "input_text", text: prefixed }] : [],
2068
+ ...c.content.filter((b) => b.type === "input_image")
2069
+ ] : prefixed;
2070
+ items.push({ type: "function_call_output", call_id: item.toolCallId, output });
2071
+ const lossy4 = item.escaped || item.isError || c.imagesDropped;
2072
+ land3(
2073
+ landings,
2074
+ item.event,
2075
+ lossy4 ? "lossy" : "exact",
2076
+ "function_call_output",
2077
+ item.isError ? "the Responses function_call_output has no error flag, so isError is expressed with a [tool error] prefix" : void 0,
2078
+ c.imagesDropped ? "the model takes no images, so they are replaced with placeholder text" : void 0,
2079
+ item.escaped ? ESCAPED_NOTE : void 0
2080
+ );
2081
+ break;
2082
+ }
2083
+ case "compaction": {
2084
+ items.push({ role: "user", content: [{ type: "input_text", text: framedSummary(item.text) }] });
2085
+ land3(
2086
+ landings,
2087
+ item.event,
2088
+ "lossy",
2089
+ "user-text",
2090
+ "the summary is rendered as user-role text",
2091
+ item.escaped ? ESCAPED_NOTE : void 0,
2092
+ item.deferred ? DEFERRED_NOTE : void 0
2093
+ );
2094
+ break;
2095
+ }
2096
+ case "system_note": {
2097
+ if (capabilities.midConversationSystem) {
2098
+ items.push({ role: systemRole, content: [{ type: "input_text", text: item.text }] });
2099
+ land3(
2100
+ landings,
2101
+ item.event,
2102
+ item.escaped ? "lossy" : "exact",
2103
+ systemRole,
2104
+ item.escaped ? ESCAPED_NOTE : void 0,
2105
+ item.deferred ? DEFERRED_NOTE : void 0
2106
+ );
2107
+ } else {
2108
+ items.push({
2109
+ role: "user",
2110
+ content: [{ type: "input_text", text: framedSystemNote(item.noteKind, item.text) }]
2111
+ });
2112
+ land3(
2113
+ landings,
2114
+ item.event,
2115
+ "lossy",
2116
+ "user-role",
2117
+ "the host declared that this upstream does not support mid-conversation system, so it is wrapped in a <system_note> tag and sent with the user role",
2118
+ item.escaped ? ESCAPED_NOTE : void 0,
2119
+ item.deferred ? DEFERRED_NOTE : void 0
2120
+ );
2121
+ }
2122
+ break;
2123
+ }
2124
+ case "assistant": {
2125
+ const out = assistantItems(
2126
+ item.blocks,
2127
+ item.origin,
2128
+ target,
2129
+ landings,
2130
+ () => `msg_reins_${++textCounter}`
2131
+ );
2132
+ items.push(...out);
2133
+ break;
2134
+ }
2135
+ case "dropped":
2136
+ land3(landings, item.event, "dropped", "none", item.note);
2137
+ break;
2138
+ }
2139
+ }
2140
+ const ro = input.requestOptions ?? {};
2141
+ const { input: _input, tools: _tools, previous_response_id: _prev, ...passthrough } = ro;
2142
+ const body = {
2143
+ ...passthrough,
2144
+ model: model.id,
2145
+ input: items,
2146
+ stream: true,
2147
+ store: false
2148
+ };
2149
+ const shownTools = (input.tools ?? []).filter((t) => t.deferLoading !== true);
2150
+ if (shownTools.length > 0) {
2151
+ body.tools = shownTools.map(
2152
+ (t) => ({
2153
+ type: "function",
2154
+ name: t.name,
2155
+ description: t.description,
2156
+ parameters: t.inputSchema,
2157
+ strict: false
2158
+ })
2159
+ );
2160
+ }
2161
+ if (model.reasoning && dialect.encryptedReasoning !== false) {
2162
+ const hostInclude = Array.isArray(ro.include) ? ro.include.filter((x) => typeof x === "string") : [];
2163
+ body.include = hostInclude.includes(ENCRYPTED_INCLUDE) ? hostInclude : [...hostInclude, ENCRYPTED_INCLUDE];
2164
+ }
2165
+ return { body, landings: orderLandings(input.events, landings) };
2166
+ }
2167
+ function assistantItems(blocks, origin, target, landings, nextTextId) {
2168
+ const out = [];
2169
+ const foreign = foreignOrigin(origin, target);
2170
+ const sameModel = !foreign && origin.model === target.model;
2171
+ for (const b of blocks) {
2172
+ switch (b.type) {
2173
+ case "text": {
2174
+ if (b.text.length === 0) {
2175
+ land3(landings, b.event, "dropped", "none", EMPTY_NOTE2);
2176
+ break;
2177
+ }
2178
+ const own = foreign ? {} : textItemId(b.replay);
2179
+ const id = own.id ?? nextTextId();
2180
+ out.push({
2181
+ type: "message",
2182
+ role: "assistant",
2183
+ id,
2184
+ status: "completed",
2185
+ content: [{ type: "output_text", text: b.text, annotations: [] }],
2186
+ ...own.phase !== void 0 ? { phase: own.phase } : {}
2187
+ });
2188
+ land3(
2189
+ landings,
2190
+ b.event,
2191
+ "exact",
2192
+ "assistant-message",
2193
+ own.id === void 0 ? "the item id is supplied by this package" : void 0
2194
+ );
2195
+ break;
2196
+ }
2197
+ case "thinking": {
2198
+ if (foreign) {
2199
+ land3(
2200
+ landings,
2201
+ b.event,
2202
+ "dropped",
2203
+ "none",
2204
+ `reasoning from ${origin.provider}/${origin.api} has no encrypted item of its own, so it is not replayed`
2205
+ );
2206
+ break;
2207
+ }
2208
+ if (!sameModel) {
2209
+ land3(
2210
+ landings,
2211
+ b.event,
2212
+ "dropped",
2213
+ "none",
2214
+ `the reasoning item comes from ${origin.model}, but this request targets ${target.model}; encrypted reasoning is only valid for the model that produced it, so it is not replayed`
2215
+ );
2216
+ break;
2217
+ }
2218
+ const item = reasoningItemOf(b.replay);
2219
+ if (!item) {
2220
+ land3(
2221
+ landings,
2222
+ b.event,
2223
+ "dropped",
2224
+ "none",
2225
+ "the reasoning item has no encrypted_content (it was not requested, or the stream broke), so it cannot be replayed under store:false"
2226
+ );
2227
+ break;
2228
+ }
2229
+ out.push({ ...item, type: "reasoning" });
2230
+ land3(landings, b.event, "exact", "reasoning-item");
2231
+ break;
2232
+ }
2233
+ case "tool_call": {
2234
+ const itemId = sameModel && typeof b.replay.itemId === "string" ? b.replay.itemId : void 0;
2235
+ out.push({
2236
+ type: "function_call",
2237
+ ...itemId !== void 0 ? { id: itemId } : {},
2238
+ call_id: b.id,
2239
+ name: b.name,
2240
+ arguments: argumentsOf2(b.args)
2241
+ });
2242
+ land3(
2243
+ landings,
2244
+ b.event,
2245
+ "exact",
2246
+ "function_call",
2247
+ itemId === void 0 && typeof b.replay.itemId === "string" ? "the model changed, so the fc_ item id is not carried back" : void 0
2248
+ );
2249
+ break;
2250
+ }
2251
+ }
2252
+ }
2253
+ return out;
2254
+ }
2255
+
2256
+ // src/sse.ts
2257
+ async function* parseSse(body) {
2258
+ const reader = body.getReader();
2259
+ const decoder = new TextDecoder();
2260
+ let buffer = "";
2261
+ let event;
2262
+ let id;
2263
+ const data = [];
2264
+ const flush = () => {
2265
+ const msg = { data: data.join("\n") };
2266
+ if (event !== void 0) msg.event = event;
2267
+ if (id !== void 0) msg.id = id;
2268
+ const empty = data.length === 0 && event === void 0;
2269
+ event = void 0;
2270
+ id = void 0;
2271
+ data.length = 0;
2272
+ return empty ? void 0 : msg;
2273
+ };
2274
+ const line = (raw) => {
2275
+ if (raw === "") return flush();
2276
+ if (raw.startsWith(":")) return void 0;
2277
+ const colon = raw.indexOf(":");
2278
+ const field = colon === -1 ? raw : raw.slice(0, colon);
2279
+ let value = colon === -1 ? "" : raw.slice(colon + 1);
2280
+ if (value.startsWith(" ")) value = value.slice(1);
2281
+ switch (field) {
2282
+ case "data":
2283
+ data.push(value);
2284
+ break;
2285
+ case "event":
2286
+ event = value;
2287
+ break;
2288
+ case "id":
2289
+ id = value;
2290
+ break;
2291
+ default:
2292
+ break;
2293
+ }
2294
+ return void 0;
2295
+ };
2296
+ try {
2297
+ while (true) {
2298
+ const { done, value } = await reader.read();
2299
+ buffer += done ? decoder.decode() : decoder.decode(value, { stream: true });
2300
+ let start = 0;
2301
+ while (true) {
2302
+ const nl = buffer.indexOf("\n", start);
2303
+ const cr = buffer.indexOf("\r", start);
2304
+ let end = -1;
2305
+ let skip = 1;
2306
+ if (nl === -1 && cr === -1) break;
2307
+ if (cr !== -1 && (nl === -1 || cr < nl)) {
2308
+ end = cr;
2309
+ if (cr === buffer.length - 1 && !done) break;
2310
+ if (buffer[cr + 1] === "\n") skip = 2;
2311
+ } else end = nl;
2312
+ const msg = line(buffer.slice(start, end));
2313
+ if (msg) yield msg;
2314
+ start = end + skip;
2315
+ }
2316
+ buffer = buffer.slice(start);
2317
+ if (done) break;
2318
+ }
2319
+ if (buffer !== "") {
2320
+ const msg = line(buffer);
2321
+ if (msg) yield msg;
2322
+ }
2323
+ const tail = flush();
2324
+ if (tail) yield tail;
2325
+ } finally {
2326
+ reader.releaseLock();
2327
+ }
2328
+ }
2329
+
2330
+ // src/lowering.ts
2331
+ var ANTHROPIC_VERSION = "2023-06-01";
2332
+ var FetchLowering = class {
2333
+ constructor(opts) {
2334
+ this.opts = opts;
2335
+ this.extraModels = opts.models ?? [];
2336
+ this.timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
2337
+ }
2338
+ opts;
2339
+ extraModels;
2340
+ timeoutMs;
2341
+ capabilities(ref) {
2342
+ return capabilitiesOf(resolveModel(ref, this.extraModels));
2343
+ }
2344
+ toRequest(input) {
2345
+ const model = resolveModel(input.model, this.extraModels);
2346
+ const capabilities = capabilitiesOf(model);
2347
+ const ir = eventsToIr({
2348
+ events: input.events,
2349
+ target: { provider: model.provider, api: model.api, model: model.id },
2350
+ ...this.opts.trustMarkers !== void 0 ? { trustMarkers: this.opts.trustMarkers } : {}
2351
+ });
2352
+ const requestOptions = this.opts.requestOptions?.(input.model);
2353
+ const encodeInput = {
2354
+ ir,
2355
+ events: input.events,
2356
+ model,
2357
+ capabilities,
2358
+ ...input.tools ? { tools: input.tools } : {},
2359
+ ...input.systemPrompt !== void 0 ? { systemPrompt: input.systemPrompt } : {},
2360
+ ...requestOptions ? { requestOptions } : {}
2361
+ };
2362
+ switch (model.api) {
2363
+ case "openai-chat": {
2364
+ const { body, landings } = encodeChatRequest(encodeInput);
2365
+ return { model: input.model, capabilities, landings, payload: { api: model.api, body } };
2366
+ }
2367
+ case "anthropic-messages": {
2368
+ const { body, landings } = encodeAnthropicRequest(encodeInput);
2369
+ return { model: input.model, capabilities, landings, payload: { api: model.api, body } };
2370
+ }
2371
+ case "openai-responses": {
2372
+ const { body, landings } = encodeResponsesRequest(encodeInput);
2373
+ return { model: input.model, capabilities, landings, payload: { api: model.api, body } };
2374
+ }
2375
+ default:
2376
+ throw new LoweringError2("unsupported_api", `unsupported wire protocol ${model.api}`, {
2377
+ api: model.api
2378
+ });
2379
+ }
2380
+ }
2381
+ async *stream(req, ctx = {}) {
2382
+ const model = resolveModel(req.model, this.extraModels);
2383
+ const headers = this.headersFor(model);
2384
+ const signals = requestSignals(ctx.signal, this.timeoutMs);
2385
+ const res = await postJson({
2386
+ url: endpointOf(model),
2387
+ headers,
2388
+ body: req.payload.body,
2389
+ signal: signals.signal,
2390
+ ...this.opts.fetch ? { fetch: this.opts.fetch } : {}
2391
+ });
2392
+ if (!res.body) {
2393
+ return {
2394
+ stopReason: "error",
2395
+ usage: { input: 0, output: 0 },
2396
+ errorMessage: "the response has no body"
2397
+ };
2398
+ }
2399
+ const streamInput = {
2400
+ messages: parseSse(res.body),
2401
+ origin: { provider: model.provider, api: model.api, model: model.id },
2402
+ ...model.cost ? { cost: model.cost } : {},
2403
+ ...ctx.signal ? { signal: ctx.signal } : {},
2404
+ timedOut: signals.timedOut,
2405
+ timeoutMs: this.timeoutMs
2406
+ };
2407
+ switch (req.payload.api) {
2408
+ case "openai-chat":
2409
+ return yield* consumeChatStream(streamInput, ctx);
2410
+ case "anthropic-messages":
2411
+ return yield* consumeAnthropicStream(streamInput, ctx);
2412
+ case "openai-responses":
2413
+ return yield* consumeResponsesStream(streamInput, ctx);
2414
+ default:
2415
+ throw new LoweringError2("unsupported_api", `unsupported wire protocol ${req.payload.api}`, {
2416
+ api: req.payload.api
2417
+ });
2418
+ }
2419
+ }
2420
+ /**
2421
+ * 鉴权头按协议缺省(Chat / Responses bearer、Anthropic x-api-key),模型声明 auth:"none" 时凭证由 headers 自带。
2422
+ * Anthropic 线固定带 `anthropic-version`,`anthropic-beta` 只在模型声明了 betas 时带(F0 结论:用到才带);模型级 headers 最后盖。
2423
+ */
2424
+ headersFor(model) {
2425
+ const protocol = {};
2426
+ if (model.api === "anthropic-messages") {
2427
+ protocol["anthropic-version"] = ANTHROPIC_VERSION;
2428
+ const betas = model.anthropic?.betas;
2429
+ if (betas && betas.length > 0) protocol["anthropic-beta"] = betas.join(",");
2430
+ }
2431
+ const headers = { ...this.opts.headers, ...protocol, ...model.headers };
2432
+ const auth = model.auth ?? (model.api === "anthropic-messages" ? "x-api-key" : "bearer");
2433
+ if (auth === "none") return headers;
2434
+ const apiKey = this.opts.apiKey(model.provider);
2435
+ if (!apiKey) {
2436
+ throw new LoweringError2("missing_api_key", `no API key configured for ${model.provider}`, {
2437
+ provider: model.provider
2438
+ });
2439
+ }
2440
+ if (auth === "bearer") headers.authorization = `Bearer ${apiKey}`;
2441
+ else headers["x-api-key"] = apiKey;
2442
+ return headers;
2443
+ }
2444
+ };
2445
+
2446
+ // src/factories.ts
2447
+ var DEFAULTS = { contextWindow: 128e3, maxOutputTokens: 16384, reasoning: false, images: false };
2448
+ function defined(o) {
2449
+ return Object.fromEntries(Object.entries(o).filter(([, v]) => v !== void 0));
2450
+ }
2451
+ function definitionOf(provider, id, api, opts) {
2452
+ const builtin = findBuiltin(provider, id, api);
2453
+ const base = builtin ?? { provider, id, api, baseUrl: opts.baseUrl ?? "", ...DEFAULTS };
2454
+ if (!builtin && !opts.baseUrl) {
2455
+ throw new RangeError(`model ${provider}/${id} is not in the built-in table, so baseUrl is required`);
2456
+ }
2457
+ const chat = base.chat || opts.chat ? { ...base.chat, ...defined(opts.chat ?? {}) } : void 0;
2458
+ const anthropic2 = base.anthropic || opts.anthropic ? { ...base.anthropic, ...defined(opts.anthropic ?? {}) } : void 0;
2459
+ const responses = base.responses || opts.responses ? { ...base.responses, ...defined(opts.responses ?? {}) } : void 0;
2460
+ return {
2461
+ ...base,
2462
+ ...defined({
2463
+ baseUrl: opts.baseUrl,
2464
+ contextWindow: opts.contextWindow,
2465
+ maxOutputTokens: opts.maxOutputTokens,
2466
+ reasoning: opts.reasoning,
2467
+ images: opts.images,
2468
+ cost: opts.cost,
2469
+ midConversationSystem: opts.midConversationSystem,
2470
+ auth: opts.auth,
2471
+ headers: opts.headers
2472
+ }),
2473
+ ...chat ? { chat } : {},
2474
+ ...anthropic2 ? { anthropic: anthropic2 } : {},
2475
+ ...responses ? { responses } : {}
2476
+ };
2477
+ }
2478
+ function bound(provider, id, model, opts) {
2479
+ const loweringOpts = {
2480
+ apiKey: (p) => p === provider ? opts.apiKey : void 0,
2481
+ models: [model],
2482
+ ...opts.requestOptions ? { requestOptions: () => opts.requestOptions ?? {} } : {},
2483
+ ...opts.fetch ? { fetch: opts.fetch } : {},
2484
+ ...opts.timeoutMs !== void 0 ? { timeoutMs: opts.timeoutMs } : {},
2485
+ ...opts.trustMarkers !== void 0 ? { trustMarkers: opts.trustMarkers } : {}
2486
+ };
2487
+ return { model: { provider, id }, lowering: new FetchLowering(loweringOpts) };
2488
+ }
2489
+ function chatCompletions(id, opts) {
2490
+ return bound(opts.provider, id, definitionOf(opts.provider, id, "openai-chat", opts), opts);
2491
+ }
2492
+ function deepseek(id, opts) {
2493
+ return chatCompletions(id, {
2494
+ provider: "deepseek",
2495
+ baseUrl: "https://api.deepseek.com",
2496
+ reasoning: true,
2497
+ images: true,
2498
+ ...opts,
2499
+ chat: { reasoningContent: true, ...opts.chat }
2500
+ });
2501
+ }
2502
+ function openaiChat(id, opts) {
2503
+ return chatCompletions(id, { provider: "openai", baseUrl: "https://api.openai.com/v1", ...opts });
2504
+ }
2505
+ function openaiResponses(id, opts) {
2506
+ return bound(opts.provider, id, definitionOf(opts.provider, id, "openai-responses", opts), opts);
2507
+ }
2508
+ function openai(id, opts) {
2509
+ const unknown = findBuiltin("openai", id, "openai-responses") === void 0;
2510
+ return openaiResponses(id, {
2511
+ provider: "openai",
2512
+ baseUrl: "https://api.openai.com/v1",
2513
+ ...unknown ? { reasoning: true, images: true } : {},
2514
+ ...opts
2515
+ });
2516
+ }
2517
+ function anthropicMessages(id, opts) {
2518
+ return bound(opts.provider, id, definitionOf(opts.provider, id, "anthropic-messages", opts), opts);
2519
+ }
2520
+ function anthropic(id, opts) {
2521
+ return anthropicMessages(id, {
2522
+ provider: "anthropic",
2523
+ baseUrl: "https://api.anthropic.com/v1",
2524
+ reasoning: true,
2525
+ images: true,
2526
+ ...opts
2527
+ });
2528
+ }
2529
+
2530
+ // src/responses/loss-matrix.ts
2531
+ var exact3 = (landing, note, when) => {
2532
+ const spec = { kind: "exact", landing };
2533
+ if (note) spec.note = note;
2534
+ if (when) spec.when = when;
2535
+ return spec;
2536
+ };
2537
+ var lossy3 = (landing, note, when) => when ? { kind: "lossy", landing, note, when } : { kind: "lossy", landing, note };
2538
+ var dropped3 = (landing, note, when) => when ? { kind: "dropped", landing, note, when } : { kind: "dropped", landing, note };
2539
+ var RESPONSES_LOSS_MATRIX = {
2540
+ "core.user_message": [
2541
+ exact3("user", "a user message in input, as input_text / input_image blocks"),
2542
+ lossy3(
2543
+ "user",
2544
+ "moved after that batch of tool results (the order changes), untrusted content escaped, or images replaced with placeholder text because the model takes none",
2545
+ "the user speaks up before all tool results are in / the content holds a </untrusted / it carries images the model does not take"
2546
+ ),
2547
+ dropped3(
2548
+ "none",
2549
+ "the content is empty, so no empty message is sent",
2550
+ "the user message has no non-empty content"
2551
+ )
2552
+ ],
2553
+ "core.model_text": [
2554
+ exact3(
2555
+ "assistant-message",
2556
+ "a type:message assistant output item; each text segment of a turn becomes its own item; the item id is replayed when it comes from the same model, otherwise a new one is made up"
2557
+ ),
2558
+ dropped3("none", "empty text is not sent", "the text is an empty string")
2559
+ ],
2560
+ "core.model_thinking": [
2561
+ exact3(
2562
+ "reasoning-item",
2563
+ "the whole reasoning item (including encrypted_content) is replayed verbatim",
2564
+ "replay.thinkingSignature is a reasoning item with encrypted_content and comes from the same source (provider + api + model)"
2565
+ ),
2566
+ dropped3(
2567
+ "none",
2568
+ "reasoning with no encrypted_content (the encrypted item was not requested, or the stream broke), from another source, or from another model of the same family (encrypted reasoning is only valid for the model that produced it) cannot be replayed, and is not downgraded to text"
2569
+ )
2570
+ ],
2571
+ "core.tool_call": [
2572
+ exact3(
2573
+ "function_call",
2574
+ "arguments is a JSON string, and non-object arguments are serialized as they are; the fc_ item id is replayed only for the same model (to stay clear of the pairing check)"
2575
+ )
2576
+ ],
2577
+ "core.tool_result": [
2578
+ exact3(
2579
+ "function_call_output",
2580
+ "matched by call_id; output is a string for text, or an array of content blocks when images are present; tool reference parts are expanded into text (Responses has no deferred-loading landing)"
2581
+ ),
2582
+ lossy3(
2583
+ "function_call_output",
2584
+ "isError is expressed with a [tool error] prefix (the protocol has no error flag), or untrusted content is escaped, or images are replaced with placeholder text because the model takes none"
2585
+ )
2586
+ ],
2587
+ "core.system_note": [
2588
+ exact3(
2589
+ "developer",
2590
+ "a developer message anywhere in input",
2591
+ "a reasoning model (or responses.systemRole = developer)"
2592
+ ),
2593
+ exact3(
2594
+ "system",
2595
+ "a system message anywhere in input",
2596
+ "a non-reasoning model (or responses.systemRole = system)"
2597
+ ),
2598
+ lossy3(
2599
+ "developer",
2600
+ "a developer message, with the early close inside untrusted content escaped",
2601
+ "a note the host marked untrusted holds a </untrusted"
2602
+ ),
2603
+ lossy3(
2604
+ "system",
2605
+ "a system message, with the early close inside untrusted content escaped",
2606
+ "same as above"
2607
+ ),
2608
+ lossy3(
2609
+ "user-role",
2610
+ "wrapped in a <system_note> tag and sent with the user role",
2611
+ "the host declared midConversationSystem: false for an unusual upstream"
2612
+ )
2613
+ ],
2614
+ "core.compaction": [
2615
+ lossy3(
2616
+ "user-text",
2617
+ "the summary is rendered as user-role text, so the model cannot tell it apart from the user's own words"
2618
+ )
2619
+ ],
2620
+ ...NOT_SENT
2621
+ };
2622
+
2623
+ // src/index.ts
2624
+ var LOSS_MATRIX = {
2625
+ "openai-chat": CHAT_LOSS_MATRIX,
2626
+ "anthropic-messages": ANTHROPIC_LOSS_MATRIX,
2627
+ "openai-responses": RESPONSES_LOSS_MATRIX
2628
+ };
2629
+ function declaredLandings(api, type) {
2630
+ const table = LOSS_MATRIX[api];
2631
+ if (!table) return [];
2632
+ return table[type] ?? (type.startsWith("ext.") ? table["ext.*"] ?? [] : []);
2633
+ }
2634
+ export {
2635
+ ANTHROPIC_LOSS_MATRIX,
2636
+ ANTHROPIC_VERSION,
2637
+ BUILTIN_MODELS,
2638
+ CHAT_LOSS_MATRIX,
2639
+ DEFAULT_TIMEOUT_MS,
2640
+ FetchLowering,
2641
+ HttpError,
2642
+ LOSS_MATRIX,
2643
+ MAX_ANTHROPIC_BREAKPOINTS,
2644
+ RESPONSES_LOSS_MATRIX,
2645
+ SUPPORTED_APIS,
2646
+ anthropic,
2647
+ anthropicMessages,
2648
+ usageOf as anthropicUsageOf,
2649
+ capabilitiesOf,
2650
+ chatCompletions,
2651
+ usageOf2 as chatUsageOf,
2652
+ consumeAnthropicStream,
2653
+ consumeChatStream,
2654
+ consumeResponsesStream,
2655
+ costOf,
2656
+ declaredLandings,
2657
+ deepseek,
2658
+ definitionOf,
2659
+ encodeAnthropicRequest,
2660
+ encodeChatRequest,
2661
+ encodeResponsesRequest,
2662
+ endpointOf,
2663
+ eventsToIr,
2664
+ findBuiltin,
2665
+ foreignOrigin,
2666
+ openai,
2667
+ openaiChat,
2668
+ openaiResponses,
2669
+ orderLandings,
2670
+ parseSse,
2671
+ postJson,
2672
+ reasoningItemOf,
2673
+ requestSignals,
2674
+ resolveModel,
2675
+ usageOf3 as responsesUsageOf,
2676
+ sameOrigin
2677
+ };
2678
+ //# sourceMappingURL=index.js.map