Skip to content
Draft
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
5 changes: 5 additions & 0 deletions .changeset/durable-agent-stream-result-totalusage.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@workflow/ai": minor
---

Expose `totalUsage` and `finishReason` on the `DurableAgent.stream()` result, mirroring the AI SDK's `GenerateTextResult`/`StreamTextResult` and the existing `onFinish` event payload.
24 changes: 24 additions & 0 deletions packages/ai/src/agent/durable-agent-compat.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1348,6 +1348,30 @@ describe('DurableAgent (ToolLoopAgent compat)', () => {
}
`);
});

it('should expose finishReason and totalUsage on the stream result', async () => {
const agent = new DurableAgent({
model: asModelFactory(mockModel),
});

const { writable } = createMockWritable();
const result = await agent.stream({
messages: [{ role: 'user' as const, content: 'test' }],
writable,
});

expect({
finishReason: result.finishReason,
inputTokens: result.totalUsage.inputTokens,
outputTokens: result.totalUsage.outputTokens,
}).toMatchInlineSnapshot(`
{
"finishReason": "stop",
"inputTokens": 3,
"outputTokens": 10,
}
`);
});
});
});

Expand Down
33 changes: 27 additions & 6 deletions packages/ai/src/agent/durable-agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -735,6 +735,16 @@ export interface DurableAgentStreamResult<
*/
toolResults: ToolResult[];

/**
* The finish reason from the last step.
*/
finishReason: FinishReason;

/**
* The total token usage across all steps.
*/
totalUsage: LanguageModelUsage;

/**
* The generated structured output. It uses the `experimental_output` specification.
* Only available when `experimental_output` is specified.
Expand Down Expand Up @@ -980,6 +990,8 @@ export class DurableAgent<TBaseTools extends ToolSet = ToolSet> {
steps,
toolCalls: [],
toolResults: [],
finishReason: 'other',
totalUsage: aggregateUsage(steps),
experimental_output: undefined as OUTPUT,
uiMessages: undefined,
};
Expand Down Expand Up @@ -1168,15 +1180,17 @@ export class DurableAgent<TBaseTools extends ToolSet = ToolSet> {
// resolved tool results), so callers can resume the conversation.
// Cast matches the existing pattern used at the end of stream().
const messages = iterMessages as unknown as ModelMessage[];
const lastStep = steps[steps.length - 1];
const totalUsage = aggregateUsage(steps);
const finishReason = lastStep?.finishReason ?? 'other';

if (mergedOnFinish && !wasAborted) {
const lastStep = steps[steps.length - 1];
await mergedOnFinish({
steps,
messages,
text: lastStep?.text ?? '',
finishReason: lastStep?.finishReason ?? 'other',
totalUsage: aggregateUsage(steps),
finishReason,
totalUsage,
experimental_context: experimentalContext,
experimental_output: undefined as OUTPUT,
});
Expand All @@ -1191,6 +1205,8 @@ export class DurableAgent<TBaseTools extends ToolSet = ToolSet> {
steps,
toolCalls: allToolCalls,
toolResults: allToolResults,
finishReason,
totalUsage,
experimental_output: undefined as OUTPUT,
uiMessages,
};
Expand Down Expand Up @@ -1328,15 +1344,18 @@ export class DurableAgent<TBaseTools extends ToolSet = ToolSet> {
}
}

const lastStep = steps[steps.length - 1];
const totalUsage = aggregateUsage(steps);
const finishReason = lastStep?.finishReason ?? 'other';

// Call onFinish callback if provided (always call, even on errors, but not on abort)
if (mergedOnFinish && !wasAborted) {
const lastStep = steps[steps.length - 1];
await mergedOnFinish({
steps,
messages: messages as ModelMessage[],
text: lastStep?.text ?? '',
finishReason: lastStep?.finishReason ?? 'other',
totalUsage: aggregateUsage(steps),
finishReason,
totalUsage,
experimental_context: experimentalContext,
experimental_output: experimentalOutput,
});
Expand All @@ -1358,6 +1377,8 @@ export class DurableAgent<TBaseTools extends ToolSet = ToolSet> {
steps,
toolCalls: lastStepToolCalls,
toolResults: lastStepToolResults,
finishReason,
totalUsage,
experimental_output: experimentalOutput,
uiMessages,
};
Expand Down
Loading