mirror of
https://github.com/murashit/codex-panel.git
synced 2026-07-22 06:57:10 +00:00
Split chat reducer slice helpers
This commit is contained in:
parent
9c52b5b3f5
commit
7507e03d2f
4 changed files with 238 additions and 185 deletions
|
|
@ -27,27 +27,27 @@ import type { PendingApproval } from "./requests/approval";
|
|||
import type { ComposerSuggestion } from "./composer/suggestions";
|
||||
import { upsertDisplayItem } from "./display/stream-updates";
|
||||
import type { DisplayItem } from "./display/types";
|
||||
import type { PendingUserInput } from "./requests/user-input";
|
||||
import type { ActiveThreadResumedAction, ActiveThreadSettingsAppliedAction } from "./chat-state-actions";
|
||||
import { initialChatTranscriptState, reduceTranscriptSlice, type ChatTranscriptState, type TranscriptAction } from "./transcript-state";
|
||||
import {
|
||||
initialChatRequestState,
|
||||
reduceRequestSlice,
|
||||
resolveChatRequest,
|
||||
type ChatRequestState,
|
||||
type RequestAction,
|
||||
} from "./requests/request-state";
|
||||
import { initialChatTurnState, transitionChatTurnLifecycleState, type ChatTurnState, type PendingTurnStart } from "./turns/turn-state";
|
||||
|
||||
export interface PendingTurnStart {
|
||||
anchorItemId: string;
|
||||
promptSubmitHookItemIds: string[];
|
||||
}
|
||||
|
||||
export type ChatTurnLifecycleState =
|
||||
| { kind: "idle" }
|
||||
| { kind: "starting"; pendingTurnStart: PendingTurnStart }
|
||||
| { kind: "running"; turnId: string };
|
||||
|
||||
export type ChatTurnLifecycleEvent =
|
||||
| { type: "started"; turnId: string }
|
||||
| { type: "completed"; turnId: string }
|
||||
| { type: "cleared" }
|
||||
| { type: "optimistic-started"; pendingTurnStart: PendingTurnStart }
|
||||
| { type: "start-acknowledged"; turnId: string }
|
||||
| { type: "start-failed" }
|
||||
| { type: "pending-start-hook-upserted"; pendingTurnStart: PendingTurnStart | null };
|
||||
export {
|
||||
activeTurnId,
|
||||
chatTurnBusy,
|
||||
pendingTurnStart,
|
||||
transitionChatTurnLifecycleState,
|
||||
type ChatTurnLifecycleState,
|
||||
type ChatTurnState,
|
||||
type PendingTurnStart,
|
||||
} from "./turns/turn-state";
|
||||
export type { ChatTranscriptState } from "./transcript-state";
|
||||
|
||||
interface ChatConnectionState {
|
||||
status: string;
|
||||
|
|
@ -73,24 +73,6 @@ export interface ChatActiveThreadState {
|
|||
|
||||
export type { ChatRuntimeState } from "../../runtime/chat-runtime-state";
|
||||
|
||||
export interface ChatTurnState {
|
||||
lifecycle: ChatTurnLifecycleState;
|
||||
}
|
||||
|
||||
export interface ChatTranscriptState {
|
||||
displayItems: readonly DisplayItem[];
|
||||
turnDiffs: ReadonlyMap<string, string>;
|
||||
historyCursor: string | null;
|
||||
loadingHistory: boolean;
|
||||
reportedLogs: ReadonlySet<string>;
|
||||
}
|
||||
|
||||
interface ChatRequestState {
|
||||
approvals: readonly PendingApproval[];
|
||||
pendingUserInputs: readonly PendingUserInput[];
|
||||
userInputDrafts: ReadonlyMap<string, string>;
|
||||
}
|
||||
|
||||
interface ChatComposerState {
|
||||
draft: string;
|
||||
suggestSelected: number;
|
||||
|
|
@ -196,25 +178,6 @@ type TurnAction =
|
|||
| TurnStartAcknowledgedAction
|
||||
| TurnStartFailedAction;
|
||||
|
||||
type RequestAction =
|
||||
| { type: "request/approval-queued"; approval: PendingApproval }
|
||||
| { type: "request/user-input-queued"; input: PendingUserInput }
|
||||
| { type: "request/user-input-draft-set"; key: string; value: string };
|
||||
|
||||
type TranscriptAction =
|
||||
| { type: "transcript/item-added"; item: DisplayItem }
|
||||
| { type: "transcript/system-message-added"; item: DisplayItem }
|
||||
| { type: "transcript/deduped-log-added"; text: string; item: DisplayItem }
|
||||
| { type: "transcript/history-loading-set"; loading: boolean }
|
||||
| {
|
||||
type: "transcript/items-replaced";
|
||||
items: readonly DisplayItem[];
|
||||
historyCursor?: string | null;
|
||||
loadingHistory?: boolean;
|
||||
}
|
||||
| { type: "transcript/item-upserted"; item: DisplayItem }
|
||||
| { type: "transcript/turn-diff-updated"; turnId: string; diff: string };
|
||||
|
||||
type ComposerAction =
|
||||
| {
|
||||
type: "composer/draft-set";
|
||||
|
|
@ -535,23 +498,9 @@ function reducePendingStartHookUpsertedTransition(state: ChatState, action: Pend
|
|||
}
|
||||
|
||||
function reduceRequestResolvedTransition(state: ChatState, action: RequestResolvedAction): ChatState {
|
||||
const resolvedInputs = state.requests.pendingUserInputs.filter((input) => input.requestId === action.requestId);
|
||||
const draftKeys = new Set(
|
||||
resolvedInputs.flatMap((input) =>
|
||||
input.params.questions.flatMap((question) => [
|
||||
`${String(input.requestId)}:${question.id}`,
|
||||
`${String(input.requestId)}:${question.id}:other`,
|
||||
]),
|
||||
),
|
||||
);
|
||||
const userInputDrafts = new Map([...state.requests.userInputDrafts].filter(([key]) => !draftKeys.has(key)));
|
||||
const displayItems = action.resultItem ? [...state.transcript.displayItems, action.resultItem] : state.transcript.displayItems;
|
||||
return patchChatState(state, {
|
||||
requests: {
|
||||
approvals: state.requests.approvals.filter((approval) => approval.requestId !== action.requestId),
|
||||
pendingUserInputs: state.requests.pendingUserInputs.filter((input) => input.requestId !== action.requestId),
|
||||
userInputDrafts,
|
||||
},
|
||||
requests: resolveChatRequest(state.requests, action.requestId),
|
||||
transcript: {
|
||||
...state.transcript,
|
||||
displayItems,
|
||||
|
|
@ -566,8 +515,8 @@ function reduceChatSlices(state: ChatState, action: ChatSliceAction): ChatState
|
|||
activeThread: reduceActiveThreadSlice(state.activeThread, action),
|
||||
runtime: reduceRuntimeSlice(state.runtime, action),
|
||||
turn: reduceTurnSlice(state.turn, action),
|
||||
requests: reduceRequestSlice(state.requests, action),
|
||||
transcript: reduceTranscriptSlice(state.transcript, action),
|
||||
requests: isRequestAction(action) ? reduceRequestSlice(state.requests, action) : state.requests,
|
||||
transcript: isTranscriptAction(action) ? reduceTranscriptSlice(state.transcript, action) : state.transcript,
|
||||
composer: reduceComposerSlice(state.composer, action),
|
||||
ui: reduceUiSlice(state.ui, action),
|
||||
});
|
||||
|
|
@ -636,51 +585,6 @@ function reduceTurnSlice(state: ChatTurnState, _action: ChatSliceAction): ChatTu
|
|||
return state;
|
||||
}
|
||||
|
||||
function reduceRequestSlice(state: ChatRequestState, action: ChatSliceAction): ChatRequestState {
|
||||
switch (action.type) {
|
||||
case "request/approval-queued":
|
||||
if (state.approvals.some((existing) => existing.requestId === action.approval.requestId)) return state;
|
||||
return patchObject(state, { approvals: [...state.approvals, action.approval] });
|
||||
case "request/user-input-queued":
|
||||
if (state.pendingUserInputs.some((existing) => existing.requestId === action.input.requestId)) return state;
|
||||
return patchObject(state, { pendingUserInputs: [...state.pendingUserInputs, action.input] });
|
||||
case "request/user-input-draft-set":
|
||||
return setUserInputDraftSlice(state, action.key, action.value);
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
function reduceTranscriptSlice(state: ChatTranscriptState, action: ChatSliceAction): ChatTranscriptState {
|
||||
switch (action.type) {
|
||||
case "transcript/item-added":
|
||||
case "transcript/system-message-added":
|
||||
return patchObject(state, { displayItems: [...state.displayItems, action.item] });
|
||||
case "transcript/deduped-log-added":
|
||||
if (state.reportedLogs.has(action.text)) return state;
|
||||
return patchObject(state, {
|
||||
reportedLogs: new Set([...state.reportedLogs, action.text]),
|
||||
displayItems: [...state.displayItems, action.item],
|
||||
});
|
||||
case "transcript/items-replaced":
|
||||
return patchObject(state, {
|
||||
displayItems: action.items,
|
||||
...definedPatch("historyCursor", action.historyCursor),
|
||||
...definedPatch("loadingHistory", action.loadingHistory),
|
||||
});
|
||||
case "transcript/history-loading-set":
|
||||
return patchObject(state, { loadingHistory: action.loading });
|
||||
case "transcript/item-upserted":
|
||||
return patchObject(state, { displayItems: upsertDisplayItem(state.displayItems, action.item) });
|
||||
case "transcript/turn-diff-updated":
|
||||
return patchObject(state, {
|
||||
turnDiffs: updatedTurnDiffs(state.turnDiffs, action.turnId, action.diff),
|
||||
});
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
function reduceComposerSlice(state: ChatComposerState, action: ChatSliceAction): ChatComposerState {
|
||||
switch (action.type) {
|
||||
case "composer/draft-set":
|
||||
|
|
@ -787,27 +691,15 @@ function initialRuntimeState(): ChatRuntimeState {
|
|||
}
|
||||
|
||||
function initialTurnState(): ChatTurnState {
|
||||
return {
|
||||
lifecycle: { kind: "idle" },
|
||||
};
|
||||
return initialChatTurnState();
|
||||
}
|
||||
|
||||
function initialTranscriptState(): ChatTranscriptState {
|
||||
return {
|
||||
displayItems: [],
|
||||
turnDiffs: new Map(),
|
||||
historyCursor: null,
|
||||
loadingHistory: false,
|
||||
reportedLogs: new Set(),
|
||||
};
|
||||
return initialChatTranscriptState();
|
||||
}
|
||||
|
||||
function initialRequestState(): ChatRequestState {
|
||||
return {
|
||||
approvals: [],
|
||||
pendingUserInputs: [],
|
||||
userInputDrafts: new Map(),
|
||||
};
|
||||
return initialChatRequestState();
|
||||
}
|
||||
|
||||
function initialComposerState(): ChatComposerState {
|
||||
|
|
@ -861,55 +753,8 @@ function cloneChatState(state: ChatState): ChatState {
|
|||
};
|
||||
}
|
||||
|
||||
export function chatTurnBusy(state: Pick<ChatState, "turn"> | { lifecycle: ChatTurnLifecycleState }): boolean {
|
||||
return turnLifecycleFor(state).kind !== "idle";
|
||||
}
|
||||
|
||||
export function activeTurnId(state: Pick<ChatState, "turn"> | { lifecycle: ChatTurnLifecycleState }): string | null {
|
||||
const lifecycle = turnLifecycleFor(state);
|
||||
return lifecycle.kind === "running" ? lifecycle.turnId : null;
|
||||
}
|
||||
|
||||
export function pendingTurnStart(state: Pick<ChatState, "turn"> | { lifecycle: ChatTurnLifecycleState }): PendingTurnStart | null {
|
||||
const lifecycle = turnLifecycleFor(state);
|
||||
return lifecycle.kind === "starting" ? lifecycle.pendingTurnStart : null;
|
||||
}
|
||||
|
||||
function turnLifecycleFor(state: Pick<ChatState, "turn"> | { lifecycle: ChatTurnLifecycleState }): ChatTurnLifecycleState {
|
||||
if ("turn" in state) return state.turn.lifecycle;
|
||||
return state.lifecycle;
|
||||
}
|
||||
|
||||
export function transitionChatTurnLifecycleState(state: ChatTurnLifecycleState, event: ChatTurnLifecycleEvent): ChatTurnLifecycleState {
|
||||
switch (event.type) {
|
||||
case "started":
|
||||
return { kind: "running", turnId: event.turnId };
|
||||
case "completed":
|
||||
return state.kind === "running" && state.turnId === event.turnId ? { kind: "idle" } : state;
|
||||
case "cleared":
|
||||
return state.kind === "idle" ? state : { kind: "idle" };
|
||||
case "optimistic-started":
|
||||
return { kind: "starting", pendingTurnStart: event.pendingTurnStart };
|
||||
case "start-acknowledged":
|
||||
if (state.kind === "idle") return state;
|
||||
if (state.kind === "running" && state.turnId !== event.turnId) return state;
|
||||
return { kind: "running", turnId: event.turnId };
|
||||
case "start-failed":
|
||||
return { kind: "idle" };
|
||||
case "pending-start-hook-upserted":
|
||||
if (event.pendingTurnStart) return { kind: "starting", pendingTurnStart: event.pendingTurnStart };
|
||||
return state.kind === "starting" ? { kind: "idle" } : state;
|
||||
}
|
||||
}
|
||||
|
||||
function updatedTurnDiffs(turnDiffs: ReadonlyMap<string, string>, turnId: string, diff: string): ReadonlyMap<string, string> {
|
||||
const next = new Map(turnDiffs);
|
||||
if (diff.trim().length > 0) {
|
||||
next.set(turnId, diff);
|
||||
} else {
|
||||
next.delete(turnId);
|
||||
}
|
||||
return next;
|
||||
function isTranscriptAction(action: ChatSliceAction): action is TranscriptAction {
|
||||
return action.type.startsWith("transcript/");
|
||||
}
|
||||
|
||||
function setPanelSlice(state: ChatUiState, panel: "history" | "chat-actions" | "status-panel" | null, toggle: boolean): ChatUiState {
|
||||
|
|
@ -938,9 +783,8 @@ function setDetailOpenSlice(state: ChatUiState, key: string, open: boolean): Cha
|
|||
return patchObject(state, { openDetails });
|
||||
}
|
||||
|
||||
function setUserInputDraftSlice(state: ChatRequestState, key: string, value: string): ChatRequestState {
|
||||
if (state.userInputDrafts.get(key) === value) return state;
|
||||
return patchObject(state, { userInputDrafts: new Map([...state.userInputDrafts, [key, value]]) });
|
||||
function isRequestAction(action: ChatSliceAction): action is RequestAction {
|
||||
return action.type.startsWith("request/");
|
||||
}
|
||||
|
||||
function setComposerSuggestionsSlice(
|
||||
|
|
|
|||
59
src/features/chat/requests/request-state.ts
Normal file
59
src/features/chat/requests/request-state.ts
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
import type { PendingApproval } from "./approval";
|
||||
import type { PendingUserInput } from "./user-input";
|
||||
|
||||
export interface ChatRequestState {
|
||||
approvals: readonly PendingApproval[];
|
||||
pendingUserInputs: readonly PendingUserInput[];
|
||||
userInputDrafts: ReadonlyMap<string, string>;
|
||||
}
|
||||
|
||||
export type RequestAction =
|
||||
| { type: "request/approval-queued"; approval: PendingApproval }
|
||||
| { type: "request/user-input-queued"; input: PendingUserInput }
|
||||
| { type: "request/user-input-draft-set"; key: string; value: string };
|
||||
|
||||
export function initialChatRequestState(): ChatRequestState {
|
||||
return {
|
||||
approvals: [],
|
||||
pendingUserInputs: [],
|
||||
userInputDrafts: new Map(),
|
||||
};
|
||||
}
|
||||
|
||||
export function reduceRequestSlice(state: ChatRequestState, action: RequestAction): ChatRequestState {
|
||||
switch (action.type) {
|
||||
case "request/approval-queued":
|
||||
if (state.approvals.some((existing) => existing.requestId === action.approval.requestId)) return state;
|
||||
return patchObject(state, { approvals: [...state.approvals, action.approval] });
|
||||
case "request/user-input-queued":
|
||||
if (state.pendingUserInputs.some((existing) => existing.requestId === action.input.requestId)) return state;
|
||||
return patchObject(state, { pendingUserInputs: [...state.pendingUserInputs, action.input] });
|
||||
case "request/user-input-draft-set":
|
||||
return setUserInputDraftSlice(state, action.key, action.value);
|
||||
}
|
||||
}
|
||||
|
||||
export function resolveChatRequest(state: ChatRequestState, requestId: PendingApproval["requestId"]): ChatRequestState {
|
||||
const resolvedInputs = state.pendingUserInputs.filter((input) => input.requestId === requestId);
|
||||
const draftKeys = new Set(
|
||||
resolvedInputs.flatMap((input) =>
|
||||
input.params.questions.flatMap((question) => [`${String(requestId)}:${question.id}`, `${String(requestId)}:${question.id}:other`]),
|
||||
),
|
||||
);
|
||||
const userInputDrafts = new Map([...state.userInputDrafts].filter(([key]) => !draftKeys.has(key)));
|
||||
return patchObject(state, {
|
||||
approvals: state.approvals.filter((approval) => approval.requestId !== requestId),
|
||||
pendingUserInputs: state.pendingUserInputs.filter((input) => input.requestId !== requestId),
|
||||
userInputDrafts,
|
||||
});
|
||||
}
|
||||
|
||||
function setUserInputDraftSlice(state: ChatRequestState, key: string, value: string): ChatRequestState {
|
||||
if (state.userInputDrafts.get(key) === value) return state;
|
||||
return patchObject(state, { userInputDrafts: new Map([...state.userInputDrafts, [key, value]]) });
|
||||
}
|
||||
|
||||
function patchObject<T extends object>(current: T, patch: Partial<T>): T {
|
||||
if (Object.entries(patch).every(([key, value]) => Object.is(current[key as keyof T], value))) return current;
|
||||
return { ...current, ...patch };
|
||||
}
|
||||
81
src/features/chat/transcript-state.ts
Normal file
81
src/features/chat/transcript-state.ts
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
import { upsertDisplayItem } from "./display/stream-updates";
|
||||
import type { DisplayItem } from "./display/types";
|
||||
|
||||
export interface ChatTranscriptState {
|
||||
displayItems: readonly DisplayItem[];
|
||||
turnDiffs: ReadonlyMap<string, string>;
|
||||
historyCursor: string | null;
|
||||
loadingHistory: boolean;
|
||||
reportedLogs: ReadonlySet<string>;
|
||||
}
|
||||
|
||||
export type TranscriptAction =
|
||||
| { type: "transcript/item-added"; item: DisplayItem }
|
||||
| { type: "transcript/system-message-added"; item: DisplayItem }
|
||||
| { type: "transcript/deduped-log-added"; text: string; item: DisplayItem }
|
||||
| { type: "transcript/history-loading-set"; loading: boolean }
|
||||
| {
|
||||
type: "transcript/items-replaced";
|
||||
items: readonly DisplayItem[];
|
||||
historyCursor?: string | null;
|
||||
loadingHistory?: boolean;
|
||||
}
|
||||
| { type: "transcript/item-upserted"; item: DisplayItem }
|
||||
| { type: "transcript/turn-diff-updated"; turnId: string; diff: string };
|
||||
|
||||
export function initialChatTranscriptState(): ChatTranscriptState {
|
||||
return {
|
||||
displayItems: [],
|
||||
turnDiffs: new Map(),
|
||||
historyCursor: null,
|
||||
loadingHistory: false,
|
||||
reportedLogs: new Set(),
|
||||
};
|
||||
}
|
||||
|
||||
export function reduceTranscriptSlice(state: ChatTranscriptState, action: TranscriptAction): ChatTranscriptState {
|
||||
switch (action.type) {
|
||||
case "transcript/item-added":
|
||||
case "transcript/system-message-added":
|
||||
return patchObject(state, { displayItems: [...state.displayItems, action.item] });
|
||||
case "transcript/deduped-log-added":
|
||||
if (state.reportedLogs.has(action.text)) return state;
|
||||
return patchObject(state, {
|
||||
reportedLogs: new Set([...state.reportedLogs, action.text]),
|
||||
displayItems: [...state.displayItems, action.item],
|
||||
});
|
||||
case "transcript/items-replaced":
|
||||
return patchObject(state, {
|
||||
displayItems: action.items,
|
||||
...definedPatch("historyCursor", action.historyCursor),
|
||||
...definedPatch("loadingHistory", action.loadingHistory),
|
||||
});
|
||||
case "transcript/history-loading-set":
|
||||
return patchObject(state, { loadingHistory: action.loading });
|
||||
case "transcript/item-upserted":
|
||||
return patchObject(state, { displayItems: upsertDisplayItem(state.displayItems, action.item) });
|
||||
case "transcript/turn-diff-updated":
|
||||
return patchObject(state, {
|
||||
turnDiffs: updatedTurnDiffs(state.turnDiffs, action.turnId, action.diff),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function updatedTurnDiffs(turnDiffs: ReadonlyMap<string, string>, turnId: string, diff: string): ReadonlyMap<string, string> {
|
||||
const next = new Map(turnDiffs);
|
||||
if (diff.trim().length > 0) {
|
||||
next.set(turnId, diff);
|
||||
} else {
|
||||
next.delete(turnId);
|
||||
}
|
||||
return next;
|
||||
}
|
||||
|
||||
function patchObject<T extends object>(current: T, patch: Partial<T>): T {
|
||||
if (Object.entries(patch).every(([key, value]) => Object.is(current[key as keyof T], value))) return current;
|
||||
return { ...current, ...patch };
|
||||
}
|
||||
|
||||
function definedPatch<Key extends string, Value>(key: Key, value: Value | undefined): Partial<Record<Key, Value>> {
|
||||
return value === undefined ? {} : ({ [key]: value } as Partial<Record<Key, Value>>);
|
||||
}
|
||||
69
src/features/chat/turns/turn-state.ts
Normal file
69
src/features/chat/turns/turn-state.ts
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
export interface PendingTurnStart {
|
||||
anchorItemId: string;
|
||||
promptSubmitHookItemIds: string[];
|
||||
}
|
||||
|
||||
export type ChatTurnLifecycleState =
|
||||
| { kind: "idle" }
|
||||
| { kind: "starting"; pendingTurnStart: PendingTurnStart }
|
||||
| { kind: "running"; turnId: string };
|
||||
|
||||
export type ChatTurnLifecycleEvent =
|
||||
| { type: "started"; turnId: string }
|
||||
| { type: "completed"; turnId: string }
|
||||
| { type: "cleared" }
|
||||
| { type: "optimistic-started"; pendingTurnStart: PendingTurnStart }
|
||||
| { type: "start-acknowledged"; turnId: string }
|
||||
| { type: "start-failed" }
|
||||
| { type: "pending-start-hook-upserted"; pendingTurnStart: PendingTurnStart | null };
|
||||
|
||||
export interface ChatTurnState {
|
||||
lifecycle: ChatTurnLifecycleState;
|
||||
}
|
||||
|
||||
export function initialChatTurnState(): ChatTurnState {
|
||||
return {
|
||||
lifecycle: { kind: "idle" },
|
||||
};
|
||||
}
|
||||
|
||||
export function chatTurnBusy(state: { turn: ChatTurnState } | { lifecycle: ChatTurnLifecycleState }): boolean {
|
||||
return turnLifecycleFor(state).kind !== "idle";
|
||||
}
|
||||
|
||||
export function activeTurnId(state: { turn: ChatTurnState } | { lifecycle: ChatTurnLifecycleState }): string | null {
|
||||
const lifecycle = turnLifecycleFor(state);
|
||||
return lifecycle.kind === "running" ? lifecycle.turnId : null;
|
||||
}
|
||||
|
||||
export function pendingTurnStart(state: { turn: ChatTurnState } | { lifecycle: ChatTurnLifecycleState }): PendingTurnStart | null {
|
||||
const lifecycle = turnLifecycleFor(state);
|
||||
return lifecycle.kind === "starting" ? lifecycle.pendingTurnStart : null;
|
||||
}
|
||||
|
||||
export function transitionChatTurnLifecycleState(state: ChatTurnLifecycleState, event: ChatTurnLifecycleEvent): ChatTurnLifecycleState {
|
||||
switch (event.type) {
|
||||
case "started":
|
||||
return { kind: "running", turnId: event.turnId };
|
||||
case "completed":
|
||||
return state.kind === "running" && state.turnId === event.turnId ? { kind: "idle" } : state;
|
||||
case "cleared":
|
||||
return state.kind === "idle" ? state : { kind: "idle" };
|
||||
case "optimistic-started":
|
||||
return { kind: "starting", pendingTurnStart: event.pendingTurnStart };
|
||||
case "start-acknowledged":
|
||||
if (state.kind === "idle") return state;
|
||||
if (state.kind === "running" && state.turnId !== event.turnId) return state;
|
||||
return { kind: "running", turnId: event.turnId };
|
||||
case "start-failed":
|
||||
return { kind: "idle" };
|
||||
case "pending-start-hook-upserted":
|
||||
if (event.pendingTurnStart) return { kind: "starting", pendingTurnStart: event.pendingTurnStart };
|
||||
return state.kind === "starting" ? { kind: "idle" } : state;
|
||||
}
|
||||
}
|
||||
|
||||
function turnLifecycleFor(state: { turn: ChatTurnState } | { lifecycle: ChatTurnLifecycleState }): ChatTurnLifecycleState {
|
||||
if ("turn" in state) return state.turn.lifecycle;
|
||||
return state.lifecycle;
|
||||
}
|
||||
Loading…
Reference in a new issue