codexRuntime.test.ts 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120
  1. import { describe, expect, it } from 'vitest';
  2. import { buildArgs, DEFAULT_API_KEY_ENV, toToolSummaries, type CodexProviderSpec } from './codexRuntime';
  3. /** 这组断言锁的是「喂给 codex.exe 的 -c 参数」——provider 能不能生效全在这里 */
  4. const API_KEY = 'sk-unit-secret-abcdef123456';
  5. const custom: CodexProviderSpec = {
  6. id: 'zsjz',
  7. name: '知数 · 第三方',
  8. baseUrl: 'https://dashscope.aliyuncs.com/compatible-mode/v1',
  9. model: 'qwen3-max',
  10. apiKey: API_KEY,
  11. httpHeaders: { 'X-DashScope-WorkSpace': 'ws-1' },
  12. extra: { request_max_retries: 3, temperature: 0.5, query_params: { version: 'v2' } },
  13. };
  14. describe('buildArgs', () => {
  15. it('没有 provider 时只起 app-server,且不传 --strict-config', () => {
  16. expect(buildArgs(null)).toEqual(['app-server', '--listen', 'stdio://']);
  17. expect(buildArgs(null)).not.toContain('--strict-config');
  18. });
  19. it('自定义 provider 走 responses + 免登录 + env_key', () => {
  20. const args = buildArgs(custom);
  21. expect(args).toContain('-c');
  22. expect(args.join(' ')).toContain('model_provider="zsjz"');
  23. expect(args.join(' ')).toContain('model="qwen3-max"');
  24. expect(args.join(' ')).toContain('model_providers.zsjz.wire_api="responses"');
  25. expect(args.join(' ')).toContain('model_providers.zsjz.requires_openai_auth=false');
  26. expect(args.join(' ')).toContain(`model_providers.zsjz.env_key="${DEFAULT_API_KEY_ENV}"`);
  27. expect(args.join(' ')).toContain('model_providers.zsjz.base_url="https://dashscope.aliyuncs.com/compatible-mode/v1"');
  28. });
  29. it('API Key 绝不出现在命令行参数里', () => {
  30. expect(JSON.stringify(buildArgs(custom))).not.toContain(API_KEY);
  31. });
  32. it('没有 apiKey 时不写 env_key(本地无鉴权服务)', () => {
  33. const args = buildArgs({ ...custom, apiKey: null });
  34. expect(args.join(' ')).not.toContain('env_key');
  35. });
  36. it('http_headers 与 query_params 输出成 TOML inline table', () => {
  37. const joined = buildArgs(custom).join(' ');
  38. // 只含 A-Za-z0-9_- 的键按 TOML bare key 规则输出,无需加引号
  39. expect(joined).toContain('model_providers.zsjz.http_headers={X-DashScope-WorkSpace="ws-1"}');
  40. expect(joined).toContain('model_providers.zsjz.query_params={version="v2"}');
  41. });
  42. it('config 白名单外的键被丢弃', () => {
  43. const joined = buildArgs(custom).join(' ');
  44. expect(joined).toContain('request_max_retries=3');
  45. expect(joined).not.toContain('temperature');
  46. });
  47. it('内置 ollama:带上真实 base_url,否则只会打 localhost', () => {
  48. const args = buildArgs({
  49. id: null,
  50. builtinProvider: 'ollama',
  51. model: 'qwen',
  52. baseUrl: 'http://10.66.66.66:8080/v1',
  53. });
  54. const joined = args.join(' ');
  55. expect(joined).toContain('model_provider="ollama"');
  56. expect(joined).toContain('model_providers.ollama.base_url="http://10.66.66.66:8080/v1"');
  57. expect(joined).not.toContain('wire_api');
  58. expect(joined).not.toContain('env_key');
  59. });
  60. it('内置 provider 未配地址时不覆盖 base_url', () => {
  61. const joined = buildArgs({ id: null, builtinProvider: 'lmstudio', model: 'local-model' }).join(' ');
  62. expect(joined).toContain('model_provider="lmstudio"');
  63. expect(joined).not.toContain('base_url');
  64. });
  65. it('值里的引号与反斜杠被 TOML 安全转义', () => {
  66. const joined = buildArgs({
  67. ...custom,
  68. name: '带"引号"和\\反斜杠',
  69. baseUrl: 'https://x/v1',
  70. }).join(' ');
  71. expect(joined).toContain('model_providers.zsjz.name="带\\"引号\\"和\\\\反斜杠"');
  72. });
  73. it('嵌套 map 输出成 TOML inline table,而不是 JSON', () => {
  74. const joined = buildArgs({
  75. ...custom,
  76. extra: { query_params: { version: 'v2', flag: 'on' } },
  77. }).join(' ');
  78. expect(joined).toContain('model_providers.zsjz.query_params={version="v2",flag="on"}');
  79. expect(joined).not.toContain('{"version"');
  80. });
  81. it('不符合 bare key 规则的键加引号', () => {
  82. const joined = buildArgs({ ...custom, httpHeaders: { 'X Token': 'a' } }).join(' ');
  83. expect(joined).toContain('model_providers.zsjz.http_headers={"X Token"="a"}');
  84. });
  85. });
  86. /** mcpServerStatus/list 的 tools 详情结构不由我们决定,这里锁住退化行为 */
  87. describe('toToolSummaries', () => {
  88. it('详情里带 name / description 时优先取详情', () => {
  89. expect(toToolSummaries({ query_case: { name: 'query_case_v2', description: '按条件查案件' } })).toEqual([
  90. { name: 'query_case_v2', description: '按条件查案件' },
  91. ]);
  92. });
  93. it('详情形态不认识时退化成用 key 当工具名,描述留空', () => {
  94. expect(toToolSummaries({ list_tables: { inputSchema: {} }, ping: null })).toEqual([
  95. { name: 'list_tables', description: '' },
  96. { name: 'ping', description: '' },
  97. ]);
  98. });
  99. it('tools 缺失时返回空列表', () => {
  100. expect(toToolSummaries(undefined)).toEqual([]);
  101. expect(toToolSummaries(null)).toEqual([]);
  102. });
  103. });