@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
|
@@ -2,90 +2,545 @@ import { jsonClone } from '../hub/types/json.js';
|
|
|
2
2
|
function isPlainRecord(value) {
|
|
3
3
|
return !!value && typeof value === 'object' && !Array.isArray(value);
|
|
4
4
|
}
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
5
|
+
const GEMINI_FUNCTION_NAME_SAFE = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
6
|
+
export function isGeminiFunctionNameSafe(name) {
|
|
7
|
+
return typeof name === 'string' && GEMINI_FUNCTION_NAME_SAFE.test(name);
|
|
8
|
+
}
|
|
9
|
+
export function sanitizeGeminiFunctionName(name, options) {
|
|
10
|
+
const raw = typeof name === 'string' ? name : '';
|
|
11
|
+
const maxLen = typeof options?.maxLen === 'number' && Number.isFinite(options.maxLen) && options.maxLen > 8
|
|
12
|
+
? Math.floor(options.maxLen)
|
|
13
|
+
: 64;
|
|
14
|
+
// Preserve existing underscores (many tool namespaces use double-underscores as separators).
|
|
15
|
+
// Only replace invalid characters.
|
|
16
|
+
const replaced = raw.replace(/[^A-Za-z0-9_]/g, '_');
|
|
17
|
+
const prefixed = /^[A-Za-z_]/.test(replaced) ? replaced : `_${replaced}`;
|
|
18
|
+
const sliced = prefixed.length > maxLen ? prefixed.slice(0, maxLen) : prefixed;
|
|
19
|
+
return sliced || '_tool';
|
|
20
|
+
}
|
|
21
|
+
// Align with opencode-antigravity-auth gcli2api-style schema transform (toGeminiSchema):
|
|
22
|
+
// Gemini/Antigravity strict protobuf-backed validation rejects these JSON Schema keywords.
|
|
23
|
+
const UNSUPPORTED_KEYWORDS = new Set([
|
|
24
|
+
'additionalProperties',
|
|
25
|
+
'$schema',
|
|
26
|
+
'$id',
|
|
27
|
+
'$comment',
|
|
28
|
+
'$ref',
|
|
29
|
+
'$defs',
|
|
30
|
+
'definitions',
|
|
31
|
+
'const',
|
|
32
|
+
'contentMediaType',
|
|
33
|
+
'contentEncoding',
|
|
34
|
+
'if',
|
|
35
|
+
'then',
|
|
36
|
+
'else',
|
|
37
|
+
'not',
|
|
38
|
+
'patternProperties',
|
|
39
|
+
'unevaluatedProperties',
|
|
40
|
+
'unevaluatedItems',
|
|
41
|
+
'dependentRequired',
|
|
42
|
+
'dependentSchemas',
|
|
43
|
+
'propertyNames',
|
|
44
|
+
'minContains',
|
|
45
|
+
'maxContains'
|
|
46
|
+
]);
|
|
47
|
+
const EMPTY_SCHEMA_PLACEHOLDER_NAME = '_placeholder';
|
|
48
|
+
const EMPTY_SCHEMA_PLACEHOLDER_DESCRIPTION = 'Placeholder. Always pass true.';
|
|
49
|
+
function appendDescriptionHint(schema, hint) {
|
|
50
|
+
const existing = typeof schema.description === 'string' ? schema.description : '';
|
|
51
|
+
const description = existing ? `${existing} (${hint})` : hint;
|
|
52
|
+
return { ...schema, description };
|
|
53
|
+
}
|
|
54
|
+
function convertRefsToHints(schema) {
|
|
55
|
+
if (!schema || typeof schema !== 'object') {
|
|
56
|
+
return schema;
|
|
57
|
+
}
|
|
58
|
+
if (Array.isArray(schema)) {
|
|
59
|
+
return schema.map((entry) => convertRefsToHints(entry));
|
|
60
|
+
}
|
|
61
|
+
const record = schema;
|
|
62
|
+
if (typeof record.$ref === 'string') {
|
|
63
|
+
const refVal = record.$ref;
|
|
64
|
+
const defName = refVal.includes('/') ? refVal.split('/').pop() : refVal;
|
|
65
|
+
const hint = `See: ${defName || refVal}`;
|
|
66
|
+
const base = { type: 'object' };
|
|
67
|
+
if (typeof record.description === 'string' && record.description.trim()) {
|
|
68
|
+
base.description = `${record.description} (${hint})`;
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
base.description = hint;
|
|
33
72
|
}
|
|
73
|
+
return base;
|
|
34
74
|
}
|
|
35
|
-
|
|
75
|
+
const out = {};
|
|
76
|
+
for (const [key, value] of Object.entries(record)) {
|
|
77
|
+
out[key] = convertRefsToHints(value);
|
|
78
|
+
}
|
|
79
|
+
return out;
|
|
36
80
|
}
|
|
37
|
-
function
|
|
38
|
-
if (
|
|
39
|
-
return
|
|
81
|
+
function convertConstToEnum(schema) {
|
|
82
|
+
if (!schema || typeof schema !== 'object') {
|
|
83
|
+
return schema;
|
|
84
|
+
}
|
|
85
|
+
if (Array.isArray(schema)) {
|
|
86
|
+
return schema.map((entry) => convertConstToEnum(entry));
|
|
87
|
+
}
|
|
88
|
+
const record = schema;
|
|
89
|
+
const out = {};
|
|
90
|
+
for (const [key, value] of Object.entries(record)) {
|
|
91
|
+
if (key === 'const' && record.enum === undefined) {
|
|
92
|
+
out.enum = [convertConstToEnum(value)];
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
out[key] = convertConstToEnum(value);
|
|
96
|
+
}
|
|
97
|
+
return out;
|
|
98
|
+
}
|
|
99
|
+
function addEnumHints(schema) {
|
|
100
|
+
if (!schema || typeof schema !== 'object') {
|
|
101
|
+
return schema;
|
|
102
|
+
}
|
|
103
|
+
if (Array.isArray(schema)) {
|
|
104
|
+
return schema.map((entry) => addEnumHints(entry));
|
|
105
|
+
}
|
|
106
|
+
const record = schema;
|
|
107
|
+
let out = { ...record };
|
|
108
|
+
if (Array.isArray(record.enum) && record.enum.length > 1 && record.enum.length <= 10) {
|
|
109
|
+
const vals = record.enum.map((v) => String(v)).join(', ');
|
|
110
|
+
out = appendDescriptionHint(out, `Allowed: ${vals}`);
|
|
111
|
+
}
|
|
112
|
+
for (const [key, value] of Object.entries(out)) {
|
|
113
|
+
if (key === 'enum')
|
|
114
|
+
continue;
|
|
115
|
+
if (value && typeof value === 'object') {
|
|
116
|
+
out[key] = addEnumHints(value);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return out;
|
|
120
|
+
}
|
|
121
|
+
function addAdditionalPropertiesHints(schema) {
|
|
122
|
+
if (!schema || typeof schema !== 'object') {
|
|
123
|
+
return schema;
|
|
124
|
+
}
|
|
125
|
+
if (Array.isArray(schema)) {
|
|
126
|
+
return schema.map((entry) => addAdditionalPropertiesHints(entry));
|
|
127
|
+
}
|
|
128
|
+
const record = schema;
|
|
129
|
+
let out = { ...record };
|
|
130
|
+
if (record.additionalProperties === false) {
|
|
131
|
+
out = appendDescriptionHint(out, 'No extra properties allowed');
|
|
132
|
+
}
|
|
133
|
+
for (const [key, value] of Object.entries(out)) {
|
|
134
|
+
if (key === 'additionalProperties')
|
|
135
|
+
continue;
|
|
136
|
+
if (value && typeof value === 'object') {
|
|
137
|
+
out[key] = addAdditionalPropertiesHints(value);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return out;
|
|
141
|
+
}
|
|
142
|
+
// Note: we intentionally do not rewrite schemas into description hints (unlike earlier variants),
|
|
143
|
+
// because Gemini/Cloud Code may compare tool schemas across turns; keep transformation minimal and deterministic.
|
|
144
|
+
function mergeAllOf(schema) {
|
|
145
|
+
if (!schema || typeof schema !== 'object') {
|
|
146
|
+
return schema;
|
|
40
147
|
}
|
|
41
|
-
if (Array.isArray(
|
|
42
|
-
return
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
if (combinator) {
|
|
56
|
-
const variants = value[combinator];
|
|
57
|
-
const chosen = pickBestSchemaVariant(Array.isArray(variants) ? variants : []);
|
|
58
|
-
const simplified = cloneParameters(chosen);
|
|
59
|
-
// Preserve description if present on the wrapper node.
|
|
60
|
-
if (isPlainRecord(simplified) &&
|
|
61
|
-
typeof value.description === 'string' &&
|
|
62
|
-
typeof simplified.description !== 'string') {
|
|
63
|
-
simplified.description = String(value.description);
|
|
148
|
+
if (Array.isArray(schema)) {
|
|
149
|
+
return schema.map((entry) => mergeAllOf(entry));
|
|
150
|
+
}
|
|
151
|
+
const record = schema;
|
|
152
|
+
let out = { ...record };
|
|
153
|
+
const allOf = out.allOf;
|
|
154
|
+
if (Array.isArray(allOf) && allOf.length > 0) {
|
|
155
|
+
const merged = {};
|
|
156
|
+
const mergedRequired = [];
|
|
157
|
+
for (const item of allOf) {
|
|
158
|
+
if (!isPlainRecord(item))
|
|
159
|
+
continue;
|
|
160
|
+
if (isPlainRecord(item.properties)) {
|
|
161
|
+
merged.properties = { ...merged.properties, ...item.properties };
|
|
64
162
|
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
if (key === 'exclusiveMinimum' || key === 'exclusiveMaximum' || key === 'propertyNames')
|
|
75
|
-
continue;
|
|
76
|
-
// Keep Gemini tool schemas mostly permissive to avoid upstream MALFORMED_FUNCTION_CALL on strict validation.
|
|
77
|
-
// We selectively re-introduce safe required fields for critical tools in `buildGeminiToolsFromBridge`.
|
|
78
|
-
if (key === 'required' || key === 'additionalProperties')
|
|
79
|
-
continue;
|
|
80
|
-
// Combinators are handled at the node level above.
|
|
81
|
-
if (key === 'oneOf' || key === 'anyOf' || key === 'allOf')
|
|
163
|
+
if (Array.isArray(item.required)) {
|
|
164
|
+
for (const req of item.required) {
|
|
165
|
+
if (typeof req === 'string' && !mergedRequired.includes(req)) {
|
|
166
|
+
mergedRequired.push(req);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
for (const [key, value] of Object.entries(item)) {
|
|
171
|
+
if (key === 'properties' || key === 'required')
|
|
82
172
|
continue;
|
|
173
|
+
if (merged[key] === undefined)
|
|
174
|
+
merged[key] = value;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
delete out.allOf;
|
|
178
|
+
if (merged.properties) {
|
|
179
|
+
out.properties = { ...(isPlainRecord(out.properties) ? out.properties : {}), ...merged.properties };
|
|
180
|
+
}
|
|
181
|
+
if (mergedRequired.length > 0) {
|
|
182
|
+
const existingRequired = Array.isArray(out.required) ? out.required.filter((r) => typeof r === 'string') : [];
|
|
183
|
+
out.required = Array.from(new Set([...existingRequired, ...mergedRequired]));
|
|
184
|
+
}
|
|
185
|
+
for (const [key, value] of Object.entries(merged)) {
|
|
186
|
+
if (key === 'properties' || key === 'required')
|
|
187
|
+
continue;
|
|
188
|
+
if (out[key] === undefined)
|
|
189
|
+
out[key] = value;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
for (const [key, value] of Object.entries(out)) {
|
|
193
|
+
if (value && typeof value === 'object') {
|
|
194
|
+
out[key] = mergeAllOf(value);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
return out;
|
|
198
|
+
}
|
|
199
|
+
function tryMergeEnumFromUnion(options) {
|
|
200
|
+
if (!Array.isArray(options) || options.length === 0)
|
|
201
|
+
return null;
|
|
202
|
+
const enumValues = [];
|
|
203
|
+
for (const option of options) {
|
|
204
|
+
if (!isPlainRecord(option))
|
|
205
|
+
return null;
|
|
206
|
+
if (option.const !== undefined) {
|
|
207
|
+
enumValues.push(option.const);
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
210
|
+
if (Array.isArray(option.enum) && option.enum.length > 0) {
|
|
211
|
+
enumValues.push(...option.enum);
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
if (option.properties || option.items || option.anyOf || option.oneOf || option.allOf) {
|
|
215
|
+
return null;
|
|
216
|
+
}
|
|
217
|
+
if (typeof option.type === 'string' && option.type) {
|
|
218
|
+
return null;
|
|
219
|
+
}
|
|
220
|
+
return null;
|
|
221
|
+
}
|
|
222
|
+
return enumValues.length > 0 ? enumValues : null;
|
|
223
|
+
}
|
|
224
|
+
function scoreSchemaOption(schema) {
|
|
225
|
+
if (!isPlainRecord(schema))
|
|
226
|
+
return { score: 0, typeName: 'unknown' };
|
|
227
|
+
const type = typeof schema.type === 'string' ? schema.type.toLowerCase() : '';
|
|
228
|
+
// Prefer 'string' to maximize compatibility (can carry arbitrary serialized content),
|
|
229
|
+
// then object/array, then primitives. This mirrors our historical Gemini tool behavior
|
|
230
|
+
// and avoids breaking tools expecting string args (e.g. cmd/command).
|
|
231
|
+
if (type === 'string')
|
|
232
|
+
return { score: 4, typeName: 'string' };
|
|
233
|
+
if (type === 'object' || schema.properties)
|
|
234
|
+
return { score: 3, typeName: 'object' };
|
|
235
|
+
if (type === 'array' || schema.items)
|
|
236
|
+
return { score: 2, typeName: 'array' };
|
|
237
|
+
if (type && type !== 'null')
|
|
238
|
+
return { score: 1, typeName: type };
|
|
239
|
+
return { score: 0, typeName: type || 'null' };
|
|
240
|
+
}
|
|
241
|
+
function flattenAnyOfOneOf(schema) {
|
|
242
|
+
if (!schema || typeof schema !== 'object') {
|
|
243
|
+
return schema;
|
|
244
|
+
}
|
|
245
|
+
if (Array.isArray(schema)) {
|
|
246
|
+
return schema.map((entry) => flattenAnyOfOneOf(entry));
|
|
247
|
+
}
|
|
248
|
+
let out = { ...schema };
|
|
249
|
+
for (const unionKey of ['anyOf', 'oneOf']) {
|
|
250
|
+
const options = out[unionKey];
|
|
251
|
+
if (Array.isArray(options) && options.length > 0) {
|
|
252
|
+
const parentDesc = typeof out.description === 'string' ? out.description : '';
|
|
253
|
+
const mergedEnum = tryMergeEnumFromUnion(options);
|
|
254
|
+
if (mergedEnum) {
|
|
255
|
+
const next = { ...out };
|
|
256
|
+
delete next[unionKey];
|
|
257
|
+
next.type = 'string';
|
|
258
|
+
next.enum = mergedEnum;
|
|
259
|
+
if (parentDesc)
|
|
260
|
+
next.description = parentDesc;
|
|
261
|
+
out = next;
|
|
262
|
+
continue;
|
|
263
|
+
}
|
|
264
|
+
let bestIdx = 0;
|
|
265
|
+
let bestScore = -1;
|
|
266
|
+
const allTypes = [];
|
|
267
|
+
for (let i = 0; i < options.length; i++) {
|
|
268
|
+
const { score, typeName } = scoreSchemaOption(options[i]);
|
|
269
|
+
allTypes.push(typeName);
|
|
270
|
+
if (score > bestScore) {
|
|
271
|
+
bestScore = score;
|
|
272
|
+
bestIdx = i;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
let selected = flattenAnyOfOneOf(options[bestIdx]) || { type: 'string' };
|
|
276
|
+
if (isPlainRecord(selected)) {
|
|
277
|
+
let selectedRecord = selected;
|
|
278
|
+
if (parentDesc) {
|
|
279
|
+
const childDesc = typeof selectedRecord.description === 'string' ? selectedRecord.description : '';
|
|
280
|
+
selectedRecord = childDesc && childDesc !== parentDesc
|
|
281
|
+
? { ...selectedRecord, description: `${parentDesc} (${childDesc})` }
|
|
282
|
+
: childDesc
|
|
283
|
+
? selectedRecord
|
|
284
|
+
: { ...selectedRecord, description: parentDesc };
|
|
285
|
+
}
|
|
286
|
+
const uniqueTypes = Array.from(new Set(allTypes.filter(Boolean)));
|
|
287
|
+
if (uniqueTypes.length > 1) {
|
|
288
|
+
selectedRecord = appendDescriptionHint(selectedRecord, `Accepts: ${uniqueTypes.join(' | ')}`);
|
|
289
|
+
}
|
|
290
|
+
const next = { ...out };
|
|
291
|
+
delete next[unionKey];
|
|
292
|
+
delete next.description;
|
|
293
|
+
out = { ...next, ...selectedRecord };
|
|
294
|
+
}
|
|
295
|
+
else {
|
|
296
|
+
delete out[unionKey];
|
|
83
297
|
}
|
|
84
|
-
cloned[key] = cloneParameters(entry);
|
|
85
298
|
}
|
|
86
|
-
return cloned;
|
|
87
299
|
}
|
|
88
|
-
|
|
300
|
+
for (const [key, value] of Object.entries(out)) {
|
|
301
|
+
if (value && typeof value === 'object') {
|
|
302
|
+
out[key] = flattenAnyOfOneOf(value);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
return out;
|
|
306
|
+
}
|
|
307
|
+
function flattenTypeArrays(schema, nullableFields, currentPath) {
|
|
308
|
+
if (!schema || typeof schema !== 'object') {
|
|
309
|
+
return schema;
|
|
310
|
+
}
|
|
311
|
+
if (Array.isArray(schema)) {
|
|
312
|
+
return schema.map((entry, idx) => flattenTypeArrays(entry, nullableFields, `${currentPath || ''}[${idx}]`));
|
|
313
|
+
}
|
|
314
|
+
let out = { ...schema };
|
|
315
|
+
const localNullableFields = nullableFields || new Map();
|
|
316
|
+
if (Array.isArray(out.type)) {
|
|
317
|
+
const types = out.type.filter((t) => typeof t === 'string' && t.trim().length > 0);
|
|
318
|
+
const hasNull = types.some((t) => t === 'null');
|
|
319
|
+
const nonNull = types.filter((t) => t !== 'null');
|
|
320
|
+
const first = nonNull.length > 0 ? nonNull[0] : 'string';
|
|
321
|
+
out.type = first;
|
|
322
|
+
if (nonNull.length > 1) {
|
|
323
|
+
out = appendDescriptionHint(out, `Accepts: ${Array.from(new Set(nonNull)).join(' | ')}`);
|
|
324
|
+
}
|
|
325
|
+
if (hasNull) {
|
|
326
|
+
out = appendDescriptionHint(out, 'nullable');
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
if (isPlainRecord(out.properties)) {
|
|
330
|
+
const nextProps = {};
|
|
331
|
+
for (const [propKey, propValue] of Object.entries(out.properties)) {
|
|
332
|
+
const propPath = currentPath ? `${currentPath}.properties.${propKey}` : `properties.${propKey}`;
|
|
333
|
+
const processed = flattenTypeArrays(propValue, localNullableFields, propPath);
|
|
334
|
+
nextProps[propKey] = processed;
|
|
335
|
+
if (isPlainRecord(processed) &&
|
|
336
|
+
typeof processed.description === 'string' &&
|
|
337
|
+
processed.description.includes('nullable')) {
|
|
338
|
+
const objectPath = currentPath || '';
|
|
339
|
+
const existing = localNullableFields.get(objectPath) || [];
|
|
340
|
+
existing.push(propKey);
|
|
341
|
+
localNullableFields.set(objectPath, existing);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
out.properties = nextProps;
|
|
345
|
+
}
|
|
346
|
+
// Remove nullable fields from required array (root-level cleanup, aligned with opencode).
|
|
347
|
+
if (Array.isArray(out.required) && !nullableFields) {
|
|
348
|
+
const nullableAtRoot = localNullableFields.get(currentPath || '') || [];
|
|
349
|
+
if (nullableAtRoot.length > 0) {
|
|
350
|
+
const nextRequired = out.required.filter((r) => typeof r === 'string' && !nullableAtRoot.includes(r));
|
|
351
|
+
out.required = nextRequired;
|
|
352
|
+
if (nextRequired.length === 0) {
|
|
353
|
+
delete out.required;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
for (const [key, value] of Object.entries(out)) {
|
|
358
|
+
if (key === 'properties')
|
|
359
|
+
continue;
|
|
360
|
+
if (value && typeof value === 'object') {
|
|
361
|
+
out[key] = flattenTypeArrays(value, localNullableFields, `${currentPath || ''}.${key}`);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
return out;
|
|
365
|
+
}
|
|
366
|
+
function removeUnsupportedKeywords(schema, insideProperties = false) {
|
|
367
|
+
if (!schema || typeof schema !== 'object') {
|
|
368
|
+
return schema;
|
|
369
|
+
}
|
|
370
|
+
if (Array.isArray(schema)) {
|
|
371
|
+
return schema.map((entry) => removeUnsupportedKeywords(entry, false));
|
|
372
|
+
}
|
|
373
|
+
const record = schema;
|
|
374
|
+
const out = {};
|
|
375
|
+
for (const [key, value] of Object.entries(record)) {
|
|
376
|
+
if (!insideProperties && UNSUPPORTED_KEYWORDS.has(key)) {
|
|
377
|
+
continue;
|
|
378
|
+
}
|
|
379
|
+
if (value && typeof value === 'object') {
|
|
380
|
+
if (key === 'properties' && isPlainRecord(value)) {
|
|
381
|
+
const propsOut = {};
|
|
382
|
+
for (const [propName, propSchema] of Object.entries(value)) {
|
|
383
|
+
propsOut[propName] = removeUnsupportedKeywords(propSchema, false);
|
|
384
|
+
}
|
|
385
|
+
out[key] = propsOut;
|
|
386
|
+
}
|
|
387
|
+
else {
|
|
388
|
+
out[key] = removeUnsupportedKeywords(value, false);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
else {
|
|
392
|
+
out[key] = value;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
return out;
|
|
396
|
+
}
|
|
397
|
+
function cleanupRequiredFields(schema) {
|
|
398
|
+
if (!schema || typeof schema !== 'object') {
|
|
399
|
+
return schema;
|
|
400
|
+
}
|
|
401
|
+
if (Array.isArray(schema)) {
|
|
402
|
+
return schema.map((entry) => cleanupRequiredFields(entry));
|
|
403
|
+
}
|
|
404
|
+
let out = { ...schema };
|
|
405
|
+
if (Array.isArray(out.required) && isPlainRecord(out.properties)) {
|
|
406
|
+
const props = out.properties;
|
|
407
|
+
const valid = out.required.filter((req) => typeof req === 'string' && Object.prototype.hasOwnProperty.call(props, req));
|
|
408
|
+
if (valid.length === 0) {
|
|
409
|
+
delete out.required;
|
|
410
|
+
}
|
|
411
|
+
else if (valid.length !== out.required.length) {
|
|
412
|
+
out.required = valid;
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
for (const [key, value] of Object.entries(out)) {
|
|
416
|
+
if (value && typeof value === 'object') {
|
|
417
|
+
out[key] = cleanupRequiredFields(value);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
return out;
|
|
421
|
+
}
|
|
422
|
+
function addEmptySchemaPlaceholder(schema) {
|
|
423
|
+
if (!schema || typeof schema !== 'object') {
|
|
424
|
+
return schema;
|
|
425
|
+
}
|
|
426
|
+
if (Array.isArray(schema)) {
|
|
427
|
+
return schema.map((entry) => addEmptySchemaPlaceholder(entry));
|
|
428
|
+
}
|
|
429
|
+
let out = { ...schema };
|
|
430
|
+
const typeRaw = typeof out.type === 'string' ? out.type.toLowerCase() : '';
|
|
431
|
+
const isObject = typeRaw === 'object' || isPlainRecord(out.properties);
|
|
432
|
+
if (isObject) {
|
|
433
|
+
const hasProps = isPlainRecord(out.properties) && Object.keys(out.properties).length > 0;
|
|
434
|
+
if (!hasProps) {
|
|
435
|
+
out.type = 'object';
|
|
436
|
+
out.properties = {
|
|
437
|
+
[EMPTY_SCHEMA_PLACEHOLDER_NAME]: {
|
|
438
|
+
type: 'boolean',
|
|
439
|
+
description: EMPTY_SCHEMA_PLACEHOLDER_DESCRIPTION
|
|
440
|
+
}
|
|
441
|
+
};
|
|
442
|
+
out.required = [EMPTY_SCHEMA_PLACEHOLDER_NAME];
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
for (const [key, value] of Object.entries(out)) {
|
|
446
|
+
if (value && typeof value === 'object') {
|
|
447
|
+
out[key] = addEmptySchemaPlaceholder(value);
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
return out;
|
|
451
|
+
}
|
|
452
|
+
function cleanJSONSchemaForAntigravity(schema) {
|
|
453
|
+
if (!schema || typeof schema !== 'object') {
|
|
454
|
+
return schema;
|
|
455
|
+
}
|
|
456
|
+
let out = schema;
|
|
457
|
+
// Keep this helper for backward compatibility in other call sites, but avoid
|
|
458
|
+
// aggressive rewriting that can destabilize schema comparison across turns.
|
|
459
|
+
out = removeUnsupportedKeywords(out);
|
|
460
|
+
out = cleanupRequiredFields(out);
|
|
461
|
+
return out;
|
|
462
|
+
}
|
|
463
|
+
function toGeminiSchema(schema) {
|
|
464
|
+
if (schema === null || typeof schema === 'string' || typeof schema === 'number' || typeof schema === 'boolean') {
|
|
465
|
+
return schema;
|
|
466
|
+
}
|
|
467
|
+
if (Array.isArray(schema)) {
|
|
468
|
+
return schema.map((entry) => toGeminiSchema(entry));
|
|
469
|
+
}
|
|
470
|
+
if (!isPlainRecord(schema)) {
|
|
471
|
+
return schema;
|
|
472
|
+
}
|
|
473
|
+
const input = schema;
|
|
474
|
+
const out = {};
|
|
475
|
+
const propertyNames = new Set();
|
|
476
|
+
if (isPlainRecord(input.properties)) {
|
|
477
|
+
for (const key of Object.keys(input.properties)) {
|
|
478
|
+
propertyNames.add(key);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
for (const [key, value] of Object.entries(input)) {
|
|
482
|
+
if (UNSUPPORTED_KEYWORDS.has(key)) {
|
|
483
|
+
continue;
|
|
484
|
+
}
|
|
485
|
+
if (key === 'type' && typeof value === 'string') {
|
|
486
|
+
out[key] = value.toUpperCase();
|
|
487
|
+
continue;
|
|
488
|
+
}
|
|
489
|
+
if (key === 'properties' && isPlainRecord(value)) {
|
|
490
|
+
const props = {};
|
|
491
|
+
for (const [propName, propSchema] of Object.entries(value)) {
|
|
492
|
+
props[propName] = toGeminiSchema(propSchema);
|
|
493
|
+
}
|
|
494
|
+
out[key] = props;
|
|
495
|
+
continue;
|
|
496
|
+
}
|
|
497
|
+
if (key === 'items' && value && typeof value === 'object') {
|
|
498
|
+
out[key] = toGeminiSchema(value);
|
|
499
|
+
continue;
|
|
500
|
+
}
|
|
501
|
+
if ((key === 'anyOf' || key === 'oneOf' || key === 'allOf') && Array.isArray(value)) {
|
|
502
|
+
out[key] = value.map((item) => toGeminiSchema(item));
|
|
503
|
+
continue;
|
|
504
|
+
}
|
|
505
|
+
if (key === 'enum' && Array.isArray(value)) {
|
|
506
|
+
out[key] = value;
|
|
507
|
+
continue;
|
|
508
|
+
}
|
|
509
|
+
if ((key === 'default' || key === 'examples') && value !== undefined) {
|
|
510
|
+
out[key] = value;
|
|
511
|
+
continue;
|
|
512
|
+
}
|
|
513
|
+
if (key === 'required' && Array.isArray(value)) {
|
|
514
|
+
if (propertyNames.size > 0) {
|
|
515
|
+
const validRequired = value.filter((entry) => typeof entry === 'string' && propertyNames.has(entry));
|
|
516
|
+
// Filter required array to only include properties that exist.
|
|
517
|
+
if (validRequired.length > 0) {
|
|
518
|
+
out[key] = validRequired;
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
else {
|
|
522
|
+
out[key] = value;
|
|
523
|
+
}
|
|
524
|
+
continue;
|
|
525
|
+
}
|
|
526
|
+
out[key] = value;
|
|
527
|
+
}
|
|
528
|
+
if (out.type === 'ARRAY' && !('items' in out)) {
|
|
529
|
+
out.items = { type: 'STRING' };
|
|
530
|
+
}
|
|
531
|
+
return out;
|
|
532
|
+
}
|
|
533
|
+
function cloneParameters(value, mode = 'default') {
|
|
534
|
+
if (value === null || typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
|
|
535
|
+
return value;
|
|
536
|
+
}
|
|
537
|
+
const cloned = jsonClone(value);
|
|
538
|
+
if (mode === 'antigravity') {
|
|
539
|
+
// gcli2api-style: minimal, deterministic transform (no schema hint rewriting).
|
|
540
|
+
return toGeminiSchema(cloned);
|
|
541
|
+
}
|
|
542
|
+
// Default Gemini: avoid aggressive schema rewriting (keep original shape where possible).
|
|
543
|
+
return convertConstToEnum(cloned);
|
|
89
544
|
}
|
|
90
545
|
function declarationToBridge(declaration) {
|
|
91
546
|
if (!declaration || typeof declaration !== 'object') {
|
|
@@ -97,7 +552,7 @@ function declarationToBridge(declaration) {
|
|
|
97
552
|
return null;
|
|
98
553
|
}
|
|
99
554
|
const description = typeof def.description === 'string' ? def.description : undefined;
|
|
100
|
-
const parameters = cloneParameters(def.parameters ?? { type: 'object', properties: {} });
|
|
555
|
+
const parameters = cloneParameters(def.parameters ?? { type: 'object', properties: {} }, 'default');
|
|
101
556
|
return {
|
|
102
557
|
type: 'function',
|
|
103
558
|
function: {
|
|
@@ -113,7 +568,7 @@ function legacyToolToBridge(entry) {
|
|
|
113
568
|
return null;
|
|
114
569
|
}
|
|
115
570
|
const description = typeof entry.description === 'string' ? entry.description : undefined;
|
|
116
|
-
const parameters = cloneParameters(entry.parameters ?? { type: 'object', properties: {} });
|
|
571
|
+
const parameters = cloneParameters(entry.parameters ?? { type: 'object', properties: {} }, 'default');
|
|
117
572
|
return {
|
|
118
573
|
type: 'function',
|
|
119
574
|
function: {
|
|
@@ -161,15 +616,22 @@ export function prepareGeminiToolsForBridge(rawTools, missing) {
|
|
|
161
616
|
});
|
|
162
617
|
return defs.length ? defs : undefined;
|
|
163
618
|
}
|
|
164
|
-
export function buildGeminiToolsFromBridge(defs) {
|
|
619
|
+
export function buildGeminiToolsFromBridge(defs, options) {
|
|
165
620
|
if (!defs || !defs.length) {
|
|
166
621
|
return undefined;
|
|
167
622
|
}
|
|
168
|
-
const
|
|
623
|
+
const mode = options?.mode === 'antigravity' ? 'antigravity' : 'default';
|
|
624
|
+
// Cloud Code Assist / Gemini CLI is sensitive to tool array shape.
|
|
625
|
+
// Keep all function declarations in a single tool group to match gcli2api snapshots
|
|
626
|
+
// and avoid backend-specific limits on the number of tool groups.
|
|
627
|
+
const functionDeclarations = [];
|
|
169
628
|
const applyFixups = (name, parameters) => {
|
|
170
629
|
if (!parameters || typeof parameters !== 'object' || Array.isArray(parameters)) {
|
|
171
630
|
return parameters;
|
|
172
631
|
}
|
|
632
|
+
if (mode !== 'antigravity') {
|
|
633
|
+
return parameters;
|
|
634
|
+
}
|
|
173
635
|
const params = parameters;
|
|
174
636
|
const propsRaw = params.properties;
|
|
175
637
|
const props = isPlainRecord(propsRaw) ? propsRaw : {};
|
|
@@ -179,21 +641,25 @@ export function buildGeminiToolsFromBridge(defs) {
|
|
|
179
641
|
// - Codex CLI: { cmd, workdir }
|
|
180
642
|
// - Some history: { command, workdir }
|
|
181
643
|
// Strict Gemini validation (and/or tool selection) is sensitive to schema/arg drift.
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
if (!Object.prototype.hasOwnProperty.call(props, 'command')) {
|
|
192
|
-
props.command = { type: 'string' };
|
|
644
|
+
// NOTE: Some upstream tool schemas encode cmd/command as `oneOf` (string|array),
|
|
645
|
+
// which Antigravity/Gemini can reject with MALFORMED_FUNCTION_CALL even when tool calling
|
|
646
|
+
// is disabled (toolConfig.mode=NONE). Force these fields to simple STRING.
|
|
647
|
+
// For Gemini wire, prefer `command` as the canonical key (gcli2api-style).
|
|
648
|
+
// The internal tool governor normalizes `command` → `cmd` before execution.
|
|
649
|
+
try {
|
|
650
|
+
if (Object.prototype.hasOwnProperty.call(props, 'cmd')) {
|
|
651
|
+
delete props.cmd;
|
|
652
|
+
}
|
|
193
653
|
}
|
|
194
|
-
|
|
195
|
-
|
|
654
|
+
catch {
|
|
655
|
+
// ignore
|
|
196
656
|
}
|
|
657
|
+
props.command = {
|
|
658
|
+
type: 'STRING',
|
|
659
|
+
description: 'Shell command to execute.'
|
|
660
|
+
};
|
|
661
|
+
// Keep workdir simple (avoid anyOf string|null patterns).
|
|
662
|
+
props.workdir = { type: 'STRING', description: 'Working directory.' };
|
|
197
663
|
params.properties = props;
|
|
198
664
|
// Avoid hard required keys for Gemini: the model may emit either alias (cmd/command),
|
|
199
665
|
// and "required" mismatch surfaces as MALFORMED_FUNCTION_CALL (empty reply) upstream.
|
|
@@ -214,10 +680,10 @@ export function buildGeminiToolsFromBridge(defs) {
|
|
|
214
680
|
props.text = props.chars;
|
|
215
681
|
}
|
|
216
682
|
if (!Object.prototype.hasOwnProperty.call(props, 'session_id')) {
|
|
217
|
-
props.session_id = { type: '
|
|
683
|
+
props.session_id = { type: 'NUMBER' };
|
|
218
684
|
}
|
|
219
685
|
if (!Object.prototype.hasOwnProperty.call(props, 'chars')) {
|
|
220
|
-
props.chars = { type: '
|
|
686
|
+
props.chars = { type: 'STRING' };
|
|
221
687
|
}
|
|
222
688
|
params.properties = props;
|
|
223
689
|
try {
|
|
@@ -235,25 +701,25 @@ export function buildGeminiToolsFromBridge(defs) {
|
|
|
235
701
|
// - input/instructions/text: historical aliases containing patch text
|
|
236
702
|
if (!Object.prototype.hasOwnProperty.call(props, 'patch')) {
|
|
237
703
|
props.patch = {
|
|
238
|
-
type: '
|
|
704
|
+
type: 'STRING',
|
|
239
705
|
description: 'Patch text (*** Begin Patch / *** End Patch or GNU unified diff).'
|
|
240
706
|
};
|
|
241
707
|
}
|
|
242
708
|
if (!Object.prototype.hasOwnProperty.call(props, 'input')) {
|
|
243
709
|
props.input = {
|
|
244
|
-
type: '
|
|
710
|
+
type: 'STRING',
|
|
245
711
|
description: 'Alias of patch (patch text). Prefer patch.'
|
|
246
712
|
};
|
|
247
713
|
}
|
|
248
714
|
if (!Object.prototype.hasOwnProperty.call(props, 'instructions')) {
|
|
249
715
|
props.instructions = {
|
|
250
|
-
type: '
|
|
716
|
+
type: 'STRING',
|
|
251
717
|
description: 'Alias of patch (patch text). Prefer patch.'
|
|
252
718
|
};
|
|
253
719
|
}
|
|
254
720
|
if (!Object.prototype.hasOwnProperty.call(props, 'text')) {
|
|
255
721
|
props.text = {
|
|
256
|
-
type: '
|
|
722
|
+
type: 'STRING',
|
|
257
723
|
description: 'Alias of patch (patch text). Prefer patch.'
|
|
258
724
|
};
|
|
259
725
|
}
|
|
@@ -269,6 +735,9 @@ export function buildGeminiToolsFromBridge(defs) {
|
|
|
269
735
|
return params;
|
|
270
736
|
};
|
|
271
737
|
const rewriteDescription = (name, description) => {
|
|
738
|
+
if (mode !== 'antigravity') {
|
|
739
|
+
return description;
|
|
740
|
+
}
|
|
272
741
|
const lowered = String(name || '').trim().toLowerCase();
|
|
273
742
|
if (lowered === 'apply_patch') {
|
|
274
743
|
return ('Edit files by providing patch text in `patch` (string). ' +
|
|
@@ -301,16 +770,19 @@ export function buildGeminiToolsFromBridge(defs) {
|
|
|
301
770
|
: typeof def.description === 'string'
|
|
302
771
|
? def.description
|
|
303
772
|
: undefined;
|
|
304
|
-
const parameters = applyFixups(name, cloneParameters(fnNode?.parameters ?? def.parameters ?? { type: 'object', properties: {} }));
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
description: rewriteDescription(name, description),
|
|
310
|
-
parameters
|
|
311
|
-
}
|
|
312
|
-
]
|
|
773
|
+
const parameters = applyFixups(name, cloneParameters(fnNode?.parameters ?? def.parameters ?? { type: 'object', properties: {} }, mode));
|
|
774
|
+
functionDeclarations.push({
|
|
775
|
+
name,
|
|
776
|
+
description: rewriteDescription(name, description),
|
|
777
|
+
parameters
|
|
313
778
|
});
|
|
314
779
|
});
|
|
315
|
-
|
|
780
|
+
if (!functionDeclarations.length) {
|
|
781
|
+
return undefined;
|
|
782
|
+
}
|
|
783
|
+
return [
|
|
784
|
+
{
|
|
785
|
+
functionDeclarations
|
|
786
|
+
}
|
|
787
|
+
];
|
|
316
788
|
}
|