Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion docs/architecture/28-model-driven-tool-discovery.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ mechanism.
## Durability and resume

Exposure state is persisted as a private durable checkpoint event,
`AGENT_RUN_TOOL_EXPOSURE_CHECKPOINT`, carrying a versioned
`AGENT_RUN_TOOL_EXPOSURE_CHECKPOINTED`, carrying a versioned
`ToolExposureCheckpoint`. `restoreToolExposureState` rehydrates it, so a resumed
run keeps the schemas it had already loaded instead of starting from an empty
set. Restoration returns exposure, not authorization: execution still re-checks
Expand Down
2 changes: 1 addition & 1 deletion src/agent/ag-ui/chunk-encoder.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ describe("agent/ag-ui-chunk-encoder", () => {
payload: {
toolCallId: "tool-1",
input: { query: "ag-ui" },
result: { ok: true },
content: { ok: true },
},
}],
);
Expand Down
8 changes: 4 additions & 4 deletions src/agent/ag-ui/encoder.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ describe("agent/ag-ui-encoder", () => {
}),
[{
event: "ToolCallResult",
payload: { toolCallId: "tool-1", result: { ok: true } },
payload: { toolCallId: "tool-1", content: { ok: true } },
}],
);
assertEquals(
Expand Down Expand Up @@ -313,7 +313,7 @@ describe("agent/ag-ui-encoder", () => {
},
{
event: "ToolCallResult",
payload: { toolCallId: "tool-2", result: { error: "invalid url" }, isError: true },
payload: { toolCallId: "tool-2", content: { error: "invalid url" }, isError: true },
},
],
);
Expand All @@ -324,7 +324,7 @@ describe("agent/ag-ui-encoder", () => {
}),
[{
event: "ToolCallResult",
payload: { toolCallId: "tool-3", result: { error: "Tool output denied" }, isError: true },
payload: { toolCallId: "tool-3", content: { error: "Tool output denied" }, isError: true },
}],
);
});
Expand Down Expand Up @@ -499,7 +499,7 @@ describe("agent/ag-ui-encoder", () => {
event: "ToolCallResult",
payload: {
toolCallId: "tool-provider",
result: { type: "web_search_result", answer: "resident" },
content: { type: "web_search_result", answer: "resident" },
},
}],
);
Expand Down
10 changes: 7 additions & 3 deletions src/agent/ag-ui/encoder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -603,16 +603,20 @@
return events;
}

/**
* Tool results carry the canonical `content` field. The value is passed through
* unchanged, which is exactly what the API stores for a legacy `result` field.
*/
function createToolResultEvent(
toolCallId: unknown,
result: Record<string, unknown> | unknown,
content: Record<string, unknown> | unknown,

Check warning on line 612 in src/agent/ag-ui/encoder.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

'unknown' overrides all other types in this union type.

See more on https://sonarcloud.io/project/issues?id=veryfront_veryfront-code&issues=AaCerp96zwde12-ALqxK&open=AaCerp96zwde12-ALqxK&pullRequest=4495
isError = false,
): AgUiEncodedEvent {
return {
event: "ToolCallResult",
payload: {
toolCallId,
result,
content,
Comment thread
kojiwakayama marked this conversation as resolved.
...(isError ? { isError: true } : {}),
},
};
Expand Down Expand Up @@ -958,7 +962,7 @@
event: "ToolCallResult",
payload: {
toolCallId: event.toolCallId,
result: {
content: {
error: typeof event.errorText === "string" ? event.errorText : "Tool input failed",
},
isError: true,
Expand Down
2 changes: 1 addition & 1 deletion src/agent/ag-ui/lifecycle-adapter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,7 @@ describe("lifecycle AG-UI adapter", () => {
events.at(-1)?.payload,
{
toolCallId: "provider-1",
result: { content: "final" },
content: { content: "final" },
},
"the final provider result must reach the client even when it carries preliminary: false",
);
Expand Down
8 changes: 4 additions & 4 deletions src/agent/ag-ui/lifecycle-adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,7 @@ export function createLifecycleAgUiAdapter(input: {
event: "ToolCallResult",
payload: {
toolCallId: event.toolCallId,
result: { error: "Tool input was rejected before handoff" },
content: { error: "Tool input was rejected before handoff" },
isError: true,
},
},
Expand All @@ -282,7 +282,7 @@ export function createLifecycleAgUiAdapter(input: {
event: "ToolCallResult",
payload: {
toolCallId: event.toolCallId,
result: safeJson(event.output),
content: safeJson(event.output),
...(event.isError ? { isError: true } : {}),
},
}];
Expand All @@ -292,7 +292,7 @@ export function createLifecycleAgUiAdapter(input: {
event: "ToolCallResult",
payload: {
toolCallId: event.toolCallId,
result: { error: "Tool output denied" },
content: { error: "Tool output denied" },
isError: true,
},
}];
Expand All @@ -302,7 +302,7 @@ export function createLifecycleAgUiAdapter(input: {
event: "ToolCallResult",
payload: {
toolCallId: event.toolCallId,
result: { error: "Provider tool execution was cancelled" },
content: { error: "Provider tool execution was cancelled" },
isError: true,
},
}];
Expand Down
2 changes: 1 addition & 1 deletion src/agent/ag-ui/runtime-event-encoder.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ describe("agent/ag-ui-runtime-event-encoder", () => {
payload: {
toolCallId: "tool-1",
input: { query: "ag-ui" },
result: { ok: true },
content: { ok: true },
},
}],
);
Expand Down
24 changes: 24 additions & 0 deletions src/agent/ag-ui/sse-parser.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,30 @@ describe("agent/ag-ui-sse-parser", () => {
);
});

it("serializes a raw ToolCallResult content value the same way it serialized result", async () => {
const response = createSseResponse([
'event: ToolCallResult\ndata: {"toolCallId":"tool-1","content":{"ok":true}}\n\n',
'event: ToolCallResult\ndata: {"toolCallId":"tool-2","result":{"ok":false}}\n\n',
]);

const run = await parseAgUiSseResponse(response);

assertEquals(run.events[0]?.content, '{"ok":true}');
assertEquals(run.events[1]?.content, '{"ok":false}');
});

it("keeps a null ToolCallResult content value as the serialized canonical field", async () => {
const response = createSseResponse([
'event: ToolCallResult\ndata: {"toolCallId":"tool-1","content":null}\n\n',
'event: ToolCallResult\ndata: {"toolCallId":"tool-2","content":null,"result":{"ok":true}}\n\n',
]);

const run = await parseAgUiSseResponse(response);

assertEquals(run.events[0]?.content, "null");
assertEquals(run.events[1]?.content, "null");
});

it("keeps parsing legacy raw AG-UI payloads", async () => {
const response = createSseResponse([
'id: 1\nevent: RunStarted\ndata: {"type":"RUN_STARTED"}\n\n',
Expand Down
8 changes: 6 additions & 2 deletions src/agent/ag-ui/sse-parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -180,12 +180,16 @@ function coerceWireEvent(
return { type: agUiSseEventTypes.toolCallArgs, ...payload };
case "ToolCallEnd":
return { type: agUiSseEventTypes.toolCallEnd, ...payload };
case "ToolCallResult":
case "ToolCallResult": {
// The canonical `content` field wins whenever it is present, even when it
// is null; only a frame without it falls back to the legacy `result`.
const toolResult = Object.hasOwn(payload, "content") ? payload.content : payload.result;
return {
type: agUiSseEventTypes.toolCallResult,
...payload,
...(payload.result !== undefined ? { content: serializeToolResult(payload.result) } : {}),
...(toolResult !== undefined ? { content: serializeToolResult(toolResult) } : {}),
Comment thread
kojiwakayama marked this conversation as resolved.
};
}
case "Custom":
return { type: agUiSseEventTypes.custom, ...payload };
case "RunError":
Expand Down
6 changes: 3 additions & 3 deletions src/agent/conversation/durable.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ const BRANCH_ID = "44444444-4444-4444-8444-444444444444";

function modelCallContextEvent(content: string) {
return {
type: "AGENT_RUN_MODEL_CALL_CONTEXT",
type: "AGENT_RUN_MODEL_CALL_CONTEXT_RECORDED",
messages: [{ role: "system", content }],
};
}
Expand Down Expand Up @@ -663,7 +663,7 @@ describe("agent/durable", () => {
let accessorReads = 0;
let fetchCalls = 0;
const event = {
type: "AGENT_RUN_MODEL_CALL_CONTEXT",
type: "AGENT_RUN_MODEL_CALL_CONTEXT_RECORDED",
messages: [],
extra: "invalid",
};
Expand Down Expand Up @@ -702,7 +702,7 @@ describe("agent/durable", () => {
let accessorReads = 0;
let fetchCalls = 0;
const event = {
type: "AGENT_RUN_MODEL_CALL_CONTEXT",
type: "AGENT_RUN_MODEL_CALL_CONTEXT_RECORDED",
messages: [],
};
Object.defineProperty(event, "tools", {
Expand Down
6 changes: 3 additions & 3 deletions src/agent/conversation/legacy-run-read-adapter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ describe("conversation run lifecycle read adapter", () => {
event: "ToolCallResult",
payload: {
toolCallId: "legacy-fetch",
result: { error: "Stored tool call ended without a result" },
content: { error: "Stored tool call ended without a result" },
isError: true,
},
}],
Expand Down Expand Up @@ -1151,7 +1151,7 @@ describe("conversation run lifecycle read adapter", () => {
aguiEvents.filter((event) => event.event === "ToolCallResult"),
[{
event: "ToolCallResult",
payload: { toolCallId: "provider-1", result: { forecast: "sunny" } },
payload: { toolCallId: "provider-1", content: { forecast: "sunny" } },
}],
);
});
Expand Down Expand Up @@ -1363,7 +1363,7 @@ describe("conversation run lifecycle read adapter", () => {
event: "ToolCallResult",
payload: {
toolCallId: "provider-err",
result: diagnostic,
content: diagnostic,
isError: true,
},
}],
Expand Down
Loading
Loading