修复 Codex MCP type 配置并更新发布包
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
|
||||
/** Gitea Workflow Codex/App 协议适配器离线单测。 */
|
||||
const assert = require('node:assert/strict');
|
||||
const { toCodexMcpServerConfig } = require('../lib/codex-mcp-config');
|
||||
const {
|
||||
buildGiteaMcpServerConfig,
|
||||
buildGiteaThreadConfig,
|
||||
@@ -17,6 +18,21 @@ const {
|
||||
} = require('../lib/gitea-workflow-codex');
|
||||
|
||||
async function main() {
|
||||
const legacyMcp = {
|
||||
type: 'streamable_http',
|
||||
url: 'https://mcp.example/tools',
|
||||
bearer_token_env_var: 'MCP_TOKEN',
|
||||
startup_timeout_sec: 20,
|
||||
tool_timeout_sec: 120,
|
||||
env: { type: '保留这个环境变量' },
|
||||
};
|
||||
const outgoingMcp = toCodexMcpServerConfig(legacyMcp);
|
||||
assert.equal(Object.hasOwn(outgoingMcp, 'type'), false);
|
||||
assert.equal(legacyMcp.type, 'streamable_http');
|
||||
assert.notEqual(outgoingMcp, legacyMcp);
|
||||
const { type: legacyType, ...connectionFields } = legacyMcp;
|
||||
assert.deepEqual(outgoingMcp, connectionFields);
|
||||
|
||||
const mcp = buildGiteaMcpServerConfig({
|
||||
host: 'https://gitea.example/',
|
||||
accessToken: 'secret-token',
|
||||
@@ -31,7 +47,10 @@ async function main() {
|
||||
host: 'https://gitea.example',
|
||||
accessToken: 'secret-token',
|
||||
});
|
||||
assert.equal(thread.config['mcp_servers.gitea'].type, 'stdio');
|
||||
assert.equal(Object.hasOwn(thread.config['mcp_servers.gitea'], 'type'), false);
|
||||
assert.equal(thread.mcpServer.type, 'stdio');
|
||||
assert.equal(thread.config['mcp_servers.gitea'].command, 'gitea-mcp');
|
||||
assert.deepEqual(thread.config['mcp_servers.gitea'].args.slice(0, 4), ['-t', 'stdio', '-H', 'https://gitea.example']);
|
||||
assert.equal(thread.config['mcp_servers.gitea'].env.GITEA_ACCESS_TOKEN, 'secret-token');
|
||||
assert.equal(Object.hasOwn(thread.config, 'model'), false);
|
||||
|
||||
|
||||
@@ -690,6 +690,8 @@ function completeMcpToolTurn(thread, turnId) {
|
||||
webSearchMode: thread.config?.web_search || null,
|
||||
hasCcwebMcpConfig: Boolean(ccwebConfig),
|
||||
hasProjectMcpConfig: Boolean(projectConfig),
|
||||
ccwebHasTopLevelType: Boolean(ccwebConfig && Object.hasOwn(ccwebConfig, 'type')),
|
||||
projectHasTopLevelType: Boolean(projectConfig && Object.hasOwn(projectConfig, 'type')),
|
||||
ccwebType: ccwebConfig?.type || (ccwebConfig?.url ? 'streamable_http' : (ccwebConfig?.command ? 'stdio' : null)),
|
||||
ccwebUrl: ccwebConfig?.url || null,
|
||||
ccwebBearerTokenEnvVar: ccwebConfig?.bearer_token_env_var || null,
|
||||
|
||||
@@ -8540,6 +8540,7 @@ async function main() {
|
||||
.split('\n')
|
||||
.find((line) => line.includes(`"event":"process_spawn"`) && line.includes(crossTargetSession.sessionId.slice(0, 8)));
|
||||
assert(mcpSpawnLine && mcpSpawnLine.includes('mcp_servers.ccweb.command') && mcpSpawnLine.includes('mcp_servers.ccweb.env_vars'), 'Codex spawn should inject ccweb MCP config');
|
||||
assert(!mcpSpawnLine.includes('mcp_servers.ccweb.type'), 'Codex CLI ccweb MCP 配置应省略服务器顶层 type');
|
||||
assert(mcpSpawnLine.includes('server.js') && mcpSpawnLine.includes('--ccweb-mcp-server'), 'Codex spawn should launch ccweb MCP through server.js in Node mode');
|
||||
assert(!mcpSpawnLine.includes(internalMcpToken), 'Codex spawn log should not expose internal MCP token');
|
||||
const projectMcpSpawnLine = processLogAfterMcp
|
||||
@@ -8547,6 +8548,7 @@ async function main() {
|
||||
.split('\n')
|
||||
.find((line) => line.includes(`"event":"process_spawn"`) && line.includes(codexSession.sessionId.slice(0, 8)));
|
||||
assert(projectMcpSpawnLine && projectMcpSpawnLine.includes('mcp_servers.reg-project.command'), 'Codex spawn should inject project MCP config from session cwd');
|
||||
assert(!projectMcpSpawnLine.includes('mcp_servers.reg-project.type'), 'Codex CLI project MCP config should omit the server top-level type');
|
||||
|
||||
ws.send(JSON.stringify({ type: 'list_cwd_suggestions' }));
|
||||
const cwdSuggestions = await nextMessage(messages, ws, (msg) => msg.type === 'cwd_suggestions');
|
||||
@@ -8826,6 +8828,8 @@ async function main() {
|
||||
const codexAppEnabledSearchDynamicTool = await nextMessage(messages, ws, (msg) => msg.type === 'tool_end' && msg.sessionId === codexAppSession.sessionId && msg.toolUseId === 'mcp-ccweb-list');
|
||||
assert(/"threadStartWebSearchMode": "live"/.test(codexAppEnabledSearchDynamicTool.result || ''), 'Codex App thread/start should pass web_search=live when Web Search is enabled');
|
||||
assert(/"webSearchMode": "live"/.test(codexAppEnabledSearchDynamicTool.result || ''), 'Codex App thread config should keep web_search=live while Web Search is enabled');
|
||||
assert(/"ccwebHasTopLevelType": false/.test(codexAppEnabledSearchDynamicTool.result || ''), 'Codex App thread/start 的 ccweb 配置应省略顶层 type');
|
||||
assert(/"projectHasTopLevelType": false/.test(codexAppEnabledSearchDynamicTool.result || ''), 'Codex App thread/start 的旧项目配置应省略顶层 type');
|
||||
assert(!/"webSearchToolConfig"/.test(codexAppEnabledSearchDynamicTool.result || ''), 'Codex App should use top-level web_search instead of tools.web_search as the mode switch');
|
||||
await nextMessage(messages, ws, (msg) => msg.type === 'done' && msg.sessionId === codexAppSession.sessionId);
|
||||
|
||||
@@ -8857,6 +8861,8 @@ async function main() {
|
||||
ws.send(JSON.stringify({ type: 'message', text: 'codexapp dynamic after web search disabled prompt', sessionId: codexAppSession.sessionId, mode: 'yolo', agent: 'codexapp' }));
|
||||
const codexAppDisabledSearchDynamicTool = await nextMessage(messages, ws, (msg) => msg.type === 'tool_end' && msg.sessionId === codexAppSession.sessionId && msg.toolUseId === 'mcp-ccweb-list');
|
||||
assert(/"threadConfigMethod": "thread\/resume"/.test(codexAppDisabledSearchDynamicTool.result || ''), 'Codex App existing thread should refresh config through thread/resume');
|
||||
assert(/"ccwebHasTopLevelType": false/.test(codexAppDisabledSearchDynamicTool.result || ''), 'Codex App thread/resume 的 ccweb 配置应省略顶层 type');
|
||||
assert(/"projectHasTopLevelType": false/.test(codexAppDisabledSearchDynamicTool.result || ''), 'Codex App thread/resume 的项目配置应省略顶层 type');
|
||||
assert(/"webSearchMode": "disabled"/.test(codexAppDisabledSearchDynamicTool.result || ''), 'Codex App thread/resume should pass web_search=disabled after Web Search is disabled');
|
||||
assert(!/"webSearchToolConfig"/.test(codexAppDisabledSearchDynamicTool.result || ''), 'Codex App should not use tools.web_search as the Web Search mode switch');
|
||||
await nextMessage(messages, ws, (msg) => msg.type === 'done' && msg.sessionId === codexAppSession.sessionId);
|
||||
@@ -9141,6 +9147,8 @@ async function main() {
|
||||
assert(/currentConversationId/.test(codexAppDynamicTool.result || ''), 'Codex App MCP tool should return ccweb conversation data');
|
||||
assert(/"hasCcwebMcpConfig": true/.test(codexAppDynamicTool.result || ''), 'Codex App thread/start should pass ccweb MCP config');
|
||||
assert(/"hasProjectMcpConfig": true/.test(codexAppDynamicTool.result || ''), 'Codex App thread/start should pass project MCP config from session cwd');
|
||||
assert(/"ccwebHasTopLevelType": false/.test(codexAppDynamicTool.result || ''), 'Codex App ccweb MCP config should omit the server top-level type');
|
||||
assert(/"projectHasTopLevelType": false/.test(codexAppDynamicTool.result || ''), 'Codex App project MCP config should omit the server top-level type');
|
||||
assert(/"ccwebType": "streamable_http"/.test(codexAppDynamicTool.result || ''), 'Codex App ccweb MCP should default to shared streamable HTTP');
|
||||
assert(/"ccwebUrl": "http:\/\/127\.0\.0\.1:\d+\/api\/internal\/mcp\/stream\?/.test(codexAppDynamicTool.result || ''), 'Codex App ccweb MCP should point to the shared cc-web HTTP endpoint');
|
||||
assert(/"ccwebBearerTokenEnvVar": "CC_WEB_CODEX_APP_MCP_TOKEN"/.test(codexAppDynamicTool.result || ''), 'Codex App ccweb MCP should use bearer_token_env_var for the shared endpoint');
|
||||
|
||||
Reference in New Issue
Block a user