mirror of
https://github.com/murashit/codex-panel.git
synced 2026-07-22 06:57:10 +00:00
Keep chat request inputs behind app-server adapters
This commit is contained in:
parent
362336456c
commit
3f70f0b160
6 changed files with 83 additions and 29 deletions
26
src/app-server/request-input.ts
Normal file
26
src/app-server/request-input.ts
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
import type { UserInput } from "../generated/app-server/v2/UserInput";
|
||||
|
||||
export interface AppServerRequestMention {
|
||||
name: string;
|
||||
path: string;
|
||||
}
|
||||
|
||||
export function appServerTextInputWithMentions(
|
||||
text: string,
|
||||
mentions: readonly AppServerRequestMention[],
|
||||
skills: readonly AppServerRequestMention[] = [],
|
||||
): UserInput[] {
|
||||
return [
|
||||
...appServerTextInput(text),
|
||||
...mentions.map((mention) => ({ type: "mention" as const, name: mention.name, path: mention.path })),
|
||||
...skills.map((skill) => ({ type: "skill" as const, name: skill.name, path: skill.path })),
|
||||
];
|
||||
}
|
||||
|
||||
export function appServerTextInputWithAttachments(text: string, input: readonly UserInput[]): UserInput[] {
|
||||
return [...appServerTextInput(text), ...input.filter((item) => item.type !== "text")];
|
||||
}
|
||||
|
||||
function appServerTextInput(text: string): UserInput[] {
|
||||
return [{ type: "text", text, text_elements: [] }];
|
||||
}
|
||||
|
|
@ -1,4 +1,3 @@
|
|||
import type { UserInput } from "../../generated/app-server/v2/UserInput";
|
||||
import { shortThreadId } from "../../utils";
|
||||
import { getThreadTitle, type Thread } from "./model";
|
||||
import type { ThreadConversationSummary } from "./transcript";
|
||||
|
|
@ -23,8 +22,8 @@ export interface ReferencedThreadTurn {
|
|||
assistantText: string | null;
|
||||
}
|
||||
|
||||
export interface ReferencedThreadInput {
|
||||
input: UserInput[];
|
||||
export interface ReferencedThreadPromptBundle {
|
||||
prompt: string;
|
||||
referencedThread: ReferencedThreadDisplay;
|
||||
status: string;
|
||||
}
|
||||
|
|
@ -69,15 +68,14 @@ function referencedThreadDisplay(thread: Thread, count: number): ReferencedThrea
|
|||
};
|
||||
}
|
||||
|
||||
export function referencedThreadInput(
|
||||
export function referencedThreadPromptBundle(
|
||||
thread: Thread,
|
||||
turns: readonly ReferencedThreadTurn[],
|
||||
userRequest: string,
|
||||
messageInput: UserInput[],
|
||||
): ReferencedThreadInput {
|
||||
): ReferencedThreadPromptBundle {
|
||||
const prompt = referencedThreadPrompt(thread, [...turns], userRequest);
|
||||
return {
|
||||
input: [{ type: "text", text: prompt, text_elements: [] }, ...messageInput.filter((item) => item.type !== "text")],
|
||||
prompt,
|
||||
referencedThread: referencedThreadDisplay(thread, turns.length),
|
||||
status: referencedThreadStatus(thread, turns.length),
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import type { UserInput } from "../../../generated/app-server/v2/UserInput";
|
||||
import { appServerTextInputWithMentions, type AppServerRequestMention } from "../../../app-server/request-input";
|
||||
import type { SkillMetadata } from "../../../domain/catalog/metadata";
|
||||
import { parseObsidianWikiLink } from "../../../shared/obsidian/wikilinks";
|
||||
|
||||
|
|
@ -31,7 +31,7 @@ export function parsedWikiLinks(text: string): ParsedWikiLink[] {
|
|||
return links;
|
||||
}
|
||||
|
||||
export function userInputWithWikiLinkMentions(text: string, resolveMention: WikiLinkMentionResolver): UserInput[] {
|
||||
export function userInputWithWikiLinkMentions(text: string, resolveMention: WikiLinkMentionResolver) {
|
||||
return userInputWithWikiLinkMentionsAndSkills(text, resolveMention, []);
|
||||
}
|
||||
|
||||
|
|
@ -39,27 +39,28 @@ export function userInputWithWikiLinkMentionsAndSkills(
|
|||
text: string,
|
||||
resolveMention: WikiLinkMentionResolver,
|
||||
skills: readonly SkillMetadata[],
|
||||
): UserInput[] {
|
||||
const input: UserInput[] = [{ type: "text", text, text_elements: [] }];
|
||||
) {
|
||||
const mentions: AppServerRequestMention[] = [];
|
||||
const seenPaths = new Set<string>();
|
||||
|
||||
for (const link of parsedWikiLinks(text)) {
|
||||
const mention = resolveMention(link.target);
|
||||
if (!mention || seenPaths.has(mention.path)) continue;
|
||||
seenPaths.add(mention.path);
|
||||
input.push({ type: "mention", name: mention.name, path: mention.path });
|
||||
mentions.push(mention);
|
||||
}
|
||||
|
||||
const skillByName = firstEnabledSkillByName(skills);
|
||||
const resolvedSkills: AppServerRequestMention[] = [];
|
||||
const seenSkillPaths = new Set<string>();
|
||||
for (const reference of parsedSkillReferences(text)) {
|
||||
const skill = skillByName.get(reference.toLowerCase());
|
||||
if (!skill || seenSkillPaths.has(skill.path)) continue;
|
||||
seenSkillPaths.add(skill.path);
|
||||
input.push({ type: "skill", name: skill.name, path: skill.path });
|
||||
resolvedSkills.push({ name: skill.name, path: skill.path });
|
||||
}
|
||||
|
||||
return input;
|
||||
return appServerTextInputWithMentions(text, mentions, resolvedSkills);
|
||||
}
|
||||
|
||||
export function parsedSkillReferences(text: string): string[] {
|
||||
|
|
|
|||
|
|
@ -1,10 +1,7 @@
|
|||
import type { AppServerClient } from "../../../app-server/client";
|
||||
import { appServerTextInputWithAttachments } from "../../../app-server/request-input";
|
||||
import { chronologicalConversationSummariesFromAppServerTurns } from "../../../app-server/turn-model";
|
||||
import {
|
||||
referencedThreadInput as buildReferencedThreadInput,
|
||||
referencedThreadTurns,
|
||||
REFERENCED_THREAD_TURN_LIMIT,
|
||||
} from "../../../domain/threads/reference";
|
||||
import { referencedThreadPromptBundle, referencedThreadTurns, REFERENCED_THREAD_TURN_LIMIT } from "../../../domain/threads/reference";
|
||||
import type { Thread } from "../../../domain/threads/model";
|
||||
import {
|
||||
executeSlashCommand as runSlashCommand,
|
||||
|
|
@ -139,9 +136,13 @@ async function referencedThreadInput(
|
|||
host.status.addSystemMessage("Referenced thread has no readable conversation turns.");
|
||||
return null;
|
||||
}
|
||||
const reference = buildReferencedThreadInput(thread, turns, message, host.codexInput(message));
|
||||
const reference = referencedThreadPromptBundle(thread, turns, message);
|
||||
const messageInput = host.codexInput(message);
|
||||
host.status.setStatus(reference.status);
|
||||
return reference;
|
||||
return {
|
||||
input: appServerTextInputWithAttachments(reference.prompt, messageInput),
|
||||
referencedThread: reference.referencedThread,
|
||||
};
|
||||
} catch (error) {
|
||||
host.status.addSystemMessage(error instanceof Error ? error.message : String(error));
|
||||
return null;
|
||||
|
|
|
|||
32
tests/app-server/request-input.test.ts
Normal file
32
tests/app-server/request-input.test.ts
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { appServerTextInputWithAttachments, appServerTextInputWithMentions } from "../../src/app-server/request-input";
|
||||
import type { UserInput } from "../../src/generated/app-server/v2/UserInput";
|
||||
|
||||
describe("app-server request input", () => {
|
||||
it("builds text input with mentions and skills", () => {
|
||||
expect(
|
||||
appServerTextInputWithMentions(
|
||||
"Use [[Note]] and $Skill",
|
||||
[{ name: "Note", path: "Note.md" }],
|
||||
[{ name: "Skill", path: ".codex/skills/skill/SKILL.md" }],
|
||||
),
|
||||
).toEqual([
|
||||
{ type: "text", text: "Use [[Note]] and $Skill", text_elements: [] },
|
||||
{ type: "mention", name: "Note", path: "Note.md" },
|
||||
{ type: "skill", name: "Skill", path: ".codex/skills/skill/SKILL.md" },
|
||||
]);
|
||||
});
|
||||
|
||||
it("replaces text input while preserving non-text attachments", () => {
|
||||
const input: UserInput[] = [
|
||||
{ type: "text", text: "visible request", text_elements: [] },
|
||||
{ type: "mention", name: "Note", path: "Note.md" },
|
||||
];
|
||||
|
||||
expect(appServerTextInputWithAttachments("rewritten prompt", input)).toEqual([
|
||||
{ type: "text", text: "rewritten prompt", text_elements: [] },
|
||||
{ type: "mention", name: "Note", path: "Note.md" },
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
|
@ -3,7 +3,7 @@ import { describe, expect, it } from "vitest";
|
|||
import type { Thread } from "../../../src/domain/threads/model";
|
||||
import {
|
||||
referencedThreadDisplayFromPrompt,
|
||||
referencedThreadInput,
|
||||
referencedThreadPromptBundle,
|
||||
referencedThreadPrompt,
|
||||
referencedThreadTurns,
|
||||
} from "../../../src/domain/threads/reference";
|
||||
|
|
@ -117,16 +117,12 @@ describe("thread reference context", () => {
|
|||
).toBeNull();
|
||||
});
|
||||
|
||||
it("builds slash command input while preserving non-text attachments", () => {
|
||||
it("builds a prompt bundle for slash command references", () => {
|
||||
const source = thread();
|
||||
const input = referencedThreadInput(source, [{ userText: "元の依頼", assistantText: "回答" }], "この続きです", [
|
||||
{ type: "text", text: "この続きです", text_elements: [] },
|
||||
{ type: "mention", name: "Note", path: "Note.md" },
|
||||
]);
|
||||
const input = referencedThreadPromptBundle(source, [{ userText: "元の依頼", assistantText: "回答" }], "この続きです");
|
||||
|
||||
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" });
|
||||
expect(input.prompt).toContain("Current user request:\nこの続きです");
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue