@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,253 @@
1
+ import { evalSessionExecutionEvidenceSchema } from "@velum-labs/routekit-control/protocol";
2
+ import { HttpSubmissionError } from "@velum-labs/routekit-runtime/effect";
3
+ import { writeFileAtomicEffect } from "@velum-labs/routekit-runtime/filesystem";
4
+ import { acquireLifecycleLock } from "@velum-labs/routekit-runtime/service";
5
+ import { Cause, Effect, FileSystem, Path } from "effect";
6
+ import { z } from "zod";
7
+ const MAX_DOCUMENT_BYTES = 32 * 1024 * 1024;
8
+ const MAX_EXECUTIONS = 8192;
9
+ const executionFile = /^[a-f0-9]{64}\.json$/u;
10
+ function untouchedClosed(evidence) {
11
+ return (evidence.state === "sealed" &&
12
+ evidence.exposure === "closed" &&
13
+ !evidence.poisoned &&
14
+ evidence.ingresses.length === 0 &&
15
+ evidence.receipts.length === 0);
16
+ }
17
+ const storedExecutionSchema = z
18
+ .strictObject({
19
+ version: z.literal(1),
20
+ evidence: evalSessionExecutionEvidenceSchema,
21
+ readinessHistory: z.array(evalSessionExecutionEvidenceSchema).max(128),
22
+ readinessReopenAllowed: z.boolean()
23
+ })
24
+ .superRefine((value, ctx) => {
25
+ const issuances = [...value.readinessHistory, value.evidence];
26
+ if ((value.readinessReopenAllowed && !untouchedClosed(value.evidence)) ||
27
+ value.readinessHistory.some((entry) => !untouchedClosed(entry) ||
28
+ entry.executionDigest !== value.evidence.executionDigest ||
29
+ entry.policy !== value.evidence.policy ||
30
+ entry.maximumProviderAttempts !== value.evidence.maximumProviderAttempts) ||
31
+ new Set(issuances.map((entry) => entry.sessionId)).size !== issuances.length ||
32
+ new Set(issuances.map((entry) => entry.grantId)).size !== issuances.length)
33
+ ctx.addIssue({
34
+ code: "custom",
35
+ message: "execution readiness issuance history is inconsistent"
36
+ });
37
+ });
38
+ function storeError(cause) {
39
+ return cause instanceof HttpSubmissionError
40
+ ? cause
41
+ : new HttpSubmissionError({
42
+ code: "execution-store",
43
+ message: "durable provider submission evidence could not be verified or persisted",
44
+ cause
45
+ });
46
+ }
47
+ /** Derive closure without ever converting a pending or unknown receipt to success. */
48
+ export function normalizeExecutionEvidence(evidence) {
49
+ const pending = evidence.receipts.some((receipt) => receipt.state === "intent");
50
+ const unknown = evidence.poisoned || evidence.receipts.some((receipt) => receipt.state === "unknown");
51
+ return {
52
+ ...evidence,
53
+ state: evidence.state === "open" && !unknown ? "open" : pending ? "draining" : "sealed",
54
+ exposure: pending || unknown ? "unknown" : "closed"
55
+ };
56
+ }
57
+ /**
58
+ * Private payload-free authority store. Each operation reads current durable
59
+ * state under the existing process-identity lifecycle lock before an atomic
60
+ * replacement. A retiring worker therefore cannot overwrite a successor's
61
+ * revocation, recovered unknown receipt, or consumed execution identity.
62
+ *
63
+ * The lock is transaction-scoped, not another gateway/request resource owner.
64
+ * Atomic rename covers interruption and reported I/O faults, not power loss.
65
+ */
66
+ export const makeEvalSessionExecutionStore = Effect.fn("EvalSessionExecutionStore.make")(function* (inputDirectory) {
67
+ const fs = yield* FileSystem.FileSystem;
68
+ const paths = yield* Path.Path;
69
+ const requested = paths.resolve(inputDirectory);
70
+ yield* fs
71
+ .makeDirectory(requested, { recursive: true, mode: 0o700 })
72
+ .pipe(Effect.mapError(storeError));
73
+ const parent = yield* fs.realPath(paths.dirname(requested)).pipe(Effect.mapError(storeError));
74
+ const directory = paths.join(parent, paths.basename(requested));
75
+ const canonical = yield* fs.realPath(directory).pipe(Effect.mapError(storeError));
76
+ const info = yield* fs.stat(directory).pipe(Effect.mapError(storeError));
77
+ if (canonical !== directory || info.type !== "Directory") {
78
+ return yield* Effect.fail(new HttpSubmissionError({
79
+ code: "execution-store",
80
+ message: "provider submission evidence requires a non-symlink private directory"
81
+ }));
82
+ }
83
+ yield* fs.chmod(directory, 0o700).pipe(Effect.mapError(storeError));
84
+ const lockPath = paths.join(directory, ".transaction.lock");
85
+ const parse = (value) => storedExecutionSchema.parse(value);
86
+ const filePath = (executionDigest) => {
87
+ if (!/^[a-f0-9]{64}$/u.test(executionDigest)) {
88
+ throw new HttpSubmissionError({
89
+ code: "execution-identity",
90
+ message: "provider submission execution digest is invalid"
91
+ });
92
+ }
93
+ return paths.join(directory, `${executionDigest}.json`);
94
+ };
95
+ const read = Effect.fnUntraced(function* (executionDigest) {
96
+ const path = yield* Effect.try({ try: () => filePath(executionDigest), catch: storeError });
97
+ const info = yield* fs.stat(path).pipe(Effect.catchIf((error) => error.reason._tag === "NotFound", () => Effect.succeed(undefined)), Effect.mapError(storeError));
98
+ if (info === undefined)
99
+ return undefined;
100
+ const canonical = yield* fs.realPath(path).pipe(Effect.mapError(storeError));
101
+ if (canonical !== path ||
102
+ info.type !== "File" ||
103
+ (info.mode & 0o077) !== 0 ||
104
+ info.size > BigInt(MAX_DOCUMENT_BYTES)) {
105
+ return yield* Effect.fail(new HttpSubmissionError({
106
+ code: "execution-store",
107
+ message: "provider submission evidence has unsafe storage metadata"
108
+ }));
109
+ }
110
+ const text = yield* fs.readFileString(path).pipe(Effect.mapError(storeError));
111
+ const value = yield* Effect.try({
112
+ try: () => {
113
+ if (Buffer.byteLength(text, "utf8") > MAX_DOCUMENT_BYTES) {
114
+ throw new Error("provider submission evidence exceeds its bounded document size");
115
+ }
116
+ return parse(JSON.parse(text));
117
+ },
118
+ catch: storeError
119
+ });
120
+ if (value.evidence.executionDigest !== executionDigest) {
121
+ return yield* Effect.fail(new HttpSubmissionError({
122
+ code: "execution-store",
123
+ message: "provider submission evidence does not match its durable execution identity"
124
+ }));
125
+ }
126
+ return value;
127
+ });
128
+ const write = Effect.fnUntraced(function* (record) {
129
+ const value = yield* Effect.try({ try: () => parse(record), catch: storeError });
130
+ const text = JSON.stringify(value);
131
+ if (Buffer.byteLength(text, "utf8") > MAX_DOCUMENT_BYTES) {
132
+ return yield* Effect.fail(new HttpSubmissionError({
133
+ code: "execution-store",
134
+ message: "provider submission evidence exceeds its bounded document size"
135
+ }));
136
+ }
137
+ yield* writeFileAtomicEffect(filePath(value.evidence.executionDigest), `${text}\n`, {
138
+ mode: 0o600
139
+ }).pipe(Effect.provideService(FileSystem.FileSystem, fs), Effect.provideService(Path.Path, paths), Effect.mapError(storeError));
140
+ return value;
141
+ });
142
+ const transaction = (operation) => Effect.scoped(Effect.gen(function* () {
143
+ // No interrupt can strand a just-acquired native lock before its
144
+ // Effect finalizer is installed, or expose a partially settled write.
145
+ yield* Effect.acquireRelease(Effect.tryPromise({
146
+ try: () => acquireLifecycleLock(lockPath, { timeoutMs: 5_000, pollMs: 10 }),
147
+ catch: storeError
148
+ }), (lock) => Effect.try({ try: () => lock.release(), catch: storeError }).pipe(Effect.orDie));
149
+ return yield* operation;
150
+ })).pipe(Effect.uninterruptible, Effect.catchCause((cause) => Effect.fail(storeError(Cause.squash(cause)))));
151
+ const store = {
152
+ directory,
153
+ create: (evidence) => transaction(Effect.gen(function* () {
154
+ const previous = yield* read(evidence.executionDigest);
155
+ if (previous !== undefined &&
156
+ (!previous.readinessReopenAllowed ||
157
+ !untouchedClosed(previous.evidence) ||
158
+ previous.evidence.policy !== evidence.policy ||
159
+ previous.evidence.maximumProviderAttempts !== evidence.maximumProviderAttempts)) {
160
+ return yield* Effect.fail(new HttpSubmissionError({
161
+ code: "execution-consumed",
162
+ message: "provider submission execution identity has already been granted"
163
+ }));
164
+ }
165
+ const files = yield* fs.readDirectory(directory).pipe(Effect.mapError(storeError));
166
+ if (previous === undefined &&
167
+ files.filter((file) => file.endsWith(".json")).length >= MAX_EXECUTIONS) {
168
+ return yield* Effect.fail(new HttpSubmissionError({
169
+ code: "execution-store",
170
+ message: "provider submission store has reached its bounded execution capacity"
171
+ }));
172
+ }
173
+ const saved = yield* write({
174
+ version: 1,
175
+ evidence: evalSessionExecutionEvidenceSchema.parse(evidence),
176
+ readinessHistory: previous === undefined ? [] : [...previous.readinessHistory, previous.evidence],
177
+ readinessReopenAllowed: false
178
+ });
179
+ return saved.evidence;
180
+ })),
181
+ read: (executionDigest, sessionId) => transaction(Effect.map(read(executionDigest), (record) => sessionId === undefined || record?.evidence.sessionId === sessionId
182
+ ? record?.evidence
183
+ : record?.readinessHistory.find((entry) => entry.sessionId === sessionId))),
184
+ update: (executionDigest, update, options) => transaction(Effect.gen(function* () {
185
+ const record = yield* read(executionDigest);
186
+ if (record === undefined) {
187
+ return yield* Effect.fail(new HttpSubmissionError({
188
+ code: "execution-store",
189
+ message: "previously granted provider submission evidence is missing"
190
+ }));
191
+ }
192
+ const current = record.evidence;
193
+ const next = yield* Effect.try({ try: () => update(current), catch: storeError });
194
+ if (next.executionDigest !== current.executionDigest ||
195
+ next.sessionId !== current.sessionId ||
196
+ next.grantId !== current.grantId ||
197
+ next.issuerIdentity !== current.issuerIdentity ||
198
+ next.maximumProviderAttempts !== current.maximumProviderAttempts ||
199
+ next.policy !== current.policy) {
200
+ return yield* Effect.fail(new HttpSubmissionError({
201
+ code: "execution-store",
202
+ message: "provider submission grant identity cannot change during a transaction"
203
+ }));
204
+ }
205
+ const saved = yield* write({
206
+ ...record,
207
+ evidence: evalSessionExecutionEvidenceSchema.parse(next),
208
+ readinessReopenAllowed: options?.allowReadinessReopen === true && untouchedClosed(next)
209
+ });
210
+ return saved.evidence;
211
+ })),
212
+ recover: (now) => transaction(Effect.gen(function* () {
213
+ const files = yield* fs.readDirectory(directory).pipe(Effect.mapError(storeError));
214
+ const records = files.filter((file) => file.endsWith(".json"));
215
+ if (records.length > MAX_EXECUTIONS ||
216
+ records.some((file) => !executionFile.test(file))) {
217
+ return yield* Effect.fail(new HttpSubmissionError({
218
+ code: "execution-store",
219
+ message: "provider submission store has invalid or excessive execution records"
220
+ }));
221
+ }
222
+ return yield* Effect.forEach(records, (file) => Effect.gen(function* () {
223
+ const previous = yield* read(file.slice(0, -".json".length));
224
+ if (previous === undefined) {
225
+ return yield* Effect.fail(new HttpSubmissionError({
226
+ code: "execution-store",
227
+ message: "provider submission evidence disappeared during recovery"
228
+ }));
229
+ }
230
+ if (previous.evidence.state === "sealed")
231
+ return previous.evidence;
232
+ const recovered = yield* write({
233
+ ...previous,
234
+ readinessReopenAllowed: false,
235
+ evidence: normalizeExecutionEvidence({
236
+ ...previous.evidence,
237
+ state: "draining",
238
+ receipts: previous.evidence.receipts.map((receipt) => receipt.state === "intent"
239
+ ? {
240
+ ...receipt,
241
+ state: "unknown",
242
+ settledAt: now,
243
+ outcome: { state: "unknown", reason: "daemon-restart" }
244
+ }
245
+ : receipt)
246
+ })
247
+ });
248
+ return recovered.evidence;
249
+ }));
250
+ }))
251
+ };
252
+ return store;
253
+ });
@@ -22,6 +22,7 @@ export type WorkerReady = WorkerSnapshot & {
22
22
  workerProcessIdentity?: string;
23
23
  workerStartedAt: string;
24
24
  packageVersion: string;
25
+ sourceBuildId?: string;
25
26
  controlUrl: string;
26
27
  controlPort: number;
27
28
  dataUrl: string;
package/dist/host.js CHANGED
@@ -128,6 +128,9 @@ function daemonHostLive(options) {
128
128
  port: controlPort,
129
129
  startedAt: hostStartedAt,
130
130
  version: ready.packageVersion,
131
+ ...(ready.sourceBuildId === undefined
132
+ ? {}
133
+ : { sourceBuildId: ready.sourceBuildId }),
131
134
  protocolVersion: CONTROL_PROTOCOL_VERSION,
132
135
  controlToken,
133
136
  dataUrl,
@@ -0,0 +1,23 @@
1
+ import { ApiProviderSource } from "@velum-labs/routekit-provider-core";
2
+ import { Effect } from "effect";
3
+ export declare const anthropic: {
4
+ readonly id: "anthropic";
5
+ readonly protocol: "anthropic";
6
+ readonly accountClass: "api-key";
7
+ readonly billingMode: "metered-api";
8
+ readonly connection: Readonly<{
9
+ validate?: (context: Readonly<{
10
+ sourceId: string;
11
+ config: import("@velum-labs/routekit-config-core").SourceConfig;
12
+ env: Readonly<Record<string, string | undefined>>;
13
+ resources?: Readonly<Record<string, unknown>>;
14
+ }>) => Effect.Effect<void, Error, import("@velum-labs/routekit-runtime/effect").RouteKitPlatform>;
15
+ credentialValidation?: "registry-key-probe" | "registry-key-probe-if-configured-models";
16
+ manifest: import("@velum-labs/routekit-provider-core").ProviderConnectionManifest;
17
+ prepare?: (input: import("@velum-labs/routekit-provider-core").PrepareProviderConnectionInput) => import("@velum-labs/routekit-provider-core").PreparedProviderConnection;
18
+ enrollment?: Readonly<Record<string, import("@velum-labs/routekit-provider-core").ProviderEnrollmentAdapter>>;
19
+ credentials?: import("@velum-labs/routekit-provider-core").ProviderCredentialAdapter;
20
+ subscriptionMode?: import("@velum-labs/routekit-registry").SubscriptionMode;
21
+ }>;
22
+ readonly createSource: (context: import("@velum-labs/routekit-provider-core").ProviderModuleContext) => Effect.Effect<ApiProviderSource, never, never>;
23
+ };
@@ -0,0 +1,42 @@
1
+ import { ApiProviderSource, connectionString, connectionStringList, defineProviderConnection, defineProvider, optionalConnectionString, preparedProviderConnection, providerSourceTransportFromContext, routeTextToolModelMetadata } from "@velum-labs/routekit-provider-core";
2
+ import { Effect } from "effect";
3
+ function anthropicMessagesModelMetadata(model) {
4
+ return /(?:^|\/)claude(?:[-.]|$)/iu.test(model)
5
+ ? routeTextToolModelMetadata({ inputModalities: ["text", "image"] })
6
+ : undefined;
7
+ }
8
+ export const anthropic = defineProvider({
9
+ id: "anthropic",
10
+ protocol: "anthropic",
11
+ accountClass: "api-key",
12
+ billingMode: "metered-api",
13
+ connection: defineProviderConnection({
14
+ manifest: {
15
+ version: 1, id: "anthropic", displayName: "Anthropic API", category: "api",
16
+ description: "Connect an Anthropic Console API key.",
17
+ methods: [{ id: "api-key", kind: "form", label: "API key", schemaRevision: 1, fields: [
18
+ { id: "api-key", label: "API key", control: "secret", required: true, placeholder: "sk-ant-…" },
19
+ { id: "base-url", label: "Base URL", control: "url", required: false, advanced: true, placeholder: "https://api.anthropic.com", description: "With explicit models, custom endpoints must support authenticated GET /v1/models for key validation." },
20
+ { id: "models", label: "Models", control: "string-list", required: false, advanced: true, description: "Optional comma- or line-separated IDs. Explicit models require read-only model-list access to validate the key." }
21
+ ] }]
22
+ },
23
+ prepare: ({ values }) => {
24
+ const env = "ROUTEKIT_CLOUD_GRANT_ANTHROPIC_KEY";
25
+ const baseUrl = optionalConnectionString(values, "base-url");
26
+ const models = connectionStringList(values, "models");
27
+ return preparedProviderConnection({
28
+ config: { use: "anthropic", auth: { kind: "env-header", env, header: "x-api-key" }, ...(baseUrl === undefined ? {} : { baseUrl }), ...(models === undefined ? {} : { options: { models } }) },
29
+ environment: { [env]: connectionString(values, "api-key") }
30
+ });
31
+ },
32
+ credentialValidation: "registry-key-probe-if-configured-models"
33
+ }),
34
+ createSource: (context) => Effect.sync(() => new ApiProviderSource({
35
+ provider: "anthropic",
36
+ sourceId: context.sourceId,
37
+ config: context.config,
38
+ env: context.env,
39
+ transport: providerSourceTransportFromContext(context),
40
+ metadataForModel: anthropicMessagesModelMetadata
41
+ }))
42
+ });
@@ -0,0 +1,33 @@
1
+ import assert from "node:assert/strict";
2
+ import { test } from "node:test";
3
+ import { Effect } from "effect";
4
+ import { anthropic } from "../index.js";
5
+ test("configured Anthropic catalogs qualify Claude routes and fail closed otherwise", async () => {
6
+ const prepared = anthropic.connection.prepare({
7
+ sourceId: "anthropic-production",
8
+ methodId: "api-key",
9
+ values: { "api-key": "anthropic-secret" }
10
+ });
11
+ const source = await Effect.runPromise(anthropic.createSource({
12
+ sourceId: "anthropic-production",
13
+ config: {
14
+ ...prepared.config,
15
+ options: {
16
+ models: ["claude-sonnet-4-6", "text-embedding-unknown", "future-anthropic-model"]
17
+ }
18
+ },
19
+ env: prepared.environment
20
+ }));
21
+ const [claude, embedding, unknown] = await Effect.runPromise(source.discovery.discoverModels());
22
+ assert.deepEqual(claude?.metadata, {
23
+ architecture: {
24
+ modality: "text+image->text",
25
+ inputModalities: ["text", "image"],
26
+ outputModalities: ["text"]
27
+ },
28
+ supportedParameters: ["tools", "tool_choice"],
29
+ provenance: "route"
30
+ });
31
+ assert.equal(embedding?.metadata, undefined);
32
+ assert.equal(unknown?.metadata, undefined);
33
+ });
@@ -0,0 +1,25 @@
1
+ import type { SourceConfig } from "@velum-labs/routekit-config-core";
2
+ import type { ProviderSource, ProviderTransport } from "@velum-labs/routekit-provider-core";
3
+ import { Effect } from "effect";
4
+ export declare function createAzureSource(sourceId: string, config: SourceConfig, env: Readonly<Record<string, string | undefined>>, transport?: ProviderTransport): ProviderSource;
5
+ export declare const azure: {
6
+ readonly id: "openai-azure";
7
+ readonly protocol: "openai";
8
+ readonly accountClass: "api-key";
9
+ readonly billingMode: "metered-api";
10
+ readonly connection: Readonly<{
11
+ validate?: (context: Readonly<{
12
+ sourceId: string;
13
+ config: SourceConfig;
14
+ env: Readonly<Record<string, string | undefined>>;
15
+ resources?: Readonly<Record<string, unknown>>;
16
+ }>) => Effect.Effect<void, Error, import("@velum-labs/routekit-runtime/effect").RouteKitPlatform>;
17
+ credentialValidation?: "registry-key-probe" | "registry-key-probe-if-configured-models";
18
+ manifest: import("@velum-labs/routekit-provider-core").ProviderConnectionManifest;
19
+ prepare?: (input: import("@velum-labs/routekit-provider-core").PrepareProviderConnectionInput) => import("@velum-labs/routekit-provider-core").PreparedProviderConnection;
20
+ enrollment?: Readonly<Record<string, import("@velum-labs/routekit-provider-core").ProviderEnrollmentAdapter>>;
21
+ credentials?: import("@velum-labs/routekit-provider-core").ProviderCredentialAdapter;
22
+ subscriptionMode?: import("@velum-labs/routekit-registry").SubscriptionMode;
23
+ }>;
24
+ readonly createSource: (context: import("@velum-labs/routekit-provider-core").ProviderModuleContext) => Effect.Effect<ProviderSource, never, never>;
25
+ };
@@ -0,0 +1,101 @@
1
+ import { connectionString, connectionStringList, defineProviderConnection, defineProvider, OpenAiBackend, preparedProviderConnection, providerSourceTransportFromContext } from "@velum-labs/routekit-provider-core";
2
+ import { Effect } from "effect";
3
+ function requiredString(config, key) {
4
+ const value = config.options?.[key];
5
+ if (typeof value !== "string" || value.length === 0) {
6
+ throw new Error(`openai-azure requires options.${key}`);
7
+ }
8
+ return value;
9
+ }
10
+ function models(config) {
11
+ const configured = config.options?.models;
12
+ if (Array.isArray(configured) && configured.every((entry) => typeof entry === "string")) {
13
+ return configured.map((id) => ({ id }));
14
+ }
15
+ const id = requiredString(config, "deployment");
16
+ return [{ id }];
17
+ }
18
+ function apiKey(config, env) {
19
+ if (config.auth?.kind !== "env-header") {
20
+ throw new Error("openai-azure requires auth.kind env-header with header api-key");
21
+ }
22
+ if (config.auth.header.toLowerCase() !== "api-key") {
23
+ throw new Error("openai-azure authentication header must be api-key");
24
+ }
25
+ const value = env[config.auth.env];
26
+ if (value === undefined || value.length === 0)
27
+ throw new Error(`missing ${config.auth.env}`);
28
+ return value;
29
+ }
30
+ export function createAzureSource(sourceId, config, env, transport) {
31
+ if (config.baseUrl === undefined)
32
+ throw new Error("openai-azure requires baseUrl");
33
+ const deployment = requiredString(config, "deployment");
34
+ const backend = new OpenAiBackend({
35
+ baseUrl: `${config.baseUrl.replace(/\/$/, "")}/openai/deployments/${encodeURIComponent(deployment)}`,
36
+ apiKey: "",
37
+ bearerAuth: false,
38
+ headers: { "api-key": apiKey(config, env) },
39
+ query: { "api-version": requiredString(config, "apiVersion") },
40
+ transport
41
+ });
42
+ const nativeResponses = backend.ports.responses;
43
+ if (nativeResponses.kind !== "responses") {
44
+ throw new Error("OpenAI backend does not expose its native Responses port");
45
+ }
46
+ return {
47
+ sourceId,
48
+ discovery: { discoverModels: () => Effect.succeed(models(config)) },
49
+ requests: {
50
+ chat: (body, signal, options) => backend.chat(body, signal, options),
51
+ embeddings: (body, signal, options) => backend.embeddings(body, signal, options)
52
+ },
53
+ responses: {
54
+ kind: "responses",
55
+ supports: (model) => nativeResponses.supports(model),
56
+ execute: (body, signal, options) => nativeResponses.execute(body, signal, options)
57
+ },
58
+ capabilities: {
59
+ forModel: () => ({}),
60
+ reasoningForModel: () => undefined
61
+ },
62
+ resource: { kind: "borrowed" }
63
+ };
64
+ }
65
+ export const azure = defineProvider({
66
+ id: "openai-azure",
67
+ protocol: "openai",
68
+ accountClass: "api-key",
69
+ billingMode: "metered-api",
70
+ connection: defineProviderConnection({
71
+ manifest: {
72
+ version: 1, id: "openai-azure", displayName: "Azure OpenAI", category: "cloud",
73
+ description: "Connect an Azure OpenAI deployment with an API key.",
74
+ methods: [{ id: "api-key", kind: "form", label: "API key", schemaRevision: 1, fields: [
75
+ { id: "api-key", label: "API key", control: "secret", required: true },
76
+ { id: "base-url", label: "Endpoint", control: "url", required: true, placeholder: "https://example.openai.azure.com" },
77
+ { id: "deployment", label: "Deployment", control: "text", required: true },
78
+ { id: "api-version", label: "API version", control: "text", required: true, defaultValue: "2024-10-21" },
79
+ { id: "models", label: "Models", control: "string-list", required: false, advanced: true, description: "Optional model IDs. The deployment name is used when empty." }
80
+ ] }]
81
+ },
82
+ prepare: ({ values }) => {
83
+ const env = "ROUTEKIT_CLOUD_GRANT_AZURE_KEY";
84
+ const models = connectionStringList(values, "models");
85
+ return preparedProviderConnection({
86
+ config: {
87
+ use: "openai-azure",
88
+ auth: { kind: "env-header", env, header: "api-key" },
89
+ baseUrl: connectionString(values, "base-url"),
90
+ options: {
91
+ deployment: connectionString(values, "deployment"),
92
+ apiVersion: connectionString(values, "api-version"),
93
+ ...(models === undefined ? {} : { models })
94
+ }
95
+ },
96
+ environment: { [env]: connectionString(values, "api-key") }
97
+ });
98
+ }
99
+ }),
100
+ createSource: (context) => Effect.sync(() => createAzureSource(context.sourceId, context.config, context.env, providerSourceTransportFromContext(context)))
101
+ });
@@ -0,0 +1,83 @@
1
+ import assert from "node:assert/strict";
2
+ import { test } from "node:test";
3
+ import { runRouteKitEffect } from "@velum-labs/routekit-runtime/effect";
4
+ import { createAzureSource } from "../index.js";
5
+ test("Azure sources use deployment URLs, api-version queries, and api-key auth", async () => {
6
+ const originalFetch = globalThis.fetch;
7
+ let request;
8
+ globalThis.fetch = async (input, init) => {
9
+ request = new Request(input, init);
10
+ return Response.json({
11
+ id: "chatcmpl-azure",
12
+ choices: [{ message: { role: "assistant", content: "azure" } }]
13
+ });
14
+ };
15
+ try {
16
+ const config = {
17
+ use: "openai-azure",
18
+ baseUrl: "https://example-resource.openai.azure.com/",
19
+ auth: { kind: "env-header", env: "AZURE_OPENAI_API_KEY", header: "api-key" },
20
+ options: {
21
+ deployment: "gpt-5 east",
22
+ apiVersion: "2026-08-01-preview",
23
+ models: ["gpt-5"]
24
+ }
25
+ };
26
+ const source = createAzureSource("azure-east", config, {
27
+ AZURE_OPENAI_API_KEY: "azure-secret"
28
+ });
29
+ const response = await runRouteKitEffect(source.requests.chat({
30
+ model: "gpt-5",
31
+ messages: [{ role: "user", content: "hello" }]
32
+ }));
33
+ assert.equal(source.sourceId, "azure-east");
34
+ assert.equal(response.status, 200);
35
+ assert.equal(request?.url, "https://example-resource.openai.azure.com/openai/deployments/gpt-5%20east/chat/completions?api-version=2026-08-01-preview");
36
+ assert.equal(request?.headers.get("api-key"), "azure-secret");
37
+ assert.equal(request?.headers.get("authorization"), null);
38
+ assert.deepEqual(await runRouteKitEffect(source.discovery.discoverModels()), [{ id: "gpt-5" }]);
39
+ }
40
+ finally {
41
+ globalThis.fetch = originalFetch;
42
+ }
43
+ });
44
+ test("Azure sources expose native Responses without guessing deployment capabilities", async () => {
45
+ const originalFetch = globalThis.fetch;
46
+ let request;
47
+ globalThis.fetch = async (input, init) => {
48
+ request = new Request(input, init);
49
+ return Response.json({
50
+ id: "resp_azure",
51
+ object: "response",
52
+ output: [],
53
+ status: "completed"
54
+ });
55
+ };
56
+ try {
57
+ const source = createAzureSource("azure-east", {
58
+ use: "openai-azure",
59
+ baseUrl: "https://example-resource.openai.azure.com/",
60
+ auth: { kind: "env-header", env: "AZURE_OPENAI_API_KEY", header: "api-key" },
61
+ options: {
62
+ deployment: "gpt-5 east",
63
+ apiVersion: "2026-08-01-preview",
64
+ models: ["gpt-5.6-sol"]
65
+ }
66
+ }, { AZURE_OPENAI_API_KEY: "azure-secret" });
67
+ assert.equal(source.responses.kind, "responses");
68
+ if (source.responses.kind !== "responses")
69
+ return;
70
+ const response = await runRouteKitEffect(source.responses.execute({ model: "gpt-5.6-sol", input: "hello" }));
71
+ assert.equal(response.status, 200);
72
+ assert.equal(request?.url, "https://example-resource.openai.azure.com/openai/deployments/gpt-5%20east/responses?api-version=2026-08-01-preview");
73
+ assert.equal(request?.headers.get("api-key"), "azure-secret");
74
+ assert.equal(request?.headers.get("authorization"), null);
75
+ assert.equal(source.capabilities.reasoningForModel("gpt-5.6-sol"), undefined);
76
+ assert.deepEqual(await runRouteKitEffect(source.discovery.discoverModels()), [
77
+ { id: "gpt-5.6-sol" }
78
+ ]);
79
+ }
80
+ finally {
81
+ globalThis.fetch = originalFetch;
82
+ }
83
+ });
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Bedrock Converse codec. Pure request/response translation between OpenAI
3
+ * Chat Completions JSON and Amazon Bedrock Converse, including streaming SSE.
4
+ * Model discovery and transport live in `bedrock-source.ts`.
5
+ */
6
+ import type { ConverseCommandInput, ConverseCommandOutput, ConverseStreamOutput } from "@aws-sdk/client-bedrock-runtime";
7
+ export declare function isOpusFiveModel(modelId: string): boolean;
8
+ export declare function toBedrockConverseInput(body: unknown): ConverseCommandInput;
9
+ export declare function fromBedrockConverseOutput(output: ConverseCommandOutput, model: string): Record<string, unknown>;
10
+ export declare function streamResponse(stream: AsyncIterable<ConverseStreamOutput>, model: string, signal?: AbortSignal): Response;
11
+ export declare function errorResponse(error: unknown): Response;