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
7 changes: 7 additions & 0 deletions .changeset/turn-owned-wait-results.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@cloudflare/think": patch
---

Return the assistant message produced by each `runTurn({ mode: "wait" })` call, even when another turn or response hook changes the session's latest message before the caller resumes. The message is matched to the turn's request ID, so a later persist that inherits the call's async context cannot replace it.

A completed turn that persists no assistant message (for example, a continuation whose only output is structured) now returns no `message`, instead of the previous assistant message in the session.
29 changes: 23 additions & 6 deletions packages/think/src/tests/agents/think-session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -232,8 +232,17 @@ function captureModelCallSettings(options: unknown): CapturedModelCallSettings {
};
}

function createMockModel(
response: string,
/** Typed model-call subset used by prompt-sensitive test models. */
export type MockModelCallOptions = {
prompt?: Array<{
role?: string;
content?: string | Array<{ type?: string; text?: string }>;
}>;
};

/** Create a streaming text model with static or prompt-derived output. */
export function createMockModel(
response: string | ((callOptions: MockModelCallOptions) => string),
options: MockModelOptions = {}
): LanguageModel {
return {
Expand All @@ -244,8 +253,10 @@ function createMockModel(
doGenerate() {
throw new Error("doGenerate not implemented in mock");
},
doStream(callOptions: unknown) {
doStream(callOptions: MockModelCallOptions) {
options.onCall?.(captureModelCallSettings(callOptions));
const responseText =
typeof response === "function" ? response(callOptions) : response;
_mockCallCount++;
const callId = _mockCallCount;
const stream = new ReadableStream({
Expand All @@ -255,7 +266,7 @@ function createMockModel(
controller.enqueue({
type: "text-delta",
id: `t-${callId}`,
delta: response
delta: responseText
});
controller.enqueue({ type: "text-end", id: `t-${callId}` });
controller.enqueue({
Expand Down Expand Up @@ -4366,7 +4377,8 @@ export class ThinkConfigInSessionAgent extends Think<Cloudflare.Env> {
// Extends Think with tools configured for tool integration testing.
// Uses a mock model that calls the "echo" tool on first invocation.

function createToolCallingMockModel(
/** Create a two-step model that calls `echo` before its final text answer. */
export function createToolCallingMockModel(
toolInput = JSON.stringify({ message: "hello" }),
onPrompt?: (prompt: string) => void
): LanguageModel {
Expand Down Expand Up @@ -6001,7 +6013,12 @@ export class ThinkProgrammaticTestAgent extends Think {
cause: new Error(message)
});
}
return super.continueLastTurn(body, options);
const result = await super.continueLastTurn(body, options);
return {
requestId: result.requestId,
status: result.status,
...(result.error !== undefined && { error: result.error })
};
}

override getModel(): LanguageModel {
Expand Down
Loading
Loading