@velum-labs/routekit-daemon 1.3.2 → 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.
Files changed (172) hide show
  1. package/dist/account-enroll-service.d.ts +1 -3
  2. package/dist/account-enroll-service.js +262 -262
  3. package/dist/account-mutation-service.d.ts +1 -3
  4. package/dist/account-mutation-service.js +302 -283
  5. package/dist/account-query-service.d.ts +1 -3
  6. package/dist/account-query-service.js +88 -86
  7. package/dist/call-attribution-store.js +544 -1
  8. package/dist/control-dispatch.js +9 -15
  9. package/dist/daemon-bootstrap-preflight.js +1 -1
  10. package/dist/daemon-control-handlers.d.ts +1 -1
  11. package/dist/daemon-control-handlers.js +24 -27
  12. package/dist/daemon-generations.d.ts +14 -28
  13. package/dist/daemon-generations.js +114 -53
  14. package/dist/daemon-lifecycle-service.d.ts +1 -3
  15. package/dist/daemon-lifecycle-service.js +88 -94
  16. package/dist/daemon-maintenance.d.ts +27 -49
  17. package/dist/daemon-maintenance.js +5 -1
  18. package/dist/daemon-options.d.ts +3 -2
  19. package/dist/daemon-policy-context.d.ts +1 -0
  20. package/dist/daemon-policy-context.js +1 -0
  21. package/dist/daemon-runtime-state.d.ts +1 -1
  22. package/dist/daemon-runtime-state.js +2 -3
  23. package/dist/daemon-state-context.d.ts +1 -1
  24. package/dist/daemon-state-context.js +1 -38
  25. package/dist/doctor-service.d.ts +1 -3
  26. package/dist/doctor-service.js +87 -91
  27. package/dist/effect/daemon-live.d.ts +7 -6
  28. package/dist/effect/daemon-live.js +57 -135
  29. package/dist/eval-routing-service.d.ts +1 -3
  30. package/dist/eval-routing-service.js +45 -69
  31. package/dist/eval-session-execution-store.d.ts +24 -0
  32. package/dist/eval-session-execution-store.js +253 -0
  33. package/dist/host-protocol.d.ts +1 -0
  34. package/dist/host.js +3 -0
  35. package/dist/internal-providers/provider-anthropic/index.d.ts +23 -0
  36. package/dist/internal-providers/provider-anthropic/index.js +42 -0
  37. package/dist/internal-providers/provider-anthropic/test/provider.test.js +33 -0
  38. package/dist/internal-providers/provider-azure/index.d.ts +25 -0
  39. package/dist/internal-providers/provider-azure/index.js +101 -0
  40. package/dist/internal-providers/provider-azure/test/provider.test.js +83 -0
  41. package/dist/internal-providers/provider-bedrock/codec.d.ts +11 -0
  42. package/dist/internal-providers/provider-bedrock/codec.js +484 -0
  43. package/dist/internal-providers/provider-bedrock/connection-settings.d.ts +6 -0
  44. package/dist/internal-providers/provider-bedrock/connection-settings.js +41 -0
  45. package/dist/internal-providers/provider-bedrock/connection.d.ts +17 -0
  46. package/dist/internal-providers/provider-bedrock/connection.js +143 -0
  47. package/dist/internal-providers/provider-bedrock/index.d.ts +27 -0
  48. package/dist/internal-providers/provider-bedrock/index.js +47 -0
  49. package/dist/internal-providers/provider-bedrock/source.d.ts +39 -0
  50. package/dist/internal-providers/provider-bedrock/source.js +454 -0
  51. package/dist/internal-providers/provider-bedrock/test/bedrock-source.test.d.ts +1 -0
  52. package/dist/internal-providers/provider-bedrock/test/bedrock-source.test.js +786 -0
  53. package/dist/internal-providers/provider-bedrock/test/connection.test.d.ts +1 -0
  54. package/dist/internal-providers/provider-bedrock/test/connection.test.js +249 -0
  55. package/dist/internal-providers/provider-bedrock/test/workload-credentials.test.d.ts +1 -0
  56. package/dist/internal-providers/provider-bedrock/test/workload-credentials.test.js +29 -0
  57. package/dist/internal-providers/provider-bedrock/workload-credentials.d.ts +5 -0
  58. package/dist/internal-providers/provider-bedrock/workload-credentials.js +14 -0
  59. package/dist/internal-providers/provider-claude-code/index.d.ts +6 -0
  60. package/dist/internal-providers/provider-claude-code/index.js +4 -0
  61. package/dist/internal-providers/provider-claude-code/module-api.d.ts +25 -0
  62. package/dist/internal-providers/provider-claude-code/module-api.js +31 -0
  63. package/dist/internal-providers/provider-claude-code/relay.d.ts +20 -0
  64. package/dist/internal-providers/provider-claude-code/relay.js +115 -0
  65. package/dist/internal-providers/provider-claude-code/subscription.d.ts +2 -0
  66. package/dist/internal-providers/provider-claude-code/subscription.js +194 -0
  67. package/dist/internal-providers/provider-claude-code/test/http-submission.test.d.ts +1 -0
  68. package/dist/internal-providers/provider-claude-code/test/http-submission.test.js +140 -0
  69. package/dist/internal-providers/provider-claude-code/test/relay.test.d.ts +1 -0
  70. package/dist/internal-providers/provider-claude-code/test/relay.test.js +379 -0
  71. package/dist/internal-providers/provider-claude-code/test/subscription.test.d.ts +1 -0
  72. package/dist/internal-providers/provider-claude-code/test/subscription.test.js +256 -0
  73. package/dist/internal-providers/provider-cliproxy/index.d.ts +9 -0
  74. package/dist/internal-providers/provider-cliproxy/index.js +15 -0
  75. package/dist/internal-providers/provider-codex/index.d.ts +6 -0
  76. package/dist/internal-providers/provider-codex/index.js +7 -0
  77. package/dist/internal-providers/provider-codex/module-api.d.ts +25 -0
  78. package/dist/internal-providers/provider-codex/module-api.js +31 -0
  79. package/dist/internal-providers/provider-codex/relay.d.ts +123 -0
  80. package/dist/internal-providers/provider-codex/relay.js +261 -0
  81. package/dist/internal-providers/provider-codex/subscription.d.ts +2 -0
  82. package/dist/internal-providers/provider-codex/subscription.js +555 -0
  83. package/dist/internal-providers/provider-codex/test/http-submission.test.d.ts +1 -0
  84. package/dist/internal-providers/provider-codex/test/http-submission.test.js +142 -0
  85. package/dist/internal-providers/provider-codex/test/relay.test.d.ts +1 -0
  86. package/dist/internal-providers/provider-codex/test/relay.test.js +600 -0
  87. package/dist/internal-providers/provider-codex/test/subscription.test.d.ts +1 -0
  88. package/dist/internal-providers/provider-codex/test/subscription.test.js +70 -0
  89. package/dist/internal-providers/provider-codex/test/translated-content.test.d.ts +1 -0
  90. package/dist/internal-providers/provider-codex/test/translated-content.test.js +91 -0
  91. package/dist/internal-providers/provider-custom/index.d.ts +9 -0
  92. package/dist/internal-providers/provider-custom/index.js +15 -0
  93. package/dist/internal-providers/provider-google/index.d.ts +23 -0
  94. package/dist/internal-providers/provider-google/index.js +43 -0
  95. package/dist/internal-providers/provider-google/test/provider.test.d.ts +1 -0
  96. package/dist/internal-providers/provider-google/test/provider.test.js +34 -0
  97. package/dist/internal-providers/provider-google-vertex/index.d.ts +28 -0
  98. package/dist/internal-providers/provider-google-vertex/index.js +137 -0
  99. package/dist/internal-providers/provider-google-vertex/test/provider.test.d.ts +1 -0
  100. package/dist/internal-providers/provider-google-vertex/test/provider.test.js +85 -0
  101. package/dist/internal-providers/provider-openai/documented-reasoning.d.ts +9 -0
  102. package/dist/internal-providers/provider-openai/documented-reasoning.js +26 -0
  103. package/dist/internal-providers/provider-openai/index.d.ts +23 -0
  104. package/dist/internal-providers/provider-openai/index.js +74 -0
  105. package/dist/internal-providers/provider-openai/test/provider.test.d.ts +1 -0
  106. package/dist/internal-providers/provider-openai/test/provider.test.js +268 -0
  107. package/dist/internal-providers/provider-openai-compatible/index.d.ts +9 -0
  108. package/dist/internal-providers/provider-openai-compatible/index.js +15 -0
  109. package/dist/internal-providers/provider-openai-compatible/test/provider.test.d.ts +1 -0
  110. package/dist/internal-providers/provider-openai-compatible/test/provider.test.js +40 -0
  111. package/dist/internal-providers/provider-openrouter/connection.d.ts +5 -0
  112. package/dist/internal-providers/provider-openrouter/connection.js +143 -0
  113. package/dist/internal-providers/provider-openrouter/index.d.ts +28 -0
  114. package/dist/internal-providers/provider-openrouter/index.js +42 -0
  115. package/dist/internal-providers/provider-openrouter/pricing.d.ts +4 -0
  116. package/dist/internal-providers/provider-openrouter/pricing.js +108 -0
  117. package/dist/internal-providers/provider-openrouter/test/connection.test.d.ts +1 -0
  118. package/dist/internal-providers/provider-openrouter/test/connection.test.js +71 -0
  119. package/dist/internal-providers/provider-openrouter/test/pricing.test.d.ts +1 -0
  120. package/dist/internal-providers/provider-openrouter/test/pricing.test.js +45 -0
  121. package/dist/internal-providers/provider-openrouter/test/provider.test.d.ts +1 -0
  122. package/dist/internal-providers/provider-openrouter/test/provider.test.js +141 -0
  123. package/dist/internal-providers/provider-openrouter/test/validation.test.d.ts +1 -0
  124. package/dist/internal-providers/provider-openrouter/test/validation.test.js +225 -0
  125. package/dist/launcher-service.d.ts +3 -7
  126. package/dist/launcher-service.js +144 -133
  127. package/dist/provider-modules.d.ts +6 -0
  128. package/dist/provider-modules.js +55 -0
  129. package/dist/provider-query-service.d.ts +1 -3
  130. package/dist/provider-query-service.js +140 -143
  131. package/dist/router-generation-service.d.ts +1 -3
  132. package/dist/router-generation-service.js +83 -78
  133. package/dist/services/eval-session/service.d.ts +20 -8
  134. package/dist/services/eval-session/service.js +410 -40
  135. package/dist/services/gateway-generation/service.d.ts +7 -3
  136. package/dist/services/gateway-generation/service.js +126 -63
  137. package/dist/services/generations/service.d.ts +3 -3
  138. package/dist/services/generations/service.js +5 -1
  139. package/dist/telemetry-application-service.d.ts +1 -3
  140. package/dist/telemetry-application-service.js +41 -45
  141. package/dist/test/call-attribution-store.test.js +186 -0
  142. package/dist/test/daemon-accounts.test.js +2 -2
  143. package/dist/test/daemon-data-plane.test.js +24 -45
  144. package/dist/test/daemon-fixtures.js +1 -1
  145. package/dist/test/daemon-generation-transaction.test.js +66 -2
  146. package/dist/test/daemon-runtime-state.test.js +28 -1
  147. package/dist/test/effect-daemon.test.js +38 -0
  148. package/dist/test/eval-routing-application-service.test.js +6 -6
  149. package/dist/test/eval-session-execution.test.d.ts +1 -0
  150. package/dist/test/eval-session-execution.test.js +356 -0
  151. package/dist/test/eval-session-service.test.js +70 -1
  152. package/dist/test/host-worker-session.test.js +1 -0
  153. package/dist/test/launcher-session.test.d.ts +1 -0
  154. package/dist/test/launcher-session.test.js +87 -0
  155. package/dist/test/leaderboard.test.js +25 -1
  156. package/dist/test/provider-modules.test.d.ts +1 -0
  157. package/dist/test/provider-modules.test.js +65 -0
  158. package/dist/test/routekit-upstream-sources.test.d.ts +1 -0
  159. package/dist/test/routekit-upstream-sources.test.js +40 -0
  160. package/dist/test/runtime-composition-types.test.js +11 -0
  161. package/dist/token-application-service.d.ts +1 -3
  162. package/dist/token-application-service.js +38 -44
  163. package/dist/worker.js +5 -3
  164. package/package.json +35 -11
  165. package/dist/application-services.d.ts +0 -10
  166. package/dist/application-services.js +0 -22
  167. package/dist/daemon-lifecycle.d.ts +0 -54
  168. package/dist/daemon-lifecycle.js +0 -177
  169. package/dist/test/application-services.test.js +0 -135
  170. package/dist/test/daemon-lifecycle.test.js +0 -182
  171. /package/dist/{test/application-services.test.d.ts → internal-providers/provider-anthropic/test/provider.test.d.ts} +0 -0
  172. /package/dist/{test/daemon-lifecycle.test.d.ts → internal-providers/provider-azure/test/provider.test.d.ts} +0 -0
@@ -0,0 +1,268 @@
1
+ import assert from "node:assert/strict";
2
+ import { test } from "node:test";
3
+ import { createConnectedProviderSource, OpenAiApiSpecificationClient } from "@velum-labs/routekit-provider-core";
4
+ import { Data, Effect } from "effect";
5
+ import { openai } from "../index.js";
6
+ class TestFailure extends Data.TaggedError("TestFailure") {
7
+ }
8
+ function context() {
9
+ const prepared = openai.connection.prepare({
10
+ sourceId: "openai-production",
11
+ methodId: "api-key",
12
+ values: { "api-key": "offline-openai-secret" }
13
+ });
14
+ return {
15
+ sourceId: "openai-production",
16
+ config: prepared.config,
17
+ env: prepared.environment
18
+ };
19
+ }
20
+ function specification(modelIds) {
21
+ return {
22
+ openapi: "3.1.0",
23
+ paths: {
24
+ "/responses": {
25
+ post: {
26
+ requestBody: {
27
+ content: {
28
+ "application/json": {
29
+ schema: {
30
+ $ref: "#/components/schemas/CreateResponse"
31
+ }
32
+ }
33
+ }
34
+ }
35
+ }
36
+ }
37
+ },
38
+ components: {
39
+ schemas: {
40
+ CreateResponse: {
41
+ allOf: [
42
+ {
43
+ $ref: "#/components/schemas/ResponseProperties"
44
+ }
45
+ ]
46
+ },
47
+ ResponseProperties: {
48
+ type: "object",
49
+ properties: {
50
+ model: {
51
+ type: "string",
52
+ enum: modelIds
53
+ },
54
+ tools: {
55
+ type: "array"
56
+ },
57
+ tool_choice: {
58
+ type: "string"
59
+ }
60
+ }
61
+ }
62
+ }
63
+ }
64
+ };
65
+ }
66
+ function specificationClient(modelIds, requests = []) {
67
+ const transport = (_url, init) => {
68
+ requests.push(init);
69
+ return Effect.succeed(Response.json(specification(modelIds)));
70
+ };
71
+ return new OpenAiApiSpecificationClient({ transport });
72
+ }
73
+ function discover(source) {
74
+ return Effect.runPromise(source.discovery.discoverModels());
75
+ }
76
+ test("a configured OpenAI inventory does not enroll an invalid key", async () => {
77
+ const ctx = context();
78
+ let calls = 0;
79
+ const transport = (url, init) => {
80
+ calls++;
81
+ assert.equal(url, "https://api.openai.com/v1/models");
82
+ assert.equal(init.redirect, "error");
83
+ return Effect.succeed(Response.json({ error: "invalid key" }, { status: 401 }));
84
+ };
85
+ await assert.rejects(Effect.runPromise(createConnectedProviderSource(openai, {
86
+ ...ctx,
87
+ config: { ...ctx.config, options: { models: ["configured-model"] } },
88
+ resources: { transport }
89
+ }, undefined, { validate: true })), /provider credential validation returned HTTP 401/);
90
+ assert.equal(calls, 1);
91
+ });
92
+ test("a valid configured OpenAI source probes once and retains its explicit inventory", async () => {
93
+ const ctx = context();
94
+ let calls = 0;
95
+ const transport = (_url, init) => {
96
+ calls++;
97
+ assert.equal(init.redirect, "error");
98
+ return Effect.succeed(Response.json({ data: [] }));
99
+ };
100
+ const source = await Effect.runPromise(createConnectedProviderSource(openai, {
101
+ ...ctx,
102
+ config: { ...ctx.config, options: { models: ["configured-model"] } },
103
+ resources: { transport, openaiApiSpecification: specificationClient(["configured-model"]) }
104
+ }, undefined, { validate: true }));
105
+ assert.deepEqual((await discover(source)).map((model) => model.id), ["configured-model"]);
106
+ assert.equal(calls, 1);
107
+ });
108
+ test("OpenAI discovery joins authenticated inventory with the official API specification", async () => {
109
+ const providerRequests = [];
110
+ const specificationRequests = [];
111
+ const transport = (url) => {
112
+ providerRequests.push(url);
113
+ return Effect.succeed(Response.json({
114
+ data: [
115
+ { id: "gpt-6-sol", created: 1_789_405_937 },
116
+ { id: "gpt-4o-mini-2024-07-18" },
117
+ { id: "private-fine-tune" }
118
+ ]
119
+ }));
120
+ };
121
+ const source = await Effect.runPromise(openai.createSource({
122
+ ...context(),
123
+ resources: {
124
+ transport,
125
+ openaiApiSpecification: specificationClient(["gpt-6-astra", "gpt-6-sol"], specificationRequests)
126
+ }
127
+ }));
128
+ assert.deepEqual(await discover(source), [
129
+ {
130
+ id: "gpt-6-sol",
131
+ createdAt: 1_789_405_937,
132
+ capabilities: {
133
+ responses: "supported"
134
+ },
135
+ reasoning: {
136
+ status: "supported",
137
+ efforts: ["none", "low", "medium", "high", "xhigh", "max"].map((id) => ({ id })),
138
+ defaultEffort: "medium",
139
+ wireShape: "openai-responses",
140
+ provenance: "builtin"
141
+ }
142
+ },
143
+ {
144
+ id: "gpt-4o-mini-2024-07-18"
145
+ },
146
+ {
147
+ id: "private-fine-tune"
148
+ }
149
+ ]);
150
+ assert.deepEqual(providerRequests, ["https://api.openai.com/v1/models"]);
151
+ assert.equal(specificationRequests.length, 1);
152
+ });
153
+ test("configured OpenAI inventory is intersected without provider discovery", async () => {
154
+ const ctx = context();
155
+ const providerTransport = () => {
156
+ assert.fail("Configured model inventory must not call provider discovery.");
157
+ };
158
+ const source = await Effect.runPromise(openai.createSource({
159
+ ...ctx,
160
+ config: {
161
+ ...ctx.config,
162
+ options: {
163
+ models: ["gpt-6-sol", "private-model"]
164
+ }
165
+ },
166
+ resources: {
167
+ transport: providerTransport,
168
+ openaiApiSpecification: specificationClient(["gpt-6-sol"])
169
+ }
170
+ }));
171
+ assert.deepEqual(await discover(source), [
172
+ {
173
+ id: "gpt-6-sol",
174
+ capabilities: {
175
+ responses: "supported"
176
+ },
177
+ reasoning: {
178
+ status: "supported",
179
+ efforts: ["none", "low", "medium", "high", "xhigh", "max"].map((id) => ({ id })),
180
+ defaultEffort: "medium",
181
+ wireShape: "openai-responses",
182
+ provenance: "builtin"
183
+ }
184
+ },
185
+ {
186
+ id: "private-model"
187
+ }
188
+ ]);
189
+ });
190
+ test("the OpenAPI specification cannot add an undiscovered model", async () => {
191
+ const transport = () => Effect.succeed(Response.json({ data: [{ id: "private-model" }] }));
192
+ const source = await Effect.runPromise(openai.createSource({
193
+ ...context(),
194
+ resources: {
195
+ transport,
196
+ openaiApiSpecification: specificationClient(["gpt-6-sol"])
197
+ }
198
+ }));
199
+ assert.deepEqual(await discover(source), [{ id: "private-model" }]);
200
+ });
201
+ test("official OpenAI models expose only their documented reasoning efforts", async () => {
202
+ const source = await Effect.runPromise(openai.createSource({
203
+ ...context(),
204
+ config: {
205
+ ...context().config,
206
+ options: { models: ["gpt-6-luna", "gpt-6-sol", "gpt-6-astra", "private-model"] }
207
+ },
208
+ resources: {
209
+ openaiApiSpecification: specificationClient([
210
+ "gpt-6-luna", "gpt-6-sol", "gpt-6-astra"
211
+ ])
212
+ }
213
+ }));
214
+ const models = await discover(source);
215
+ for (const id of ["gpt-6-luna", "gpt-6-sol"]) {
216
+ assert.deepEqual(models.find((model) => model.id === id)?.reasoning?.efforts?.map((effort) => effort.id), ["none", "low", "medium", "high", "xhigh", "max"]);
217
+ assert.equal(source.capabilities.reasoningForModel(id)?.defaultEffort, "medium");
218
+ }
219
+ assert.deepEqual(models.find((model) => model.id === "gpt-6-astra")?.reasoning?.efforts?.map((effort) => effort.id), ["low", "medium", "high", "xhigh", "max"]);
220
+ assert.equal(source.capabilities.reasoningForModel("private-model"), undefined);
221
+ assert.equal(models.find((model) => model.id === "private-model")?.reasoning, undefined);
222
+ });
223
+ test("OpenAPI failure retains the sparse authenticated inventory", async () => {
224
+ const transport = () => Effect.succeed(Response.json({ data: [{ id: "gpt-6-sol" }] }));
225
+ const specificationTransport = () => Effect.fail(new TestFailure({ message: "offline" }));
226
+ const source = await Effect.runPromise(openai.createSource({
227
+ ...context(),
228
+ resources: {
229
+ transport,
230
+ openaiApiSpecification: new OpenAiApiSpecificationClient({
231
+ transport: specificationTransport
232
+ })
233
+ }
234
+ }));
235
+ assert.deepEqual(await discover(source), [{
236
+ id: "gpt-6-sol",
237
+ reasoning: {
238
+ status: "supported",
239
+ efforts: ["none", "low", "medium", "high", "xhigh", "max"].map((id) => ({ id })),
240
+ defaultEffort: "medium",
241
+ wireShape: "openai-responses",
242
+ provenance: "builtin"
243
+ }
244
+ }]);
245
+ });
246
+ test("custom OpenAI-compatible origins do not inherit the official OpenAI specification", async () => {
247
+ const ctx = context();
248
+ const specificationRequests = [];
249
+ const custom = await Effect.runPromise(openai.createSource({
250
+ ...ctx,
251
+ env: {
252
+ ...ctx.env,
253
+ OPENAI_BASE_URL: "https://openai-compatible.example"
254
+ },
255
+ config: {
256
+ ...ctx.config,
257
+ options: {
258
+ models: ["gpt-6-sol"]
259
+ }
260
+ },
261
+ resources: {
262
+ openaiApiSpecification: specificationClient(["gpt-6-sol"], specificationRequests)
263
+ }
264
+ }));
265
+ assert.deepEqual(await discover(custom), [{ id: "gpt-6-sol" }]);
266
+ assert.equal(specificationRequests.length, 0);
267
+ assert.equal(custom.httpSubmission, undefined);
268
+ });
@@ -0,0 +1,9 @@
1
+ import { ApiProviderSource } from "@velum-labs/routekit-provider-core";
2
+ import { Effect } from "effect";
3
+ export declare const openaiCompatible: {
4
+ readonly id: "openai-compatible";
5
+ readonly protocol: "openai";
6
+ readonly accountClass: "api-key";
7
+ readonly billingMode: "metered-api";
8
+ readonly createSource: (context: import("@velum-labs/routekit-provider-core").ProviderModuleContext) => Effect.Effect<ApiProviderSource, never, never>;
9
+ };
@@ -0,0 +1,15 @@
1
+ import { ApiProviderSource, defineProvider } from "@velum-labs/routekit-provider-core";
2
+ import { Effect } from "effect";
3
+ export const openaiCompatible = defineProvider({
4
+ id: "openai-compatible",
5
+ protocol: "openai",
6
+ accountClass: "api-key",
7
+ billingMode: "metered-api",
8
+ createSource: (context) => Effect.sync(() => new ApiProviderSource({
9
+ provider: "openai-compatible",
10
+ sourceId: context.sourceId,
11
+ config: context.config,
12
+ env: context.env,
13
+ nativeResponses: true
14
+ }))
15
+ });
@@ -0,0 +1,40 @@
1
+ import assert from "node:assert/strict";
2
+ import { test } from "node:test";
3
+ import { runRouteKitEffect } from "@velum-labs/routekit-runtime/effect";
4
+ import { openaiCompatible } from "../index.js";
5
+ test("OpenAI-compatible modules expose native Responses without inventing reasoning", async () => {
6
+ let requestUrl;
7
+ const config = {
8
+ use: "openai-compatible",
9
+ baseUrl: "https://compatible.example",
10
+ auth: { kind: "env-bearer", env: "COMPATIBLE_API_KEY" },
11
+ options: { models: ["gpt-5.6-sol"] }
12
+ };
13
+ const source = await runRouteKitEffect(openaiCompatible.createSource({
14
+ sourceId: "compatible",
15
+ config,
16
+ env: { COMPATIBLE_API_KEY: "compatible-secret" }
17
+ }));
18
+ const originalFetch = globalThis.fetch;
19
+ globalThis.fetch = async (input) => {
20
+ requestUrl = String(input);
21
+ return Response.json({
22
+ id: "resp_compatible",
23
+ object: "response",
24
+ output: [],
25
+ status: "completed"
26
+ });
27
+ };
28
+ try {
29
+ assert.equal(source.responses.kind, "responses");
30
+ if (source.responses.kind !== "responses")
31
+ return;
32
+ const response = await runRouteKitEffect(source.responses.execute({ model: "gpt-5.6-sol", input: "hello" }));
33
+ assert.equal(response.status, 200);
34
+ assert.equal(requestUrl, "https://compatible.example/v1/responses");
35
+ assert.equal(source.capabilities.reasoningForModel("gpt-5.6-sol"), undefined);
36
+ }
37
+ finally {
38
+ globalThis.fetch = originalFetch;
39
+ }
40
+ });
@@ -0,0 +1,5 @@
1
+ import { Effect } from "effect";
2
+ import { ProviderEnrollmentError, type ProviderCredentialAdapter, type ProviderEnrollmentAdapter, type ProviderModuleContext } from "@velum-labs/routekit-provider-core";
3
+ export declare const validateOpenRouterConnection: (context: ProviderModuleContext) => Effect.Effect<void, ProviderEnrollmentError, import("effect/unstable/http/HttpClient").HttpClient | import("@effect/platform-node/NodeServices").NodeServices>;
4
+ export declare const openRouterEnrollment: ProviderEnrollmentAdapter;
5
+ export declare const openRouterCredentials: ProviderCredentialAdapter;
@@ -0,0 +1,143 @@
1
+ import { OpenRouterCore } from "@openrouter/sdk/core.js";
2
+ import { oAuthCreateAuthorizationUrl } from "@openrouter/sdk/funcs/oAuthCreateAuthorizationUrl.js";
3
+ import { oAuthExchangeAuthCodeForAPIKey } from "@openrouter/sdk/funcs/oAuthExchangeAuthCodeForAPIKey.js";
4
+ import { HTTPClient } from "@openrouter/sdk/lib/http.js";
5
+ import { calculatePKCECodeChallenge, generateRandomCodeVerifier } from "oauth4webapi";
6
+ import { Effect } from "effect";
7
+ import { z } from "zod";
8
+ import { ProviderEnrollmentError, authHeaders, defaultProviderTransport, preparedProviderConnection, providerCredential, providerMetadata, providerSourceTransportFromContext, providerUrl } from "@velum-labs/routekit-provider-core";
9
+ const keyEnvironment = "ROUTEKIT_CLOUD_GRANT_OPENROUTER_KEY";
10
+ const stateSchema = z.object({ verifier: z.string().min(43) }).strict();
11
+ const keySchema = z.object({ key: z.string().min(1) }).strict();
12
+ const currentKeySchema = z.object({ data: z.object({ label: z.string() }) });
13
+ const keyResponseMaxBytes = 64 * 1024;
14
+ export const validateOpenRouterConnection = Effect.fn("OpenRouter.validateConnection")(function* (context) {
15
+ const { url, headers, transport } = yield* Effect.try({
16
+ try: () => {
17
+ const info = providerMetadata("openrouter", context.config);
18
+ const probe = info.keyProbe;
19
+ const auth = context.config.auth;
20
+ const credential = auth?.kind === "env-bearer" || auth?.kind === "env-header"
21
+ ? context.env[auth.env]
22
+ : providerCredential("openrouter", info, context.env);
23
+ if (!credential)
24
+ throw new Error("Missing credential");
25
+ return {
26
+ // Match discovery/inference resolution, including custom proxy path prefixes.
27
+ url: providerUrl(context.config.baseUrl ?? info.baseUrl, probe.path),
28
+ headers: {
29
+ accept: "application/json",
30
+ ...info.attributionHeaders,
31
+ ...probe.extraHeaders,
32
+ ...(auth?.kind === "env-header"
33
+ ? { [auth.header]: credential }
34
+ : authHeaders(probe.auth, credential))
35
+ },
36
+ transport: providerSourceTransportFromContext(context) ?? defaultProviderTransport
37
+ };
38
+ },
39
+ catch: () => new ProviderEnrollmentError("setup_incomplete", "OpenRouter connection settings are invalid.")
40
+ });
41
+ const invalidResponse = () => new ProviderEnrollmentError("setup_incomplete", "OpenRouter returned an invalid current-key response.");
42
+ yield* Effect.acquireUseRelease(transport(url, { method: "GET", headers, redirect: "error" }).pipe(Effect.mapError(() => new ProviderEnrollmentError("setup_incomplete", "OpenRouter current-key request failed.")), Effect.interruptible), (response) => Effect.gen(function* () {
43
+ if (!response.ok) {
44
+ // Preserve the host's safe HTTP classification; never include an upstream body.
45
+ return yield* Effect.fail(new ProviderEnrollmentError("setup_incomplete", `provider credential validation returned HTTP ${response.status} at /v1/key`));
46
+ }
47
+ if (response.body === null)
48
+ return yield* Effect.fail(invalidResponse());
49
+ yield* Effect.acquireUseRelease(Effect.try({ try: () => response.body.getReader(), catch: invalidResponse }), (reader) => Effect.gen(function* () {
50
+ const decoder = new TextDecoder();
51
+ let bytes = 0;
52
+ let text = "";
53
+ while (true) {
54
+ const chunk = yield* Effect.tryPromise({
55
+ try: () => reader.read(),
56
+ catch: invalidResponse
57
+ });
58
+ if (chunk.done)
59
+ break;
60
+ bytes += chunk.value.byteLength;
61
+ if (bytes > keyResponseMaxBytes)
62
+ return yield* Effect.fail(invalidResponse());
63
+ text += decoder.decode(chunk.value, { stream: true });
64
+ }
65
+ return yield* Effect.try({
66
+ try: () => currentKeySchema.parse(JSON.parse(text + decoder.decode())),
67
+ catch: invalidResponse
68
+ });
69
+ }), (reader) => Effect.sync(() => reader.releaseLock()));
70
+ // Key identity is not inference admission. Zero/exhausted/unlimited allowance is valid.
71
+ }), (response) => Effect.tryPromise({
72
+ try: () => response.body?.cancel() ?? Promise.resolve(),
73
+ catch: invalidResponse
74
+ }).pipe(Effect.timeout(250), Effect.ignore));
75
+ },
76
+ // A host's shorter discovery deadline still wins, including during body consumption.
77
+ Effect.timeoutOrElse({
78
+ duration: 10_000,
79
+ orElse: () => Effect.fail(new ProviderEnrollmentError("setup_incomplete", "OpenRouter current-key request exceeded its deadline."))
80
+ }),
81
+ // Hosts acquire the connected source in an uninterruptible resource acquisition.
82
+ // The credential check owns no source yet and must honor cancellation through EOF.
83
+ Effect.interruptible);
84
+ export const openRouterEnrollment = {
85
+ start: (context) => Effect.tryPromise({
86
+ try: async () => {
87
+ const verifier = generateRandomCodeVerifier();
88
+ const result = oAuthCreateAuthorizationUrl(new OpenRouterCore(), {
89
+ callbackUrl: context.callbackUrl,
90
+ codeChallengeMethod: "S256",
91
+ codeChallenge: await calculatePKCECodeChallenge(verifier)
92
+ });
93
+ if (!result.ok)
94
+ throw new ProviderEnrollmentError("invalid_state", "Could not start OpenRouter authorization.");
95
+ return {
96
+ kind: "action",
97
+ action: { kind: "external", stepId: "authorize", title: "Authorize OpenRouter", url: result.value, completion: "callback" },
98
+ state: { version: 1, data: { verifier } }
99
+ };
100
+ },
101
+ catch: () => new ProviderEnrollmentError("invalid_state", "Could not start OpenRouter authorization.")
102
+ }),
103
+ resume: (context, state, event) => Effect.tryPromise({
104
+ try: async () => {
105
+ if (state.version !== 1 || event.kind !== "callback")
106
+ throw new ProviderEnrollmentError("invalid_state", "Restart this connection.");
107
+ const { verifier } = stateSchema.parse(state.data);
108
+ if (event.parameters.error !== undefined)
109
+ throw new ProviderEnrollmentError("provider_denied", "OpenRouter authorization was declined.");
110
+ const code = event.parameters.code;
111
+ if (!code)
112
+ throw new ProviderEnrollmentError("invalid_state", "OpenRouter did not return an authorization code.");
113
+ const client = new OpenRouterCore({ httpClient: new HTTPClient({ fetcher: context.fetch }), appTitle: "RouteKit Cloud" });
114
+ // A code may have been consumed even when the exchange response is lost.
115
+ const result = await oAuthExchangeAuthCodeForAPIKey(client, {
116
+ requestBody: { code, codeVerifier: verifier, codeChallengeMethod: "S256" }
117
+ }, { retries: { strategy: "none" }, timeoutMs: 20_000 });
118
+ if (!result.ok)
119
+ throw new ProviderEnrollmentError("exchange_failed", "OpenRouter authorization could not be completed. Reconnect to try again.");
120
+ const { key } = keySchema.parse({ key: result.value.key });
121
+ return {
122
+ kind: "complete",
123
+ connection: preparedProviderConnection({
124
+ config: { use: "openrouter", auth: { kind: "env-bearer", env: keyEnvironment } },
125
+ environment: {},
126
+ credential: { version: 1, kind: "api-key", payload: { key } }
127
+ })
128
+ };
129
+ },
130
+ catch: (cause) => cause instanceof ProviderEnrollmentError ? cause :
131
+ new ProviderEnrollmentError("exchange_failed", "OpenRouter authorization could not be completed. Reconnect to try again.")
132
+ })
133
+ };
134
+ export const openRouterCredentials = {
135
+ resolve: (binding) => Effect.try({
136
+ try: () => {
137
+ if (binding.version !== 1 || binding.kind !== "api-key")
138
+ throw new Error("Unsupported OpenRouter credential.");
139
+ return { environment: { [keyEnvironment]: keySchema.parse(binding.payload).key } };
140
+ },
141
+ catch: () => new ProviderEnrollmentError("invalid_state", "The stored OpenRouter credential is invalid.")
142
+ })
143
+ };
@@ -0,0 +1,28 @@
1
+ import { ApiProviderSource } from "@velum-labs/routekit-provider-core";
2
+ import { Effect } from "effect";
3
+ export { decodeOpenRouterPricing, openRouterPricingAdapter } from "./pricing.js";
4
+ export declare const openrouter: {
5
+ readonly id: "openrouter";
6
+ readonly protocol: "openai";
7
+ readonly accountClass: "api-key";
8
+ readonly billingMode: "metered-api";
9
+ readonly pricing: Readonly<{
10
+ source: string;
11
+ discover(input: import("@velum-labs/routekit-provider-core").ProviderPricingDiscoveryInput): Effect.Effect<readonly import("@velum-labs/routekit-contracts").PricingQuote[], Error, import("@velum-labs/routekit-runtime/effect").RouteKitPlatform>;
12
+ }>;
13
+ readonly connection: Readonly<{
14
+ validate?: (context: Readonly<{
15
+ sourceId: string;
16
+ config: import("@velum-labs/routekit-config-core").SourceConfig;
17
+ env: Readonly<Record<string, string | undefined>>;
18
+ resources?: Readonly<Record<string, unknown>>;
19
+ }>) => Effect.Effect<void, Error, import("@velum-labs/routekit-runtime/effect").RouteKitPlatform>;
20
+ credentialValidation?: "registry-key-probe" | "registry-key-probe-if-configured-models";
21
+ manifest: import("@velum-labs/routekit-provider-core").ProviderConnectionManifest;
22
+ prepare?: (input: import("@velum-labs/routekit-provider-core").PrepareProviderConnectionInput) => import("@velum-labs/routekit-provider-core").PreparedProviderConnection;
23
+ enrollment?: Readonly<Record<string, import("@velum-labs/routekit-provider-core").ProviderEnrollmentAdapter>>;
24
+ credentials?: import("@velum-labs/routekit-provider-core").ProviderCredentialAdapter;
25
+ subscriptionMode?: import("@velum-labs/routekit-registry").SubscriptionMode;
26
+ }>;
27
+ readonly createSource: (context: import("@velum-labs/routekit-provider-core").ProviderModuleContext) => Effect.Effect<ApiProviderSource, never, never>;
28
+ };
@@ -0,0 +1,42 @@
1
+ import { ApiProviderSource, connectionString, connectionStringList, defineProviderConnection, defineProvider, optionalConnectionString, preparedProviderConnection, providerSourceTransportFromContext } from "@velum-labs/routekit-provider-core";
2
+ import { Effect } from "effect";
3
+ import { openRouterCredentials, openRouterEnrollment, validateOpenRouterConnection } from "./connection.js";
4
+ import { openRouterPricingAdapter } from "./pricing.js";
5
+ export { decodeOpenRouterPricing, openRouterPricingAdapter } from "./pricing.js";
6
+ export const openrouter = defineProvider({
7
+ id: "openrouter",
8
+ protocol: "openai",
9
+ accountClass: "api-key",
10
+ billingMode: "metered-api",
11
+ pricing: openRouterPricingAdapter,
12
+ connection: defineProviderConnection({
13
+ enrollment: { "browser": openRouterEnrollment },
14
+ credentials: openRouterCredentials,
15
+ manifest: {
16
+ version: 1, id: "openrouter", displayName: "OpenRouter", category: "api",
17
+ description: "Connect your OpenRouter account or API key.",
18
+ methods: [{ id: "api-key", kind: "form", label: "API key", schemaRevision: 1, fields: [
19
+ { id: "api-key", label: "API key", control: "secret", required: true, placeholder: "sk-or-…" },
20
+ { id: "base-url", label: "Base URL", control: "url", required: false, advanced: true, placeholder: "https://openrouter.ai/api", description: "Custom endpoints must support authenticated GET /v1/key for key validation." },
21
+ { id: "models", label: "Models", control: "string-list", required: false, advanced: true, description: "Optional comma- or line-separated model IDs. Leave empty to discover them." }
22
+ ] }, { id: "browser", kind: "flow", label: "Connect OpenRouter", schemaRevision: 1, fields: [] }]
23
+ },
24
+ prepare: ({ values }) => {
25
+ const env = "ROUTEKIT_CLOUD_GRANT_OPENROUTER_KEY";
26
+ const baseUrl = optionalConnectionString(values, "base-url");
27
+ const models = connectionStringList(values, "models");
28
+ return preparedProviderConnection({
29
+ config: { use: "openrouter", auth: { kind: "env-bearer", env }, ...(baseUrl === undefined ? {} : { baseUrl }), ...(models === undefined ? {} : { options: { models } }) },
30
+ environment: { [env]: connectionString(values, "api-key") }
31
+ });
32
+ },
33
+ validate: validateOpenRouterConnection
34
+ }),
35
+ createSource: (context) => Effect.sync(() => new ApiProviderSource({
36
+ provider: "openrouter",
37
+ sourceId: context.sourceId,
38
+ config: context.config,
39
+ env: context.env,
40
+ transport: providerSourceTransportFromContext(context)
41
+ }))
42
+ });
@@ -0,0 +1,4 @@
1
+ import type { PricingQuote } from "@velum-labs/routekit-contracts";
2
+ import type { ProviderPricingAdapter } from "@velum-labs/routekit-provider-core";
3
+ export declare function decodeOpenRouterPricing(value: unknown, retrievedAt: string): readonly PricingQuote[];
4
+ export declare const openRouterPricingAdapter: ProviderPricingAdapter;