opencode-zen-freex 0.2.16 → 0.2.18
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/MAINTAINING.md +33 -0
- package/README.md +2 -2
- package/limits.mjs +7 -1
- package/models.mjs +105 -8
- package/package.json +1 -1
- package/provider.mjs +254 -17
- package/stream.mjs +234 -55
- package/tool-response.mjs +339 -106
package/stream.mjs
CHANGED
|
@@ -94,6 +94,13 @@ function carries(snapshot, accumulated, fields) {
|
|
|
94
94
|
return weight(snapshot, fields) > 0 || weight(accumulated, fields) === 0;
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
+
// Whether a value can be read as a snapshot of something the stream
|
|
98
|
+
// accumulated. A snapshot is the upstream's own view of an item, a block or a
|
|
99
|
+
// part; a null, a string or a list is not one, and stands in for nothing.
|
|
100
|
+
function isSnapshotObject(value) {
|
|
101
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
102
|
+
}
|
|
103
|
+
|
|
97
104
|
function chatAccumulator() {
|
|
98
105
|
const body = { object: "chat.completion", choices: [] };
|
|
99
106
|
const choices = new Map();
|
|
@@ -184,67 +191,178 @@ function chatAccumulator() {
|
|
|
184
191
|
function responsesAccumulator() {
|
|
185
192
|
let body = { object: "response", output: [] };
|
|
186
193
|
let final;
|
|
187
|
-
|
|
194
|
+
// The items a reply holds, in the order the stream opened them, keyed by the
|
|
195
|
+
// order itself. The key is not the item's output_index: an index the
|
|
196
|
+
// upstream states and an index this file has to infer are two different
|
|
197
|
+
// things, and while they shared one key space a frame that stated
|
|
198
|
+
// `output_index: 0` landed on the item an earlier frame had inferred into
|
|
199
|
+
// slot 0 — `keep` read the second item as a newer view of the first and the
|
|
200
|
+
// reply answered 200 with one item of the two, wearing the first one's id.
|
|
201
|
+
// One key space per fact: `output` follows the arrival order, `indexMap`
|
|
202
|
+
// holds only the indices the upstream itself stated, and a frame that states
|
|
203
|
+
// an index no item holds opens a new item rather than displacing one.
|
|
204
|
+
const output = new Map(); // arrival order -> { value, id, index }
|
|
205
|
+
let next = 0;
|
|
206
|
+
const indexMap = new Map(); // stated output_index -> arrival order
|
|
207
|
+
// The id a streamed item was named by, once a frame names it: an item opened
|
|
208
|
+
// by a frame that carries no id of its own is named by the frames after it.
|
|
209
|
+
const idMap = new Map(); // item id -> arrival order
|
|
188
210
|
// The fields a streamed item fills in: a message its content, a call its
|
|
189
211
|
// arguments, a reasoning item its summary.
|
|
190
212
|
const fields = ["content", "arguments", "summary", "text"];
|
|
191
|
-
// The
|
|
192
|
-
//
|
|
193
|
-
//
|
|
194
|
-
//
|
|
195
|
-
//
|
|
196
|
-
//
|
|
197
|
-
//
|
|
198
|
-
//
|
|
199
|
-
//
|
|
200
|
-
//
|
|
201
|
-
|
|
202
|
-
//
|
|
203
|
-
//
|
|
204
|
-
//
|
|
205
|
-
// the
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
213
|
+
// The newest item this stream opened without an id, by kind: a reply whose
|
|
214
|
+
// frames name nothing at all is one the upstream never indexed, and every
|
|
215
|
+
// frame after its item — the ones that say `added` and the ones that only
|
|
216
|
+
// fill a field in — belongs to that item. Remembering where it is keeps a
|
|
217
|
+
// frame with no id from opening a second item of its own kind (`item()`
|
|
218
|
+
// defaults to "message", so a text delta of such a reply would), while a
|
|
219
|
+
// frame of another kind is still not allowed to continue the wrong item —
|
|
220
|
+
// measured: a response's text landing on its function call. An item that
|
|
221
|
+
// adopts an id later still has the frames after the id that name nothing at
|
|
222
|
+
// all, so the slot stays here after it is named.
|
|
223
|
+
const openNone = new Map();
|
|
224
|
+
// What a streamed item is named by, when it is named at all: a non-empty
|
|
225
|
+
// string. `""` names nothing a client can key by, and neither does a number
|
|
226
|
+
// or a boolean — an item whose id is `0` or `false` has no identity here,
|
|
227
|
+
// however loudly the upstream says it. The same rule, in the same words, as
|
|
228
|
+
// `identity()` in tool-response.mjs: one upstream value must not be a name to
|
|
229
|
+
// one file and no name to the other, or two items of one reply share an item
|
|
230
|
+
// (measured: two calls with `"id": 0` and their own output_indexes — 0 and 1 —
|
|
231
|
+
// resolved to the first one's item, and the second call's arguments were
|
|
232
|
+
// written over the first's).
|
|
233
|
+
function identity(value) {
|
|
234
|
+
return typeof value === "string" && value !== "" ? value : undefined;
|
|
235
|
+
}
|
|
236
|
+
const at = (id) => idMap.get(id);
|
|
237
|
+
// Whether a record is still nameless: it was opened by a frame that said
|
|
238
|
+
// nothing about what it is, which is the one shape an id-less frame or a
|
|
239
|
+
// stated index may still claim as its own.
|
|
240
|
+
const nameless = (record) => record.id === undefined && record.index === undefined;
|
|
241
|
+
// A record's item and the item's identity are one fact told twice: the
|
|
242
|
+
// frames that follow a name an item took must answer by it, and the finished
|
|
243
|
+
// item must carry it. Written once, so the two cannot drift.
|
|
244
|
+
function named(slot, id) {
|
|
245
|
+
const record = output.get(slot);
|
|
246
|
+
record.id = id;
|
|
247
|
+
if (record.value !== undefined) record.value.id = id;
|
|
248
|
+
// Which item a frame that names the word alone reaches, when the reply has
|
|
249
|
+
// spent the word on two items: the one that took it last — the item the
|
|
250
|
+
// stream is working on, which is the reading this file already gives a
|
|
251
|
+
// frame that names nothing at all (openNone, above). The item it pointed
|
|
252
|
+
// at before keeps the id it answered with; only the routing moves. Measured
|
|
253
|
+
// on two calls sharing one id, each with its own output_index and one of
|
|
254
|
+
// them naming the id alone in its arguments frames: the older item held the
|
|
255
|
+
// word and every argument of the newer landed on it, so the reply came back
|
|
256
|
+
// with the newer call's arguments on both items.
|
|
257
|
+
idMap.set(id, slot);
|
|
258
|
+
}
|
|
259
|
+
// The record a frame belongs to. What it states comes first: the
|
|
260
|
+
// output_index it carries, then the item id it names — an item is the one
|
|
261
|
+
// its own frames say it is. What it leaves to this file comes second: a
|
|
262
|
+
// frame naming nothing continues the item the stream opened namelessly,
|
|
263
|
+
// which is what a reply that never indexed its items means. Anything else
|
|
264
|
+
// is a frame over an item this stream never opened, which is still not
|
|
265
|
+
// something to guess at.
|
|
266
|
+
function locate(data, id, opening = false, kind) {
|
|
267
|
+
const name = identity(id);
|
|
268
|
+
if (data.output_index !== undefined) {
|
|
269
|
+
const held = indexMap.get(index(data.output_index));
|
|
270
|
+
if (held !== undefined) return held;
|
|
271
|
+
} else if (name !== undefined) {
|
|
272
|
+
const held = at(name);
|
|
273
|
+
if (held !== undefined) return held;
|
|
274
|
+
const candidates = [...output].filter(
|
|
275
|
+
([, record]) => record.id === undefined && record.value?.type === kind,
|
|
276
|
+
);
|
|
277
|
+
const open = openNone.get(kind);
|
|
278
|
+
if (open !== undefined && output.get(open).id === undefined) {
|
|
279
|
+
// The item the stream opened namelessly is the one an id-less reply
|
|
280
|
+
// names from the frames after it, so the id is adopted and one call
|
|
281
|
+
// stays one item (measured on this route: a call's output_item.added
|
|
282
|
+
// arrives with no id at all). An item that already has an id of its
|
|
283
|
+
// own is not it, and one named item is not a candidate at all.
|
|
284
|
+
named(open, name);
|
|
285
|
+
return open;
|
|
286
|
+
}
|
|
287
|
+
if (!opening) {
|
|
288
|
+
// Exactly one nameless item of this kind is the item the frame names,
|
|
289
|
+
// so the id is adopted; two of them, or none, is a guess.
|
|
290
|
+
if (candidates.length === 1) {
|
|
291
|
+
const [held] = candidates[0];
|
|
292
|
+
named(held, name);
|
|
293
|
+
return held;
|
|
294
|
+
}
|
|
295
|
+
throw new Error("Upstream frame names an unknown item without an output index");
|
|
217
296
|
}
|
|
297
|
+
} else if (!opening) {
|
|
298
|
+
const open = openNone.get(kind);
|
|
299
|
+
if (open !== undefined) return open;
|
|
300
|
+
const candidates = [...output].filter(
|
|
301
|
+
([, record]) => nameless(record) && record.value?.type === kind,
|
|
302
|
+
);
|
|
303
|
+
if (candidates.length === 1) return candidates[0][0];
|
|
218
304
|
throw new Error("Upstream frame names an unknown item without an output index");
|
|
219
305
|
}
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
306
|
+
// A stated index this stream has not seen opens the item it names; the
|
|
307
|
+
// item a stream opens namelessly takes its place in the arrival order,
|
|
308
|
+
// which no stated index can collide with.
|
|
309
|
+
const slot = next++;
|
|
310
|
+
const record = { value: undefined, id: undefined, index: undefined };
|
|
311
|
+
output.set(slot, record);
|
|
312
|
+
if (data.output_index !== undefined) {
|
|
313
|
+
record.index = index(data.output_index);
|
|
314
|
+
indexMap.set(record.index, slot);
|
|
315
|
+
}
|
|
316
|
+
if (name !== undefined) {
|
|
317
|
+
// An `added` frame opens an item, and the id it carries is that item's
|
|
318
|
+
// own even when the reply has spent the word on an item already. A
|
|
319
|
+
// stated index says this frame belongs to another item, and a stated
|
|
320
|
+
// index is a fact about the reply; an id another item happens to hold is
|
|
321
|
+
// not a reason to refuse the frame. Refusing it lost the whole reply —
|
|
322
|
+
// two calls, one name, and a client that gets neither — where the
|
|
323
|
+
// upstream's own contradiction costs the reply only its second name.
|
|
324
|
+
named(slot, name);
|
|
325
|
+
}
|
|
326
|
+
if (nameless(record) && name === undefined) openNone.set(kind, slot);
|
|
327
|
+
return slot;
|
|
223
328
|
}
|
|
224
329
|
// A snapshot replaces the item in its slot only when it is a whole view of
|
|
225
330
|
// it; a partial one leaves what the deltas delivered standing. A snapshot
|
|
226
331
|
// that carries no id does not take away the one the stream named the item by
|
|
227
|
-
// (
|
|
332
|
+
// (locate, above): the item's own snapshot arrives without it in the shape
|
|
228
333
|
// that names an item from the frames after it, and taking the snapshot as it
|
|
229
334
|
// stands lost the id again. One that carries an id still wins, as it did.
|
|
230
|
-
function keep(
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
335
|
+
function keep(slot, value) {
|
|
336
|
+
const record = output.get(slot);
|
|
337
|
+
if (!carries(value, record.value, fields)) return;
|
|
338
|
+
// The id is inherited by the same item's partial view, never by another
|
|
339
|
+
// item. The slot is the item's own — locate resolves a stated index or id
|
|
340
|
+
// to the item that stated it, and opens a new item for one no item holds —
|
|
341
|
+
// so the snapshot in it names the same item, and a snapshot that brings an
|
|
342
|
+
// id of its own is that item named at last.
|
|
343
|
+
const stated = identity(value.id);
|
|
344
|
+
if (stated === undefined && record.id !== undefined) value.id = record.id;
|
|
345
|
+
record.value = value;
|
|
346
|
+
if (stated !== undefined) named(slot, stated);
|
|
347
|
+
if (record.index !== undefined) indexMap.set(record.index, slot);
|
|
236
348
|
}
|
|
237
349
|
function item(data, type = "message") {
|
|
238
|
-
|
|
239
|
-
|
|
350
|
+
// The name is read the one way this file reads one (identity, above), so a
|
|
351
|
+
// frame carrying `0` or `false` opens the same item a frame carrying no id
|
|
352
|
+
// at all does, rather than one that is half-named.
|
|
353
|
+
const slot = locate(data, identity(data.item_id), false, type);
|
|
354
|
+
const record = output.get(slot);
|
|
355
|
+
if (record.value === undefined) {
|
|
240
356
|
const value = { type };
|
|
241
|
-
|
|
357
|
+
// locate has already recorded the name on the record when the frame
|
|
358
|
+
// carried one, so the item takes the name it was opened with.
|
|
359
|
+
if (record.id !== undefined) value.id = record.id;
|
|
242
360
|
if (type === "message") Object.assign(value, { role: "assistant", content: [] });
|
|
243
361
|
if (type === "function_call") value.arguments = "";
|
|
244
362
|
if (type === "reasoning") value.summary = [];
|
|
245
|
-
|
|
363
|
+
record.value = value;
|
|
246
364
|
}
|
|
247
|
-
return
|
|
365
|
+
return record.value;
|
|
248
366
|
}
|
|
249
367
|
return {
|
|
250
368
|
apply(data, type) {
|
|
@@ -254,13 +372,18 @@ function responsesAccumulator() {
|
|
|
254
372
|
// A snapshot that carries an item where the reply already has one
|
|
255
373
|
// adds only what it actually carries: the accumulated items stand
|
|
256
374
|
// against an output list it does not carry, or carries empty.
|
|
257
|
-
for (const
|
|
258
|
-
keep(
|
|
375
|
+
for (const value of data.response.output ?? [])
|
|
376
|
+
keep(locate({}, identity(value?.id), true, value?.type), value);
|
|
259
377
|
}
|
|
260
378
|
} else if (type === "response.output_item.added" || type === "response.output_item.done") {
|
|
261
379
|
if (data.item)
|
|
262
380
|
keep(
|
|
263
|
-
|
|
381
|
+
locate(
|
|
382
|
+
data,
|
|
383
|
+
identity(data.item.id),
|
|
384
|
+
type === "response.output_item.added",
|
|
385
|
+
data.item?.type,
|
|
386
|
+
),
|
|
264
387
|
data.item,
|
|
265
388
|
);
|
|
266
389
|
} else if (type === "response.content_part.added" || type === "response.content_part.done") {
|
|
@@ -332,11 +455,40 @@ function responsesAccumulator() {
|
|
|
332
455
|
if (data.usage) body.usage = { ...body.usage, ...data.usage };
|
|
333
456
|
},
|
|
334
457
|
finish() {
|
|
458
|
+
// The reply holds the items that reached a value — a frame described one
|
|
459
|
+
// and a snapshot may not have arrived yet. Their order is the order the
|
|
460
|
+
// upstream placed them in: the output_index it stated for an item is
|
|
461
|
+
// where that item sits in the reply, and an item it stated none for takes
|
|
462
|
+
// the first place no stated index claims, in the order the stream opened
|
|
463
|
+
// it. That is the ordering this file's slots used to give by construction
|
|
464
|
+
// — an indexed item sat at its index, an unplaced one at the next free
|
|
465
|
+
// number — and the client that reads no stream has only the array's order
|
|
466
|
+
// to read the reply by: answering the items in arrival order instead
|
|
467
|
+
// answered them 2 then 1 for a reply that said 1 then 2. A stated index
|
|
468
|
+
// is a fact and an inferred place is a guess, so where the two meet the
|
|
469
|
+
// stated index keeps its place and the unplaced item takes the next one.
|
|
470
|
+
const items = () => {
|
|
471
|
+
const stated = new Set();
|
|
472
|
+
for (const record of output.values())
|
|
473
|
+
if (record.index !== undefined) stated.add(record.index);
|
|
474
|
+
const placed = [];
|
|
475
|
+
let free = 0;
|
|
476
|
+
for (const record of output.values()) {
|
|
477
|
+
if (record.value === undefined) continue;
|
|
478
|
+
if (record.index !== undefined) {
|
|
479
|
+
placed.push([record.index, record.value]);
|
|
480
|
+
continue;
|
|
481
|
+
}
|
|
482
|
+
while (stated.has(free)) free++;
|
|
483
|
+
placed.push([free++, record.value]);
|
|
484
|
+
}
|
|
485
|
+
return placed.sort(([a], [b]) => a - b).map(([, value]) => value);
|
|
486
|
+
};
|
|
335
487
|
if (!final) {
|
|
336
|
-
body.output =
|
|
488
|
+
body.output = items();
|
|
337
489
|
return body;
|
|
338
490
|
}
|
|
339
|
-
const kept =
|
|
491
|
+
const kept = items();
|
|
340
492
|
// A terminal object that carries no output of its own — absent, or
|
|
341
493
|
// empty while the stream accumulated some — is not a whole view of the
|
|
342
494
|
// reply: returning it verbatim discards every delta already delivered,
|
|
@@ -363,6 +515,19 @@ function responsesAccumulator() {
|
|
|
363
515
|
};
|
|
364
516
|
}
|
|
365
517
|
|
|
518
|
+
// The block field each Anthropic text delta fills in. A Map rather than an
|
|
519
|
+
// object literal, and one of them for the whole module rather than one per
|
|
520
|
+
// delta: read off an object, a `delta.type` of "__proto__", "toString" or
|
|
521
|
+
// "constructor" reached the member the prototype chain holds there and the
|
|
522
|
+
// delta wrote that key onto its block — a key the delta never named, holding a
|
|
523
|
+
// function that JSON.stringify then dropped. A Map holds no chain: those three
|
|
524
|
+
// names are the misses they are.
|
|
525
|
+
const ANTHROPIC_TEXT_FIELDS = new Map([
|
|
526
|
+
["text_delta", "text"],
|
|
527
|
+
["thinking_delta", "thinking"],
|
|
528
|
+
["signature_delta", "signature"],
|
|
529
|
+
]);
|
|
530
|
+
|
|
366
531
|
function anthropicAccumulator() {
|
|
367
532
|
let body = {
|
|
368
533
|
type: "message",
|
|
@@ -380,12 +545,21 @@ function anthropicAccumulator() {
|
|
|
380
545
|
apply(data, type) {
|
|
381
546
|
if (type === "message_start") {
|
|
382
547
|
body = { ...body, ...data.message };
|
|
548
|
+
// `content` is the one key of a message_start that `apply` fills in
|
|
549
|
+
// itself, and a value that is not a list of blocks — a string, a
|
|
550
|
+
// number, an object — is not a view of the reply to take it from: the
|
|
551
|
+
// spread above would set it on the body and answer with it, ahead of
|
|
552
|
+
// the blocks this stream accumulates. A null or a missing one is the
|
|
553
|
+
// shape every frame of this accumulator leaves alone already.
|
|
554
|
+
const stated = Array.isArray(data.message?.content) ? data.message.content : [];
|
|
555
|
+
body.content = stated;
|
|
383
556
|
// A message_start that repeats beside the deltas it opened, or one
|
|
384
557
|
// whose block has not been filled in yet, is a partial view of the
|
|
385
558
|
// reply: a block that carries nothing where the accumulated one has
|
|
386
559
|
// content leaves it standing.
|
|
387
|
-
for (const [position, block] of
|
|
388
|
-
if (carries(block, blocks.get(position), fields))
|
|
560
|
+
for (const [position, block] of stated.entries())
|
|
561
|
+
if (isSnapshotObject(block) && carries(block, blocks.get(position), fields))
|
|
562
|
+
blocks.set(position, block);
|
|
389
563
|
} else if (type === "content_block_start") {
|
|
390
564
|
// The same test message_start's blocks get, for the same reason: a
|
|
391
565
|
// gateway re-emitting a block's opening frame carries a block that
|
|
@@ -393,19 +567,24 @@ function anthropicAccumulator() {
|
|
|
393
567
|
// it would discard the text, thinking or signature the deltas
|
|
394
568
|
// already delivered. A start with nothing accumulated applies, as
|
|
395
569
|
// does one that carries content of its own.
|
|
570
|
+
//
|
|
571
|
+
// A frame that carries no block at all, or a null or a flat value
|
|
572
|
+
// where one belongs, is the same partial view: `carries` weighs it as
|
|
573
|
+
// nothing, so it was stored as it stood and the reply answered 200
|
|
574
|
+
// with a null among its content, or threw on the first delta that
|
|
575
|
+
// read a field of it. Nothing to take hold of is not a reason to fail
|
|
576
|
+
// a stream whose other frames are sound — it opens nothing, and the
|
|
577
|
+
// deltas that follow it are refused exactly like a delta with no
|
|
578
|
+
// block above them, which is the answer this file gives that shape.
|
|
396
579
|
const position = index(data.index);
|
|
397
|
-
if (carries(data.content_block, blocks.get(position), fields))
|
|
580
|
+
if (isSnapshotObject(data.content_block) && carries(data.content_block, blocks.get(position), fields))
|
|
398
581
|
blocks.set(position, data.content_block);
|
|
399
582
|
} else if (type === "content_block_delta") {
|
|
400
583
|
const position = index(data.index);
|
|
401
584
|
const block = blocks.get(position);
|
|
402
585
|
if (!block) throw new Error("Content delta without a content block");
|
|
403
586
|
const delta = data.delta ?? {};
|
|
404
|
-
const field =
|
|
405
|
-
text_delta: "text",
|
|
406
|
-
thinking_delta: "thinking",
|
|
407
|
-
signature_delta: "signature",
|
|
408
|
-
}[delta.type];
|
|
587
|
+
const field = ANTHROPIC_TEXT_FIELDS.get(delta.type);
|
|
409
588
|
if (field) applyText(block, field, delta[field], true);
|
|
410
589
|
if (delta.type === "input_json_delta") {
|
|
411
590
|
argumentsJSON.set(
|