codex-grok-bridge 1.0.4 → 1.5.0

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/src/tools.mjs CHANGED
@@ -4,6 +4,7 @@ import {
4
4
  isImageGenerationItem,
5
5
  saveGeneratedImage,
6
6
  } from "./imagegen.mjs";
7
+ import { resolveGrokModel } from "./models.mjs";
7
8
 
8
9
  const OBJECT_SCHEMA = { type: "object", properties: {} };
9
10
 
@@ -56,16 +57,145 @@ const GROK_INPUT_ITEM_TYPES = new Set([
56
57
  "function_call_output",
57
58
  "shell_call",
58
59
  ]);
60
+ // Codex item ids, status, and new client metadata have 422'd the upstream.
61
+ // Keep only the fields Grok's Responses input is known to accept.
62
+ const INPUT_ITEM_FIELDS = {
63
+ message: ["type", "role", "content"],
64
+ reasoning: ["type", "summary"],
65
+ function_call: ["type", "name", "call_id", "arguments"],
66
+ function_call_output: ["type", "name", "call_id", "output"],
67
+ shell_call: ["type", "name", "call_id", "action"],
68
+ };
69
+ const CONTENT_PART_FIELDS = {
70
+ input_text: ["type", "text"],
71
+ output_text: ["type", "text"],
72
+ summary_text: ["type", "text"],
73
+ input_image: ["type", "image_url", "detail"],
74
+ };
75
+
76
+ function pickKeys(node, keys) {
77
+ const next = {};
78
+ for (const key of keys) {
79
+ if (node[key] !== undefined) next[key] = node[key];
80
+ }
81
+ return next;
82
+ }
83
+
84
+ function whitelistContentPart(part) {
85
+ if (!part || typeof part !== "object" || Array.isArray(part)) return part;
86
+ const keys = CONTENT_PART_FIELDS[part.type];
87
+ if (!keys) return part;
88
+ const next = pickKeys(part, keys);
89
+ if (next.image_url && typeof next.image_url === "object")
90
+ next.image_url = pickKeys(next.image_url, ["url", "detail"]);
91
+ return next;
92
+ }
93
+
94
+ function whitelistInputNode(node) {
95
+ if (!node || typeof node !== "object" || Array.isArray(node)) return node;
96
+ const itemKeys = INPUT_ITEM_FIELDS[node.type];
97
+ if (itemKeys) {
98
+ const next = pickKeys(node, itemKeys);
99
+ if (Array.isArray(next.content))
100
+ next.content = next.content.map(whitelistContentPart);
101
+ return next;
102
+ }
103
+ if (node.type == null && node.role != null && node.content !== undefined) {
104
+ const next = pickKeys(node, ["role", "content"]);
105
+ if (Array.isArray(next.content))
106
+ next.content = next.content.map(whitelistContentPart);
107
+ return next;
108
+ }
109
+ return whitelistContentPart(node);
110
+ }
111
+
112
+ function isObjectFragment(schema) {
113
+ return (
114
+ schema &&
115
+ typeof schema === "object" &&
116
+ !Array.isArray(schema) &&
117
+ (schema.type === "object" || schema.type == null)
118
+ );
119
+ }
120
+
121
+ function mergeObjectFragments(fragments) {
122
+ const properties = {};
123
+ let required;
124
+ for (const fragment of fragments) {
125
+ if (fragment.properties && typeof fragment.properties === "object")
126
+ Object.assign(properties, fragment.properties);
127
+ if (Array.isArray(fragment.required)) {
128
+ required =
129
+ required === undefined
130
+ ? fragment.required.slice()
131
+ : required.filter((key) => fragment.required.includes(key));
132
+ }
133
+ }
134
+ return { properties, required };
135
+ }
136
+
137
+ function emitObjectParameters(source, overrides = {}) {
138
+ const properties =
139
+ overrides.properties ??
140
+ (source.properties && typeof source.properties === "object"
141
+ ? source.properties
142
+ : {});
143
+ const required =
144
+ overrides.required !== undefined
145
+ ? overrides.required
146
+ : Array.isArray(source.required)
147
+ ? source.required
148
+ : undefined;
149
+ const next = { type: "object", properties: { ...properties } };
150
+ if (required?.length) next.required = required.slice();
151
+ if (source.additionalProperties !== undefined)
152
+ next.additionalProperties = source.additionalProperties;
153
+ if (source.description !== undefined) next.description = source.description;
154
+ return next;
155
+ }
59
156
 
60
157
  function usableParameters(parameters) {
61
- if (!parameters || typeof parameters !== "object") return OBJECT_SCHEMA;
62
- if (
63
- parameters.type === "object" ||
64
- Array.isArray(parameters.oneOf) ||
65
- Array.isArray(parameters.anyOf)
66
- )
67
- return parameters;
68
- return OBJECT_SCHEMA;
158
+ if (!parameters || typeof parameters !== "object" || Array.isArray(parameters))
159
+ return OBJECT_SCHEMA;
160
+ const variants = Array.isArray(parameters.oneOf)
161
+ ? parameters.oneOf
162
+ : Array.isArray(parameters.anyOf)
163
+ ? parameters.anyOf
164
+ : null;
165
+ if (!variants) {
166
+ return parameters.type === "object"
167
+ ? emitObjectParameters(parameters)
168
+ : OBJECT_SCHEMA;
169
+ }
170
+ const fragments = variants.filter(isObjectFragment);
171
+ if (fragments.length === 0) {
172
+ return parameters.type === "object"
173
+ ? emitObjectParameters(parameters)
174
+ : OBJECT_SCHEMA;
175
+ }
176
+ const merged = mergeObjectFragments(fragments);
177
+ const properties = {
178
+ ...(parameters.properties && typeof parameters.properties === "object"
179
+ ? parameters.properties
180
+ : {}),
181
+ ...merged.properties,
182
+ };
183
+ let required = merged.required;
184
+ if (Array.isArray(parameters.required)) {
185
+ required =
186
+ required === undefined
187
+ ? parameters.required.slice()
188
+ : required.filter((key) => parameters.required.includes(key));
189
+ }
190
+ const first = fragments[0];
191
+ return emitObjectParameters(
192
+ {
193
+ additionalProperties:
194
+ parameters.additionalProperties ?? first.additionalProperties,
195
+ description: parameters.description ?? first.description,
196
+ },
197
+ { properties, required },
198
+ );
69
199
  }
70
200
 
71
201
  function sanitize(name) {
@@ -189,7 +319,7 @@ function toProxyInputNode(node, map, state) {
189
319
  if (
190
320
  key === "encrypted_content" ||
191
321
  key === "encrypted_function_args" ||
192
- key === "internal_chat_message_metadata_passthrough"
322
+ key.startsWith("internal_")
193
323
  )
194
324
  continue;
195
325
  const converted = toProxyInputNode(value, map, state);
@@ -204,7 +334,7 @@ function toProxyInputNode(node, map, state) {
204
334
  (part) => part && typeof part.text === "string" && part.text.trim(),
205
335
  );
206
336
  if (!summary.length) return DROP;
207
- return { type: "reasoning", summary };
337
+ return whitelistInputNode({ type: "reasoning", summary });
208
338
  }
209
339
 
210
340
  if (next.type === "agent_message") {
@@ -214,7 +344,7 @@ function toProxyInputNode(node, map, state) {
214
344
  ? [{ type: "input_text", text: next.text }]
215
345
  : [];
216
346
  if (!content.length) return DROP;
217
- return { type: "message", role: "assistant", content };
347
+ return whitelistInputNode({ type: "message", role: "assistant", content });
218
348
  }
219
349
 
220
350
  if (
@@ -227,10 +357,7 @@ function toProxyInputNode(node, map, state) {
227
357
  delete next.namespace;
228
358
  if (typeof next.call_id === "string") state.callIds.set(next.call_id, proxyName);
229
359
  }
230
- return next;
231
- }
232
-
233
- if (
360
+ } else if (
234
361
  next.type === "custom_tool_call" &&
235
362
  typeof next.name === "string"
236
363
  ) {
@@ -244,10 +371,7 @@ function toProxyInputNode(node, map, state) {
244
371
  delete next.input;
245
372
  if (typeof next.call_id === "string") state.callIds.set(next.call_id, proxyName);
246
373
  }
247
- return next;
248
- }
249
-
250
- if (
374
+ } else if (
251
375
  next.type === "function_call_output" ||
252
376
  next.type === "custom_tool_call_output"
253
377
  ) {
@@ -262,10 +386,9 @@ function toProxyInputNode(node, map, state) {
262
386
  next.name = proxyName;
263
387
  delete next.namespace;
264
388
  }
265
- return next;
266
389
  }
267
390
 
268
- return next;
391
+ return whitelistInputNode(next);
269
392
  }
270
393
 
271
394
  function toProxyInput(input, map) {
@@ -304,7 +427,7 @@ export function toProxyRequest(body) {
304
427
  const { tools, map } = flattenCodexTools(body.tools ?? []);
305
428
  const effort = EFFORT[body.reasoning?.effort] ?? "high";
306
429
  const request = {
307
- model: "grok-4.6",
430
+ model: resolveGrokModel(body.model),
308
431
  input: toProxyInput(body.input, map),
309
432
  tools,
310
433
  reasoning: { effort },