@jsonstudio/rcc 0.89.3 → 0.89.164
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +240 -179
- package/config/modules.json +1 -11
- package/dist/build-info.js +2 -2
- package/dist/build-info.js.map +1 -1
- package/dist/client/gemini-cli/gemini-cli-protocol-client.d.ts +16 -0
- package/dist/client/gemini-cli/gemini-cli-protocol-client.js +56 -0
- package/dist/client/gemini-cli/gemini-cli-protocol-client.js.map +1 -0
- package/dist/client/openai/chat-protocol-client.js.map +1 -1
- package/dist/config/modules.json +1 -11
- package/dist/core/provider-health-manager.d.ts +17 -0
- package/dist/core/provider-health-manager.js +66 -0
- package/dist/core/provider-health-manager.js.map +1 -0
- package/dist/error-handling/route-error-hub.d.ts +48 -0
- package/dist/error-handling/route-error-hub.js +131 -0
- package/dist/error-handling/route-error-hub.js.map +1 -0
- package/dist/index.js +26 -1
- package/dist/index.js.map +1 -1
- package/dist/modules/llmswitch/bridge.d.ts +2 -0
- package/dist/modules/llmswitch/bridge.js +17 -0
- package/dist/modules/llmswitch/bridge.js.map +1 -1
- package/dist/modules/pipeline/utils/colored-logger.d.ts +14 -0
- package/dist/modules/pipeline/utils/colored-logger.js +48 -0
- package/dist/modules/pipeline/utils/colored-logger.js.map +1 -0
- package/dist/modules/pipeline/utils/debug-logger.d.ts +2 -0
- package/dist/modules/pipeline/utils/debug-logger.js +36 -0
- package/dist/modules/pipeline/utils/debug-logger.js.map +1 -1
- package/dist/providers/auth/gemini-cli-userinfo-helper.d.ts +53 -0
- package/dist/providers/auth/gemini-cli-userinfo-helper.js +152 -0
- package/dist/providers/auth/gemini-cli-userinfo-helper.js.map +1 -0
- package/dist/providers/auth/oauth-auth.js +3 -2
- package/dist/providers/auth/oauth-auth.js.map +1 -1
- package/dist/providers/auth/oauth-lifecycle.js +21 -20
- package/dist/providers/auth/oauth-lifecycle.js.map +1 -1
- package/dist/providers/auth/oauth-logger.d.ts +1 -0
- package/dist/providers/auth/oauth-logger.js +21 -0
- package/dist/providers/auth/oauth-logger.js.map +1 -0
- package/dist/providers/compat/compat-directory-loader.js +2 -55
- package/dist/providers/compat/compat-directory-loader.js.map +1 -1
- package/dist/providers/compat/compatibility-factory.d.ts +4 -4
- package/dist/providers/compat/compatibility-factory.js +108 -0
- package/dist/providers/compat/compatibility-factory.js.map +1 -1
- package/dist/providers/compat/glm/glm-compatibility.d.ts +2 -2
- package/dist/providers/compat/glm/glm-compatibility.js +7 -7
- package/dist/providers/compat/glm/glm-compatibility.js.map +1 -1
- package/dist/providers/compat/glm/index.js +0 -6
- package/dist/providers/compat/glm/index.js.map +1 -1
- package/dist/providers/compat/iflow/iflow-compatibility.d.ts +1 -1
- package/dist/providers/compat/iflow/iflow-compatibility.js +6 -6
- package/dist/providers/compat/iflow/iflow-compatibility.js.map +1 -1
- package/dist/providers/compat/index.d.ts +0 -6
- package/dist/providers/compat/index.js +0 -7
- package/dist/providers/compat/index.js.map +1 -1
- package/dist/providers/compat/lmstudio-compatibility.d.ts +2 -2
- package/dist/providers/compat/lmstudio-compatibility.js +4 -4
- package/dist/providers/compat/lmstudio-compatibility.js.map +1 -1
- package/dist/providers/compat/passthrough-compatibility.d.ts +1 -1
- package/dist/providers/compat/passthrough-compatibility.js +3 -3
- package/dist/providers/compat/passthrough-compatibility.js.map +1 -1
- package/dist/providers/compat/profiles/chat/glm/index.d.ts +6 -0
- package/dist/providers/compat/profiles/chat/glm/index.js +6 -0
- package/dist/providers/compat/profiles/chat/glm/index.js.map +1 -0
- package/dist/providers/compat/profiles/chat/iflow/index.d.ts +6 -0
- package/dist/providers/compat/profiles/chat/iflow/index.js +6 -0
- package/dist/providers/compat/profiles/chat/iflow/index.js.map +1 -0
- package/dist/providers/compat/profiles/chat/lmstudio/index.d.ts +6 -0
- package/dist/providers/compat/profiles/chat/lmstudio/index.js +6 -0
- package/dist/providers/compat/profiles/chat/lmstudio/index.js.map +1 -0
- package/dist/providers/compat/profiles/chat/qwen/index.d.ts +6 -0
- package/dist/providers/compat/profiles/chat/qwen/index.js +6 -0
- package/dist/providers/compat/profiles/chat/qwen/index.js.map +1 -0
- package/dist/providers/compat/profiles/compat/passthrough/index.d.ts +6 -0
- package/dist/providers/compat/profiles/compat/passthrough/index.js +6 -0
- package/dist/providers/compat/profiles/compat/passthrough/index.js.map +1 -0
- package/dist/providers/compat/profiles/responses/c4m/index.d.ts +6 -0
- package/dist/providers/compat/profiles/responses/c4m/index.js +6 -0
- package/dist/providers/compat/profiles/responses/c4m/index.js.map +1 -0
- package/dist/providers/compat/profiles/responses/default/index.d.ts +6 -0
- package/dist/providers/compat/profiles/responses/default/index.js +6 -0
- package/dist/providers/compat/profiles/responses/default/index.js.map +1 -0
- package/dist/providers/compat/profiles/responses/fai/index.d.ts +6 -0
- package/dist/providers/compat/profiles/responses/fai/index.js +6 -0
- package/dist/providers/compat/profiles/responses/fai/index.js.map +1 -0
- package/dist/providers/compat/profiles/responses/fc/index.d.ts +6 -0
- package/dist/providers/compat/profiles/responses/fc/index.js +6 -0
- package/dist/providers/compat/profiles/responses/fc/index.js.map +1 -0
- package/dist/providers/compat/qwen/index.js +0 -6
- package/dist/providers/compat/qwen/index.js.map +1 -1
- package/dist/providers/compat/qwen-compatibility.d.ts +2 -2
- package/dist/providers/compat/qwen-compatibility.js +4 -4
- package/dist/providers/compat/qwen-compatibility.js.map +1 -1
- package/dist/providers/compat/register-compat-module.d.ts +8 -0
- package/dist/providers/compat/register-compat-module.js +53 -0
- package/dist/providers/compat/register-compat-module.js.map +1 -0
- package/dist/providers/compat/responses/c4m-responses-compatibility.d.ts +6 -2
- package/dist/providers/compat/responses/c4m-responses-compatibility.js +85 -3
- package/dist/providers/compat/responses/c4m-responses-compatibility.js.map +1 -1
- package/dist/providers/compat/standard-compatibility-utils.js +45 -15
- package/dist/providers/compat/standard-compatibility-utils.js.map +1 -1
- package/dist/providers/core/api/provider-config.d.ts +1 -1
- package/dist/providers/core/api/provider-types.d.ts +3 -1
- package/dist/providers/core/api/provider-types.js +1 -0
- package/dist/providers/core/api/provider-types.js.map +1 -1
- package/dist/providers/core/config/service-profiles.js +5 -2
- package/dist/providers/core/config/service-profiles.js.map +1 -1
- package/dist/providers/core/runtime/base-provider.d.ts +3 -0
- package/dist/providers/core/runtime/base-provider.js +101 -6
- package/dist/providers/core/runtime/base-provider.js.map +1 -1
- package/dist/providers/core/runtime/gemini-cli-http-provider.d.ts +34 -0
- package/dist/providers/core/runtime/gemini-cli-http-provider.js +152 -0
- package/dist/providers/core/runtime/gemini-cli-http-provider.js.map +1 -0
- package/dist/providers/core/runtime/http-transport-provider.d.ts +1 -0
- package/dist/providers/core/runtime/http-transport-provider.js +178 -123
- package/dist/providers/core/runtime/http-transport-provider.js.map +1 -1
- package/dist/providers/core/runtime/provider-factory.d.ts +1 -1
- package/dist/providers/core/runtime/provider-factory.js +8 -0
- package/dist/providers/core/runtime/provider-factory.js.map +1 -1
- package/dist/providers/core/runtime/provider-runtime-metadata.d.ts +1 -0
- package/dist/providers/core/runtime/provider-runtime-metadata.js.map +1 -1
- package/dist/providers/core/runtime/responses-provider.d.ts +7 -0
- package/dist/providers/core/runtime/responses-provider.js +228 -12
- package/dist/providers/core/runtime/responses-provider.js.map +1 -1
- package/dist/providers/core/strategies/oauth-auth-code-flow.js +10 -9
- package/dist/providers/core/strategies/oauth-auth-code-flow.js.map +1 -1
- package/dist/providers/core/strategies/oauth-device-flow.js +10 -9
- package/dist/providers/core/strategies/oauth-device-flow.js.map +1 -1
- package/dist/providers/core/utils/provider-error-reporter.js +63 -15
- package/dist/providers/core/utils/provider-error-reporter.js.map +1 -1
- package/dist/providers/core/utils/provider-type-utils.d.ts +1 -1
- package/dist/providers/core/utils/provider-type-utils.js +6 -1
- package/dist/providers/core/utils/provider-type-utils.js.map +1 -1
- package/dist/providers/core/utils/snapshot-writer.d.ts +10 -0
- package/dist/providers/core/utils/snapshot-writer.js +85 -0
- package/dist/providers/core/utils/snapshot-writer.js.map +1 -1
- package/dist/providers/mock/mock-provider-runtime.js +44 -0
- package/dist/providers/mock/mock-provider-runtime.js.map +1 -1
- package/dist/providers/profile/provider-profile-loader.js +26 -19
- package/dist/providers/profile/provider-profile-loader.js.map +1 -1
- package/dist/providers/profile/provider-profile.d.ts +2 -2
- package/dist/server/handlers/chat-handler.js +9 -3
- package/dist/server/handlers/chat-handler.js.map +1 -1
- package/dist/server/handlers/handler-utils.d.ts +7 -1
- package/dist/server/handlers/handler-utils.js +64 -52
- package/dist/server/handlers/handler-utils.js.map +1 -1
- package/dist/server/handlers/messages-handler.js +9 -3
- package/dist/server/handlers/messages-handler.js.map +1 -1
- package/dist/server/handlers/responses-handler.js +21 -13
- package/dist/server/handlers/responses-handler.js.map +1 -1
- package/dist/server/runtime/http-server/colored-logger.d.ts +1 -0
- package/dist/server/runtime/http-server/colored-logger.js +33 -0
- package/dist/server/runtime/http-server/colored-logger.js.map +1 -0
- package/dist/server/runtime/http-server/index.d.ts +3 -0
- package/dist/server/runtime/http-server/index.js +76 -19
- package/dist/server/runtime/http-server/index.js.map +1 -1
- package/dist/server/runtime/http-server/provider-utils.d.ts +3 -1
- package/dist/server/runtime/http-server/provider-utils.js +12 -2
- package/dist/server/runtime/http-server/provider-utils.js.map +1 -1
- package/dist/server/runtime/http-server/request-executor.js +6 -2
- package/dist/server/runtime/http-server/request-executor.js.map +1 -1
- package/dist/server/runtime/http-server/routes.js +31 -11
- package/dist/server/runtime/http-server/routes.js.map +1 -1
- package/dist/server/runtime/http-server/types.d.ts +2 -1
- package/dist/utils/error-center-payload.d.ts +7 -0
- package/dist/utils/error-center-payload.js +67 -0
- package/dist/utils/error-center-payload.js.map +1 -0
- package/dist/utils/error-handler-registry.d.ts +7 -0
- package/dist/utils/error-handler-registry.js +44 -12
- package/dist/utils/error-handler-registry.js.map +1 -1
- package/node_modules/@jsonstudio/llms/dist/conversion/codecs/responses-openai-codec.js +16 -1
- package/node_modules/@jsonstudio/llms/dist/conversion/compat/profiles/chat-glm.json +17 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/compat/profiles/chat-iflow.json +36 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/compat/profiles/chat-lmstudio.json +37 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/compat/profiles/chat-qwen.json +18 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/compat/profiles/responses-c4m.json +45 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/config/compat-profiles.json +38 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/config/sample-config.json +314 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/config/version-switch.json +150 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/compat/compat-engine.d.ts +4 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/compat/compat-engine.js +667 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/compat/compat-profile-store.d.ts +2 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/compat/compat-profile-store.js +76 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/compat/compat-types.d.ts +62 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/compat/compat-types.js +1 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/hub-pipeline.d.ts +2 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/hub-pipeline.js +110 -29
- package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage3_compat/index.d.ts +14 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage3_compat/index.js +23 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/stages/resp_outbound/resp_outbound_stage1_client_remap/index.js +34 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/hub/process/chat-process.js +4 -1
- package/node_modules/@jsonstudio/llms/dist/conversion/hub/response/provider-response.js +26 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/responses/responses-openai-bridge.d.ts +1 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/responses/responses-openai-bridge.js +71 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/shared/responses-conversation-store.d.ts +35 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/shared/responses-conversation-store.js +64 -19
- package/node_modules/@jsonstudio/llms/dist/conversion/shared/tool-filter-pipeline.d.ts +21 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/shared/tool-filter-pipeline.js +138 -22
- package/node_modules/@jsonstudio/llms/dist/conversion/shared/tool-governor.d.ts +21 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/shared/tool-governor.js +116 -1
- package/node_modules/@jsonstudio/llms/dist/conversion/shared/tool-mapping.js +52 -2
- package/node_modules/@jsonstudio/llms/dist/filters/config/openai-openai.fieldmap.json +18 -0
- package/node_modules/@jsonstudio/llms/dist/filters/special/request-tools-normalize.js +20 -1
- package/node_modules/@jsonstudio/llms/dist/guidance/index.js +6 -2
- package/node_modules/@jsonstudio/llms/dist/router/virtual-router/bootstrap.js +16 -7
- package/node_modules/@jsonstudio/llms/dist/router/virtual-router/classifier.js +40 -37
- package/node_modules/@jsonstudio/llms/dist/router/virtual-router/default-thinking-keywords.d.ts +1 -0
- package/node_modules/@jsonstudio/llms/dist/router/virtual-router/default-thinking-keywords.js +13 -0
- package/node_modules/@jsonstudio/llms/dist/router/virtual-router/engine.d.ts +39 -0
- package/node_modules/@jsonstudio/llms/dist/router/virtual-router/engine.js +52 -11
- package/node_modules/@jsonstudio/llms/dist/router/virtual-router/features.js +340 -11
- package/node_modules/@jsonstudio/llms/dist/router/virtual-router/token-counter.d.ts +2 -0
- package/node_modules/@jsonstudio/llms/dist/router/virtual-router/token-counter.js +105 -0
- package/node_modules/@jsonstudio/llms/dist/router/virtual-router/types.d.ts +8 -0
- package/node_modules/@jsonstudio/llms/dist/router/virtual-router/types.js +2 -2
- package/node_modules/@jsonstudio/llms/dist/sse/sse-to-json/builders/response-builder.d.ts +2 -0
- package/node_modules/@jsonstudio/llms/dist/sse/sse-to-json/builders/response-builder.js +53 -11
- package/node_modules/@jsonstudio/llms/dist/test-output/virtual-router/results.json +1 -0
- package/node_modules/@jsonstudio/llms/dist/test-output/virtual-router/summary.json +12 -0
- package/node_modules/@jsonstudio/llms/dist/tools/tool-registry.js +4 -3
- package/node_modules/@jsonstudio/llms/package.json +3 -3
- package/package.json +11 -9
- package/scripts/analyze-routing-classifier.mjs +166 -0
- package/scripts/analyze-routing-samples.mjs +216 -0
- package/scripts/analyze-thinking-keywords.mjs +17 -2
- package/scripts/build-core.mjs +8 -0
- package/scripts/classify-sample-tools.mjs +252 -0
- package/scripts/ensure-llmswitch-mode.mjs +95 -0
- package/scripts/gen-build-info.mjs +58 -4
- package/scripts/install-global.sh +1 -1
- package/scripts/install-release.sh +7 -0
- package/scripts/mock-provider/run-regressions.mjs +60 -14
- package/scripts/tests/apply-patch-loop.mjs +100 -9
- package/scripts/tests/golden-provider-cycle.mjs +12 -1
- package/scripts/tests/responses-provider-dry-run.mjs +15 -1
- package/scripts/tests/virtual-router-health.mjs +12 -5
- package/scripts/tools/capture-provider-goldens.mjs +75 -25
- package/scripts/tools/responses-golden-dry-run.mjs +17 -1
- package/scripts/tools/responses-provider-replay.mjs +17 -1
- package/scripts/tools/sync-ci-goldens.mjs +131 -0
- package/scripts/verification/samples/openai-chat-list-local-files.json +19 -796
- package/scripts/verify-e2e-toolcall.mjs +52 -0
- package/dist/providers/compat/config/index.d.ts +0 -1
- package/dist/providers/compat/config/index.js +0 -5
- package/dist/providers/compat/config/index.js.map +0 -1
- package/dist/providers/compat/iflow/index.d.ts +0 -27
- package/dist/providers/compat/iflow/index.js +0 -32
- package/dist/providers/compat/iflow/index.js.map +0 -1
- package/dist/providers/compat/lmstudio/index.d.ts +0 -4
- package/dist/providers/compat/lmstudio/index.js +0 -10
- package/dist/providers/compat/lmstudio/index.js.map +0 -1
- package/dist/providers/compat/passthrough/index.d.ts +0 -4
- package/dist/providers/compat/passthrough/index.js +0 -9
- package/dist/providers/compat/passthrough/index.js.map +0 -1
- package/dist/providers/compat/responses/index.d.ts +0 -1
- package/dist/providers/compat/responses/index.js +0 -8
- package/dist/providers/compat/responses/index.js.map +0 -1
- package/dist/providers/core/composite/compat/anthropic.d.ts +0 -3
- package/dist/providers/core/composite/compat/anthropic.js +0 -7
- package/dist/providers/core/composite/compat/anthropic.js.map +0 -1
- package/dist/providers/core/composite/compat/gemini.d.ts +0 -3
- package/dist/providers/core/composite/compat/gemini.js +0 -7
- package/dist/providers/core/composite/compat/gemini.js.map +0 -1
- package/dist/providers/core/composite/compat/openai-compat-aggregator.d.ts +0 -9
- package/dist/providers/core/composite/compat/openai-compat-aggregator.js +0 -135
- package/dist/providers/core/composite/compat/openai-compat-aggregator.js.map +0 -1
- package/dist/providers/core/composite/compat/responses.d.ts +0 -3
- package/dist/providers/core/composite/compat/responses.js +0 -91
- package/dist/providers/core/composite/compat/responses.js.map +0 -1
- package/dist/providers/core/composite/provider-composite.d.ts +0 -50
- package/dist/providers/core/composite/provider-composite.js +0 -235
- package/dist/providers/core/composite/provider-composite.js.map +0 -1
- package/scripts/tests/apply-patch-loop.mjs.bak +0 -363
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import fs from 'node:fs/promises';
|
|
3
3
|
import path from 'node:path';
|
|
4
|
+
import os from 'node:os';
|
|
4
5
|
import { fileURLToPath } from 'node:url';
|
|
5
6
|
import { TextDecoder } from 'node:util';
|
|
6
7
|
import { Readable } from 'node:stream';
|
|
@@ -14,6 +15,11 @@ const PROJECT_ROOT = path.resolve(__dirname, '../..');
|
|
|
14
15
|
const MOCK_SAMPLES_DIR = path.join(PROJECT_ROOT, 'samples/mock-provider');
|
|
15
16
|
const PORT = Number(process.env.RCC_TOOL_LOOP_PORT || 5555);
|
|
16
17
|
const BASE_URL = `http://127.0.0.1:${PORT}`;
|
|
18
|
+
const HOME = os.homedir();
|
|
19
|
+
const STAGE_DIR = path.join(HOME, '.routecodex', 'golden_samples', 'openai-responses');
|
|
20
|
+
const STAGE_SUFFIX = '_req_outbound_stage2_format_build.json';
|
|
21
|
+
const STAGE1_SUFFIX = '_req_outbound_stage1_semantic_map.json';
|
|
22
|
+
const MOCK_PROVIDER_ID = 'mock.apply_patch.toolloop';
|
|
17
23
|
|
|
18
24
|
function listProcessesOnPort(port) {
|
|
19
25
|
try {
|
|
@@ -59,6 +65,84 @@ async function ensurePortFree(port) {
|
|
|
59
65
|
await delay(200);
|
|
60
66
|
}
|
|
61
67
|
|
|
68
|
+
async function snapshotStageFiles() {
|
|
69
|
+
try {
|
|
70
|
+
const entries = await fs.readdir(STAGE_DIR);
|
|
71
|
+
return new Set(entries.filter((name) => name.endsWith(STAGE_SUFFIX)));
|
|
72
|
+
} catch {
|
|
73
|
+
return new Set();
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async function diffStageFiles(beforeSet) {
|
|
78
|
+
try {
|
|
79
|
+
const entries = await fs.readdir(STAGE_DIR);
|
|
80
|
+
return entries
|
|
81
|
+
.filter((name) => name.endsWith(STAGE_SUFFIX))
|
|
82
|
+
.filter((name) => !beforeSet.has(name));
|
|
83
|
+
} catch {
|
|
84
|
+
return [];
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async function waitForMockStage(beforeSet, timeoutMs = 10000) {
|
|
89
|
+
const deadline = Date.now() + timeoutMs;
|
|
90
|
+
while (Date.now() < deadline) {
|
|
91
|
+
const candidates = await diffStageFiles(beforeSet);
|
|
92
|
+
for (const name of candidates) {
|
|
93
|
+
const stage1Name = name.replace(STAGE_SUFFIX, STAGE1_SUFFIX);
|
|
94
|
+
const stage1Path = path.join(STAGE_DIR, stage1Name);
|
|
95
|
+
try {
|
|
96
|
+
await fs.access(stage1Path);
|
|
97
|
+
} catch {
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
let providerId = '';
|
|
101
|
+
try {
|
|
102
|
+
const stage1Doc = JSON.parse(await fs.readFile(stage1Path, 'utf-8'));
|
|
103
|
+
providerId = stage1Doc?.body?.meta?.context?.providerId ?? '';
|
|
104
|
+
} catch {
|
|
105
|
+
providerId = '';
|
|
106
|
+
}
|
|
107
|
+
if (typeof providerId === 'string' && providerId === MOCK_PROVIDER_ID) {
|
|
108
|
+
return path.join(STAGE_DIR, name);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
await delay(250);
|
|
112
|
+
}
|
|
113
|
+
throw new Error('mock apply_patch stage snapshot not found (enable ROUTECODEX_STAGE_LOG)');
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async function verifyApplyPatchTool(stagePath) {
|
|
117
|
+
const raw = await fs.readFile(stagePath, 'utf-8');
|
|
118
|
+
const doc = JSON.parse(raw);
|
|
119
|
+
const payload = doc?.body ?? doc;
|
|
120
|
+
const tools = Array.isArray(payload?.tools) ? payload.tools : [];
|
|
121
|
+
if (!tools.length) {
|
|
122
|
+
throw new Error('provider payload missing tools array');
|
|
123
|
+
}
|
|
124
|
+
const match = tools.find((tool) => {
|
|
125
|
+
const name = tool?.name || tool?.function?.name;
|
|
126
|
+
return typeof name === 'string' && name.trim() === 'apply_patch';
|
|
127
|
+
});
|
|
128
|
+
if (!match) {
|
|
129
|
+
throw new Error('apply_patch tool declaration missing in provider payload');
|
|
130
|
+
}
|
|
131
|
+
const params = match.parameters || match.function?.parameters;
|
|
132
|
+
const props = params?.properties;
|
|
133
|
+
const inputField = props?.input;
|
|
134
|
+
if (!inputField || typeof inputField !== 'object') {
|
|
135
|
+
throw new Error('apply_patch.parameters.input missing');
|
|
136
|
+
}
|
|
137
|
+
if (String(inputField.type).toLowerCase() !== 'string') {
|
|
138
|
+
throw new Error('apply_patch.parameters.input must be a string');
|
|
139
|
+
}
|
|
140
|
+
const required = Array.isArray(params?.required) ? params.required.map((v) => String(v)) : [];
|
|
141
|
+
if (!required.includes('input')) {
|
|
142
|
+
throw new Error('apply_patch.parameters.required must include \"input\"');
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
62
146
|
function buildMockConfig(port) {
|
|
63
147
|
return {
|
|
64
148
|
version: '1.0.0',
|
|
@@ -210,11 +294,12 @@ async function requestApplyPatchLoop() {
|
|
|
210
294
|
responseId = String(data?.response?.id || '');
|
|
211
295
|
console.log(`[tool-loop] response.created id=${responseId}`);
|
|
212
296
|
} else if (ev.event === 'response.required_action') {
|
|
213
|
-
toolCalls
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
297
|
+
if (!toolCalls.length) {
|
|
298
|
+
toolCalls = Array.isArray(data?.required_action?.submit_tool_outputs?.tool_calls)
|
|
299
|
+
? data.required_action.submit_tool_outputs.tool_calls
|
|
300
|
+
: [];
|
|
301
|
+
console.log(`[tool-loop] required_action tool_calls=${toolCalls.length}`);
|
|
302
|
+
}
|
|
218
303
|
}
|
|
219
304
|
}
|
|
220
305
|
|
|
@@ -231,7 +316,8 @@ async function requestApplyPatchLoop() {
|
|
|
231
316
|
let patchText = '';
|
|
232
317
|
try {
|
|
233
318
|
const parsed = JSON.parse(firstCall.function.arguments || '{}');
|
|
234
|
-
|
|
319
|
+
const diffText = parsed?.input ?? parsed?.patch;
|
|
320
|
+
patchText = typeof diffText === 'string' ? diffText : '';
|
|
235
321
|
} catch {
|
|
236
322
|
throw new Error('apply_patch.arguments JSON parse failed');
|
|
237
323
|
}
|
|
@@ -264,12 +350,12 @@ function buildResponsesPayload() {
|
|
|
264
350
|
parameters: {
|
|
265
351
|
type: 'object',
|
|
266
352
|
properties: {
|
|
267
|
-
|
|
353
|
+
input: {
|
|
268
354
|
type: 'string',
|
|
269
355
|
description: 'Unified diff patch content (*** Begin Patch ... *** End Patch)'
|
|
270
356
|
}
|
|
271
357
|
},
|
|
272
|
-
required: ['
|
|
358
|
+
required: ['input'],
|
|
273
359
|
additionalProperties: false
|
|
274
360
|
},
|
|
275
361
|
strict: true
|
|
@@ -336,13 +422,18 @@ async function main() {
|
|
|
336
422
|
ROUTECODEX_MOCK_CONFIG_PATH: file,
|
|
337
423
|
ROUTECODEX_MOCK_SAMPLES_DIR: MOCK_SAMPLES_DIR,
|
|
338
424
|
ROUTECODEX_MOCK_VALIDATE_NAMES: '1',
|
|
425
|
+
ROUTECODEX_CONFIG_PATH: file,
|
|
339
426
|
ROUTECODEX_PORT: String(PORT),
|
|
340
|
-
ROUTECODEX_STAGE_LOG:
|
|
427
|
+
ROUTECODEX_STAGE_LOG: '1'
|
|
341
428
|
}
|
|
342
429
|
});
|
|
343
430
|
try {
|
|
344
431
|
await waitForHealth(server);
|
|
432
|
+
const stageBefore = await snapshotStageFiles();
|
|
345
433
|
const { responseId, toolCalls, patchText } = await requestApplyPatchLoop();
|
|
434
|
+
const stagePath = await waitForMockStage(stageBefore);
|
|
435
|
+
await verifyApplyPatchTool(stagePath);
|
|
436
|
+
console.log(`[tool-loop] verified provider payload stage → ${stagePath}`);
|
|
346
437
|
await submitToolOutputs(responseId, toolCalls, patchText);
|
|
347
438
|
console.log('[tool-loop] apply_patch loop PASSED');
|
|
348
439
|
} finally {
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import os from 'node:os';
|
|
3
5
|
import { spawn } from 'node:child_process';
|
|
4
6
|
import { fileURLToPath } from 'node:url';
|
|
5
7
|
import path from 'node:path';
|
|
6
8
|
|
|
7
9
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
8
10
|
const PROJECT_ROOT = path.resolve(__dirname, '../..');
|
|
11
|
+
const HOME = os.homedir();
|
|
12
|
+
const USER_SAMPLES_HINT = path.join(HOME, '.routecodex', 'codex-samples');
|
|
9
13
|
|
|
10
14
|
function run(command, args, env = {}) {
|
|
11
15
|
return new Promise((resolve, reject) => {
|
|
@@ -27,7 +31,14 @@ async function main() {
|
|
|
27
31
|
'--custom-only',
|
|
28
32
|
'--update-golden'
|
|
29
33
|
]);
|
|
30
|
-
await run('node', ['scripts/mock-provider/run-regressions.mjs']
|
|
34
|
+
await run('node', ['scripts/mock-provider/run-regressions.mjs'], {
|
|
35
|
+
ROUTECODEX_MOCK_ENTRY_FILTER: 'all'
|
|
36
|
+
});
|
|
37
|
+
if (fs.existsSync(USER_SAMPLES_HINT)) {
|
|
38
|
+
console.log('[golden-cycle] detected ~/.routecodex/codex-samples; run "node scripts/mock-provider/capture-from-configs.mjs" to ingest latest provider recordings for deep regression.');
|
|
39
|
+
} else {
|
|
40
|
+
console.log('[golden-cycle] ~/.routecodex/codex-samples missing; skipping deep regression (optional).');
|
|
41
|
+
}
|
|
31
42
|
}
|
|
32
43
|
|
|
33
44
|
main().catch((error) => {
|
|
@@ -32,6 +32,20 @@ if (!providerDef) usage(`Provider "${providerId}" not found in config`);
|
|
|
32
32
|
const auth = providerDef.auth || {};
|
|
33
33
|
const apiKey = auth.apiKey || auth.value || process.env.DRY_RUN_API_KEY || 'dry-run-key';
|
|
34
34
|
|
|
35
|
+
const legacyCompatFields = [];
|
|
36
|
+
if (typeof providerDef.compatibility_profile === 'string') legacyCompatFields.push('compatibility_profile');
|
|
37
|
+
if (typeof providerDef.compat === 'string') legacyCompatFields.push('compat');
|
|
38
|
+
if (providerDef.compatibility && typeof providerDef.compatibility === 'object') {
|
|
39
|
+
if (typeof providerDef.compatibility.profile === 'string') legacyCompatFields.push('compatibility.profile');
|
|
40
|
+
if (typeof providerDef.compatibility.id === 'string') legacyCompatFields.push('compatibility.id');
|
|
41
|
+
}
|
|
42
|
+
if (legacyCompatFields.length > 0) {
|
|
43
|
+
usage(`Provider "${providerId}" uses legacy compatibility field(s): ${legacyCompatFields.join(', ')}. Rename to "compatibilityProfile".`);
|
|
44
|
+
}
|
|
45
|
+
const compatProfile =
|
|
46
|
+
(typeof providerDef.compatibilityProfile === 'string' && providerDef.compatibilityProfile.trim()) ||
|
|
47
|
+
undefined;
|
|
48
|
+
|
|
35
49
|
const runtime = {
|
|
36
50
|
runtimeKey: `${providerId}.dry`,
|
|
37
51
|
providerId,
|
|
@@ -42,7 +56,7 @@ const runtime = {
|
|
|
42
56
|
type: 'apikey',
|
|
43
57
|
value: apiKey
|
|
44
58
|
},
|
|
45
|
-
compatibilityProfile:
|
|
59
|
+
compatibilityProfile: compatProfile,
|
|
46
60
|
outboundProfile: providerDef.type === 'responses' ? 'openai-responses' : 'openai-chat',
|
|
47
61
|
defaultModel: modelId
|
|
48
62
|
};
|
|
@@ -45,7 +45,7 @@ function buildProviderProfile(providerKey, endpoint) {
|
|
|
45
45
|
endpoint,
|
|
46
46
|
auth: { type: 'apiKey', secretRef: providerKey },
|
|
47
47
|
outboundProfile: 'openai-chat',
|
|
48
|
-
compatibilityProfile: '
|
|
48
|
+
compatibilityProfile: 'compat:passthrough',
|
|
49
49
|
defaultModel: 'sim-model'
|
|
50
50
|
};
|
|
51
51
|
}
|
|
@@ -65,7 +65,7 @@ function createRouterConfig() {
|
|
|
65
65
|
routing,
|
|
66
66
|
providers,
|
|
67
67
|
classifier: {
|
|
68
|
-
longContextThresholdTokens:
|
|
68
|
+
longContextThresholdTokens: 180000,
|
|
69
69
|
thinkingKeywords: ['think', '考', 'reason'],
|
|
70
70
|
backgroundKeywords: []
|
|
71
71
|
},
|
|
@@ -128,9 +128,9 @@ class VirtualRouterSimulator {
|
|
|
128
128
|
return event;
|
|
129
129
|
}
|
|
130
130
|
|
|
131
|
-
runRoute(label = 'default') {
|
|
131
|
+
runRoute(label = 'default', text) {
|
|
132
132
|
const requestId = `req_${++this.sequence}`;
|
|
133
|
-
const request = cloneRequest(`scenario:${label}:${Date.now()}`);
|
|
133
|
+
const request = cloneRequest(text ?? `scenario:${label}:${Date.now()}`);
|
|
134
134
|
const metadata = {
|
|
135
135
|
requestId,
|
|
136
136
|
entryEndpoint: '/v1/chat/completions',
|
|
@@ -227,6 +227,12 @@ async function scenarioScheduler(sim) {
|
|
|
227
227
|
sim.runRoute('thinking');
|
|
228
228
|
}
|
|
229
229
|
|
|
230
|
+
async function scenarioRoutingDirectives(sim) {
|
|
231
|
+
sim.runRoute('baseline', '普通请求');
|
|
232
|
+
sim.runRoute('forced-thinking', '请仔细分析这个问题 <**thinking**>');
|
|
233
|
+
sim.runRoute('forced-provider', '请强制使用这个provider <**charlie.sim-model**> 来回答');
|
|
234
|
+
}
|
|
235
|
+
|
|
230
236
|
async function main() {
|
|
231
237
|
const args = parseArgs(process.argv.slice(2));
|
|
232
238
|
if (args.help) {
|
|
@@ -240,7 +246,8 @@ async function main() {
|
|
|
240
246
|
['client-error', scenarioClientError],
|
|
241
247
|
['upstream', scenarioUpstream],
|
|
242
248
|
['timeout', scenarioTimeout],
|
|
243
|
-
['scheduler', scenarioScheduler]
|
|
249
|
+
['scheduler', scenarioScheduler],
|
|
250
|
+
['routing-directives', scenarioRoutingDirectives]
|
|
244
251
|
];
|
|
245
252
|
|
|
246
253
|
const summary = [];
|
|
@@ -15,7 +15,11 @@ const HOME = os.homedir();
|
|
|
15
15
|
const PROVIDER_ROOT = path.join(HOME, '.routecodex', 'provider');
|
|
16
16
|
const SNAPSHOT_ROOT = path.join(HOME, '.routecodex', 'golden_samples');
|
|
17
17
|
const PROVIDER_GOLDEN_ROOT = path.join(SNAPSHOT_ROOT, 'provider_golden_samples');
|
|
18
|
-
const
|
|
18
|
+
const CI_GOLDENS_ROOT = path.join(ROOT, 'samples', 'ci-goldens');
|
|
19
|
+
const CUSTOM_SAMPLE_ROOTS = [
|
|
20
|
+
path.join(SNAPSHOT_ROOT, 'new'),
|
|
21
|
+
CI_GOLDENS_ROOT
|
|
22
|
+
];
|
|
19
23
|
const TEMP_ROOT = path.join(process.cwd(), 'tmp', 'provider-captures');
|
|
20
24
|
const STAGE_DIRS = {
|
|
21
25
|
'openai-chat': path.join(SNAPSHOT_ROOT, 'openai-chat'),
|
|
@@ -138,7 +142,7 @@ function ensureDir(p) {
|
|
|
138
142
|
function listProviderConfigs() {
|
|
139
143
|
const entries = [];
|
|
140
144
|
if (!fs.existsSync(PROVIDER_ROOT)) {
|
|
141
|
-
|
|
145
|
+
return entries;
|
|
142
146
|
}
|
|
143
147
|
for (const dir of fs.readdirSync(PROVIDER_ROOT)) {
|
|
144
148
|
const absDir = path.join(PROVIDER_ROOT, dir);
|
|
@@ -169,6 +173,39 @@ function listProviderConfigs() {
|
|
|
169
173
|
return entries;
|
|
170
174
|
}
|
|
171
175
|
|
|
176
|
+
function mapEntryTypeToProviderType(entryType) {
|
|
177
|
+
if (entryType === 'anthropic-messages') return 'anthropic-http-provider';
|
|
178
|
+
if (entryType === 'openai-responses') return 'responses-http-provider';
|
|
179
|
+
return 'openai-http-provider';
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function listCiGoldenProviders() {
|
|
183
|
+
const entries = [];
|
|
184
|
+
if (!fs.existsSync(CI_GOLDENS_ROOT)) {
|
|
185
|
+
return entries;
|
|
186
|
+
}
|
|
187
|
+
for (const entryType of fs.readdirSync(CI_GOLDENS_ROOT)) {
|
|
188
|
+
const entryDir = path.join(CI_GOLDENS_ROOT, entryType);
|
|
189
|
+
if (!fs.statSync(entryDir).isDirectory()) continue;
|
|
190
|
+
for (const providerId of fs.readdirSync(entryDir)) {
|
|
191
|
+
const providerDir = path.join(entryDir, providerId);
|
|
192
|
+
if (!fs.statSync(providerDir).isDirectory()) continue;
|
|
193
|
+
entries.push({
|
|
194
|
+
dir: 'ci-goldens',
|
|
195
|
+
configFile: path.join(providerDir, 'request.sample.json'),
|
|
196
|
+
providerId,
|
|
197
|
+
providerConfig: {
|
|
198
|
+
id: providerId,
|
|
199
|
+
type: mapEntryTypeToProviderType(entryType)
|
|
200
|
+
},
|
|
201
|
+
doc: null,
|
|
202
|
+
entryTypeOverride: entryType
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
return entries;
|
|
207
|
+
}
|
|
208
|
+
|
|
172
209
|
function detectEntryType(providerConfig) {
|
|
173
210
|
const type = String(providerConfig?.type || '').toLowerCase();
|
|
174
211
|
if (type.includes('anthropic')) return 'anthropic-messages';
|
|
@@ -309,24 +346,30 @@ function diffJson(expected, actual, prefix = '<root>') {
|
|
|
309
346
|
}
|
|
310
347
|
|
|
311
348
|
function loadCustomSample(providerId, entryType) {
|
|
312
|
-
const
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
try {
|
|
319
|
-
const meta = JSON.parse(fs.readFileSync(metaPath, 'utf-8'));
|
|
320
|
-
stageFile = meta?.stageFile;
|
|
321
|
-
} catch (error) {
|
|
322
|
-
console.warn(`[capture] custom meta parse failed for ${providerId}/${entryType}: ${error.message}`);
|
|
349
|
+
for (const root of CUSTOM_SAMPLE_ROOTS) {
|
|
350
|
+
if (!root) continue;
|
|
351
|
+
const dir = path.join(root, entryType, providerId);
|
|
352
|
+
const samplePath = path.join(dir, 'request.sample.json');
|
|
353
|
+
if (!fs.existsSync(samplePath)) {
|
|
354
|
+
continue;
|
|
323
355
|
}
|
|
356
|
+
let stageFile;
|
|
357
|
+
const metaPath = path.join(dir, 'meta.json');
|
|
358
|
+
if (fs.existsSync(metaPath)) {
|
|
359
|
+
try {
|
|
360
|
+
const meta = JSON.parse(fs.readFileSync(metaPath, 'utf-8'));
|
|
361
|
+
stageFile = meta?.stageFile || meta?.originStage || null;
|
|
362
|
+
} catch (error) {
|
|
363
|
+
console.warn(`[capture] custom meta parse failed for ${providerId}/${entryType}: ${error.message}`);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
return {
|
|
367
|
+
body: JSON.parse(fs.readFileSync(samplePath, 'utf-8')),
|
|
368
|
+
stageFile,
|
|
369
|
+
dir
|
|
370
|
+
};
|
|
324
371
|
}
|
|
325
|
-
return
|
|
326
|
-
body: JSON.parse(fs.readFileSync(samplePath, 'utf-8')),
|
|
327
|
-
stageFile,
|
|
328
|
-
dir
|
|
329
|
-
};
|
|
372
|
+
return null;
|
|
330
373
|
}
|
|
331
374
|
|
|
332
375
|
function saveProviderSample(providerId, entryType, sourceStagePath, providedBody, updateGolden) {
|
|
@@ -427,24 +470,26 @@ async function captureProvider(providerEntry, entryType, port, options, sanitize
|
|
|
427
470
|
async function main() {
|
|
428
471
|
const options = parseArgs();
|
|
429
472
|
ensureDir(TEMP_ROOT);
|
|
430
|
-
const providers =
|
|
473
|
+
const providers = [
|
|
474
|
+
...listProviderConfigs(),
|
|
475
|
+
...listCiGoldenProviders()
|
|
476
|
+
];
|
|
477
|
+
if (!providers.length) {
|
|
478
|
+
console.warn('[capture] no provider configs or ci goldens detected; exiting');
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
431
481
|
const captured = new Set();
|
|
432
482
|
const results = [];
|
|
433
483
|
let portBase = 5800;
|
|
434
484
|
|
|
435
485
|
for (const entry of providers) {
|
|
436
|
-
const entryType = detectEntryType(entry.providerConfig);
|
|
486
|
+
const entryType = entry.entryTypeOverride || detectEntryType(entry.providerConfig);
|
|
437
487
|
const entryDef = ENTRY_DEFS[entryType];
|
|
438
488
|
if (!entryDef) continue;
|
|
439
489
|
const captureKey = `${entry.providerId}:${entryType}`;
|
|
440
490
|
if (captured.has(captureKey)) {
|
|
441
491
|
continue;
|
|
442
492
|
}
|
|
443
|
-
const sampleExists = fs.existsSync(entryDef.samplePath);
|
|
444
|
-
if (!sampleExists) {
|
|
445
|
-
results.push({ provider: entry.providerId, entryType, status: 'skipped', reason: 'sample missing' });
|
|
446
|
-
continue;
|
|
447
|
-
}
|
|
448
493
|
const custom = loadCustomSample(entry.providerId, entryType);
|
|
449
494
|
if (custom) {
|
|
450
495
|
console.log(`[capture] using custom sample for ${entry.providerId} (${entryType}) from ${custom.dir}`);
|
|
@@ -459,6 +504,11 @@ async function main() {
|
|
|
459
504
|
});
|
|
460
505
|
continue;
|
|
461
506
|
}
|
|
507
|
+
const sampleExists = fs.existsSync(entryDef.samplePath);
|
|
508
|
+
if (!sampleExists) {
|
|
509
|
+
results.push({ provider: entry.providerId, entryType, status: 'skipped', reason: 'sample missing' });
|
|
510
|
+
continue;
|
|
511
|
+
}
|
|
462
512
|
if (options.customOnly) {
|
|
463
513
|
results.push({
|
|
464
514
|
provider: entry.providerId,
|
|
@@ -64,6 +64,22 @@ function resolveProviderRuntime(configPath, target) {
|
|
|
64
64
|
const auth = providerDef.auth || {};
|
|
65
65
|
const apiKey = auth.apiKey || auth.value || process.env.C4M_API_KEY || process.env.RCC_PROVIDER_KEY;
|
|
66
66
|
if (!apiKey) throw new Error(`Missing API key for ${providerId} (set in config or env)`);
|
|
67
|
+
const legacyCompatFields = [];
|
|
68
|
+
if (typeof providerDef.compatibility_profile === 'string') legacyCompatFields.push('compatibility_profile');
|
|
69
|
+
if (typeof providerDef.compat === 'string') legacyCompatFields.push('compat');
|
|
70
|
+
if (providerDef.compatibility && typeof providerDef.compatibility === 'object') {
|
|
71
|
+
if (typeof providerDef.compatibility.profile === 'string') legacyCompatFields.push('compatibility.profile');
|
|
72
|
+
if (typeof providerDef.compatibility.id === 'string') legacyCompatFields.push('compatibility.id');
|
|
73
|
+
}
|
|
74
|
+
if (legacyCompatFields.length > 0) {
|
|
75
|
+
throw new Error(
|
|
76
|
+
`Provider ${providerId} uses legacy compatibility field(s): ${legacyCompatFields.join(
|
|
77
|
+
', '
|
|
78
|
+
)}. Rename to "compatibilityProfile".`
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
const compatProfile =
|
|
82
|
+
(typeof providerDef.compatibilityProfile === 'string' && providerDef.compatibilityProfile.trim()) || undefined;
|
|
67
83
|
return {
|
|
68
84
|
runtimeKey: `${providerId}.golden.${Date.now()}`,
|
|
69
85
|
providerId,
|
|
@@ -72,7 +88,7 @@ function resolveProviderRuntime(configPath, target) {
|
|
|
72
88
|
providerType: (providerDef.type || 'responses').toLowerCase(),
|
|
73
89
|
endpoint: providerDef.baseURL || providerDef.baseUrl || providerDef.endpoint || 'https://api.example.net/v1',
|
|
74
90
|
auth: { type: 'apikey', value: apiKey },
|
|
75
|
-
compatibilityProfile:
|
|
91
|
+
compatibilityProfile: compatProfile,
|
|
76
92
|
outboundProfile: 'openai-responses',
|
|
77
93
|
defaultModel: modelId
|
|
78
94
|
};
|
|
@@ -209,6 +209,22 @@ function resolveProviderRuntime(configPath, target) {
|
|
|
209
209
|
const auth = providerDef.auth || {};
|
|
210
210
|
const apiKey = auth.apiKey || auth.value || process.env.C4M_API_KEY;
|
|
211
211
|
if (!apiKey) throw new Error(`Missing API key for ${providerId}. set in config or C4M_API_KEY env`);
|
|
212
|
+
const legacyCompatFields = [];
|
|
213
|
+
if (typeof providerDef.compatibility_profile === 'string') legacyCompatFields.push('compatibility_profile');
|
|
214
|
+
if (typeof providerDef.compat === 'string') legacyCompatFields.push('compat');
|
|
215
|
+
if (providerDef.compatibility && typeof providerDef.compatibility === 'object') {
|
|
216
|
+
if (typeof providerDef.compatibility.profile === 'string') legacyCompatFields.push('compatibility.profile');
|
|
217
|
+
if (typeof providerDef.compatibility.id === 'string') legacyCompatFields.push('compatibility.id');
|
|
218
|
+
}
|
|
219
|
+
if (legacyCompatFields.length > 0) {
|
|
220
|
+
throw new Error(
|
|
221
|
+
`Provider ${providerId} uses legacy compatibility field(s): ${legacyCompatFields.join(
|
|
222
|
+
', '
|
|
223
|
+
)}. Rename to "compatibilityProfile".`
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
const compatProfile =
|
|
227
|
+
(typeof providerDef.compatibilityProfile === 'string' && providerDef.compatibilityProfile.trim()) || undefined;
|
|
212
228
|
return {
|
|
213
229
|
runtime: {
|
|
214
230
|
runtimeKey: `${providerId}.replay.${Date.now()}`,
|
|
@@ -218,7 +234,7 @@ function resolveProviderRuntime(configPath, target) {
|
|
|
218
234
|
providerType: (providerDef.type || 'responses').toLowerCase(),
|
|
219
235
|
endpoint: providerDef.baseURL || providerDef.baseUrl || providerDef.endpoint || 'https://api.example.net/v1',
|
|
220
236
|
auth: { type: 'apikey', value: apiKey },
|
|
221
|
-
compatibilityProfile:
|
|
237
|
+
compatibilityProfile: compatProfile,
|
|
222
238
|
outboundProfile: 'openai-responses',
|
|
223
239
|
defaultModel: modelId
|
|
224
240
|
},
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import os from 'node:os';
|
|
6
|
+
import { fileURLToPath } from 'node:url';
|
|
7
|
+
|
|
8
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
9
|
+
const __dirname = path.dirname(__filename);
|
|
10
|
+
const PROJECT_ROOT = path.resolve(__dirname, '../..');
|
|
11
|
+
const TARGET_ROOT = path.join(PROJECT_ROOT, 'samples', 'ci-goldens');
|
|
12
|
+
const SOURCE_ROOT = path.join(os.homedir(), '.routecodex', 'golden_samples', 'new');
|
|
13
|
+
|
|
14
|
+
function ensureDir(dir) {
|
|
15
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function readJson(file) {
|
|
19
|
+
return JSON.parse(fs.readFileSync(file, 'utf-8'));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function writeIfChanged(target, content) {
|
|
23
|
+
const next = typeof content === 'string' ? content : JSON.stringify(content, null, 2);
|
|
24
|
+
if (fs.existsSync(target)) {
|
|
25
|
+
const prev = fs.readFileSync(target, 'utf-8');
|
|
26
|
+
if (prev === next) {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
fs.writeFileSync(target, next);
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function syncProvider(entryType, providerId) {
|
|
35
|
+
const sourceDir = path.join(SOURCE_ROOT, entryType, providerId);
|
|
36
|
+
const requestPath = path.join(sourceDir, 'request.sample.json');
|
|
37
|
+
if (!fs.existsSync(requestPath)) {
|
|
38
|
+
console.warn(`[sync-ci-goldens] skip ${entryType}/${providerId}: request.sample.json missing`);
|
|
39
|
+
return { status: 'skipped' };
|
|
40
|
+
}
|
|
41
|
+
const targetDir = path.join(TARGET_ROOT, entryType, providerId);
|
|
42
|
+
ensureDir(targetDir);
|
|
43
|
+
const targetRequest = path.join(targetDir, 'request.sample.json');
|
|
44
|
+
const changedRequest = writeIfChanged(targetRequest, fs.readFileSync(requestPath, 'utf-8'));
|
|
45
|
+
const sourceMetaPath = path.join(sourceDir, 'meta.json');
|
|
46
|
+
const targetMeta = path.join(targetDir, 'meta.json');
|
|
47
|
+
if (fs.existsSync(sourceMetaPath)) {
|
|
48
|
+
const meta = readJson(sourceMetaPath);
|
|
49
|
+
meta.source = 'ci-goldens';
|
|
50
|
+
writeIfChanged(targetMeta, meta);
|
|
51
|
+
} else if (!fs.existsSync(targetMeta)) {
|
|
52
|
+
const meta = {
|
|
53
|
+
providerId,
|
|
54
|
+
entryType,
|
|
55
|
+
capturedAt: new Date().toISOString(),
|
|
56
|
+
source: 'ci-goldens',
|
|
57
|
+
stageFile: null
|
|
58
|
+
};
|
|
59
|
+
writeIfChanged(targetMeta, meta);
|
|
60
|
+
}
|
|
61
|
+
return { status: changedRequest ? 'updated' : 'unchanged', targetDir };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function listEntries() {
|
|
65
|
+
if (!fs.existsSync(SOURCE_ROOT)) {
|
|
66
|
+
throw new Error(`Source golden samples missing: ${SOURCE_ROOT}`);
|
|
67
|
+
}
|
|
68
|
+
const entries = [];
|
|
69
|
+
for (const entryType of fs.readdirSync(SOURCE_ROOT)) {
|
|
70
|
+
const entryDir = path.join(SOURCE_ROOT, entryType);
|
|
71
|
+
if (!fs.statSync(entryDir).isDirectory()) continue;
|
|
72
|
+
for (const providerId of fs.readdirSync(entryDir)) {
|
|
73
|
+
const providerDir = path.join(entryDir, providerId);
|
|
74
|
+
if (!fs.statSync(providerDir).isDirectory()) continue;
|
|
75
|
+
entries.push({ entryType, providerId });
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return entries;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function usage() {
|
|
82
|
+
console.log('Usage: node scripts/tools/sync-ci-goldens.mjs [--entry <type>] [--provider <id>]');
|
|
83
|
+
process.exit(0);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function parseArgs() {
|
|
87
|
+
const args = process.argv.slice(2);
|
|
88
|
+
const filters = { entry: null, provider: null };
|
|
89
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
90
|
+
const arg = args[i];
|
|
91
|
+
if (arg === '--entry') {
|
|
92
|
+
filters.entry = args[++i] || null;
|
|
93
|
+
} else if (arg === '--provider') {
|
|
94
|
+
filters.provider = args[++i] || null;
|
|
95
|
+
} else if (arg === '--help' || arg === '-h') {
|
|
96
|
+
usage();
|
|
97
|
+
} else {
|
|
98
|
+
console.error(`Unknown argument: ${arg}`);
|
|
99
|
+
usage();
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return filters;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function main() {
|
|
106
|
+
const filters = parseArgs();
|
|
107
|
+
const entries = listEntries().filter((item) => {
|
|
108
|
+
if (filters.entry && filters.entry !== item.entryType) return false;
|
|
109
|
+
if (filters.provider && filters.provider !== item.providerId) return false;
|
|
110
|
+
return true;
|
|
111
|
+
});
|
|
112
|
+
if (!entries.length) {
|
|
113
|
+
console.warn('[sync-ci-goldens] no matching samples found');
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
let updated = 0;
|
|
117
|
+
let unchanged = 0;
|
|
118
|
+
for (const item of entries) {
|
|
119
|
+
const result = syncProvider(item.entryType, item.providerId);
|
|
120
|
+
if (result.status === 'updated') updated += 1;
|
|
121
|
+
else if (result.status === 'unchanged') unchanged += 1;
|
|
122
|
+
}
|
|
123
|
+
console.log(`[sync-ci-goldens] synced ${entries.length} provider samples (${updated} updated, ${unchanged} unchanged).`);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
try {
|
|
127
|
+
main();
|
|
128
|
+
} catch (error) {
|
|
129
|
+
console.error('[sync-ci-goldens] failed:', error.message || error);
|
|
130
|
+
process.exit(1);
|
|
131
|
+
}
|