diff --git a/src/domain/threads/reference.ts b/src/domain/threads/reference.ts index f006af7b..ffaab660 100644 --- a/src/domain/threads/reference.ts +++ b/src/domain/threads/reference.ts @@ -1,6 +1,7 @@ import type { Thread } from "../../generated/app-server/v2/Thread"; import type { ThreadItem } from "../../generated/app-server/v2/ThreadItem"; import type { Turn } from "../../generated/app-server/v2/Turn"; +import type { UserInput } from "../../generated/app-server/v2/UserInput"; import { inputToText, shortThreadId } from "../../utils"; import { getThreadTitle } from "./model"; @@ -18,6 +19,12 @@ export interface ReferencedThreadTurn { assistantText: string | null; } +export interface ReferencedThreadInput { + input: UserInput[]; + referencedThread: ReferencedThreadDisplay; + status: string; +} + export function referencedThreadTurns(turns: Turn[]): ReferencedThreadTurn[] { return [...turns] .sort((a, b) => (a.startedAt ?? 0) - (b.startedAt ?? 0)) @@ -69,6 +76,20 @@ export function referencedThreadDisplay(thread: Thread, count: number): Referenc }; } +export function referencedThreadInput( + thread: Thread, + turns: readonly ReferencedThreadTurn[], + userRequest: string, + messageInput: UserInput[], +): ReferencedThreadInput { + const prompt = referencedThreadPrompt(thread, [...turns], userRequest); + return { + input: [{ type: "text", text: prompt, text_elements: [] }, ...messageInput.filter((item) => item.type !== "text")], + referencedThread: referencedThreadDisplay(thread, turns.length), + status: referencedThreadStatus(thread, turns.length), + }; +} + export function referencedThreadDisplayFromPrompt(text: string): { text: string; reference: ReferencedThreadDisplay } | null { const headerStart = text.indexOf("[Codex Panel referenced thread]"); const headerEnd = text.indexOf("[/Codex Panel referenced thread]"); diff --git a/src/features/chat/view.ts b/src/features/chat/view.ts index 453d3c27..6d8e8605 100644 --- a/src/features/chat/view.ts +++ b/src/features/chat/view.ts @@ -31,9 +31,7 @@ import { type ChatState, } from "./chat-state"; import { - referencedThreadDisplay, - referencedThreadPrompt, - referencedThreadStatus, + referencedThreadInput as buildReferencedThreadInput, referencedThreadTurns, REFERENCED_THREAD_TURN_LIMIT, type ReferencedThreadDisplay, @@ -910,13 +908,9 @@ export class CodexChatView extends ItemView { this.addSystemMessage("Referenced thread has no readable conversation turns."); return null; } - const prompt = referencedThreadPrompt(thread, turns, message); - const messageInput = this.composerController.codexInput(message); - this.setStatus(referencedThreadStatus(thread, turns.length)); - return { - input: [{ type: "text", text: prompt, text_elements: [] }, ...messageInput.filter((item) => item.type !== "text")], - referencedThread: referencedThreadDisplay(thread, turns.length), - }; + const reference = buildReferencedThreadInput(thread, turns, message, this.composerController.codexInput(message)); + this.setStatus(reference.status); + return reference; } catch (error) { this.addSystemMessage(error instanceof Error ? error.message : String(error)); return null; diff --git a/tests/domain/threads/reference.test.ts b/tests/domain/threads/reference.test.ts index 07266bf8..41326c31 100644 --- a/tests/domain/threads/reference.test.ts +++ b/tests/domain/threads/reference.test.ts @@ -2,7 +2,12 @@ import { describe, expect, it } from "vitest"; import type { Thread } from "../../../src/generated/app-server/v2/Thread"; import type { Turn } from "../../../src/generated/app-server/v2/Turn"; -import { referencedThreadDisplayFromPrompt, referencedThreadPrompt, referencedThreadTurns } from "../../../src/domain/threads/reference"; +import { + referencedThreadDisplayFromPrompt, + referencedThreadInput, + referencedThreadPrompt, + referencedThreadTurns, +} from "../../../src/domain/threads/reference"; function thread(overrides: Partial = {}): Thread { return { @@ -97,4 +102,17 @@ describe("thread reference context", () => { }, }); }); + + it("builds slash command input while preserving non-text attachments", () => { + const source = thread(); + const input = referencedThreadInput(source, [{ userText: "元の依頼", assistantText: "回答" }], "この続きです", [ + { type: "text", text: "この続きです", text_elements: [] }, + { type: "mention", name: "Note", path: "Note.md" }, + ]); + + expect(input.status).toBe("Referencing 019abcde (1/20 turns)."); + expect(input.referencedThread).toMatchObject({ threadId: source.id, title: "参照元", includedTurns: 1 }); + expect(input.input[0]).toMatchObject({ type: "text" }); + expect(input.input[1]).toEqual({ type: "mention", name: "Note", path: "Note.md" }); + }); });