@animalabs/agent-framework 0.26.2 → 0.27.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/README.md +56 -6
- package/dist/src/agent.d.ts +263 -22
- package/dist/src/agent.d.ts.map +1 -1
- package/dist/src/agent.js +531 -54
- package/dist/src/agent.js.map +1 -1
- package/dist/src/api/server.d.ts.map +1 -1
- package/dist/src/api/server.js +1 -2
- package/dist/src/api/server.js.map +1 -1
- package/dist/src/auto-continue.d.ts +33 -0
- package/dist/src/auto-continue.d.ts.map +1 -0
- package/dist/src/auto-continue.js +35 -0
- package/dist/src/auto-continue.js.map +1 -0
- package/dist/src/code-execution/isolate/scripts.d.ts +11 -0
- package/dist/src/code-execution/isolate/scripts.d.ts.map +1 -1
- package/dist/src/code-execution/isolate/scripts.js +5 -1
- package/dist/src/code-execution/isolate/scripts.js.map +1 -1
- package/dist/src/framework.d.ts +759 -129
- package/dist/src/framework.d.ts.map +1 -1
- package/dist/src/framework.js +3482 -928
- package/dist/src/framework.js.map +1 -1
- package/dist/src/gate/event-gate.d.ts +309 -46
- package/dist/src/gate/event-gate.d.ts.map +1 -1
- package/dist/src/gate/event-gate.js +2117 -395
- package/dist/src/gate/event-gate.js.map +1 -1
- package/dist/src/gate/index.d.ts +2 -2
- package/dist/src/gate/index.d.ts.map +1 -1
- package/dist/src/gate/index.js +1 -1
- package/dist/src/gate/index.js.map +1 -1
- package/dist/src/gate/recipe-reconcile.d.ts +103 -0
- package/dist/src/gate/recipe-reconcile.d.ts.map +1 -0
- package/dist/src/gate/recipe-reconcile.js +325 -0
- package/dist/src/gate/recipe-reconcile.js.map +1 -0
- package/dist/src/gate/types.d.ts +68 -8
- package/dist/src/gate/types.d.ts.map +1 -1
- package/dist/src/index.d.ts +6 -4
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +5 -2
- package/dist/src/index.js.map +1 -1
- package/dist/src/mcpl/channel-registry.d.ts +433 -113
- package/dist/src/mcpl/channel-registry.d.ts.map +1 -1
- package/dist/src/mcpl/channel-registry.js +1152 -434
- package/dist/src/mcpl/channel-registry.js.map +1 -1
- package/dist/src/mcpl/conversation-router.d.ts +16 -8
- package/dist/src/mcpl/conversation-router.d.ts.map +1 -1
- package/dist/src/mcpl/conversation-router.js +29 -15
- package/dist/src/mcpl/conversation-router.js.map +1 -1
- package/dist/src/mcpl/convert-block.d.ts.map +1 -1
- package/dist/src/mcpl/convert-block.js +14 -2
- package/dist/src/mcpl/convert-block.js.map +1 -1
- package/dist/src/mcpl/hook-orchestrator.d.ts +7 -1
- package/dist/src/mcpl/hook-orchestrator.d.ts.map +1 -1
- package/dist/src/mcpl/hook-orchestrator.js +32 -7
- package/dist/src/mcpl/hook-orchestrator.js.map +1 -1
- package/dist/src/mcpl/index.d.ts +5 -1
- package/dist/src/mcpl/index.d.ts.map +1 -1
- package/dist/src/mcpl/index.js +4 -0
- package/dist/src/mcpl/index.js.map +1 -1
- package/dist/src/mcpl/inference-router.d.ts +14 -2
- package/dist/src/mcpl/inference-router.d.ts.map +1 -1
- package/dist/src/mcpl/inference-router.js +73 -28
- package/dist/src/mcpl/inference-router.js.map +1 -1
- package/dist/src/mcpl/prose-grammar.d.ts +4 -0
- package/dist/src/mcpl/prose-grammar.d.ts.map +1 -1
- package/dist/src/mcpl/prose-grammar.js +6 -0
- package/dist/src/mcpl/prose-grammar.js.map +1 -1
- package/dist/src/mcpl/protocol-family.d.ts.map +1 -1
- package/dist/src/mcpl/protocol-family.js +3 -0
- package/dist/src/mcpl/protocol-family.js.map +1 -1
- package/dist/src/mcpl/push-coalescer.d.ts +20 -2
- package/dist/src/mcpl/push-coalescer.d.ts.map +1 -1
- package/dist/src/mcpl/push-coalescer.js +33 -11
- package/dist/src/mcpl/push-coalescer.js.map +1 -1
- package/dist/src/mcpl/reference-fetcher.d.ts +63 -42
- package/dist/src/mcpl/reference-fetcher.d.ts.map +1 -1
- package/dist/src/mcpl/reference-fetcher.js +177 -145
- package/dist/src/mcpl/reference-fetcher.js.map +1 -1
- package/dist/src/mcpl/references.d.ts +380 -46
- package/dist/src/mcpl/references.d.ts.map +1 -1
- package/dist/src/mcpl/references.js +736 -77
- package/dist/src/mcpl/references.js.map +1 -1
- package/dist/src/mcpl/server-connection.d.ts +82 -3
- package/dist/src/mcpl/server-connection.d.ts.map +1 -1
- package/dist/src/mcpl/server-connection.js +132 -7
- package/dist/src/mcpl/server-connection.js.map +1 -1
- package/dist/src/mcpl/tool-lifecycle.d.ts +11 -0
- package/dist/src/mcpl/tool-lifecycle.d.ts.map +1 -1
- package/dist/src/mcpl/tool-lifecycle.js +21 -0
- package/dist/src/mcpl/tool-lifecycle.js.map +1 -1
- package/dist/src/mcpl/tool-presence.d.ts +259 -0
- package/dist/src/mcpl/tool-presence.d.ts.map +1 -0
- package/dist/src/mcpl/tool-presence.js +628 -0
- package/dist/src/mcpl/tool-presence.js.map +1 -0
- package/dist/src/mcpl/tool-result-normalize.d.ts +9 -3
- package/dist/src/mcpl/tool-result-normalize.d.ts.map +1 -1
- package/dist/src/mcpl/tool-result-normalize.js +14 -6
- package/dist/src/mcpl/tool-result-normalize.js.map +1 -1
- package/dist/src/mcpl/tool-signature.d.ts +39 -0
- package/dist/src/mcpl/tool-signature.d.ts.map +1 -0
- package/dist/src/mcpl/tool-signature.js +292 -0
- package/dist/src/mcpl/tool-signature.js.map +1 -0
- package/dist/src/mcpl/toolset-host.d.ts +200 -0
- package/dist/src/mcpl/toolset-host.d.ts.map +1 -0
- package/dist/src/mcpl/toolset-host.js +621 -0
- package/dist/src/mcpl/toolset-host.js.map +1 -0
- package/dist/src/mcpl/toolsets-module.d.ts +80 -0
- package/dist/src/mcpl/toolsets-module.d.ts.map +1 -0
- package/dist/src/mcpl/toolsets-module.js +431 -0
- package/dist/src/mcpl/toolsets-module.js.map +1 -0
- package/dist/src/mcpl/transport.d.ts +2 -2
- package/dist/src/mcpl/transport.d.ts.map +1 -1
- package/dist/src/mcpl/transport.js +3 -2
- package/dist/src/mcpl/transport.js.map +1 -1
- package/dist/src/mcpl/types.d.ts +21 -1
- package/dist/src/mcpl/types.d.ts.map +1 -1
- package/dist/src/mcpl/types.js.map +1 -1
- package/dist/src/mcpl/visible-content.d.ts +7 -9
- package/dist/src/mcpl/visible-content.d.ts.map +1 -1
- package/dist/src/mcpl/visible-content.js +21 -9
- package/dist/src/mcpl/visible-content.js.map +1 -1
- package/dist/src/module-registry.d.ts +23 -8
- package/dist/src/module-registry.d.ts.map +1 -1
- package/dist/src/module-registry.js +68 -15
- package/dist/src/module-registry.js.map +1 -1
- package/dist/src/modules/discord/index.js +1 -1
- package/dist/src/modules/discord/index.js.map +1 -1
- package/dist/src/modules/history/index.d.ts +2 -1
- package/dist/src/modules/history/index.d.ts.map +1 -1
- package/dist/src/modules/history/index.js +51 -17
- package/dist/src/modules/history/index.js.map +1 -1
- package/dist/src/modules/history/semantic.d.ts +108 -6
- package/dist/src/modules/history/semantic.d.ts.map +1 -1
- package/dist/src/modules/history/semantic.js +273 -57
- package/dist/src/modules/history/semantic.js.map +1 -1
- package/dist/src/modules/workspace/direct-write.d.ts +181 -0
- package/dist/src/modules/workspace/direct-write.d.ts.map +1 -0
- package/dist/src/modules/workspace/direct-write.js +384 -0
- package/dist/src/modules/workspace/direct-write.js.map +1 -0
- package/dist/src/modules/workspace/edit-text.d.ts +26 -12
- package/dist/src/modules/workspace/edit-text.d.ts.map +1 -1
- package/dist/src/modules/workspace/edit-text.js +58 -28
- package/dist/src/modules/workspace/edit-text.js.map +1 -1
- package/dist/src/modules/workspace/effects.d.ts +10 -0
- package/dist/src/modules/workspace/effects.d.ts.map +1 -1
- package/dist/src/modules/workspace/effects.js +1 -1
- package/dist/src/modules/workspace/effects.js.map +1 -1
- package/dist/src/modules/workspace/index.d.ts +246 -27
- package/dist/src/modules/workspace/index.d.ts.map +1 -1
- package/dist/src/modules/workspace/index.js +1216 -236
- package/dist/src/modules/workspace/index.js.map +1 -1
- package/dist/src/modules/workspace/read-page.d.ts +41 -0
- package/dist/src/modules/workspace/read-page.d.ts.map +1 -0
- package/dist/src/modules/workspace/read-page.js +213 -0
- package/dist/src/modules/workspace/read-page.js.map +1 -0
- package/dist/src/modules/workspace/result-text.d.ts +67 -0
- package/dist/src/modules/workspace/result-text.d.ts.map +1 -0
- package/dist/src/modules/workspace/result-text.js +94 -0
- package/dist/src/modules/workspace/result-text.js.map +1 -0
- package/dist/src/modules/workspace/text.d.ts +29 -12
- package/dist/src/modules/workspace/text.d.ts.map +1 -1
- package/dist/src/modules/workspace/text.js +35 -33
- package/dist/src/modules/workspace/text.js.map +1 -1
- package/dist/src/modules/workspace/types.d.ts +41 -17
- package/dist/src/modules/workspace/types.d.ts.map +1 -1
- package/dist/src/modules/workspace/types.js.map +1 -1
- package/dist/src/sent-rows.d.ts +59 -0
- package/dist/src/sent-rows.d.ts.map +1 -0
- package/dist/src/sent-rows.js +145 -0
- package/dist/src/sent-rows.js.map +1 -0
- package/dist/src/silent-heartbeat.d.ts +17 -10
- package/dist/src/silent-heartbeat.d.ts.map +1 -1
- package/dist/src/silent-heartbeat.js.map +1 -1
- package/dist/src/stable-hash.d.ts +3 -0
- package/dist/src/stable-hash.d.ts.map +1 -0
- package/dist/src/stable-hash.js +15 -0
- package/dist/src/stable-hash.js.map +1 -0
- package/dist/src/stored-injections.d.ts +99 -0
- package/dist/src/stored-injections.d.ts.map +1 -0
- package/dist/src/stored-injections.js +106 -0
- package/dist/src/stored-injections.js.map +1 -0
- package/dist/src/text-as-stored.d.ts +4 -3
- package/dist/src/text-as-stored.d.ts.map +1 -1
- package/dist/src/text-as-stored.js +4 -3
- package/dist/src/text-as-stored.js.map +1 -1
- package/dist/src/thinking-drops.d.ts +81 -0
- package/dist/src/thinking-drops.d.ts.map +1 -0
- package/dist/src/thinking-drops.js +103 -0
- package/dist/src/thinking-drops.js.map +1 -0
- package/dist/src/tool-image-ledger.d.ts +9 -8
- package/dist/src/tool-image-ledger.d.ts.map +1 -1
- package/dist/src/tool-image-ledger.js +9 -8
- package/dist/src/tool-image-ledger.js.map +1 -1
- package/dist/src/tool-presentation.d.ts.map +1 -1
- package/dist/src/tool-presentation.js +3 -1
- package/dist/src/tool-presentation.js.map +1 -1
- package/dist/src/tool-result-guard.d.ts +4 -2
- package/dist/src/tool-result-guard.d.ts.map +1 -1
- package/dist/src/tool-result-guard.js +21 -6
- package/dist/src/tool-result-guard.js.map +1 -1
- package/dist/src/tool-result-history.d.ts +176 -24
- package/dist/src/tool-result-history.d.ts.map +1 -1
- package/dist/src/tool-result-history.js +411 -91
- package/dist/src/tool-result-history.js.map +1 -1
- package/dist/src/tune-out/coordinator.d.ts +17 -9
- package/dist/src/tune-out/coordinator.d.ts.map +1 -1
- package/dist/src/tune-out/coordinator.js +84 -37
- package/dist/src/tune-out/coordinator.js.map +1 -1
- package/dist/src/types/agent.d.ts +186 -7
- package/dist/src/types/agent.d.ts.map +1 -1
- package/dist/src/types/events.d.ts +15 -1
- package/dist/src/types/events.d.ts.map +1 -1
- package/dist/src/types/framework.d.ts +24 -3
- package/dist/src/types/framework.d.ts.map +1 -1
- package/dist/src/types/module.d.ts +90 -7
- package/dist/src/types/module.d.ts.map +1 -1
- package/dist/src/types/trace.d.ts +57 -2
- package/dist/src/types/trace.d.ts.map +1 -1
- package/package.json +4 -3
- package/src/agent.ts +593 -60
- package/src/api/server.ts +1 -2
- package/src/auto-continue.ts +42 -0
- package/src/code-execution/isolate/scripts.ts +11 -3
- package/src/framework.ts +3601 -967
- package/src/gate/event-gate.ts +2315 -389
- package/src/gate/index.ts +2 -1
- package/src/gate/recipe-reconcile.ts +355 -0
- package/src/gate/types.ts +70 -8
- package/src/index.ts +7 -4
- package/src/mcpl/channel-registry.ts +1220 -467
- package/src/mcpl/conversation-router.ts +34 -16
- package/src/mcpl/convert-block.ts +14 -1
- package/src/mcpl/hook-orchestrator.ts +33 -6
- package/src/mcpl/index.ts +6 -1
- package/src/mcpl/inference-router.ts +79 -34
- package/src/mcpl/prose-grammar.ts +7 -0
- package/src/mcpl/protocol-family.ts +2 -0
- package/src/mcpl/push-coalescer.ts +51 -13
- package/src/mcpl/reference-fetcher.ts +201 -162
- package/src/mcpl/references.ts +851 -77
- package/src/mcpl/server-connection.ts +145 -7
- package/src/mcpl/tool-lifecycle.ts +21 -0
- package/src/mcpl/tool-presence.ts +650 -0
- package/src/mcpl/tool-result-normalize.ts +18 -6
- package/src/mcpl/tool-signature.ts +285 -0
- package/src/mcpl/toolset-host.ts +637 -0
- package/src/mcpl/toolsets-module.ts +451 -0
- package/src/mcpl/transport.ts +3 -2
- package/src/mcpl/types.ts +22 -1
- package/src/mcpl/visible-content.ts +22 -9
- package/src/module-registry.ts +77 -24
- package/src/modules/discord/index.ts +1 -1
- package/src/modules/history/index.ts +48 -18
- package/src/modules/history/semantic.ts +308 -54
- package/src/modules/workspace/direct-write.ts +425 -0
- package/src/modules/workspace/edit-text.ts +82 -35
- package/src/modules/workspace/effects.ts +1 -1
- package/src/modules/workspace/index.ts +1274 -241
- package/src/modules/workspace/read-page.ts +222 -0
- package/src/modules/workspace/result-text.ts +119 -0
- package/src/modules/workspace/text.ts +58 -32
- package/src/modules/workspace/types.ts +41 -17
- package/src/sent-rows.ts +162 -0
- package/src/silent-heartbeat.ts +17 -10
- package/src/stable-hash.ts +14 -0
- package/src/stored-injections.ts +131 -0
- package/src/text-as-stored.ts +4 -3
- package/src/thinking-drops.ts +141 -0
- package/src/tool-image-ledger.ts +9 -8
- package/src/tool-presentation.ts +3 -1
- package/src/tool-result-guard.ts +19 -6
- package/src/tool-result-history.ts +471 -90
- package/src/tune-out/coordinator.ts +80 -37
- package/src/types/agent.ts +189 -7
- package/src/types/events.ts +15 -1
- package/src/types/framework.ts +21 -3
- package/src/types/module.ts +86 -7
- package/src/types/trace.ts +58 -2
|
@@ -9,30 +9,201 @@
|
|
|
9
9
|
* `maxChars` slice and re-introduces, one turn deferred, exactly the silent
|
|
10
10
|
* hallucination this feature exists to kill.
|
|
11
11
|
*
|
|
12
|
-
*
|
|
13
|
-
* apart from a lone UTF-16 surrogate,
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
12
|
+
* A content array (toolResultBlocks) is shown block by block, each block as
|
|
13
|
+
* both copies show it: text verbatim, apart from a lone UTF-16 surrogate,
|
|
14
|
+
* which becomes U+FFFD (#695), an image as a short
|
|
15
|
+
* `[image: mimeType, ~NKB]` placeholder, a reference as its stub, and any
|
|
16
|
+
* other block as a marker saying what it was and why it isn't shown. Anything
|
|
17
|
+
* else falls back to JSON, written by RFC-005's rules at every depth
|
|
18
|
+
* (historyJson): a reference is shown as its stub wherever it sits in the
|
|
19
|
+
* data, so its uri reaches history only as a link's line shows it where the
|
|
20
|
+
* host can't fetch the link and its uri can't carry a credential
|
|
21
|
+
* (shownLinkUri), and inline data that claims a uri or a disposition is
|
|
22
|
+
* withheld. A string is already text and is kept as it is.
|
|
20
23
|
*/
|
|
21
24
|
|
|
22
|
-
import {
|
|
23
|
-
|
|
25
|
+
import {
|
|
26
|
+
AUDIO_NO_DATA_TEXT,
|
|
27
|
+
AUDIO_NO_MEDIA_TYPE_TEXT,
|
|
28
|
+
IMAGE_NO_DATA_TEXT,
|
|
29
|
+
IMAGE_NO_MEDIA_TYPE_TEXT,
|
|
30
|
+
INLINE_WITHHELD_TEXT,
|
|
31
|
+
buildReferenceStub,
|
|
32
|
+
classifyBlock,
|
|
33
|
+
contentClaims,
|
|
34
|
+
inlineNeverUri,
|
|
35
|
+
referenceBlockForScript,
|
|
36
|
+
referenceStubOrNull,
|
|
37
|
+
referenceTypeLabel,
|
|
38
|
+
resultClaims,
|
|
39
|
+
type ReferenceTestimony,
|
|
40
|
+
type ResultClaims,
|
|
41
|
+
type ScriptLine,
|
|
42
|
+
} from './mcpl/references.js';
|
|
24
43
|
import { formatToolImagePlaceholder, toolResultCutAt } from './tool-image-ledger.js';
|
|
25
44
|
|
|
26
45
|
/** Where one block of a content array sits in its history text, and what
|
|
27
46
|
* kind of block it was: text, an inline image (its placeholder), an RFC-005
|
|
28
|
-
* reference (its stub),
|
|
47
|
+
* reference (its stub), inline data withheld for a contradiction, or a
|
|
48
|
+
* marker in place of a block the lane can't show. */
|
|
29
49
|
export interface HistoryBlockSpan {
|
|
30
|
-
kind: '
|
|
50
|
+
kind: ToolResultBlock['kind'];
|
|
31
51
|
start: number;
|
|
32
52
|
end: number;
|
|
33
53
|
}
|
|
34
54
|
|
|
55
|
+
/**
|
|
56
|
+
* Whether a tool result's live copy goes as native blocks, an image among
|
|
57
|
+
* them, rather than as its stored text. It does when the result holds an
|
|
58
|
+
* image and its links' blocks and markers come to no more than `maxChars`
|
|
59
|
+
* together. Past that, the live copy is the stored text, cut at the cap, so
|
|
60
|
+
* no number of links and markers carries a result past it. A reference's
|
|
61
|
+
* stub and a withheld line go whole beside an image whatever their length,
|
|
62
|
+
* and past its cut the stored copy keeps the newest 64 image slots the live
|
|
63
|
+
* copy showed (#403). Text blocks don't count either: the native copy cuts
|
|
64
|
+
* each at the cap on its own. Both copies decide by this, from each block's
|
|
65
|
+
* kind and the length of its text.
|
|
66
|
+
*/
|
|
67
|
+
export function goesNative(
|
|
68
|
+
blocks: ReadonlyArray<{ kind: ToolResultBlock['kind']; length: number }>,
|
|
69
|
+
maxChars: number | undefined,
|
|
70
|
+
): boolean {
|
|
71
|
+
if (!blocks.some((block) => block.kind === 'image')) return false;
|
|
72
|
+
if (!maxChars) return true;
|
|
73
|
+
let bounded = 0;
|
|
74
|
+
for (const block of blocks) if (block.kind === 'link' || block.kind === 'marker') bounded += block.length;
|
|
75
|
+
return bounded <= maxChars;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* One block of a tool result's content array as both of its copies show it
|
|
80
|
+
* (toolResultBlocks): a text block's text, an inline image, which the stored
|
|
81
|
+
* copy shows as its placeholder and the live copy as itself, or the text that
|
|
82
|
+
* stands in its place, the same in both. That text is a reference's stub, a
|
|
83
|
+
* link's stub or line (MCP's resource_link, `link`), inline data withheld for
|
|
84
|
+
* a contradiction, or a marker saying what kind of block it was and why the
|
|
85
|
+
* lane can't show it.
|
|
86
|
+
*/
|
|
87
|
+
export type ToolResultBlock =
|
|
88
|
+
| { kind: 'text'; text: string }
|
|
89
|
+
| { kind: 'image'; data: string; mimeType: string }
|
|
90
|
+
| { kind: 'reference' | 'link' | 'withheld' | 'marker'; text: string };
|
|
91
|
+
|
|
92
|
+
/** The block types MCP defines for a tool result's content. */
|
|
93
|
+
const MCP_BLOCK_TYPES = new Set(['text', 'image', 'audio', 'resource', 'resource_link']);
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Each element of `data` as both copies of a tool result show it, or null
|
|
97
|
+
* when `data` isn't a content array, so the result is data, written as JSON.
|
|
98
|
+
* An array is content when the result says so (`dataIsContent`, which the
|
|
99
|
+
* framework sets on an MCPL tool's result: every element of a protocol
|
|
100
|
+
* result's content is a block, of a type the host shows or not). Otherwise
|
|
101
|
+
* it's content only when every element is a block of one of MCP's own types,
|
|
102
|
+
* so a list of records of other types stays data. A module's array of
|
|
103
|
+
* records that all carry MCP's type names, such as `[{ type: 'image', url }]`,
|
|
104
|
+
* is read as content, and shows a marker for each record that isn't a block
|
|
105
|
+
* the lane can show.
|
|
106
|
+
*/
|
|
107
|
+
export function toolResultBlocks(data: unknown, dataIsContent?: boolean, claims?: ResultClaims): ToolResultBlock[] | null {
|
|
108
|
+
if (!isContentArray(data, dataIsContent)) return null;
|
|
109
|
+
// The result's claims, read as one (ResultClaims): what any of its
|
|
110
|
+
// references says of a uri (a `disposition: never`, an expiry) holds for
|
|
111
|
+
// each block that names it, whatever their order.
|
|
112
|
+
const read = claims ?? contentClaims(data);
|
|
113
|
+
// Each text well formed, as the context store makes it when it writes, so
|
|
114
|
+
// the copy sent and the copy stored carry the same text (#695): a stub's
|
|
115
|
+
// labels and a link's uri come from the block too.
|
|
116
|
+
return data.map((raw) => {
|
|
117
|
+
const block = toolResultBlock(raw, read);
|
|
118
|
+
return 'text' in block ? { ...block, text: block.text.toWellFormed() } : block;
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** Whether a tool result's data is a content array, which toolResultBlocks
|
|
123
|
+
* shows block by block. */
|
|
124
|
+
export function isContentArray(data: unknown, dataIsContent?: boolean): data is unknown[] {
|
|
125
|
+
return Array.isArray(data)
|
|
126
|
+
&& (dataIsContent === true || data.every((element) => isPlainRecord(element) && MCP_BLOCK_TYPES.has(element.type as string)));
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* The claims of a tool result's data (ResultClaims), as showing it reads
|
|
131
|
+
* them: a content array's from its elements (contentClaims), and any other
|
|
132
|
+
* data's by the walk serialization takes (claimsInHistory). For a decision
|
|
133
|
+
* made about the result outside showing it, such as which of its references
|
|
134
|
+
* to fetch, or for showing part of it as the rest of it claims. Null where a
|
|
135
|
+
* value stops the walk, so what the data claims is unknown (claimsInHistory).
|
|
136
|
+
*/
|
|
137
|
+
export function resultClaimsOf(data: unknown, dataIsContent?: boolean): ResultClaims | null {
|
|
138
|
+
return isContentArray(data, dataIsContent) ? contentClaims(data) : claimsInHistory(data);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** resultClaimsOf, throwing what stops the walk, for a caller that answers
|
|
142
|
+
* a stop itself: a script's text route, which fails then, as its view does
|
|
143
|
+
* (Ada-1017, room-203 #200374). */
|
|
144
|
+
export function resultClaimsOrThrow(data: unknown, dataIsContent?: boolean): ResultClaims {
|
|
145
|
+
return isContentArray(data, dataIsContent) ? contentClaims(data) : claimsInHistoryOrThrow(data);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** One element of a content array, as toolResultBlocks gives it. Only an
|
|
149
|
+
* image with data and a media type is shown as itself, as the push lane's
|
|
150
|
+
* converter shows one, and only text and images can be: this host's tool
|
|
151
|
+
* results carry nothing else, so inline audio is a marker here. */
|
|
152
|
+
function toolResultBlock(raw: unknown, claims: ResultClaims): ToolResultBlock {
|
|
153
|
+
const c = classifyBlock(raw);
|
|
154
|
+
switch (c.kind) {
|
|
155
|
+
case 'text':
|
|
156
|
+
return { kind: 'text', text: (raw as { text: string }).text };
|
|
157
|
+
case 'reference':
|
|
158
|
+
// A bounded stub, never the block's JSON, which would show its uri. A
|
|
159
|
+
// stub that shows an image type occupies a slot in save_recent_image's
|
|
160
|
+
// inventory (a failing one: its bytes are behind fetch_reference), so
|
|
161
|
+
// the index never slides past an image the resident saw. A link the
|
|
162
|
+
// host can't fetch, shown by its uri, is no slot, as no link was before
|
|
163
|
+
// links were references, when the result's JSON showed it. A link's
|
|
164
|
+
// block, stub or line, is `link`, counted toward the cap (goesNative).
|
|
165
|
+
return {
|
|
166
|
+
kind: c.testimony.fromLink ? 'link' : 'reference',
|
|
167
|
+
text: buildReferenceStub(c.testimony, 'from tool result', undefined, { unfetchedLinkUri: true, claims }),
|
|
168
|
+
};
|
|
169
|
+
case 'invalid':
|
|
170
|
+
return { kind: 'marker', text: c.shown };
|
|
171
|
+
case 'embedded':
|
|
172
|
+
// MCP's embedded resource shows as the block its contents make would
|
|
173
|
+
// show here. Its uri never shows.
|
|
174
|
+
if (c.contents.kind === 'block') return toolResultBlock(c.contents.block, claims);
|
|
175
|
+
return c.contents.kind === 'withheld'
|
|
176
|
+
? { kind: 'withheld', text: INLINE_WITHHELD_TEXT }
|
|
177
|
+
: { kind: 'marker', text: c.contents.text };
|
|
178
|
+
case 'inline': {
|
|
179
|
+
if (c.contradiction) return { kind: 'withheld', text: INLINE_WITHHELD_TEXT };
|
|
180
|
+
const b = raw as { type: 'image' | 'audio'; data: string; mimeType?: unknown };
|
|
181
|
+
const image = b.type === 'image';
|
|
182
|
+
if (b.data === '') return { kind: 'marker', text: image ? IMAGE_NO_DATA_TEXT : AUDIO_NO_DATA_TEXT };
|
|
183
|
+
if (typeof b.mimeType !== 'string' || b.mimeType === '') {
|
|
184
|
+
return { kind: 'marker', text: image ? IMAGE_NO_MEDIA_TYPE_TEXT : AUDIO_NO_MEDIA_TYPE_TEXT };
|
|
185
|
+
}
|
|
186
|
+
if (image) return { kind: 'image', data: b.data, mimeType: b.mimeType };
|
|
187
|
+
return { kind: 'marker', text: toolResultAudioText(b.mimeType, b.data) };
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/** What a tool result's inline audio shows as: its type and size, and that
|
|
193
|
+
* this host's tool results carry only text and images. */
|
|
194
|
+
function toolResultAudioText(mimeType: string, data: string): string {
|
|
195
|
+
const what = [referenceTypeLabel(mimeType), formatSize(Math.floor(data.length * 3 / 4))].filter(Boolean).join(', ');
|
|
196
|
+
return `[Audio not shown: this host's tool results carry only text and images (${what}).]`;
|
|
197
|
+
}
|
|
198
|
+
|
|
35
199
|
export interface HistorySerializeOptions {
|
|
200
|
+
/**
|
|
201
|
+
* Whether the result's data is a protocol result's content
|
|
202
|
+
* (ToolResult.dataIsContent), so an array is shown block by block whatever
|
|
203
|
+
* its blocks' types (toolResultBlocks). nonErrorResultText takes it from
|
|
204
|
+
* the result instead, whatever this says.
|
|
205
|
+
*/
|
|
206
|
+
dataIsContent?: boolean;
|
|
36
207
|
/**
|
|
37
208
|
* Called for every image block the serializer replaces with a placeholder
|
|
38
209
|
* (issue #104). Returns the retention ref to embed in the placeholder —
|
|
@@ -43,10 +214,23 @@ export interface HistorySerializeOptions {
|
|
|
43
214
|
/**
|
|
44
215
|
* Called once a content array has been serialized block by block, rather
|
|
45
216
|
* than given up on for the fallback, with where each block sits in the
|
|
46
|
-
* joined text, before any cut at `maxChars
|
|
47
|
-
*
|
|
217
|
+
* joined text, before any cut at `maxChars`, and the blocks themselves as
|
|
218
|
+
* this text was made from them. The framework reads from it what a
|
|
219
|
+
* result's live copy shows (#403), and builds that copy from these very
|
|
220
|
+
* blocks, so the two copies come from one classification.
|
|
221
|
+
*/
|
|
222
|
+
onBlocks?: (spans: HistoryBlockSpan[], blocks: ToolResultBlock[]) => void;
|
|
223
|
+
/**
|
|
224
|
+
* The claims to read (ResultClaims) in place of the data's own, where the
|
|
225
|
+
* data shown is part of a larger result whose claims these are
|
|
226
|
+
* (resultClaimsOf), such as the fields a withheld result keeps.
|
|
48
227
|
*/
|
|
49
|
-
|
|
228
|
+
claims?: ResultClaims;
|
|
229
|
+
/**
|
|
230
|
+
* Whose result this is, as nonErrorResultText's log line names it where it
|
|
231
|
+
* shows UNCHECKED_RESULT_TEXT for a result it couldn't read or show.
|
|
232
|
+
*/
|
|
233
|
+
toolName?: string;
|
|
50
234
|
}
|
|
51
235
|
|
|
52
236
|
/**
|
|
@@ -85,7 +269,7 @@ export function toolResultDataToHistoryString(
|
|
|
85
269
|
// RFC-005 shows only objects otherwise (shownInHistory), so a string has
|
|
86
270
|
// nothing for historyJson to stub or withhold.
|
|
87
271
|
const str = fromArray
|
|
88
|
-
?? (typeof data === 'string' ? data.toWellFormed() : historyJson(data))
|
|
272
|
+
?? (typeof data === 'string' ? data.toWellFormed() : historyJson(data, options?.claims))
|
|
89
273
|
?? '';
|
|
90
274
|
return maxChars ? truncateForHistory(str, maxChars) : str;
|
|
91
275
|
}
|
|
@@ -98,6 +282,11 @@ export function toolResultDataToHistoryString(
|
|
|
98
282
|
*
|
|
99
283
|
* Each field is read once: the direct Agent API passes its caller's own
|
|
100
284
|
* result here, unchecked, and a getter needn't answer the same way twice.
|
|
285
|
+
* Whatever stops reading or showing the data is UNCHECKED_RESULT_TEXT, with
|
|
286
|
+
* a log line naming the tool (`options.toolName`) and the error: the model's
|
|
287
|
+
* lane has no failure answer that doesn't wedge the round.
|
|
288
|
+
* Whether an array is content is the result's own `dataIsContent`, read for
|
|
289
|
+
* its truth as a result's flags are, whatever `options` says.
|
|
101
290
|
*
|
|
102
291
|
* Each text a tool result's copies carry is made well formed where it's
|
|
103
292
|
* taken from the result: a lone UTF-16 surrogate becomes U+FFFD, as the
|
|
@@ -108,7 +297,44 @@ export function toolResultDataToHistoryString(
|
|
|
108
297
|
* as before; a cut at a lone high half no longer steps back over it.
|
|
109
298
|
*/
|
|
110
299
|
export function nonErrorResultText(
|
|
111
|
-
result: { data?: unknown; error?: string },
|
|
300
|
+
result: { data?: unknown; error?: string; dataIsContent?: boolean },
|
|
301
|
+
maxChars?: number,
|
|
302
|
+
options?: HistorySerializeOptions,
|
|
303
|
+
): string {
|
|
304
|
+
// The blocks go to the caller only once the text is made from them, so a
|
|
305
|
+
// result that shows the notice has no live copy of its own: the notice is
|
|
306
|
+
// both copies' text.
|
|
307
|
+
let blocks: [HistoryBlockSpan[], ToolResultBlock[]] | undefined;
|
|
308
|
+
const onBlocks = options?.onBlocks;
|
|
309
|
+
try {
|
|
310
|
+
// The result's own fields are read inside the try too: the direct Agent
|
|
311
|
+
// API's result is its caller's own object, whose `data` can be a getter.
|
|
312
|
+
const text = nonErrorResultTextOrThrow(result, maxChars, {
|
|
313
|
+
...options,
|
|
314
|
+
...(onBlocks ? { onBlocks: (spans: HistoryBlockSpan[], made: ToolResultBlock[]) => { blocks = [spans, made]; } } : {}),
|
|
315
|
+
});
|
|
316
|
+
if (onBlocks && blocks) onBlocks(blocks[0], blocks[1]);
|
|
317
|
+
return text;
|
|
318
|
+
} catch (error) {
|
|
319
|
+
// The model's lane has no failure answer that doesn't wedge the round, so
|
|
320
|
+
// whatever stops reading or showing a result, such as a getter that
|
|
321
|
+
// throws in a content array's block or a render deeper than the claims
|
|
322
|
+
// walk got (a script's routes call the serializer directly, and fail),
|
|
323
|
+
// is the notice, with a line naming the tool and the error, since it can
|
|
324
|
+
// also be a fault of this host's (Ada-1017, room-203 #201013).
|
|
325
|
+
console.error(`[tool-results] ${options?.toolName ? `tool '${options.toolName}'` : 'a tool'}: its result couldn't be read or shown, so the model reads that it was withheld: ${thrownLine(error)}`);
|
|
326
|
+
return maxChars ? truncateForHistory(UNCHECKED_RESULT_TEXT, maxChars) : UNCHECKED_RESULT_TEXT;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* nonErrorResultText's text, throwing whatever stops reading or showing the
|
|
332
|
+
* result, such as a stopped claims walk (historyJson), for a caller that
|
|
333
|
+
* answers a stop itself: a script's routes, which fail naming the tool
|
|
334
|
+
* (Ada-1017, room-203 #200374), never the model lane's notice.
|
|
335
|
+
*/
|
|
336
|
+
export function nonErrorResultTextOrThrow(
|
|
337
|
+
result: { data?: unknown; error?: string; dataIsContent?: boolean },
|
|
112
338
|
maxChars?: number,
|
|
113
339
|
options?: HistorySerializeOptions,
|
|
114
340
|
): string {
|
|
@@ -120,7 +346,16 @@ export function nonErrorResultText(
|
|
|
120
346
|
return maxChars ? truncateForHistory(text, maxChars) : text;
|
|
121
347
|
}
|
|
122
348
|
}
|
|
123
|
-
return toolResultDataToHistoryString(data, maxChars, options);
|
|
349
|
+
return toolResultDataToHistoryString(data, maxChars, { ...options, dataIsContent: Boolean(result.dataIsContent) });
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/** The first line of what was thrown, for a log line. */
|
|
353
|
+
function thrownLine(error: unknown): string {
|
|
354
|
+
try {
|
|
355
|
+
return (error instanceof Error ? error.message : String(error)).split('\n')[0] || 'no reason given';
|
|
356
|
+
} catch {
|
|
357
|
+
return 'a value that can\'t be read';
|
|
358
|
+
}
|
|
124
359
|
}
|
|
125
360
|
|
|
126
361
|
/** Bounded copy of an arbitrary string with the standard truncation notice,
|
|
@@ -137,46 +372,18 @@ export function truncateForHistory(str: string, maxChars: number): string {
|
|
|
137
372
|
}
|
|
138
373
|
|
|
139
374
|
/**
|
|
140
|
-
*
|
|
141
|
-
*
|
|
142
|
-
*
|
|
143
|
-
* reference anywhere in the data as its stub.
|
|
375
|
+
* A content array's history text (toolResultBlocks): each block's text, an
|
|
376
|
+
* image as its placeholder, joined by newlines. Null when `data` isn't a
|
|
377
|
+
* content array, for the fallback, historyJson.
|
|
144
378
|
*/
|
|
145
379
|
function tryHistoryStringFromContentArray(data: unknown, options?: HistorySerializeOptions): string | null {
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
if (
|
|
150
|
-
const
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
// rather than sending the whole array to the JSON fallback.
|
|
154
|
-
// Read once: a direct Agent API result is unchecked.
|
|
155
|
-
const text = b.text;
|
|
156
|
-
parts.push({ kind: 'text', text: typeof text === 'string' ? text.toWellFormed() : NON_STRING_TEXT_PLACEHOLDER });
|
|
157
|
-
} else if (isInlineContradiction(b)) {
|
|
158
|
-
// RFC-005 vector 2: checked before the data branch so inline data
|
|
159
|
-
// claiming bulk disposition is withheld, not rendered.
|
|
160
|
-
parts.push({ kind: 'withheld', text: INLINE_WITHHELD_TEXT });
|
|
161
|
-
} else if (b.type === 'image' && typeof b.data === 'string' && typeof b.mimeType === 'string') {
|
|
162
|
-
// Decoded byte estimate from base64 length (3/4 ratio, rounded).
|
|
163
|
-
const approxBytes = Math.floor(b.data.length * 3 / 4);
|
|
164
|
-
const ref = options?.imageRef?.(blockIndex, { data: b.data, mimeType: b.mimeType }) ?? null;
|
|
165
|
-
parts.push({ kind: 'image', text: formatToolImagePlaceholder(b.mimeType, formatSize(approxBytes), ref) });
|
|
166
|
-
} else if (b.type === 'resource' || ((b.type === 'image' || b.type === 'audio') && typeof (b as { uri?: unknown }).uri === 'string')) {
|
|
167
|
-
// RFC-005 reference block: a bounded stub, never the raw block JSON
|
|
168
|
-
// (which used to inline the URI — and the whole array — into history).
|
|
169
|
-
// A stub that shows an image type occupies a slot in
|
|
170
|
-
// save_recent_image's inventory (a failing one — bytes live behind
|
|
171
|
-
// fetch_reference), so the index never slides past an image the
|
|
172
|
-
// resident saw.
|
|
173
|
-
const stub = referenceStubOrNull(b, 'from tool result');
|
|
174
|
-
if (stub === null) return null;
|
|
175
|
-
parts.push({ kind: 'reference', text: stub });
|
|
176
|
-
} else {
|
|
177
|
-
return null;
|
|
178
|
-
}
|
|
179
|
-
}
|
|
380
|
+
const blocks = toolResultBlocks(data, options?.dataIsContent, options?.claims);
|
|
381
|
+
if (blocks === null) return null;
|
|
382
|
+
const parts = blocks.map((block, blockIndex): { kind: HistoryBlockSpan['kind']; text: string } => {
|
|
383
|
+
if (block.kind !== 'image') return block;
|
|
384
|
+
const ref = options?.imageRef?.(blockIndex, { data: block.data, mimeType: block.mimeType }) ?? null;
|
|
385
|
+
return { kind: 'image', text: storedText(block, ref) };
|
|
386
|
+
});
|
|
180
387
|
if (options?.onBlocks) {
|
|
181
388
|
const spans: HistoryBlockSpan[] = [];
|
|
182
389
|
let start = 0;
|
|
@@ -184,23 +391,47 @@ function tryHistoryStringFromContentArray(data: unknown, options?: HistorySerial
|
|
|
184
391
|
spans.push({ kind: part.kind, start, end: start + part.text.length });
|
|
185
392
|
start += part.text.length + 1; // the newline that joins them
|
|
186
393
|
}
|
|
187
|
-
options.onBlocks(spans);
|
|
394
|
+
options.onBlocks(spans, blocks);
|
|
188
395
|
}
|
|
189
396
|
return parts.map((part) => part.text).join('\n');
|
|
190
397
|
}
|
|
191
398
|
|
|
192
399
|
/**
|
|
193
400
|
* Whether RFC-005 governs a value of a tool result's data, wherever it sits.
|
|
194
|
-
* A reference is a `resource` with a uri
|
|
195
|
-
* `
|
|
196
|
-
*
|
|
197
|
-
*
|
|
198
|
-
*
|
|
199
|
-
*
|
|
200
|
-
*
|
|
401
|
+
* A reference is a `resource` with a uri at its top level, or an `image` or
|
|
402
|
+
* `audio` given by uri; inline data is an `image` or `audio` whose data is a
|
|
403
|
+
* string, or MCP's embedded resource, a `resource` with a uri under
|
|
404
|
+
* `resource` beside its contents. There a uri that is null, absent or empty
|
|
405
|
+
* counts as none, except under `resource`, where a string, an empty one
|
|
406
|
+
* included, tells an embedded resource, as the classifier tells it. Data
|
|
407
|
+
* that isn't a string isn't inline data, so a record that only shares one of
|
|
408
|
+
* those type names is data. A uri of any other value, string or not, counts,
|
|
409
|
+
* so a credential fails closed. MCP's `resource_link` (RFC-005 §8.1) is
|
|
410
|
+
* governed whatever value its uri has, so one whose uri isn't usable is its
|
|
411
|
+
* marker; one that names no uri is data here, where as a block of a content
|
|
412
|
+
* array, which the classifier reads, it's the marker.
|
|
201
413
|
*/
|
|
202
414
|
function governedByRfc005(b: Record<string, unknown>): boolean {
|
|
203
|
-
if (b.type === 'resource')
|
|
415
|
+
if (b.type === 'resource') {
|
|
416
|
+
// MCP's embedded resource is told by a string uri under `resource`, ''
|
|
417
|
+
// included, as classifyBlock tells it; a uri of any other value there
|
|
418
|
+
// counts too, failing closed.
|
|
419
|
+
return hasUri(b.uri) || (isPlainRecord(b.resource) && (typeof b.resource.uri === 'string' || hasUri(b.resource.uri)));
|
|
420
|
+
}
|
|
421
|
+
// A link that names a uri is governed whatever its value, so one without
|
|
422
|
+
// a usable uri (empty, null, not a string) is its marker here, as on the
|
|
423
|
+
// content lanes, never its fields. One that names none is data at depth,
|
|
424
|
+
// on the model's lanes and a script's, as main has every link at depth:
|
|
425
|
+
// its fields hold no uri RFC-005 governs, though a value in them, such as
|
|
426
|
+
// an untyped object's `uri`, is data as anywhere else. As a block of a
|
|
427
|
+
// content array on the model's line, the classifier reads it, and there
|
|
428
|
+
// it's the marker; a script's view of a module's array, which reads every
|
|
429
|
+
// block here, shows its fields, as module data reached a script on main
|
|
430
|
+
// (Ada-1017, room-203 #202138).
|
|
431
|
+
// That's the object the script lane makes of a link whose uri it leaves
|
|
432
|
+
// out (referenceBlockForScript), which a script's view, given it again,
|
|
433
|
+
// leaves as it is, as it leaves a reference's stub.
|
|
434
|
+
if (b.type === 'resource_link') return b.uri !== undefined;
|
|
204
435
|
if (b.type === 'image' || b.type === 'audio') return typeof b.data === 'string' || hasUri(b.uri);
|
|
205
436
|
return false;
|
|
206
437
|
}
|
|
@@ -217,14 +448,36 @@ function isPlainRecord(value: unknown): value is Record<string, unknown> {
|
|
|
217
448
|
* A value of a tool result's data as history shows it, by RFC-005's rules,
|
|
218
449
|
* wherever it sits in the data. A value RFC-005 governs (governedByRfc005) is
|
|
219
450
|
* shown as the reference lanes show it: a reference as its stub, an invalid
|
|
220
|
-
* one (such as a uri that isn't a string
|
|
221
|
-
*
|
|
222
|
-
*
|
|
223
|
-
*
|
|
224
|
-
*
|
|
451
|
+
* one (such as a uri that isn't a string) as the marker saying why it isn't
|
|
452
|
+
* shown, MCP's embedded resource as the text its stored copy shows (its
|
|
453
|
+
* text, an image's placeholder or a marker), never its fields, which carry
|
|
454
|
+
* its uri, and inline data that also claims a uri or a disposition as
|
|
455
|
+
* withheld. So of the uris RFC-005 governs, the only one shown is a link's,
|
|
456
|
+
* in its line, where the host can't fetch the link and its uri can't carry a
|
|
457
|
+
* credential (shownLinkUri). Inline data that claims neither is itself, as is
|
|
458
|
+
* any other value: a block of another type keeps its fields, a url among
|
|
225
459
|
* them, since RFC-005 governs references.
|
|
226
460
|
*/
|
|
227
|
-
function shownInHistory(value: unknown): unknown {
|
|
461
|
+
function shownInHistory(value: unknown, claims?: ResultClaims): unknown {
|
|
462
|
+
return walkedInHistory(value, (b) => {
|
|
463
|
+
// MCP's embedded resource is the text its stored copy shows: its text,
|
|
464
|
+
// an image's placeholder or its marker. As JSON it would carry its uri.
|
|
465
|
+
// Made well formed, as a content array's texts are (toolResultBlocks),
|
|
466
|
+
// since at the root this text is the result's whole text, as it's sent.
|
|
467
|
+
if (classifyBlock(b).kind === 'embedded') return storedText(toolResultBlock(b, claims ?? resultClaims([]))).toWellFormed();
|
|
468
|
+
return referenceStubOrNull(b, 'from tool result', undefined, { unfetchedLinkUri: true, claims });
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
/**
|
|
473
|
+
* The one walk of a tool result's data that showing it and reading its
|
|
474
|
+
* claims both take: a value RFC-005 governs is text, from `asText`, except
|
|
475
|
+
* inline data that claims no uri or disposition, which is itself, as is any
|
|
476
|
+
* other value, so serialization descends into the same values each time, so
|
|
477
|
+
* long as each value's toJSON and getters answer the same each time they're
|
|
478
|
+
* read. `asText` gives a governed value's text, or null where it's itself.
|
|
479
|
+
*/
|
|
480
|
+
function walkedInHistory(value: unknown, asText: (b: Record<string, unknown>) => string | null): unknown {
|
|
228
481
|
if (!value || typeof value !== 'object' || Array.isArray(value)) return value;
|
|
229
482
|
const b = value as Record<string, unknown>;
|
|
230
483
|
if (!governedByRfc005(b)) return value;
|
|
@@ -235,30 +488,118 @@ function shownInHistory(value: unknown): unknown {
|
|
|
235
488
|
// carries none (hasUri).
|
|
236
489
|
return hasUri(b.uri) || b.disposition !== undefined ? INLINE_WITHHELD_TEXT : value;
|
|
237
490
|
}
|
|
238
|
-
return
|
|
491
|
+
return asText(b) ?? value;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
/**
|
|
495
|
+
* What a tool result's data claims of each uri its references name
|
|
496
|
+
* (ResultClaims), read by the walk serialization takes (walkedInHistory),
|
|
497
|
+
* through toJSON, the fields of inline data and every depth it shows: so
|
|
498
|
+
* every reference whose stub the result shows counts, and nothing it doesn't
|
|
499
|
+
* show does. A value the walk can't read or write, such as a cycle or a
|
|
500
|
+
* getter that throws, stops it, and then it's null, since what the rest of
|
|
501
|
+
* the data claims is unknown. It's a walk of its own before the one that
|
|
502
|
+
* shows the data, so a value's toJSON and getters run in both: one that
|
|
503
|
+
* answers differently the second time can claim what the shown text doesn't
|
|
504
|
+
* show, or show a reference the claims never read. Only data no check has
|
|
505
|
+
* read through JSON can do either: a module's result is parsed JSON from its
|
|
506
|
+
* dispatch, and an MCPL peer's blocks are parsed wire JSON, so this is the
|
|
507
|
+
* direct Agent API's, whose results a host gives unchecked.
|
|
508
|
+
*/
|
|
509
|
+
function claimsInHistory(data: unknown): ResultClaims | null {
|
|
510
|
+
try {
|
|
511
|
+
return claimsInHistoryOrThrow(data);
|
|
512
|
+
} catch {
|
|
513
|
+
// What the rest of the data claims is unknown, so a withheld result's
|
|
514
|
+
// kept fields keep nothing (keptWhenWithheld).
|
|
515
|
+
return null;
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
/** claimsInHistory's walk, which throws what stops it. */
|
|
520
|
+
function claimsInHistoryOrThrow(data: unknown): ResultClaims {
|
|
521
|
+
const testimonies: ReferenceTestimony[] = [];
|
|
522
|
+
// Inline data's never, which walkedInHistory withholds before it asks
|
|
523
|
+
// `read`, and an embedded resource's, which `read` notes (inlineNeverUri).
|
|
524
|
+
const nevers: string[] = [];
|
|
525
|
+
const noteInline = (value: unknown): void => {
|
|
526
|
+
if (!isPlainRecord(value) || (value.type !== 'image' && value.type !== 'audio')) return;
|
|
527
|
+
const never = inlineNeverUri(value);
|
|
528
|
+
if (never !== null) nevers.push(never);
|
|
529
|
+
};
|
|
530
|
+
const read = (b: Record<string, unknown>): string | null => {
|
|
531
|
+
const c = classifyBlock(b);
|
|
532
|
+
if (c.kind === 'reference') testimonies.push(c.testimony);
|
|
533
|
+
const never = inlineNeverUri(b, c);
|
|
534
|
+
if (never !== null) nevers.push(never);
|
|
535
|
+
// Text ('') wherever showing gives text (shownInHistory), an embedded
|
|
536
|
+
// resource included, so the walk stops where showing stops, never
|
|
537
|
+
// reading such a value's fields, and descends only where showing does.
|
|
538
|
+
// Inline data never reaches here: walkedInHistory decides it first.
|
|
539
|
+
return c.kind === 'text' ? null : '';
|
|
540
|
+
};
|
|
541
|
+
noteInline(data);
|
|
542
|
+
const root = walkedInHistory(data, read);
|
|
543
|
+
// Its text is thrown away, so a string is written as 0, which keeps a
|
|
544
|
+
// long one from being copied for nothing; the walk descends as before.
|
|
545
|
+
if (root === data || typeof root !== 'string') {
|
|
546
|
+
JSON.stringify(data, (_key, value: unknown) => {
|
|
547
|
+
if (value !== data) noteInline(value);
|
|
548
|
+
const walked = walkedInHistory(value, read);
|
|
549
|
+
return typeof walked === 'string' ? 0 : walked;
|
|
550
|
+
});
|
|
551
|
+
}
|
|
552
|
+
return resultClaims(testimonies, nevers);
|
|
239
553
|
}
|
|
240
554
|
|
|
241
555
|
/**
|
|
242
556
|
* A value of a tool result's data as a script reads it, by history's rules
|
|
243
557
|
* (shownInHistory), as a JSON replacer applies them at every depth: what
|
|
244
558
|
* RFC-005 governs is shown as the script lane shows it, a reference as its
|
|
245
|
-
* stub's facts
|
|
246
|
-
*
|
|
247
|
-
*
|
|
248
|
-
*
|
|
249
|
-
*
|
|
250
|
-
*
|
|
251
|
-
*
|
|
252
|
-
*
|
|
559
|
+
* stub's facts, a link's uri among them only where the model's line for it
|
|
560
|
+
* would show it, an invalid one as the notice, and MCP's embedded resource as
|
|
561
|
+
* the block its contents make (referenceBlockForScript), and inline data
|
|
562
|
+
* that also claims a uri of any type or a disposition as the withheld
|
|
563
|
+
* notice, each notice as a text block. A null, absent or empty uri claims
|
|
564
|
+
* none (hasUri), so inline data beside one is itself. Any other value is
|
|
565
|
+
* itself. `claims` are those of everything the script's view shows
|
|
566
|
+
* (scriptViewClaims), and `line` the model's line for the result, its claims
|
|
567
|
+
* and its records as it reads them (ScriptLine), which decide where a link's
|
|
568
|
+
* uri is shown. What it gives is either a stub, a notice or a text block,
|
|
569
|
+
* which RFC-005 doesn't govern, or inline data that claims nothing, or the
|
|
570
|
+
* value itself, each of which it gives again, so applying it twice gives
|
|
571
|
+
* what applying it once gave.
|
|
253
572
|
*/
|
|
254
|
-
export function shownToScript(value: unknown): unknown {
|
|
573
|
+
export function shownToScript(value: unknown, claims?: ResultClaims, line?: ScriptLine): unknown {
|
|
255
574
|
if (!value || typeof value !== 'object' || Array.isArray(value)) return value;
|
|
256
575
|
const b = value as Record<string, unknown>;
|
|
257
576
|
if (!governedByRfc005(b)) return value;
|
|
258
577
|
if ((b.type === 'image' || b.type === 'audio') && typeof b.data === 'string') {
|
|
259
578
|
return hasUri(b.uri) || b.disposition !== undefined ? { type: 'text', text: INLINE_WITHHELD_TEXT } : value;
|
|
260
579
|
}
|
|
261
|
-
return referenceBlockForScript(b);
|
|
580
|
+
return referenceBlockForScript(b, undefined, claims, line);
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
/**
|
|
584
|
+
* The claims (ResultClaims) of everything a script's view of a content array
|
|
585
|
+
* shows, nested fields included (Ada-1017, room-203 #200094), by the walk
|
|
586
|
+
* history's JSON takes (claimsInHistory): a script is shown the references
|
|
587
|
+
* nested in a block's fields, each as its stub, so a `never` among them
|
|
588
|
+
* withholds that uri from every block in the view. A link's uri is still
|
|
589
|
+
* shown only where the model's line shows it, by the claims the line reads
|
|
590
|
+
* (contentClaims, which reads a block and not its fields): reading more
|
|
591
|
+
* claims can loosen as well as tighten, since an expiry makes a link one the
|
|
592
|
+
* host can't fetch (referenceBlockForScript's `line`). An MCPL peer's
|
|
593
|
+
* top-level block the classifier rejects is
|
|
594
|
+
* its notice, its fields unshown, so they don't count (`fromServer`). It
|
|
595
|
+
* throws what stops the walk, such as a value too deep to convert or a
|
|
596
|
+
* getter that throws, and the script's view then isn't written: the
|
|
597
|
+
* treatment never runs without the claims it reads (Ada-1017, room-203
|
|
598
|
+
* #200250), whether or not its own walk, with other replacers and so other
|
|
599
|
+
* stack depths, would stop at the same value.
|
|
600
|
+
*/
|
|
601
|
+
export function scriptViewClaims(content: readonly unknown[], fromServer: boolean): ResultClaims {
|
|
602
|
+
return claimsInHistoryOrThrow(fromServer ? content.filter((block) => classifyBlock(block).kind !== 'invalid') : content);
|
|
262
603
|
}
|
|
263
604
|
|
|
264
605
|
/**
|
|
@@ -270,32 +611,72 @@ export function shownToScript(value: unknown): unknown {
|
|
|
270
611
|
* peer's block (referenceBlockForScript), its reference bound to the server
|
|
271
612
|
* that sent it, and any other value is itself. A stub or a notice isn't
|
|
272
613
|
* governed, so a block the top-level pass already treated is left as it is.
|
|
614
|
+
* `claims` are those of the script's whole view (scriptViewClaims).
|
|
273
615
|
*/
|
|
274
|
-
export function shownToScriptFromServer(value: unknown, serverId: string): unknown {
|
|
616
|
+
export function shownToScriptFromServer(value: unknown, serverId: string, claims?: ResultClaims, line?: ScriptLine): unknown {
|
|
275
617
|
if (!value || typeof value !== 'object' || Array.isArray(value)) return value;
|
|
276
618
|
const b = value as Record<string, unknown>;
|
|
277
|
-
return governedByRfc005(b) ? referenceBlockForScript(b, serverId) : value;
|
|
619
|
+
return governedByRfc005(b) ? referenceBlockForScript(b, serverId, claims, line) : value;
|
|
278
620
|
}
|
|
279
621
|
|
|
280
622
|
/**
|
|
281
623
|
* The text RFC-005 shows a value of a tool result's data as, when it shows it
|
|
282
624
|
* as text rather than as its fields: a reference's stub, an invalid one's
|
|
283
|
-
*
|
|
625
|
+
* marker, inline data withheld, or what an embedded resource's stored copy
|
|
626
|
+
* shows. Null for any other value.
|
|
284
627
|
*/
|
|
285
628
|
export function rfc005TextOf(value: unknown): string | null {
|
|
629
|
+
// A single value claims only what it says itself, which its stub reads.
|
|
286
630
|
const shown = shownInHistory(value);
|
|
287
631
|
return shown !== value && typeof shown === 'string' ? shown : null;
|
|
288
632
|
}
|
|
289
633
|
|
|
634
|
+
/**
|
|
635
|
+
* What the model's lane shows for a tool result it couldn't read through,
|
|
636
|
+
* check for references and show (nonErrorResultText, the lane's one catch,
|
|
637
|
+
* which logs the tool and the error): the tool ran, and its result isn't
|
|
638
|
+
* shown, rather than shown with fewer claims, where a link could show a uri a
|
|
639
|
+
* reference in the result withholds, and rather than a throw, which would
|
|
640
|
+
* leave the round unstored and unsent. Where the walk of its claims stops
|
|
641
|
+
* (historyJson throws then), the claims walk meets data no JSON can hold,
|
|
642
|
+
* such as a cycle or a BigInt, or data too deep to write, before the render
|
|
643
|
+
* does, so such data shows this, where the render threw (Desmond-2579's
|
|
644
|
+
* run); so does a render that stops after the walk, or a getter that throws
|
|
645
|
+
* (Ada-1017, room-203 #200344, #201013). The round completes, as a withheld
|
|
646
|
+
* result's kept fields keep nothing then (keptWhenWithheld). A script never
|
|
647
|
+
* gets it: its routes call the serializer, or nonErrorResultTextOrThrow,
|
|
648
|
+
* directly, whose walk throws where it stops, and fail naming the tool
|
|
649
|
+
* (#200374). An error result's own error field is read as before, and so
|
|
650
|
+
* are the fields the tool-result guard keeps of a result it withholds.
|
|
651
|
+
*/
|
|
652
|
+
export const UNCHECKED_RESULT_TEXT =
|
|
653
|
+
"[Result not shown: the tool ran, but its result couldn't be read through, checked for references and shown, so this host withheld it.]";
|
|
654
|
+
|
|
290
655
|
/**
|
|
291
656
|
* JSON of a tool result's data with each value in it shown as history shows
|
|
292
|
-
* it (shownInHistory), at any depth
|
|
293
|
-
*
|
|
657
|
+
* it (shownInHistory), at any depth, by the claims `given` or else its own
|
|
658
|
+
* (claimsInHistoryOrThrow), throwing where that walk stops. Data that is
|
|
659
|
+
* itself shown as text, such as a bare reference, is that text.
|
|
294
660
|
*/
|
|
295
|
-
function historyJson(data: unknown): string | undefined {
|
|
296
|
-
|
|
661
|
+
function historyJson(data: unknown, given?: ResultClaims): string | undefined {
|
|
662
|
+
// A walk that stopped can't say what the data claims, and showing it with
|
|
663
|
+
// no claims would let a link show a uri a reference withholds, should the
|
|
664
|
+
// render's own walk, with other replacers, get past the same value.
|
|
665
|
+
// Where the walk stops this throws: the model's lane answers with
|
|
666
|
+
// UNCHECKED_RESULT_TEXT and a log line (nonErrorResultText), a script's
|
|
667
|
+
// routes with a failure naming the tool.
|
|
668
|
+
const claims = given ?? claimsInHistoryOrThrow(data);
|
|
669
|
+
const root = shownInHistory(data, claims);
|
|
297
670
|
if (root !== data && typeof root === 'string') return root;
|
|
298
|
-
return JSON.stringify(data, (_key, value: unknown) => shownInHistory(value)) as string | undefined;
|
|
671
|
+
return JSON.stringify(data, (_key, value: unknown) => shownInHistory(value, claims)) as string | undefined;
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
/** A block's text in the stored copy: an image as its placeholder, with the
|
|
675
|
+
* retention ref given, and any other block as its text. */
|
|
676
|
+
function storedText(block: ToolResultBlock, ref: string | null = null): string {
|
|
677
|
+
if (block.kind !== 'image') return block.text;
|
|
678
|
+
// Decoded byte estimate from base64 length (3/4 ratio, rounded).
|
|
679
|
+
return formatToolImagePlaceholder(block.mimeType, formatSize(Math.floor(block.data.length * 3 / 4)), ref);
|
|
299
680
|
}
|
|
300
681
|
|
|
301
682
|
function formatSize(bytes: number): string {
|