cloudflare / cloudflare/agents

Think: `TurnResult` in wait mode lacks the parsed `output` when `TurnConfig.output` is set

Open
#2,263 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
TypeScript
Stars
5.6k
Forks
711
Avg merge
1d 20h
Merged PRs (30d)
53

Description

## Summary

`TurnConfig.output` forwards an AI SDK structured-output spec to `streamText`. In `mode: "wait"`, `TurnResult` does not carry the parsed value. Callers re-parse the last assistant text.

## Environment

- `@cloudflare/think@0.17.0`
- `runTurn({ mode: "wait" })` with `beforeTurn` returning `{ output: Output.object({ schema, name }) }`

## What the types allow

`TurnConfig` (`dist/index-B7zEkBBM.d.ts`):

```ts
interface TurnConfig {
// …
/**
* Optional structured-output specification (AI SDK `output`).
* Forwarded to `streamText` so the model's final response is parsed
* against the supplied schema. Use the AI SDK's `Output.object({ schema })`
* / `Output.text()` helpers.
*/
output?: Parameters[0]["output"];
}
```

`TurnResult` in wait mode:

```ts
type TurnResult = SaveMessagesResult & {
message?: SessionMessage;
continuation: boolean;
};
```

`SaveMessagesResult` (`agents/dist/chat/index.d.ts`) is `{ requestId, status, error? }`. There is no `output` (or `object`, `experimental_output`) field.

`streamText` itself returns the parsed value on the result. Think drops it on the way to `TurnResult`.

## Reproduction

```ts
override async beforeTurn(ctx: TurnContext) {
return {
...(await super.beforeTurn(ctx)),
output: Output.object({ schema: TurnSchema, name: "Turn" }),
activeTools: [],
};
}

const result = await this.runTurn({
mode: "wait",
input: [{ id, role: "user", parts: [{ type: "text", text: q }] }],
});
// result.status === "completed"
// "output" in result === false
```

The parsed object is not on `result`. The assistant message text is a JSON string the caller must `JSON.parse` and run through the same Zod schema again.

## Workaround

Take the assistant text and parse it:

```ts
const text = assistant.parts
.filter((part): part is { type: "text"; text: string } => part.type === "text")
.map((part) => part.text)
.join("");
const parsed = schema.parse(JSON.parse(text));
```

## Suggested fix

When `TurnConfig.output` is set and the turn completes, put the AI SDK parsed value on `TurnResult` (e.g. `output: unknown`, or a generic `TurnResult`). Callers that passed a Zod schema should not have to parse the transcript a second time.

Contributor guide

Open the contributing guide

Research direction

Trace the runTurn({ mode: "wait" }) path from TurnConfig and TurnResult, comparing it with streamText's result and SaveMessagesResult in agents/dist/chat/index.d.ts. Verify how the completed turn is assembled and add or run coverage for the provided structured-output reproduction. Done means the wait-mode result exposes the parsed output while preserving its existing status and message fields.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
ai, api
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
68/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.