mirror of
https://github.com/murashit/codex-panel.git
synced 2026-07-22 06:57:10 +00:00
103 lines
3.9 KiB
TypeScript
103 lines
3.9 KiB
TypeScript
import { modelMetadataFromCatalogModels } from "../../app-server/protocol/catalog";
|
|
import {
|
|
runEphemeralStructuredTurn,
|
|
type EphemeralStructuredTurnClient,
|
|
type EphemeralStructuredTurnClientFactory,
|
|
type EphemeralStructuredTurnRuntimeClient,
|
|
type StructuredTurnOutputSchema,
|
|
} from "../../app-server/services/ephemeral-structured-turn";
|
|
import { conversationAssistantTextFromTurnRecord, type TurnRecord } from "../../app-server/protocol/turn";
|
|
import type { ModelMetadata, ReasoningEffort } from "../../domain/catalog/metadata";
|
|
import { runtimeOverride, validatedRuntimeOverrideForModelMetadata } from "../../domain/catalog/runtime-overrides";
|
|
import { threadTitleFromGeneratedText, threadTitlePrompt, type ThreadTitleContext } from "./model";
|
|
|
|
const THREAD_TITLE_SERVICE_NAME = "codex-panel-naming";
|
|
const THREAD_TITLE_TIMEOUT_MS = 60_000;
|
|
const MAX_TITLE_CHARS = 40;
|
|
|
|
const TITLE_OUTPUT_SCHEMA: StructuredTurnOutputSchema = {
|
|
type: "object",
|
|
properties: {
|
|
title: {
|
|
type: "string",
|
|
minLength: 1,
|
|
maxLength: MAX_TITLE_CHARS,
|
|
},
|
|
},
|
|
required: ["title"],
|
|
additionalProperties: false,
|
|
};
|
|
|
|
const TITLE_DEVELOPER_INSTRUCTIONS = [
|
|
"You generate short titles for Codex thread history.",
|
|
"Infer the main language of the user's initial request and write the title in that language.",
|
|
"Return only a JSON object matching the requested schema.",
|
|
"Do not include Markdown, quotes around the whole response, explanations, or alternatives.",
|
|
].join("\n");
|
|
|
|
export interface ThreadTitleRuntimeSettings {
|
|
threadNamingModel: string | null;
|
|
threadNamingEffort: ReasoningEffort | null;
|
|
}
|
|
|
|
export type ThreadTitleClient = EphemeralStructuredTurnClient;
|
|
export type ThreadTitleClientFactory = EphemeralStructuredTurnClientFactory;
|
|
|
|
export async function generateThreadTitleWithCodex(
|
|
codexPath: string,
|
|
cwd: string,
|
|
context: ThreadTitleContext,
|
|
runtimeSettings: ThreadTitleRuntimeSettings,
|
|
clientFactory?: ThreadTitleClientFactory,
|
|
): Promise<string | null> {
|
|
const turn = await runEphemeralStructuredTurn({
|
|
codexPath,
|
|
cwd,
|
|
serviceName: THREAD_TITLE_SERVICE_NAME,
|
|
developerInstructions: TITLE_DEVELOPER_INSTRUCTIONS,
|
|
prompt: threadTitlePrompt(context),
|
|
outputSchema: TITLE_OUTPUT_SCHEMA,
|
|
timeoutMs: THREAD_TITLE_TIMEOUT_MS,
|
|
unhandledServerRequestMessage: "Thread title generation does not handle server requests.",
|
|
exitedMessage: "Codex title generation app-server exited.",
|
|
timedOutMessage: "Timed out while generating a Codex thread title.",
|
|
resolveRuntime: (client) => threadTitleRuntimeOverrideForClient(client, runtimeSettings),
|
|
clientFactory,
|
|
});
|
|
return threadTitleFromGenerationTurn(turn);
|
|
}
|
|
|
|
export interface ThreadTitleRuntimeOverride {
|
|
model?: string;
|
|
effort?: ReasoningEffort;
|
|
}
|
|
|
|
export function threadTitleFromGenerationTurn(turn: TurnRecord): string | null {
|
|
const response = conversationAssistantTextFromTurnRecord(turn);
|
|
return response ? threadTitleFromGeneratedText(response) : null;
|
|
}
|
|
|
|
export function threadTitleRuntimeOverride(settings: ThreadTitleRuntimeSettings): ThreadTitleRuntimeOverride {
|
|
return runtimeOverride({ model: settings.threadNamingModel, effort: settings.threadNamingEffort });
|
|
}
|
|
|
|
export function validatedThreadTitleRuntimeOverride(
|
|
settings: ThreadTitleRuntimeSettings,
|
|
models: readonly ModelMetadata[],
|
|
): ThreadTitleRuntimeOverride {
|
|
return validatedRuntimeOverrideForModelMetadata({ model: settings.threadNamingModel, effort: settings.threadNamingEffort }, models);
|
|
}
|
|
|
|
async function threadTitleRuntimeOverrideForClient(
|
|
client: EphemeralStructuredTurnRuntimeClient,
|
|
settings: ThreadTitleRuntimeSettings,
|
|
): Promise<ThreadTitleRuntimeOverride> {
|
|
const runtime = threadTitleRuntimeOverride(settings);
|
|
if (!runtime.model || !runtime.effort) return runtime;
|
|
try {
|
|
const response = await client.listModels(false);
|
|
return validatedThreadTitleRuntimeOverride(settings, modelMetadataFromCatalogModels(response.data));
|
|
} catch {
|
|
return runtime;
|
|
}
|
|
}
|