diff --git a/packages/opencode/test/session/llm.test.ts b/packages/opencode/test/session/llm.test.ts index 5202c06dd9..d393560720 100644 --- a/packages/opencode/test/session/llm.test.ts +++ b/packages/opencode/test/session/llm.test.ts @@ -3,7 +3,6 @@ import path from "path" import { tool, type ModelMessage } from "ai" import z from "zod" import { LLM } from "../../src/session/llm" -import { Global } from "../../src/global" import { Instance } from "../../src/project/instance" import { Provider } from "../../src/provider/provider" import { ProviderTransform } from "../../src/provider/transform" @@ -535,6 +534,130 @@ describe("session.llm.stream", () => { }) }) + test("accepts user image attachments as data URLs for OpenAI models", async () => { + const server = state.server + if (!server) { + throw new Error("Server not initialized") + } + + const source = await loadFixture("openai", "gpt-5.2") + const model = source.model + const chunks = [ + { + type: "response.created", + response: { + id: "resp-data-url", + created_at: Math.floor(Date.now() / 1000), + model: model.id, + service_tier: null, + }, + }, + { + type: "response.output_text.delta", + item_id: "item-data-url", + delta: "Looks good", + logprobs: null, + }, + { + type: "response.completed", + response: { + incomplete_details: null, + usage: { + input_tokens: 1, + input_tokens_details: null, + output_tokens: 1, + output_tokens_details: null, + }, + service_tier: null, + }, + }, + ] + const request = waitRequest("/responses", createEventResponse(chunks, true)) + const image = `data:image/png;base64,${Buffer.from( + await Bun.file(path.join(import.meta.dir, "../tool/fixtures/large-image.png")).arrayBuffer(), + ).toString("base64")}` + + 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: ["openai"], + provider: { + openai: { + name: "OpenAI", + env: ["OPENAI_API_KEY"], + npm: "@ai-sdk/openai", + api: "https://api.openai.com/v1", + models: { + [model.id]: model, + }, + options: { + apiKey: "test-openai-key", + baseURL: `${server.url.origin}/v1`, + }, + }, + }, + }), + ) + }, + }) + + await Instance.provide({ + directory: tmp.path, + fn: async () => { + const resolved = await Provider.getModel(ProviderID.openai, ModelID.make(model.id)) + const sessionID = SessionID.make("session-test-data-url") + const agent = { + name: "test", + mode: "primary", + options: {}, + permission: [{ permission: "*", pattern: "*", action: "allow" }], + } satisfies Agent.Info + + const user = { + id: MessageID.make("user-data-url"), + sessionID, + role: "user", + time: { created: Date.now() }, + agent: agent.name, + model: { providerID: ProviderID.make("openai"), modelID: resolved.id }, + } satisfies MessageV2.User + + const stream = await LLM.stream({ + user, + sessionID, + model: resolved, + agent, + system: ["You are a helpful assistant."], + abort: new AbortController().signal, + messages: [ + { + role: "user", + content: [ + { type: "text", text: "Describe this image" }, + { + type: "file", + mediaType: "image/png", + filename: "large-image.png", + data: image, + }, + ], + }, + ] as ModelMessage[], + tools: {}, + }) + + for await (const _ of stream.fullStream) { + } + + const capture = await request + expect(capture.url.pathname.endsWith("/responses")).toBe(true) + }, + }) + }) + test("sends messages API payload for Anthropic models", async () => { const server = state.server if (!server) {