mirror of
https://github.com/wu736139669/hapi.git
synced 2026-08-05 06:24:37 +00:00
Add Codex model selection
This commit is contained in:
@@ -19,6 +19,31 @@ export interface InitializeResponse {
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
export interface ModelListParams {
|
||||
includeHidden?: boolean;
|
||||
}
|
||||
|
||||
export interface ModelListItem {
|
||||
id: string;
|
||||
model?: string;
|
||||
displayName?: string;
|
||||
description?: string;
|
||||
hidden?: boolean;
|
||||
supportedReasoningEfforts?: Array<{
|
||||
reasoningEffort?: string;
|
||||
description?: string;
|
||||
}>;
|
||||
defaultReasoningEffort?: string | null;
|
||||
isDefault?: boolean;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
export interface ModelListResponse {
|
||||
data?: ModelListItem[];
|
||||
nextCursor?: string | null;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
export interface ThreadStartParams {
|
||||
model?: string;
|
||||
modelProvider?: string;
|
||||
|
||||
@@ -4,6 +4,8 @@ import { killProcessByChildProcess } from '@/utils/process';
|
||||
import type {
|
||||
InitializeParams,
|
||||
InitializeResponse,
|
||||
ModelListParams,
|
||||
ModelListResponse,
|
||||
ThreadStartParams,
|
||||
ThreadStartResponse,
|
||||
ThreadResumeParams,
|
||||
@@ -133,6 +135,13 @@ export class CodexAppServerClient {
|
||||
return response as InitializeResponse;
|
||||
}
|
||||
|
||||
async listModels(params?: ModelListParams): Promise<ModelListResponse> {
|
||||
const response = await this.sendRequest('model/list', params ?? {}, {
|
||||
timeoutMs: 30_000
|
||||
});
|
||||
return response as ModelListResponse;
|
||||
}
|
||||
|
||||
async startThread(params: ThreadStartParams, options?: { signal?: AbortSignal }): Promise<ThreadStartResponse> {
|
||||
const response = await this.sendRequest('thread/start', params, {
|
||||
signal: options?.signal,
|
||||
|
||||
+37
-18
@@ -71,21 +71,15 @@ export async function runCodex(opts: {
|
||||
lifecycle.registerProcessHandlers();
|
||||
registerKillSessionHandler(session.rpcHandlerManager, lifecycle.cleanupAndExit);
|
||||
|
||||
const syncSessionMode = () => {
|
||||
const applyCurrentConfigToSession = (options?: { syncModel?: boolean }) => {
|
||||
const sessionInstance = sessionWrapperRef.current;
|
||||
if (!sessionInstance) {
|
||||
return;
|
||||
}
|
||||
const sessionModel = sessionInstance.getModel();
|
||||
if (sessionModel !== undefined) {
|
||||
currentModel = sessionModel ?? undefined;
|
||||
}
|
||||
const sessionModelReasoningEffort = sessionInstance.getModelReasoningEffort();
|
||||
if (sessionModelReasoningEffort !== undefined) {
|
||||
currentModelReasoningEffort = (sessionModelReasoningEffort ?? undefined) as ReasoningEffort | undefined;
|
||||
}
|
||||
sessionInstance.setPermissionMode(currentPermissionMode);
|
||||
sessionInstance.setModel(currentModel ?? null);
|
||||
if (options?.syncModel !== false) {
|
||||
sessionInstance.setModel(currentModel ?? null);
|
||||
}
|
||||
sessionInstance.setModelReasoningEffort(currentModelReasoningEffort ?? null);
|
||||
sessionInstance.setCollaborationMode(currentCollaborationMode);
|
||||
logger.debug(
|
||||
@@ -167,16 +161,32 @@ export async function runCodex(opts: {
|
||||
return value as ReasoningEffort;
|
||||
};
|
||||
|
||||
const resolveModel = (value: unknown): string => {
|
||||
if (typeof value !== 'string') {
|
||||
throw new Error('Invalid model');
|
||||
}
|
||||
const trimmedValue = value.trim();
|
||||
if (!trimmedValue) {
|
||||
throw new Error('Invalid model');
|
||||
}
|
||||
return trimmedValue;
|
||||
};
|
||||
|
||||
session.rpcHandlerManager.registerHandler('set-session-config', async (payload: unknown) => {
|
||||
if (!payload || typeof payload !== 'object') {
|
||||
throw new Error('Invalid session config payload');
|
||||
}
|
||||
const config = payload as { permissionMode?: unknown; modelReasoningEffort?: unknown; collaborationMode?: unknown };
|
||||
const config = payload as { permissionMode?: unknown; model?: unknown; modelReasoningEffort?: unknown; collaborationMode?: unknown };
|
||||
|
||||
if (config.permissionMode !== undefined) {
|
||||
currentPermissionMode = resolvePermissionMode(config.permissionMode);
|
||||
}
|
||||
|
||||
const shouldSyncModel = config.model !== undefined;
|
||||
if (shouldSyncModel) {
|
||||
currentModel = resolveModel(config.model);
|
||||
}
|
||||
|
||||
if (config.modelReasoningEffort !== undefined) {
|
||||
currentModelReasoningEffort = resolveModelReasoningEffort(config.modelReasoningEffort);
|
||||
}
|
||||
@@ -185,13 +195,22 @@ export async function runCodex(opts: {
|
||||
currentCollaborationMode = resolveCollaborationMode(config.collaborationMode);
|
||||
}
|
||||
|
||||
syncSessionMode();
|
||||
applyCurrentConfigToSession({ syncModel: shouldSyncModel });
|
||||
const applied: {
|
||||
permissionMode: PermissionMode;
|
||||
model?: string | null;
|
||||
modelReasoningEffort: ReasoningEffort | null;
|
||||
collaborationMode: EnhancedMode['collaborationMode'];
|
||||
} = {
|
||||
permissionMode: currentPermissionMode,
|
||||
modelReasoningEffort: currentModelReasoningEffort ?? null,
|
||||
collaborationMode: currentCollaborationMode
|
||||
};
|
||||
if (shouldSyncModel) {
|
||||
applied.model = currentModel ?? null;
|
||||
}
|
||||
return {
|
||||
applied: {
|
||||
permissionMode: currentPermissionMode,
|
||||
modelReasoningEffort: currentModelReasoningEffort ?? null,
|
||||
collaborationMode: currentCollaborationMode
|
||||
}
|
||||
applied
|
||||
};
|
||||
});
|
||||
|
||||
@@ -213,7 +232,7 @@ export async function runCodex(opts: {
|
||||
onModeChange: createModeChangeHandler(session),
|
||||
onSessionReady: (instance) => {
|
||||
sessionWrapperRef.current = instance;
|
||||
syncSessionMode();
|
||||
applyCurrentConfigToSession();
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
import { CodexAppServerClient } from '@/codex/codexAppServerClient';
|
||||
import { getErrorMessage } from './rpcResponses';
|
||||
|
||||
export interface CodexModelSummary {
|
||||
id: string;
|
||||
displayName: string;
|
||||
isDefault: boolean;
|
||||
defaultReasoningEffort?: string | null;
|
||||
supportedReasoningEfforts?: string[];
|
||||
}
|
||||
|
||||
export interface ListCodexModelsRequest {
|
||||
includeHidden?: boolean;
|
||||
}
|
||||
|
||||
export interface ListCodexModelsResponse {
|
||||
success: boolean;
|
||||
models?: CodexModelSummary[];
|
||||
error?: string;
|
||||
}
|
||||
|
||||
function asNonEmptyString(value: unknown): string | null {
|
||||
return typeof value === 'string' && value.trim().length > 0 ? value.trim() : null;
|
||||
}
|
||||
|
||||
function normalizeSupportedReasoningEfforts(value: unknown): string[] | undefined {
|
||||
if (!Array.isArray(value)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const efforts = value
|
||||
.map((entry) => {
|
||||
if (!entry || typeof entry !== 'object') {
|
||||
return null;
|
||||
}
|
||||
const reasoningEffort = asNonEmptyString((entry as { reasoningEffort?: unknown }).reasoningEffort);
|
||||
return reasoningEffort;
|
||||
})
|
||||
.filter((entry): entry is string => entry !== null);
|
||||
|
||||
return efforts.length > 0 ? efforts : undefined;
|
||||
}
|
||||
|
||||
function normalizeModel(entry: unknown): CodexModelSummary | null {
|
||||
if (!entry || typeof entry !== 'object') {
|
||||
return null;
|
||||
}
|
||||
|
||||
const record = entry as Record<string, unknown>;
|
||||
const id = asNonEmptyString(record.id) ?? asNonEmptyString(record.model);
|
||||
if (!id) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
id,
|
||||
displayName: asNonEmptyString(record.displayName) ?? id,
|
||||
isDefault: record.isDefault === true,
|
||||
defaultReasoningEffort: asNonEmptyString(record.defaultReasoningEffort),
|
||||
supportedReasoningEfforts: normalizeSupportedReasoningEfforts(record.supportedReasoningEfforts)
|
||||
};
|
||||
}
|
||||
|
||||
export async function listCodexModels(includeHidden: boolean = false): Promise<CodexModelSummary[]> {
|
||||
const client = new CodexAppServerClient();
|
||||
|
||||
try {
|
||||
await client.connect();
|
||||
await client.initialize({
|
||||
clientInfo: {
|
||||
name: 'hapi-codex-models',
|
||||
version: '1.0.0'
|
||||
},
|
||||
capabilities: {
|
||||
experimentalApi: true
|
||||
}
|
||||
});
|
||||
|
||||
const response = await client.listModels({ includeHidden });
|
||||
const models = Array.isArray(response.data)
|
||||
? response.data.map(normalizeModel).filter((model): model is CodexModelSummary => model !== null)
|
||||
: [];
|
||||
|
||||
return models;
|
||||
} catch (error) {
|
||||
throw new Error(getErrorMessage(error, 'Failed to list Codex models'));
|
||||
} finally {
|
||||
await client.disconnect().catch(() => undefined);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import { logger } from '@/ui/logger';
|
||||
import type { RpcHandlerManager } from '@/api/rpc/RpcHandlerManager';
|
||||
import {
|
||||
listCodexModels,
|
||||
type ListCodexModelsRequest,
|
||||
type ListCodexModelsResponse
|
||||
} from '../codexModels';
|
||||
import { getErrorMessage, rpcError } from '../rpcResponses';
|
||||
|
||||
export function registerCodexModelHandlers(rpcHandlerManager: RpcHandlerManager): void {
|
||||
rpcHandlerManager.registerHandler<ListCodexModelsRequest, ListCodexModelsResponse>('listCodexModels', async (data) => {
|
||||
logger.debug('List Codex models request');
|
||||
|
||||
try {
|
||||
const models = await listCodexModels(data?.includeHidden === true);
|
||||
return { success: true, models };
|
||||
} catch (error) {
|
||||
logger.debug('Failed to list Codex models:', error);
|
||||
return rpcError(getErrorMessage(error, 'Failed to list Codex models'));
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { RpcHandlerManager } from '@/api/rpc/RpcHandlerManager'
|
||||
import { registerBashHandlers } from './handlers/bash'
|
||||
import { registerCodexModelHandlers } from './handlers/codexModels'
|
||||
import { registerDirectoryHandlers } from './handlers/directories'
|
||||
import { registerDifftasticHandlers } from './handlers/difftastic'
|
||||
import { registerFileHandlers } from './handlers/files'
|
||||
@@ -11,6 +12,7 @@ import { registerUploadHandlers } from './handlers/uploads'
|
||||
|
||||
export function registerCommonHandlers(rpcHandlerManager: RpcHandlerManager, workingDirectory: string): void {
|
||||
registerBashHandlers(rpcHandlerManager, workingDirectory)
|
||||
registerCodexModelHandlers(rpcHandlerManager)
|
||||
registerFileHandlers(rpcHandlerManager, workingDirectory)
|
||||
registerDirectoryHandlers(rpcHandlerManager, workingDirectory)
|
||||
registerRipgrepHandlers(rpcHandlerManager, workingDirectory)
|
||||
|
||||
Reference in New Issue
Block a user