cloudflare / cloudflare/agents
Think: `TurnResult` in wait mode lacks the parsed `output` when `TurnConfig.output` is set
- 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
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