@velum-labs/routekit-gateway 0.9.2 → 0.9.4
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/router.js +18 -2
- package/dist/server.js +2 -0
- package/dist/test/chat.test.js +5 -1
- package/dist/test/responses.test.js +6 -1
- package/dist/test/router.test.js +61 -0
- package/package.json +5 -5
package/dist/router.js
CHANGED
|
@@ -340,7 +340,8 @@ export class CatalogBackend {
|
|
|
340
340
|
}
|
|
341
341
|
}, { status: 400 }));
|
|
342
342
|
}
|
|
343
|
-
const
|
|
343
|
+
const requestedSelection = reasoningSelectionOf(body);
|
|
344
|
+
const selection = this.#validatedReasoning(entry, requestedSelection);
|
|
344
345
|
if (typeof selection === "string") {
|
|
345
346
|
return Promise.resolve(Response.json({
|
|
346
347
|
error: {
|
|
@@ -362,11 +363,19 @@ export class CatalogBackend {
|
|
|
362
363
|
if (nativeBody !== null &&
|
|
363
364
|
typeof nativeBody === "object" &&
|
|
364
365
|
!Array.isArray(nativeBody)) {
|
|
365
|
-
|
|
366
|
+
const egressSelection = requestedSelection.mode === "effort" &&
|
|
367
|
+
requestedSelection.effort === "none" &&
|
|
368
|
+
selection.mode === "disabled"
|
|
369
|
+
? { mode: "auto" }
|
|
370
|
+
: selection;
|
|
371
|
+
attachReasoningSelection(nativeBody, egressSelection);
|
|
366
372
|
if (selection.mode === "effort") {
|
|
367
373
|
nativeBody.reasoning_effort =
|
|
368
374
|
selection.effort;
|
|
369
375
|
}
|
|
376
|
+
else {
|
|
377
|
+
delete nativeBody.reasoning_effort;
|
|
378
|
+
}
|
|
370
379
|
}
|
|
371
380
|
return entry.source.chat(nativeBody, signal, {
|
|
372
381
|
...options,
|
|
@@ -437,6 +446,13 @@ export class CatalogBackend {
|
|
|
437
446
|
return selection;
|
|
438
447
|
}
|
|
439
448
|
const capability = entry.reasoning;
|
|
449
|
+
if (selection.mode === "effort" &&
|
|
450
|
+
selection.effort === "none" &&
|
|
451
|
+
(capability === undefined ||
|
|
452
|
+
capability.status === "unknown" ||
|
|
453
|
+
capability.status === "unsupported")) {
|
|
454
|
+
return { mode: "disabled" };
|
|
455
|
+
}
|
|
440
456
|
if (capability === undefined || capability.status === "unknown") {
|
|
441
457
|
return `model "${entry.publicId}" has no discovered reasoning controls`;
|
|
442
458
|
}
|
package/dist/server.js
CHANGED
|
@@ -247,6 +247,7 @@ export async function startGateway(options) {
|
|
|
247
247
|
: base.data ?? [];
|
|
248
248
|
writeJson(res, 200, {
|
|
249
249
|
object: "list",
|
|
250
|
+
default_model: backend.defaultModel,
|
|
250
251
|
data,
|
|
251
252
|
models: codexPickerModels(backend, base.data ?? [], merged.models, codexProviderRelay === undefined)
|
|
252
253
|
});
|
|
@@ -263,6 +264,7 @@ export async function startGateway(options) {
|
|
|
263
264
|
writeJson(res, 200, {
|
|
264
265
|
...modelPayload,
|
|
265
266
|
object: typeof modelPayload.object === "string" ? modelPayload.object : "list",
|
|
267
|
+
default_model: backend.defaultModel,
|
|
266
268
|
data,
|
|
267
269
|
models: codexPickerModels(backend, data.flatMap((entry) => typeof entry.id === "string"
|
|
268
270
|
? [{ ...entry, id: entry.id }]
|
package/dist/test/chat.test.js
CHANGED
|
@@ -333,12 +333,16 @@ test("forceModel overrides the requested model on every upstream call", async ()
|
|
|
333
333
|
test("lists models from the backend", async () => {
|
|
334
334
|
const mock = await startMock();
|
|
335
335
|
const gateway = await startGateway({
|
|
336
|
-
backend: new OpenAiBackend({
|
|
336
|
+
backend: new OpenAiBackend({
|
|
337
|
+
baseUrl: `${mock.url}/v1`,
|
|
338
|
+
defaultModel: "mlx-default"
|
|
339
|
+
})
|
|
337
340
|
});
|
|
338
341
|
try {
|
|
339
342
|
const response = await fetch(`${gateway.url()}/v1/models`);
|
|
340
343
|
assert.equal(response.status, 200);
|
|
341
344
|
const json = (await response.json());
|
|
345
|
+
assert.equal(json.default_model, "mlx-default");
|
|
342
346
|
assert.equal(json.data.length, 1);
|
|
343
347
|
}
|
|
344
348
|
finally {
|
|
@@ -5,6 +5,7 @@ import { OpenAiBackend } from "../backend.js";
|
|
|
5
5
|
import { MODEL_CALL_ID_HEADER } from "../provenance.js";
|
|
6
6
|
import { CatalogBackend } from "../router.js";
|
|
7
7
|
import { chatToResponses, customToolNames, openAiSseToResponses, responsesToChat, responsesToolRegistry } from "../adapters/responses.js";
|
|
8
|
+
import { reasoningSelectionOf } from "../adapters/openai-chat-wire.js";
|
|
8
9
|
import { startGateway } from "../server.js";
|
|
9
10
|
function sendJson(res, status, value) {
|
|
10
11
|
res.statusCode = status;
|
|
@@ -657,6 +658,7 @@ test("Codex picker aliases use the canonical catalog and pooled native relay", a
|
|
|
657
658
|
const catalogResponse = await fetch(`${gateway.url()}/v1/models?client_version=1.0.0`);
|
|
658
659
|
assert.equal(catalogResponse.headers.get("etag"), null, "a projected managed catalog must not reuse the upstream ETag");
|
|
659
660
|
const catalog = (await catalogResponse.json());
|
|
661
|
+
assert.equal(catalog.default_model, "codex/gpt-5.5");
|
|
660
662
|
assert.deepEqual(catalog.data.map((model) => model.id), ["codex/gpt-5.5", "claude-code/claude-sonnet-4-6"]);
|
|
661
663
|
assert.deepEqual(catalog.models.map(({ slug, display_name }) => [slug, display_name]), [
|
|
662
664
|
["gpt-5.5", "GPT-5.5"],
|
|
@@ -693,11 +695,14 @@ test("Codex picker aliases use the canonical catalog and pooled native relay", a
|
|
|
693
695
|
body: JSON.stringify({
|
|
694
696
|
model: "claude-code/claude-sonnet-4-6",
|
|
695
697
|
instructions: "You are Codex, a coding agent based on GPT-5.",
|
|
696
|
-
input: "hi"
|
|
698
|
+
input: "hi",
|
|
699
|
+
reasoning: { effort: "none" }
|
|
697
700
|
})
|
|
698
701
|
});
|
|
699
702
|
assert.equal(foreign.status, 200);
|
|
700
703
|
assert.deepEqual(sourceCalls, ["claude-sonnet-4-6"]);
|
|
704
|
+
assert.equal(sourceBodies[0]?.reasoning_effort, undefined);
|
|
705
|
+
assert.deepEqual(reasoningSelectionOf(sourceBodies[0]), { mode: "auto" });
|
|
701
706
|
assert.ok(!JSON.stringify(sourceBodies[0]?.messages).includes("based on GPT-5"), "a stale startup-model identity must not cross into a foreign provider");
|
|
702
707
|
const unknown = await fetch(`${gateway.url()}/v1/responses`, {
|
|
703
708
|
method: "POST",
|
package/dist/test/router.test.js
CHANGED
|
@@ -268,6 +268,67 @@ test("catalog applies configured opaque efforts and rejects unavailable values b
|
|
|
268
268
|
assert.equal(calls.length, 1);
|
|
269
269
|
assert.equal(backend.reasoningCapabilities("openai/opaque")?.provenance, "config");
|
|
270
270
|
});
|
|
271
|
+
test("catalog treats Codex none as disabled only for models without reasoning controls", async () => {
|
|
272
|
+
const exercise = async (reasoning, effort) => {
|
|
273
|
+
const bodies = [];
|
|
274
|
+
const backend = await CatalogBackend.create({
|
|
275
|
+
config: {
|
|
276
|
+
providers: { openai: {} },
|
|
277
|
+
defaultModel: "openai/model"
|
|
278
|
+
},
|
|
279
|
+
sources: {
|
|
280
|
+
openai: {
|
|
281
|
+
sourceId: "openai",
|
|
282
|
+
async discoverModels() {
|
|
283
|
+
return [{ id: "model", ...(reasoning !== undefined ? { reasoning } : {}) }];
|
|
284
|
+
},
|
|
285
|
+
async chat(body) {
|
|
286
|
+
bodies.push(body);
|
|
287
|
+
return Response.json({});
|
|
288
|
+
},
|
|
289
|
+
async embeddings() {
|
|
290
|
+
return Response.json({});
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
});
|
|
295
|
+
return {
|
|
296
|
+
response: await backend.chat({
|
|
297
|
+
model: "openai/model",
|
|
298
|
+
reasoning_effort: effort,
|
|
299
|
+
messages: []
|
|
300
|
+
}),
|
|
301
|
+
bodies
|
|
302
|
+
};
|
|
303
|
+
};
|
|
304
|
+
for (const reasoning of [
|
|
305
|
+
undefined,
|
|
306
|
+
{ status: "unknown", provenance: "unknown" },
|
|
307
|
+
{ status: "unsupported", provenance: "provider" }
|
|
308
|
+
]) {
|
|
309
|
+
const normalized = await exercise(reasoning, "none");
|
|
310
|
+
assert.equal(normalized.response.status, 200);
|
|
311
|
+
assert.equal(normalized.bodies.length, 1);
|
|
312
|
+
assert.equal(normalized.bodies[0]?.reasoning_effort, undefined);
|
|
313
|
+
}
|
|
314
|
+
const undiscoveredEffort = await exercise(undefined, "medium");
|
|
315
|
+
assert.equal(undiscoveredEffort.response.status, 400);
|
|
316
|
+
assert.equal(undiscoveredEffort.bodies.length, 0);
|
|
317
|
+
const advertisedNone = await exercise({
|
|
318
|
+
status: "supported",
|
|
319
|
+
efforts: [{ id: "none" }, { id: "high" }],
|
|
320
|
+
provenance: "provider"
|
|
321
|
+
}, "none");
|
|
322
|
+
assert.equal(advertisedNone.response.status, 200);
|
|
323
|
+
assert.equal(advertisedNone.bodies[0]?.reasoning_effort, "none");
|
|
324
|
+
const unsupportedNone = await exercise({
|
|
325
|
+
status: "supported",
|
|
326
|
+
efforts: [{ id: "low" }, { id: "high" }],
|
|
327
|
+
provenance: "provider"
|
|
328
|
+
}, "none");
|
|
329
|
+
assert.equal(unsupportedNone.response.status, 400);
|
|
330
|
+
assert.equal(unsupportedNone.bodies.length, 0);
|
|
331
|
+
});
|
|
271
332
|
test("unknown models never fall through to the default source", async () => {
|
|
272
333
|
const backend = await CatalogBackend.create({
|
|
273
334
|
config: { providers: { openai: {} } },
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@velum-labs/routekit-gateway",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "0.9.
|
|
4
|
+
"version": "0.9.4",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
7
|
"url": "git+https://github.com/velum-labs/handoffkit.git",
|
|
@@ -27,10 +27,10 @@
|
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
29
|
"zod": "4.4.3",
|
|
30
|
-
"@velum-labs/routekit-registry": "0.9.
|
|
31
|
-
"@velum-labs/routekit-
|
|
32
|
-
"@velum-labs/routekit-
|
|
33
|
-
"@velum-labs/routekit-
|
|
30
|
+
"@velum-labs/routekit-registry": "0.9.4",
|
|
31
|
+
"@velum-labs/routekit-runtime": "0.9.4",
|
|
32
|
+
"@velum-labs/routekit-contracts": "0.9.4",
|
|
33
|
+
"@velum-labs/routekit-tracing": "0.9.4"
|
|
34
34
|
},
|
|
35
35
|
"keywords": [
|
|
36
36
|
"routekit",
|