@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
@@ -1,22 +1,76 @@
1
1
  import { createHash, randomBytes } from "node:crypto";
2
+ import { evalSessionEvidenceRequiredSchema, httpSubmissionBindingSchema, httpSubmissionOutcomeSchema, httpSubmissionRequestSchema } from "@velum-labs/routekit-control/protocol";
2
3
  import { ControlError } from "@velum-labs/routekit-runtime/control";
3
- import { Context, Effect, Layer } from "effect";
4
- import { ActiveGateway } from "../active-gateway/service.js";
4
+ import { HttpSubmissionError } from "@velum-labs/routekit-runtime/effect";
5
+ import { Cause, Context, Effect, Layer, Semaphore } from "effect";
5
6
  import { DaemonEnv } from "../../daemon-env-context.js";
7
+ import { normalizeExecutionEvidence } from "../../eval-session-execution-store.js";
8
+ import { ActiveGateway } from "../active-gateway/service.js";
6
9
  function digest(value) {
7
10
  return createHash("sha256").update(value, "utf8").digest("hex");
8
11
  }
9
- /** Daemon-owned, memory-only registry for short-lived eval credentials. */
12
+ function submissionError(code, message) {
13
+ return new HttpSubmissionError({ code, message });
14
+ }
15
+ function grantOf(evidence) {
16
+ return {
17
+ grantId: evidence.grantId,
18
+ executionDigest: evidence.executionDigest,
19
+ issuerIdentity: evidence.issuerIdentity,
20
+ maximumProviderAttempts: evidence.maximumProviderAttempts,
21
+ policy: evidence.policy
22
+ };
23
+ }
24
+ function assertGrant(evidence, identity) {
25
+ if (evidence.sessionId !== identity.sessionId ||
26
+ evidence.grantId !== identity.grantId ||
27
+ evidence.executionDigest !== identity.executionDigest ||
28
+ evidence.issuerIdentity !== identity.issuerIdentity) {
29
+ throw submissionError("execution-identity", "provider submission grant identity does not match");
30
+ }
31
+ }
32
+ function assertOpen(evidence) {
33
+ if (evidence.state !== "open" ||
34
+ evidence.poisoned ||
35
+ evidence.receipts.some((receipt) => receipt.state === "unknown")) {
36
+ throw submissionError("execution-closed", "provider submission grant is revoked or poisoned");
37
+ }
38
+ }
39
+ function assertIngress(evidence, binding) {
40
+ assertGrant(evidence, binding);
41
+ const ingress = evidence.ingresses.find((entry) => entry.ingressId === binding.ingressId);
42
+ if (ingress === undefined ||
43
+ ingress.requestDigest !== binding.requestDigest ||
44
+ ingress.model !== binding.model) {
45
+ throw submissionError("ingress-identity", "provider submission ingress identity does not match");
46
+ }
47
+ return ingress;
48
+ }
49
+ /** Ephemeral bearer credentials with a separate durable, payload-free execution authority. */
10
50
  export class EvalSessionManager {
11
51
  #sessions = new Map();
12
52
  #sessionByTokenDigest = new Map();
13
53
  #now;
14
54
  #random;
55
+ #store;
56
+ #gate = Semaphore.makeUnsafe(1);
57
+ #executionsBySession = new Map();
58
+ #poisoned = new Set();
59
+ #initialized = false;
15
60
  constructor(options = {}) {
16
61
  this.#now = options.now ?? Date.now;
17
62
  this.#random = options.random ?? randomBytes;
63
+ this.#store = options.store;
18
64
  }
19
65
  open(input) {
66
+ if (input.evidenceRequired !== undefined) {
67
+ throw submissionError("execution-await-required", "strict eval sessions require awaited openEffect");
68
+ }
69
+ const prepared = this.#prepare(input);
70
+ this.#register(prepared.session);
71
+ return prepared.result;
72
+ }
73
+ #prepare(input) {
20
74
  const now = this.#now();
21
75
  const expiresAtMs = now + Math.min(input.expiresInSeconds * 1_000, input.limits.wallTimeMs);
22
76
  const sessionId = `eval_${this.#random(16).toString("base64url")}`;
@@ -34,17 +88,72 @@ export class EvalSessionManager {
34
88
  reservedOutputTokens: 0,
35
89
  closed: false
36
90
  };
37
- this.#sessions.set(sessionId, session);
38
- this.#sessionByTokenDigest.set(tokenDigest, sessionId);
39
91
  return {
40
- sessionId,
41
- gatewayUrl: input.gatewayUrl,
42
- bearerCredential: credential,
43
- targetIdentity: input.targetIdentity,
44
- expiresAt: new Date(expiresAtMs).toISOString()
92
+ session,
93
+ result: {
94
+ sessionId,
95
+ gatewayUrl: input.gatewayUrl,
96
+ bearerCredential: credential,
97
+ targetIdentity: input.targetIdentity,
98
+ expiresAt: new Date(expiresAtMs).toISOString()
99
+ }
45
100
  };
46
101
  }
47
- close(sessionId) {
102
+ #register(session) {
103
+ this.#sessions.set(session.id, session);
104
+ this.#sessionByTokenDigest.set(session.tokenDigest, session.id);
105
+ if (session.executionGrant !== undefined) {
106
+ this.#executionsBySession.set(session.id, session.executionGrant);
107
+ }
108
+ }
109
+ initialize() {
110
+ const self = this;
111
+ return this.#gate.withPermit(Effect.gen(function* () {
112
+ if (self.#initialized)
113
+ return;
114
+ const records = self.#store === undefined
115
+ ? []
116
+ : yield* self.#store.recover(new Date(self.#now()).toISOString());
117
+ for (const record of records) {
118
+ self.#executionsBySession.set(record.sessionId, grantOf(record));
119
+ }
120
+ self.#initialized = true;
121
+ }).pipe(Effect.uninterruptible));
122
+ }
123
+ openEffect(input) {
124
+ if (input.evidenceRequired === undefined)
125
+ return Effect.sync(() => this.open(input));
126
+ const self = this;
127
+ const requested = evalSessionEvidenceRequiredSchema.safeParse(input.evidenceRequired);
128
+ if (!requested.success) {
129
+ return Effect.fail(submissionError("execution-policy", "strict eval execution policy is invalid"));
130
+ }
131
+ const evidenceRequired = requested.data;
132
+ return Effect.gen(function* () {
133
+ yield* self.initialize();
134
+ return yield* self.#withExecution(evidenceRequired.executionDigest, (store) => Effect.gen(function* () {
135
+ const prepared = self.#prepare(input);
136
+ const executionGrant = {
137
+ ...evidenceRequired,
138
+ grantId: `grant_${self.#random(16).toString("base64url")}`,
139
+ issuerIdentity: input.targetIdentity
140
+ };
141
+ yield* store.create({
142
+ ...executionGrant,
143
+ version: 1,
144
+ sessionId: prepared.session.id,
145
+ state: "open",
146
+ exposure: "closed",
147
+ poisoned: false,
148
+ ingresses: [],
149
+ receipts: []
150
+ });
151
+ self.#register({ ...prepared.session, executionGrant });
152
+ return { ...prepared.result, executionGrant };
153
+ }));
154
+ });
155
+ }
156
+ #revoke(sessionId) {
48
157
  const session = this.#sessions.get(sessionId);
49
158
  if (session === undefined || session.closed)
50
159
  return false;
@@ -52,6 +161,262 @@ export class EvalSessionManager {
52
161
  this.#sessionByTokenDigest.delete(session.tokenDigest);
53
162
  return true;
54
163
  }
164
+ close(sessionId) {
165
+ const session = this.#sessions.get(sessionId);
166
+ if (session?.executionGrant !== undefined) {
167
+ throw submissionError("execution-await-required", "strict eval sessions require awaited closeEffect");
168
+ }
169
+ return this.#revoke(sessionId);
170
+ }
171
+ closeEffect(sessionId) {
172
+ const self = this;
173
+ const grant = this.#executionsBySession.get(sessionId);
174
+ if (grant === undefined)
175
+ return Effect.sync(() => this.close(sessionId));
176
+ return this.#withExecution(grant.executionDigest, (store) => Effect.gen(function* () {
177
+ // Revoke in memory immediately, but never acknowledge durable closure
178
+ // before its write. Existing provider owners retain only settlement.
179
+ self.#revoke(sessionId);
180
+ const previous = yield* store.read(grant.executionDigest, sessionId);
181
+ if (previous === undefined) {
182
+ return yield* Effect.fail(submissionError("execution-store", "eval grant evidence is missing"));
183
+ }
184
+ assertGrant(previous, { sessionId, ...grant });
185
+ if (previous.state !== "open")
186
+ return false;
187
+ yield* store.update(grant.executionDigest, (current) => {
188
+ assertGrant(current, { sessionId, ...grant });
189
+ return normalizeExecutionEvidence({ ...current, state: "draining" });
190
+ }, { allowReadinessReopen: true });
191
+ return true;
192
+ }));
193
+ }
194
+ bindIngress(sessionId, input) {
195
+ const self = this;
196
+ return Effect.suspend(() => {
197
+ const session = self.#sessions.get(sessionId);
198
+ const grant = session?.executionGrant;
199
+ if (grant === undefined) {
200
+ return Effect.fail(submissionError("execution-unavailable", "strict eval execution grant is unavailable"));
201
+ }
202
+ const candidate = {
203
+ sessionId,
204
+ grantId: grant.grantId,
205
+ executionDigest: grant.executionDigest,
206
+ issuerIdentity: grant.issuerIdentity,
207
+ ingressId: `ingress_${self.#random(16).toString("base64url")}`,
208
+ requestDigest: input.requestDigest,
209
+ model: input.model
210
+ };
211
+ const validated = httpSubmissionBindingSchema.safeParse(candidate);
212
+ if (!validated.success) {
213
+ return Effect.fail(submissionError("ingress-identity", "strict eval ingress identity is invalid"));
214
+ }
215
+ return self.#withExecution(grant.executionDigest, (store) => Effect.gen(function* () {
216
+ self.#assertLiveSession(sessionId, input.model);
217
+ yield* store.update(grant.executionDigest, (current) => {
218
+ assertGrant(current, validated.data);
219
+ assertOpen(current);
220
+ if (current.ingresses.length >= 8192) {
221
+ throw submissionError("ingress-limit", "strict eval ingress bound has been reached");
222
+ }
223
+ return {
224
+ ...current,
225
+ ingresses: [
226
+ ...current.ingresses,
227
+ {
228
+ ingressId: validated.data.ingressId,
229
+ requestDigest: input.requestDigest,
230
+ model: input.model,
231
+ delivery: "pending",
232
+ status: "no-attempt"
233
+ }
234
+ ]
235
+ };
236
+ });
237
+ return validated.data;
238
+ }));
239
+ });
240
+ }
241
+ resolveSubmission(binding) {
242
+ const self = this;
243
+ const parsed = httpSubmissionBindingSchema.safeParse(binding);
244
+ if (!parsed.success) {
245
+ return Effect.fail(submissionError("ingress-identity", "strict eval ingress identity is invalid"));
246
+ }
247
+ return this.#withExecution(binding.executionDigest, (store) => Effect.gen(function* () {
248
+ self.#assertLiveSession(binding.sessionId, binding.model);
249
+ yield* store.update(binding.executionDigest, (current) => {
250
+ const ingress = assertIngress(current, binding);
251
+ assertOpen(current);
252
+ if (ingress.delivery !== "pending") {
253
+ throw submissionError("ingress-consumed", "strict eval ingress has already been delivered");
254
+ }
255
+ return {
256
+ ...current,
257
+ ingresses: current.ingresses.map((entry) => entry.ingressId === binding.ingressId ? { ...entry, delivery: "claimed" } : entry)
258
+ };
259
+ });
260
+ return {
261
+ begin: (request) => self.#begin(binding, request),
262
+ settle: (attemptId, outcome) => self.#settle(binding, attemptId, outcome)
263
+ };
264
+ }));
265
+ }
266
+ #begin(binding, request) {
267
+ const self = this;
268
+ const parsed = httpSubmissionRequestSchema.safeParse(request);
269
+ if (!parsed.success) {
270
+ return Effect.fail(submissionError("submission-identity", "provider submission identity is invalid"));
271
+ }
272
+ return this.#withExecution(binding.executionDigest, (store) => Effect.gen(function* () {
273
+ self.#assertLiveSession(binding.sessionId, binding.model);
274
+ const attemptId = `attempt_${self.#random(16).toString("base64url")}`;
275
+ yield* store.update(binding.executionDigest, (current) => {
276
+ const ingress = assertIngress(current, binding);
277
+ assertOpen(current);
278
+ if (ingress.delivery !== "claimed") {
279
+ throw submissionError("ingress-unclaimed", "strict eval ingress is not claimed");
280
+ }
281
+ if (current.receipts.length >= current.maximumProviderAttempts) {
282
+ throw submissionError("provider-attempt-limit", "strict eval provider attempt allowance is exhausted");
283
+ }
284
+ return normalizeExecutionEvidence({
285
+ ...current,
286
+ ingresses: current.ingresses.map((entry) => entry.ingressId === binding.ingressId ? { ...entry, status: "attempted" } : entry),
287
+ receipts: [
288
+ ...current.receipts,
289
+ {
290
+ attemptId,
291
+ ingressId: binding.ingressId,
292
+ request: parsed.data,
293
+ intentAt: new Date(self.#now()).toISOString(),
294
+ state: "intent"
295
+ }
296
+ ]
297
+ });
298
+ });
299
+ // This is the permit: it cannot escape before the durable intent.
300
+ return { attemptId };
301
+ }));
302
+ }
303
+ #settle(binding, attemptId, outcome) {
304
+ const self = this;
305
+ const parsed = httpSubmissionOutcomeSchema.safeParse(outcome);
306
+ if (!parsed.success) {
307
+ return Effect.fail(submissionError("submission-outcome", "provider submission outcome is invalid"));
308
+ }
309
+ return this.#withExecution(binding.executionDigest, (store) => Effect.gen(function* () {
310
+ const settled = yield* store.update(binding.executionDigest, (current) => {
311
+ assertIngress(current, binding);
312
+ const receipt = current.receipts.find((entry) => entry.attemptId === attemptId);
313
+ if (receipt === undefined || receipt.ingressId !== binding.ingressId) {
314
+ throw submissionError("attempt-identity", "provider submission attempt does not belong to this ingress");
315
+ }
316
+ // Recovery or an earlier unknown result is sticky, including when a
317
+ // retiring process later reports a successful response.
318
+ if (receipt.state === "unknown")
319
+ return current;
320
+ if (receipt.state === "completed") {
321
+ if (JSON.stringify(receipt.outcome) === JSON.stringify(parsed.data))
322
+ return current;
323
+ throw submissionError("attempt-settled", "provider submission attempt is already settled differently");
324
+ }
325
+ return normalizeExecutionEvidence({
326
+ ...current,
327
+ receipts: current.receipts.map((entry) => entry.attemptId === attemptId
328
+ ? {
329
+ ...entry,
330
+ state: parsed.data.state,
331
+ settledAt: new Date(self.#now()).toISOString(),
332
+ outcome: parsed.data
333
+ }
334
+ : entry)
335
+ });
336
+ });
337
+ if (settled.state !== "open")
338
+ self.#revoke(binding.sessionId);
339
+ }));
340
+ }
341
+ executionEvidence(identity) {
342
+ const self = this;
343
+ return Effect.gen(function* () {
344
+ yield* self.initialize();
345
+ const session = self.#sessions.get(identity.sessionId);
346
+ if (session !== undefined && self.#now() >= session.expiresAtMs) {
347
+ yield* self.closeEffect(session.id);
348
+ }
349
+ return yield* self.#withExecution(identity.executionDigest, (store) => Effect.gen(function* () {
350
+ if (self.#poisoned.has(identity.executionDigest)) {
351
+ yield* store.update(identity.executionDigest, (current) => normalizeExecutionEvidence({ ...current, poisoned: true, state: "draining" }));
352
+ }
353
+ const evidence = yield* store.read(identity.executionDigest, identity.sessionId);
354
+ if (evidence === undefined) {
355
+ return yield* Effect.fail(submissionError("execution-not-found", "provider submission grant was not found"));
356
+ }
357
+ assertGrant(evidence, identity);
358
+ return evidence;
359
+ }));
360
+ });
361
+ }
362
+ #assertLiveSession(sessionId, model) {
363
+ const session = this.#sessions.get(sessionId);
364
+ if (session === undefined ||
365
+ session.closed ||
366
+ this.#now() >= session.expiresAtMs ||
367
+ session.executionGrant === undefined ||
368
+ this.#poisoned.has(session.executionGrant.executionDigest) ||
369
+ !session.allowedModels.includes(model)) {
370
+ throw submissionError("execution-closed", "strict eval session is unavailable, expired, or revoked");
371
+ }
372
+ }
373
+ #withExecution(executionDigest, operation) {
374
+ const self = this;
375
+ return this.#gate.withPermit(Effect.suspend(() => {
376
+ const store = self.#store;
377
+ if (store === undefined) {
378
+ return Effect.fail(submissionError("execution-unavailable", "durable provider submission authority is unavailable"));
379
+ }
380
+ return Effect.suspend(() => operation(store)).pipe(Effect.catchCause((cause) => {
381
+ const error = Cause.squash(cause);
382
+ return Effect.fail(error instanceof HttpSubmissionError
383
+ ? error
384
+ : new HttpSubmissionError({
385
+ code: "execution-store",
386
+ message: "provider submission authority failed to verify its durable state",
387
+ cause: error
388
+ }));
389
+ }), Effect.catch((error) => {
390
+ if (error.code !== "execution-store")
391
+ return Effect.fail(error);
392
+ self.#poisoned.add(executionDigest);
393
+ for (const [sessionId, grant] of self.#executionsBySession) {
394
+ if (grant.executionDigest === executionDigest)
395
+ self.#revoke(sessionId);
396
+ }
397
+ // Best effort is only for preserving the poison marker. The
398
+ // mandatory original I/O error always propagates; no receipt or
399
+ // permission is fabricated if this second write also fails.
400
+ return store
401
+ .update(executionDigest, (current) => normalizeExecutionEvidence({ ...current, poisoned: true, state: "draining" }))
402
+ .pipe(Effect.ignore, Effect.andThen(Effect.fail(error)));
403
+ }));
404
+ }).pipe(Effect.uninterruptible));
405
+ }
406
+ shutdown() {
407
+ const self = this;
408
+ return Effect.gen(function* () {
409
+ self.closeAll();
410
+ let firstFailure;
411
+ for (const sessionId of self.#executionsBySession.keys()) {
412
+ yield* self.closeEffect(sessionId).pipe(Effect.catch((error) => Effect.sync(() => {
413
+ firstFailure ??= error;
414
+ })));
415
+ }
416
+ if (firstFailure !== undefined)
417
+ return yield* Effect.fail(firstFailure);
418
+ }).pipe(Effect.ensuring(Effect.sync(() => self.closeAll())));
419
+ }
55
420
  closeAll() {
56
421
  for (const session of this.#sessions.values())
57
422
  session.closed = true;
@@ -79,6 +444,7 @@ export class EvalSessionManager {
79
444
  allowedModels: session.allowedModels,
80
445
  expiresAt: new Date(session.expiresAtMs).toISOString(),
81
446
  perCallOutputTokens: session.limits.perCallOutputTokens,
447
+ ...(session.executionGrant === undefined ? {} : { executionGrant: session.executionGrant }),
82
448
  admit: (model, inputTokenUpperBound, requestedOutputTokens) => this.#admit(session, model, inputTokenUpperBound, requestedOutputTokens)
83
449
  }
84
450
  };
@@ -114,37 +480,41 @@ export class EvalSessionManager {
114
480
  }
115
481
  export class EvalSessions extends Context.Service()("@velum-labs/routekit-daemon/EvalSessions") {
116
482
  static layer(options = {}) {
117
- return Layer.effect(EvalSessions, Effect.acquireRelease(Effect.sync(() => new EvalSessionManager(options)), (sessions) => Effect.sync(() => sessions.closeAll())));
483
+ return Layer.effect(EvalSessions, Effect.acquireRelease(Effect.gen(function* () {
484
+ const sessions = new EvalSessionManager(options);
485
+ yield* sessions.initialize();
486
+ return sessions;
487
+ }), (sessions) => sessions.shutdown().pipe(Effect.orDie)));
118
488
  }
119
489
  }
120
490
  /** Owns short-lived, model-restricted eval data-plane sessions. */
121
- export class EvalSessionApplicationService {
122
- handlers() {
491
+ export const evalSessionHandlers = {
492
+ "evalSession.open": (params) => Effect.gen(function* () {
493
+ const env = yield* DaemonEnv;
494
+ const gateway = yield* ActiveGateway;
495
+ const sessions = yield* EvalSessions;
496
+ const gatewayUrl = gateway.dataUrl();
497
+ if (gatewayUrl === undefined) {
498
+ return yield* Effect.fail(new ControlError({
499
+ code: "unavailable",
500
+ message: "eval session cannot open before the data gateway is ready"
501
+ }));
502
+ }
503
+ return yield* sessions.openEffect({
504
+ ...params,
505
+ gatewayUrl,
506
+ targetIdentity: `routekit-generation:${env.generation}`
507
+ });
508
+ }),
509
+ "evalSession.close": (params) => Effect.gen(function* () {
510
+ const sessions = yield* EvalSessions;
123
511
  return {
124
- "evalSession.open": (params) => Effect.gen(function* () {
125
- const env = yield* DaemonEnv;
126
- const gateway = yield* ActiveGateway;
127
- const sessions = yield* EvalSessions;
128
- const gatewayUrl = gateway.dataUrl();
129
- if (gatewayUrl === undefined) {
130
- return yield* Effect.fail(new ControlError({
131
- code: "unavailable",
132
- message: "eval session cannot open before the data gateway is ready"
133
- }));
134
- }
135
- return sessions.open({
136
- ...params,
137
- gatewayUrl,
138
- targetIdentity: `routekit-generation:${env.generation}`
139
- });
140
- }),
141
- "evalSession.close": (params) => Effect.gen(function* () {
142
- const sessions = yield* EvalSessions;
143
- return {
144
- sessionId: params.sessionId,
145
- closed: sessions.close(params.sessionId)
146
- };
147
- })
512
+ sessionId: params.sessionId,
513
+ closed: yield* sessions.closeEffect(params.sessionId)
148
514
  };
149
- }
150
- }
515
+ }),
516
+ "evalSession.executionEvidence": (params) => Effect.gen(function* () {
517
+ const sessions = yield* EvalSessions;
518
+ return yield* sessions.executionEvidence(params);
519
+ })
520
+ };
@@ -1,8 +1,9 @@
1
1
  import type { RedeemResetCreditResult, ResetCreditSnapshot, SubscriptionAccountSetSnapshot, SubscriptionUsageResponse } from "@velum-labs/routekit-accounts";
2
2
  import type { AccountActivityService, AccountAuthService } from "@velum-labs/routekit-accounts/effect";
3
- import { type ProviderId, type RouterConfig } from "@velum-labs/routekit-config";
4
- import type { CatalogModelInfo, CompositionalRoutingObservation, CompositionalRoutingPolicyReader, Gateway, ProvenanceSink, ProviderSource } from "@velum-labs/routekit-gateway";
3
+ import { type RouterConfig, type SourceId } from "@velum-labs/routekit-config";
4
+ import type { CatalogModelInfo, CompositionalRoutingObservation, CompositionalRoutingPolicyReader, Gateway, GatewayOptions, ProvenanceSink } from "@velum-labs/routekit-gateway";
5
5
  import { RequestDecomposer, RoutingBackend } from "@velum-labs/routekit-gateway";
6
+ import type { ProviderSource } from "@velum-labs/routekit-provider-core";
6
7
  import { type RouteKitPlatform } from "@velum-labs/routekit-runtime/effect";
7
8
  import { Effect, Layer } from "effect";
8
9
  export type GatewayGenerationOptions = {
@@ -11,8 +12,9 @@ export type GatewayGenerationOptions = {
11
12
  port?: number;
12
13
  authToken?: string;
13
14
  env?: NodeJS.ProcessEnv;
14
- sources?: Partial<Record<ProviderId, ProviderSource>>;
15
+ sources?: Partial<Record<SourceId, ProviderSource>>;
15
16
  provenance?: ProvenanceSink;
17
+ resolveHttpSubmission?: GatewayOptions["resolveHttpSubmission"];
16
18
  /** Published model-by-dimension evidence used by automatic routing. */
17
19
  compositionalPolicyReader?: CompositionalRoutingPolicyReader;
18
20
  /** Override the default semantic classifier Layer. */
@@ -51,8 +53,10 @@ export type RunningGatewayGeneration = {
51
53
  modelCatalog(): readonly CatalogModelInfo[];
52
54
  modelInfo(model: string): ReturnType<RoutingBackend["modelInfo"]>;
53
55
  accountSnapshots(): SubscriptionAccountSetSnapshot[];
56
+ activeCredentialFingerprints(): ReadonlyMap<string, string>;
54
57
  usage(signal?: AbortSignal): Effect.Effect<SubscriptionUsageResponse, Error, RouteKitPlatform>;
55
58
  listResetCredits(kind: "codex", label: string, signal?: AbortSignal): Effect.Effect<ResetCreditSnapshot, Error, RouteKitPlatform>;
56
59
  redeemReset(input: GatewayGenerationRedeemResetOptions, signal?: AbortSignal): Effect.Effect<GatewayGenerationRedeemResetResponse, Error, RouteKitPlatform>;
57
60
  };
61
+ export declare function routeKitUpstreamSources(config: RouterConfig, env: NodeJS.ProcessEnv): Partial<Record<SourceId, ProviderSource>>;
58
62
  export declare function startGatewayGenerationEffect(options: GatewayGenerationOptions): Effect.Effect<RunningGatewayGeneration, Error, RouteKitPlatform>;