@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 CHANGED
@@ -340,7 +340,8 @@ export class CatalogBackend {
340
340
  }
341
341
  }, { status: 400 }));
342
342
  }
343
- const selection = this.#validatedReasoning(entry, reasoningSelectionOf(body));
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
- attachReasoningSelection(nativeBody, selection);
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 }]
@@ -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({ baseUrl: `${mock.url}/v1` })
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",
@@ -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.2",
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.2",
31
- "@velum-labs/routekit-contracts": "0.9.2",
32
- "@velum-labs/routekit-tracing": "0.9.2",
33
- "@velum-labs/routekit-runtime": "0.9.2"
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",