Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
44543ae
fix(tool-results): show each block of a content array, never the arra…
Oct 18, 2026
1dc2c9e
fix(references): read a credential's value both ways; correct two docs
Oct 18, 2026
adc597e
fix(tool-results): send the stored text when links and markers pass t…
Oct 18, 2026
122373a
Merge main 124edb46: #531's refusals in the checks before dialing tha…
Oct 18, 2026
930b541
docs(changelog): a stub's refusal words after #531; no plaintext refusal
Oct 18, 2026
59b1bef
test(mcpl): the fetcher's own clauses name a server by its bounded id
Oct 18, 2026
34418e2
Merge main ce1a0735: #262's modern MCP client beside this branch's co…
Oct 18, 2026
ec8809b
fix(tool-results): one classification for both copies, references reg…
Oct 18, 2026
5a2dcb3
Merge main 00eb87a8: #538's script result flags beside this branch's …
Oct 18, 2026
b98c56a
docs(changelog): a script's reference object carries the stub's words…
Oct 18, 2026
0395acd
docs(references): dialCheck's doc says a stopped framework's stubs an…
Oct 18, 2026
45c782d
test(tool-results): read #538's script result as { text, isError } in…
Oct 18, 2026
2b74db9
fix(references): a script's reference object says what its refId meet…
Oct 18, 2026
e30b1c8
docs(changelog): what agents stop seeing includes a link whose uri a …
Oct 18, 2026
9a4068c
test(tool-results): the audio marker test reads the history text, so …
Oct 18, 2026
cddaa58
fix(references): a result's claims are read as one, strictest wins; o…
Oct 19, 2026
262f348
fix(references): a result's claims are one summary per uri, its own f…
Oct 19, 2026
6341f5f
fix(references): EXPIRY_REASON's order words competing expiries; a sc…
Oct 19, 2026
125db57
fix(tool-results): a withheld result keeps nothing where a value stop…
Oct 19, 2026
b1f1e0a
Merge main 8ec1de5a into #527, keeping main's script lane and text rules
Oct 24, 2026
d5b60e4
chore: drop contentClaims from framework.ts's imports, which the merg…
Oct 24, 2026
e247dd9
fix(references): a script's view fails closed where its claims walk s…
Oct 24, 2026
b56606e
fix(tool-results): data whose claims walk stops shows a notice of its…
Oct 24, 2026
85b7391
fix(references): a script's text route fails where the claims walk st…
Oct 24, 2026
91d6709
test(tool-results): a cycle shows as the unchecked notice, and regist…
Oct 24, 2026
50c2c20
fix(references, tool-results): review fixes: a link's uri by the line…
Oct 24, 2026
cec3c35
fix(tool-results): the model lane's catch covers reading the result's…
Oct 24, 2026
e067cc8
docs(tool-results): the comment says the try, not the catch
Oct 24, 2026
7bd4f19
fix(references, tool-results): review fixes: block order, a never by …
Oct 24, 2026
3aad97b
refactor(framework): a script's text route takes the serializer's own…
Oct 24, 2026
5147cb1
fix(references): review fixes: a registry journal for the line's reco…
Oct 24, 2026
b9ca38c
docs(references): openJournal's doc says which saved paths a journal …
Oct 24, 2026
7b682f1
fix(references, framework): review fixes: the module demand loop regi…
Oct 24, 2026
ef6c7d0
test(tool-result-blocks): the cut-userinfo test uses a uri whose cut …
Oct 24, 2026
2ba363b
revert(references): the whole-uri credential check, which no link rea…
Oct 24, 2026
1754ec8
fix(framework): review fixes: the module demand loop is the line's on…
Oct 24, 2026
17633af
fix(framework, references): review fixes: the flag as the render read…
Oct 24, 2026
056b5b5
fix(references): a uri in a parameter's value is read for credentials…
Oct 24, 2026
24ec92b
fix(references): a relative uri in a parameter's value, and one encod…
Oct 24, 2026
7ca4e7a
fix(references): a value's decodes stop at the bound, and one that wo…
Oct 24, 2026
7c5a7c0
fix(references): a value is decoded again leniently, as the parameter…
Oct 24, 2026
15c7eb6
fix(references): a relative value that doesn't resolve may carry a cr…
Oct 24, 2026
d28980e
fix(references): only a protocol-relative value that doesn't resolve …
Oct 24, 2026
3bdc670
fix(references): a relative value is read against one fixed base, by …
Oct 24, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/tool-result-blocks.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- `ToolResult.dataIsContent` says a result's array `data` is a protocol result's content, in which every element is a block, of a type the host can show or not. The model then reads each element as itself, as a reference's stub, or as a marker saying what it was, never as the array's JSON. agent-framework sets it on an MCPL tool's result, and a module that returns a content array can set it too.
5 changes: 5 additions & 0 deletions changelog.d/tool-result-blocks.changed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
- In an MCPL tool's result, or a module's, a link (MCP's `resource_link`) the host can't fetch whose uri may carry a credential now shows a stub that says why it can't be fetched, with no uri, where the result's JSON showed the uri. So an agent no longer sees that uri, and has no other handle on the link here. Such a uri has userinfo, or a query or fragment parameter that has a value and is named like a signed URL's or a token, such as `X-Amz-Signature`, `sig`, `hm`, `token`, `access_token` or `key`, compared by letters and digits alone, so `accessToken` and `token[]` count too. A parameter's value that is itself a uri is read the same way, whether absolute (an image proxy's `?url=` holding a percent-encoded signed URL), protocol-relative, or relative with a query or fragment (a login's `?next=%2Freset%3Ftoken%3D…`), and also once decoded again where a redirector encoded it twice, each read and decode a level, up to four; a uri reached deeper counts too, and one in another encoding, such as base64, isn't read. RFC-005 §5 gives the reason: signed URLs and query capabilities are bearer credentials that look like locations. So does a link to a uri that any reference in the same result marks `disposition: never`, or to a uri that shows as the same text (a label's cleaning drops controls and bidi marks and trims surrounding space, and a lone surrogate shows as U+FFFD), or, where no reference in the result names the uri (a link names it without claiming anything), whose reference from an earlier result, as the host's record of the uri holds it, did (the latest reference until its bytes are saved, then the one they were verified against), since RFC-005 §5 removes such a uri unconditionally. An ordinary link with a query, such as `watch?v=…`, and an anchor such as `#token`, which has no value, still show their uri. A standard MCP server's result, a modern server's or an MCP-only one's, isn't read this way: its links show their uri as the server sent it.
- In a script's structured result, from an MCPL peer or a module, a reference arrives as its object without its uri, as before, except a link (MCP's `resource_link`), which carries `uri` where the model's line for it shows the uri. One the fetcher would refuse carries `unfetchable`, the words a stub gives in place of advising `fetch_reference`, as one that counts as expired carries `expired`. Both give why a `fetch_reference` with the object's `refId` would be refused, as the result's references claim it, so a link whose line shows its uri carries them too. An MCPL peer's link reached a script as the unknown-block notice, and a module's as its fields, its uri among them. A script's view reads the claims of every reference it shows, those nested in a block's fields included, so a `disposition: never` nested in one block withholds that uri from a link elsewhere in the same result, where the model's line, which shows a block and not its fields, can still show it. A link's object carries its uri only where the model's line for the result shows it, by the claims that line reads and the uri's record as that line leaves the registry, before the script lane's own registrations, so a script never sees, through a result's blocks, a uri the model's line withholds, within 2,000 references in one result: past that, either can lose an earlier result's `never`, and a script reading a module's content array can lose it where the line, rendering in order, hasn't yet. A result's structured content reaches a script as sent, as before. A script's demand fetch of an MCPL peer's references reads the same claims as its view, as a module's does, so a uri a reference nested in a block calls expired isn't fetched.
- A uri inline data marks `disposition: never` beside its data, which is withheld, is withheld from every other block of the result too, such as a link naming it, as a reference's `never` is (RFC-005 §5).
- Inline data beside an empty `uri` reaches a script as sent, from an MCPL peer as from a module, where 0.26.1 and 0.26.2 gave a script an MCPL peer's as the withheld notice. An empty uri carries nothing a credential could ride on. Inline data beside one that also claims a disposition is still withheld.
- A tool result's data that can't be read through to apply RFC-005's rules, or shown once it has been, shows `[Result not shown: the tool ran, but its result couldn't be read through, checked for references and shown, so this host withheld it.]` on the model's lane, in the stored and sent copies alike, with a log line naming the tool and the error. It was shown with none of its references' claims where the walk reading them stopped and the render's didn't, or, for data no JSON can hold, such as a cycle or a BigInt, a getter that throws, or data too deep to write, the copies' serializer threw. So a host passing such a result to the direct Agent API gets the notice where it got an exception, with the tool-result guard off; with it on, its archive of the result, and the fields it keeps of a result it withholds, still throw, as before, as does an error result whose own error field can't be read. A script meets it as a failure naming the tool, on its text route as on its structured view, and from an MCPL peer's structured view as a result it can't convert.
7 changes: 7 additions & 0 deletions changelog.d/tool-result-blocks.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
- A tool result's content array with a block agent-framework couldn't show, such as inline audio, an image with no media type or no data, or a block of a type it doesn't know, was stored, and sent to the model, as the whole array's JSON. Inline data's base64 went into the agent's context and history, and so did a block's uri, and an image beside it went as base64 text instead of an image. Each block now shows on its own, in the stored copy and the one the model reads alike: as itself, as a reference's stub, or as a marker saying what it was and why it isn't shown. Beside an image, the model reads them as blocks while the result's links and markers fit its inline cap together, and past that it reads the stored text, cut at the cap. Inline audio reads `[Audio not shown: this host's tool results carry only text and images (<type>, <size>).]`, and the other blocks read the markers the push lane shows for them. An MCPL tool's result is content whatever its blocks' types. Any other array, such as a module's that doesn't set `dataIsContent`, is content only when every element is a block of one of MCP's types (text, image, audio, resource, resource_link). Any other array is data, written as JSON as before. Results stored before the upgrade keep the text they were stored with.
- MCP's `resource_link` is a reference (RFC-005 §8.1): its `uri`, `name`, `mimeType` and `size` (as `sizeBytes`) make its record, with no disposition. On every lane, a link `fetch_reference` can fetch shows as a reference's stub, with its name, type and size. In a tool result, whose JSON showed every link's uri, one it can't fetch shows as `[resource link "<name>": <uri>, <type>; not fetched]`, keeping the uri, the agent's only handle on it, as a standard MCP server's link shows. That holds at any depth in a tool result's data too, where a link's fields were written as data, and a link whose uri is empty, null or not a string is the marker that says so. A link that names no uri at all is the marker as a block of a content array the model reads, and at depth, or in a script's view of a module's result, stays data, as before, since its fields hold no uri this reads. That covers a uri that isn't http(s), one off its server's origin, one from a server with no address, and a link no server delivered. Where the uri may carry a credential, the link shows a stub instead (under Changed), and so does a link whose uri is a `data:` uri, which holds its content rather than naming where it lies. On every other lane, a link that can't be fetched shows the stub that says why it can't be fetched, with no uri, where the push lane showed an unknown-block marker. A link is registered with the server that sent it, but it's never fetched eagerly or on a script's demand: a uri a link names is fetched only when a block of the result that isn't a link names it too. A link's claims never replace a reference's in the registry, so a later link to a uri can't lift its expiry or its digest check.
- A reference's stub now offers `fetch_reference` only where a fetch can reach the reference through a server that delivered it and the reference hasn't expired, as the servers' configs have it. Where two frameworks in one process configure one server id at different urls, a stub reads the address recorded most recently by a framework that hasn't since removed the server or stopped. A framework records its servers' addresses as it configures them and again as it starts. Otherwise it ends with why `fetch_reference` would refuse, without what calling again can do. The stub reads an expiry from the references in its result where they name its uri, and the fetch from the reference as the host last recorded it, so where one result names a uri both expired and fresh, the stub says it's expired while a fetch can succeed. A fetch's own limits, a spent budget or a claim over its ceiling, still apply when it's made. That covers a server with no address, such as a stdio one, a server the host doesn't know, one whose address doesn't parse, a uri off its server's origin, one with a user name or password, or a scheme the fetcher doesn't fetch. A third-party origin and a refused scheme are described there without naming the origins or the scheme. Before, the stub offered the fetch for any reference a server delivered. A server's id in a stub, or in `fetch_reference`'s refusal, is bounded and stripped of controls, as a label is, and so is a scheme in that refusal.
- A label in a stub (a name, a type, a server's id, a provenance or a saved path) loses any line or paragraph separator (U+2028, U+2029), as it loses control characters.
- A script's demand fetch of a reference not yet fetched reads that reference's own digest and expiry, where it read whatever an earlier reference to the same uri had claimed.
- Inline data beside an empty `uri` is shown as any inline data is, on every lane, where it was withheld as data that also claimed a link.
- On the tool-result lane, an image whose data or media type is an empty string shows its marker, as on the push lane, where it was sent to the model as an image with that field empty.
2 changes: 1 addition & 1 deletion src/agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2096,7 +2096,7 @@ export class Agent {
toolUseId: r.id,
content: isError
? truncateForHistory(typeof error === 'string' ? error.toWellFormed() : error as string, DEFAULT_TOOL_RESULT_INLINE_MAX_CHARS)
: nonErrorResultText(r.result, DEFAULT_TOOL_RESULT_INLINE_MAX_CHARS),
: nonErrorResultText(r.result, DEFAULT_TOOL_RESULT_INLINE_MAX_CHARS, { toolName: r.name }),
isError,
};
});
Expand Down
Loading
Loading