Move thread reference input derivation

This commit is contained in:
murashit 2026-05-29 05:54:42 +09:00
parent 8632c629e5
commit 8d5f89636c
3 changed files with 44 additions and 11 deletions

View file

@ -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]");

View file

@ -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;

View file

@ -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> = {}): 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" });
});
});