@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
package/node_modules/@jsonstudio/llms/dist/conversion/shared/responses-conversation-store.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const TTL_MS = 1000 * 60 * 30; //
|
|
1
|
+
const TTL_MS = 1000 * 60 * 30; // 30min
|
|
2
2
|
function cloneDeep(value) {
|
|
3
3
|
try {
|
|
4
4
|
if (typeof globalThis.structuredClone === 'function') {
|
|
@@ -49,19 +49,26 @@ function pickPersistedFields(payload) {
|
|
|
49
49
|
'input_audio',
|
|
50
50
|
'output_audio'
|
|
51
51
|
];
|
|
52
|
-
const
|
|
52
|
+
const next = {};
|
|
53
53
|
for (const key of fields) {
|
|
54
54
|
if (payload[key] !== undefined) {
|
|
55
|
-
|
|
55
|
+
next[key] = cloneDeep(payload[key]);
|
|
56
56
|
}
|
|
57
57
|
}
|
|
58
|
-
return
|
|
58
|
+
return next;
|
|
59
59
|
}
|
|
60
60
|
function normalizeOutputItemToInput(item) {
|
|
61
|
+
if (!item || typeof item !== 'object') {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
61
64
|
const type = typeof item.type === 'string' ? item.type : '';
|
|
62
65
|
if (type === 'message' || type === 'reasoning') {
|
|
63
66
|
const role = typeof item.role === 'string' ? item.role : 'assistant';
|
|
64
|
-
const content = Array.isArray(item.content)
|
|
67
|
+
const content = Array.isArray(item.content)
|
|
68
|
+
? cloneDeep(item.content)
|
|
69
|
+
: typeof item.text === 'string'
|
|
70
|
+
? [{ type: 'text', text: item.text }]
|
|
71
|
+
: [];
|
|
65
72
|
return {
|
|
66
73
|
type: 'message',
|
|
67
74
|
role,
|
|
@@ -70,7 +77,11 @@ function normalizeOutputItemToInput(item) {
|
|
|
70
77
|
};
|
|
71
78
|
}
|
|
72
79
|
if (type === 'function_call') {
|
|
73
|
-
const callId = typeof item.call_id === 'string'
|
|
80
|
+
const callId = typeof item.call_id === 'string'
|
|
81
|
+
? item.call_id
|
|
82
|
+
: typeof item.id === 'string'
|
|
83
|
+
? item.id
|
|
84
|
+
: undefined;
|
|
74
85
|
return {
|
|
75
86
|
type: 'function_call',
|
|
76
87
|
role: 'assistant',
|
|
@@ -80,9 +91,9 @@ function normalizeOutputItemToInput(item) {
|
|
|
80
91
|
arguments: item.arguments,
|
|
81
92
|
function: isRecord(item.function)
|
|
82
93
|
? cloneDeep(item.function)
|
|
83
|
-
:
|
|
94
|
+
: typeof item.name === 'string'
|
|
84
95
|
? { name: item.name, arguments: item.arguments }
|
|
85
|
-
: undefined
|
|
96
|
+
: undefined
|
|
86
97
|
};
|
|
87
98
|
}
|
|
88
99
|
return null;
|
|
@@ -139,6 +150,18 @@ function normalizeSubmittedToolOutputs(toolOutputs) {
|
|
|
139
150
|
class ResponsesConversationStore {
|
|
140
151
|
requestMap = new Map();
|
|
141
152
|
responseIndex = new Map();
|
|
153
|
+
rebindRequestId(oldId, newId) {
|
|
154
|
+
if (!oldId || !newId || oldId === newId) {
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
const entry = this.requestMap.get(oldId);
|
|
158
|
+
if (!entry) {
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
this.requestMap.delete(oldId);
|
|
162
|
+
entry.requestId = newId;
|
|
163
|
+
this.requestMap.set(newId, entry);
|
|
164
|
+
}
|
|
142
165
|
captureRequestContext(args) {
|
|
143
166
|
const { requestId, payload, context } = args;
|
|
144
167
|
if (!requestId || !payload)
|
|
@@ -148,7 +171,8 @@ class ResponsesConversationStore {
|
|
|
148
171
|
requestId,
|
|
149
172
|
basePayload: pickPersistedFields(payload),
|
|
150
173
|
input: coerceInputArray(context.input),
|
|
151
|
-
tools: coerceTools(context.toolsRaw ||
|
|
174
|
+
tools: coerceTools(context.toolsRaw) ||
|
|
175
|
+
coerceTools(Array.isArray(payload.tools) ? payload.tools : undefined),
|
|
152
176
|
createdAt: Date.now(),
|
|
153
177
|
updatedAt: Date.now()
|
|
154
178
|
};
|
|
@@ -164,7 +188,7 @@ class ResponsesConversationStore {
|
|
|
164
188
|
this.requestMap.set(requestId, entry);
|
|
165
189
|
}
|
|
166
190
|
recordResponse(args) {
|
|
167
|
-
const entry = this.requestMap.get(args.requestId);
|
|
191
|
+
const entry = args.requestId ? this.requestMap.get(args.requestId) : undefined;
|
|
168
192
|
if (!entry)
|
|
169
193
|
return;
|
|
170
194
|
const response = args.response;
|
|
@@ -188,9 +212,7 @@ class ResponsesConversationStore {
|
|
|
188
212
|
if (!entry) {
|
|
189
213
|
throw new Error('Responses conversation expired or not found');
|
|
190
214
|
}
|
|
191
|
-
const toolOutputs = Array.isArray(submitPayload.tool_outputs)
|
|
192
|
-
? submitPayload.tool_outputs
|
|
193
|
-
: [];
|
|
215
|
+
const toolOutputs = Array.isArray(submitPayload.tool_outputs) ? submitPayload.tool_outputs : [];
|
|
194
216
|
if (!toolOutputs.length) {
|
|
195
217
|
throw new Error('tool_outputs array is required when submitting Responses tool results');
|
|
196
218
|
}
|
|
@@ -208,7 +230,8 @@ class ResponsesConversationStore {
|
|
|
208
230
|
payload.model = submitPayload.model.trim();
|
|
209
231
|
}
|
|
210
232
|
if (submitPayload.metadata && isRecord(submitPayload.metadata)) {
|
|
211
|
-
|
|
233
|
+
const baseMeta = isRecord(payload.metadata) ? payload.metadata : {};
|
|
234
|
+
payload.metadata = { ...baseMeta, ...cloneDeep(submitPayload.metadata) };
|
|
212
235
|
}
|
|
213
236
|
delete payload.tool_outputs;
|
|
214
237
|
delete payload.response_id;
|
|
@@ -225,6 +248,8 @@ class ResponsesConversationStore {
|
|
|
225
248
|
};
|
|
226
249
|
}
|
|
227
250
|
clearRequest(requestId) {
|
|
251
|
+
if (!requestId)
|
|
252
|
+
return;
|
|
228
253
|
const entry = this.requestMap.get(requestId);
|
|
229
254
|
if (!entry)
|
|
230
255
|
return;
|
|
@@ -254,10 +279,14 @@ class ResponsesConversationStore {
|
|
|
254
279
|
}
|
|
255
280
|
}
|
|
256
281
|
}
|
|
257
|
-
|
|
282
|
+
const store = new ResponsesConversationStore();
|
|
283
|
+
const RESPONSES_DEBUG = (process.env.ROUTECODEX_RESPONSES_DEBUG || '').trim() === '1';
|
|
258
284
|
export function captureResponsesRequestContext(args) {
|
|
259
285
|
try {
|
|
260
|
-
|
|
286
|
+
if (RESPONSES_DEBUG) {
|
|
287
|
+
console.log('[responses-store] capture', args.requestId);
|
|
288
|
+
}
|
|
289
|
+
store.captureRequestContext(args);
|
|
261
290
|
}
|
|
262
291
|
catch {
|
|
263
292
|
/* ignore capture failures */
|
|
@@ -265,15 +294,31 @@ export function captureResponsesRequestContext(args) {
|
|
|
265
294
|
}
|
|
266
295
|
export function recordResponsesResponse(args) {
|
|
267
296
|
try {
|
|
268
|
-
|
|
297
|
+
if (RESPONSES_DEBUG) {
|
|
298
|
+
console.log('[responses-store] record', args.requestId, args.response?.id);
|
|
299
|
+
}
|
|
300
|
+
store.recordResponse(args);
|
|
269
301
|
}
|
|
270
302
|
catch {
|
|
271
303
|
/* ignore */
|
|
272
304
|
}
|
|
273
305
|
}
|
|
274
306
|
export function resumeResponsesConversation(responseId, submitPayload, options) {
|
|
275
|
-
|
|
307
|
+
if (RESPONSES_DEBUG) {
|
|
308
|
+
console.log('[responses-store] resume', responseId);
|
|
309
|
+
}
|
|
310
|
+
return store.resumeConversation(responseId, submitPayload, options);
|
|
276
311
|
}
|
|
277
312
|
export function clearResponsesConversationByRequestId(requestId) {
|
|
278
|
-
|
|
313
|
+
if (RESPONSES_DEBUG && requestId) {
|
|
314
|
+
console.log('[responses-store] clear', requestId);
|
|
315
|
+
}
|
|
316
|
+
store.clearRequest(requestId);
|
|
317
|
+
}
|
|
318
|
+
export function rebindResponsesConversationRequestId(oldId, newId) {
|
|
319
|
+
if (RESPONSES_DEBUG && oldId && newId) {
|
|
320
|
+
console.log('[responses-store] rebind', oldId, '->', newId);
|
|
321
|
+
}
|
|
322
|
+
store.rebindRequestId(oldId, newId);
|
|
279
323
|
}
|
|
324
|
+
export { store as responsesConversationStore };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { ToolFilterHints } from '../../filters/index.js';
|
|
2
|
+
interface RequestFilterOptions {
|
|
3
|
+
entryEndpoint?: string;
|
|
4
|
+
requestId?: string;
|
|
5
|
+
model?: string;
|
|
6
|
+
profile?: string;
|
|
7
|
+
stream?: boolean;
|
|
8
|
+
toolFilterHints?: ToolFilterHints;
|
|
9
|
+
/**
|
|
10
|
+
* Optional raw payload snapshot for local tool governance (e.g. view_image exposure).
|
|
11
|
+
*/
|
|
12
|
+
rawPayload?: Record<string, unknown>;
|
|
13
|
+
}
|
|
14
|
+
interface ResponseFilterOptions {
|
|
15
|
+
entryEndpoint?: string;
|
|
16
|
+
requestId?: string;
|
|
17
|
+
profile?: string;
|
|
18
|
+
}
|
|
19
|
+
export declare function runChatRequestToolFilters(chatRequest: any, options?: RequestFilterOptions): Promise<any>;
|
|
20
|
+
export declare function runChatResponseToolFilters(chatJson: any, options?: ResponseFilterOptions): Promise<any>;
|
|
21
|
+
export {};
|
|
@@ -1,6 +1,30 @@
|
|
|
1
1
|
import { FilterEngine } from '../../filters/index.js';
|
|
2
2
|
import { loadFieldMapConfig } from '../../filters/utils/fieldmap-loader.js';
|
|
3
3
|
import { createSnapshotWriter } from './snapshot-utils.js';
|
|
4
|
+
const REQUEST_FILTER_STAGES = [
|
|
5
|
+
'request_pre',
|
|
6
|
+
'request_map',
|
|
7
|
+
'request_post',
|
|
8
|
+
'request_finalize'
|
|
9
|
+
];
|
|
10
|
+
const RESPONSE_FILTER_STAGES = [
|
|
11
|
+
'response_pre',
|
|
12
|
+
'response_map',
|
|
13
|
+
'response_post',
|
|
14
|
+
'response_finalize'
|
|
15
|
+
];
|
|
16
|
+
function assertStageCoverage(label, registeredStages, skeletonStages) {
|
|
17
|
+
const allowed = new Set(skeletonStages);
|
|
18
|
+
const uncovered = [];
|
|
19
|
+
for (const stage of registeredStages) {
|
|
20
|
+
if (!allowed.has(stage)) {
|
|
21
|
+
uncovered.push(stage);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
if (uncovered.length) {
|
|
25
|
+
throw new Error(`[tool-filter-pipeline] ${label}: registered filter stage(s) not covered by skeleton: ${uncovered.join(', ')}`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
4
28
|
export async function runChatRequestToolFilters(chatRequest, options = {}) {
|
|
5
29
|
const reqCtxBase = {
|
|
6
30
|
requestId: options.requestId ?? `req_${Date.now()}`,
|
|
@@ -21,24 +45,30 @@ export async function runChatRequestToolFilters(chatRequest, options = {}) {
|
|
|
21
45
|
return;
|
|
22
46
|
snapshot(stage, payload);
|
|
23
47
|
};
|
|
24
|
-
|
|
48
|
+
const preFiltered = applyLocalToolGovernance(chatRequest, options.rawPayload);
|
|
49
|
+
recordStage('req_process_tool_filters_input', preFiltered);
|
|
25
50
|
const engine = new FilterEngine();
|
|
51
|
+
const registeredStages = new Set();
|
|
52
|
+
const register = (filter) => {
|
|
53
|
+
registeredStages.add(filter.stage);
|
|
54
|
+
engine.registerFilter(filter);
|
|
55
|
+
};
|
|
26
56
|
const profile = (reqCtxBase.profile || '').toLowerCase();
|
|
27
57
|
const endpoint = (reqCtxBase.endpoint || '').toLowerCase();
|
|
28
58
|
const isAnthropic = profile === 'anthropic-messages' || endpoint.includes('/v1/messages');
|
|
29
59
|
if (!isAnthropic) {
|
|
30
60
|
try {
|
|
31
61
|
const { RequestToolListFilter } = await import('../../filters/index.js');
|
|
32
|
-
|
|
62
|
+
register(new RequestToolListFilter());
|
|
33
63
|
}
|
|
34
64
|
catch {
|
|
35
65
|
/* optional */
|
|
36
66
|
}
|
|
37
67
|
}
|
|
38
68
|
const { RequestToolCallsStringifyFilter, RequestToolChoicePolicyFilter } = await import('../../filters/index.js');
|
|
39
|
-
|
|
69
|
+
register(new RequestToolCallsStringifyFilter());
|
|
40
70
|
if (!isAnthropic) {
|
|
41
|
-
|
|
71
|
+
register(new RequestToolChoicePolicyFilter());
|
|
42
72
|
}
|
|
43
73
|
try {
|
|
44
74
|
const cfg = await loadFieldMapConfig('openai-openai.fieldmap.json');
|
|
@@ -52,15 +82,96 @@ export async function runChatRequestToolFilters(chatRequest, options = {}) {
|
|
|
52
82
|
} })());
|
|
53
83
|
}
|
|
54
84
|
catch { /* ignore */ }
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
85
|
+
try {
|
|
86
|
+
const { RequestOpenAIToolsNormalizeFilter, ToolPostConstraintsFilter } = await import('../../filters/index.js');
|
|
87
|
+
register(new RequestOpenAIToolsNormalizeFilter());
|
|
88
|
+
register(new ToolPostConstraintsFilter('request_finalize'));
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
// optional; keep prior behavior when filter not available
|
|
92
|
+
}
|
|
93
|
+
assertStageCoverage('request', registeredStages, REQUEST_FILTER_STAGES);
|
|
94
|
+
let staged = preFiltered;
|
|
95
|
+
for (const stage of REQUEST_FILTER_STAGES) {
|
|
96
|
+
staged = await engine.run(stage, staged, reqCtxBase);
|
|
97
|
+
recordStage(`req_process_tool_filters_${stage}`, staged);
|
|
98
|
+
}
|
|
61
99
|
recordStage('req_process_tool_filters_output', staged);
|
|
62
100
|
return staged;
|
|
63
101
|
}
|
|
102
|
+
function applyLocalToolGovernance(chatRequest, rawPayload) {
|
|
103
|
+
if (!chatRequest || typeof chatRequest !== 'object') {
|
|
104
|
+
return chatRequest;
|
|
105
|
+
}
|
|
106
|
+
const messages = Array.isArray(chatRequest.messages) ? chatRequest.messages : undefined;
|
|
107
|
+
const tools = Array.isArray(chatRequest.tools) ? chatRequest.tools : undefined;
|
|
108
|
+
if (!tools || !tools.length) {
|
|
109
|
+
return chatRequest;
|
|
110
|
+
}
|
|
111
|
+
const hasImageHint = detectImageHint(messages, rawPayload);
|
|
112
|
+
if (hasImageHint) {
|
|
113
|
+
return chatRequest;
|
|
114
|
+
}
|
|
115
|
+
const filteredTools = tools.filter((tool) => {
|
|
116
|
+
if (!tool || typeof tool !== 'object')
|
|
117
|
+
return false;
|
|
118
|
+
const fn = tool.function;
|
|
119
|
+
if (!fn || typeof fn !== 'object')
|
|
120
|
+
return true;
|
|
121
|
+
const name = fn.name;
|
|
122
|
+
if (typeof name !== 'string')
|
|
123
|
+
return true;
|
|
124
|
+
return name.trim() !== 'view_image';
|
|
125
|
+
});
|
|
126
|
+
if (filteredTools.length === tools.length) {
|
|
127
|
+
return chatRequest;
|
|
128
|
+
}
|
|
129
|
+
return {
|
|
130
|
+
...chatRequest,
|
|
131
|
+
tools: filteredTools
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
function detectImageHint(messages, rawPayload) {
|
|
135
|
+
const candidates = [];
|
|
136
|
+
const collect = (value) => {
|
|
137
|
+
if (typeof value === 'string' && value) {
|
|
138
|
+
candidates.push(value);
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
if (Array.isArray(messages)) {
|
|
142
|
+
for (const msg of messages) {
|
|
143
|
+
if (msg && typeof msg === 'object') {
|
|
144
|
+
const text = msg.content;
|
|
145
|
+
if (typeof text === 'string') {
|
|
146
|
+
collect(text);
|
|
147
|
+
}
|
|
148
|
+
else if (Array.isArray(text)) {
|
|
149
|
+
for (const part of text) {
|
|
150
|
+
if (part && typeof part === 'object') {
|
|
151
|
+
collect(part.text);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
if (rawPayload && typeof rawPayload === 'object') {
|
|
159
|
+
collect(rawPayload.content);
|
|
160
|
+
}
|
|
161
|
+
if (!candidates.length) {
|
|
162
|
+
return false;
|
|
163
|
+
}
|
|
164
|
+
const patterns = ['.png', '.jpg', '.jpeg', '.gif', '.webp', '.bmp', '.svg'];
|
|
165
|
+
for (const text of candidates) {
|
|
166
|
+
const lower = text.toLowerCase();
|
|
167
|
+
for (const ext of patterns) {
|
|
168
|
+
if (lower.includes(ext)) {
|
|
169
|
+
return true;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return false;
|
|
174
|
+
}
|
|
64
175
|
export async function runChatResponseToolFilters(chatJson, options = {}) {
|
|
65
176
|
const resCtxBase = {
|
|
66
177
|
requestId: options.requestId ?? `req_${Date.now()}`,
|
|
@@ -81,20 +192,25 @@ export async function runChatResponseToolFilters(chatJson, options = {}) {
|
|
|
81
192
|
};
|
|
82
193
|
recordStage('resp_process_tool_filters_input', chatJson);
|
|
83
194
|
const engine = new FilterEngine();
|
|
195
|
+
const registeredStages = new Set();
|
|
196
|
+
const register = (filter) => {
|
|
197
|
+
registeredStages.add(filter.stage);
|
|
198
|
+
engine.registerFilter(filter);
|
|
199
|
+
};
|
|
84
200
|
const { ResponseToolTextCanonicalizeFilter, ResponseToolArgumentsStringifyFilter, ResponseFinishInvariantsFilter } = await import('../../filters/index.js');
|
|
85
|
-
|
|
201
|
+
register(new ResponseToolTextCanonicalizeFilter());
|
|
86
202
|
try {
|
|
87
203
|
const { ResponseToolArgumentsToonDecodeFilter, ResponseToolArgumentsBlacklistFilter, ResponseToolArgumentsSchemaConvergeFilter } = await import('../../filters/index.js');
|
|
88
|
-
|
|
204
|
+
register(new ResponseToolArgumentsToonDecodeFilter());
|
|
89
205
|
try {
|
|
90
|
-
|
|
206
|
+
register(new ResponseToolArgumentsSchemaConvergeFilter());
|
|
91
207
|
}
|
|
92
208
|
catch { /* optional */ }
|
|
93
|
-
|
|
209
|
+
register(new ResponseToolArgumentsBlacklistFilter());
|
|
94
210
|
}
|
|
95
211
|
catch { /* optional */ }
|
|
96
|
-
|
|
97
|
-
|
|
212
|
+
register(new ResponseToolArgumentsStringifyFilter());
|
|
213
|
+
register(new ResponseFinishInvariantsFilter());
|
|
98
214
|
try {
|
|
99
215
|
const cfg = await loadFieldMapConfig('openai-openai.fieldmap.json');
|
|
100
216
|
if (cfg)
|
|
@@ -107,12 +223,12 @@ export async function runChatResponseToolFilters(chatJson, options = {}) {
|
|
|
107
223
|
} })());
|
|
108
224
|
}
|
|
109
225
|
catch { /* ignore */ }
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
226
|
+
assertStageCoverage('response', registeredStages, RESPONSE_FILTER_STAGES);
|
|
227
|
+
let staged = chatJson;
|
|
228
|
+
for (const stage of RESPONSE_FILTER_STAGES) {
|
|
229
|
+
staged = await engine.run(stage, staged, resCtxBase);
|
|
230
|
+
recordStage(`resp_process_tool_filters_${stage}`, staged);
|
|
231
|
+
}
|
|
116
232
|
recordStage('resp_process_tool_filters_output', staged);
|
|
117
233
|
return staged;
|
|
118
234
|
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
type Unknown = Record<string, unknown>;
|
|
2
|
+
export interface ToolGovernanceOptions {
|
|
3
|
+
injectGuidance?: boolean;
|
|
4
|
+
snapshot?: {
|
|
5
|
+
enabled?: boolean;
|
|
6
|
+
endpoint?: string;
|
|
7
|
+
requestId?: string;
|
|
8
|
+
baseDir?: string;
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
export declare function processChatRequestTools(request: Unknown, opts?: ToolGovernanceOptions): Unknown;
|
|
12
|
+
export declare function processChatResponseTools(resp: Unknown): Unknown;
|
|
13
|
+
export interface GovernContext extends ToolGovernanceOptions {
|
|
14
|
+
phase: 'request' | 'response';
|
|
15
|
+
endpoint?: 'chat' | 'responses' | 'messages';
|
|
16
|
+
stream?: boolean;
|
|
17
|
+
produceRequiredAction?: boolean;
|
|
18
|
+
requestId?: string;
|
|
19
|
+
}
|
|
20
|
+
export declare function governTools(payload: Unknown, ctx: GovernContext): Unknown;
|
|
21
|
+
export {};
|
|
@@ -40,6 +40,114 @@ function tryWriteSnapshot(options, stage, data) {
|
|
|
40
40
|
* - Inject/Refine system tool guidance (idempotent)
|
|
41
41
|
* - Canonicalize textual tool markup to tool_calls; set content=null when applicable
|
|
42
42
|
*/
|
|
43
|
+
const IMAGE_EXT_RE = /\.(png|jpe?g|gif|webp|bmp|svg)(?:[?#].*)?$/i;
|
|
44
|
+
function hasImageReference(messages) {
|
|
45
|
+
if (!Array.isArray(messages))
|
|
46
|
+
return false;
|
|
47
|
+
for (const entry of messages) {
|
|
48
|
+
if (!entry || typeof entry !== 'object')
|
|
49
|
+
continue;
|
|
50
|
+
const content = entry.content;
|
|
51
|
+
if (!content)
|
|
52
|
+
continue;
|
|
53
|
+
if (Array.isArray(content)) {
|
|
54
|
+
if (content.some((part) => isImagePart(part)))
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
else if (isObject(content)) {
|
|
58
|
+
if (isImagePart(content))
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
else if (typeof content === 'string') {
|
|
62
|
+
if (stringHasImageLink(content))
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
function hasInputImage(entries) {
|
|
69
|
+
if (!Array.isArray(entries))
|
|
70
|
+
return false;
|
|
71
|
+
for (const entry of entries) {
|
|
72
|
+
if (!entry || typeof entry !== 'object')
|
|
73
|
+
continue;
|
|
74
|
+
const type = String(entry.type || '').toLowerCase();
|
|
75
|
+
if (type.includes('image'))
|
|
76
|
+
return true;
|
|
77
|
+
const content = entry.content;
|
|
78
|
+
if (!content)
|
|
79
|
+
continue;
|
|
80
|
+
if (Array.isArray(content)) {
|
|
81
|
+
if (content.some((part) => isImagePart(part)))
|
|
82
|
+
return true;
|
|
83
|
+
}
|
|
84
|
+
else if (isObject(content)) {
|
|
85
|
+
if (isImagePart(content))
|
|
86
|
+
return true;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
function attachmentsHaveImage(payload) {
|
|
92
|
+
const attachments = payload?.attachments;
|
|
93
|
+
if (!Array.isArray(attachments))
|
|
94
|
+
return false;
|
|
95
|
+
for (const attachment of attachments) {
|
|
96
|
+
if (!attachment || typeof attachment !== 'object')
|
|
97
|
+
continue;
|
|
98
|
+
const mime = typeof attachment.mime === 'string' ? attachment.mime.toLowerCase() : '';
|
|
99
|
+
if (mime.startsWith('image/'))
|
|
100
|
+
return true;
|
|
101
|
+
const name = typeof attachment.name === 'string' ? attachment.name : '';
|
|
102
|
+
if (IMAGE_EXT_RE.test(name))
|
|
103
|
+
return true;
|
|
104
|
+
const url = typeof attachment.url === 'string' ? attachment.url : '';
|
|
105
|
+
if (stringHasImageLink(url))
|
|
106
|
+
return true;
|
|
107
|
+
}
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
function stringHasImageLink(value) {
|
|
111
|
+
if (!value)
|
|
112
|
+
return false;
|
|
113
|
+
if (value.includes('cid:'))
|
|
114
|
+
return true;
|
|
115
|
+
if (IMAGE_EXT_RE.test(value))
|
|
116
|
+
return true;
|
|
117
|
+
const lowered = value.toLowerCase();
|
|
118
|
+
return lowered.includes('image://');
|
|
119
|
+
}
|
|
120
|
+
function isImagePart(part) {
|
|
121
|
+
if (!part || typeof part !== 'object')
|
|
122
|
+
return false;
|
|
123
|
+
const type = String(part.type || '').toLowerCase();
|
|
124
|
+
if (type.includes('image'))
|
|
125
|
+
return true;
|
|
126
|
+
const imageUrl = part.image_url || part.imageUrl;
|
|
127
|
+
if (typeof imageUrl === 'string')
|
|
128
|
+
return true;
|
|
129
|
+
if (isObject(imageUrl) && typeof imageUrl.url === 'string')
|
|
130
|
+
return true;
|
|
131
|
+
const url = part.url;
|
|
132
|
+
if (typeof url === 'string' && stringHasImageLink(url))
|
|
133
|
+
return true;
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
function shouldExposeViewImage(payload) {
|
|
137
|
+
if (hasImageReference(payload?.messages))
|
|
138
|
+
return true;
|
|
139
|
+
if (hasInputImage(payload?.input))
|
|
140
|
+
return true;
|
|
141
|
+
if (attachmentsHaveImage(payload))
|
|
142
|
+
return true;
|
|
143
|
+
return false;
|
|
144
|
+
}
|
|
145
|
+
function isViewImageTool(tool) {
|
|
146
|
+
if (!tool || typeof tool !== 'object')
|
|
147
|
+
return false;
|
|
148
|
+
const name = String(tool.name || tool?.function?.name || '').toLowerCase();
|
|
149
|
+
return name === 'view_image';
|
|
150
|
+
}
|
|
43
151
|
export function processChatRequestTools(request, opts) {
|
|
44
152
|
const options = { ...(opts || {}) };
|
|
45
153
|
if (!isObject(request))
|
|
@@ -48,8 +156,15 @@ export function processChatRequestTools(request, opts) {
|
|
|
48
156
|
// tools 形状最小修复:为缺失 function.parameters 的工具补一个空对象,避免上游
|
|
49
157
|
// Responses/OpenAI 校验 422(外部错误必须暴露,但这里属于规范化入口)。
|
|
50
158
|
try {
|
|
51
|
-
|
|
159
|
+
let tools = Array.isArray(out?.tools) ? out.tools : [];
|
|
52
160
|
if (tools.length > 0) {
|
|
161
|
+
if (!shouldExposeViewImage(out)) {
|
|
162
|
+
const filtered = tools.filter((tool) => !isViewImageTool(tool));
|
|
163
|
+
if (filtered.length !== tools.length) {
|
|
164
|
+
tools = filtered;
|
|
165
|
+
out.tools = tools;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
53
168
|
for (const t of tools) {
|
|
54
169
|
if (!t || typeof t !== 'object')
|
|
55
170
|
continue;
|
|
@@ -8,6 +8,56 @@ export function stringifyArgs(args) {
|
|
|
8
8
|
return String(args);
|
|
9
9
|
}
|
|
10
10
|
}
|
|
11
|
+
function isPlainObject(value) {
|
|
12
|
+
return !!value && typeof value === 'object' && !Array.isArray(value);
|
|
13
|
+
}
|
|
14
|
+
function clonePlainObject(value) {
|
|
15
|
+
try {
|
|
16
|
+
return JSON.parse(JSON.stringify(value));
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return { ...value };
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function asSchema(value) {
|
|
23
|
+
if (isPlainObject(value)) {
|
|
24
|
+
return clonePlainObject(value);
|
|
25
|
+
}
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
28
|
+
function ensureApplyPatchSchema(seed) {
|
|
29
|
+
const schema = seed ? { ...seed } : {};
|
|
30
|
+
schema.type = typeof schema.type === 'string' ? schema.type : 'object';
|
|
31
|
+
const properties = isPlainObject(schema.properties) ? { ...schema.properties } : {};
|
|
32
|
+
properties.input = {
|
|
33
|
+
type: 'string',
|
|
34
|
+
description: 'Unified diff patch body between *** Begin Patch/*** End Patch.'
|
|
35
|
+
};
|
|
36
|
+
if (!properties.patch || typeof properties.patch !== 'object') {
|
|
37
|
+
properties.patch = {
|
|
38
|
+
type: 'string',
|
|
39
|
+
description: 'Alias of input for backwards compatibility.'
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
schema.properties = properties;
|
|
43
|
+
const requiredList = Array.isArray(schema.required) ? schema.required.filter((entry) => typeof entry === 'string') : [];
|
|
44
|
+
if (!requiredList.includes('input')) {
|
|
45
|
+
requiredList.push('input');
|
|
46
|
+
}
|
|
47
|
+
schema.required = requiredList;
|
|
48
|
+
if (typeof schema.additionalProperties !== 'boolean') {
|
|
49
|
+
schema.additionalProperties = false;
|
|
50
|
+
}
|
|
51
|
+
return schema;
|
|
52
|
+
}
|
|
53
|
+
function enforceBuiltinToolSchema(name, candidate) {
|
|
54
|
+
const normalizedName = typeof name === 'string' ? name.trim().toLowerCase() : '';
|
|
55
|
+
if (normalizedName === 'apply_patch') {
|
|
56
|
+
const base = asSchema(candidate);
|
|
57
|
+
return ensureApplyPatchSchema(base);
|
|
58
|
+
}
|
|
59
|
+
return asSchema(candidate);
|
|
60
|
+
}
|
|
11
61
|
const DEFAULT_SANITIZER = (value) => {
|
|
12
62
|
if (typeof value === 'string') {
|
|
13
63
|
const trimmed = value.trim();
|
|
@@ -66,7 +116,7 @@ export function bridgeToolToChatDefinition(rawTool, options) {
|
|
|
66
116
|
return null;
|
|
67
117
|
}
|
|
68
118
|
const description = resolveToolDescription(fnNode?.description ?? tool.description);
|
|
69
|
-
const parameters = resolveToolParameters(fnNode, tool);
|
|
119
|
+
const parameters = enforceBuiltinToolSchema(name, resolveToolParameters(fnNode, tool));
|
|
70
120
|
const strict = resolveToolStrict(fnNode, tool);
|
|
71
121
|
const rawType = typeof tool.type === 'string' && tool.type.trim().length ? tool.type.trim() : 'function';
|
|
72
122
|
const normalizedType = rawType.toLowerCase() === 'custom' ? 'function' : rawType;
|
|
@@ -105,7 +155,7 @@ export function chatToolToBridgeDefinition(rawTool, options) {
|
|
|
105
155
|
return null;
|
|
106
156
|
}
|
|
107
157
|
const description = resolveToolDescription(fnNode?.description);
|
|
108
|
-
const parameters = resolveToolParameters(fnNode, undefined);
|
|
158
|
+
const parameters = enforceBuiltinToolSchema(name, resolveToolParameters(fnNode, undefined));
|
|
109
159
|
const strict = resolveToolStrict(fnNode, undefined);
|
|
110
160
|
const normalizedType = typeof tool.type === 'string' && tool.type.trim().length ? tool.type.trim() : 'function';
|
|
111
161
|
const responseShape = {
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"request": [
|
|
3
|
+
{
|
|
4
|
+
"sourcePath": "messages[*].tool_calls[*].function.arguments",
|
|
5
|
+
"targetPath": "messages[*].tool_calls[*].function.arguments",
|
|
6
|
+
"type": "string",
|
|
7
|
+
"transform": "stringifyJson"
|
|
8
|
+
}
|
|
9
|
+
],
|
|
10
|
+
"response": [
|
|
11
|
+
{
|
|
12
|
+
"sourcePath": "choices[*].message.tool_calls[*].function.arguments",
|
|
13
|
+
"targetPath": "choices[*].message.tool_calls[*].function.arguments",
|
|
14
|
+
"type": "string",
|
|
15
|
+
"transform": "stringifyJson"
|
|
16
|
+
}
|
|
17
|
+
]
|
|
18
|
+
}
|