mirror of
https://github.com/murashit/codex-panel.git
synced 2026-07-22 06:57:10 +00:00
Extract turn submission derivations
This commit is contained in:
parent
172bacfe30
commit
6b0707112e
3 changed files with 124 additions and 15 deletions
|
|
@ -1,6 +1,8 @@
|
|||
import type { PendingTurnStart } from "./chat-state";
|
||||
import type { DisplayFileMention, DisplayItem, MessageDisplayItem } from "./display/types";
|
||||
import { fileMentionsFromInput } from "./display/thread-items";
|
||||
import { attachHookRunsToTurn } from "./hook-display";
|
||||
import type { UserInput } from "../../generated/app-server/v2/UserInput";
|
||||
|
||||
export interface LocalUserMessageParams {
|
||||
id: string;
|
||||
|
|
@ -17,6 +19,24 @@ export interface OptimisticTurnStartAckParams {
|
|||
pendingTurnStart: PendingTurnStart | null;
|
||||
}
|
||||
|
||||
export interface LocalUserMessageFromInputParams extends Omit<LocalUserMessageParams, "mentionedFiles"> {
|
||||
codexInput: readonly UserInput[];
|
||||
}
|
||||
|
||||
export type OptimisticTurnStartParams = LocalUserMessageFromInputParams;
|
||||
|
||||
export interface OptimisticTurnStart {
|
||||
item: MessageDisplayItem;
|
||||
pendingTurnStart: PendingTurnStart;
|
||||
}
|
||||
|
||||
export interface TurnStartAckMatchParams {
|
||||
pendingTurnStart: PendingTurnStart | null;
|
||||
activeTurnId: string | null;
|
||||
optimisticUserId: string;
|
||||
responseTurnId: string;
|
||||
}
|
||||
|
||||
export interface FailedTurnStartCleanupParams {
|
||||
items: readonly DisplayItem[];
|
||||
optimisticUserId: string | null;
|
||||
|
|
@ -38,6 +58,30 @@ export function localUserMessageItem(params: LocalUserMessageParams): MessageDis
|
|||
};
|
||||
}
|
||||
|
||||
export function localUserMessageItemFromInput(params: LocalUserMessageFromInputParams): MessageDisplayItem {
|
||||
return localUserMessageItem({
|
||||
id: params.id,
|
||||
text: params.text,
|
||||
...(params.turnId ? { turnId: params.turnId } : {}),
|
||||
...(params.referencedThread ? { referencedThread: params.referencedThread } : {}),
|
||||
mentionedFiles: fileMentionsFromInput([...params.codexInput]),
|
||||
});
|
||||
}
|
||||
|
||||
export function optimisticTurnStart(params: OptimisticTurnStartParams): OptimisticTurnStart {
|
||||
return {
|
||||
item: localUserMessageItemFromInput(params),
|
||||
pendingTurnStart: { anchorItemId: params.id, promptSubmitHookItemIds: [] },
|
||||
};
|
||||
}
|
||||
|
||||
export function shouldAcknowledgeTurnStart(params: TurnStartAckMatchParams): boolean {
|
||||
return (
|
||||
params.pendingTurnStart?.anchorItemId === params.optimisticUserId ||
|
||||
(!params.pendingTurnStart && params.activeTurnId === params.responseTurnId)
|
||||
);
|
||||
}
|
||||
|
||||
export function acknowledgeOptimisticTurnStart(params: OptimisticTurnStartAckParams): DisplayItem[] {
|
||||
const displayItems = params.items.map((item) => (item.id === params.optimisticUserId ? { ...item, turnId: params.turnId } : item));
|
||||
if (!params.pendingTurnStart) return displayItems;
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import type { SlashCommandName } from "./composer/slash-commands";
|
|||
import { parseSlashCommand } from "./composer/suggestions";
|
||||
import { VIEW_TYPE_CODEX_PANEL } from "../../constants";
|
||||
import { createSystemItem } from "./display/system";
|
||||
import { fileMentionsFromInput } from "./display/thread-items";
|
||||
import type { DisplayDetailSection, DisplayItem } from "./display/types";
|
||||
import type { ReasoningEffort } from "../../generated/app-server/ReasoningEffort";
|
||||
import type { Model } from "../../generated/app-server/v2/Model";
|
||||
|
|
@ -86,7 +85,13 @@ import {
|
|||
type ActiveChatResume,
|
||||
type ChatViewRenderScheduleOptions,
|
||||
} from "./view-lifecycle";
|
||||
import { acknowledgeOptimisticTurnStart, cleanupFailedTurnStart, localUserMessageItem } from "./turn-submission";
|
||||
import {
|
||||
acknowledgeOptimisticTurnStart,
|
||||
cleanupFailedTurnStart,
|
||||
localUserMessageItemFromInput,
|
||||
optimisticTurnStart,
|
||||
shouldAcknowledgeTurnStart,
|
||||
} from "./turn-submission";
|
||||
|
||||
export interface CodexChatHost {
|
||||
readonly settings: CodexPanelSettings;
|
||||
|
|
@ -756,18 +761,17 @@ export class CodexChatView extends ItemView {
|
|||
if (!(await this.runtimeSettings.applyPendingThreadSettings())) return;
|
||||
|
||||
const codexInput = codexInputOverride ?? this.composerController.codexInput(text);
|
||||
const mentionedFiles = fileMentionsFromInput(codexInput);
|
||||
optimisticUserId = `local-user-${String(Date.now())}`;
|
||||
const optimisticUserItem = localUserMessageItem({
|
||||
const optimistic = optimisticTurnStart({
|
||||
id: optimisticUserId,
|
||||
text,
|
||||
codexInput,
|
||||
referencedThread,
|
||||
mentionedFiles,
|
||||
});
|
||||
this.dispatch({
|
||||
type: "turn/optimistic-started",
|
||||
item: optimisticUserItem,
|
||||
pendingTurnStart: { anchorItemId: optimisticUserId, promptSubmitHookItemIds: [] },
|
||||
item: optimistic.item,
|
||||
pendingTurnStart: optimistic.pendingTurnStart,
|
||||
});
|
||||
this.queueMessagesBottomScroll();
|
||||
this.composerController.setDraft("");
|
||||
|
|
@ -775,10 +779,14 @@ export class CodexChatView extends ItemView {
|
|||
|
||||
const response = await client.startTurn(activeThreadId, this.plugin.vaultPath, codexInput);
|
||||
const pendingTurnStart = this.pendingTurnStart;
|
||||
const currentTurnId = this.activeTurnId;
|
||||
const responseMatchesCurrentStart =
|
||||
pendingTurnStart?.anchorItemId === optimisticUserId || (!pendingTurnStart && currentTurnId === response.turn.id);
|
||||
if (responseMatchesCurrentStart) {
|
||||
if (
|
||||
shouldAcknowledgeTurnStart({
|
||||
pendingTurnStart,
|
||||
activeTurnId: this.activeTurnId,
|
||||
optimisticUserId,
|
||||
responseTurnId: response.turn.id,
|
||||
})
|
||||
) {
|
||||
const displayItems = acknowledgeOptimisticTurnStart({
|
||||
items: this.state.displayItems,
|
||||
optimisticUserId,
|
||||
|
|
@ -814,7 +822,6 @@ export class CodexChatView extends ItemView {
|
|||
const threadId = this.state.activeThreadId;
|
||||
const expectedTurnId = this.activeTurnId;
|
||||
const codexInput = codexInputOverride ?? this.composerController.codexInput(text);
|
||||
const mentionedFiles = fileMentionsFromInput(codexInput);
|
||||
|
||||
this.composerController.setDraft("", { clearSuggestions: true });
|
||||
|
||||
|
|
@ -822,12 +829,12 @@ export class CodexChatView extends ItemView {
|
|||
await this.client.steerTurn(threadId, expectedTurnId, codexInput);
|
||||
this.dispatch({
|
||||
type: "system/message-added",
|
||||
item: localUserMessageItem({
|
||||
item: localUserMessageItemFromInput({
|
||||
id: `local-steer-${String(Date.now())}`,
|
||||
text,
|
||||
turnId: expectedTurnId,
|
||||
referencedThread,
|
||||
mentionedFiles,
|
||||
codexInput,
|
||||
}),
|
||||
});
|
||||
this.queueMessagesBottomScroll();
|
||||
|
|
|
|||
|
|
@ -1,6 +1,13 @@
|
|||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { acknowledgeOptimisticTurnStart, cleanupFailedTurnStart, localUserMessageItem } from "../../../src/features/chat/turn-submission";
|
||||
import {
|
||||
acknowledgeOptimisticTurnStart,
|
||||
cleanupFailedTurnStart,
|
||||
localUserMessageItem,
|
||||
localUserMessageItemFromInput,
|
||||
optimisticTurnStart,
|
||||
shouldAcknowledgeTurnStart,
|
||||
} from "../../../src/features/chat/turn-submission";
|
||||
import type { DisplayItem } from "../../../src/features/chat/display/types";
|
||||
|
||||
describe("chat turn submission helpers", () => {
|
||||
|
|
@ -14,6 +21,57 @@ describe("chat turn submission helpers", () => {
|
|||
expect(item.mentionedFiles).toEqual([{ name: "Note", path: "Note.md" }]);
|
||||
});
|
||||
|
||||
it("builds optimistic turn starts from immutable input snapshots", () => {
|
||||
const input = [
|
||||
{ type: "text" as const, text: "hello [[Note]]", text_elements: [] },
|
||||
{ type: "mention" as const, name: "Note", path: "Note.md" },
|
||||
];
|
||||
|
||||
const start = optimisticTurnStart({ id: "local-user", text: "hello [[Note]]", codexInput: input });
|
||||
|
||||
expect(start.pendingTurnStart).toEqual({ anchorItemId: "local-user", promptSubmitHookItemIds: [] });
|
||||
expect(start.item).toMatchObject({
|
||||
id: "local-user",
|
||||
kind: "message",
|
||||
role: "user",
|
||||
text: "hello [[Note]]",
|
||||
mentionedFiles: [{ name: "Note", path: "Note.md" }],
|
||||
});
|
||||
|
||||
expect(localUserMessageItemFromInput({ id: "steer", text: "hello [[Note]]", turnId: "turn", codexInput: input })).toMatchObject({
|
||||
id: "steer",
|
||||
turnId: "turn",
|
||||
mentionedFiles: [{ name: "Note", path: "Note.md" }],
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps turn start acknowledgement matching explicit", () => {
|
||||
expect(
|
||||
shouldAcknowledgeTurnStart({
|
||||
pendingTurnStart: { anchorItemId: "local-user", promptSubmitHookItemIds: [] },
|
||||
activeTurnId: null,
|
||||
optimisticUserId: "local-user",
|
||||
responseTurnId: "turn",
|
||||
}),
|
||||
).toBe(true);
|
||||
expect(
|
||||
shouldAcknowledgeTurnStart({
|
||||
pendingTurnStart: null,
|
||||
activeTurnId: "turn",
|
||||
optimisticUserId: "local-user",
|
||||
responseTurnId: "turn",
|
||||
}),
|
||||
).toBe(true);
|
||||
expect(
|
||||
shouldAcknowledgeTurnStart({
|
||||
pendingTurnStart: { anchorItemId: "other", promptSubmitHookItemIds: [] },
|
||||
activeTurnId: "stale-turn",
|
||||
optimisticUserId: "local-user",
|
||||
responseTurnId: "turn",
|
||||
}),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it("attaches acknowledged turn ids and pending hook runs immutably", () => {
|
||||
const items: DisplayItem[] = [
|
||||
localUserMessageItem({ id: "local-user", text: "hello" }),
|
||||
|
|
|
|||
Loading…
Reference in a new issue