@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.
- package/dist/account-enroll-service.d.ts +1 -3
- package/dist/account-enroll-service.js +262 -262
- package/dist/account-mutation-service.d.ts +1 -3
- package/dist/account-mutation-service.js +302 -283
- package/dist/account-query-service.d.ts +1 -3
- package/dist/account-query-service.js +88 -86
- package/dist/call-attribution-store.js +544 -1
- package/dist/control-dispatch.js +9 -15
- package/dist/daemon-bootstrap-preflight.js +1 -1
- package/dist/daemon-control-handlers.d.ts +1 -1
- package/dist/daemon-control-handlers.js +24 -27
- package/dist/daemon-generations.d.ts +14 -28
- package/dist/daemon-generations.js +114 -53
- package/dist/daemon-lifecycle-service.d.ts +1 -3
- package/dist/daemon-lifecycle-service.js +88 -94
- package/dist/daemon-maintenance.d.ts +27 -49
- package/dist/daemon-maintenance.js +5 -1
- package/dist/daemon-options.d.ts +3 -2
- package/dist/daemon-policy-context.d.ts +1 -0
- package/dist/daemon-policy-context.js +1 -0
- package/dist/daemon-runtime-state.d.ts +1 -1
- package/dist/daemon-runtime-state.js +2 -3
- package/dist/daemon-state-context.d.ts +1 -1
- package/dist/daemon-state-context.js +1 -38
- package/dist/doctor-service.d.ts +1 -3
- package/dist/doctor-service.js +87 -91
- package/dist/effect/daemon-live.d.ts +7 -6
- package/dist/effect/daemon-live.js +57 -135
- package/dist/eval-routing-service.d.ts +1 -3
- package/dist/eval-routing-service.js +45 -69
- package/dist/eval-session-execution-store.d.ts +24 -0
- package/dist/eval-session-execution-store.js +253 -0
- package/dist/host-protocol.d.ts +1 -0
- package/dist/host.js +3 -0
- package/dist/internal-providers/provider-anthropic/index.d.ts +23 -0
- package/dist/internal-providers/provider-anthropic/index.js +42 -0
- package/dist/internal-providers/provider-anthropic/test/provider.test.js +33 -0
- package/dist/internal-providers/provider-azure/index.d.ts +25 -0
- package/dist/internal-providers/provider-azure/index.js +101 -0
- package/dist/internal-providers/provider-azure/test/provider.test.js +83 -0
- package/dist/internal-providers/provider-bedrock/codec.d.ts +11 -0
- package/dist/internal-providers/provider-bedrock/codec.js +484 -0
- package/dist/internal-providers/provider-bedrock/connection-settings.d.ts +6 -0
- package/dist/internal-providers/provider-bedrock/connection-settings.js +41 -0
- package/dist/internal-providers/provider-bedrock/connection.d.ts +17 -0
- package/dist/internal-providers/provider-bedrock/connection.js +143 -0
- package/dist/internal-providers/provider-bedrock/index.d.ts +27 -0
- package/dist/internal-providers/provider-bedrock/index.js +47 -0
- package/dist/internal-providers/provider-bedrock/source.d.ts +39 -0
- package/dist/internal-providers/provider-bedrock/source.js +454 -0
- package/dist/internal-providers/provider-bedrock/test/bedrock-source.test.d.ts +1 -0
- package/dist/internal-providers/provider-bedrock/test/bedrock-source.test.js +786 -0
- package/dist/internal-providers/provider-bedrock/test/connection.test.d.ts +1 -0
- package/dist/internal-providers/provider-bedrock/test/connection.test.js +249 -0
- package/dist/internal-providers/provider-bedrock/test/workload-credentials.test.d.ts +1 -0
- package/dist/internal-providers/provider-bedrock/test/workload-credentials.test.js +29 -0
- package/dist/internal-providers/provider-bedrock/workload-credentials.d.ts +5 -0
- package/dist/internal-providers/provider-bedrock/workload-credentials.js +14 -0
- package/dist/internal-providers/provider-claude-code/index.d.ts +6 -0
- package/dist/internal-providers/provider-claude-code/index.js +4 -0
- package/dist/internal-providers/provider-claude-code/module-api.d.ts +25 -0
- package/dist/internal-providers/provider-claude-code/module-api.js +31 -0
- package/dist/internal-providers/provider-claude-code/relay.d.ts +20 -0
- package/dist/internal-providers/provider-claude-code/relay.js +115 -0
- package/dist/internal-providers/provider-claude-code/subscription.d.ts +2 -0
- package/dist/internal-providers/provider-claude-code/subscription.js +194 -0
- package/dist/internal-providers/provider-claude-code/test/http-submission.test.d.ts +1 -0
- package/dist/internal-providers/provider-claude-code/test/http-submission.test.js +140 -0
- package/dist/internal-providers/provider-claude-code/test/relay.test.d.ts +1 -0
- package/dist/internal-providers/provider-claude-code/test/relay.test.js +379 -0
- package/dist/internal-providers/provider-claude-code/test/subscription.test.d.ts +1 -0
- package/dist/internal-providers/provider-claude-code/test/subscription.test.js +256 -0
- package/dist/internal-providers/provider-cliproxy/index.d.ts +9 -0
- package/dist/internal-providers/provider-cliproxy/index.js +15 -0
- package/dist/internal-providers/provider-codex/index.d.ts +6 -0
- package/dist/internal-providers/provider-codex/index.js +7 -0
- package/dist/internal-providers/provider-codex/module-api.d.ts +25 -0
- package/dist/internal-providers/provider-codex/module-api.js +31 -0
- package/dist/internal-providers/provider-codex/relay.d.ts +123 -0
- package/dist/internal-providers/provider-codex/relay.js +261 -0
- package/dist/internal-providers/provider-codex/subscription.d.ts +2 -0
- package/dist/internal-providers/provider-codex/subscription.js +555 -0
- package/dist/internal-providers/provider-codex/test/http-submission.test.d.ts +1 -0
- package/dist/internal-providers/provider-codex/test/http-submission.test.js +142 -0
- package/dist/internal-providers/provider-codex/test/relay.test.d.ts +1 -0
- package/dist/internal-providers/provider-codex/test/relay.test.js +600 -0
- package/dist/internal-providers/provider-codex/test/subscription.test.d.ts +1 -0
- package/dist/internal-providers/provider-codex/test/subscription.test.js +70 -0
- package/dist/internal-providers/provider-codex/test/translated-content.test.d.ts +1 -0
- package/dist/internal-providers/provider-codex/test/translated-content.test.js +91 -0
- package/dist/internal-providers/provider-custom/index.d.ts +9 -0
- package/dist/internal-providers/provider-custom/index.js +15 -0
- package/dist/internal-providers/provider-google/index.d.ts +23 -0
- package/dist/internal-providers/provider-google/index.js +43 -0
- package/dist/internal-providers/provider-google/test/provider.test.d.ts +1 -0
- package/dist/internal-providers/provider-google/test/provider.test.js +34 -0
- package/dist/internal-providers/provider-google-vertex/index.d.ts +28 -0
- package/dist/internal-providers/provider-google-vertex/index.js +137 -0
- package/dist/internal-providers/provider-google-vertex/test/provider.test.d.ts +1 -0
- package/dist/internal-providers/provider-google-vertex/test/provider.test.js +85 -0
- package/dist/internal-providers/provider-openai/documented-reasoning.d.ts +9 -0
- package/dist/internal-providers/provider-openai/documented-reasoning.js +26 -0
- package/dist/internal-providers/provider-openai/index.d.ts +23 -0
- package/dist/internal-providers/provider-openai/index.js +74 -0
- package/dist/internal-providers/provider-openai/test/provider.test.d.ts +1 -0
- package/dist/internal-providers/provider-openai/test/provider.test.js +268 -0
- package/dist/internal-providers/provider-openai-compatible/index.d.ts +9 -0
- package/dist/internal-providers/provider-openai-compatible/index.js +15 -0
- package/dist/internal-providers/provider-openai-compatible/test/provider.test.d.ts +1 -0
- package/dist/internal-providers/provider-openai-compatible/test/provider.test.js +40 -0
- package/dist/internal-providers/provider-openrouter/connection.d.ts +5 -0
- package/dist/internal-providers/provider-openrouter/connection.js +143 -0
- package/dist/internal-providers/provider-openrouter/index.d.ts +28 -0
- package/dist/internal-providers/provider-openrouter/index.js +42 -0
- package/dist/internal-providers/provider-openrouter/pricing.d.ts +4 -0
- package/dist/internal-providers/provider-openrouter/pricing.js +108 -0
- package/dist/internal-providers/provider-openrouter/test/connection.test.d.ts +1 -0
- package/dist/internal-providers/provider-openrouter/test/connection.test.js +71 -0
- package/dist/internal-providers/provider-openrouter/test/pricing.test.d.ts +1 -0
- package/dist/internal-providers/provider-openrouter/test/pricing.test.js +45 -0
- package/dist/internal-providers/provider-openrouter/test/provider.test.d.ts +1 -0
- package/dist/internal-providers/provider-openrouter/test/provider.test.js +141 -0
- package/dist/internal-providers/provider-openrouter/test/validation.test.d.ts +1 -0
- package/dist/internal-providers/provider-openrouter/test/validation.test.js +225 -0
- package/dist/launcher-service.d.ts +3 -7
- package/dist/launcher-service.js +144 -133
- package/dist/provider-modules.d.ts +6 -0
- package/dist/provider-modules.js +55 -0
- package/dist/provider-query-service.d.ts +1 -3
- package/dist/provider-query-service.js +140 -143
- package/dist/router-generation-service.d.ts +1 -3
- package/dist/router-generation-service.js +83 -78
- package/dist/services/eval-session/service.d.ts +20 -8
- package/dist/services/eval-session/service.js +410 -40
- package/dist/services/gateway-generation/service.d.ts +7 -3
- package/dist/services/gateway-generation/service.js +126 -63
- package/dist/services/generations/service.d.ts +3 -3
- package/dist/services/generations/service.js +5 -1
- package/dist/telemetry-application-service.d.ts +1 -3
- package/dist/telemetry-application-service.js +41 -45
- package/dist/test/call-attribution-store.test.js +186 -0
- package/dist/test/daemon-accounts.test.js +2 -2
- package/dist/test/daemon-data-plane.test.js +24 -45
- package/dist/test/daemon-fixtures.js +1 -1
- package/dist/test/daemon-generation-transaction.test.js +66 -2
- package/dist/test/daemon-runtime-state.test.js +28 -1
- package/dist/test/effect-daemon.test.js +38 -0
- package/dist/test/eval-routing-application-service.test.js +6 -6
- package/dist/test/eval-session-execution.test.d.ts +1 -0
- package/dist/test/eval-session-execution.test.js +356 -0
- package/dist/test/eval-session-service.test.js +70 -1
- package/dist/test/host-worker-session.test.js +1 -0
- package/dist/test/launcher-session.test.d.ts +1 -0
- package/dist/test/launcher-session.test.js +87 -0
- package/dist/test/leaderboard.test.js +25 -1
- package/dist/test/provider-modules.test.d.ts +1 -0
- package/dist/test/provider-modules.test.js +65 -0
- package/dist/test/routekit-upstream-sources.test.d.ts +1 -0
- package/dist/test/routekit-upstream-sources.test.js +40 -0
- package/dist/test/runtime-composition-types.test.js +11 -0
- package/dist/token-application-service.d.ts +1 -3
- package/dist/token-application-service.js +38 -44
- package/dist/worker.js +5 -3
- package/package.json +35 -11
- package/dist/application-services.d.ts +0 -10
- package/dist/application-services.js +0 -22
- package/dist/daemon-lifecycle.d.ts +0 -54
- package/dist/daemon-lifecycle.js +0 -177
- package/dist/test/application-services.test.js +0 -135
- package/dist/test/daemon-lifecycle.test.js +0 -182
- /package/dist/{test/application-services.test.d.ts → internal-providers/provider-anthropic/test/provider.test.d.ts} +0 -0
- /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 {
|
|
4
|
-
import {
|
|
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
|
-
|
|
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
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
|
122
|
-
|
|
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
|
-
|
|
125
|
-
|
|
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
|
|
4
|
-
import type { CatalogModelInfo, CompositionalRoutingObservation, CompositionalRoutingPolicyReader, 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<
|
|
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>;
|