Files
ccs/tests/unit/targets/codex-adapter.test.ts
T
walkerandClaude Opus 4.6 eb8149e8fa feat(browser): 接入 CCS browser MCP 与最小交互闭环
新增 browser MCP 安装、Chrome 复用与运行时接线,
实现 navigate、click、type、take_screenshot 四个 phase-1 工具并补齐相关单测。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-13 11:19:16 +08:00

279 lines
9.1 KiB
TypeScript

import { describe, expect, test } from 'bun:test';
import { CodexAdapter } from '../../../src/targets/codex-adapter';
import {
buildCodexBrowserMcpOverrides,
getCodexBrowserMcpServerName,
} from '../../../src/utils/browser-codex-overrides';
describe('CodexAdapter', () => {
const adapter = new CodexAdapter();
test('supports only adapter-level default and cliproxy profile types', () => {
expect(adapter.supportsProfileType('default')).toBe(true);
expect(adapter.supportsProfileType('cliproxy')).toBe(true);
expect(adapter.supportsProfileType('settings')).toBe(false);
expect(adapter.supportsProfileType('account')).toBe(false);
expect(adapter.supportsProfileType('copilot')).toBe(false);
});
test('passes default-mode args through unchanged', () => {
expect(
adapter.buildArgs('default', ['--search'], {
profileType: 'default',
})
).toEqual(['--search']);
});
test('builds browser MCP overrides with Codex-safe defaults', () => {
const serverName = getCodexBrowserMcpServerName();
expect(buildCodexBrowserMcpOverrides()).toEqual([
`mcp_servers.${serverName}.command=${JSON.stringify(process.platform === 'win32' ? 'npx.cmd' : 'npx')}`,
`mcp_servers.${serverName}.args=${JSON.stringify(['-y', '@playwright/mcp@0.0.70'])}`,
`mcp_servers.${serverName}.enabled=true`,
`mcp_servers.${serverName}.tool_timeout_sec=30`,
]);
});
test('translates default-mode reasoning overrides into transient codex config', () => {
const args = adapter.buildArgs('default', ['--search'], {
profileType: 'default',
creds: {
profile: 'default',
baseUrl: '',
apiKey: '',
reasoningOverride: 'medium',
},
binaryInfo: {
path: '/tmp/codex',
needsShell: false,
features: ['config-overrides'],
},
});
expect(args).toEqual(['-c', 'model_reasoning_effort="medium"', '--search']);
});
test('injects runtime config overrides for native Codex default launches', () => {
const runtimeConfigOverrides = buildCodexBrowserMcpOverrides();
const args = adapter.buildArgs('default', ['--search'], {
profileType: 'default',
creds: {
profile: 'default',
baseUrl: '',
apiKey: '',
runtimeConfigOverrides,
},
binaryInfo: {
path: '/tmp/codex',
needsShell: false,
features: ['config-overrides'],
},
});
expect(args).toEqual([
...runtimeConfigOverrides.flatMap((override) => ['-c', override]),
'--search',
]);
});
test('rejects default-mode reasoning overrides when codex lacks config override support', () => {
expect(() =>
adapter.buildArgs('default', ['--search'], {
profileType: 'default',
creds: {
profile: 'default',
baseUrl: '',
apiKey: '',
reasoningOverride: 'high',
},
binaryInfo: {
path: '/tmp/codex',
needsShell: false,
version: 'codex-cli 0.1.0',
features: [],
},
})
).toThrow(/does not advertise --config overrides/);
});
test('injects transient config overrides for CCS-backed launches', () => {
const runtimeConfigOverrides = buildCodexBrowserMcpOverrides();
const args = adapter.buildArgs('codex', ['--search'], {
profileType: 'cliproxy',
creds: {
profile: 'codex',
baseUrl: 'http://127.0.0.1:8317/api/provider/codex',
apiKey: 'cliproxy-token',
model: 'gpt-5.4',
reasoningOverride: 'high',
runtimeConfigOverrides,
},
binaryInfo: {
path: '/tmp/codex',
needsShell: false,
features: ['config-overrides'],
},
});
expect(args).toContain('-c');
expect(args).toContain('model_provider="ccs_runtime"');
expect(args).toContain('model_providers.ccs_runtime.env_key="CCS_CODEX_API_KEY"');
expect(args).toContain('model="gpt-5.4"');
expect(args).toContain(`mcp_servers.${getCodexBrowserMcpServerName()}.command=${JSON.stringify(process.platform === 'win32' ? 'npx.cmd' : 'npx')}`);
expect(args).toContain('model_reasoning_effort="high"');
expect(args[args.length - 1]).toBe('--search');
});
test('fails fast when Codex binary lacks config override support', () => {
expect(() =>
adapter.buildArgs('codex', [], {
profileType: 'cliproxy',
creds: {
profile: 'codex',
baseUrl: 'http://127.0.0.1:8317/api/provider/codex',
apiKey: 'cliproxy-token',
},
binaryInfo: {
path: '/tmp/codex',
needsShell: false,
version: 'codex-cli 0.1.0',
features: [],
},
})
).toThrow(/does not advertise --config overrides/);
});
test('rejects native Codex provider-selection flags for CCS-backed launches', () => {
expect(() =>
adapter.buildArgs('codex', ['--profile', 'other', '--search'], {
profileType: 'cliproxy',
creds: {
profile: 'codex',
baseUrl: 'http://127.0.0.1:8317/api/provider/codex',
apiKey: 'cliproxy-token',
},
binaryInfo: {
path: '/tmp/codex',
needsShell: false,
features: ['config-overrides'],
},
})
).toThrow(/does not allow --profile\/-p/);
});
test('rejects user-supplied --config overrides for CCS-backed launches', () => {
const options = {
profileType: 'cliproxy' as const,
creds: {
profile: 'codex',
baseUrl: 'http://127.0.0.1:8317/api/provider/codex',
apiKey: 'cliproxy-token',
},
binaryInfo: {
path: '/tmp/codex',
needsShell: false,
features: ['config-overrides'],
},
};
expect(() => adapter.buildArgs('codex', ['-c', 'model="other"', '--search'], options)).toThrow(
/does not allow --config\/-c/
);
expect(() =>
adapter.buildArgs('codex', ['--config=model="other"', '--search'], options)
).toThrow(/does not allow --config\/-c/);
});
test('rejects unsupported reasoning override values for CCS-backed launches', () => {
expect(() =>
adapter.buildArgs('codex', ['--search'], {
profileType: 'cliproxy',
creds: {
profile: 'codex',
baseUrl: 'http://127.0.0.1:8317/api/provider/codex',
apiKey: 'cliproxy-token',
reasoningOverride: 8192,
},
binaryInfo: {
path: '/tmp/codex',
needsShell: false,
features: ['config-overrides'],
},
})
).toThrow(/supports reasoning levels only/);
});
test('injects CCS_CODEX_API_KEY for CCS-backed launches only', () => {
const originalCodeXHome = process.env.CODEX_HOME;
const originalCodeXCi = process.env.CODEX_CI;
const originalCodeXManagedByBun = process.env.CODEX_MANAGED_BY_BUN;
const originalCodeXThreadId = process.env.CODEX_THREAD_ID;
const originalAnthropicBaseUrl = process.env.ANTHROPIC_BASE_URL;
try {
process.env.CODEX_HOME = '/tmp/codex-home';
process.env.CODEX_CI = '1';
process.env.CODEX_MANAGED_BY_BUN = '1';
process.env.CODEX_THREAD_ID = 'thread-123';
process.env.ANTHROPIC_BASE_URL = 'https://stale-proxy.invalid';
const settingsEnv = adapter.buildEnv(
{
profile: 'codex',
baseUrl: 'http://127.0.0.1:8317/api/provider/codex',
apiKey: 'cliproxy-token',
},
'cliproxy'
);
expect(settingsEnv.CCS_CODEX_API_KEY).toBe('cliproxy-token');
expect(settingsEnv.CODEX_HOME).toBe('/tmp/codex-home');
expect(settingsEnv.CODEX_CI).toBeUndefined();
expect(settingsEnv.CODEX_MANAGED_BY_BUN).toBeUndefined();
expect(settingsEnv.CODEX_THREAD_ID).toBeUndefined();
expect(settingsEnv.ANTHROPIC_BASE_URL).toBeUndefined();
const defaultEnv = adapter.buildEnv(
{
profile: 'default',
baseUrl: '',
apiKey: '',
},
'default'
);
expect(defaultEnv.CCS_CODEX_API_KEY).toBeUndefined();
expect(defaultEnv.CODEX_HOME).toBe('/tmp/codex-home');
expect(defaultEnv.CODEX_CI).toBeUndefined();
expect(defaultEnv.CODEX_MANAGED_BY_BUN).toBeUndefined();
expect(defaultEnv.CODEX_THREAD_ID).toBeUndefined();
expect(defaultEnv.ANTHROPIC_BASE_URL).toBeUndefined();
} finally {
if (originalCodeXHome === undefined) {
delete process.env.CODEX_HOME;
} else {
process.env.CODEX_HOME = originalCodeXHome;
}
if (originalCodeXCi === undefined) {
delete process.env.CODEX_CI;
} else {
process.env.CODEX_CI = originalCodeXCi;
}
if (originalCodeXManagedByBun === undefined) {
delete process.env.CODEX_MANAGED_BY_BUN;
} else {
process.env.CODEX_MANAGED_BY_BUN = originalCodeXManagedByBun;
}
if (originalCodeXThreadId === undefined) {
delete process.env.CODEX_THREAD_ID;
} else {
process.env.CODEX_THREAD_ID = originalCodeXThreadId;
}
if (originalAnthropicBaseUrl === undefined) {
delete process.env.ANTHROPIC_BASE_URL;
} else {
process.env.ANTHROPIC_BASE_URL = originalAnthropicBaseUrl;
}
}
});
});