@velum-labs/routekit-gateway 1.5.0 → 1.5.2

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.
@@ -1,7 +1,10 @@
1
1
  import { type Usage } from "@velum-labs/routekit-contracts/protocol-ir";
2
2
  /**
3
3
  * Translate Chat Completions token accounting to the Responses vocabulary.
4
- * Detail objects are copied so compatible current and future counters survive;
5
- * notably cached_tokens and reasoning_tokens retain their native values.
4
+ * Codex requires cached_tokens and reasoning_tokens when their optional detail
5
+ * objects are present. Emit details only when those counters are known, so an
6
+ * empty provider object cannot break completion parsing and unknown accounting
7
+ * is not replaced by a measured zero. Complete detail objects retain compatible
8
+ * current and future counters.
6
9
  */
7
10
  export declare function chatUsageToResponses(usage: Usage | null | undefined): Record<string, unknown> | null;
@@ -1,8 +1,11 @@
1
1
  import { extensionValue } from "@velum-labs/routekit-contracts/protocol-ir";
2
2
  /**
3
3
  * Translate Chat Completions token accounting to the Responses vocabulary.
4
- * Detail objects are copied so compatible current and future counters survive;
5
- * notably cached_tokens and reasoning_tokens retain their native values.
4
+ * Codex requires cached_tokens and reasoning_tokens when their optional detail
5
+ * objects are present. Emit details only when those counters are known, so an
6
+ * empty provider object cannot break completion parsing and unknown accounting
7
+ * is not replaced by a measured zero. Complete detail objects retain compatible
8
+ * current and future counters.
6
9
  */
7
10
  export function chatUsageToResponses(usage) {
8
11
  if (usage == null)
@@ -16,18 +19,24 @@ export function chatUsageToResponses(usage) {
16
19
  const details = extensionValue(usage.extensions, "openai.chat.usage-details");
17
20
  const inputDetails = details?.promptTokens;
18
21
  const outputDetails = details?.completionTokens;
22
+ const cachedTokens = inputDetails?.cached_tokens ?? usage.cachedInputTokens;
23
+ const reasoningTokens = outputDetails?.reasoning_tokens;
19
24
  if (inputTokens === undefined &&
20
25
  outputTokens === undefined &&
21
26
  totalTokens === undefined &&
22
- inputDetails == null &&
23
- outputDetails == null) {
27
+ cachedTokens == null &&
28
+ reasoningTokens == null) {
24
29
  return null;
25
30
  }
26
31
  return {
27
32
  ...(inputTokens !== undefined ? { input_tokens: inputTokens } : {}),
28
33
  ...(outputTokens !== undefined ? { output_tokens: outputTokens } : {}),
29
34
  ...(totalTokens !== undefined ? { total_tokens: totalTokens } : {}),
30
- ...(inputDetails != null ? { input_tokens_details: { ...inputDetails } } : {}),
31
- ...(outputDetails != null ? { output_tokens_details: { ...outputDetails } } : {})
35
+ ...(cachedTokens != null
36
+ ? { input_tokens_details: { ...inputDetails, cached_tokens: cachedTokens } }
37
+ : {}),
38
+ ...(reasoningTokens != null
39
+ ? { output_tokens_details: { ...outputDetails, reasoning_tokens: reasoningTokens } }
40
+ : {})
32
41
  };
33
42
  }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,172 @@
1
+ import assert from "node:assert/strict";
2
+ import { test } from "node:test";
3
+ import { AnthropicBackend, decodeBufferedSse } from "@velum-labs/routekit-provider-core";
4
+ import { Effect } from "effect";
5
+ import { startGateway } from "../gateway-service.js";
6
+ import { parseUsageFromSse } from "../observability/cost.js";
7
+ import { asTransport, sse } from "./provider-backends-fixtures.js";
8
+ const MODEL = "claude-stream-usage-test";
9
+ const INITIAL_USAGE = {
10
+ input_tokens: 64,
11
+ output_tokens: 0,
12
+ cache_read_input_tokens: 8,
13
+ cache_creation_input_tokens: 2
14
+ };
15
+ const cases = [
16
+ { name: "split counters", initial: INITIAL_USAGE, terminal: { output_tokens: 26 }, total: 100 },
17
+ {
18
+ name: "full terminal counters",
19
+ initial: INITIAL_USAGE,
20
+ terminal: { ...INITIAL_USAGE, output_tokens: 26 },
21
+ total: 100
22
+ },
23
+ {
24
+ name: "terminal null input and cache counters",
25
+ initial: INITIAL_USAGE,
26
+ terminal: {
27
+ input_tokens: null,
28
+ output_tokens: 26,
29
+ cache_read_input_tokens: null,
30
+ cache_creation_input_tokens: null
31
+ },
32
+ total: 100
33
+ },
34
+ {
35
+ name: "provider-reported initial total",
36
+ initial: { ...INITIAL_USAGE, total_tokens: 123 },
37
+ terminal: { output_tokens: 26 },
38
+ total: 123
39
+ },
40
+ {
41
+ name: "provider-reported terminal total replaces the initial total",
42
+ initial: { ...INITIAL_USAGE, total_tokens: 123 },
43
+ terminal: { output_tokens: 26, total_tokens: 124 },
44
+ total: 124
45
+ }
46
+ ];
47
+ function nativeStream(initial, terminal) {
48
+ return sse([
49
+ {
50
+ event: "message_start",
51
+ data: {
52
+ type: "message_start",
53
+ message: {
54
+ id: "msg_stream_usage",
55
+ type: "message",
56
+ role: "assistant",
57
+ model: MODEL,
58
+ content: [],
59
+ usage: initial
60
+ }
61
+ }
62
+ },
63
+ {
64
+ event: "content_block_start",
65
+ data: {
66
+ type: "content_block_start",
67
+ index: 0,
68
+ content_block: { type: "text", text: "" }
69
+ }
70
+ },
71
+ {
72
+ event: "content_block_delta",
73
+ data: {
74
+ type: "content_block_delta",
75
+ index: 0,
76
+ delta: { type: "text_delta", text: "STREAM_USAGE_OK" }
77
+ }
78
+ },
79
+ { event: "content_block_stop", data: { type: "content_block_stop", index: 0 } },
80
+ {
81
+ event: "message_delta",
82
+ data: { type: "message_delta", delta: { stop_reason: "end_turn" }, usage: terminal }
83
+ },
84
+ { event: "message_stop", data: { type: "message_stop" } }
85
+ ]);
86
+ }
87
+ for (const endpoint of ["chat/completions", "responses"]) {
88
+ for (const fixture of cases) {
89
+ test(`Anthropic ${endpoint} streaming usage keeps ${fixture.name} complete and request-local`, async () => {
90
+ let submissions = 0;
91
+ const backend = new AnthropicBackend({
92
+ baseUrl: "https://api.anthropic.test/v1",
93
+ apiKey: "test-only",
94
+ defaultModel: MODEL,
95
+ transport: asTransport(async (_url, init) => {
96
+ const body = JSON.parse(String(init.body));
97
+ assert.equal(body.stream, true);
98
+ submissions += 1;
99
+ assert.ok(submissions <= 2, "each gateway request must submit upstream exactly once");
100
+ return submissions === 1
101
+ ? nativeStream(fixture.initial, fixture.terminal)
102
+ : nativeStream({ input_tokens: 5, output_tokens: 0 }, { output_tokens: 3 });
103
+ })
104
+ });
105
+ const gateway = await startGateway({ backend });
106
+ try {
107
+ for (const requestNumber of [1, 2]) {
108
+ const response = await fetch(`${gateway.url()}/v1/${endpoint}`, {
109
+ method: "POST",
110
+ headers: { "content-type": "application/json" },
111
+ body: JSON.stringify({
112
+ model: MODEL,
113
+ stream: true,
114
+ ...(endpoint === "responses"
115
+ ? { input: "hello" }
116
+ : {
117
+ messages: [{ role: "user", content: "hello" }],
118
+ stream_options: { include_usage: true }
119
+ })
120
+ })
121
+ });
122
+ assert.equal(response.status, 200);
123
+ const wire = await response.text();
124
+ assert.match(wire, /STREAM_USAGE_OK/);
125
+ const frames = decodeBufferedSse(wire)
126
+ .filter((event) => event.data.trim() !== "[DONE]")
127
+ .map((event) => JSON.parse(event.data));
128
+ let finalUsage;
129
+ if (endpoint === "responses") {
130
+ const completed = frames.filter((frame) => frame.type === "response.completed");
131
+ assert.equal(completed.length, 1);
132
+ assert.doesNotMatch(wire, /event: response\.(incomplete|failed)/);
133
+ const finalResponse = completed[0]?.response;
134
+ assert.equal(finalResponse.status, "completed");
135
+ finalUsage = finalResponse.usage;
136
+ }
137
+ else {
138
+ const terminal = frames.filter((frame) => frame.choices.some((choice) => choice.finish_reason != null));
139
+ assert.equal(terminal.length, 1);
140
+ finalUsage = terminal[0]?.usage;
141
+ const initialUsage = frames.find((frame) => frame.usage != null)?.usage;
142
+ assert.equal(initialUsage?.total_tokens, requestNumber === 1
143
+ ? "total_tokens" in fixture.initial
144
+ ? fixture.initial.total_tokens
145
+ : 74
146
+ : 5, "each usage snapshot reports the current cumulative total");
147
+ }
148
+ const firstRequest = requestNumber === 1;
149
+ const input = firstRequest ? 74 : 5;
150
+ const output = firstRequest ? 26 : 3;
151
+ const total = firstRequest ? fixture.total : 8;
152
+ assert.equal(finalUsage[endpoint === "responses" ? "input_tokens" : "prompt_tokens"], input);
153
+ assert.equal(finalUsage[endpoint === "responses" ? "output_tokens" : "completion_tokens"], output);
154
+ assert.equal(finalUsage.total_tokens, total);
155
+ const observed = parseUsageFromSse(wire);
156
+ assert.equal(observed?.promptTokens, input);
157
+ assert.equal(observed?.completionTokens, output);
158
+ assert.equal(observed?.totalTokens, total);
159
+ assert.equal(observed?.cachedInputTokens, firstRequest ? 8 : undefined);
160
+ assert.equal(observed?.reasoningTokens, undefined);
161
+ if (endpoint === "chat/completions") {
162
+ assert.equal(observed?.cacheCreationInputTokens, firstRequest ? 2 : undefined);
163
+ }
164
+ assert.equal(submissions, requestNumber);
165
+ }
166
+ }
167
+ finally {
168
+ await Effect.runPromise(gateway.close);
169
+ }
170
+ });
171
+ }
172
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,107 @@
1
+ import assert from "node:assert/strict";
2
+ import { test } from "node:test";
3
+ import { AnthropicBackend } from "@velum-labs/routekit-provider-core";
4
+ import { runRouteKitEffect } from "@velum-labs/routekit-runtime/effect";
5
+ import { Effect } from "effect";
6
+ import { handleResponses } from "../adapters/responses.js";
7
+ const MODEL = "claude-fable-5-1";
8
+ const TOOL = "staging_subscription_echo";
9
+ const PARTIAL_ARGUMENTS = { marker: "RO" };
10
+ const COMPLETE_MARKER = "ROUTEKIT_CLAUDE_TOOL_OK";
11
+ function refusalResponse(stream) {
12
+ const tool = { type: "tool_use", id: "toolu_refused", name: TOOL };
13
+ const usage = { input_tokens: 467, output_tokens: 36 };
14
+ if (!stream) {
15
+ return Response.json({
16
+ id: "msg_refused",
17
+ type: "message",
18
+ role: "assistant",
19
+ model: MODEL,
20
+ content: [{ ...tool, input: PARTIAL_ARGUMENTS }],
21
+ stop_reason: "refusal",
22
+ usage
23
+ });
24
+ }
25
+ const events = [
26
+ {
27
+ type: "message_start",
28
+ message: { id: "msg_refused", content: [], usage: { input_tokens: 467, output_tokens: 0 } }
29
+ },
30
+ { type: "content_block_start", index: 0, content_block: { ...tool, input: {} } },
31
+ {
32
+ type: "content_block_delta",
33
+ index: 0,
34
+ delta: { type: "input_json_delta", partial_json: '{"marker":"' }
35
+ },
36
+ {
37
+ type: "content_block_delta",
38
+ index: 0,
39
+ delta: { type: "input_json_delta", partial_json: 'RO"}' }
40
+ },
41
+ { type: "content_block_stop", index: 0 },
42
+ { type: "message_delta", delta: { stop_reason: "refusal" }, usage },
43
+ { type: "message_stop" }
44
+ ];
45
+ return new Response(events.map((event) => `event: ${event.type}\ndata: ${JSON.stringify(event)}\n\n`).join(""), { headers: { "content-type": "text/event-stream" } });
46
+ }
47
+ for (const stream of [false, true]) {
48
+ test(`Claude ${stream ? "streamed" : "buffered"} refusal remains an incomplete Responses tool turn`, async () => {
49
+ const submitted = [];
50
+ const backend = new AnthropicBackend({
51
+ baseUrl: "https://anthropic.test/v1",
52
+ apiKey: "test-only",
53
+ defaultModel: MODEL,
54
+ transport: (_url, init) => {
55
+ submitted.push(JSON.parse(String(init.body)));
56
+ return Effect.succeed(refusalResponse(stream));
57
+ }
58
+ });
59
+ const response = await runRouteKitEffect(handleResponses(backend, {
60
+ model: MODEL,
61
+ stream,
62
+ input: `Call ${TOOL} with marker ${COMPLETE_MARKER}.`,
63
+ tool_choice: "auto",
64
+ tools: [
65
+ {
66
+ type: "function",
67
+ name: TOOL,
68
+ parameters: {
69
+ type: "object",
70
+ properties: { marker: { type: "string" } },
71
+ required: ["marker"]
72
+ }
73
+ }
74
+ ]
75
+ }));
76
+ assert.equal(response.status, 200, "provider refusal is a terminal model outcome");
77
+ assert.equal(submitted.length, 1, "the gateway must not issue a follow-up call after refusal");
78
+ assert.equal(submitted[0]?.stream, stream);
79
+ assert.deepEqual(submitted[0]?.tool_choice, { type: "auto" });
80
+ let terminal;
81
+ if (stream) {
82
+ const wire = await response.text();
83
+ const events = wire
84
+ .split("\n\n")
85
+ .flatMap((frame) => {
86
+ const data = frame.split("\n").find((line) => line.startsWith("data: "));
87
+ return data === undefined ? [] : [JSON.parse(data.slice(6))];
88
+ });
89
+ assert.equal(events.filter((event) => event.type === "response.completed").length, 0);
90
+ const incomplete = events.filter((event) => event.type === "response.incomplete");
91
+ assert.equal(incomplete.length, 1);
92
+ terminal = incomplete[0]?.response;
93
+ }
94
+ else {
95
+ terminal = (await response.json());
96
+ }
97
+ assert.equal(terminal.status, "incomplete");
98
+ assert.deepEqual(terminal.incomplete_details, { reason: "content_filter" });
99
+ const output = terminal.output;
100
+ assert.equal(output.length, 1);
101
+ assert.equal(output[0]?.type, "function_call");
102
+ assert.equal(output[0]?.call_id, "toolu_refused");
103
+ assert.equal(output[0]?.name, TOOL);
104
+ assert.deepEqual(JSON.parse(String(output[0]?.arguments)), PARTIAL_ARGUMENTS);
105
+ assert.notEqual(JSON.parse(String(output[0]?.arguments)).marker, COMPLETE_MARKER);
106
+ });
107
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,213 @@
1
+ import assert from "node:assert/strict";
2
+ import { test } from "node:test";
3
+ import { AnthropicBackend } from "@velum-labs/routekit-provider-core";
4
+ import { Effect } from "effect";
5
+ import { chatUsageToResponses } from "../adapters/responses-usage.js";
6
+ import { startGateway } from "../gateway-service.js";
7
+ import { parseUsage, parseUsageFromSse } from "../observability/cost.js";
8
+ import { asTransport, sse } from "./provider-backends-fixtures.js";
9
+ test("generated Responses usage omits unknown details without changing provider accounting", () => {
10
+ for (const details of [
11
+ undefined,
12
+ { promptTokens: {}, completionTokens: {} },
13
+ { promptTokens: { audio_tokens: 1 }, completionTokens: { accepted_prediction_tokens: 2 } }
14
+ ]) {
15
+ const usage = Object.freeze({
16
+ inputTokens: 64,
17
+ outputTokens: 26,
18
+ ...(details === undefined
19
+ ? {}
20
+ : {
21
+ extensions: Object.freeze([
22
+ {
23
+ namespace: "openai.chat.usage-details",
24
+ value: Object.freeze({
25
+ promptTokens: Object.freeze(details.promptTokens),
26
+ completionTokens: Object.freeze(details.completionTokens)
27
+ })
28
+ }
29
+ ])
30
+ })
31
+ });
32
+ const before = structuredClone(usage);
33
+ const wire = chatUsageToResponses(usage);
34
+ assert.equal(wire?.input_tokens, 64);
35
+ assert.equal(wire?.output_tokens, 26);
36
+ assert.equal(wire?.total_tokens, 90);
37
+ assert.equal(wire?.input_tokens_details, undefined);
38
+ assert.equal(wire?.output_tokens_details, undefined);
39
+ assert.deepEqual(parseUsage(wire), { promptTokens: 64, completionTokens: 26, totalTokens: 90 });
40
+ assert.deepEqual(usage, before);
41
+ }
42
+ assert.equal(chatUsageToResponses(undefined), null);
43
+ assert.equal(chatUsageToResponses(null), null);
44
+ assert.equal(chatUsageToResponses({}), null);
45
+ assert.equal(chatUsageToResponses({
46
+ extensions: [
47
+ {
48
+ namespace: "openai.chat.usage-details",
49
+ value: { promptTokens: {}, completionTokens: { accepted_prediction_tokens: 2 } }
50
+ }
51
+ ]
52
+ }), null);
53
+ });
54
+ test("generated Responses usage preserves measured details and cached-input accounting", () => {
55
+ const usage = {
56
+ inputTokens: 12,
57
+ outputTokens: 7,
58
+ cachedInputTokens: 6,
59
+ extensions: [
60
+ {
61
+ namespace: "openai.chat.usage-details",
62
+ value: {
63
+ promptTokens: { cached_tokens: 8, cache_write_tokens: 2, audio_tokens: 1 },
64
+ completionTokens: { reasoning_tokens: 5, accepted_prediction_tokens: 2 }
65
+ }
66
+ }
67
+ ]
68
+ };
69
+ assert.deepEqual(chatUsageToResponses(usage), {
70
+ input_tokens: 12,
71
+ output_tokens: 7,
72
+ total_tokens: 19,
73
+ input_tokens_details: { cached_tokens: 8, cache_write_tokens: 2, audio_tokens: 1 },
74
+ output_tokens_details: { reasoning_tokens: 5, accepted_prediction_tokens: 2 }
75
+ });
76
+ assert.deepEqual(parseUsage(chatUsageToResponses(usage)), {
77
+ promptTokens: 12,
78
+ cachedInputTokens: 8,
79
+ completionTokens: 7,
80
+ reasoningTokens: 5,
81
+ totalTokens: 19
82
+ });
83
+ assert.deepEqual(chatUsageToResponses({
84
+ inputTokens: 12,
85
+ outputTokens: 7,
86
+ extensions: [
87
+ {
88
+ namespace: "openai.chat.usage-details",
89
+ value: { promptTokens: { cached_tokens: 0 }, completionTokens: { reasoning_tokens: 0 } }
90
+ }
91
+ ]
92
+ })?.output_tokens_details, { reasoning_tokens: 0 });
93
+ assert.deepEqual(chatUsageToResponses({ inputTokens: 12, outputTokens: 7, cachedInputTokens: 6 })
94
+ ?.input_tokens_details, {
95
+ cached_tokens: 6
96
+ });
97
+ });
98
+ for (const { stream, providerTotal } of [
99
+ { stream: false, providerTotal: undefined },
100
+ { stream: true, providerTotal: undefined },
101
+ { stream: true, providerTotal: 123 }
102
+ ]) {
103
+ test(`Claude Messages ${stream ? "SSE" : "JSON"} completes with Codex-compatible Responses usage${providerTotal === undefined ? "" : " and a provider-reported total"}`, async () => {
104
+ let submissions = 0;
105
+ const backend = new AnthropicBackend({
106
+ baseUrl: "https://api.anthropic.test/v1",
107
+ apiKey: "unused",
108
+ defaultModel: "claude-fable-5.1",
109
+ transport: asTransport(async (_url, init) => {
110
+ submissions += 1;
111
+ const body = JSON.parse(String(init.body));
112
+ assert.equal(body.stream === true, stream);
113
+ return stream
114
+ ? sse([
115
+ {
116
+ event: "message_start",
117
+ data: {
118
+ type: "message_start",
119
+ message: {
120
+ id: "msg_claude_usage",
121
+ type: "message",
122
+ role: "assistant",
123
+ model: "claude-fable-5.1",
124
+ content: [],
125
+ usage: {
126
+ input_tokens: 64,
127
+ output_tokens: 0,
128
+ cache_read_input_tokens: 0,
129
+ cache_creation_input_tokens: 0,
130
+ ...(providerTotal === undefined ? {} : { total_tokens: providerTotal })
131
+ }
132
+ }
133
+ }
134
+ },
135
+ {
136
+ event: "content_block_start",
137
+ data: {
138
+ type: "content_block_start",
139
+ index: 0,
140
+ content_block: { type: "text", text: "" }
141
+ }
142
+ },
143
+ {
144
+ event: "content_block_delta",
145
+ data: {
146
+ type: "content_block_delta",
147
+ index: 0,
148
+ delta: { type: "text_delta", text: "CLAUDE_USAGE_OK" }
149
+ }
150
+ },
151
+ { event: "content_block_stop", data: { type: "content_block_stop", index: 0 } },
152
+ {
153
+ event: "message_delta",
154
+ data: {
155
+ type: "message_delta",
156
+ delta: { stop_reason: "end_turn" },
157
+ usage: { output_tokens: 26 }
158
+ }
159
+ },
160
+ { event: "message_stop", data: { type: "message_stop" } }
161
+ ])
162
+ : Response.json({
163
+ id: "msg_claude_usage",
164
+ type: "message",
165
+ role: "assistant",
166
+ model: "claude-fable-5.1",
167
+ content: [{ type: "text", text: "CLAUDE_USAGE_OK" }],
168
+ stop_reason: "end_turn",
169
+ usage: { input_tokens: 64, output_tokens: 26, cache_read_input_tokens: 0 }
170
+ });
171
+ })
172
+ });
173
+ const gateway = await startGateway({ backend });
174
+ try {
175
+ const response = await fetch(`${gateway.url()}/v1/responses`, {
176
+ method: "POST",
177
+ headers: { "content-type": "application/json" },
178
+ body: JSON.stringify({ input: "hello", stream })
179
+ });
180
+ assert.equal(response.status, 200);
181
+ const text = await response.text();
182
+ assert.match(text, /CLAUDE_USAGE_OK/);
183
+ const terminal = stream
184
+ ? text.split("\n\n").filter((event) => event.startsWith("event: response.completed"))
185
+ : [];
186
+ if (stream) {
187
+ assert.equal(terminal.length, 1);
188
+ assert.doesNotMatch(text, /event: response\.(incomplete|failed)/);
189
+ }
190
+ const completed = stream
191
+ ? JSON.parse(terminal[0].slice(terminal[0].indexOf("data:") + 5)).response
192
+ : JSON.parse(text);
193
+ assert.equal(completed.status, "completed");
194
+ const usage = completed.usage;
195
+ assert.equal(usage.input_tokens, 64);
196
+ assert.equal(usage.output_tokens, 26);
197
+ assert.equal(usage.total_tokens, providerTotal ?? 90);
198
+ assert.deepEqual(usage.input_tokens_details, { cached_tokens: 0 });
199
+ assert.equal(usage.output_tokens_details, undefined);
200
+ const observed = stream ? parseUsageFromSse(text) : parseUsage(usage);
201
+ assert.deepEqual(observed, {
202
+ promptTokens: 64,
203
+ cachedInputTokens: 0,
204
+ completionTokens: 26,
205
+ totalTokens: providerTotal ?? 90
206
+ });
207
+ assert.equal(submissions, 1);
208
+ }
209
+ finally {
210
+ await Effect.runPromise(gateway.close);
211
+ }
212
+ });
213
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@velum-labs/routekit-gateway",
3
3
  "private": false,
4
- "version": "1.5.0",
4
+ "version": "1.5.2",
5
5
  "repository": {
6
6
  "type": "git",
7
7
  "url": "git+https://github.com/velum-labs/routekit.git",
@@ -43,13 +43,13 @@
43
43
  },
44
44
  "dependencies": {
45
45
  "effect": "4.0.0-rc.108",
46
- "@velum-labs/routekit-config-core": "1.5.0",
47
- "@velum-labs/routekit-contracts": "1.5.0",
48
- "@velum-labs/routekit-eval-contracts": "1.5.0",
49
- "@velum-labs/routekit-eval-core": "1.5.0",
50
- "@velum-labs/routekit-registry": "1.5.0",
51
- "@velum-labs/routekit-runtime": "1.5.0",
52
- "@velum-labs/routekit-provider-core": "1.5.0"
46
+ "@velum-labs/routekit-config-core": "1.5.2",
47
+ "@velum-labs/routekit-contracts": "1.5.2",
48
+ "@velum-labs/routekit-eval-contracts": "1.5.2",
49
+ "@velum-labs/routekit-eval-core": "1.5.2",
50
+ "@velum-labs/routekit-registry": "1.5.2",
51
+ "@velum-labs/routekit-runtime": "1.5.2",
52
+ "@velum-labs/routekit-provider-core": "1.5.2"
53
53
  },
54
54
  "keywords": [
55
55
  "routekit",