@animalabs/agent-framework 0.26.0 → 0.26.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/src/framework.d.ts +141 -24
- package/dist/src/framework.d.ts.map +1 -1
- package/dist/src/framework.js +781 -133
- package/dist/src/framework.js.map +1 -1
- package/dist/src/mcpl/channel-registry.d.ts.map +1 -1
- package/dist/src/mcpl/channel-registry.js +24 -3
- package/dist/src/mcpl/channel-registry.js.map +1 -1
- package/dist/src/mcpl/push-coalescer.d.ts +34 -11
- package/dist/src/mcpl/push-coalescer.d.ts.map +1 -1
- package/dist/src/mcpl/push-coalescer.js +61 -27
- package/dist/src/mcpl/push-coalescer.js.map +1 -1
- package/dist/src/mcpl/references.d.ts +11 -5
- package/dist/src/mcpl/references.d.ts.map +1 -1
- package/dist/src/mcpl/references.js +21 -6
- package/dist/src/mcpl/references.js.map +1 -1
- package/dist/src/tool-result-history.d.ts +25 -0
- package/dist/src/tool-result-history.d.ts.map +1 -1
- package/dist/src/tool-result-history.js +44 -3
- package/dist/src/tool-result-history.js.map +1 -1
- package/dist/src/types/events.d.ts +7 -2
- package/dist/src/types/events.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/framework.ts +781 -135
- package/src/mcpl/channel-registry.ts +23 -4
- package/src/mcpl/push-coalescer.ts +83 -34
- package/src/mcpl/references.ts +22 -7
- package/src/tool-result-history.ts +43 -3
- package/src/types/events.ts +7 -2
|
@@ -1708,6 +1708,9 @@ export class ChannelRegistry {
|
|
|
1708
1708
|
responder?: Responder,
|
|
1709
1709
|
): Promise<void> {
|
|
1710
1710
|
const results: ChannelIncomingMessageResult[] = [];
|
|
1711
|
+
// Set when a coalesced message was admitted but its acknowledgement
|
|
1712
|
+
// failed: the request is then answered with this error (below).
|
|
1713
|
+
let requestError: { code: number; message: string; field?: string } | undefined;
|
|
1711
1714
|
// The batch came on the transport open now: a close after this point
|
|
1712
1715
|
// (transportClosed) leaves its traffic nothing to confirm.
|
|
1713
1716
|
const closesAtArrival = this.transportCloses.get(serverId) ?? 0;
|
|
@@ -1919,10 +1922,26 @@ export class ChannelRegistry {
|
|
|
1919
1922
|
if (result.accepted) markAccepted(firstView === true);
|
|
1920
1923
|
results.push(result);
|
|
1921
1924
|
} catch (error) {
|
|
1922
|
-
const err = error as Error & { code?: number; field?: string };
|
|
1925
|
+
const err = error as Error & { code?: number; field?: string; admitted?: boolean };
|
|
1923
1926
|
// -32602 is answered coalesce_invalid; anything else as its message,
|
|
1924
1927
|
// a plain Error standing for an internal error (-32603).
|
|
1925
|
-
|
|
1928
|
+
const code = typeof err.code === 'number' ? err.code : -32603;
|
|
1929
|
+
this.traceCoalesceRejection(serverId, message, err, code);
|
|
1930
|
+
if (err.admitted && responder?.respondError) {
|
|
1931
|
+
// Admitted, with its effects in: only the acknowledgement failed,
|
|
1932
|
+
// so `accepted: false`, which says the message wasn't admitted,
|
|
1933
|
+
// would be untrue, and a producer would hold it as never
|
|
1934
|
+
// delivered. A per-message result can't say "may have been
|
|
1935
|
+
// admitted" (§14.3); an error for the request is the spec's
|
|
1936
|
+
// answer for that. Nothing after this message is admitted, since
|
|
1937
|
+
// the producer sends the request's messages again: a coalesced
|
|
1938
|
+
// one is answered from its receipt, and an uncoalesced one
|
|
1939
|
+
// before this message is admitted again. (A responder without
|
|
1940
|
+
// respondError, which only an in-process caller passes, gets the
|
|
1941
|
+
// per-message answer below: it can't be told anything else.)
|
|
1942
|
+
requestError = { code, message: err.message, ...(err.field ? { field: err.field } : {}) };
|
|
1943
|
+
break;
|
|
1944
|
+
}
|
|
1926
1945
|
results.push({
|
|
1927
1946
|
messageId: message.messageId,
|
|
1928
1947
|
accepted: false,
|
|
@@ -1950,8 +1969,8 @@ export class ChannelRegistry {
|
|
|
1950
1969
|
});
|
|
1951
1970
|
}
|
|
1952
1971
|
|
|
1953
|
-
|
|
1954
|
-
responder?.respond(
|
|
1972
|
+
if (requestError) responder!.respondError!(requestError.code, requestError.message, { field: requestError.field });
|
|
1973
|
+
else responder?.respond({ results } satisfies ChannelsIncomingResult);
|
|
1955
1974
|
|
|
1956
1975
|
this.emitTraceFn({
|
|
1957
1976
|
type: 'mcpl:channels-incoming',
|
|
@@ -32,9 +32,14 @@ export type CoalesceOutcome = typeof COALESCE_OUTCOMES[number];
|
|
|
32
32
|
class CancelledRender extends Error {}
|
|
33
33
|
|
|
34
34
|
export class CoalesceError extends Error {
|
|
35
|
-
|
|
35
|
+
/** Set on a failure that came after the occurrence's effects: it was
|
|
36
|
+
* admitted, and only its acknowledgement failed (acknowledge). A lane
|
|
37
|
+
* must not answer it as not admitted. */
|
|
38
|
+
readonly admitted: boolean;
|
|
39
|
+
constructor(readonly field: string, message: string, readonly code = -32602, options: { admitted?: true } = {}) {
|
|
36
40
|
super(message);
|
|
37
41
|
this.name = 'CoalesceError';
|
|
42
|
+
this.admitted = options.admitted === true;
|
|
38
43
|
}
|
|
39
44
|
}
|
|
40
45
|
|
|
@@ -210,7 +215,16 @@ interface SubjectState<E> {
|
|
|
210
215
|
touchedAt: number;
|
|
211
216
|
}
|
|
212
217
|
|
|
213
|
-
interface ReceiptEntry {
|
|
218
|
+
interface ReceiptEntry {
|
|
219
|
+
result: PushEventResult;
|
|
220
|
+
at: number;
|
|
221
|
+
committed: boolean;
|
|
222
|
+
firstView?: true;
|
|
223
|
+
/** Set while the receipt's record isn't written (recordReceipt failed):
|
|
224
|
+
* what a rewrite keeps from the admission. The rest of the record is the
|
|
225
|
+
* subject's state when it's written. */
|
|
226
|
+
unwritten?: { subject: string; born?: SubjectState<unknown>['history'] };
|
|
227
|
+
}
|
|
214
228
|
|
|
215
229
|
export interface CoalescingSnapshot {
|
|
216
230
|
version: 1;
|
|
@@ -244,15 +258,18 @@ export interface CoalescingReceiptRecord {
|
|
|
244
258
|
/** History the subject was created with, for a subject first seen by this receipt. */
|
|
245
259
|
born?: SubjectState<unknown>['history'];
|
|
246
260
|
/** The subject's first view had ended by this acceptance
|
|
247
|
-
* (SubjectState.firstViewEnded),
|
|
248
|
-
* can't forget the end. A record
|
|
249
|
-
* none and reads as not ended, as a
|
|
261
|
+
* (SubjectState.firstViewEnded), or by the time a retry wrote the record,
|
|
262
|
+
* so a crash before the snapshot flush can't forget the end. A record
|
|
263
|
+
* written before this field existed has none and reads as not ended, as a
|
|
264
|
+
* snapshot does. */
|
|
250
265
|
firstViewEnded?: true;
|
|
251
266
|
/** The subject's pending batch AFTER this acceptance: the accepted work
|
|
252
267
|
* itself (so a crash before the snapshot flush cannot lose it while keeping
|
|
253
268
|
* the receipt that suppresses its retry), or `null` when the acceptance
|
|
254
269
|
* left no batch — a retraction or plain replacement must be recoverable
|
|
255
|
-
* too. Records replay in order, so the last one per subject wins.
|
|
270
|
+
* too. Records replay in order, so the last one per subject wins. A
|
|
271
|
+
* record a retry writes (its first write failed) takes the batch the
|
|
272
|
+
* subject has then, a rendering one included. */
|
|
256
273
|
batch: { latest: CoalescedOccurrence<unknown>; notices: DeferredNotice[]; dropped: number } | null;
|
|
257
274
|
}
|
|
258
275
|
|
|
@@ -298,11 +315,13 @@ export interface CoalescerHost<E> {
|
|
|
298
315
|
* be recoverable before the RPC is issued, §3.2). Throws on failure. */
|
|
299
316
|
saveNow?(snapshot: CoalescingSnapshot): void;
|
|
300
317
|
/** Persist one receipt now, before the acceptance is acknowledged. Throws
|
|
301
|
-
* on failure, which fails
|
|
318
|
+
* on failure, which fails that answer: the receipt is kept unwritten, and
|
|
319
|
+
* the producer's retry writes it again rather than admitting it again. */
|
|
302
320
|
recordReceipt?(record: CoalescingReceiptRecord): void;
|
|
303
321
|
/** One call per ADMITTED occurrence (never for a producer retry the receipt
|
|
304
|
-
* deduplicates), after its
|
|
305
|
-
* is contained here and never fails the
|
|
322
|
+
* deduplicates), after its effects, whether or not its receipt could be
|
|
323
|
+
* written. Observation only: a throw is contained here and never fails the
|
|
324
|
+
* acceptance. */
|
|
306
325
|
accepted?(occurrence: CoalescedOccurrence<E>): void;
|
|
307
326
|
/** Make every write so far DURABLE (an fsync; the coalescer requests each
|
|
308
327
|
* commit under its lock, so no two acceptances share one).
|
|
@@ -540,17 +559,27 @@ export class PushCoalescer<E = unknown> {
|
|
|
540
559
|
return entry;
|
|
541
560
|
}
|
|
542
561
|
|
|
543
|
-
/** Acknowledge a receipt: durable first. A receipt whose
|
|
544
|
-
*
|
|
545
|
-
*
|
|
562
|
+
/** Acknowledge a receipt: durable first. A receipt whose record couldn't be
|
|
563
|
+
* written is written now, and one whose commit failed is committed now (no
|
|
564
|
+
* effects are repeated); if either fails again, so does the
|
|
565
|
+
* acknowledgement, and the producer retries once more. */
|
|
546
566
|
private async acknowledge(key: string, entry: ReceiptEntry, subject: string, eventId: string): Promise<PushEventResult> {
|
|
567
|
+
if (entry.unwritten) {
|
|
568
|
+
try {
|
|
569
|
+
this.recordReceipt(key, entry, entry.unwritten);
|
|
570
|
+
} catch (error) {
|
|
571
|
+
this.host.audit({ kind: 'persist-failed', subject, eventId, error: String(error) });
|
|
572
|
+
throw new CoalesceError('eventId', 'host could not persist the acceptance; retry', -32000, { admitted: true });
|
|
573
|
+
}
|
|
574
|
+
delete entry.unwritten;
|
|
575
|
+
}
|
|
547
576
|
if (!entry.committed) {
|
|
548
577
|
try {
|
|
549
578
|
await this.host.commit?.();
|
|
550
579
|
entry.committed = true;
|
|
551
580
|
} catch (error) {
|
|
552
581
|
this.host.audit({ kind: 'persist-failed', subject, eventId, error: String(error) });
|
|
553
|
-
throw new CoalesceError('eventId', 'host could not make the acceptance durable; retry', -32000);
|
|
582
|
+
throw new CoalesceError('eventId', 'host could not make the acceptance durable; retry', -32000, { admitted: true });
|
|
554
583
|
}
|
|
555
584
|
}
|
|
556
585
|
return structuredClone(entry.result);
|
|
@@ -571,9 +600,11 @@ export class PushCoalescer<E = unknown> {
|
|
|
571
600
|
}
|
|
572
601
|
|
|
573
602
|
private async admit(occurrence: CoalescedOccurrence<E>): Promise<PushEventResult> {
|
|
574
|
-
if (this.suspended) throw new CoalesceError('serverId', 'host is stopping', -32000);
|
|
575
|
-
const subject = coalescingSubjectKey(occurrence.serverId, occurrence.binding, occurrence.scope, occurrence.key);
|
|
576
603
|
const duplicate = this.receiptEntry(coalescingReceiptKey(occurrence.serverId, occurrence.binding, occurrence.eventId));
|
|
604
|
+
// A retry of an occurrence admitted before the stop is refused too, but
|
|
605
|
+
// as one admitted: its effects are in, so no lane may say it wasn't.
|
|
606
|
+
if (this.suspended) throw new CoalesceError('serverId', 'host is stopping', -32000, duplicate ? { admitted: true } : {});
|
|
607
|
+
const subject = coalescingSubjectKey(occurrence.serverId, occurrence.binding, occurrence.scope, occurrence.key);
|
|
577
608
|
if (duplicate) {
|
|
578
609
|
// A producer retrying acknowledged work is also a chance to notice a
|
|
579
610
|
// batch left asleep by a failed freeze: wake it now, without effects.
|
|
@@ -605,19 +636,18 @@ export class PushCoalescer<E = unknown> {
|
|
|
605
636
|
state.touchedAt = this.now();
|
|
606
637
|
const result: PushEventResult = { accepted: true, coalesce: { outcome, ...(priorEventId ? { priorEventId } : {}) } };
|
|
607
638
|
const receiptKey = coalescingReceiptKey(occurrence.serverId, occurrence.binding, occurrence.eventId);
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
this.
|
|
619
|
-
|
|
620
|
-
}
|
|
639
|
+
// The effects are done, so the receipt is kept whatever its record's
|
|
640
|
+
// write does: a retry is answered from it, never admitted again (RFC-006
|
|
641
|
+
// §3.1). Its record is written and made durable before an acknowledgement
|
|
642
|
+
// (acknowledge), by this answer or a retry's. While this process lives,
|
|
643
|
+
// the in-memory receipt deduplicates a retry, which writes or commits
|
|
644
|
+
// what failed. The snapshot carries it too, so a later save and commit
|
|
645
|
+
// keep it across a restart; a kill before either leaves nothing of it,
|
|
646
|
+
// and the retry is a fresh acceptance.
|
|
647
|
+
const entry: ReceiptEntry = {
|
|
648
|
+
result: structuredClone(result), at: this.now(), committed: false, unwritten: { subject, ...(born ? { born } : {}) },
|
|
649
|
+
...(this.deliveredFirstView ? { firstView: true as const } : {}),
|
|
650
|
+
};
|
|
621
651
|
this.receipts.set(receiptKey, entry);
|
|
622
652
|
try {
|
|
623
653
|
this.host.accepted?.(occurrence);
|
|
@@ -626,12 +656,31 @@ export class PushCoalescer<E = unknown> {
|
|
|
626
656
|
}
|
|
627
657
|
this.prune();
|
|
628
658
|
this.persist();
|
|
629
|
-
// Written but not yet durable: the in-memory receipt deduplicates a retry
|
|
630
|
-
// while this process lives, and the retry commits it (acknowledge); after
|
|
631
|
-
// a kill nothing of it exists and the retry is a fresh acceptance.
|
|
632
659
|
return this.acknowledge(receiptKey, entry, subject, occurrence.eventId);
|
|
633
660
|
}
|
|
634
661
|
|
|
662
|
+
/** Write a receipt's record: what the admission fixed (`fixed`), with the
|
|
663
|
+
* subject's first-view end and pending batch as they are now. A record
|
|
664
|
+
* written by a retry follows any written since its admission, and records
|
|
665
|
+
* replay in order, so it carries the batch the subject has now, not the
|
|
666
|
+
* one it had then. That includes a batch whose render has started, which
|
|
667
|
+
* the render-start snapshot holds too: a `null` would clear it at a
|
|
668
|
+
* restore, and a crash before the render's content is stored would lose
|
|
669
|
+
* the accepted work. (At an admission no batch is rendering without a
|
|
670
|
+
* pending one: a plain occurrence or a retraction cancels the render, and
|
|
671
|
+
* a deferred notice opens a batch.) Throws when the host's write does. */
|
|
672
|
+
private recordReceipt(key: string, entry: ReceiptEntry, fixed: { subject: string; born?: SubjectState<unknown>['history'] }): void {
|
|
673
|
+
const state = this.subjects.get(fixed.subject);
|
|
674
|
+
const batch = state?.batch ?? state?.rendering?.batch;
|
|
675
|
+
this.host.recordReceipt?.({
|
|
676
|
+
// A birth is the subject's only while the coalescer still holds it: one
|
|
677
|
+
// evicted since its admission is untracked, `unknown` (prune).
|
|
678
|
+
key, result: entry.result, at: entry.at, subject: fixed.subject, ...(fixed.born && state ? { born: fixed.born } : {}),
|
|
679
|
+
...(state?.firstViewEnded ? { firstViewEnded: true as const } : {}),
|
|
680
|
+
batch: batch ? structuredClone({ latest: batch.latest as CoalescedOccurrence<unknown>, notices: batch.notices, dropped: batch.dropped }) : null,
|
|
681
|
+
});
|
|
682
|
+
}
|
|
683
|
+
|
|
635
684
|
private subjectFor(subject: string, occurrence: CoalescedOccurrence<E>): SubjectState<E> {
|
|
636
685
|
let state = this.subjects.get(subject);
|
|
637
686
|
if (!state) {
|
|
@@ -1193,9 +1242,9 @@ export class PushCoalescer<E = unknown> {
|
|
|
1193
1242
|
if (r.batch === undefined) continue; // pre-round-3 record: no batch information
|
|
1194
1243
|
// The subject's batch AFTER that acceptance, newer than the snapshot:
|
|
1195
1244
|
// the accepted deferred work itself, or null for a withdrawal /
|
|
1196
|
-
// replacement that cleared it.
|
|
1197
|
-
// record was written
|
|
1198
|
-
//
|
|
1245
|
+
// replacement that cleared it. Its render may have started before the
|
|
1246
|
+
// record was written (a retry's record) or after, and the snapshot
|
|
1247
|
+
// cannot say whether it ran: take the fallback.
|
|
1199
1248
|
const state = this.subjects.get(r.subject) ?? { history: 'unknown' as const, touchedAt: now };
|
|
1200
1249
|
state.batch = r.batch
|
|
1201
1250
|
? { latest: r.batch.latest as CoalescedOccurrence<E>, notices: r.batch.notices, dropped: r.batch.dropped, noRender: true }
|
package/src/mcpl/references.ts
CHANGED
|
@@ -454,7 +454,9 @@ export function referenceStubOrNull(block: unknown, provenance?: string, serverI
|
|
|
454
454
|
* RFC-005 treatment for one MCPL wire block, for a script's structured
|
|
455
455
|
* result, which carries blocks rather than text. It makes the decision
|
|
456
456
|
* referenceStubOrNull makes for the model's lanes, since what a script prints
|
|
457
|
-
* comes back to the model
|
|
457
|
+
* comes back to the model, except that a script reads every field, so inline
|
|
458
|
+
* data beside a uri of any type but null or absent is withheld, where the
|
|
459
|
+
* model's lanes, which show no uri, withhold it beside a string one. A reference becomes the stub's facts as data,
|
|
458
460
|
* without its URI: the host's reference id, what the server said about it in
|
|
459
461
|
* this block (its labels stripped and bounded as in the stub), and `savedTo`,
|
|
460
462
|
* the workspace path its payload was fetched to, or null when it wasn't
|
|
@@ -462,18 +464,31 @@ export function referenceStubOrNull(block: unknown, provenance?: string, serverI
|
|
|
462
464
|
* counts as expired also carries `expired`, the reason the stub gives in
|
|
463
465
|
* place of advising fetch_reference. The reference is registered bound to
|
|
464
466
|
* `serverId`, the server whose tool returned it, as the model's lanes bind
|
|
465
|
-
* a tool result's references, so the fetch its id offers can be made.
|
|
466
|
-
*
|
|
467
|
+
* a tool result's references, so the fetch its id offers can be made. With
|
|
468
|
+
* no `serverId`, as for a module's result, it's registered as the model's
|
|
469
|
+
* history registers a module's: the record its uri already has, bound where
|
|
470
|
+
* it arrived (a module's ctx.callTool registers an MCPL peer's references),
|
|
471
|
+
* or a new one bound to no server. A block the classifier
|
|
467
472
|
* rejects becomes the notice the model's lanes show, and inline data that
|
|
468
|
-
* also claims a bulk disposition
|
|
473
|
+
* also claims a bulk disposition, or a uri of any type but null or absent
|
|
474
|
+
* (an empty string included), the withheld notice, each as a text block.
|
|
469
475
|
* A text block, or inline data that claims nothing, is returned as sent.
|
|
470
476
|
*/
|
|
471
|
-
export function referenceBlockForScript(block: unknown, serverId
|
|
477
|
+
export function referenceBlockForScript(block: unknown, serverId?: string): unknown {
|
|
472
478
|
const c = classifyBlock(block);
|
|
473
479
|
switch (c.kind) {
|
|
474
480
|
case 'text': return block;
|
|
475
|
-
case 'inline':
|
|
476
|
-
|
|
481
|
+
case 'inline': {
|
|
482
|
+
// A script reads every field, as history's JSON writes it, so inline
|
|
483
|
+
// data beside a uri of any type is withheld here, not only beside a
|
|
484
|
+
// string one as in the model's lanes, which show no uri: an object can
|
|
485
|
+
// carry a credential. An absent or null uri carries none; a string one,
|
|
486
|
+
// an empty one included, is already the classifier's contradiction.
|
|
487
|
+
const uri = (block as { uri?: unknown }).uri;
|
|
488
|
+
return c.contradiction || (uri !== undefined && uri !== null)
|
|
489
|
+
? { type: 'text', text: INLINE_WITHHELD_TEXT }
|
|
490
|
+
: block;
|
|
491
|
+
}
|
|
477
492
|
case 'invalid': return { type: 'text', text: c.shown };
|
|
478
493
|
case 'reference': {
|
|
479
494
|
const t = c.testimony;
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
* is withheld.
|
|
20
20
|
*/
|
|
21
21
|
|
|
22
|
-
import { INLINE_WITHHELD_TEXT, isInlineContradiction, referenceStubOrNull } from './mcpl/references.js';
|
|
22
|
+
import { INLINE_WITHHELD_TEXT, isInlineContradiction, referenceBlockForScript, referenceStubOrNull } from './mcpl/references.js';
|
|
23
23
|
import { NON_STRING_TEXT_PLACEHOLDER } from './mcpl/visible-content.js';
|
|
24
24
|
import { formatToolImagePlaceholder, toolResultCutAt } from './tool-image-ledger.js';
|
|
25
25
|
|
|
@@ -229,14 +229,54 @@ function shownInHistory(value: unknown): unknown {
|
|
|
229
229
|
const b = value as Record<string, unknown>;
|
|
230
230
|
if (!governedByRfc005(b)) return value;
|
|
231
231
|
if ((b.type === 'image' || b.type === 'audio') && typeof b.data === 'string') {
|
|
232
|
-
// JSON writes every field, so here inline data beside a uri of any
|
|
232
|
+
// JSON writes every field, so here inline data beside a uri of any type
|
|
233
233
|
// is withheld, not only beside a string one as in the lanes, which show
|
|
234
|
-
// no uri: an object can carry a credential.
|
|
234
|
+
// no uri: an object can carry a credential. A null, absent or empty uri
|
|
235
|
+
// carries none (hasUri).
|
|
235
236
|
return hasUri(b.uri) || b.disposition !== undefined ? INLINE_WITHHELD_TEXT : value;
|
|
236
237
|
}
|
|
237
238
|
return referenceStubOrNull(b, 'from tool result') ?? value;
|
|
238
239
|
}
|
|
239
240
|
|
|
241
|
+
/**
|
|
242
|
+
* A value of a tool result's data as a script reads it, by history's rules
|
|
243
|
+
* (shownInHistory), as a JSON replacer applies them at every depth: what
|
|
244
|
+
* RFC-005 governs is shown as the script lane shows it, a reference as its
|
|
245
|
+
* stub's facts without its uri and an invalid one as the notice
|
|
246
|
+
* (referenceBlockForScript), and inline data that also claims a uri of any
|
|
247
|
+
* type or a disposition as the withheld notice, each as a text block. A
|
|
248
|
+
* null, absent or empty uri claims none (hasUri), so inline data beside one
|
|
249
|
+
* is itself. Any
|
|
250
|
+
* other value is itself. What it gives is either a stub or a notice, which
|
|
251
|
+
* RFC-005 doesn't govern, or the value itself, which it gives again, so
|
|
252
|
+
* applying it twice gives what applying it once gave.
|
|
253
|
+
*/
|
|
254
|
+
export function shownToScript(value: unknown): unknown {
|
|
255
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) return value;
|
|
256
|
+
const b = value as Record<string, unknown>;
|
|
257
|
+
if (!governedByRfc005(b)) return value;
|
|
258
|
+
if ((b.type === 'image' || b.type === 'audio') && typeof b.data === 'string') {
|
|
259
|
+
return hasUri(b.uri) || b.disposition !== undefined ? { type: 'text', text: INLINE_WITHHELD_TEXT } : value;
|
|
260
|
+
}
|
|
261
|
+
return referenceBlockForScript(b);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* A value nested in an MCPL peer's content block as a script reads it, as a
|
|
266
|
+
* JSON replacer applies it inside the blocks: what RFC-005 governs
|
|
267
|
+
* (governedByRfc005, history's guard, since the classifier would make a
|
|
268
|
+
* notice of an object that isn't a content block, such as a peer's own
|
|
269
|
+
* nested data) gets the treatment the script lane gives a
|
|
270
|
+
* peer's block (referenceBlockForScript), its reference bound to the server
|
|
271
|
+
* that sent it, and any other value is itself. A stub or a notice isn't
|
|
272
|
+
* governed, so a block the top-level pass already treated is left as it is.
|
|
273
|
+
*/
|
|
274
|
+
export function shownToScriptFromServer(value: unknown, serverId: string): unknown {
|
|
275
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) return value;
|
|
276
|
+
const b = value as Record<string, unknown>;
|
|
277
|
+
return governedByRfc005(b) ? referenceBlockForScript(b, serverId) : value;
|
|
278
|
+
}
|
|
279
|
+
|
|
240
280
|
/**
|
|
241
281
|
* The text RFC-005 shows a value of a tool result's data as, when it shows it
|
|
242
282
|
* as text rather than as its fields: a reference's stub, an invalid one's
|
package/src/types/events.ts
CHANGED
|
@@ -291,13 +291,18 @@ export interface ToolCall {
|
|
|
291
291
|
* text answer's envelope, over six: 7,456,527 at the default 128 MiB. On
|
|
292
292
|
* Node, past about 1.5 GiB, the host's longest string bounds it instead.
|
|
293
293
|
* A result with a `structured` value reaches a script as its content's
|
|
294
|
-
* blocks
|
|
294
|
+
* blocks, a module's or an MCPL peer's with RFC-005's treatment of what it
|
|
295
|
+
* governs (a reference as its stub's facts) and a standard peer's as sent,
|
|
296
|
+
* beside that value as JSON, and the figure counts
|
|
295
297
|
* neither the value nor what the blocks carry past their text, such as each
|
|
296
298
|
* further block's keys or an image's data, so a tool that returns one
|
|
297
299
|
* leaves room for them; and a result nested past 2,000 levels, or whose parse
|
|
298
300
|
* needs more of the script's memory than is free, doesn't reach the script
|
|
299
301
|
* whatever its length. A result that doesn't reach it is answered
|
|
300
|
-
* ResultTooLarge, the call having taken effect.
|
|
302
|
+
* ResultTooLarge, the call having taken effect. One the host can't convert
|
|
303
|
+
* to JSON at all, such as one nested deeper than its runtime's
|
|
304
|
+
* JSON.stringify can take, is answered instead as a failure naming the
|
|
305
|
+
* tool and saying the call itself completed. Nothing is cut or saved to
|
|
301
306
|
* a file. A tool that pages or budgets its answer can stay under it. The
|
|
302
307
|
* framework sets it on a model's, a script's and the operator's puppet's
|
|
303
308
|
* call to a module, a utility run through `utils` included. A call
|