Clarify domain and app-server adapter names

This commit is contained in:
murashit 2026-06-09 23:39:58 +09:00
parent ec64a9666a
commit 62005852ea
58 changed files with 403 additions and 429 deletions

View file

@ -1,10 +1,10 @@
import type { HookMetadata } from "../generated/app-server/v2/HookMetadata";
import type { Model } from "../generated/app-server/v2/Model";
import type { SkillMetadata } from "../generated/app-server/v2/SkillMetadata";
import type { SkillMetadata as AppServerSkillMetadata } from "../generated/app-server/v2/SkillMetadata";
import type { AppServerHookOperation } from "./client";
import type { PanelHookItem, PanelModelOption, PanelSkillOption } from "../domain/catalog/metadata";
import type { HookItem, ModelMetadata, SkillMetadata } from "../domain/catalog/metadata";
function panelModelOptionFromAppServerModel(model: Model): PanelModelOption {
function modelMetadataFromAppServerModel(model: Model): ModelMetadata {
return {
id: model.id,
model: model.model,
@ -21,11 +21,11 @@ function panelModelOptionFromAppServerModel(model: Model): PanelModelOption {
};
}
export function panelModelOptionsFromAppServerModels(models: readonly Model[]): PanelModelOption[] {
return models.map((model) => panelModelOptionFromAppServerModel(model));
export function modelMetadataFromAppServerModels(models: readonly Model[]): ModelMetadata[] {
return models.map((model) => modelMetadataFromAppServerModel(model));
}
function panelSkillOptionFromAppServerSkill(skill: SkillMetadata): PanelSkillOption {
function skillMetadataFromAppServerSkill(skill: AppServerSkillMetadata): SkillMetadata {
return {
name: skill.name,
description: skill.description,
@ -36,11 +36,11 @@ function panelSkillOptionFromAppServerSkill(skill: SkillMetadata): PanelSkillOpt
};
}
export function panelSkillOptionsFromAppServerSkills(skills: readonly SkillMetadata[]): PanelSkillOption[] {
return skills.map((skill) => panelSkillOptionFromAppServerSkill(skill));
export function skillMetadataFromAppServerSkills(skills: readonly AppServerSkillMetadata[]): SkillMetadata[] {
return skills.map((skill) => skillMetadataFromAppServerSkill(skill));
}
function panelHookItemFromAppServerHook(hook: HookMetadata): PanelHookItem {
function hookItemFromAppServerHook(hook: HookMetadata): HookItem {
return {
key: hook.key,
eventName: hook.eventName,
@ -55,11 +55,11 @@ function panelHookItemFromAppServerHook(hook: HookMetadata): PanelHookItem {
};
}
export function panelHookItemsFromAppServerHooks(hooks: readonly HookMetadata[]): PanelHookItem[] {
return hooks.map((hook) => panelHookItemFromAppServerHook(hook));
export function hookItemsFromAppServerHooks(hooks: readonly HookMetadata[]): HookItem[] {
return hooks.map((hook) => hookItemFromAppServerHook(hook));
}
export function appServerHookOperationFromPanelHookItem(hook: PanelHookItem): AppServerHookOperation {
export function appServerHookOperationFromHookItem(hook: HookItem): AppServerHookOperation {
return {
key: hook.key,
currentHash: hook.currentHash,

View file

@ -1,67 +0,0 @@
import type { AppServerClient } from "./client";
import {
appServerHookOperationFromPanelHookItem,
panelHookItemsFromAppServerHooks,
panelModelOptionsFromAppServerModels,
panelSkillOptionsFromAppServerSkills,
} from "./catalog-model";
import { panelThreadFromAppServerThread, panelThreadsFromAppServerThreads } from "./thread-model";
import type { PanelHookItem, PanelModelOption, PanelSkillOption } from "../domain/catalog/metadata";
import type { PanelThread } from "../domain/threads/model";
export interface PanelHookData {
hooks: PanelHookItem[];
warnings: string[];
errors: string[];
}
export async function listPanelThreads(client: AppServerClient, cwd: string, options: { archived?: boolean } = {}): Promise<PanelThread[]> {
const archived = options.archived ?? false;
const response = await client.listThreads(cwd, archived);
return panelThreadsFromAppServerThreads(response.data, { archived });
}
export async function listPanelModelOptions(
client: AppServerClient,
options: { includeHidden?: boolean } = {},
): Promise<PanelModelOption[]> {
const response = await client.listModels(options.includeHidden ?? false);
return panelModelOptionsFromAppServerModels(response.data);
}
export async function listPanelSkillCatalog(
client: AppServerClient,
cwd: string,
options: { forceReload?: boolean; enabledOnly?: boolean } = {},
): Promise<{ skills: PanelSkillOption[]; totalCount: number }> {
const response = await client.listSkills(cwd, options.forceReload ?? false);
const skills = response.data.flatMap((entry) => entry.skills);
return {
skills: panelSkillOptionsFromAppServerSkills(options.enabledOnly === false ? skills : skills.filter((skill) => skill.enabled)),
totalCount: skills.length,
};
}
export async function listPanelHookData(client: AppServerClient, cwd: string): Promise<PanelHookData> {
const response = await client.listHooks(cwd);
const entry = response.data.find((item) => item.cwd === cwd);
if (!entry) return { hooks: [], warnings: [], errors: [] };
return {
hooks: panelHookItemsFromAppServerHooks(entry.hooks),
warnings: entry.warnings,
errors: entry.errors.map((error) => JSON.stringify(error)),
};
}
export async function trustPanelHook(client: AppServerClient, hook: PanelHookItem): Promise<void> {
await client.trustHook(appServerHookOperationFromPanelHookItem(hook));
}
export async function setPanelHookEnabled(client: AppServerClient, hook: PanelHookItem, enabled: boolean): Promise<void> {
await client.setHookEnabled(appServerHookOperationFromPanelHookItem(hook), enabled);
}
export async function restoreArchivedPanelThread(client: AppServerClient, threadId: string): Promise<PanelThread> {
const response = await client.unarchiveThread(threadId);
return panelThreadFromAppServerThread(response.thread);
}

View file

@ -0,0 +1,64 @@
import type { AppServerClient } from "./client";
import {
appServerHookOperationFromHookItem,
hookItemsFromAppServerHooks,
modelMetadataFromAppServerModels,
skillMetadataFromAppServerSkills,
} from "./catalog-model";
import { threadFromAppServerThread, threadsFromAppServerThreads } from "./thread-model";
import type { HookItem, ModelMetadata, SkillMetadata } from "../domain/catalog/metadata";
import type { Thread } from "../domain/threads/model";
export interface HookData {
hooks: HookItem[];
warnings: string[];
errors: string[];
}
export async function listThreads(client: AppServerClient, cwd: string, options: { archived?: boolean } = {}): Promise<Thread[]> {
const archived = options.archived ?? false;
const response = await client.listThreads(cwd, archived);
return threadsFromAppServerThreads(response.data, { archived });
}
export async function listModelMetadata(client: AppServerClient, options: { includeHidden?: boolean } = {}): Promise<ModelMetadata[]> {
const response = await client.listModels(options.includeHidden ?? false);
return modelMetadataFromAppServerModels(response.data);
}
export async function listSkillCatalog(
client: AppServerClient,
cwd: string,
options: { forceReload?: boolean; enabledOnly?: boolean } = {},
): Promise<{ skills: SkillMetadata[]; totalCount: number }> {
const response = await client.listSkills(cwd, options.forceReload ?? false);
const skills = response.data.flatMap((entry) => entry.skills);
return {
skills: skillMetadataFromAppServerSkills(options.enabledOnly === false ? skills : skills.filter((skill) => skill.enabled)),
totalCount: skills.length,
};
}
export async function listHookData(client: AppServerClient, cwd: string): Promise<HookData> {
const response = await client.listHooks(cwd);
const entry = response.data.find((item) => item.cwd === cwd);
if (!entry) return { hooks: [], warnings: [], errors: [] };
return {
hooks: hookItemsFromAppServerHooks(entry.hooks),
warnings: entry.warnings,
errors: entry.errors.map((error) => JSON.stringify(error)),
};
}
export async function trustHookItem(client: AppServerClient, hook: HookItem): Promise<void> {
await client.trustHook(appServerHookOperationFromHookItem(hook));
}
export async function setHookItemEnabled(client: AppServerClient, hook: HookItem, enabled: boolean): Promise<void> {
await client.setHookEnabled(appServerHookOperationFromHookItem(hook), enabled);
}
export async function restoreArchivedThread(client: AppServerClient, threadId: string): Promise<Thread> {
const response = await client.unarchiveThread(threadId);
return threadFromAppServerThread(response.thread);
}

View file

@ -1,13 +1,13 @@
import type { AppServerDiagnostics } from "./compatibility";
import type { ConfigReadResponse } from "../generated/app-server/v2/ConfigReadResponse";
import type { RateLimitSnapshot } from "../generated/app-server/v2/RateLimitSnapshot";
import type { PanelThread } from "../domain/threads/model";
import type { PanelModelOption, PanelSkillOption } from "../domain/catalog/metadata";
import type { Thread } from "../domain/threads/model";
import type { ModelMetadata, SkillMetadata } from "../domain/catalog/metadata";
export interface SharedAppServerMetadata {
effectiveConfig: ConfigReadResponse | null;
availableModels: readonly PanelModelOption[];
availableSkills: readonly PanelSkillOption[];
availableModels: readonly ModelMetadata[];
availableSkills: readonly SkillMetadata[];
rateLimit: RateLimitSnapshot | null;
appServerDiagnostics: AppServerDiagnostics;
}
@ -15,9 +15,9 @@ export interface SharedAppServerMetadata {
type SharedCache<T> = { kind: "unloaded" } | { kind: "loaded"; data: T };
export interface SharedAppServerState {
threads: SharedCache<readonly PanelThread[]>;
threads: SharedCache<readonly Thread[]>;
appServerMetadata: SharedCache<SharedAppServerMetadata>;
availableModels: readonly PanelModelOption[];
availableModels: readonly ModelMetadata[];
}
export function createSharedAppServerState(): SharedAppServerState {
@ -28,7 +28,7 @@ export function createSharedAppServerState(): SharedAppServerState {
};
}
export function applySharedThreadList(state: SharedAppServerState, threads: readonly PanelThread[]): SharedAppServerState {
export function applySharedThreadList(state: SharedAppServerState, threads: readonly Thread[]): SharedAppServerState {
return {
...state,
threads: { kind: "loaded", data: cloneThreads(threads) },
@ -40,23 +40,23 @@ export function applySharedAppServerMetadata(state: SharedAppServerState, metada
return {
...state,
appServerMetadata: { kind: "loaded", data: clonedMetadata },
availableModels: cloneModelOptions(clonedMetadata.availableModels),
availableModels: cloneModelMetadata(clonedMetadata.availableModels),
};
}
export function applySharedModels(state: SharedAppServerState, models: readonly PanelModelOption[]): SharedAppServerState {
const clonedModels = cloneModelOptions(models);
export function applySharedModels(state: SharedAppServerState, models: readonly ModelMetadata[]): SharedAppServerState {
const clonedModels = cloneModelMetadata(models);
return {
...state,
appServerMetadata:
state.appServerMetadata.kind === "loaded"
? { kind: "loaded", data: { ...state.appServerMetadata.data, availableModels: cloneModelOptions(clonedModels) } }
? { kind: "loaded", data: { ...state.appServerMetadata.data, availableModels: cloneModelMetadata(clonedModels) } }
: state.appServerMetadata,
availableModels: clonedModels,
};
}
export function cachedSharedThreadList(state: SharedAppServerState): readonly PanelThread[] | null {
export function cachedSharedThreadList(state: SharedAppServerState): readonly Thread[] | null {
return state.threads.kind === "loaded" ? cloneThreads(state.threads.data) : null;
}
@ -65,15 +65,15 @@ export function cachedSharedAppServerMetadata(state: SharedAppServerState): Shar
return null;
}
export function cachedSharedModels(state: SharedAppServerState): PanelModelOption[] {
return cloneModelOptions(state.availableModels);
export function cachedSharedModels(state: SharedAppServerState): ModelMetadata[] {
return cloneModelMetadata(state.availableModels);
}
function cloneSharedAppServerMetadata(metadata: SharedAppServerMetadata): SharedAppServerMetadata {
return {
...metadata,
availableModels: cloneModelOptions(metadata.availableModels),
availableSkills: cloneSkillOptions(metadata.availableSkills),
availableModels: cloneModelMetadata(metadata.availableModels),
availableSkills: cloneSkillMetadata(metadata.availableSkills),
appServerDiagnostics: {
probes: { ...metadata.appServerDiagnostics.probes },
mcpServers: metadata.appServerDiagnostics.mcpServers.map((server) => ({ ...server })),
@ -81,11 +81,11 @@ function cloneSharedAppServerMetadata(metadata: SharedAppServerMetadata): Shared
};
}
function cloneThreads(threads: readonly PanelThread[]): PanelThread[] {
function cloneThreads(threads: readonly Thread[]): Thread[] {
return threads.map((thread) => ({ ...thread }));
}
function cloneModelOptions(models: readonly PanelModelOption[]): PanelModelOption[] {
function cloneModelMetadata(models: readonly ModelMetadata[]): ModelMetadata[] {
return models.map((model) => ({
...model,
supportedReasoningEfforts: [...model.supportedReasoningEfforts],
@ -95,6 +95,6 @@ function cloneModelOptions(models: readonly PanelModelOption[]): PanelModelOptio
}));
}
function cloneSkillOptions(skills: readonly PanelSkillOption[]): PanelSkillOption[] {
function cloneSkillMetadata(skills: readonly SkillMetadata[]): SkillMetadata[] {
return skills.map((skill) => ({ ...skill }));
}

View file

@ -1,5 +1,5 @@
import type { PanelThread } from "../domain/threads/model";
import type { PanelModelOption } from "../domain/catalog/metadata";
import type { Thread } from "../domain/threads/model";
import type { ModelMetadata } from "../domain/catalog/metadata";
import {
applySharedAppServerMetadata,
applySharedModels,
@ -12,16 +12,16 @@ import {
type SharedAppServerState,
} from "./shared-cache-state";
type ThreadListRefreshLifecycleState = { kind: "idle" } | { kind: "refreshing"; promise: Promise<readonly PanelThread[]> };
type ThreadListRefreshLifecycleState = { kind: "idle" } | { kind: "refreshing"; promise: Promise<readonly Thread[]> };
export class SharedAppServerCache {
private state: SharedAppServerState = createSharedAppServerState();
private threadListRefreshLifecycle: ThreadListRefreshLifecycleState = { kind: "idle" };
refreshThreadList(
fetchThreads: () => Promise<readonly PanelThread[]>,
onSnapshot?: (threads: readonly PanelThread[]) => void,
): Promise<readonly PanelThread[]> {
fetchThreads: () => Promise<readonly Thread[]>,
onSnapshot?: (threads: readonly Thread[]) => void,
): Promise<readonly Thread[]> {
if (this.threadListRefreshLifecycle.kind === "refreshing") return this.threadListRefreshLifecycle.promise;
const promise = fetchThreads()
.then((threads) => {
@ -38,11 +38,11 @@ export class SharedAppServerCache {
return promise;
}
applyThreadListSnapshot(threads: readonly PanelThread[]): void {
applyThreadListSnapshot(threads: readonly Thread[]): void {
this.state = applySharedThreadList(this.state, threads);
}
cachedThreadList(): readonly PanelThread[] | null {
cachedThreadList(): readonly Thread[] | null {
return cachedSharedThreadList(this.state);
}
@ -54,11 +54,11 @@ export class SharedAppServerCache {
return cachedSharedAppServerMetadata(this.state);
}
applyModelsSnapshot(models: readonly PanelModelOption[]): void {
applyModelsSnapshot(models: readonly ModelMetadata[]): void {
this.state = applySharedModels(this.state, models);
}
cachedModels(): PanelModelOption[] {
cachedModels(): ModelMetadata[] {
return cachedSharedModels(this.state);
}
}

View file

@ -1,7 +1,7 @@
import type { Thread } from "../generated/app-server/v2/Thread";
import type { PanelThread } from "../domain/threads/model";
import type { Thread as AppServerThread } from "../generated/app-server/v2/Thread";
import type { Thread } from "../domain/threads/model";
export function panelThreadFromAppServerThread(thread: Thread, options: { archived?: boolean } = {}): PanelThread {
export function threadFromAppServerThread(thread: AppServerThread, options: { archived?: boolean } = {}): Thread {
return {
id: thread.id,
preview: normalizeString(thread.preview),
@ -12,8 +12,8 @@ export function panelThreadFromAppServerThread(thread: Thread, options: { archiv
};
}
export function panelThreadsFromAppServerThreads(threads: readonly Thread[], options: { archived?: boolean } = {}): PanelThread[] {
return threads.map((thread) => panelThreadFromAppServerThread(thread, options));
export function threadsFromAppServerThreads(threads: readonly AppServerThread[], options: { archived?: boolean } = {}): Thread[] {
return threads.map((thread) => threadFromAppServerThread(thread, options));
}
function normalizeNullableString(value: string | null): string | null {

View file

@ -5,10 +5,10 @@ import {
type EphemeralStructuredTurnRuntimeClient,
type StructuredTurnOutputSchema,
} from "./ephemeral-structured-turn";
import { panelModelOptionsFromAppServerModels } from "./catalog-model";
import { modelMetadataFromAppServerModels } from "./catalog-model";
import type { Turn } from "../generated/app-server/v2/Turn";
import type { PanelModelOption, ReasoningEffort } from "../domain/catalog/metadata";
import { runtimeOverride, validatedRuntimeOverrideForModelOptions } from "../domain/catalog/runtime-overrides";
import type { ModelMetadata, ReasoningEffort } from "../domain/catalog/metadata";
import { runtimeOverride, validatedRuntimeOverrideForModelMetadata } from "../domain/catalog/runtime-overrides";
import { namingPrompt, titleFromGeneratedText, type ThreadNamingContext } from "../domain/threads/naming";
import { turnConversationSummary } from "../domain/threads/transcript";
@ -84,9 +84,9 @@ export function threadNamingRuntimeOverride(settings: ThreadNamingRuntimeSetting
export function validatedThreadNamingRuntimeOverride(
settings: ThreadNamingRuntimeSettings,
models: readonly PanelModelOption[],
models: readonly ModelMetadata[],
): ThreadNamingRuntimeOverride {
return validatedRuntimeOverrideForModelOptions({ model: settings.threadNamingModel, effort: settings.threadNamingEffort }, models);
return validatedRuntimeOverrideForModelMetadata({ model: settings.threadNamingModel, effort: settings.threadNamingEffort }, models);
}
async function threadNamingRuntimeOverrideForClient(
@ -97,7 +97,7 @@ async function threadNamingRuntimeOverrideForClient(
if (!runtime.model || !runtime.effort) return runtime;
try {
const response = await client.listModels(false);
return validatedThreadNamingRuntimeOverride(settings, panelModelOptionsFromAppServerModels(response.data));
return validatedThreadNamingRuntimeOverride(settings, modelMetadataFromAppServerModels(response.data));
} catch {
return runtime;
}

View file

@ -1,9 +1,9 @@
interface PanelModelServiceTier {
interface ModelServiceTier {
id: string;
name: string;
}
export interface PanelModelOption {
export interface ModelMetadata {
id: string;
model: string;
displayName: string;
@ -13,12 +13,12 @@ export interface PanelModelOption {
defaultReasoningEffort: string | null;
inputModalities: readonly string[];
additionalSpeedTiers: readonly string[];
serviceTiers: readonly PanelModelServiceTier[];
serviceTiers: readonly ModelServiceTier[];
defaultServiceTier: string | null;
isDefault: boolean;
}
export interface PanelSkillOption {
export interface SkillMetadata {
name: string;
description: string;
shortDescription?: string;
@ -27,9 +27,9 @@ export interface PanelSkillOption {
enabled: boolean;
}
type PanelHookTrustStatus = "managed" | "untrusted" | "trusted" | "modified";
type HookTrustStatus = "managed" | "untrusted" | "trusted" | "modified";
export interface PanelHookItem {
export interface HookItem {
key: string;
eventName: string;
matcher: string | null;
@ -39,7 +39,7 @@ export interface PanelHookItem {
enabled: boolean;
isManaged: boolean;
currentHash: string;
trustStatus: PanelHookTrustStatus;
trustStatus: HookTrustStatus;
}
export const REASONING_EFFORTS = ["none", "minimal", "low", "medium", "high", "xhigh"] as const;
@ -54,25 +54,25 @@ export function normalizeReasoningEffort(value: unknown): ReasoningEffort | null
return isReasoningEffort(value) ? value : null;
}
export function supportedEffortsForModelOption(model: PanelModelOption | null): ReasoningEffort[] {
export function supportedEffortsForModelMetadata(model: ModelMetadata | null): ReasoningEffort[] {
const efforts = model?.supportedReasoningEfforts.filter(isReasoningEffort) ?? [];
return efforts.length > 0 ? efforts : [...REASONING_EFFORTS];
}
export function defaultEffortForModelOption(model: PanelModelOption | null): ReasoningEffort | null {
export function defaultEffortForModelMetadata(model: ModelMetadata | null): ReasoningEffort | null {
return normalizeReasoningEffort(model?.defaultReasoningEffort);
}
export function sortedModelOptions(models: readonly PanelModelOption[]): PanelModelOption[] {
export function sortedModelMetadata(models: readonly ModelMetadata[]): ModelMetadata[] {
return [...models]
.filter((model) => !model.hidden)
.sort((a, b) => Number(b.isDefault) - Number(a.isDefault) || a.model.localeCompare(b.model));
}
export function findModelOptionByIdOrName(
models: readonly PanelModelOption[],
export function findModelMetadataByIdOrName(
models: readonly ModelMetadata[],
modelIdOrName: string | null | undefined,
): PanelModelOption | null {
): ModelMetadata | null {
if (!modelIdOrName) return null;
return models.find((model) => !model.hidden && (model.model === modelIdOrName || model.id === modelIdOrName)) ?? null;
}

View file

@ -1,5 +1,5 @@
import type { PanelModelOption, ReasoningEffort } from "./metadata";
import { findModelOptionByIdOrName, supportedEffortsForModelOption } from "./metadata";
import type { ModelMetadata, ReasoningEffort } from "./metadata";
import { findModelMetadataByIdOrName, supportedEffortsForModelMetadata } from "./metadata";
export interface RuntimeOverrideSettings {
model: string | null;
@ -18,16 +18,16 @@ export function runtimeOverride(settings: RuntimeOverrideSettings): RuntimeOverr
};
}
export function validatedRuntimeOverrideForModelOptions(
export function validatedRuntimeOverrideForModelMetadata(
settings: RuntimeOverrideSettings,
models: readonly PanelModelOption[],
models: readonly ModelMetadata[],
): RuntimeOverride {
const runtime = runtimeOverride(settings);
if (!runtime.model || !runtime.effort) return runtime;
const model = findModelOptionByIdOrName(models, runtime.model);
const model = findModelMetadataByIdOrName(models, runtime.model);
if (!model) return runtime;
const supportedEfforts = new Set(supportedEffortsForModelOption(model));
const supportedEfforts = new Set(supportedEffortsForModelMetadata(model));
return supportedEfforts.has(runtime.effort) ? runtime : { model: runtime.model };
}

View file

@ -1,6 +1,6 @@
import type { Turn } from "../../generated/app-server/v2/Turn";
import { shortThreadId } from "../../utils";
import { getThreadTitle, type PanelThread } from "./model";
import { getThreadTitle, type Thread } from "./model";
import { referencedThreadDisplayFromPrompt } from "./reference";
import { turnTranscriptEntries, type TurnTranscriptEntry } from "./transcript";
@ -36,7 +36,7 @@ export interface ArchiveExportSettings {
vaultPath?: string;
}
export interface ArchiveThreadInput extends PanelThread {
export interface ArchiveThreadInput extends Thread {
turns: Turn[];
}

View file

@ -3,7 +3,7 @@ import { shortThreadId } from "../../utils";
const MAX_THREAD_DISPLAY_TITLE_LENGTH = 96;
const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
export interface PanelThread {
export interface Thread {
id: string;
preview: string;
name: string | null;
@ -12,23 +12,19 @@ export interface PanelThread {
updatedAt: number;
}
export function getThreadTitle(thread: PanelThread): string {
export function getThreadTitle(thread: Thread): string {
return (
[thread.name, thread.preview, thread.id].map((value) => (typeof value === "string" ? normalizeTitle(value) : "")).find(Boolean) ??
thread.id
);
}
export function explicitThreadName(thread: PanelThread): string | null {
export function explicitThreadName(thread: Thread): string | null {
const name = typeof thread.name === "string" ? normalizeTitle(thread.name) : "";
return name.length > 0 ? name : null;
}
export function codexPanelDisplayTitle(
activeThreadId: string | null,
threads: readonly PanelThread[],
fallbackTitle?: string | null,
): string {
export function codexPanelDisplayTitle(activeThreadId: string | null, threads: readonly Thread[], fallbackTitle?: string | null): string {
if (!activeThreadId) return "Codex";
const thread = threads.find((item) => item.id === activeThreadId);
@ -36,24 +32,24 @@ export function codexPanelDisplayTitle(
return title ? `Codex: ${title}` : "Codex";
}
export function inheritedForkThreadName(threadId: string, threads: readonly PanelThread[]): string | null {
export function inheritedForkThreadName(threadId: string, threads: readonly Thread[]): string | null {
const thread = threads.find((item) => item.id === threadId);
return thread ? explicitThreadName(thread) : null;
}
export function upsertThread(threads: readonly PanelThread[], thread: PanelThread): PanelThread[] {
export function upsertThread(threads: readonly Thread[], thread: Thread): Thread[] {
const index = threads.findIndex((item) => item.id === thread.id);
if (index === -1) return [thread, ...threads];
return threads.map((item, itemIndex) => (itemIndex === index ? { ...item, ...thread } : item));
}
export function archivedThreadDisplayTitle(thread: PanelThread): string {
export function archivedThreadDisplayTitle(thread: Thread): string {
const title = fullThreadTitle(thread);
if (!title || title === thread.id || UUID_PATTERN.test(title)) return "Untitled archived thread";
return truncateTitle(title, MAX_THREAD_DISPLAY_TITLE_LENGTH);
}
function fullThreadTitle(thread: PanelThread): string {
function fullThreadTitle(thread: Thread): string {
return normalizeTitle(getThreadTitle(thread));
}

View file

@ -1,7 +1,7 @@
import type { Turn } from "../../generated/app-server/v2/Turn";
import type { UserInput } from "../../generated/app-server/v2/UserInput";
import { shortThreadId } from "../../utils";
import { getThreadTitle, type PanelThread } from "./model";
import { getThreadTitle, type Thread } from "./model";
import { chronologicalTurnConversationSummaries } from "./transcript";
export const REFERENCED_THREAD_TURN_LIMIT = 20;
@ -34,7 +34,7 @@ export function referencedThreadTurns(turns: Turn[]): ReferencedThreadTurn[] {
return chronologicalTurnConversationSummaries(turns);
}
export function referencedThreadPrompt(thread: PanelThread, turns: ReferencedThreadTurn[], userRequest: string): string {
export function referencedThreadPrompt(thread: Thread, turns: ReferencedThreadTurn[], userRequest: string): string {
const reference = referencedThreadDisplay(thread, turns.length);
const envelope = referencedThreadEnvelope(reference, userRequest);
@ -57,11 +57,11 @@ export function referencedThreadPrompt(thread: PanelThread, turns: ReferencedThr
].join("\n");
}
function referencedThreadStatus(thread: PanelThread, count: number): string {
function referencedThreadStatus(thread: Thread, count: number): string {
return `Referencing ${shortThreadId(thread.id)} (${String(count)}/${String(REFERENCED_THREAD_TURN_LIMIT)} turns).`;
}
function referencedThreadDisplay(thread: PanelThread, count: number): ReferencedThreadDisplay {
function referencedThreadDisplay(thread: Thread, count: number): ReferencedThreadDisplay {
return {
threadId: thread.id,
title: getThreadTitle(thread),
@ -71,7 +71,7 @@ function referencedThreadDisplay(thread: PanelThread, count: number): Referenced
}
export function referencedThreadInput(
thread: PanelThread,
thread: Thread,
turns: readonly ReferencedThreadTurn[],
userRequest: string,
messageInput: UserInput[],

View file

@ -1,8 +1,8 @@
import { type AppServerDiagnostics, capabilityProbeError, capabilityProbeOk } from "../../../app-server/compatibility";
import { listPanelModelOptions, listPanelSkillCatalog } from "../../../app-server/panel-data";
import { listModelMetadata, listSkillCatalog } from "../../../app-server/resource-operations";
import type { RateLimitSnapshot } from "../../../generated/app-server/v2/RateLimitSnapshot";
import type { SharedAppServerMetadata } from "../../../app-server/shared-cache-state";
import type { PanelModelOption, PanelSkillOption } from "../../../domain/catalog/metadata";
import type { ModelMetadata, SkillMetadata } from "../../../domain/catalog/metadata";
import { cloneAppServerDiagnostics, type ChatAppServerBaseHost } from "./shared";
export interface ChatAppServerMetadataActionsHost extends ChatAppServerBaseHost {
@ -17,10 +17,10 @@ export interface ChatAppServerMetadataActions {
refreshPublishedAppServerMetadata: () => Promise<SharedAppServerMetadata | null>;
publishAppServerMetadataSnapshot: () => void;
refreshModels: () => Promise<void>;
loadModels: () => Promise<{ data: PanelModelOption[]; probe: AppServerDiagnostics["probes"]["model/list"] }>;
loadModels: () => Promise<{ data: ModelMetadata[]; probe: AppServerDiagnostics["probes"]["model/list"] }>;
refreshSkills: (forceReload?: boolean) => Promise<void>;
refreshPublishedSkills: (forceReload?: boolean) => Promise<void>;
loadSkills: (forceReload?: boolean) => Promise<{ data: PanelSkillOption[]; probe: AppServerDiagnostics["probes"]["skills/list"] }>;
loadSkills: (forceReload?: boolean) => Promise<{ data: SkillMetadata[]; probe: AppServerDiagnostics["probes"]["skills/list"] }>;
refreshRateLimits: () => Promise<void>;
refreshPublishedRateLimits: () => Promise<void>;
loadRateLimit: () => Promise<{ data: RateLimitSnapshot | null; probe: AppServerDiagnostics["probes"]["account/rateLimits/read"] }>;
@ -118,11 +118,11 @@ async function refreshModels(host: ChatAppServerMetadataActionsHost): Promise<vo
async function loadModels(
host: ChatAppServerMetadataActionsHost,
): Promise<{ data: PanelModelOption[]; probe: AppServerDiagnostics["probes"]["model/list"] }> {
): Promise<{ data: ModelMetadata[]; probe: AppServerDiagnostics["probes"]["model/list"] }> {
const client = host.currentClient();
if (!client) return { data: [], probe: capabilityProbeError("model/list", new Error("Codex app-server is not connected.")) };
try {
const data = await listPanelModelOptions(client);
const data = await listModelMetadata(client);
return {
data,
probe: capabilityProbeOk("model/list", `${String(data.length)} models`),
@ -151,11 +151,11 @@ async function refreshPublishedSkills(host: ChatAppServerMetadataActionsHost, fo
async function loadSkills(
host: ChatAppServerMetadataActionsHost,
forceReload = false,
): Promise<{ data: PanelSkillOption[]; probe: AppServerDiagnostics["probes"]["skills/list"] }> {
): Promise<{ data: SkillMetadata[]; probe: AppServerDiagnostics["probes"]["skills/list"] }> {
const client = host.currentClient();
if (!client) return { data: [], probe: capabilityProbeError("skills/list", new Error("Codex app-server is not connected.")) };
try {
const catalog = await listPanelSkillCatalog(client, host.vaultPath, { forceReload });
const catalog = await listSkillCatalog(client, host.vaultPath, { forceReload });
return { data: catalog.skills, probe: capabilityProbeOk("skills/list", `${String(catalog.totalCount)} skills`) };
} catch (error) {
return { data: [], probe: capabilityProbeError("skills/list", error) };

View file

@ -1,5 +1,5 @@
import { listPanelThreads } from "../../../app-server/panel-data";
import type { PanelThread } from "../../../domain/threads/model";
import { listThreads } from "../../../app-server/resource-operations";
import type { Thread } from "../../../domain/threads/model";
import { requestedOrConfiguredServiceTier, type RuntimeSnapshot } from "../runtime/effective-settings";
import { resumedThreadActionFromAppServerResponse } from "../threads/thread-resume";
import type { ChatAppServerBaseHost } from "./shared";
@ -10,13 +10,13 @@ interface StartedThreadSummary {
export interface ChatAppServerThreadActionsHost extends ChatAppServerBaseHost {
runtimeSnapshot: () => RuntimeSnapshot;
publishThreadList: (threads: readonly PanelThread[]) => void;
publishThreadList: (threads: readonly Thread[]) => void;
syncThreadGoal: (threadId: string) => void;
}
export interface ChatAppServerThreadActions {
applyThreadList: (threads: readonly PanelThread[]) => void;
loadThreadList: () => Promise<readonly PanelThread[]>;
applyThreadList: (threads: readonly Thread[]) => void;
loadThreadList: () => Promise<readonly Thread[]>;
startThread: (preview?: string, options?: { syncGoal?: boolean }) => Promise<StartedThreadSummary | null>;
}
@ -30,14 +30,14 @@ export function createChatAppServerThreadActions(host: ChatAppServerThreadAction
};
}
function applyThreadList(host: ChatAppServerThreadActionsHost, threads: readonly PanelThread[]): void {
function applyThreadList(host: ChatAppServerThreadActionsHost, threads: readonly Thread[]): void {
host.stateStore.dispatch({ type: "thread-list/applied", threads, threadsLoaded: true });
}
async function loadThreadList(host: ChatAppServerThreadActionsHost): Promise<readonly PanelThread[]> {
async function loadThreadList(host: ChatAppServerThreadActionsHost): Promise<readonly Thread[]> {
const client = host.currentClient();
if (!client) throw new Error("Codex app-server is not connected.");
return listPanelThreads(client, host.vaultPath);
return listThreads(client, host.vaultPath);
}
async function startThread(

View file

@ -1,4 +1,4 @@
import type { PanelThread } from "../../domain/threads/model";
import type { Thread } from "../../domain/threads/model";
import type { SharedAppServerMetadata } from "../../app-server/shared-cache-state";
import type { CodexPanelSettings } from "../../settings/model";
import type { ChatTurnDiffViewState } from "./ui/turn-diff";
@ -13,9 +13,9 @@ export interface CodexChatHost {
notifyThreadRenamed(threadId: string, name: string | null): void;
refreshThreadsViewLiveState(): void;
refreshSharedThreadListFromOpenSurface(): void;
applyThreadListSnapshot(threads: readonly PanelThread[]): void;
refreshThreadList(fetchThreads: () => Promise<readonly PanelThread[]>): Promise<readonly PanelThread[]>;
cachedThreadList(): readonly PanelThread[] | null;
applyThreadListSnapshot(threads: readonly Thread[]): void;
refreshThreadList(fetchThreads: () => Promise<readonly Thread[]>): Promise<readonly Thread[]>;
cachedThreadList(): readonly Thread[] | null;
publishAppServerMetadata(metadata: SharedAppServerMetadata): void;
cachedAppServerMetadata(): SharedAppServerMetadata | null;
}

View file

@ -1,16 +1,16 @@
import type { InitializeResponse } from "../../generated/app-server/InitializeResponse";
import type { PanelThread } from "../../domain/threads/model";
import type { Thread } from "../../domain/threads/model";
import type { ThreadTokenUsage } from "../../generated/app-server/v2/ThreadTokenUsage";
import type { ReasoningEffort } from "../../domain/catalog/metadata";
import type { ChatRuntimeState } from "./runtime/state";
import type { PanelCollaborationMode } from "./runtime/collaboration";
import type { CollaborationMode } from "./runtime/collaboration";
import { parseServiceTier, type ServiceTier } from "../../app-server/thread-settings";
import type { ChatAction, PendingTurnStart } from "./chat-state";
import type { DisplayItem } from "./display/types";
export interface ActiveThreadResumedAction {
type: "active-thread/resumed";
thread: PanelThread;
thread: Thread;
cwd: string;
model: string | null;
reasoningEffort: ReasoningEffort | null;
@ -20,7 +20,7 @@ export interface ActiveThreadResumedAction {
activePermissionProfile: ChatRuntimeState["activePermissionProfile"];
displayItems?: readonly DisplayItem[];
status?: string;
listedThreads?: readonly PanelThread[];
listedThreads?: readonly Thread[];
}
export interface ActiveThreadSettingsAppliedAction {
@ -28,7 +28,7 @@ export interface ActiveThreadSettingsAppliedAction {
cwd: string;
model: string | null;
reasoningEffort: ReasoningEffort | null;
collaborationMode: PanelCollaborationMode;
collaborationMode: CollaborationMode;
serviceTier: ServiceTier | null;
approvalPolicy: ChatRuntimeState["activeApprovalPolicy"];
approvalsReviewer: ChatRuntimeState["activeApprovalsReviewer"];
@ -39,7 +39,7 @@ export interface ActiveThreadSettingsAppliedActionSettings {
cwd: string;
model: string | null;
effort: ReasoningEffort | null;
collaborationMode: { mode: PanelCollaborationMode };
collaborationMode: { mode: CollaborationMode };
serviceTier: string | null;
approvalPolicy: ChatRuntimeState["activeApprovalPolicy"];
approvalsReviewer: ChatRuntimeState["activeApprovalsReviewer"];
@ -66,7 +66,7 @@ export function clearActiveThreadAction(): ChatAction {
return { type: "active-thread/cleared" };
}
export function applyThreadListAction(threads: readonly PanelThread[], threadsLoaded?: boolean): ChatAction {
export function applyThreadListAction(threads: readonly Thread[], threadsLoaded?: boolean): ChatAction {
return { type: "thread-list/applied", threads, ...(threadsLoaded === undefined ? {} : { threadsLoaded }) };
}

View file

@ -1,6 +1,6 @@
import { activeTurnId as selectActiveTurnId, chatTurnBusy, pendingTurnStart } from "./chat-state";
import type { ChatState, PendingTurnStart } from "./chat-state";
import type { PanelThread } from "../../domain/threads/model";
import type { Thread } from "../../domain/threads/model";
import type { PendingApproval } from "./requests/approval";
import type { PendingUserInput } from "./requests/user-input";
import type { DisplayItem } from "./display/types";
@ -17,7 +17,7 @@ export interface SubmissionStateSnapshot {
activeThreadId: string | null;
activeTurnId: string | null;
busy: boolean;
listedThreads: readonly PanelThread[];
listedThreads: readonly Thread[];
displayItems: readonly DisplayItem[];
pendingTurnStart: PendingTurnStart | null;
}
@ -39,7 +39,7 @@ export function canSwitchToThread(state: ChatState, threadId: string): boolean {
return !chatTurnBusy(state) || threadId === state.activeThread.id;
}
export function listedThreads(state: ChatState): readonly PanelThread[] {
export function listedThreads(state: ChatState): readonly Thread[] {
return state.threadList.listedThreads;
}

View file

@ -2,15 +2,15 @@ import type { InitializeResponse } from "../../generated/app-server/InitializeRe
import type { ReasoningEffort } from "../../domain/catalog/metadata";
import type { ConfigReadResponse } from "../../generated/app-server/v2/ConfigReadResponse";
import type { RateLimitSnapshot } from "../../generated/app-server/v2/RateLimitSnapshot";
import type { PanelThread } from "../../domain/threads/model";
import type { PanelModelOption, PanelSkillOption } from "../../domain/catalog/metadata";
import type { Thread } from "../../domain/threads/model";
import type { ModelMetadata, SkillMetadata } from "../../domain/catalog/metadata";
import type { ThreadGoal } from "../../generated/app-server/v2/ThreadGoal";
import type { ThreadSettingsUpdate } from "../../app-server/thread-settings";
import type { ThreadTokenUsage } from "../../generated/app-server/v2/ThreadTokenUsage";
import type { AppServerDiagnostics } from "../../app-server/compatibility";
import { createAppServerDiagnostics } from "../../app-server/compatibility";
import type { ApprovalsReviewer } from "./runtime/approvals";
import type { PanelCollaborationMode } from "./runtime/collaboration";
import type { CollaborationMode } from "./runtime/collaboration";
import type { RequestedServiceTier } from "./runtime/service-tier-state";
import {
commitPendingThreadSettingsRuntimeState,
@ -55,12 +55,12 @@ interface ChatConnectionState {
initializeResponse: InitializeResponse | null;
appServerDiagnostics: AppServerDiagnostics;
rateLimit: RateLimitSnapshot | null;
availableModels: readonly PanelModelOption[];
availableSkills: readonly PanelSkillOption[];
availableModels: readonly ModelMetadata[];
availableSkills: readonly SkillMetadata[];
}
interface ChatThreadListState {
listedThreads: readonly PanelThread[];
listedThreads: readonly Thread[];
threadsLoaded: boolean;
}
@ -108,15 +108,15 @@ type ConnectionAction =
| {
type: "connection/metadata-applied";
effectiveConfig?: ConfigReadResponse | null;
availableModels?: readonly PanelModelOption[];
availableSkills?: readonly PanelSkillOption[];
availableModels?: readonly ModelMetadata[];
availableSkills?: readonly SkillMetadata[];
rateLimit?: RateLimitSnapshot | null;
appServerDiagnostics?: AppServerDiagnostics;
};
interface ThreadListAppliedAction {
type: "thread-list/applied";
threads?: readonly PanelThread[];
threads?: readonly Thread[];
threadsLoaded?: boolean;
}
@ -132,7 +132,7 @@ type RuntimeAction =
| { type: "runtime/requested-effort-set"; effort: ReasoningEffort | null }
| { type: "runtime/requested-service-tier-set"; serviceTier: RequestedServiceTier | null }
| { type: "runtime/requested-approvals-reviewer-set"; approvalsReviewer: ApprovalsReviewer | null }
| { type: "runtime/requested-collaboration-mode-set"; collaborationMode: PanelCollaborationMode }
| { type: "runtime/requested-collaboration-mode-set"; collaborationMode: CollaborationMode }
| { type: "runtime/pending-thread-settings-committed"; update: ThreadSettingsUpdate };
interface TurnStartedAction {

View file

@ -1,8 +1,8 @@
import type { PanelThread } from "../../../domain/threads/model";
import type { PanelModelOption, PanelSkillOption } from "../../../domain/catalog/metadata";
import type { Thread } from "../../../domain/threads/model";
import type { ModelMetadata, SkillMetadata } from "../../../domain/catalog/metadata";
import { prepareFuzzySearch, sortSearchResults, type SearchResult } from "obsidian";
import { findModelOptionByIdOrName, sortedModelOptions } from "../../../domain/catalog/metadata";
import { isReasoningEffort, REASONING_EFFORTS, supportedEffortsForModelOption } from "../../../domain/catalog/metadata";
import { findModelMetadataByIdOrName, sortedModelMetadata } from "../../../domain/catalog/metadata";
import { isReasoningEffort, REASONING_EFFORTS, supportedEffortsForModelMetadata } from "../../../domain/catalog/metadata";
import { SLASH_COMMANDS, slashCommandSubcommands, type SlashCommandName } from "./slash-commands";
import { getThreadTitle } from "../../../domain/threads/model";
import { shortThreadId } from "../../../utils";
@ -52,9 +52,9 @@ export function parseSlashCommand(text: string): { command: SlashCommandName; ar
export function activeComposerSuggestions(
beforeCursor: string,
notes: NoteCandidate[],
skills: readonly PanelSkillOption[],
threads: readonly PanelThread[] = [],
models: readonly PanelModelOption[] = [],
skills: readonly SkillMetadata[],
threads: readonly Thread[] = [],
models: readonly ModelMetadata[] = [],
currentModel: string | null = null,
): ComposerSuggestion[] {
return (
@ -269,7 +269,7 @@ function activeSlashSubcommandSuggestions(beforeCursor: string): ComposerSuggest
}));
}
function activeThreadCommandSuggestions(beforeCursor: string, threads: readonly PanelThread[]): ComposerSuggestion[] | null {
function activeThreadCommandSuggestions(beforeCursor: string, threads: readonly Thread[]): ComposerSuggestion[] | null {
const completion = activeCommandArgumentCompletionQuery(beforeCursor, /^\/(?:resume|refer|archive|rename)\s+([^\s\n]{0,120})$/);
if (!completion) return null;
@ -295,7 +295,7 @@ function activeThreadCommandSuggestions(beforeCursor: string, threads: readonly
}));
}
function activeModelOverrideSuggestions(beforeCursor: string, models: readonly PanelModelOption[]): ComposerSuggestion[] | null {
function activeModelOverrideSuggestions(beforeCursor: string, models: readonly ModelMetadata[]): ComposerSuggestion[] | null {
const completion = activeCommandArgumentCompletionQuery(beforeCursor, /^\/model\s+([^\n]{0,120})$/);
if (!completion) return null;
@ -310,7 +310,7 @@ function activeModelOverrideSuggestions(beforeCursor: string, models: readonly P
start,
appendSpaceOnInsert: true,
},
...sortedModelOptions(models)
...sortedModelMetadata(models)
.map((model, index) => {
const id = model.id.toLowerCase();
const name = model.model.toLowerCase();
@ -336,7 +336,7 @@ function activeModelOverrideSuggestions(beforeCursor: string, models: readonly P
function activeReasoningEffortSuggestions(
beforeCursor: string,
models: readonly PanelModelOption[],
models: readonly ModelMetadata[],
currentModel: string | null,
): ComposerSuggestion[] | null {
const completion = activeCommandArgumentCompletionQuery(beforeCursor, /^\/reasoning\s+([^\n]{0,120})$/);
@ -344,8 +344,8 @@ function activeReasoningEffortSuggestions(
const { query, start } = completion;
if (query === "default" || isReasoningEffort(query)) return null;
const model = findModelOptionByIdOrName(models, currentModel);
const efforts = model ? supportedEffortsForModelOption(model) : REASONING_EFFORTS;
const model = findModelMetadataByIdOrName(models, currentModel);
const efforts = model ? supportedEffortsForModelMetadata(model) : REASONING_EFFORTS;
const modelDetail = model ? `Supported by ${model.model}` : "Supported reasoning effort";
const suggestions = [
{
@ -378,7 +378,7 @@ function activeCommandArgumentCompletionQuery(beforeCursor: string, pattern: Reg
return { query, start: beforeCursor.length - rawQuery.length };
}
function activeSkillSuggestions(beforeCursor: string, skills: readonly PanelSkillOption[]): ComposerSuggestion[] | null {
function activeSkillSuggestions(beforeCursor: string, skills: readonly SkillMetadata[]): ComposerSuggestion[] | null {
const match = /(^|[\s([{])\$([^\s\])}]{0,120})$/.exec(beforeCursor);
if (match?.index === undefined) return null;

View file

@ -1,5 +1,5 @@
import type { UserInput } from "../../../generated/app-server/v2/UserInput";
import type { PanelSkillOption } from "../../../domain/catalog/metadata";
import type { SkillMetadata } from "../../../domain/catalog/metadata";
import { parseObsidianWikiLink } from "../../../shared/obsidian/wikilinks";
export interface ParsedWikiLink {
@ -38,7 +38,7 @@ export function userInputWithWikiLinkMentions(text: string, resolveMention: Wiki
export function userInputWithWikiLinkMentionsAndSkills(
text: string,
resolveMention: WikiLinkMentionResolver,
skills: readonly PanelSkillOption[],
skills: readonly SkillMetadata[],
): UserInput[] {
const input: UserInput[] = [{ type: "text", text, text_elements: [] }];
const seenPaths = new Set<string>();
@ -81,8 +81,8 @@ function parseWikiLink(raw: string): ParsedWikiLink | null {
return parsed ? { raw, ...parsed } : null;
}
function firstEnabledSkillByName(skills: readonly PanelSkillOption[]): Map<string, PanelSkillOption> {
const byName = new Map<string, PanelSkillOption>();
function firstEnabledSkillByName(skills: readonly SkillMetadata[]): Map<string, SkillMetadata> {
const byName = new Map<string, SkillMetadata>();
for (const skill of skills) {
if (!skill.enabled) continue;
const key = skill.name.toLowerCase();

View file

@ -24,7 +24,7 @@ export interface ChatPanelContext {
lifecycle: ChatPanelLifecycleContext;
render: ChatPanelRenderContext;
runtime: ChatPanelRuntimeContext;
thread: ChatPanelThreadContext;
thread: ChatThreadContext;
liveState: ChatPanelLiveStateContext;
scroll: ChatPanelScrollContext;
status: ChatPanelStatusContext;
@ -99,7 +99,7 @@ interface ChatPanelRuntimeContext {
statusSummaryLines: () => string[];
}
interface ChatPanelThreadContext {
interface ChatThreadContext {
ensureRestoredThreadLoaded: () => Promise<boolean>;
startNewThread: () => Promise<void>;
selectThread: (threadId: string) => Promise<void>;

View file

@ -6,7 +6,7 @@ import {
serviceTierLabel,
supportedReasoningEfforts,
} from "../../runtime/effective-settings";
import { sortedModelOptions } from "../../../../domain/catalog/metadata";
import { sortedModelMetadata } from "../../../../domain/catalog/metadata";
import { contextSummary, rateLimitSummary, type RateLimitSummary } from "../../runtime/status-summary";
import type {
EffortStatusLinesInput,
@ -46,7 +46,7 @@ export function runtimeComposerChoices(input: RuntimeComposerChoicesInput): {
} {
const config = readRuntimeConfig(input.state.connection.effectiveConfig);
const activeModel = currentModel(input.snapshot, config);
const models = sortedModelOptions(input.state.connection.availableModels);
const models = sortedModelMetadata(input.state.connection.availableModels);
const modelChoices: RuntimeChoice[] = models.slice(0, 12).map((model) => ({
label: model.model,
selected: activeModel === model.model,

View file

@ -1,4 +1,4 @@
import type { PanelThread } from "../../../../domain/threads/model";
import type { Thread } from "../../../../domain/threads/model";
import { getThreadTitle } from "../../../../domain/threads/model";
import { effectiveConfigSections, rateLimitSummary } from "../../runtime/status-summary";
import { connectionDiagnosticSections } from "../../diagnostics";
@ -36,7 +36,7 @@ export function toolbarViewModel(input: ToolbarViewModelInput): ToolbarViewModel
}
function toolbarThreadRows(input: {
threads: readonly PanelThread[];
threads: readonly Thread[];
activeThreadId: string | null;
turnBusy: boolean;
archiveConfirmThreadId: string | null;

View file

@ -1,4 +1,4 @@
import type { PanelThread } from "../../../domain/threads/model";
import type { Thread } from "../../../domain/threads/model";
import type { OpenCodexPanelSnapshot } from "../../../workspace/open-panel-snapshot";
import { readRuntimeConfig } from "../runtime/config";
import { currentModel } from "../runtime/effective-settings";
@ -154,7 +154,7 @@ function displayItemsSignature(items: readonly DisplayItem[]): string {
return stableSignature(items);
}
function threadListSignature(threads: readonly PanelThread[]): string {
function threadListSignature(threads: readonly Thread[]): string {
return threads.map((thread) => signatureParts(thread.id, thread.name, thread.preview, thread.updatedAt, thread.archived)).join("\n");
}

View file

@ -1,13 +1,13 @@
export type PanelCollaborationMode = "default" | "plan";
export type CollaborationMode = "default" | "plan";
export function nextCollaborationMode(mode: PanelCollaborationMode): PanelCollaborationMode {
export function nextCollaborationMode(mode: CollaborationMode): CollaborationMode {
return mode === "plan" ? "default" : "plan";
}
export function collaborationModeLabel(mode: PanelCollaborationMode): string {
export function collaborationModeLabel(mode: CollaborationMode): string {
return mode === "plan" ? "Plan" : "Default";
}
export function collaborationModeToggleMessage(mode: PanelCollaborationMode): string {
export function collaborationModeToggleMessage(mode: CollaborationMode): string {
return mode === "plan" ? "Plan mode on for subsequent turns." : "Plan mode off for subsequent turns.";
}

View file

@ -3,7 +3,7 @@ import type { AskForApproval } from "../../../generated/app-server/v2/AskForAppr
import type { ConfigReadResponse } from "../../../generated/app-server/v2/ConfigReadResponse";
import type { RateLimitSnapshot } from "../../../generated/app-server/v2/RateLimitSnapshot";
import type { ThreadTokenUsage } from "../../../generated/app-server/v2/ThreadTokenUsage";
import { findModelOptionByIdOrName, type PanelModelOption } from "../../../domain/catalog/metadata";
import { findModelMetadataByIdOrName, type ModelMetadata } from "../../../domain/catalog/metadata";
import {
configuredServiceTierRequestValue,
clearedServiceTierRequestValue,
@ -11,9 +11,9 @@ import {
type ServiceTier,
type ServiceTierRequest,
} from "../../../app-server/thread-settings";
import { supportedEffortsForModelOption, type ReasoningEffort } from "../../../domain/catalog/metadata";
import { supportedEffortsForModelMetadata, type ReasoningEffort } from "../../../domain/catalog/metadata";
import { readRuntimeConfig, type RuntimeConfigProjection } from "./config";
import type { PanelCollaborationMode } from "./collaboration";
import type { CollaborationMode } from "./collaboration";
import { isAutoReviewReviewer, type ApprovalsReviewer } from "./approvals";
import { isFastServiceTier, type RequestedServiceTier } from "./service-tier-state";
@ -24,7 +24,7 @@ export interface RuntimeSnapshot {
activeThreadId: string | null;
activeModel: string | null;
activeReasoningEffort: ReasoningEffort | null;
activeCollaborationMode: PanelCollaborationMode;
activeCollaborationMode: CollaborationMode;
activeServiceTier: ServiceTier | null;
activeApprovalPolicy: AskForApproval | null;
activeApprovalsReviewer: ApprovalsReviewer | null;
@ -32,12 +32,12 @@ export interface RuntimeSnapshot {
requestedModel: PendingRuntimeSetting<string>;
requestedReasoningEffort: PendingRuntimeSetting<ReasoningEffort>;
requestedApprovalsReviewer: PendingRuntimeSetting<ApprovalsReviewer>;
selectedCollaborationMode: PanelCollaborationMode;
selectedCollaborationMode: CollaborationMode;
requestedServiceTier: PendingRuntimeSetting<RequestedServiceTier>;
tokenUsage: ThreadTokenUsage | null;
rateLimit: RateLimitSnapshot | null;
hasThreadTurns: boolean;
availableModels: readonly PanelModelOption[];
availableModels: readonly ModelMetadata[];
}
export function currentServiceTier(
@ -109,7 +109,7 @@ export function autoReviewActive(
export function supportedReasoningEfforts(snapshot: RuntimeSnapshot): ReasoningEffort[] {
const model = currentModel(snapshot);
return supportedEffortsForModelOption(findModelOptionByIdOrName(snapshot.availableModels, model));
return supportedEffortsForModelMetadata(findModelMetadataByIdOrName(snapshot.availableModels, model));
}
export function serviceTierLabel(
@ -170,6 +170,6 @@ export function pendingRuntimeSettingLabel<T>(setting: PendingRuntimeSetting<T>)
function currentModelServiceTiers(
snapshot: RuntimeSnapshot,
config: RuntimeConfigProjection = readRuntimeConfig(snapshot.effectiveConfig),
): PanelModelOption["serviceTiers"] {
return findModelOptionByIdOrName(snapshot.availableModels, currentModel(snapshot, config))?.serviceTiers ?? [];
): ModelMetadata["serviceTiers"] {
return findModelMetadataByIdOrName(snapshot.availableModels, currentModel(snapshot, config))?.serviceTiers ?? [];
}

View file

@ -1,7 +1,7 @@
import type { AppServerClient } from "../../../app-server/client";
import type { ReasoningEffort } from "../../../domain/catalog/metadata";
import { autoReviewReviewerForState, autoReviewToggleMessage, nextAutoReviewState } from "./approvals";
import type { PanelCollaborationMode } from "./collaboration";
import type { CollaborationMode } from "./collaboration";
import { collaborationModeToggleMessage, nextCollaborationMode } from "./collaboration";
import { readRuntimeConfig } from "./config";
import { autoReviewActive, fastModeActive, type RuntimeSnapshot } from "./effective-settings";
@ -41,7 +41,7 @@ export interface ChatRuntimeSettingsActions {
setRequestedReasoningEffortFromUi: (effort: ReasoningEffort | null) => Promise<void>;
toggleFastMode: () => Promise<void>;
toggleCollaborationMode: () => Promise<void>;
setCollaborationMode: (collaborationMode: PanelCollaborationMode) => Promise<boolean>;
setCollaborationMode: (collaborationMode: CollaborationMode) => Promise<boolean>;
toggleAutoReview: () => Promise<void>;
}
@ -119,7 +119,7 @@ async function toggleCollaborationMode(host: RuntimeSettingsActionsHost): Promis
await setCollaborationMode(host, next);
}
async function setCollaborationMode(host: RuntimeSettingsActionsHost, collaborationMode: PanelCollaborationMode): Promise<boolean> {
async function setCollaborationMode(host: RuntimeSettingsActionsHost, collaborationMode: CollaborationMode): Promise<boolean> {
dispatch(host, { type: "runtime/requested-collaboration-mode-set", collaborationMode });
dispatch(host, { type: "ui/panel-set", panel: null });
const result = await applyPendingThreadSettingsResult(host);

View file

@ -1,8 +1,8 @@
import type { PanelModelOption } from "../../../domain/catalog/metadata";
import type { ModelMetadata } from "../../../domain/catalog/metadata";
export type RequestedServiceTier = "fast" | "off";
export function isFastServiceTier(value: string | null | undefined, serviceTiers: PanelModelOption["serviceTiers"] = []): boolean {
export function isFastServiceTier(value: string | null | undefined, serviceTiers: ModelMetadata["serviceTiers"] = []): boolean {
if (!value) return false;
if (value === "fast") return true;
if (serviceTiers.length === 0) return value === "priority";

View file

@ -1,7 +1,7 @@
import type { ActivePermissionProfile } from "../../../generated/app-server/v2/ActivePermissionProfile";
import type { AskForApproval } from "../../../generated/app-server/v2/AskForApproval";
import { parseServiceTier, type ServiceTier, type ThreadSettingsUpdate } from "../../../app-server/thread-settings";
import type { PanelCollaborationMode } from "./collaboration";
import type { CollaborationMode } from "./collaboration";
import type { ReasoningEffort } from "../../../domain/catalog/metadata";
import type { ApprovalsReviewer } from "./approvals";
import type { RequestedServiceTier } from "./service-tier-state";
@ -15,7 +15,7 @@ import {
export interface ChatRuntimeState {
activeModel: string | null;
activeReasoningEffort: ReasoningEffort | null;
activeCollaborationMode: PanelCollaborationMode;
activeCollaborationMode: CollaborationMode;
activeServiceTier: ServiceTier | null;
activeApprovalPolicy: AskForApproval | null;
activeApprovalsReviewer: ApprovalsReviewer | null;
@ -23,7 +23,7 @@ export interface ChatRuntimeState {
requestedModel: PendingRuntimeSetting<string>;
requestedReasoningEffort: PendingRuntimeSetting<ReasoningEffort>;
requestedApprovalsReviewer: PendingRuntimeSetting<ApprovalsReviewer>;
selectedCollaborationMode: PanelCollaborationMode;
selectedCollaborationMode: CollaborationMode;
requestedServiceTier: PendingRuntimeSetting<RequestedServiceTier>;
}
@ -90,10 +90,7 @@ export function setRequestedApprovalsReviewerRuntimeState(
};
}
export function setSelectedCollaborationModeRuntimeState(
state: ChatRuntimeState,
collaborationMode: PanelCollaborationMode,
): ChatRuntimeState {
export function setSelectedCollaborationModeRuntimeState(state: ChatRuntimeState, collaborationMode: CollaborationMode): ChatRuntimeState {
return {
...state,
selectedCollaborationMode: collaborationMode,

View file

@ -2,8 +2,8 @@ import type { RateLimitWindow } from "../../../generated/app-server/v2/RateLimit
import type { SpendControlLimitSnapshot } from "../../../generated/app-server/v2/SpendControlLimitSnapshot";
import type { ThreadTokenUsage } from "../../../generated/app-server/v2/ThreadTokenUsage";
import { jsonPreview } from "../../../utils";
import { sortedModelOptions } from "../../../domain/catalog/metadata";
import { defaultEffortForModelOption } from "../../../domain/catalog/metadata";
import { sortedModelMetadata } from "../../../domain/catalog/metadata";
import { defaultEffortForModelMetadata } from "../../../domain/catalog/metadata";
import { readRuntimeConfig, type RuntimeConfigProjection } from "./config";
import {
currentApprovalsReviewer,
@ -184,14 +184,14 @@ function contextUsageTokens(usage: ThreadTokenUsage): number {
function configuredModel(snapshot: RuntimeSnapshot, config: RuntimeConfigProjection): string | null {
if (config.model) return config.model;
return sortedModelOptions(snapshot.availableModels).find((model) => model.isDefault)?.model ?? null;
return sortedModelMetadata(snapshot.availableModels).find((model) => model.isDefault)?.model ?? null;
}
function configuredReasoningEffort(snapshot: RuntimeSnapshot, config: RuntimeConfigProjection): string | null {
if (config.rawReasoningEffort) return config.rawReasoningEffort;
const model = configuredModel(snapshot, config);
return defaultEffortForModelOption(
sortedModelOptions(snapshot.availableModels).find((availableModel) => availableModel.model === model) ?? null,
return defaultEffortForModelMetadata(
sortedModelMetadata(snapshot.availableModels).find((availableModel) => availableModel.model === model) ?? null,
);
}

View file

@ -1,7 +1,7 @@
import { Notice } from "obsidian";
import type { AppServerClient } from "../../../app-server/client";
import { panelThreadFromAppServerThread } from "../../../app-server/thread-model";
import { threadFromAppServerThread } from "../../../app-server/thread-model";
import { exportArchivedThreadMarkdown } from "../../../domain/threads/export";
import type { ArchiveExportAdapter } from "../../../domain/threads/export";
import { inheritedForkThreadName } from "../../../domain/threads/model";
@ -88,7 +88,7 @@ async function archiveThreadOnServer(
if (saveMarkdown) {
const response = await client.readThread(threadId, true);
const result = await exportArchivedThreadMarkdown(
{ ...panelThreadFromAppServerThread(response.thread, { archived: true }), turns: response.thread.turns },
{ ...threadFromAppServerThread(response.thread, { archived: true }), turns: response.thread.turns },
{ ...settings, vaultPath: host.vaultPath },
host.archiveAdapter(),
);
@ -182,7 +182,7 @@ async function rollbackThread(host: ChatThreadActionsHost, threadId: string): Pr
try {
host.setStatus("Rolling back latest turn...");
const response = await client.rollbackThread(threadId);
const thread = panelThreadFromAppServerThread(response.thread);
const thread = threadFromAppServerThread(response.thread);
dispatch(
host,
resumedThreadActionFromActiveRuntime({

View file

@ -6,7 +6,7 @@ import {
THREAD_NAMING_CONTEXT_UNAVAILABLE_MESSAGE,
type ThreadNamingContext,
} from "../../../domain/threads/naming";
import type { PanelThread } from "../../../domain/threads/model";
import type { Thread } from "../../../domain/threads/model";
import type { Turn } from "../../../generated/app-server/v2/Turn";
import type { CodexPanelSettings } from "../../../settings/model";
import type { ChatAction, ChatState, ChatStateStore } from "../chat-state";
@ -257,7 +257,7 @@ export class ThreadRenameController {
return Boolean(this.thread(threadId)?.name?.trim());
}
private thread(threadId: string): PanelThread | undefined {
private thread(threadId: string): Thread | undefined {
return this.state.threadList.listedThreads.find((item) => item.id === threadId);
}
}

View file

@ -1,9 +1,9 @@
import { parseServiceTier } from "../../../app-server/thread-settings";
import { upsertThread } from "../../../domain/threads/model";
import { panelThreadFromAppServerThread } from "../../../app-server/thread-model";
import { threadFromAppServerThread } from "../../../app-server/thread-model";
import type { ReasoningEffort } from "../../../domain/catalog/metadata";
import type { AskForApproval } from "../../../generated/app-server/v2/AskForApproval";
import type { PanelThread } from "../../../domain/threads/model";
import type { Thread } from "../../../domain/threads/model";
import type { ThreadStartResponse } from "../../../generated/app-server/v2/ThreadStartResponse";
import type { ThreadResumeResponse } from "../../../generated/app-server/v2/ThreadResumeResponse";
import type { ChatRuntimeState } from "../runtime/state";
@ -11,7 +11,7 @@ import type { DisplayItem } from "../display/types";
import type { ActiveThreadResumedAction } from "../chat-state-actions";
interface ThreadActivationResponse {
thread: PanelThread;
thread: Thread;
cwd: string;
model: string | null;
serviceTier: string | null;
@ -23,18 +23,18 @@ interface ThreadActivationResponse {
export interface ResumedThreadActionParams {
response: ThreadActivationResponse;
listedThreads?: readonly PanelThread[];
listedThreads?: readonly Thread[];
displayItems?: readonly DisplayItem[];
}
export interface ResumedThreadFromAppServerParams {
response: ThreadStartResponse | ThreadResumeResponse;
listedThreads?: readonly PanelThread[];
listedThreads?: readonly Thread[];
displayItems?: readonly DisplayItem[];
}
export interface ResumedThreadFromActiveRuntimeParams {
thread: PanelThread;
thread: Thread;
cwd: string;
runtime: Pick<
ChatRuntimeState,
@ -45,7 +45,7 @@ export interface ResumedThreadFromActiveRuntimeParams {
| "activeApprovalsReviewer"
| "activePermissionProfile"
>;
listedThreads?: readonly PanelThread[];
listedThreads?: readonly Thread[];
displayItems?: readonly DisplayItem[];
}
@ -93,7 +93,7 @@ export function resumedThreadAction(params: ResumedThreadActionParams): ActiveTh
function threadActivationResponseFromAppServerResponse(response: ThreadStartResponse | ThreadResumeResponse): ThreadActivationResponse {
return {
thread: panelThreadFromAppServerThread(response.thread),
thread: threadFromAppServerThread(response.thread),
cwd: response.cwd,
model: response.model,
reasoningEffort: response.reasoningEffort,

View file

@ -4,7 +4,7 @@ import {
referencedThreadTurns,
REFERENCED_THREAD_TURN_LIMIT,
} from "../../../domain/threads/reference";
import type { PanelThread } from "../../../domain/threads/model";
import type { Thread } from "../../../domain/threads/model";
import {
executeSlashCommand as runSlashCommand,
type SlashCommandExecutionResult,
@ -128,7 +128,7 @@ async function executeSlashCommand(
async function referencedThreadInput(
host: SlashCommandActionsHost,
client: AppServerClient,
thread: PanelThread,
thread: Thread,
message: string,
): Promise<ThreadReferenceInput | null> {
try {

View file

@ -1,5 +1,5 @@
import type { ReasoningEffort } from "../../../domain/catalog/metadata";
import type { PanelThread } from "../../../domain/threads/model";
import type { Thread } from "../../../domain/threads/model";
import type { ThreadGoal } from "../../../generated/app-server/v2/ThreadGoal";
import type { ThreadGoalStatus } from "../../../generated/app-server/v2/ThreadGoalStatus";
import type { UserInput } from "../../../generated/app-server/v2/UserInput";
@ -24,11 +24,11 @@ import {
export interface SlashCommandExecutionContext {
activeThreadId: string | null;
busy: boolean;
listedThreads: readonly PanelThread[];
listedThreads: readonly Thread[];
startNewThread: () => Promise<void>;
startThreadForGoal: (objective: string) => Promise<string | null>;
resumeThread: (threadId: string) => Promise<void>;
referThread: (thread: PanelThread, message: string) => Promise<ThreadReferenceInput | null>;
referThread: (thread: Thread, message: string) => Promise<ThreadReferenceInput | null>;
forkThread: (threadId: string) => Promise<void>;
rollbackThread: (threadId: string) => Promise<void>;
compactThread: (threadId: string) => Promise<void>;
@ -392,7 +392,7 @@ function lineToRow(line: string): DisplayDetailMetaRow {
return { key: "message", value: line };
}
type ThreadResolution = { ok: true; thread: PanelThread } | { ok: false; message: string };
type ThreadResolution = { ok: true; thread: Thread } | { ok: false; message: string };
function parseReferArgs(args: string): { threadQuery: string; message: string } | null {
const parsed = parseThreadAndTextArgs(args);
@ -414,7 +414,7 @@ function parseThreadAndNameArgs(args: string): { threadQuery: string; text: stri
return text ? { threadQuery: parsed.threadQuery, text } : null;
}
function resolveThreadArgument(args: string, threads: readonly PanelThread[]): ThreadResolution {
function resolveThreadArgument(args: string, threads: readonly Thread[]): ThreadResolution {
const query = args.trim();
if (!query) {
const thread = threads.at(0);

View file

@ -4,8 +4,8 @@ import type { AppServerClient } from "../../app-server/client";
import { VIEW_TYPE_CODEX_PANEL } from "../../constants";
import type { DisplayDetailSection, DisplayItem } from "./display/types";
import type { ReasoningEffort } from "../../domain/catalog/metadata";
import type { PanelModelOption } from "../../domain/catalog/metadata";
import type { PanelThread } from "../../domain/threads/model";
import type { ModelMetadata } from "../../domain/catalog/metadata";
import type { Thread } from "../../domain/threads/model";
import { collaborationModeLabel as formatCollaborationModeLabel } from "./runtime/collaboration";
import type { RuntimeSnapshot } from "./runtime/effective-settings";
import { chatTurnBusy, createChatStateStore, type ChatState, type ChatAction } from "./chat-state";
@ -409,7 +409,7 @@ export class CodexChatView extends ItemView {
return this.loadSharedThreadList();
}
applyThreadListSnapshot(threads: readonly PanelThread[]): void {
applyThreadListSnapshot(threads: readonly Thread[]): void {
this.controllers.appServer.threads.applyThreadList(threads);
this.refreshTabHeader();
this.controllers.render.controller.render();
@ -420,7 +420,7 @@ export class CodexChatView extends ItemView {
this.controllers.render.controller.render();
}
applyAvailableModelsSnapshot(models: readonly PanelModelOption[]): void {
applyAvailableModelsSnapshot(models: readonly ModelMetadata[]): void {
this.dispatch({ type: "connection/metadata-applied", availableModels: models });
this.controllers.render.controller.render();
}

View file

@ -6,9 +6,9 @@ import {
type StructuredTurnOutputSchema,
} from "../../app-server/ephemeral-structured-turn";
import type { ReasoningEffort } from "../../domain/catalog/metadata";
import { panelModelOptionsFromAppServerModels } from "../../app-server/catalog-model";
import type { PanelModelOption } from "../../domain/catalog/metadata";
import { runtimeOverride, validatedRuntimeOverrideForModelOptions } from "../../domain/catalog/runtime-overrides";
import { modelMetadataFromAppServerModels } from "../../app-server/catalog-model";
import type { ModelMetadata } from "../../domain/catalog/metadata";
import { runtimeOverride, validatedRuntimeOverrideForModelMetadata } from "../../domain/catalog/runtime-overrides";
import type { SelectionRewriteRuntimeSettings } from "./model";
import { SELECTION_REWRITE_DEVELOPER_INSTRUCTIONS, SELECTION_REWRITE_SERVICE_NAME } from "./prompt";
import { SelectionRewriteOutputError, selectionRewriteOutputParseResultFromTurn, type SelectionRewriteOutput } from "./output";
@ -86,9 +86,9 @@ export function selectionRewriteRuntimeOverride(settings: SelectionRewriteRuntim
export function validatedSelectionRewriteRuntimeOverride(
settings: SelectionRewriteRuntimeSettings,
models: readonly PanelModelOption[],
models: readonly ModelMetadata[],
): SelectionRewriteRuntimeOverride {
return validatedRuntimeOverrideForModelOptions(
return validatedRuntimeOverrideForModelMetadata(
{ model: settings.rewriteSelectionModel, effort: settings.rewriteSelectionEffort },
models,
);
@ -102,7 +102,7 @@ async function selectionRewriteRuntimeOverrideForClient(
if (!runtime.model || !runtime.effort) return runtime;
try {
const response = await client.listModels(false);
return validatedSelectionRewriteRuntimeOverride(settings, panelModelOptionsFromAppServerModels(response.data));
return validatedSelectionRewriteRuntimeOverride(settings, modelMetadataFromAppServerModels(response.data));
} catch {
return runtime;
}

View file

@ -1,9 +1,9 @@
import { Notice, Platform, SuggestModal, type App } from "obsidian";
import { listPanelThreads } from "../../app-server/panel-data";
import { listThreads } from "../../app-server/resource-operations";
import { withShortLivedAppServerClient } from "../../app-server/short-lived-client";
import { getThreadTitle } from "../../domain/threads/model";
import type { PanelThread } from "../../domain/threads/model";
import type { Thread } from "../../domain/threads/model";
import type { CodexPanelSettings } from "../../settings/model";
import { shortThreadId } from "../../utils";
@ -11,14 +11,14 @@ export interface ThreadPickerHost {
readonly app: App;
readonly settings: CodexPanelSettings;
readonly vaultPath: string;
cachedThreadList(): readonly PanelThread[] | null;
refreshThreadList(fetchThreads: () => Promise<readonly PanelThread[]>): Promise<readonly PanelThread[]>;
cachedThreadList(): readonly Thread[] | null;
refreshThreadList(fetchThreads: () => Promise<readonly Thread[]>): Promise<readonly Thread[]>;
openThreadInCurrentView(threadId: string): Promise<void>;
openThreadInAvailableView(threadId: string): Promise<void>;
}
interface ThreadSuggestion {
thread: PanelThread;
thread: Thread;
title: string;
}
@ -41,7 +41,7 @@ export async function openThreadPicker(host: ThreadPickerHost): Promise<void> {
}
}
export function threadPickerSuggestions(threads: readonly PanelThread[], queryText: string): ThreadSuggestion[] {
export function threadPickerSuggestions(threads: readonly Thread[], queryText: string): ThreadSuggestion[] {
const query = queryText.trim().toLowerCase();
return [...threads]
.sort((a, b) => b.updatedAt - a.updatedAt)
@ -78,7 +78,7 @@ export function threadOpenModeFromEvent(evt: MouseEvent | KeyboardEvent): Thread
return "current";
}
async function loadThreadPickerThreads(host: ThreadPickerHost): Promise<readonly PanelThread[]> {
async function loadThreadPickerThreads(host: ThreadPickerHost): Promise<readonly Thread[]> {
const cached = host.cachedThreadList();
if (cached) return cached;
@ -87,7 +87,7 @@ async function loadThreadPickerThreads(host: ThreadPickerHost): Promise<readonly
host.vaultPath,
async (client) =>
host.refreshThreadList(async () => {
return listPanelThreads(client, host.vaultPath);
return listThreads(client, host.vaultPath);
}),
{
unhandledServerRequestMessage: "Codex thread picker does not handle server requests.",
@ -98,7 +98,7 @@ async function loadThreadPickerThreads(host: ThreadPickerHost): Promise<readonly
class ThreadPickerModal extends SuggestModal<ThreadSuggestion> {
constructor(
private readonly host: ThreadPickerHost,
private readonly threads: readonly PanelThread[],
private readonly threads: readonly Thread[],
) {
super(host.app);
this.limit = MAX_THREAD_PICKER_SUGGESTIONS;

View file

@ -1,5 +1,5 @@
import type { OpenCodexPanelSnapshot } from "../../workspace/open-panel-snapshot";
import type { PanelThread } from "../../domain/threads/model";
import type { Thread } from "../../domain/threads/model";
import { getThreadTitle } from "../../domain/threads/model";
type ThreadsLiveStatus = "needs-input" | "approval" | "running" | "draft" | "offline" | "open";
@ -12,7 +12,7 @@ export interface ThreadsLiveState {
}
export interface ThreadsRowModel {
thread: PanelThread;
thread: Thread;
title: string;
live: ThreadsLiveState | null;
selected: boolean;
@ -33,7 +33,7 @@ const STATUS_PRIORITY: Record<ThreadsLiveStatus, number> = {
};
export function threadRows(
threads: readonly PanelThread[],
threads: readonly Thread[],
snapshots: OpenCodexPanelSnapshot[],
renameStates: ReadonlyMap<string, ThreadsRenameState>,
archiveConfirmThreadId: string | null = null,

View file

@ -2,10 +2,10 @@ import { ItemView, Notice, type WorkspaceLeaf } from "obsidian";
import type { AppServerClient } from "../../app-server/client";
import { ConnectionManager, StaleConnectionError } from "../../app-server/connection-manager";
import { listPanelThreads } from "../../app-server/panel-data";
import { panelThreadFromAppServerThread } from "../../app-server/thread-model";
import { listThreads } from "../../app-server/resource-operations";
import { threadFromAppServerThread } from "../../app-server/thread-model";
import { VIEW_TYPE_CODEX_THREADS } from "../../constants";
import type { PanelThread } from "../../domain/threads/model";
import type { Thread } from "../../domain/threads/model";
import type { CodexPanelSettings } from "../../settings/model";
import { exportArchivedThreadMarkdown } from "../../domain/threads/export";
import type { OpenCodexPanelSnapshot } from "../../workspace/open-panel-snapshot";
@ -40,8 +40,8 @@ export interface CodexThreadsHost {
getOpenPanelSnapshots(): OpenCodexPanelSnapshot[];
notifyThreadArchived(threadId: string, options?: { closeOpenPanels?: boolean }): void;
notifyThreadRenamed(threadId: string, name: string | null): void;
refreshThreadList(fetchThreads: () => Promise<readonly PanelThread[]>): Promise<readonly PanelThread[]>;
cachedThreadList(): readonly PanelThread[] | null;
refreshThreadList(fetchThreads: () => Promise<readonly Thread[]>): Promise<readonly Thread[]>;
cachedThreadList(): readonly Thread[] | null;
}
type ThreadsViewStatus =
@ -58,7 +58,7 @@ export class CodexThreadsView extends ItemView {
private connectionLifecycle: ThreadsViewConnectionLifecycleState = { kind: "idle" };
private refreshLifecycle: ThreadsViewRefreshLifecycleState = { kind: "idle" };
private status: ThreadsViewStatus = { kind: "idle" };
private threads: readonly PanelThread[] = [];
private threads: readonly Thread[] = [];
private readonly renameStates = new Map<string, ThreadsRenameState>();
private archiveConfirmThreadId: string | null = null;
@ -132,7 +132,7 @@ export class CodexThreadsView extends ItemView {
if (this.isStaleRefresh(refresh) || !this.client) return;
const threads = await this.plugin.refreshThreadList(async () => {
if (!this.client) return [];
return listPanelThreads(this.client, this.plugin.vaultPath);
return listThreads(this.client, this.plugin.vaultPath);
});
if (this.isStaleRefresh(refresh)) return;
this.threads = threads;
@ -253,7 +253,7 @@ export class CodexThreadsView extends ItemView {
this.scheduleRender();
}
applyThreadListSnapshot(threads: readonly PanelThread[]): void {
applyThreadListSnapshot(threads: readonly Thread[]): void {
this.threads = threads;
this.status = threads.length === 0 ? { kind: "empty", message: "No threads" } : { kind: "idle" };
this.render();
@ -367,7 +367,7 @@ export class CodexThreadsView extends ItemView {
if (saveMarkdown) {
const response = await this.client.readThread(threadId, true);
const result = await exportArchivedThreadMarkdown(
{ ...panelThreadFromAppServerThread(response.thread, { archived: true }), turns: response.thread.turns },
{ ...threadFromAppServerThread(response.thread, { archived: true }), turns: response.thread.turns },
{ ...this.plugin.settings, vaultPath: this.plugin.vaultPath },
this.app.vault.adapter,
);

View file

@ -1,17 +1,17 @@
import type { AppServerClient } from "../app-server/client";
import { listPanelHookData, listPanelModelOptions, listPanelThreads, type PanelHookData } from "../app-server/panel-data";
import type { PanelModelOption } from "../domain/catalog/metadata";
import type { PanelThread } from "../domain/threads/model";
import { listHookData, listModelMetadata, listThreads, type HookData } from "../app-server/resource-operations";
import type { ModelMetadata } from "../domain/catalog/metadata";
import type { Thread } from "../domain/threads/model";
import { errorMessage } from "../utils";
export interface LoadedHooks extends PanelHookData {
export interface LoadedHooks extends HookData {
status: string;
}
export interface SettingsDataLoad {
models: SettledSettingsData<PanelModelOption[]>;
models: SettledSettingsData<ModelMetadata[]>;
hooks: SettledSettingsData<LoadedHooks>;
archivedThreads: SettledSettingsData<PanelThread[]>;
archivedThreads: SettledSettingsData<Thread[]>;
}
type SettledSettingsData<T> =
@ -27,9 +27,9 @@ type SettledSettingsData<T> =
export async function loadSettingsData(client: AppServerClient, cwd: string): Promise<SettingsDataLoad> {
const [modelsResult, hooksResult, archivedThreadsResult] = await Promise.allSettled([
listPanelModelOptions(client),
listPanelHookData(client, cwd),
listPanelThreads(client, cwd, { archived: true }),
listModelMetadata(client),
listHookData(client, cwd),
listThreads(client, cwd, { archived: true }),
] as const);
return {
@ -57,7 +57,7 @@ export async function loadSettingsData(client: AppServerClient, cwd: string): Pr
}
export async function loadHookData(client: AppServerClient, cwd: string): Promise<LoadedHooks> {
const hooks = await listPanelHookData(client, cwd);
const hooks = await listHookData(client, cwd);
return {
...hooks,
status: hooksStatus(hooks.hooks.length),

View file

@ -1,7 +1,7 @@
import { Setting } from "obsidian";
import type { PanelHookItem } from "../domain/catalog/metadata";
import type { PanelThread } from "../domain/threads/model";
import type { HookItem } from "../domain/catalog/metadata";
import type { Thread } from "../domain/threads/model";
import { archivedThreadDisplayTitle } from "../domain/threads/model";
import { shortThreadId } from "../utils";
@ -10,7 +10,7 @@ export interface ArchivedThreadSectionState {
exportFolderTemplate: string;
exportFilenameTemplate: string;
exportTags: string;
threads: PanelThread[];
threads: Thread[];
loaded: boolean;
loading: boolean;
status: string;
@ -22,14 +22,14 @@ export interface ArchivedThreadSectionState {
}
export interface HookSectionState {
hooks: PanelHookItem[];
hooks: HookItem[];
warnings: string[];
errors: string[];
loaded: boolean;
loading: boolean;
status: string;
onTrust(hook: PanelHookItem): void;
onToggleEnabled(hook: PanelHookItem, enabled: boolean): void;
onTrust(hook: HookItem): void;
onToggleEnabled(hook: HookItem, enabled: boolean): void;
}
export function renderHookSection(containerEl: HTMLElement, state: HookSectionState): void {
@ -166,7 +166,7 @@ function renderHooks(containerEl: HTMLElement, state: HookSectionState): void {
}
}
function renderHookRow(list: HTMLElement, hook: PanelHookItem, state: HookSectionState): void {
function renderHookRow(list: HTMLElement, hook: HookItem, state: HookSectionState): void {
const canTrust = !hook.isManaged && (hook.trustStatus === "untrusted" || hook.trustStatus === "modified");
const hookName = firstNonEmptyString(hook.statusMessage, hook.command, hook.matcher, hook.eventName);
const setting = new Setting(list)

View file

@ -1,14 +1,14 @@
import { type App, Notice, type Plugin, PluginSettingTab, Setting, setIcon } from "obsidian";
import type { AppServerClient } from "../app-server/client";
import { restoreArchivedPanelThread, setPanelHookEnabled, trustPanelHook } from "../app-server/panel-data";
import { restoreArchivedThread, setHookItemEnabled, trustHookItem } from "../app-server/resource-operations";
import { withShortLivedAppServerClient } from "../app-server/short-lived-client";
import { DEFAULT_CODEX_PATH } from "../constants";
import type { ReasoningEffort } from "../domain/catalog/metadata";
import type { PanelHookItem, PanelModelOption } from "../domain/catalog/metadata";
import type { PanelThread } from "../domain/threads/model";
import { REASONING_EFFORTS, supportedEffortsForModelOption } from "../domain/catalog/metadata";
import { findModelOptionByIdOrName, sortedModelOptions } from "../domain/catalog/metadata";
import type { HookItem, ModelMetadata } from "../domain/catalog/metadata";
import type { Thread } from "../domain/threads/model";
import { REASONING_EFFORTS, supportedEffortsForModelMetadata } from "../domain/catalog/metadata";
import { findModelMetadataByIdOrName, sortedModelMetadata } from "../domain/catalog/metadata";
import { archivedThreadDisplayTitle } from "../domain/threads/model";
import { errorMessage } from "../utils";
import {
@ -37,13 +37,13 @@ export class CodexPanelSettingTab extends PluginSettingTab {
private settingsDataAutoLoadStarted = false;
private settingsDynamicOperationId = 0;
private settingsDataRefreshLifecycle: SettingsDataRefreshLifecycleState = { kind: "idle" };
private archivedThreads: PanelThread[] = [];
private archivedThreads: Thread[] = [];
private archivedThreadsLifecycle = createSettingsDynamicSectionLifecycle();
private hooks: PanelHookItem[] = [];
private hooks: HookItem[] = [];
private hookWarnings: string[] = [];
private hookErrors: string[] = [];
private hooksLifecycle = createSettingsDynamicSectionLifecycle();
private models: PanelModelOption[] = [];
private models: ModelMetadata[] = [];
private modelsLifecycle = createSettingsDynamicSectionLifecycle();
constructor(
@ -127,7 +127,7 @@ export class CodexPanelSettingTab extends PluginSettingTab {
.setDesc("Choose the model and reasoning effort used to suggest thread names.")
.addDropdown((dropdown) => {
const current = this.plugin.settings.threadNamingModel;
const options = this.modelOptions();
const options = this.modelMetadata();
dropdown.selectEl.ariaLabel = "Automatic thread naming model";
dropdown.addOption(CODEX_DEFAULT_VALUE, "Codex default");
if (current && !options.some((model) => model.model === current || model.id === current)) {
@ -164,7 +164,7 @@ export class CodexPanelSettingTab extends PluginSettingTab {
.setDesc("Choose the model and reasoning effort used by rewrite selection.")
.addDropdown((dropdown) => {
const current = this.plugin.settings.rewriteSelectionModel;
const options = this.modelOptions();
const options = this.modelMetadata();
dropdown.selectEl.ariaLabel = "Selection rewrite model";
dropdown.addOption(CODEX_DEFAULT_VALUE, "Codex default");
if (current && !options.some((model) => model.model === current || model.id === current)) {
@ -394,7 +394,7 @@ export class CodexPanelSettingTab extends PluginSettingTab {
}
}
private async trustHook(hook: PanelHookItem): Promise<void> {
private async trustHook(hook: HookItem): Promise<void> {
const operationId = this.nextSettingsDynamicOperationId();
this.hooksLifecycle = transitionSettingsDynamicSectionLifecycle(this.hooksLifecycle, {
type: "started",
@ -403,7 +403,7 @@ export class CodexPanelSettingTab extends PluginSettingTab {
});
this.display();
try {
await this.withSettingsConnection((client) => trustPanelHook(client, hook));
await this.withSettingsConnection((client) => trustHookItem(client, hook));
this.hooksLifecycle = transitionSettingsDynamicSectionLifecycle(this.hooksLifecycle, {
type: "loaded",
status: "Trusted hook definition.",
@ -421,7 +421,7 @@ export class CodexPanelSettingTab extends PluginSettingTab {
}
}
private async setHookEnabled(hook: PanelHookItem, enabled: boolean): Promise<void> {
private async setHookEnabled(hook: HookItem, enabled: boolean): Promise<void> {
const operationId = this.nextSettingsDynamicOperationId();
this.hooksLifecycle = transitionSettingsDynamicSectionLifecycle(this.hooksLifecycle, {
type: "started",
@ -430,7 +430,7 @@ export class CodexPanelSettingTab extends PluginSettingTab {
});
this.display();
try {
await this.withSettingsConnection((client) => setPanelHookEnabled(client, hook, enabled));
await this.withSettingsConnection((client) => setHookItemEnabled(client, hook, enabled));
this.hooksLifecycle = transitionSettingsDynamicSectionLifecycle(this.hooksLifecycle, {
type: "loaded",
status: enabled ? "Enabled hook." : "Disabled hook.",
@ -478,7 +478,7 @@ export class CodexPanelSettingTab extends PluginSettingTab {
});
this.display();
try {
const restoredThread = await this.withSettingsConnection((client) => restoreArchivedPanelThread(client, threadId));
const restoredThread = await this.withSettingsConnection((client) => restoreArchivedThread(client, threadId));
this.archivedThreads = this.archivedThreads.filter((thread) => thread.id !== threadId);
this.archivedThreadsLifecycle = transitionSettingsDynamicSectionLifecycle(this.archivedThreadsLifecycle, {
type: "loaded",
@ -509,13 +509,13 @@ export class CodexPanelSettingTab extends PluginSettingTab {
return this.settingsDynamicOperationId;
}
private modelOptions(): PanelModelOption[] {
return sortedModelOptions(this.models);
private modelMetadata(): ModelMetadata[] {
return sortedModelMetadata(this.models);
}
private effortOptions(modelIdOrName: string | null): ReasoningEffort[] {
const model = this.selectedModel(modelIdOrName);
return model ? supportedEffortsForModelOption(model) : [...REASONING_EFFORTS];
return model ? supportedEffortsForModelMetadata(model) : [...REASONING_EFFORTS];
}
private namingEffortSupported(effort: ReasoningEffort | null): boolean {
@ -526,8 +526,8 @@ export class CodexPanelSettingTab extends PluginSettingTab {
return !effort || this.effortOptions(this.plugin.settings.rewriteSelectionModel).includes(effort);
}
private selectedModel(modelIdOrName: string | null): PanelModelOption | null {
return findModelOptionByIdOrName(this.models, modelIdOrName);
private selectedModel(modelIdOrName: string | null): ModelMetadata | null {
return findModelMetadataByIdOrName(this.models, modelIdOrName);
}
}
@ -537,6 +537,6 @@ export interface CodexPanelSettingTabHost {
saveSettings(): Promise<void>;
refreshOpenViews(): void;
refreshSharedThreadListFromOpenSurface(): void;
cachedModels(): PanelModelOption[];
publishModels(models: PanelModelOption[]): void;
cachedModels(): ModelMetadata[];
publishModels(models: ModelMetadata[]): void;
}

View file

@ -146,7 +146,7 @@ export class WorkspacePanelCoordinator {
panelLeavesForThread(threadId: string): WorkspaceLeaf[] {
return this.panelLeaves().filter((leaf) => {
if (leaf.view instanceof CodexChatView) return leaf.view.openPanelSnapshot().threadId === threadId;
return restoredPanelThreadId(leaf) === threadId;
return restoredThreadId(leaf) === threadId;
});
}
@ -211,7 +211,7 @@ export class WorkspacePanelCoordinator {
private findRestoredThreadPanelTarget(threadId: string): ThreadPanelTarget | null {
for (const leaf of this.panelLeaves()) {
if (leaf.view instanceof CodexChatView) continue;
if (restoredPanelThreadId(leaf) !== threadId) continue;
if (restoredThreadId(leaf) !== threadId) continue;
return { kind: "restored", leaf };
}
return null;
@ -369,7 +369,7 @@ function focusedPanelViewId(leaf: WorkspaceLeaf | null): string | null {
return leaf?.view instanceof CodexChatView ? leaf.view.openPanelSnapshot().viewId : null;
}
function restoredPanelThreadId(leaf: WorkspaceLeaf): string | null {
function restoredThreadId(leaf: WorkspaceLeaf): string | null {
const state = leaf.getViewState().state;
if (!state || typeof state !== "object") return null;
const threadId = (state as { threadId?: unknown }).threadId;

View file

@ -2,8 +2,8 @@ import type { App } from "obsidian";
import { VIEW_TYPE_CODEX_THREADS } from "../constants";
import { CodexThreadsView } from "../features/threads-view/view";
import type { PanelModelOption } from "../domain/catalog/metadata";
import type { PanelThread } from "../domain/threads/model";
import type { ModelMetadata } from "../domain/catalog/metadata";
import type { Thread } from "../domain/threads/model";
import type { SharedAppServerMetadata } from "../app-server/shared-cache-state";
import type { WorkspacePanelCoordinator } from "./panel-coordinator";
@ -33,7 +33,7 @@ export class ThreadSurfaceCoordinator {
if (threadsView) void threadsView.refresh();
}
applyThreadListSnapshot(threads: readonly PanelThread[]): void {
applyThreadListSnapshot(threads: readonly Thread[]): void {
for (const view of this.options.panels.panelViews()) {
view.applyThreadListSnapshot(threads);
}
@ -48,7 +48,7 @@ export class ThreadSurfaceCoordinator {
}
}
publishModels(models: readonly PanelModelOption[]): void {
publishModels(models: readonly ModelMetadata[]): void {
for (const view of this.options.panels.panelViews()) {
view.applyAvailableModelsSnapshot(models);
}

View file

@ -1,10 +1,10 @@
import { describe, expect, it } from "vitest";
import {
appServerHookOperationFromPanelHookItem,
panelHookItemsFromAppServerHooks,
panelModelOptionsFromAppServerModels,
panelSkillOptionsFromAppServerSkills,
appServerHookOperationFromHookItem,
hookItemsFromAppServerHooks,
modelMetadataFromAppServerModels,
skillMetadataFromAppServerSkills,
} from "../../src/app-server/catalog-model";
import type { HookMetadata } from "../../src/generated/app-server/v2/HookMetadata";
import type { Model } from "../../src/generated/app-server/v2/Model";
@ -12,7 +12,7 @@ import type { SkillMetadata } from "../../src/generated/app-server/v2/SkillMetad
describe("app-server catalog model mappers", () => {
it("maps app-server models to panel model options", () => {
expect(panelModelOptionsFromAppServerModels([modelFixture()])).toEqual([
expect(modelMetadataFromAppServerModels([modelFixture()])).toEqual([
{
id: "gpt-5.5-id",
model: "gpt-5.5",
@ -33,11 +33,11 @@ describe("app-server catalog model mappers", () => {
it("preserves unknown app-server default reasoning efforts as raw catalog metadata", () => {
const model = { ...modelFixture(), defaultReasoningEffort: "extreme" as never };
expect(panelModelOptionsFromAppServerModels([model])[0]?.defaultReasoningEffort).toBe("extreme");
expect(modelMetadataFromAppServerModels([model])[0]?.defaultReasoningEffort).toBe("extreme");
});
it("maps app-server skills to composer skill options", () => {
expect(panelSkillOptionsFromAppServerSkills([skillFixture()])).toEqual([
expect(skillMetadataFromAppServerSkills([skillFixture()])).toEqual([
{
name: "writer",
description: "Write notes",
@ -50,7 +50,7 @@ describe("app-server catalog model mappers", () => {
});
it("maps app-server hooks to settings hook items", () => {
expect(panelHookItemsFromAppServerHooks([hookFixture()])).toEqual([
expect(hookItemsFromAppServerHooks([hookFixture()])).toEqual([
{
key: "hook-key",
eventName: "postToolUse",
@ -67,10 +67,10 @@ describe("app-server catalog model mappers", () => {
});
it("maps panel hook items to minimal app-server hook operations", () => {
const hook = panelHookItemsFromAppServerHooks([hookFixture()])[0];
const hook = hookItemsFromAppServerHooks([hookFixture()])[0];
if (!hook) throw new Error("Expected mapped hook");
expect(appServerHookOperationFromPanelHookItem(hook)).toEqual({
expect(appServerHookOperationFromHookItem(hook)).toEqual({
key: "hook-key",
currentHash: "hash",
trustStatus: "modified",

View file

@ -1,21 +1,21 @@
import { describe, expect, it, vi } from "vitest";
import type { AppServerClient } from "../../src/app-server/client";
import { listPanelHookData, listPanelSkillCatalog, listPanelThreads } from "../../src/app-server/panel-data";
import { listHookData, listSkillCatalog, listThreads } from "../../src/app-server/resource-operations";
describe("panel app-server data loaders", () => {
it("maps listed threads to panel threads with archive state", async () => {
const listThreads = vi.fn().mockResolvedValue({
describe("app-server resource operations", () => {
it("maps listed threads to domain threads with archive state", async () => {
const clientListThreads = vi.fn().mockResolvedValue({
data: [{ id: "thread-1", preview: "Preview", name: null, createdAt: 10, updatedAt: 20 }],
});
const client = {
listThreads,
listThreads: clientListThreads,
} as unknown as AppServerClient;
await expect(listPanelThreads(client, "/vault", { archived: true })).resolves.toEqual([
await expect(listThreads(client, "/vault", { archived: true })).resolves.toEqual([
{ id: "thread-1", preview: "Preview", name: null, archived: true, createdAt: 10, updatedAt: 20 },
]);
expect(listThreads).toHaveBeenCalledWith("/vault", true);
expect(clientListThreads).toHaveBeenCalledWith("/vault", true);
});
it("uses only hook rows for the requested cwd", async () => {
@ -28,7 +28,7 @@ describe("panel app-server data loaders", () => {
}),
} as unknown as AppServerClient;
await expect(listPanelHookData(client, "/vault")).resolves.toMatchObject({
await expect(listHookData(client, "/vault")).resolves.toMatchObject({
hooks: [{ key: "vault" }],
warnings: ["warn"],
errors: ['{"message":"err"}'],
@ -50,7 +50,7 @@ describe("panel app-server data loaders", () => {
}),
} as unknown as AppServerClient;
await expect(listPanelSkillCatalog(client, "/vault")).resolves.toMatchObject({
await expect(listSkillCatalog(client, "/vault")).resolves.toMatchObject({
skills: [{ name: "enabled", description: "Enabled skill", path: "/skills/enabled", enabled: true }],
totalCount: 2,
});

View file

@ -10,9 +10,8 @@ import {
cachedSharedThreadList,
createSharedAppServerState,
} from "../../src/app-server/shared-cache-state";
import type { PanelModelOption } from "../../src/domain/catalog/metadata";
import type { PanelThread } from "../../src/domain/threads/model";
import type { Thread } from "../../src/generated/app-server/v2/Thread";
import type { ModelMetadata } from "../../src/domain/catalog/metadata";
import type { Thread } from "../../src/domain/threads/model";
describe("shared app-server cache state", () => {
it("keeps snapshots detached from caller-owned arrays", () => {
@ -23,7 +22,7 @@ describe("shared app-server cache state", () => {
const cachedThreads = cachedSharedThreadList(threadState);
expect(cachedThreads?.map((thread) => thread.id)).toEqual(["thread-1"]);
const mutableCachedThreads = cachedThreads as PanelThread[];
const mutableCachedThreads = cachedThreads as Thread[];
mutableCachedThreads.push(threadFixture("thread-3"));
expect(cachedSharedThreadList(threadState)?.map((thread) => thread.id)).toEqual(["thread-1"]);
@ -51,33 +50,18 @@ describe("shared app-server cache state", () => {
});
});
function threadFixture(id: string): Thread & { archived: boolean } {
function threadFixture(id: string): Thread {
return {
id,
sessionId: "session",
forkedFromId: null,
parentThreadId: null,
preview: "",
ephemeral: false,
modelProvider: "openai",
createdAt: 1,
updatedAt: 1,
status: { type: "idle" },
path: null,
cwd: "/vault",
cliVersion: "0.0.0",
source: "appServer",
threadSource: null,
agentNickname: null,
agentRole: null,
gitInfo: null,
name: null,
archived: false,
turns: [],
createdAt: 1,
updatedAt: 1,
};
}
function modelFixture(model: string): PanelModelOption {
function modelFixture(model: string): ModelMetadata {
return {
id: model,
model,

View file

@ -1,7 +1,7 @@
import { describe, expect, it, vi } from "vitest";
import type { AppServerClient } from "../../../src/app-server/client";
import { panelThreadFromAppServerThread } from "../../../src/app-server/thread-model";
import { threadFromAppServerThread } from "../../../src/app-server/thread-model";
import { createChatAppServerDiagnosticsActions } from "../../../src/features/chat/app-server/diagnostics-actions";
import { createChatAppServerMetadataActions } from "../../../src/features/chat/app-server/metadata-actions";
import { createChatAppServerThreadActions } from "../../../src/features/chat/app-server/thread-actions";
@ -16,11 +16,11 @@ describe("chat app-server controllers", () => {
it("publishes newly started threads before the first turn completes", async () => {
const state = createChatState();
const existing = threadFixture("existing");
state.threadList.listedThreads = [panelThreadFromAppServerThread(existing)];
state.threadList.listedThreads = [threadFromAppServerThread(existing)];
const stateStore = createChatStateStore(state);
const started = threadFixture("started");
const optimistic = panelThreadFromAppServerThread({ ...started, preview: "first prompt" });
const existingPanelThread = panelThreadFromAppServerThread(existing);
const optimistic = threadFromAppServerThread({ ...started, preview: "first prompt" });
const existingThread = threadFromAppServerThread(existing);
const publishThreadList = vi.fn();
const syncThreadGoal = vi.fn();
const client = {
@ -47,8 +47,8 @@ describe("chat app-server controllers", () => {
await controller.startThread("first prompt");
expect(stateStore.getState().threadList.listedThreads).toEqual([optimistic, existingPanelThread]);
expect(publishThreadList).toHaveBeenCalledWith([optimistic, existingPanelThread]);
expect(stateStore.getState().threadList.listedThreads).toEqual([optimistic, existingThread]);
expect(publishThreadList).toHaveBeenCalledWith([optimistic, existingThread]);
expect(syncThreadGoal).toHaveBeenCalledWith("started");
});
@ -111,7 +111,7 @@ describe("chat app-server controllers", () => {
await controller.startThread("local preview");
expect(publishThreadList).toHaveBeenCalledWith([panelThreadFromAppServerThread(started)]);
expect(publishThreadList).toHaveBeenCalledWith([threadFromAppServerThread(started)]);
});
it("reuses cached app-server metadata for deferred diagnostics", async () => {

View file

@ -2,7 +2,7 @@ import { describe, expect, it } from "vitest";
import type { ReasoningEffort } from "../../../../src/generated/app-server/ReasoningEffort";
import type { Thread } from "../../../../src/generated/app-server/v2/Thread";
import type { PanelModelOption } from "../../../../src/domain/catalog/metadata";
import type { ModelMetadata } from "../../../../src/domain/catalog/metadata";
import {
activeComposerSuggestions,
applyComposerSuggestionInsertion,
@ -46,7 +46,7 @@ function thread(overrides: Partial<Thread & { archived: boolean }> = {}): Thread
} as Thread & { archived: boolean };
}
function model(name: string, efforts: ReasoningEffort[], overrides: Partial<PanelModelOption> = {}): PanelModelOption {
function model(name: string, efforts: ReasoningEffort[], overrides: Partial<ModelMetadata> = {}): ModelMetadata {
return {
id: name,
model: name,

View file

@ -8,7 +8,7 @@ import { createChatState, createChatStateStore, type ChatState } from "../../../
import { runtimeSnapshotForChatSlices } from "../../../../src/features/chat/panel/model";
import type { ActiveThreadSettingsAppliedAction } from "../../../../src/features/chat/chat-state-actions";
import type { AppServerClient } from "../../../../src/app-server/client";
import type { PanelModelOption } from "../../../../src/domain/catalog/metadata";
import type { ModelMetadata } from "../../../../src/domain/catalog/metadata";
describe("createChatRuntimeSettingsActions", () => {
it("applies pending runtime overrides through thread settings and commits them", async () => {
@ -139,7 +139,7 @@ function runtimeSnapshotFixture(state: ChatState) {
});
}
function modelFixture(model: string, fastTierId: string): PanelModelOption {
function modelFixture(model: string, fastTierId: string): ModelMetadata {
return {
id: model,
model,

View file

@ -31,7 +31,7 @@ import type { ThreadItem } from "../../../../src/generated/app-server/v2/ThreadI
import type { ThreadStartResponse } from "../../../../src/generated/app-server/v2/ThreadStartResponse";
import type { Turn } from "../../../../src/generated/app-server/v2/Turn";
import type { TurnStartResponse } from "../../../../src/generated/app-server/v2/TurnStartResponse";
import { panelModelOptionsFromAppServerModels } from "../../../../src/app-server/catalog-model";
import { modelMetadataFromAppServerModels } from "../../../../src/app-server/catalog-model";
describe("thread naming", () => {
it("extracts the first user request and final assistant response from a completed turn", () => {
@ -264,7 +264,7 @@ describe("thread naming", () => {
expect(
validatedThreadNamingRuntimeOverride(
{ threadNamingModel: "gpt-5.4-mini", threadNamingEffort: "minimal" },
panelModelOptionsFromAppServerModels([model("gpt-5.4-mini", ["low", "medium", "high", "xhigh"])]),
modelMetadataFromAppServerModels([model("gpt-5.4-mini", ["low", "medium", "high", "xhigh"])]),
),
).toEqual({ model: "gpt-5.4-mini" });
});
@ -273,7 +273,7 @@ describe("thread naming", () => {
expect(
validatedThreadNamingRuntimeOverride(
{ threadNamingModel: "gpt-5.4-mini", threadNamingEffort: "low" },
panelModelOptionsFromAppServerModels([model("gpt-5.4-mini", ["low", "medium", "high", "xhigh"])]),
modelMetadataFromAppServerModels([model("gpt-5.4-mini", ["low", "medium", "high", "xhigh"])]),
),
).toEqual({ model: "gpt-5.4-mini", effort: "low" });
});

View file

@ -161,13 +161,13 @@ describe("composer renderer decisions", () => {
await waitForAsyncWork(() => {
expect(parent.querySelector(".codex-panel__composer-meta-popover--model")?.textContent).toBe("gpt-5.5gpt-5.4");
});
const modelOptions = Array.from(parent.querySelectorAll<HTMLElement>(".codex-panel__composer-meta-option"));
expect(modelOptions.map((option) => option.getAttribute("role"))).toEqual([null, null]);
expect(modelOptions.map((option) => option.getAttribute("tabindex"))).toEqual([null, null]);
expect(modelOptions.map((option) => option.getAttribute("aria-selected"))).toEqual([null, null]);
expect(modelOptions.every((option) => !option.classList.contains("is-selected"))).toBe(true);
expect(modelOptions.every((option) => !option.classList.contains("codex-panel-ui__nav-item"))).toBe(true);
expect(modelOptions.every((option) => !option.classList.contains("codex-panel__toolbar-panel-item"))).toBe(true);
const metaOptions = Array.from(parent.querySelectorAll<HTMLElement>(".codex-panel__composer-meta-option"));
expect(metaOptions.map((option) => option.getAttribute("role"))).toEqual([null, null]);
expect(metaOptions.map((option) => option.getAttribute("tabindex"))).toEqual([null, null]);
expect(metaOptions.map((option) => option.getAttribute("aria-selected"))).toEqual([null, null]);
expect(metaOptions.every((option) => !option.classList.contains("is-selected"))).toBe(true);
expect(metaOptions.every((option) => !option.classList.contains("codex-panel-ui__nav-item"))).toBe(true);
expect(metaOptions.every((option) => !option.classList.contains("codex-panel__toolbar-panel-item"))).toBe(true);
parent.querySelector<HTMLElement>(".codex-panel__composer-meta-effort")?.dispatchEvent(new MouseEvent("mousedown", { bubbles: true }));
await waitForAsyncWork(() => {

View file

@ -5,7 +5,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { DEFAULT_SETTINGS } from "../../../src/settings/model";
import type { CodexChatHost } from "../../../src/features/chat/chat-host";
import { createAppServerDiagnostics } from "../../../src/app-server/compatibility";
import { panelThreadFromAppServerThread } from "../../../src/app-server/thread-model";
import { threadFromAppServerThread } from "../../../src/app-server/thread-model";
import { createChatState, type ChatState } from "../../../src/features/chat/chat-state";
import { composerSlotSnapshot } from "../../../src/features/chat/panel/snapshot";
import type { ServerNotification } from "../../../src/generated/app-server/ServerNotification";
@ -159,7 +159,7 @@ describe("CodexChatView connection lifecycle", () => {
expect(refreshThreadList).toHaveBeenCalledOnce();
expect((view as unknown as { state: { threadList: { listedThreads: unknown[] } } }).state.threadList.listedThreads).toEqual(
threads.map((thread) => panelThreadFromAppServerThread(thread)),
threads.map((thread) => threadFromAppServerThread(thread)),
);
});
@ -393,7 +393,7 @@ describe("CodexChatView connection lifecycle", () => {
expect(client.readAccountRateLimits).toHaveBeenCalledOnce();
expect(client.listThreads).toHaveBeenCalledWith("/vault", false);
expect((view as unknown as { state: { threadList: { listedThreads: unknown[] } } }).state.threadList.listedThreads).toEqual([
panelThreadFromAppServerThread(threadFixture("thread-1")),
threadFromAppServerThread(threadFixture("thread-1")),
]);
});

View file

@ -16,7 +16,7 @@ import {
toolbarViewModel,
} from "../../../src/features/chat/panel/model";
import type { ChatState } from "../../../src/features/chat/chat-state";
import type { PanelModelOption } from "../../../src/domain/catalog/metadata";
import type { ModelMetadata } from "../../../src/domain/catalog/metadata";
import type { Thread } from "../../../src/generated/app-server/v2/Thread";
import type { ConfigReadResponse } from "../../../src/generated/app-server/v2/ConfigReadResponse";
@ -295,7 +295,7 @@ function threadFixture(id: string, name: string | null): Thread & { archived: bo
};
}
function modelFixture(model: string, fastTierId?: string): PanelModelOption {
function modelFixture(model: string, fastTierId?: string): ModelMetadata {
return {
id: model,
model,

View file

@ -26,7 +26,7 @@ import {
type SelectionRewriteClientFactory,
} from "../../../src/features/selection-rewrite/runner";
import type { AppServerClientHandlers } from "../../../src/app-server/client";
import type { PanelModelOption } from "../../../src/domain/catalog/metadata";
import type { ModelMetadata } from "../../../src/domain/catalog/metadata";
import type { InitializeResponse } from "../../../src/generated/app-server/InitializeResponse";
import type { ModelListResponse } from "../../../src/generated/app-server/v2/ModelListResponse";
import type { ServerNotification } from "../../../src/generated/app-server/ServerNotification";
@ -501,7 +501,7 @@ describe("selection rewrite runtime overrides", () => {
it("omits an explicit selection rewrite effort when the selected model does not support it", () => {
expect(
validatedSelectionRewriteRuntimeOverride({ rewriteSelectionModel: "gpt-5.4-mini", rewriteSelectionEffort: "minimal" }, [
panelModel("gpt-5.4-mini", ["low", "medium", "high", "xhigh"]),
modelMetadata("gpt-5.4-mini", ["low", "medium", "high", "xhigh"]),
]),
).toEqual({ model: "gpt-5.4-mini" });
});
@ -775,7 +775,7 @@ function turnCompletedNotification(threadId: string, completedTurn: Turn): Serve
};
}
function panelModel(name: string, efforts: ReasoningEffort[]): PanelModelOption {
function modelMetadata(name: string, efforts: ReasoningEffort[]): ModelMetadata {
return {
id: name,
model: name,

View file

@ -1,7 +1,7 @@
import { describe, expect, it } from "vitest";
import type { ConfigReadResponse } from "../../src/generated/app-server/v2/ConfigReadResponse";
import type { PanelModelOption } from "../../src/domain/catalog/metadata";
import type { ModelMetadata } from "../../src/domain/catalog/metadata";
import {
compactModelLabel,
compactReasoningEffortLabel,
@ -698,7 +698,7 @@ function configLayer(config: Record<string, unknown>, profile: string | null): N
};
}
function modelFixture(model: string): PanelModelOption {
function modelFixture(model: string): ModelMetadata {
return {
id: model,
model,

View file

@ -6,8 +6,8 @@ import type { Thread } from "../../src/generated/app-server/v2/Thread";
import type { HookMetadata } from "../../src/generated/app-server/v2/HookMetadata";
import type { Model } from "../../src/generated/app-server/v2/Model";
import type { ReasoningEffort } from "../../src/generated/app-server/ReasoningEffort";
import type { PanelModelOption } from "../../src/domain/catalog/metadata";
import { panelModelOptionsFromAppServerModels } from "../../src/app-server/catalog-model";
import type { ModelMetadata } from "../../src/domain/catalog/metadata";
import { modelMetadataFromAppServerModels } from "../../src/app-server/catalog-model";
import { CodexPanelSettingTab } from "../../src/settings/tab";
import { archivedThreadDisplayTitle } from "../../src/domain/threads/model";
import { notices } from "../mocks/obsidian";
@ -193,7 +193,7 @@ describe("settings tab", () => {
withShortLivedAppServerClientMock.mockImplementation(
(_codexPath: string, _cwd: string, operation: (client: unknown) => Promise<unknown>) => operation(client),
);
const tab = newSettingsTab({ cachedModels: panelModelOptionsFromAppServerModels([model("gpt-cached")]), publishModels });
const tab = newSettingsTab({ cachedModels: modelMetadataFromAppServerModels([model("gpt-cached")]), publishModels });
tab.display();
@ -201,7 +201,7 @@ describe("settings tab", () => {
await flushPromises();
expect(publishModels).toHaveBeenCalledWith(panelModelOptionsFromAppServerModels([model("gpt-5.5")]));
expect(publishModels).toHaveBeenCalledWith(modelMetadataFromAppServerModels([model("gpt-5.5")]));
expect(tab.containerEl.textContent).toContain("gpt-5.5");
});
@ -348,8 +348,8 @@ function newSettingsTab(
options: {
saveSettings?: () => Promise<void>;
sendShortcut?: "enter" | "mod-enter";
cachedModels?: PanelModelOption[];
publishModels?: (models: PanelModelOption[]) => void;
cachedModels?: ModelMetadata[];
publishModels?: (models: ModelMetadata[]) => void;
refreshOpenViews?: () => void;
} = {},
): CodexPanelSettingTab {