@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
|
@@ -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
|
+
});
|
package/dist/host-protocol.d.ts
CHANGED
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;
|