mirror of
https://github.com/tiennm99/ccs.git
synced 2026-10-12 20:15:40 +00:00
fix(codex): align runtime compatibility and dashboard types
This commit is contained in:
1 parent
8c5da9f9e8
commit
3c52b1ab6d
7 files changed
+192
-35
No files matched your search
+50
-27
@@ -87,6 +87,11 @@ interface DetectedProfile {
|
|||||||
remainingArgs: string[];
|
remainingArgs: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface RuntimeReasoningResolution {
|
||||||
|
argsWithoutReasoningFlags: string[];
|
||||||
|
reasoningOverride: string | number | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Smart profile detection
|
* Smart profile detection
|
||||||
*/
|
*/
|
||||||
@@ -100,6 +105,42 @@ function detectProfile(args: string[]): DetectedProfile {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function resolveRuntimeReasoningFlags(
|
||||||
|
args: string[],
|
||||||
|
envThinkingValue: string | undefined
|
||||||
|
): RuntimeReasoningResolution {
|
||||||
|
const runtime = resolveDroidReasoningRuntime(args, envThinkingValue);
|
||||||
|
|
||||||
|
if (runtime.duplicateDisplays.length > 0) {
|
||||||
|
console.error(
|
||||||
|
warn(
|
||||||
|
`[!] Multiple reasoning flags detected. Using first occurrence: ${runtime.sourceDisplay || '<first-flag>'}`
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
argsWithoutReasoningFlags: runtime.argsWithoutReasoningFlags,
|
||||||
|
reasoningOverride: runtime.reasoningOverride,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function exitWithRuntimeReasoningFlagError(
|
||||||
|
message: string,
|
||||||
|
options: {
|
||||||
|
codexAliasLevels: string;
|
||||||
|
includeDroidExecExample?: boolean;
|
||||||
|
}
|
||||||
|
): never {
|
||||||
|
console.error(fail(message));
|
||||||
|
console.error(' Examples: --thinking low, --thinking 8192, --thinking off');
|
||||||
|
console.error(` Codex alias: --effort ${options.codexAliasLevels}`);
|
||||||
|
if (options.includeDroidExecExample) {
|
||||||
|
console.error(' Droid exec: --reasoning-effort high');
|
||||||
|
}
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
// ========== Main Execution ==========
|
// ========== Main Execution ==========
|
||||||
|
|
||||||
interface ProfileError extends Error {
|
interface ProfileError extends Error {
|
||||||
@@ -463,17 +504,9 @@ async function main(): Promise<void> {
|
|||||||
targetRemainingArgs = droidRoute.argsForDroid;
|
targetRemainingArgs = droidRoute.argsForDroid;
|
||||||
|
|
||||||
if (droidRoute.mode === 'interactive') {
|
if (droidRoute.mode === 'interactive') {
|
||||||
const runtime = resolveDroidReasoningRuntime(remainingArgs, process.env.CCS_THINKING);
|
const runtime = resolveRuntimeReasoningFlags(remainingArgs, process.env.CCS_THINKING);
|
||||||
targetRemainingArgs = runtime.argsWithoutReasoningFlags;
|
targetRemainingArgs = runtime.argsWithoutReasoningFlags;
|
||||||
runtimeReasoningOverride = runtime.reasoningOverride;
|
runtimeReasoningOverride = runtime.reasoningOverride;
|
||||||
|
|
||||||
if (runtime.duplicateDisplays.length > 0) {
|
|
||||||
console.error(
|
|
||||||
warn(
|
|
||||||
`[!] Multiple reasoning flags detected. Using first occurrence: ${runtime.sourceDisplay || '<first-flag>'}`
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
if (droidRoute.duplicateReasoningDisplays.length > 0) {
|
if (droidRoute.duplicateReasoningDisplays.length > 0) {
|
||||||
console.error(
|
console.error(
|
||||||
@@ -490,33 +523,23 @@ async function main(): Promise<void> {
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof DroidReasoningFlagError || error instanceof DroidCommandRouterError) {
|
if (error instanceof DroidReasoningFlagError || error instanceof DroidCommandRouterError) {
|
||||||
console.error(fail(error.message));
|
exitWithRuntimeReasoningFlagError(error.message, {
|
||||||
console.error(' Examples: --thinking low, --thinking 8192, --thinking off');
|
codexAliasLevels: 'medium|high|xhigh',
|
||||||
console.error(' Codex alias: --effort medium|high|xhigh');
|
includeDroidExecExample: true,
|
||||||
console.error(' Droid exec: --reasoning-effort high');
|
});
|
||||||
process.exit(1);
|
|
||||||
}
|
}
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
} else if (resolvedTarget === 'codex') {
|
} else if (resolvedTarget === 'codex') {
|
||||||
try {
|
try {
|
||||||
const runtime = resolveDroidReasoningRuntime(remainingArgs, process.env.CCS_THINKING);
|
const runtime = resolveRuntimeReasoningFlags(remainingArgs, process.env.CCS_THINKING);
|
||||||
targetRemainingArgs = runtime.argsWithoutReasoningFlags;
|
targetRemainingArgs = runtime.argsWithoutReasoningFlags;
|
||||||
runtimeReasoningOverride = runtime.reasoningOverride;
|
runtimeReasoningOverride = runtime.reasoningOverride;
|
||||||
|
|
||||||
if (runtime.duplicateDisplays.length > 0) {
|
|
||||||
console.error(
|
|
||||||
warn(
|
|
||||||
`[!] Multiple reasoning flags detected. Using first occurrence: ${runtime.sourceDisplay || '<first-flag>'}`
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof DroidReasoningFlagError) {
|
if (error instanceof DroidReasoningFlagError) {
|
||||||
console.error(fail(error.message));
|
exitWithRuntimeReasoningFlagError(error.message, {
|
||||||
console.error(' Examples: --thinking low, --thinking 8192, --thinking off');
|
codexAliasLevels: 'minimal|low|medium|high|xhigh',
|
||||||
console.error(' Codex alias: --effort minimal|low|medium|high|xhigh');
|
});
|
||||||
process.exit(1);
|
|
||||||
}
|
}
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -229,6 +229,8 @@ export class CodexAdapter implements TargetAdapter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
supportsProfileType(profileType: ProfileType): boolean {
|
supportsProfileType(profileType: ProfileType): boolean {
|
||||||
return profileType === 'default' || profileType === 'settings' || profileType === 'cliproxy';
|
// Bridge-backed settings profiles need additional compatibility context that the
|
||||||
|
// adapter contract does not receive, so keep the adapter-level claim conservative.
|
||||||
|
return profileType === 'default' || profileType === 'cliproxy';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -25,7 +25,7 @@ export const TARGET_METADATA: Record<TargetType, TargetMetadata> = {
|
|||||||
legacyAliasEnvVar: 'CCS_CODEX_ALIASES',
|
legacyAliasEnvVar: 'CCS_CODEX_ALIASES',
|
||||||
persistedTarget: false,
|
persistedTarget: false,
|
||||||
},
|
},
|
||||||
};
|
} satisfies Record<TargetType, TargetMetadata>;
|
||||||
|
|
||||||
export const RUNTIME_TARGET_TYPES = Object.freeze(
|
export const RUNTIME_TARGET_TYPES = Object.freeze(
|
||||||
Object.keys(TARGET_METADATA) as TargetType[]
|
Object.keys(TARGET_METADATA) as TargetType[]
|
||||||
|
|||||||
@@ -5,10 +5,10 @@ import { CodexAdapter } from '../../../src/targets/codex-adapter';
|
|||||||
describe('CodexAdapter', () => {
|
describe('CodexAdapter', () => {
|
||||||
const adapter = new CodexAdapter();
|
const adapter = new CodexAdapter();
|
||||||
|
|
||||||
test('supports default, settings, and cliproxy profile types', () => {
|
test('supports only adapter-level default and cliproxy profile types', () => {
|
||||||
expect(adapter.supportsProfileType('default')).toBe(true);
|
expect(adapter.supportsProfileType('default')).toBe(true);
|
||||||
expect(adapter.supportsProfileType('settings')).toBe(true);
|
|
||||||
expect(adapter.supportsProfileType('cliproxy')).toBe(true);
|
expect(adapter.supportsProfileType('cliproxy')).toBe(true);
|
||||||
|
expect(adapter.supportsProfileType('settings')).toBe(false);
|
||||||
expect(adapter.supportsProfileType('account')).toBe(false);
|
expect(adapter.supportsProfileType('account')).toBe(false);
|
||||||
expect(adapter.supportsProfileType('copilot')).toBe(false);
|
expect(adapter.supportsProfileType('copilot')).toBe(false);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -60,6 +60,13 @@ describe('evaluateTargetRuntimeCompatibility', () => {
|
|||||||
});
|
});
|
||||||
expect(compatibility.supported).toBe(false);
|
expect(compatibility.supported).toBe(false);
|
||||||
expect(compatibility.reason).toMatch(/only supports CLIProxy Codex bridge profiles/);
|
expect(compatibility.reason).toMatch(/only supports CLIProxy Codex bridge profiles/);
|
||||||
|
|
||||||
|
const genericSettingsCompatibility = evaluateTargetRuntimeCompatibility({
|
||||||
|
target: 'codex',
|
||||||
|
profileType: 'settings',
|
||||||
|
});
|
||||||
|
expect(genericSettingsCompatibility.supported).toBe(false);
|
||||||
|
expect(genericSettingsCompatibility.reason).toMatch(/currently supports native default sessions/);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('rejects account and copilot profiles on Codex target', () => {
|
test('rejects account and copilot profiles on Codex target', () => {
|
||||||
|
|||||||
@@ -0,0 +1,128 @@
|
|||||||
|
export interface CompatibleCliDocLink {
|
||||||
|
id: string;
|
||||||
|
label: string;
|
||||||
|
url: string;
|
||||||
|
category: 'overview' | 'configuration' | 'byok' | 'reference';
|
||||||
|
source: 'factory' | 'provider' | 'openai' | 'github';
|
||||||
|
description: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CompatibleCliProviderDocLink {
|
||||||
|
provider: string;
|
||||||
|
label: string;
|
||||||
|
apiFormat: string;
|
||||||
|
url: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CompatibleCliDocsReference {
|
||||||
|
providerValues: string[];
|
||||||
|
settingsHierarchy: string[];
|
||||||
|
notes: string[];
|
||||||
|
links: CompatibleCliDocLink[];
|
||||||
|
providerDocs: CompatibleCliProviderDocLink[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CodexBinaryDiagnostics {
|
||||||
|
installed: boolean;
|
||||||
|
path: string | null;
|
||||||
|
installDir: string | null;
|
||||||
|
source: 'CCS_CODEX_PATH' | 'PATH' | 'missing';
|
||||||
|
version: string | null;
|
||||||
|
overridePath: string | null;
|
||||||
|
supportsConfigOverrides: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CodexConfigFileDiagnostics {
|
||||||
|
label: string;
|
||||||
|
path: string;
|
||||||
|
resolvedPath: string;
|
||||||
|
exists: boolean;
|
||||||
|
isSymlink: boolean;
|
||||||
|
isRegularFile: boolean;
|
||||||
|
sizeBytes: number | null;
|
||||||
|
mtimeMs: number | null;
|
||||||
|
parseError: string | null;
|
||||||
|
readError: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CodexModelProviderDiagnostics {
|
||||||
|
name: string;
|
||||||
|
baseUrl: string | null;
|
||||||
|
envKey: string | null;
|
||||||
|
wireApi: string | null;
|
||||||
|
requiresOpenaiAuth: boolean;
|
||||||
|
supportsWebsockets: boolean;
|
||||||
|
hasQueryParams: boolean;
|
||||||
|
hasHttpHeaders: boolean;
|
||||||
|
usesExperimentalBearerToken: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CodexFeatureFlagDiagnostics {
|
||||||
|
name: string;
|
||||||
|
state: 'enabled' | 'disabled' | 'custom';
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CodexProjectTrustDiagnostics {
|
||||||
|
path: string;
|
||||||
|
trustLevel: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CodexMcpServerDiagnostics {
|
||||||
|
name: string;
|
||||||
|
transport: 'stdio' | 'streamable-http' | 'unknown';
|
||||||
|
enabled: boolean;
|
||||||
|
required: boolean;
|
||||||
|
startupTimeoutSec: number | null;
|
||||||
|
toolTimeoutSec: number | null;
|
||||||
|
enabledToolsCount: number;
|
||||||
|
disabledToolsCount: number;
|
||||||
|
usesInlineBearerToken: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CodexSupportMatrixEntry {
|
||||||
|
id: string;
|
||||||
|
label: string;
|
||||||
|
supported: boolean;
|
||||||
|
notes: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CodexUserConfigDiagnostics {
|
||||||
|
model: string | null;
|
||||||
|
modelProvider: string | null;
|
||||||
|
activeProfile: string | null;
|
||||||
|
approvalPolicy: string | null;
|
||||||
|
sandboxMode: string | null;
|
||||||
|
webSearch: string | null;
|
||||||
|
topLevelKeys: string[];
|
||||||
|
profileCount: number;
|
||||||
|
profileNames: string[];
|
||||||
|
modelProviderCount: number;
|
||||||
|
modelProviders: CodexModelProviderDiagnostics[];
|
||||||
|
featureCount: number;
|
||||||
|
enabledFeatures: CodexFeatureFlagDiagnostics[];
|
||||||
|
disabledFeatures: CodexFeatureFlagDiagnostics[];
|
||||||
|
trustedProjectCount: number;
|
||||||
|
untrustedProjectCount: number;
|
||||||
|
projectTrust: CodexProjectTrustDiagnostics[];
|
||||||
|
mcpServerCount: number;
|
||||||
|
mcpServers: CodexMcpServerDiagnostics[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CodexDashboardDiagnostics {
|
||||||
|
binary: CodexBinaryDiagnostics;
|
||||||
|
file: CodexConfigFileDiagnostics;
|
||||||
|
config: CodexUserConfigDiagnostics;
|
||||||
|
supportMatrix: CodexSupportMatrixEntry[];
|
||||||
|
warnings: string[];
|
||||||
|
docsReference: CompatibleCliDocsReference;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CodexRawConfigResponse {
|
||||||
|
path: string;
|
||||||
|
resolvedPath: string;
|
||||||
|
exists: boolean;
|
||||||
|
mtime: number;
|
||||||
|
rawText: string;
|
||||||
|
config: Record<string, unknown> | null;
|
||||||
|
parseError: string | null;
|
||||||
|
}
|
||||||
@@ -2,10 +2,7 @@ import { useMemo } from 'react';
|
|||||||
import { parse as parseToml } from 'smol-toml';
|
import { parse as parseToml } from 'smol-toml';
|
||||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||||
import { ApiConflictError, withApiBase } from '@/lib/api-client';
|
import { ApiConflictError, withApiBase } from '@/lib/api-client';
|
||||||
import type {
|
import type { CodexDashboardDiagnostics, CodexRawConfigResponse } from './use-codex-types';
|
||||||
CodexDashboardDiagnostics,
|
|
||||||
CodexRawConfigResponse,
|
|
||||||
} from '../../../src/web-server/services/compatible-cli-types';
|
|
||||||
|
|
||||||
type CodexRawConfig = CodexRawConfigResponse;
|
type CodexRawConfig = CodexRawConfigResponse;
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user