@jsonstudio/llms 0.6.1164 → 0.6.1354
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/dist/conversion/codecs/gemini-openai-codec.d.ts +3 -1
- package/dist/conversion/codecs/gemini-openai-codec.js +10 -4
- package/dist/conversion/compat/actions/gemini-web-search.d.ts +1 -1
- package/dist/conversion/compat/actions/gemini-web-search.js +5 -2
- package/dist/conversion/compat/actions/iflow-tool-text-fallback.d.ts +12 -0
- package/dist/conversion/compat/actions/iflow-tool-text-fallback.js +199 -0
- package/dist/conversion/compat/actions/iflow-web-search.d.ts +1 -1
- package/dist/conversion/compat/actions/iflow-web-search.js +5 -2
- package/dist/conversion/hub/operation-table/semantic-mappers/anthropic-mapper.js +47 -56
- package/dist/conversion/hub/operation-table/semantic-mappers/chat-mapper.js +1 -13
- package/dist/conversion/hub/operation-table/semantic-mappers/gemini-mapper.js +523 -50
- package/dist/conversion/hub/operation-table/semantic-mappers/responses-mapper.js +18 -38
- package/dist/conversion/hub/pipeline/compat/compat-pipeline-executor.js +6 -0
- package/dist/conversion/hub/pipeline/compat/compat-types.d.ts +3 -0
- package/dist/conversion/hub/pipeline/hub-pipeline/adapter-context.d.ts +10 -0
- package/dist/conversion/hub/pipeline/hub-pipeline/adapter-context.js +134 -0
- package/dist/conversion/hub/pipeline/hub-pipeline/anthropic-alias-map.d.ts +6 -0
- package/dist/conversion/hub/pipeline/hub-pipeline/anthropic-alias-map.js +79 -0
- package/dist/conversion/hub/pipeline/hub-pipeline/apply-patch-tool-mode.d.ts +3 -0
- package/dist/conversion/hub/pipeline/hub-pipeline/apply-patch-tool-mode.js +46 -0
- package/dist/conversion/hub/pipeline/hub-pipeline/execute-chat-process-entry.d.ts +8 -0
- package/dist/conversion/hub/pipeline/hub-pipeline/execute-chat-process-entry.js +366 -0
- package/dist/conversion/hub/pipeline/hub-pipeline/execute-request-stage.d.ts +9 -0
- package/dist/conversion/hub/pipeline/hub-pipeline/execute-request-stage.js +384 -0
- package/dist/conversion/hub/pipeline/hub-pipeline/node-results.d.ts +3 -0
- package/dist/conversion/hub/pipeline/hub-pipeline/node-results.js +14 -0
- package/dist/conversion/hub/pipeline/hub-pipeline/payload-normalize.d.ts +2 -0
- package/dist/conversion/hub/pipeline/hub-pipeline/payload-normalize.js +144 -0
- package/dist/conversion/hub/pipeline/hub-pipeline/policy.d.ts +4 -0
- package/dist/conversion/hub/pipeline/hub-pipeline/policy.js +32 -0
- package/dist/conversion/hub/pipeline/hub-pipeline/protocol.d.ts +8 -0
- package/dist/conversion/hub/pipeline/hub-pipeline/protocol.js +63 -0
- package/dist/conversion/hub/pipeline/hub-pipeline/resolve-protocol-hooks.d.ts +2 -0
- package/dist/conversion/hub/pipeline/hub-pipeline/resolve-protocol-hooks.js +43 -0
- package/dist/conversion/hub/pipeline/hub-pipeline/semantic-gate.d.ts +1 -0
- package/dist/conversion/hub/pipeline/hub-pipeline/semantic-gate.js +29 -0
- package/dist/conversion/hub/pipeline/hub-pipeline/servertool-runtime-config.d.ts +2 -0
- package/dist/conversion/hub/pipeline/hub-pipeline/servertool-runtime-config.js +16 -0
- package/dist/conversion/hub/pipeline/hub-pipeline/types.d.ts +116 -0
- package/dist/conversion/hub/pipeline/hub-pipeline/types.js +1 -0
- package/dist/conversion/hub/pipeline/hub-pipeline.d.ts +3 -95
- package/dist/conversion/hub/pipeline/hub-pipeline.js +19 -1281
- package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage1_format_parse/index.js +1 -1
- package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage2_semantic_map/index.d.ts +7 -0
- package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage2_semantic_map/index.js +65 -1
- package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage3_context_capture/index.js +25 -22
- package/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage1_semantic_map/index.js +1 -1
- package/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage2_format_build/index.d.ts +1 -1
- package/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage2_format_build/index.js +2 -2
- package/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage3_compat/index.js +2 -2
- package/dist/conversion/hub/pipeline/stages/req_process/req_process_stage1_tool_governance/index.js +1 -1
- package/dist/conversion/hub/pipeline/stages/req_process/req_process_stage2_route_select/index.js +1 -1
- package/dist/conversion/hub/pipeline/stages/resp_inbound/resp_inbound_stage1_sse_decode/index.js +11 -11
- package/dist/conversion/hub/pipeline/stages/resp_inbound/resp_inbound_stage2_format_parse/index.js +1 -1
- package/dist/conversion/hub/pipeline/stages/resp_inbound/resp_inbound_stage3_semantic_map/index.d.ts +1 -0
- package/dist/conversion/hub/pipeline/stages/resp_inbound/resp_inbound_stage3_semantic_map/index.js +4 -2
- package/dist/conversion/hub/pipeline/stages/resp_outbound/resp_outbound_stage1_client_remap/index.d.ts +1 -0
- package/dist/conversion/hub/pipeline/stages/resp_outbound/resp_outbound_stage1_client_remap/index.js +17 -9
- package/dist/conversion/hub/pipeline/stages/resp_outbound/resp_outbound_stage2_sse_stream/index.js +2 -2
- package/dist/conversion/hub/pipeline/stages/resp_process/resp_process_stage1_tool_governance/index.js +40 -2
- package/dist/conversion/hub/pipeline/stages/resp_process/resp_process_stage2_finalize/index.js +1 -1
- package/dist/conversion/hub/pipeline/target-utils.js +9 -5
- package/dist/conversion/hub/process/chat-process.js +256 -16
- package/dist/conversion/hub/response/provider-response.d.ts +8 -0
- package/dist/conversion/hub/response/provider-response.js +85 -27
- package/dist/conversion/hub/response/response-mappers.d.ts +10 -3
- package/dist/conversion/hub/response/response-mappers.js +30 -6
- package/dist/conversion/hub/response/response-runtime.js +4 -38
- package/dist/conversion/hub/snapshot-recorder.js +5 -1
- package/dist/conversion/hub/standardized-bridge.js +23 -15
- package/dist/conversion/pipeline/codecs/v2/anthropic-openai-pipeline.js +36 -5
- package/dist/conversion/responses/responses-openai-bridge.js +20 -4
- package/dist/conversion/shared/gemini-tool-utils.d.ts +8 -1
- package/dist/conversion/shared/gemini-tool-utils.js +580 -108
- package/dist/conversion/shared/jsonish.js +1 -1
- package/dist/conversion/shared/mcp-injection.js +67 -33
- package/dist/conversion/shared/openai-finalizer.js +2 -1
- package/dist/conversion/shared/openai-message-normalize.js +76 -21
- package/dist/conversion/shared/responses-output-builder.js +6 -0
- package/dist/conversion/shared/runtime-metadata.d.ts +7 -0
- package/dist/conversion/shared/runtime-metadata.js +23 -0
- package/dist/conversion/shared/text-markup-normalizer.d.ts +2 -0
- package/dist/conversion/shared/text-markup-normalizer.js +284 -4
- package/dist/conversion/shared/tool-canonicalizer.js +2 -1
- package/dist/conversion/shared/tool-governor.js +3 -3
- package/dist/filters/engine.js +5 -5
- package/dist/filters/special/request-tool-list-filter.js +194 -60
- package/dist/filters/special/request-tools-normalize.js +1 -1
- package/dist/filters/special/response-tool-text-canonicalize.d.ts +4 -7
- package/dist/filters/special/response-tool-text-canonicalize.js +7 -35
- package/dist/filters/special/tool-filter-hooks.js +58 -62
- package/dist/guidance/index.js +5 -1
- package/dist/http/sse-response.js +6 -6
- package/dist/router/virtual-router/bootstrap.js +65 -5
- package/dist/router/virtual-router/context-advisor.d.ts +4 -0
- package/dist/router/virtual-router/context-advisor.js +3 -0
- package/dist/router/virtual-router/context-weighted.d.ts +31 -0
- package/dist/router/virtual-router/context-weighted.js +54 -0
- package/dist/router/virtual-router/engine-health.d.ts +1 -1
- package/dist/router/virtual-router/engine-health.js +11 -110
- package/dist/router/virtual-router/engine-selection/alias-selection.d.ts +15 -0
- package/dist/router/virtual-router/engine-selection/alias-selection.js +156 -0
- package/dist/router/virtual-router/engine-selection/context-weight-multipliers.d.ts +11 -0
- package/dist/router/virtual-router/engine-selection/context-weight-multipliers.js +23 -0
- package/dist/router/virtual-router/engine-selection/direct-provider-model.d.ts +9 -0
- package/dist/router/virtual-router/engine-selection/direct-provider-model.js +49 -0
- package/dist/router/virtual-router/engine-selection/instruction-target.d.ts +6 -0
- package/dist/router/virtual-router/engine-selection/instruction-target.js +54 -0
- package/dist/router/virtual-router/engine-selection/key-parsing.d.ts +8 -0
- package/dist/router/virtual-router/engine-selection/key-parsing.js +64 -0
- package/dist/router/virtual-router/engine-selection/route-utils.d.ts +12 -0
- package/dist/router/virtual-router/engine-selection/route-utils.js +150 -0
- package/dist/router/virtual-router/engine-selection/routing-state-filter.d.ts +4 -0
- package/dist/router/virtual-router/engine-selection/routing-state-filter.js +50 -0
- package/dist/router/virtual-router/engine-selection/selection-deps.d.ts +39 -0
- package/dist/router/virtual-router/engine-selection/selection-deps.js +1 -0
- package/dist/router/virtual-router/engine-selection/sticky-pool.d.ts +11 -0
- package/dist/router/virtual-router/engine-selection/sticky-pool.js +109 -0
- package/dist/router/virtual-router/engine-selection/tier-priority.d.ts +12 -0
- package/dist/router/virtual-router/engine-selection/tier-priority.js +55 -0
- package/dist/router/virtual-router/engine-selection/tier-selection-select.d.ts +22 -0
- package/dist/router/virtual-router/engine-selection/tier-selection-select.js +400 -0
- package/dist/router/virtual-router/engine-selection/tier-selection.d.ts +3 -0
- package/dist/router/virtual-router/engine-selection/tier-selection.js +225 -0
- package/dist/router/virtual-router/engine-selection.d.ts +4 -30
- package/dist/router/virtual-router/engine-selection.js +10 -815
- package/dist/router/virtual-router/engine.d.ts +1 -0
- package/dist/router/virtual-router/engine.js +55 -10
- package/dist/router/virtual-router/routing-instructions.js +6 -1
- package/dist/router/virtual-router/stop-message-state-sync.d.ts +5 -0
- package/dist/router/virtual-router/stop-message-state-sync.js +6 -14
- package/dist/router/virtual-router/types.d.ts +53 -1
- package/dist/servertool/clock/config.d.ts +8 -0
- package/dist/servertool/clock/config.js +22 -0
- package/dist/servertool/clock/log.d.ts +3 -0
- package/dist/servertool/clock/log.js +13 -0
- package/dist/servertool/clock/task-store.d.ts +1 -1
- package/dist/servertool/clock/task-store.js +1 -1
- package/dist/servertool/clock/tasks.js +1 -1
- package/dist/servertool/engine.js +146 -21
- package/dist/servertool/handlers/clock-auto.js +11 -6
- package/dist/servertool/handlers/clock.js +36 -10
- package/dist/servertool/handlers/followup-request-builder.js +8 -2
- package/dist/servertool/handlers/gemini-empty-reply-continue.js +15 -9
- package/dist/servertool/handlers/iflow-model-error-retry.js +6 -4
- package/dist/servertool/handlers/recursive-detection-guard.js +4 -2
- package/dist/servertool/handlers/stop-message-auto.js +100 -10
- package/dist/servertool/handlers/vision.js +4 -1
- package/dist/servertool/handlers/web-search.js +3 -1
- package/dist/servertool/pending-session.d.ts +19 -0
- package/dist/servertool/pending-session.js +97 -0
- package/dist/servertool/reenter-backend.js +5 -3
- package/dist/servertool/server-side-tools.js +235 -6
- package/dist/servertool/types.d.ts +13 -0
- package/dist/sse/json-to-sse/event-generators/responses.js +1 -1
- package/dist/sse/shared/chat-serializer.js +2 -2
- package/dist/sse/shared/constants.js +1 -1
- package/dist/sse/sse-to-json/anthropic-sse-to-json-converter.d.ts +7 -1
- package/dist/sse/sse-to-json/builders/response-builder.js +16 -0
- package/dist/sse/sse-to-json/responses-sse-to-json-converter.d.ts +1 -1
- package/dist/tools/apply-patch/execution-capturer.js +1 -1
- package/dist/tools/exec-command/normalize.js +4 -0
- package/dist/tools/exec-command/regression-capturer.js +1 -1
- package/package.json +10 -5
|
@@ -15,6 +15,60 @@ function getEnvServers() {
|
|
|
15
15
|
return [];
|
|
16
16
|
return raw.split(',').map(s => s.trim()).filter(Boolean);
|
|
17
17
|
}
|
|
18
|
+
function extractMcpServerLabelsFromOutput(output) {
|
|
19
|
+
const found = [];
|
|
20
|
+
const add = (v) => {
|
|
21
|
+
if (typeof v === 'string') {
|
|
22
|
+
const s = v.trim();
|
|
23
|
+
if (s)
|
|
24
|
+
found.push(s);
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
try {
|
|
28
|
+
if (Array.isArray(output)) {
|
|
29
|
+
for (const item of output) {
|
|
30
|
+
if (typeof item === 'string')
|
|
31
|
+
add(item);
|
|
32
|
+
else if (isObject(item))
|
|
33
|
+
add(item.server);
|
|
34
|
+
}
|
|
35
|
+
return found;
|
|
36
|
+
}
|
|
37
|
+
if (!isObject(output))
|
|
38
|
+
return found;
|
|
39
|
+
// Common aggregator shapes:
|
|
40
|
+
// - { servers: ["context7", ...] }
|
|
41
|
+
// - { resources: [{ server: "..." }, ...] }
|
|
42
|
+
// - { resourceTemplates: [{ server: "..." }, ...] }
|
|
43
|
+
const servers = output.servers;
|
|
44
|
+
if (Array.isArray(servers)) {
|
|
45
|
+
for (const s of servers)
|
|
46
|
+
add(s);
|
|
47
|
+
}
|
|
48
|
+
const resources = output.resources;
|
|
49
|
+
if (Array.isArray(resources)) {
|
|
50
|
+
for (const r of resources) {
|
|
51
|
+
if (!isObject(r))
|
|
52
|
+
continue;
|
|
53
|
+
add(r.server);
|
|
54
|
+
add(r.source?.server);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
const templates = output.resourceTemplates;
|
|
58
|
+
if (Array.isArray(templates)) {
|
|
59
|
+
for (const t of templates) {
|
|
60
|
+
if (!isObject(t))
|
|
61
|
+
continue;
|
|
62
|
+
add(t.server);
|
|
63
|
+
add(t.source?.server);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
// best-effort
|
|
69
|
+
}
|
|
70
|
+
return found;
|
|
71
|
+
}
|
|
18
72
|
function collectServersFromMessages(messages) {
|
|
19
73
|
const out = new Set();
|
|
20
74
|
try {
|
|
@@ -22,47 +76,103 @@ function collectServersFromMessages(messages) {
|
|
|
22
76
|
if (!m || typeof m !== 'object')
|
|
23
77
|
continue;
|
|
24
78
|
const role = String(m.role || '').toLowerCase();
|
|
25
|
-
if (role
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
79
|
+
if (role !== 'tool')
|
|
80
|
+
continue;
|
|
81
|
+
const c = m.content;
|
|
82
|
+
if (typeof c !== 'string' || c.trim().length === 0)
|
|
83
|
+
continue;
|
|
84
|
+
// IMPORTANT: Only trust tool *results* (not assistant guesses in tool_calls).
|
|
85
|
+
// Extract server labels only from rcc.tool.v1 envelope outputs for list_mcp_resources.
|
|
86
|
+
try {
|
|
87
|
+
const parsed = JSON.parse(c);
|
|
88
|
+
if (!parsed || typeof parsed !== 'object')
|
|
89
|
+
continue;
|
|
90
|
+
// rcc.tool.v1 envelope: { version, tool:{name}, result:{output} }
|
|
91
|
+
if (parsed.version === 'rcc.tool.v1' && parsed.tool && typeof parsed.tool.name === 'string') {
|
|
92
|
+
const toolName = String(parsed.tool.name).toLowerCase();
|
|
93
|
+
if (toolName !== 'list_mcp_resources')
|
|
94
|
+
continue;
|
|
95
|
+
const output = parsed.result?.output;
|
|
96
|
+
for (const s of extractMcpServerLabelsFromOutput(output))
|
|
97
|
+
out.add(s);
|
|
98
|
+
continue;
|
|
45
99
|
}
|
|
100
|
+
// Fallback: some environments may return raw shapes (no envelope).
|
|
101
|
+
// Only accept when output clearly advertises server labels, not echoed arguments.
|
|
102
|
+
for (const s of extractMcpServerLabelsFromOutput(parsed.output ?? parsed))
|
|
103
|
+
out.add(s);
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
// ignore parse errors
|
|
46
107
|
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
// ignore errors
|
|
112
|
+
}
|
|
113
|
+
return Array.from(out);
|
|
114
|
+
}
|
|
115
|
+
function detectEmptyMcpListFromMessages(messages) {
|
|
116
|
+
try {
|
|
117
|
+
for (const m of messages) {
|
|
118
|
+
if (!m || typeof m !== 'object')
|
|
119
|
+
continue;
|
|
120
|
+
const role = String(m.role || '').toLowerCase();
|
|
121
|
+
if (role !== 'tool')
|
|
122
|
+
continue;
|
|
123
|
+
const c = m.content;
|
|
124
|
+
if (typeof c !== 'string' || c.trim().length === 0)
|
|
125
|
+
continue;
|
|
126
|
+
const lowered = c.toLowerCase();
|
|
127
|
+
// Some clients surface MCP "resources/list" unsupported as a plain error string.
|
|
128
|
+
if (lowered.includes('-32601') || (lowered.includes('method') && lowered.includes('not found'))) {
|
|
129
|
+
return true;
|
|
130
|
+
}
|
|
131
|
+
try {
|
|
132
|
+
const parsed = JSON.parse(c);
|
|
133
|
+
if (!parsed || typeof parsed !== 'object')
|
|
134
|
+
continue;
|
|
135
|
+
// rcc.tool.v1 envelope: { version, tool:{name}, result:{output} }
|
|
136
|
+
if (parsed.version === 'rcc.tool.v1' && parsed.tool && typeof parsed.tool.name === 'string') {
|
|
137
|
+
const toolName = String(parsed.tool.name).toLowerCase();
|
|
138
|
+
if (toolName !== 'list_mcp_resources')
|
|
139
|
+
continue;
|
|
140
|
+
const out = parsed.result?.output;
|
|
141
|
+
if (Array.isArray(out) && out.length === 0)
|
|
142
|
+
return true;
|
|
143
|
+
if (out && typeof out === 'object' && Array.isArray(out.resources) && out.resources.length === 0) {
|
|
144
|
+
return true;
|
|
145
|
+
}
|
|
146
|
+
if (out && typeof out === 'object' && Array.isArray(out.servers) && out.servers.length === 0) {
|
|
147
|
+
return true;
|
|
58
148
|
}
|
|
59
|
-
|
|
149
|
+
continue;
|
|
60
150
|
}
|
|
151
|
+
// Fallback: raw shapes (no envelope).
|
|
152
|
+
const payload = parsed.output ?? parsed;
|
|
153
|
+
if (payload && typeof payload === 'object') {
|
|
154
|
+
if (Array.isArray(payload.resources) && payload.resources.length === 0)
|
|
155
|
+
return true;
|
|
156
|
+
if (Array.isArray(payload.servers) && payload.servers.length === 0)
|
|
157
|
+
return true;
|
|
158
|
+
const err = payload.error ?? parsed.error;
|
|
159
|
+
if (err && typeof err === 'object') {
|
|
160
|
+
const code = err.code;
|
|
161
|
+
const msg = typeof err.message === 'string' ? String(err.message).toLowerCase() : '';
|
|
162
|
+
if (code === -32601 || msg.includes('method not found'))
|
|
163
|
+
return true;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
catch {
|
|
168
|
+
// ignore parse errors
|
|
61
169
|
}
|
|
62
170
|
}
|
|
63
171
|
}
|
|
64
|
-
catch {
|
|
65
|
-
|
|
172
|
+
catch {
|
|
173
|
+
// ignore errors
|
|
174
|
+
}
|
|
175
|
+
return false;
|
|
66
176
|
}
|
|
67
177
|
function ensureFunctionTool(tools, name, description, parameters) {
|
|
68
178
|
const idx = tools.findIndex(t => t && typeof t === 'object' && t.type === 'function' && t.function && t.function.name === name);
|
|
@@ -70,13 +180,10 @@ function ensureFunctionTool(tools, name, description, parameters) {
|
|
|
70
180
|
const cur = tools[idx];
|
|
71
181
|
const fn = (cur.function = cur.function || {});
|
|
72
182
|
fn.name = name;
|
|
73
|
-
if (typeof fn.description !== 'string')
|
|
183
|
+
if (typeof fn.description !== 'string' || !String(fn.description).trim())
|
|
74
184
|
fn.description = description;
|
|
75
185
|
fn.parameters = parameters;
|
|
76
186
|
}
|
|
77
|
-
else {
|
|
78
|
-
tools.push({ type: 'function', function: { name, description, parameters } });
|
|
79
|
-
}
|
|
80
187
|
}
|
|
81
188
|
function removeToolByName(tools, name) {
|
|
82
189
|
for (let i = tools.length - 1; i >= 0; i--) {
|
|
@@ -98,15 +205,22 @@ export class RequestToolListFilter {
|
|
|
98
205
|
try {
|
|
99
206
|
const out = JSON.parse(JSON.stringify(input || {}));
|
|
100
207
|
const hadIncomingTools = Array.isArray(out.tools);
|
|
101
|
-
|
|
208
|
+
// IMPORTANT: do not force-inject any tools. If the inbound request didn't include `tools`,
|
|
209
|
+
// keep it absent.
|
|
102
210
|
if (!hadIncomingTools) {
|
|
103
|
-
out
|
|
211
|
+
return { ok: true, data: out };
|
|
212
|
+
}
|
|
213
|
+
const tools = out.tools;
|
|
214
|
+
const hasMcpTool = Array.isArray(tools) &&
|
|
215
|
+
tools.some((t) => t && typeof t === 'object' && t.type === 'function' && t.function && typeof t.function.name === 'string' && (t.function.name === 'list_mcp_resources' ||
|
|
216
|
+
t.function.name === 'list_mcp_resource_templates' ||
|
|
217
|
+
t.function.name === 'read_mcp_resource'));
|
|
218
|
+
if (!hasMcpTool) {
|
|
219
|
+
return { ok: true, data: out };
|
|
104
220
|
}
|
|
105
221
|
const mode = envMode();
|
|
106
222
|
if (mode === 'off') {
|
|
107
|
-
|
|
108
|
-
delete out.tools;
|
|
109
|
-
}
|
|
223
|
+
// Explicitly do not mutate tools when disabled.
|
|
110
224
|
return { ok: true, data: out };
|
|
111
225
|
}
|
|
112
226
|
const messages = Array.isArray(out.messages) ? out.messages : [];
|
|
@@ -116,11 +230,21 @@ export class RequestToolListFilter {
|
|
|
116
230
|
for (const s of collectServersFromMessages(messages))
|
|
117
231
|
servers.add(s);
|
|
118
232
|
const knownServers = Array.from(servers);
|
|
233
|
+
const mcpListEmpty = detectEmptyMcpListFromMessages(messages);
|
|
234
|
+
const formatKnownServers = (list) => {
|
|
235
|
+
if (!Array.isArray(list) || list.length === 0)
|
|
236
|
+
return '';
|
|
237
|
+
const shown = list.slice(0, 8);
|
|
238
|
+
const suffix = list.length > shown.length ? ` (+${list.length - shown.length} more)` : '';
|
|
239
|
+
return `Known MCP servers: ${shown.join(', ')}${suffix}.`;
|
|
240
|
+
};
|
|
241
|
+
const mcpServerReminder = 'Note: arguments.server is an MCP server label (NOT a tool name like shell/exec_command/apply_patch).';
|
|
242
|
+
// IMPORTANT: do not remove tools based on session history. Tool list must remain stable for Gemini/Antigravity.
|
|
119
243
|
// MCP tool schemas
|
|
120
244
|
const listResParams = {
|
|
121
245
|
type: 'object',
|
|
122
246
|
properties: {
|
|
123
|
-
server: { type: 'string' },
|
|
247
|
+
server: knownServers.length > 0 ? { type: 'string', enum: knownServers, minLength: 1 } : { type: 'string', minLength: 1 },
|
|
124
248
|
filter: { type: 'string' },
|
|
125
249
|
root: { type: 'string' }
|
|
126
250
|
},
|
|
@@ -130,7 +254,7 @@ export class RequestToolListFilter {
|
|
|
130
254
|
type: 'object',
|
|
131
255
|
properties: {
|
|
132
256
|
cursor: { type: 'string' },
|
|
133
|
-
server: { type: 'string' }
|
|
257
|
+
server: knownServers.length > 0 ? { type: 'string', enum: knownServers, minLength: 1 } : { type: 'string', minLength: 1 }
|
|
134
258
|
},
|
|
135
259
|
additionalProperties: false
|
|
136
260
|
};
|
|
@@ -143,31 +267,41 @@ export class RequestToolListFilter {
|
|
|
143
267
|
required: ['server', 'uri'],
|
|
144
268
|
additionalProperties: false
|
|
145
269
|
};
|
|
146
|
-
const
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
270
|
+
const listDescription = [
|
|
271
|
+
'List resources exposed by MCP servers.',
|
|
272
|
+
'Only use this for MCP resources (not MCP tools). Many MCP servers expose tools only; if the result is empty, do not retry.',
|
|
273
|
+
'If you do not know the MCP server name yet, call this tool with {} once; then reuse the returned server names for subsequent calls.',
|
|
274
|
+
mcpServerReminder,
|
|
275
|
+
formatKnownServers(knownServers)
|
|
276
|
+
].filter(Boolean).join('\n');
|
|
277
|
+
const templatesDescription = [
|
|
278
|
+
'List resource templates exposed by MCP servers.',
|
|
279
|
+
'Only use this for MCP resources (not MCP tools). If list_mcp_resources returns empty, do not retry.',
|
|
280
|
+
'If you do not know the MCP server name yet, call list_mcp_resources({}) once first.',
|
|
281
|
+
mcpServerReminder,
|
|
282
|
+
formatKnownServers(knownServers)
|
|
283
|
+
].filter(Boolean).join('\n');
|
|
284
|
+
const readDescription = [
|
|
285
|
+
'Read a specific MCP resource by { server, uri }.',
|
|
286
|
+
'Only use this for MCP resources (not MCP tools). If list_mcp_resources returns empty, do not retry.',
|
|
287
|
+
'If you do not know the MCP server name yet, call list_mcp_resources({}) once first.',
|
|
288
|
+
mcpServerReminder,
|
|
289
|
+
formatKnownServers(knownServers)
|
|
290
|
+
].filter(Boolean).join('\n');
|
|
153
291
|
if (mode === 'all') {
|
|
154
292
|
ensureFunctionTool(tools, 'list_mcp_resources', listDescription, listResParams);
|
|
155
|
-
ensureFunctionTool(tools, 'list_mcp_resource_templates',
|
|
156
|
-
ensureFunctionTool(tools, 'read_mcp_resource',
|
|
293
|
+
ensureFunctionTool(tools, 'list_mcp_resource_templates', templatesDescription, listTplParams);
|
|
294
|
+
ensureFunctionTool(tools, 'read_mcp_resource', readDescription, readResParamsBase);
|
|
157
295
|
}
|
|
158
296
|
else {
|
|
159
297
|
// phase
|
|
160
298
|
ensureFunctionTool(tools, 'list_mcp_resources', listDescription, listResParams);
|
|
161
|
-
ensureFunctionTool(tools, 'list_mcp_resource_templates',
|
|
299
|
+
ensureFunctionTool(tools, 'list_mcp_resource_templates', templatesDescription, listTplParams);
|
|
162
300
|
// read is only exposed when we have known servers
|
|
163
301
|
if (knownServers.length > 0) {
|
|
164
302
|
const withEnum = JSON.parse(JSON.stringify(readResParamsBase));
|
|
165
303
|
withEnum.properties.server = { type: 'string', enum: knownServers };
|
|
166
|
-
ensureFunctionTool(tools, 'read_mcp_resource',
|
|
167
|
-
}
|
|
168
|
-
else {
|
|
169
|
-
// remove any existing read tool to prevent premature exposure
|
|
170
|
-
removeToolByName(tools, 'read_mcp_resource');
|
|
304
|
+
ensureFunctionTool(tools, 'read_mcp_resource', readDescription, withEnum);
|
|
171
305
|
}
|
|
172
306
|
}
|
|
173
307
|
out.tools = tools;
|
|
@@ -1,13 +1,10 @@
|
|
|
1
1
|
import type { Filter, FilterContext, FilterResult, JsonObject } from '../types.js';
|
|
2
2
|
/**
|
|
3
|
-
* Canonicalize
|
|
3
|
+
* Canonicalize structured tool_calls (Chat path).
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* 这样可以保证:无论上游 provider 是哪种协议,只要在文本中输出符合规范的 apply_patch / shell 等片段,
|
|
10
|
-
* 在响应侧都会被折叠为标准的 tool_calls,供后续工具治理与客户端透明消费。
|
|
5
|
+
* 注意:该 filter 不会将纯文本中的“工具标记”提升为 tool_calls。
|
|
6
|
+
* 在 processMode=chat 的严格路径下,工具调用应以结构化 tool_calls 形态出现;
|
|
7
|
+
* 若上游以文本形式输出工具调用,应作为问题暴露给上层(而不是在此处兜底转换)。
|
|
11
8
|
*/
|
|
12
9
|
export declare class ResponseToolTextCanonicalizeFilter implements Filter<JsonObject> {
|
|
13
10
|
readonly name = "response_tool_text_canonicalize";
|
|
@@ -1,44 +1,16 @@
|
|
|
1
|
+
import { canonicalizeChatResponseTools } from '../../conversion/shared/tool-canonicalizer.js';
|
|
1
2
|
/**
|
|
2
|
-
* Canonicalize
|
|
3
|
+
* Canonicalize structured tool_calls (Chat path).
|
|
3
4
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* 这样可以保证:无论上游 provider 是哪种协议,只要在文本中输出符合规范的 apply_patch / shell 等片段,
|
|
9
|
-
* 在响应侧都会被折叠为标准的 tool_calls,供后续工具治理与客户端透明消费。
|
|
5
|
+
* 注意:该 filter 不会将纯文本中的“工具标记”提升为 tool_calls。
|
|
6
|
+
* 在 processMode=chat 的严格路径下,工具调用应以结构化 tool_calls 形态出现;
|
|
7
|
+
* 若上游以文本形式输出工具调用,应作为问题暴露给上层(而不是在此处兜底转换)。
|
|
10
8
|
*/
|
|
11
9
|
export class ResponseToolTextCanonicalizeFilter {
|
|
12
10
|
name = 'response_tool_text_canonicalize';
|
|
13
11
|
stage = 'response_pre';
|
|
14
12
|
apply(input) {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
const { canonicalizeChatResponseTools } = require('../../conversion/shared/tool-canonicalizer.js');
|
|
18
|
-
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
19
|
-
const { normalizeAssistantTextToToolCalls } = require('../../conversion/shared/text-markup-normalizer.js');
|
|
20
|
-
// 先在文本层面抽取工具调用(apply_patch / shell / MCP 等)
|
|
21
|
-
let working = input && typeof input === 'object' ? JSON.parse(JSON.stringify(input)) : input;
|
|
22
|
-
try {
|
|
23
|
-
const choices = Array.isArray(working?.choices) ? working.choices : [];
|
|
24
|
-
for (const ch of choices) {
|
|
25
|
-
if (!ch || typeof ch !== 'object')
|
|
26
|
-
continue;
|
|
27
|
-
const msg = ch.message;
|
|
28
|
-
if (msg && typeof msg === 'object') {
|
|
29
|
-
ch.message = normalizeAssistantTextToToolCalls(msg);
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
working.choices = choices;
|
|
33
|
-
}
|
|
34
|
-
catch {
|
|
35
|
-
// best-effort:文本解析失败时保留原始 payload
|
|
36
|
-
}
|
|
37
|
-
const out = canonicalizeChatResponseTools(working);
|
|
38
|
-
return { ok: true, data: out };
|
|
39
|
-
}
|
|
40
|
-
catch {
|
|
41
|
-
return { ok: true, data: input };
|
|
42
|
-
}
|
|
13
|
+
const out = canonicalizeChatResponseTools(input);
|
|
14
|
+
return { ok: true, data: out };
|
|
43
15
|
}
|
|
44
16
|
}
|
|
@@ -110,6 +110,46 @@ function isMcpToolName(name) {
|
|
|
110
110
|
function deriveMcpSessionState(messages) {
|
|
111
111
|
let listRequested = false;
|
|
112
112
|
let listEmpty = false;
|
|
113
|
+
const extractToolContentText = (content) => {
|
|
114
|
+
if (typeof content === 'string')
|
|
115
|
+
return content;
|
|
116
|
+
if (!Array.isArray(content))
|
|
117
|
+
return '';
|
|
118
|
+
const parts = [];
|
|
119
|
+
for (const part of content) {
|
|
120
|
+
if (!part || typeof part !== 'object')
|
|
121
|
+
continue;
|
|
122
|
+
const p = part;
|
|
123
|
+
if (typeof p.text === 'string' && p.text.trim().length) {
|
|
124
|
+
parts.push(p.text);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return parts.join('\n');
|
|
128
|
+
};
|
|
129
|
+
const markListEmptyFromPayload = (payload) => {
|
|
130
|
+
if (!payload || typeof payload !== 'object')
|
|
131
|
+
return;
|
|
132
|
+
const out = payload.output ?? payload;
|
|
133
|
+
if (!out || typeof out !== 'object')
|
|
134
|
+
return;
|
|
135
|
+
if (Array.isArray(out.resources) && out.resources.length === 0) {
|
|
136
|
+
listRequested = true;
|
|
137
|
+
listEmpty = true;
|
|
138
|
+
}
|
|
139
|
+
if (Array.isArray(out.servers) && out.servers.length === 0) {
|
|
140
|
+
listRequested = true;
|
|
141
|
+
listEmpty = true;
|
|
142
|
+
}
|
|
143
|
+
const err = out.error ?? payload.error;
|
|
144
|
+
if (err && typeof err === 'object') {
|
|
145
|
+
const code = err.code;
|
|
146
|
+
const msg = typeof err.message === 'string' ? String(err.message).toLowerCase() : '';
|
|
147
|
+
if (code === -32601 || msg.includes('method not found')) {
|
|
148
|
+
listRequested = true;
|
|
149
|
+
listEmpty = true;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
};
|
|
113
153
|
try {
|
|
114
154
|
for (const m of messages) {
|
|
115
155
|
if (!m || typeof m !== 'object')
|
|
@@ -133,9 +173,18 @@ function deriveMcpSessionState(messages) {
|
|
|
133
173
|
}
|
|
134
174
|
}
|
|
135
175
|
// tool 角色消息:检查 rcc.tool.v1 包装的结果是否为空
|
|
136
|
-
if (role === 'tool'
|
|
176
|
+
if (role === 'tool') {
|
|
137
177
|
try {
|
|
138
|
-
const
|
|
178
|
+
const rawText = extractToolContentText(m.content);
|
|
179
|
+
if (!rawText || rawText.trim().length === 0) {
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
const lowered = rawText.toLowerCase();
|
|
183
|
+
if (lowered.includes('-32601') || (lowered.includes('method') && lowered.includes('not found'))) {
|
|
184
|
+
listRequested = true;
|
|
185
|
+
listEmpty = true;
|
|
186
|
+
}
|
|
187
|
+
const parsed = JSON.parse(rawText);
|
|
139
188
|
if (parsed &&
|
|
140
189
|
typeof parsed === 'object' &&
|
|
141
190
|
parsed.version === 'rcc.tool.v1' &&
|
|
@@ -155,7 +204,11 @@ function deriveMcpSessionState(messages) {
|
|
|
155
204
|
listEmpty = true;
|
|
156
205
|
}
|
|
157
206
|
}
|
|
207
|
+
continue;
|
|
158
208
|
}
|
|
209
|
+
// Non-wrapped tool output (common for Codex tool responses).
|
|
210
|
+
// If the payload looks like list_mcp_resources output and is empty/unsupported, disable MCP tools for this session.
|
|
211
|
+
markListEmptyFromPayload(parsed);
|
|
159
212
|
}
|
|
160
213
|
catch {
|
|
161
214
|
// ignore parse errors
|
|
@@ -208,66 +261,9 @@ const mcpToolHook = ctx => {
|
|
|
208
261
|
}
|
|
209
262
|
return;
|
|
210
263
|
}
|
|
211
|
-
//
|
|
212
|
-
//
|
|
213
|
-
//
|
|
214
|
-
const { listRequested, listEmpty } = deriveMcpSessionState(messages);
|
|
215
|
-
const next = [];
|
|
216
|
-
for (const t of tools) {
|
|
217
|
-
let name = '';
|
|
218
|
-
try {
|
|
219
|
-
name =
|
|
220
|
-
t &&
|
|
221
|
-
typeof t === 'object' &&
|
|
222
|
-
t.function &&
|
|
223
|
-
typeof t.function.name === 'string'
|
|
224
|
-
? String(t.function.name)
|
|
225
|
-
: '';
|
|
226
|
-
}
|
|
227
|
-
catch {
|
|
228
|
-
name = '';
|
|
229
|
-
}
|
|
230
|
-
const lower = name.toLowerCase();
|
|
231
|
-
const isMcp = isMcpToolName(name);
|
|
232
|
-
if (!isMcp) {
|
|
233
|
-
next.push(t);
|
|
234
|
-
continue;
|
|
235
|
-
}
|
|
236
|
-
if (!listRequested) {
|
|
237
|
-
// list 未被请求过:保留 list_mcp_resources,其它 MCP 工具全部过滤
|
|
238
|
-
if (lower === 'list_mcp_resources') {
|
|
239
|
-
next.push(t);
|
|
240
|
-
recordDecision({
|
|
241
|
-
name,
|
|
242
|
-
action: 'allow',
|
|
243
|
-
category: 'mcp',
|
|
244
|
-
reason: 'mcp_list_exposed_before_first_use',
|
|
245
|
-
});
|
|
246
|
-
}
|
|
247
|
-
else {
|
|
248
|
-
recordDecision({
|
|
249
|
-
name,
|
|
250
|
-
action: 'block',
|
|
251
|
-
category: 'mcp',
|
|
252
|
-
reason: 'mcp_non_list_blocked_until_list_called',
|
|
253
|
-
});
|
|
254
|
-
}
|
|
255
|
-
continue;
|
|
256
|
-
}
|
|
257
|
-
if (listEmpty) {
|
|
258
|
-
// list 已请求且结果为空:完全屏蔽所有 MCP 工具
|
|
259
|
-
recordDecision({
|
|
260
|
-
name,
|
|
261
|
-
action: 'block',
|
|
262
|
-
category: 'mcp',
|
|
263
|
-
reason: 'mcp_disabled_for_session_after_empty_list',
|
|
264
|
-
});
|
|
265
|
-
continue;
|
|
266
|
-
}
|
|
267
|
-
// list 已请求且非空:保留现有行为(交由其它层/配置控制)
|
|
268
|
-
next.push(t);
|
|
269
|
-
}
|
|
270
|
-
ctx.tools = next;
|
|
264
|
+
// IMPORTANT: Do not mutate MCP tool lists based on session history.
|
|
265
|
+
// Gemini/Antigravity are sensitive to tool-list drift (history vs current declarations).
|
|
266
|
+
// We keep the inbound tool list stable and rely on schema/description hints instead.
|
|
271
267
|
};
|
|
272
268
|
const requestHooks = [visionToolHook, mcpToolHook];
|
|
273
269
|
export class ToolFilterHookFilter {
|
package/dist/guidance/index.js
CHANGED
|
@@ -158,7 +158,11 @@ function augmentMCP(fn, toolName) {
|
|
|
158
158
|
const marker = `[Codex MCP Guidance:${toolName}]`;
|
|
159
159
|
const guidance = [
|
|
160
160
|
marker,
|
|
161
|
-
'Use MCP resources sparingly. Provide only required fields; avoid unnecessary large reads.'
|
|
161
|
+
'Use MCP resources sparingly. Provide only required fields; avoid unnecessary large reads.',
|
|
162
|
+
'Do not call MCP resource tools unless you actually need MCP resources (not MCP tools).',
|
|
163
|
+
'If list_mcp_resources returns an empty list or a "Method not found" error (-32601), do not retry; assume no MCP resources are available in this session.',
|
|
164
|
+
'If you need MCP resources but do not know the MCP server label, call list_mcp_resources({}) once and reuse the returned server labels.',
|
|
165
|
+
'Note: arguments.server is an MCP server label (NOT a tool name like shell/exec_command/apply_patch).'
|
|
162
166
|
].join('\n');
|
|
163
167
|
fn.description = appendOnce(fn.description, guidance, marker);
|
|
164
168
|
}
|
|
@@ -15,7 +15,7 @@ export function sendNodeSSE(res, stream) {
|
|
|
15
15
|
try {
|
|
16
16
|
res.write(': ok\n\n');
|
|
17
17
|
}
|
|
18
|
-
catch { }
|
|
18
|
+
catch { /* ignore */ }
|
|
19
19
|
stream.on('data', (chunk) => {
|
|
20
20
|
res.write(typeof chunk === 'string' ? chunk : chunk.toString());
|
|
21
21
|
});
|
|
@@ -23,7 +23,7 @@ export function sendNodeSSE(res, stream) {
|
|
|
23
23
|
try {
|
|
24
24
|
res.write(':\n\n');
|
|
25
25
|
}
|
|
26
|
-
catch { }
|
|
26
|
+
catch { /* ignore */ }
|
|
27
27
|
res.end();
|
|
28
28
|
});
|
|
29
29
|
stream.on('error', (err) => {
|
|
@@ -31,7 +31,7 @@ export function sendNodeSSE(res, stream) {
|
|
|
31
31
|
res.write(`event: error\n`);
|
|
32
32
|
res.write(`data: ${JSON.stringify({ message: String(err?.message || 'sse_error') })}\n\n`);
|
|
33
33
|
}
|
|
34
|
-
catch { }
|
|
34
|
+
catch { /* ignore */ }
|
|
35
35
|
res.end();
|
|
36
36
|
});
|
|
37
37
|
}
|
|
@@ -50,7 +50,7 @@ export function sendExpressSSE(res, stream) {
|
|
|
50
50
|
try {
|
|
51
51
|
res.write(': ok\n\n');
|
|
52
52
|
}
|
|
53
|
-
catch { }
|
|
53
|
+
catch { /* ignore */ }
|
|
54
54
|
stream.on('data', (chunk) => res.write(typeof chunk === 'string' ? chunk : chunk.toString()));
|
|
55
55
|
stream.on('end', () => res.end());
|
|
56
56
|
stream.on('error', (err) => {
|
|
@@ -58,7 +58,7 @@ export function sendExpressSSE(res, stream) {
|
|
|
58
58
|
res.write(`event: error\n`);
|
|
59
59
|
res.write(`data: ${JSON.stringify({ message: String(err?.message || 'sse_error') })}\n\n`);
|
|
60
60
|
}
|
|
61
|
-
catch { }
|
|
61
|
+
catch { /* ignore */ }
|
|
62
62
|
res.end();
|
|
63
63
|
});
|
|
64
64
|
}
|
|
@@ -97,7 +97,7 @@ export function sendSSEOrJSON(resOrReply, payload) {
|
|
|
97
97
|
try {
|
|
98
98
|
resOrReply.setHeader?.('Content-Type', 'application/json; charset=utf-8');
|
|
99
99
|
}
|
|
100
|
-
catch { }
|
|
100
|
+
catch { /* ignore */ }
|
|
101
101
|
return resOrReply.end(jsonTxt);
|
|
102
102
|
}
|
|
103
103
|
}
|