@velum-labs/routekit-gateway 1.0.2 → 1.0.3

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.
@@ -135,6 +135,17 @@ function anthropicToolChoice(choice, parallelToolCalls) {
135
135
  ? { type: "tool", name: fn.name, ...disableParallel }
136
136
  : undefined;
137
137
  }
138
+ function anthropicStructuredOutputFormat(responseFormat) {
139
+ if (responseFormat?.type !== "json_schema" ||
140
+ typeof responseFormat.json_schema?.schema !== "object" ||
141
+ responseFormat.json_schema.schema === null) {
142
+ return undefined;
143
+ }
144
+ return {
145
+ type: "json_schema",
146
+ schema: responseFormat.json_schema.schema
147
+ };
148
+ }
138
149
  export function anthropicMessages(body, model) {
139
150
  const system = (body.messages ?? [])
140
151
  .filter((message) => message.role === "system")
@@ -199,6 +210,21 @@ export function anthropicMessages(body, model) {
199
210
  : undefined;
200
211
  const translatedOutput = selection.mode === "effort" ? { effort: selection.effort } : undefined;
201
212
  const thinking = metadata?.thinking ?? translatedThinking;
213
+ const structuredOutputFormat = anthropicStructuredOutputFormat(body.response_format);
214
+ const outputConfig = metadata?.output_config != null
215
+ ? {
216
+ ...metadata.output_config,
217
+ ...(structuredOutputFormat !== undefined &&
218
+ !Object.hasOwn(metadata.output_config, "format")
219
+ ? { format: structuredOutputFormat }
220
+ : {})
221
+ }
222
+ : translatedOutput !== undefined || structuredOutputFormat !== undefined
223
+ ? {
224
+ ...translatedOutput,
225
+ ...(structuredOutputFormat !== undefined ? { format: structuredOutputFormat } : {})
226
+ }
227
+ : undefined;
202
228
  const toolChoice = anthropicToolChoice(body.tool_choice, body.parallel_tool_calls);
203
229
  return {
204
230
  model,
@@ -210,11 +236,7 @@ export function anthropicMessages(body, model) {
210
236
  ...(body.top_p !== undefined ? { top_p: body.top_p } : {}),
211
237
  ...(body.top_k !== undefined ? { top_k: body.top_k } : {}),
212
238
  ...(thinking !== undefined ? { thinking } : {}),
213
- ...(metadata?.output_config != null
214
- ? { output_config: metadata.output_config }
215
- : translatedOutput !== undefined
216
- ? { output_config: translatedOutput }
217
- : {}),
239
+ ...(outputConfig !== undefined ? { output_config: outputConfig } : {}),
218
240
  ...(toolChoice !== undefined ? { tool_choice: toolChoice } : {}),
219
241
  ...(body.tools !== undefined
220
242
  ? {
@@ -36,6 +36,14 @@ export type ChatBody = {
36
36
  top_p?: number;
37
37
  top_k?: number;
38
38
  reasoning_effort?: string;
39
+ response_format?: {
40
+ type?: string;
41
+ json_schema?: {
42
+ name?: string;
43
+ schema?: unknown;
44
+ strict?: boolean;
45
+ };
46
+ };
39
47
  };
40
48
  export type ProviderBackendOptions = {
41
49
  baseUrl: string;
@@ -1,11 +1,11 @@
1
1
  import { conversationFromOpenAiMessages, conversationText } from "@velum-labs/routekit-contracts/protocol-ir";
2
- import { randomId } from "@velum-labs/routekit-runtime/timing";
3
2
  import { executeWebRequest, routeKitError } from "@velum-labs/routekit-runtime/effect";
4
3
  import { StreamPump } from "@velum-labs/routekit-runtime/sse";
4
+ import { randomId } from "@velum-labs/routekit-runtime/timing";
5
5
  import { Effect } from "effect";
6
- import { staticBackendModelPort } from "./backend.js";
7
6
  import { jsonResponse } from "../http/response.js";
8
7
  import { SseParseError } from "../sse/parse.js";
8
+ import { staticBackendModelPort } from "./backend.js";
9
9
  export function invalidReasoningControlResponse(message, metadata = false, path) {
10
10
  return jsonResponse({
11
11
  error: {
@@ -3,9 +3,38 @@ import { test } from "node:test";
3
3
  import { runRouteKitEffect } from "@velum-labs/routekit-runtime/effect";
4
4
  import { anthropicToChat } from "../adapters/anthropic.js";
5
5
  import { ANTHROPIC_MESSAGE_CONTENT, ANTHROPIC_REQUEST_METADATA, attachReasoningSelection, REASONING_SELECTION } from "../adapters/openai-chat-wire.js";
6
- import { OpenAiBackend } from "../providers/openai-backend.js";
6
+ import { responsesToChat } from "../adapters/responses.js";
7
+ import { anthropicMessages } from "../providers/anthropic-codec.js";
7
8
  import { AnthropicBackend, CodexResponsesBackend, GoogleGenAiBackend } from "../providers/backends.js";
9
+ import { OpenAiBackend } from "../providers/openai-backend.js";
8
10
  import { asTransport } from "./provider-backends-fixtures.js";
11
+ test("Responses JSON schemas reach Anthropic as native structured output formats", () => {
12
+ const schema = {
13
+ type: "object",
14
+ additionalProperties: false,
15
+ required: ["dimensions"],
16
+ properties: { dimensions: { type: "array" } }
17
+ };
18
+ const chat = responsesToChat({
19
+ model: "claude-code/claude-opus-5",
20
+ input: "propose dimensions",
21
+ text: {
22
+ format: {
23
+ type: "json_schema",
24
+ name: "routekit_routing_basis",
25
+ schema,
26
+ strict: true
27
+ }
28
+ }
29
+ }, "claude-opus-5");
30
+ const outbound = anthropicMessages(chat, "claude-opus-5");
31
+ assert.deepEqual(outbound.output_config, {
32
+ format: {
33
+ type: "json_schema",
34
+ schema
35
+ }
36
+ });
37
+ });
9
38
  test("direct provider backends reject malformed reasoning controls before transport", async () => {
10
39
  const cases = [
11
40
  {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@velum-labs/routekit-gateway",
3
3
  "private": false,
4
- "version": "1.0.2",
4
+ "version": "1.0.3",
5
5
  "repository": {
6
6
  "type": "git",
7
7
  "url": "git+https://github.com/velum-labs/routekit.git",
@@ -45,12 +45,12 @@
45
45
  "@aws-sdk/client-bedrock": "3.1095.0",
46
46
  "@aws-sdk/client-bedrock-runtime": "3.1095.0",
47
47
  "effect": "4.0.0-rc.108",
48
- "@velum-labs/routekit-config-core": "1.0.2",
49
- "@velum-labs/routekit-contracts": "1.0.2",
50
- "@velum-labs/routekit-eval-contracts": "1.0.2",
51
- "@velum-labs/routekit-eval-core": "1.0.2",
52
- "@velum-labs/routekit-registry": "1.0.2",
53
- "@velum-labs/routekit-runtime": "1.0.2"
48
+ "@velum-labs/routekit-config-core": "1.0.3",
49
+ "@velum-labs/routekit-contracts": "1.0.3",
50
+ "@velum-labs/routekit-eval-contracts": "1.0.3",
51
+ "@velum-labs/routekit-eval-core": "1.0.3",
52
+ "@velum-labs/routekit-registry": "1.0.3",
53
+ "@velum-labs/routekit-runtime": "1.0.3"
54
54
  },
55
55
  "keywords": [
56
56
  "routekit",