diff --git a/packages/app/e2e/commands/input-focus.spec.ts b/packages/app/e2e/commands/input-focus.spec.ts index 4ba1aa3e6908..a3b7bc7c17ce 100644 --- a/packages/app/e2e/commands/input-focus.spec.ts +++ b/packages/app/e2e/commands/input-focus.spec.ts @@ -7,7 +7,9 @@ test("ctrl+l focuses the prompt", async ({ page, gotoSession }) => { const prompt = page.locator(promptSelector) await expect(prompt).toBeVisible() - await page.locator("main").click({ position: { x: 5, y: 5 } }) + await prompt.evaluate((node) => { + if (node instanceof HTMLElement) node.blur() + }) await expect(prompt).not.toBeFocused() await page.keyboard.press("Control+L") diff --git a/packages/app/src/components/prompt-input.tsx b/packages/app/src/components/prompt-input.tsx index eedbc91cfdbe..9bc4ac22c232 100644 --- a/packages/app/src/components/prompt-input.tsx +++ b/packages/app/src/components/prompt-input.tsx @@ -898,7 +898,8 @@ export const PromptInput: Component = (props) => { const slashMatch = rawText.match(/^\/(\S*)$/) if (atMatch) { - atOnInput(atMatch[1]) + const mentionQuery = atMatch[1].replace(/\\/g, "/") + atOnInput(mentionQuery) setStore("popover", "at") } else if (slashMatch) { slashOnInput(slashMatch[1]) diff --git a/packages/opencode/script/seed-e2e.ts b/packages/opencode/script/seed-e2e.ts index f5bd7194f255..f4599a8fe27b 100644 --- a/packages/opencode/script/seed-e2e.ts +++ b/packages/opencode/script/seed-e2e.ts @@ -10,21 +10,16 @@ const now = Date.now() const seed = async () => { const { Instance } = await import("../src/project/instance") const { InstanceBootstrap } = await import("../src/project/bootstrap") - const { Config } = await import("../src/config/config") const { Session } = await import("../src/session") const { MessageID, PartID } = await import("../src/session/schema") const { Project } = await import("../src/project/project") const { ModelID, ProviderID } = await import("../src/provider/schema") - const { ToolRegistry } = await import("../src/tool/registry") try { await Instance.provide({ directory: dir, init: InstanceBootstrap, fn: async () => { - await Config.waitForDependencies() - await ToolRegistry.ids() - const session = await Session.create({ title }) const messageID = MessageID.ascending() const partID = PartID.ascending() diff --git a/packages/opencode/src/server/projectors.ts b/packages/opencode/src/server/projectors.ts index eb85a8017f87..c685ed80fb6d 100644 --- a/packages/opencode/src/server/projectors.ts +++ b/packages/opencode/src/server/projectors.ts @@ -24,5 +24,3 @@ export function initProjectors() { }, }) } - -initProjectors() diff --git a/packages/opencode/src/session/message-v2.ts b/packages/opencode/src/session/message-v2.ts index 61c159646d88..ea95c0f1cd35 100644 --- a/packages/opencode/src/session/message-v2.ts +++ b/packages/opencode/src/session/message-v2.ts @@ -619,7 +619,9 @@ export namespace MessageV2 { attachments?: Array<{ mime: string; url: string }> } const attachments = (outputObject.attachments ?? []).filter((attachment) => { - return attachment.url.startsWith("data:") && attachment.url.includes(",") + if (!attachment.url.startsWith("data:") || !attachment.url.includes(",")) return false + if (attachment.mime === "application/pdf" && !model.capabilities.input.pdf) return false + return true }) return { diff --git a/packages/opencode/test/session/message-v2.test.ts b/packages/opencode/test/session/message-v2.test.ts index 64a5d3e4b257..be3371934a0d 100644 --- a/packages/opencode/test/session/message-v2.test.ts +++ b/packages/opencode/test/session/message-v2.test.ts @@ -359,6 +359,85 @@ describe("session.message-v2.toModelMessage", () => { ]) }) + test("omits pdf tool-result attachments for models without pdf input support", async () => { + const userID = "m-user" + const assistantID = "m-assistant" + + const input: MessageV2.WithParts[] = [ + { + info: userInfo(userID), + parts: [ + { + ...basePart(userID, "u1"), + type: "text", + text: "read pdf", + }, + ] as MessageV2.Part[], + }, + { + info: assistantInfo(assistantID, userID), + parts: [ + { + ...basePart(assistantID, "a1"), + type: "tool", + callID: "call-1", + tool: "read", + state: { + status: "completed", + input: { filePath: "debug/cv-print-test.pdf" }, + output: "PDF read successfully", + title: "Read PDF", + metadata: {}, + time: { start: 0, end: 1 }, + attachments: [ + { + ...basePart(assistantID, "file-1"), + type: "file", + mime: "application/pdf", + filename: "cv-print-test.pdf", + url: "data:application/pdf;base64,Zm9v", + }, + ], + }, + }, + ] as MessageV2.Part[], + }, + ] + + expect(await MessageV2.toModelMessages(input, model)).toStrictEqual([ + { + role: "user", + content: [{ type: "text", text: "read pdf" }], + }, + { + role: "assistant", + content: [ + { + type: "tool-call", + toolCallId: "call-1", + toolName: "read", + input: { filePath: "debug/cv-print-test.pdf" }, + providerExecuted: undefined, + }, + ], + }, + { + role: "tool", + content: [ + { + type: "tool-result", + toolCallId: "call-1", + toolName: "read", + output: { + type: "content", + value: [{ type: "text", text: "PDF read successfully" }], + }, + }, + ], + }, + ]) + }) + test("omits provider metadata when assistant model differs", async () => { const userID = "m-user" const assistantID = "m-assistant" diff --git a/packages/opencode/test/tool/edit.test.ts b/packages/opencode/test/tool/edit.test.ts index 96d41400e3cf..8a9b9e8545c0 100644 --- a/packages/opencode/test/tool/edit.test.ts +++ b/packages/opencode/test/tool/edit.test.ts @@ -89,7 +89,12 @@ describe("tool.edit", () => { const { FileWatcher } = await import("../../src/file/watcher") const events: string[] = [] + let resolveUpdated!: () => void + const updated = new Promise((resolve) => { + resolveUpdated = resolve + }) const unsubUpdated = Bus.subscribe(FileWatcher.Event.Updated, () => events.push("updated")) + const unsubUpdatedOnce = Bus.subscribe(FileWatcher.Event.Updated, () => resolveUpdated()) const edit = await EditTool.init() await edit.execute( @@ -101,7 +106,9 @@ describe("tool.edit", () => { ctx, ) + await updated expect(events).toContain("updated") + unsubUpdatedOnce() unsubUpdated() }, }) @@ -305,7 +312,12 @@ describe("tool.edit", () => { const { FileWatcher } = await import("../../src/file/watcher") const events: string[] = [] + let resolveUpdated!: () => void + const updated = new Promise((resolve) => { + resolveUpdated = resolve + }) const unsubUpdated = Bus.subscribe(FileWatcher.Event.Updated, () => events.push("updated")) + const unsubUpdatedOnce = Bus.subscribe(FileWatcher.Event.Updated, () => resolveUpdated()) const edit = await EditTool.init() await edit.execute( @@ -317,7 +329,9 @@ describe("tool.edit", () => { ctx, ) + await updated expect(events).toContain("updated") + unsubUpdatedOnce() unsubUpdated() }, })