diff --git a/packages/opencode/test/session/llm.test.ts b/packages/opencode/test/session/llm.test.ts
index cbf767b4bd..8719e348d7 100644
--- a/packages/opencode/test/session/llm.test.ts
+++ b/packages/opencode/test/session/llm.test.ts
@@ -13,7 +13,7 @@ import { ProviderID, ModelID } from "../../src/provider/schema"
import { Filesystem } from "../../src/util/filesystem"
import { tmpdir } from "../fixture/fixture"
import type { Agent } from "../../src/agent/agent"
-import type { MessageV2 } from "../../src/session/message-v2"
+import { MessageV2 } from "../../src/session/message-v2"
import { SessionID, MessageID } from "../../src/session/schema"
import { AppRuntime } from "../../src/effect/app-runtime"
@@ -909,6 +909,269 @@ describe("session.llm.stream", () => {
})
})
+ test("sends anthropic tool_use blocks with tool_result immediately after them", async () => {
+ const server = state.server
+ if (!server) {
+ throw new Error("Server not initialized")
+ }
+
+ const source = await loadFixture("anthropic", "claude-opus-4-6")
+ const model = source.model
+ const chunks = [
+ {
+ type: "message_start",
+ message: {
+ id: "msg-tool-order",
+ model: model.id,
+ usage: {
+ input_tokens: 3,
+ cache_creation_input_tokens: null,
+ cache_read_input_tokens: null,
+ },
+ },
+ },
+ {
+ type: "content_block_start",
+ index: 0,
+ content_block: { type: "text", text: "" },
+ },
+ {
+ type: "content_block_delta",
+ index: 0,
+ delta: { type: "text_delta", text: "ok" },
+ },
+ { type: "content_block_stop", index: 0 },
+ {
+ type: "message_delta",
+ delta: { stop_reason: "end_turn", stop_sequence: null, container: null },
+ usage: {
+ input_tokens: 3,
+ output_tokens: 2,
+ cache_creation_input_tokens: null,
+ cache_read_input_tokens: null,
+ },
+ },
+ { type: "message_stop" },
+ ]
+ const request = waitRequest("/messages", createEventResponse(chunks))
+
+ await using tmp = await tmpdir({
+ init: async (dir) => {
+ await Bun.write(
+ path.join(dir, "opencode.json"),
+ JSON.stringify({
+ $schema: "https://opencode.ai/config.json",
+ enabled_providers: ["anthropic"],
+ provider: {
+ anthropic: {
+ name: "Anthropic",
+ env: ["ANTHROPIC_API_KEY"],
+ npm: "@ai-sdk/anthropic",
+ api: "https://api.anthropic.com/v1",
+ models: {
+ [model.id]: model,
+ },
+ options: {
+ apiKey: "test-anthropic-key",
+ baseURL: `${server.url.origin}/v1`,
+ },
+ },
+ },
+ }),
+ )
+ },
+ })
+
+ await Instance.provide({
+ directory: tmp.path,
+ fn: async () => {
+ const resolved = await getModel(ProviderID.make("anthropic"), ModelID.make(model.id))
+ const sessionID = SessionID.make("session-test-anthropic-tools")
+ const agent = {
+ name: "test",
+ mode: "primary",
+ options: {},
+ permission: [{ permission: "*", pattern: "*", action: "allow" }],
+ } satisfies Agent.Info
+ const user = {
+ id: MessageID.make("user-anthropic-tools"),
+ sessionID,
+ role: "user",
+ time: { created: Date.now() },
+ agent: agent.name,
+ model: { providerID: ProviderID.make("anthropic"), modelID: resolved.id, variant: "max" },
+ } satisfies MessageV2.User
+
+ const input = [
+ {
+ info: {
+ id: "msg_user",
+ sessionID,
+ role: "user",
+ time: { created: 1 },
+ agent: "gentleman",
+ model: { providerID: "anthropic", modelID: "claude-opus-4-6", variant: "max" },
+ },
+ parts: [
+ {
+ id: "p_user",
+ sessionID,
+ messageID: "msg_user",
+ type: "text",
+ text: "Can you check whether there are any PDF files in my home directory?",
+ },
+ ],
+ },
+ {
+ info: {
+ id: "msg_call",
+ sessionID,
+ parentID: "msg_user",
+ role: "assistant",
+ mode: "gentleman",
+ agent: "gentleman",
+ variant: "max",
+ path: { cwd: "/root", root: "/" },
+ cost: 0,
+ tokens: { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } },
+ modelID: "claude-opus-4-6",
+ providerID: "anthropic",
+ time: { created: 2, completed: 3 },
+ finish: "tool-calls",
+ },
+ parts: [
+ {
+ id: "p_step",
+ sessionID,
+ messageID: "msg_call",
+ type: "step-start",
+ },
+ {
+ id: "p_read",
+ sessionID,
+ messageID: "msg_call",
+ type: "tool",
+ tool: "read",
+ callID: "toolu_01N8mDEzG8DSTs7UPHFtmgCT",
+ state: {
+ status: "completed",
+ input: { filePath: "/root" },
+ output: "/root",
+ metadata: {},
+ title: "root",
+ time: { start: 10, end: 11 },
+ },
+ },
+ {
+ id: "p_glob",
+ sessionID,
+ messageID: "msg_call",
+ type: "tool",
+ tool: "glob",
+ callID: "toolu_01APxrADs7VozN8uWzw9WwHr",
+ state: {
+ status: "completed",
+ input: { pattern: "**/*.pdf", path: "/root" },
+ output: "No files found",
+ metadata: {},
+ title: "root",
+ time: { start: 12, end: 13 },
+ },
+ },
+ {
+ id: "p_text",
+ sessionID,
+ messageID: "msg_call",
+ type: "text",
+ text: "I checked your home directory and looked for PDF files.",
+ time: { start: 14, end: 15 },
+ },
+ ],
+ },
+ ] as any[]
+
+ await drain({
+ user,
+ sessionID,
+ model: resolved,
+ agent,
+ system: [],
+ messages: await MessageV2.toModelMessages(input as any, resolved),
+ tools: {
+ read: tool({
+ description: "Stub read tool",
+ inputSchema: z.object({
+ filePath: z.string(),
+ }),
+ execute: async () => ({ output: "stub" }),
+ }),
+ glob: tool({
+ description: "Stub glob tool",
+ inputSchema: z.object({
+ pattern: z.string(),
+ path: z.string().optional(),
+ }),
+ execute: async () => ({ output: "stub" }),
+ }),
+ },
+ })
+
+ const capture = await request
+ const body = capture.body
+
+ expect(capture.url.pathname.endsWith("/messages")).toBe(true)
+ expect(body.messages).toStrictEqual([
+ {
+ role: "user",
+ content: [{ type: "text", text: "Can you check whether there are any PDF files in my home directory?" }],
+ },
+ {
+ role: "assistant",
+ content: [
+ {
+ type: "text",
+ text: "I checked your home directory and looked for PDF files.",
+ cache_control: {
+ type: "ephemeral",
+ },
+ },
+ {
+ type: "tool_use",
+ id: "toolu_01N8mDEzG8DSTs7UPHFtmgCT",
+ name: "read",
+ input: { filePath: "/root" },
+ },
+ {
+ type: "tool_use",
+ id: "toolu_01APxrADs7VozN8uWzw9WwHr",
+ name: "glob",
+ input: { pattern: "**/*.pdf", path: "/root" },
+ },
+ ],
+ },
+ {
+ role: "user",
+ content: [
+ {
+ type: "tool_result",
+ tool_use_id: "toolu_01N8mDEzG8DSTs7UPHFtmgCT",
+ content: "/root",
+ },
+ {
+ type: "tool_result",
+ tool_use_id: "toolu_01APxrADs7VozN8uWzw9WwHr",
+ content: "No files found",
+ cache_control: {
+ type: "ephemeral",
+ },
+ },
+ ],
+ },
+ ])
+ },
+ })
+ })
+
test("sends Google API payload for Gemini models", async () => {
const server = state.server
if (!server) {