@velum-labs/routekit-daemon 1.3.2 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (172) hide show
  1. package/dist/account-enroll-service.d.ts +1 -3
  2. package/dist/account-enroll-service.js +262 -262
  3. package/dist/account-mutation-service.d.ts +1 -3
  4. package/dist/account-mutation-service.js +302 -283
  5. package/dist/account-query-service.d.ts +1 -3
  6. package/dist/account-query-service.js +88 -86
  7. package/dist/call-attribution-store.js +544 -1
  8. package/dist/control-dispatch.js +9 -15
  9. package/dist/daemon-bootstrap-preflight.js +1 -1
  10. package/dist/daemon-control-handlers.d.ts +1 -1
  11. package/dist/daemon-control-handlers.js +24 -27
  12. package/dist/daemon-generations.d.ts +14 -28
  13. package/dist/daemon-generations.js +114 -53
  14. package/dist/daemon-lifecycle-service.d.ts +1 -3
  15. package/dist/daemon-lifecycle-service.js +88 -94
  16. package/dist/daemon-maintenance.d.ts +27 -49
  17. package/dist/daemon-maintenance.js +5 -1
  18. package/dist/daemon-options.d.ts +3 -2
  19. package/dist/daemon-policy-context.d.ts +1 -0
  20. package/dist/daemon-policy-context.js +1 -0
  21. package/dist/daemon-runtime-state.d.ts +1 -1
  22. package/dist/daemon-runtime-state.js +2 -3
  23. package/dist/daemon-state-context.d.ts +1 -1
  24. package/dist/daemon-state-context.js +1 -38
  25. package/dist/doctor-service.d.ts +1 -3
  26. package/dist/doctor-service.js +87 -91
  27. package/dist/effect/daemon-live.d.ts +7 -6
  28. package/dist/effect/daemon-live.js +57 -135
  29. package/dist/eval-routing-service.d.ts +1 -3
  30. package/dist/eval-routing-service.js +45 -69
  31. package/dist/eval-session-execution-store.d.ts +24 -0
  32. package/dist/eval-session-execution-store.js +253 -0
  33. package/dist/host-protocol.d.ts +1 -0
  34. package/dist/host.js +3 -0
  35. package/dist/internal-providers/provider-anthropic/index.d.ts +23 -0
  36. package/dist/internal-providers/provider-anthropic/index.js +42 -0
  37. package/dist/internal-providers/provider-anthropic/test/provider.test.js +33 -0
  38. package/dist/internal-providers/provider-azure/index.d.ts +25 -0
  39. package/dist/internal-providers/provider-azure/index.js +101 -0
  40. package/dist/internal-providers/provider-azure/test/provider.test.js +83 -0
  41. package/dist/internal-providers/provider-bedrock/codec.d.ts +11 -0
  42. package/dist/internal-providers/provider-bedrock/codec.js +484 -0
  43. package/dist/internal-providers/provider-bedrock/connection-settings.d.ts +6 -0
  44. package/dist/internal-providers/provider-bedrock/connection-settings.js +41 -0
  45. package/dist/internal-providers/provider-bedrock/connection.d.ts +17 -0
  46. package/dist/internal-providers/provider-bedrock/connection.js +143 -0
  47. package/dist/internal-providers/provider-bedrock/index.d.ts +27 -0
  48. package/dist/internal-providers/provider-bedrock/index.js +47 -0
  49. package/dist/internal-providers/provider-bedrock/source.d.ts +39 -0
  50. package/dist/internal-providers/provider-bedrock/source.js +454 -0
  51. package/dist/internal-providers/provider-bedrock/test/bedrock-source.test.d.ts +1 -0
  52. package/dist/internal-providers/provider-bedrock/test/bedrock-source.test.js +786 -0
  53. package/dist/internal-providers/provider-bedrock/test/connection.test.d.ts +1 -0
  54. package/dist/internal-providers/provider-bedrock/test/connection.test.js +249 -0
  55. package/dist/internal-providers/provider-bedrock/test/workload-credentials.test.d.ts +1 -0
  56. package/dist/internal-providers/provider-bedrock/test/workload-credentials.test.js +29 -0
  57. package/dist/internal-providers/provider-bedrock/workload-credentials.d.ts +5 -0
  58. package/dist/internal-providers/provider-bedrock/workload-credentials.js +14 -0
  59. package/dist/internal-providers/provider-claude-code/index.d.ts +6 -0
  60. package/dist/internal-providers/provider-claude-code/index.js +4 -0
  61. package/dist/internal-providers/provider-claude-code/module-api.d.ts +25 -0
  62. package/dist/internal-providers/provider-claude-code/module-api.js +31 -0
  63. package/dist/internal-providers/provider-claude-code/relay.d.ts +20 -0
  64. package/dist/internal-providers/provider-claude-code/relay.js +115 -0
  65. package/dist/internal-providers/provider-claude-code/subscription.d.ts +2 -0
  66. package/dist/internal-providers/provider-claude-code/subscription.js +194 -0
  67. package/dist/internal-providers/provider-claude-code/test/http-submission.test.d.ts +1 -0
  68. package/dist/internal-providers/provider-claude-code/test/http-submission.test.js +140 -0
  69. package/dist/internal-providers/provider-claude-code/test/relay.test.d.ts +1 -0
  70. package/dist/internal-providers/provider-claude-code/test/relay.test.js +379 -0
  71. package/dist/internal-providers/provider-claude-code/test/subscription.test.d.ts +1 -0
  72. package/dist/internal-providers/provider-claude-code/test/subscription.test.js +256 -0
  73. package/dist/internal-providers/provider-cliproxy/index.d.ts +9 -0
  74. package/dist/internal-providers/provider-cliproxy/index.js +15 -0
  75. package/dist/internal-providers/provider-codex/index.d.ts +6 -0
  76. package/dist/internal-providers/provider-codex/index.js +7 -0
  77. package/dist/internal-providers/provider-codex/module-api.d.ts +25 -0
  78. package/dist/internal-providers/provider-codex/module-api.js +31 -0
  79. package/dist/internal-providers/provider-codex/relay.d.ts +123 -0
  80. package/dist/internal-providers/provider-codex/relay.js +261 -0
  81. package/dist/internal-providers/provider-codex/subscription.d.ts +2 -0
  82. package/dist/internal-providers/provider-codex/subscription.js +555 -0
  83. package/dist/internal-providers/provider-codex/test/http-submission.test.d.ts +1 -0
  84. package/dist/internal-providers/provider-codex/test/http-submission.test.js +142 -0
  85. package/dist/internal-providers/provider-codex/test/relay.test.d.ts +1 -0
  86. package/dist/internal-providers/provider-codex/test/relay.test.js +600 -0
  87. package/dist/internal-providers/provider-codex/test/subscription.test.d.ts +1 -0
  88. package/dist/internal-providers/provider-codex/test/subscription.test.js +70 -0
  89. package/dist/internal-providers/provider-codex/test/translated-content.test.d.ts +1 -0
  90. package/dist/internal-providers/provider-codex/test/translated-content.test.js +91 -0
  91. package/dist/internal-providers/provider-custom/index.d.ts +9 -0
  92. package/dist/internal-providers/provider-custom/index.js +15 -0
  93. package/dist/internal-providers/provider-google/index.d.ts +23 -0
  94. package/dist/internal-providers/provider-google/index.js +43 -0
  95. package/dist/internal-providers/provider-google/test/provider.test.d.ts +1 -0
  96. package/dist/internal-providers/provider-google/test/provider.test.js +34 -0
  97. package/dist/internal-providers/provider-google-vertex/index.d.ts +28 -0
  98. package/dist/internal-providers/provider-google-vertex/index.js +137 -0
  99. package/dist/internal-providers/provider-google-vertex/test/provider.test.d.ts +1 -0
  100. package/dist/internal-providers/provider-google-vertex/test/provider.test.js +85 -0
  101. package/dist/internal-providers/provider-openai/documented-reasoning.d.ts +9 -0
  102. package/dist/internal-providers/provider-openai/documented-reasoning.js +26 -0
  103. package/dist/internal-providers/provider-openai/index.d.ts +23 -0
  104. package/dist/internal-providers/provider-openai/index.js +74 -0
  105. package/dist/internal-providers/provider-openai/test/provider.test.d.ts +1 -0
  106. package/dist/internal-providers/provider-openai/test/provider.test.js +268 -0
  107. package/dist/internal-providers/provider-openai-compatible/index.d.ts +9 -0
  108. package/dist/internal-providers/provider-openai-compatible/index.js +15 -0
  109. package/dist/internal-providers/provider-openai-compatible/test/provider.test.d.ts +1 -0
  110. package/dist/internal-providers/provider-openai-compatible/test/provider.test.js +40 -0
  111. package/dist/internal-providers/provider-openrouter/connection.d.ts +5 -0
  112. package/dist/internal-providers/provider-openrouter/connection.js +143 -0
  113. package/dist/internal-providers/provider-openrouter/index.d.ts +28 -0
  114. package/dist/internal-providers/provider-openrouter/index.js +42 -0
  115. package/dist/internal-providers/provider-openrouter/pricing.d.ts +4 -0
  116. package/dist/internal-providers/provider-openrouter/pricing.js +108 -0
  117. package/dist/internal-providers/provider-openrouter/test/connection.test.d.ts +1 -0
  118. package/dist/internal-providers/provider-openrouter/test/connection.test.js +71 -0
  119. package/dist/internal-providers/provider-openrouter/test/pricing.test.d.ts +1 -0
  120. package/dist/internal-providers/provider-openrouter/test/pricing.test.js +45 -0
  121. package/dist/internal-providers/provider-openrouter/test/provider.test.d.ts +1 -0
  122. package/dist/internal-providers/provider-openrouter/test/provider.test.js +141 -0
  123. package/dist/internal-providers/provider-openrouter/test/validation.test.d.ts +1 -0
  124. package/dist/internal-providers/provider-openrouter/test/validation.test.js +225 -0
  125. package/dist/launcher-service.d.ts +3 -7
  126. package/dist/launcher-service.js +144 -133
  127. package/dist/provider-modules.d.ts +6 -0
  128. package/dist/provider-modules.js +55 -0
  129. package/dist/provider-query-service.d.ts +1 -3
  130. package/dist/provider-query-service.js +140 -143
  131. package/dist/router-generation-service.d.ts +1 -3
  132. package/dist/router-generation-service.js +83 -78
  133. package/dist/services/eval-session/service.d.ts +20 -8
  134. package/dist/services/eval-session/service.js +410 -40
  135. package/dist/services/gateway-generation/service.d.ts +7 -3
  136. package/dist/services/gateway-generation/service.js +126 -63
  137. package/dist/services/generations/service.d.ts +3 -3
  138. package/dist/services/generations/service.js +5 -1
  139. package/dist/telemetry-application-service.d.ts +1 -3
  140. package/dist/telemetry-application-service.js +41 -45
  141. package/dist/test/call-attribution-store.test.js +186 -0
  142. package/dist/test/daemon-accounts.test.js +2 -2
  143. package/dist/test/daemon-data-plane.test.js +24 -45
  144. package/dist/test/daemon-fixtures.js +1 -1
  145. package/dist/test/daemon-generation-transaction.test.js +66 -2
  146. package/dist/test/daemon-runtime-state.test.js +28 -1
  147. package/dist/test/effect-daemon.test.js +38 -0
  148. package/dist/test/eval-routing-application-service.test.js +6 -6
  149. package/dist/test/eval-session-execution.test.d.ts +1 -0
  150. package/dist/test/eval-session-execution.test.js +356 -0
  151. package/dist/test/eval-session-service.test.js +70 -1
  152. package/dist/test/host-worker-session.test.js +1 -0
  153. package/dist/test/launcher-session.test.d.ts +1 -0
  154. package/dist/test/launcher-session.test.js +87 -0
  155. package/dist/test/leaderboard.test.js +25 -1
  156. package/dist/test/provider-modules.test.d.ts +1 -0
  157. package/dist/test/provider-modules.test.js +65 -0
  158. package/dist/test/routekit-upstream-sources.test.d.ts +1 -0
  159. package/dist/test/routekit-upstream-sources.test.js +40 -0
  160. package/dist/test/runtime-composition-types.test.js +11 -0
  161. package/dist/token-application-service.d.ts +1 -3
  162. package/dist/token-application-service.js +38 -44
  163. package/dist/worker.js +5 -3
  164. package/package.json +35 -11
  165. package/dist/application-services.d.ts +0 -10
  166. package/dist/application-services.js +0 -22
  167. package/dist/daemon-lifecycle.d.ts +0 -54
  168. package/dist/daemon-lifecycle.js +0 -177
  169. package/dist/test/application-services.test.js +0 -135
  170. package/dist/test/daemon-lifecycle.test.js +0 -182
  171. /package/dist/{test/application-services.test.d.ts → internal-providers/provider-anthropic/test/provider.test.d.ts} +0 -0
  172. /package/dist/{test/daemon-lifecycle.test.d.ts → internal-providers/provider-azure/test/provider.test.d.ts} +0 -0
@@ -0,0 +1,786 @@
1
+ import assert from "node:assert/strict";
2
+ import { test } from "node:test";
3
+ import { ListFoundationModelsCommand } from "@aws-sdk/client-bedrock";
4
+ import { ConverseCommand, ConverseStreamCommand } from "@aws-sdk/client-bedrock-runtime";
5
+ import { runRouteKitEffect } from "@velum-labs/routekit-runtime/effect";
6
+ import { Effect } from "effect";
7
+ import { BEDROCK_OPENAI_ALLOWLIST, BedrockProviderSource, isBedrockOpenAiModel, sanitizeBedrockMantleRequestBody, toBedrockConverseInput } from "../source.js";
8
+ test("Bedrock discovery includes active Anthropic foundations and paginated backed profiles", async () => {
9
+ const commands = [];
10
+ const source = new BedrockProviderSource({
11
+ controlClient: {
12
+ send: async (command) => {
13
+ commands.push(command);
14
+ if (command instanceof ListFoundationModelsCommand)
15
+ return {
16
+ modelSummaries: [
17
+ {
18
+ modelId: "anthropic.claude-3",
19
+ providerName: "Anthropic",
20
+ modelLifecycle: { status: "ACTIVE" },
21
+ inputModalities: ["TEXT", "IMAGE"],
22
+ outputModalities: ["TEXT"],
23
+ responseStreamingSupported: true
24
+ },
25
+ {
26
+ modelId: "anthropic.claude-opus-5",
27
+ providerName: "Anthropic",
28
+ modelLifecycle: { status: "ACTIVE" },
29
+ inputModalities: ["TEXT"],
30
+ outputModalities: ["TEXT"],
31
+ responseStreamingSupported: true
32
+ },
33
+ {
34
+ modelId: "anthropic.old",
35
+ providerName: "Anthropic",
36
+ modelLifecycle: { status: "LEGACY" }
37
+ },
38
+ {
39
+ modelId: "amazon.titan",
40
+ providerName: "Amazon",
41
+ modelLifecycle: { status: "ACTIVE" }
42
+ }
43
+ ]
44
+ };
45
+ const token = command.input.nextToken;
46
+ return token === undefined
47
+ ? {
48
+ inferenceProfileSummaries: [
49
+ {
50
+ inferenceProfileId: "us.anthropic.claude-3",
51
+ inferenceProfileName: "Claude",
52
+ inferenceProfileArn: "arn:profile",
53
+ status: "ACTIVE",
54
+ type: "SYSTEM_DEFINED",
55
+ createdAt: new Date("2026-07-09T00:00:00Z"),
56
+ models: [
57
+ { modelArn: "arn:aws:bedrock:us-east-1::foundation-model/anthropic.claude-3" }
58
+ ]
59
+ },
60
+ {
61
+ inferenceProfileId: "us.anthropic.claude-opus-5",
62
+ inferenceProfileName: "Opus 5",
63
+ inferenceProfileArn: "arn:profile-opus-5",
64
+ status: "ACTIVE",
65
+ type: "SYSTEM_DEFINED",
66
+ models: [
67
+ {
68
+ modelArn: "arn:aws:bedrock:us-east-1::foundation-model/anthropic.claude-opus-5"
69
+ }
70
+ ]
71
+ },
72
+ {
73
+ inferenceProfileId: "us.amazon.titan",
74
+ inferenceProfileName: "Titan",
75
+ inferenceProfileArn: "arn:profile2",
76
+ status: "ACTIVE",
77
+ type: "SYSTEM_DEFINED",
78
+ models: [{ modelArn: "arn:aws:bedrock:us-east-1::foundation-model/amazon.titan" }]
79
+ }
80
+ ],
81
+ nextToken: "page-2"
82
+ }
83
+ : {
84
+ inferenceProfileSummaries: [
85
+ {
86
+ inferenceProfileId: "eu.anthropic.claude-3",
87
+ inferenceProfileName: "Claude EU",
88
+ inferenceProfileArn: "arn:profile3",
89
+ status: "ACTIVE",
90
+ type: "SYSTEM_DEFINED",
91
+ models: [
92
+ { modelArn: "arn:aws:bedrock:eu-west-1::foundation-model/anthropic.claude-3" }
93
+ ]
94
+ }
95
+ ]
96
+ };
97
+ }
98
+ },
99
+ runtimeClient: { send: async () => ({}) }
100
+ });
101
+ const discovered = await runRouteKitEffect(source.discovery.discoverModels());
102
+ assert.deepEqual(discovered.map((model) => model.id), [
103
+ "anthropic.claude-3",
104
+ "anthropic.claude-opus-5",
105
+ "us.anthropic.claude-3",
106
+ "us.anthropic.claude-opus-5",
107
+ "eu.anthropic.claude-3"
108
+ ]);
109
+ assert.deepEqual(discovered[0]?.metadata?.architecture, {
110
+ modality: "text+image->text",
111
+ inputModalities: ["text", "image"],
112
+ outputModalities: ["text"]
113
+ });
114
+ assert.deepEqual(discovered[2]?.metadata, discovered[0]?.metadata);
115
+ assert.ok(discovered.every((model) => model.createdAt === undefined && model.providerPriority === undefined), "profile creation time is not model recency");
116
+ assert.equal(discovered[0]?.capabilities?.streaming, "supported");
117
+ assert.equal(discovered[0]?.reasoning, undefined);
118
+ assert.deepEqual(discovered[1]?.reasoning, {
119
+ status: "supported",
120
+ efforts: [{ id: "low" }, { id: "medium" }, { id: "high" }, { id: "max" }],
121
+ adaptive: true,
122
+ wireShape: "bedrock-converse",
123
+ provenance: "builtin"
124
+ });
125
+ assert.deepEqual(discovered[3]?.reasoning, discovered[1]?.reasoning);
126
+ assert.equal(commands.length, 3);
127
+ });
128
+ test("Bedrock OpenAI model ids stay on mantle and never match Anthropic Converse", () => {
129
+ assert.equal(isBedrockOpenAiModel("openai.gpt-5.7"), true);
130
+ assert.equal(isBedrockOpenAiModel("openai.gpt-5.6-sol"), true);
131
+ assert.equal(isBedrockOpenAiModel("us.openai.gpt-5.6-terra"), true);
132
+ assert.equal(isBedrockOpenAiModel("anthropic.claude-3"), false);
133
+ assert.equal(isBedrockOpenAiModel("us.anthropic.claude-3"), false);
134
+ });
135
+ test("Bedrock discovery advertises only verified OpenAI mantle models", async () => {
136
+ assert.deepEqual(BEDROCK_OPENAI_ALLOWLIST, [
137
+ "openai.gpt-5.6-sol",
138
+ "openai.gpt-5.6-terra",
139
+ "openai.gpt-5.6-luna"
140
+ ]);
141
+ const source = new BedrockProviderSource({
142
+ env: { AWS_BEARER_TOKEN_BEDROCK: "bedrock-key", AWS_REGION: "us-east-1" },
143
+ controlClient: {
144
+ send: async (command) => {
145
+ if (command instanceof ListFoundationModelsCommand) {
146
+ return {
147
+ modelSummaries: [
148
+ {
149
+ modelId: "anthropic.claude-3",
150
+ providerName: "Anthropic",
151
+ modelLifecycle: { status: "ACTIVE" }
152
+ }
153
+ ]
154
+ };
155
+ }
156
+ return { inferenceProfileSummaries: [] };
157
+ }
158
+ },
159
+ runtimeClient: { send: async () => ({}) }
160
+ });
161
+ const discovered = await runRouteKitEffect(source.discovery.discoverModels());
162
+ assert.deepEqual(discovered.map((model) => model.id), ["anthropic.claude-3", ...BEDROCK_OPENAI_ALLOWLIST]);
163
+ const sol = discovered.find((model) => model.id === "openai.gpt-5.6-sol");
164
+ assert.equal(sol?.reasoning?.wireShape, "openai-responses");
165
+ assert.equal(sol?.reasoning?.defaultEffort, "medium");
166
+ });
167
+ test("Bedrock discovery keeps OpenAI models when Anthropic listing fails", async () => {
168
+ const source = new BedrockProviderSource({
169
+ env: { AWS_BEARER_TOKEN_BEDROCK: "bedrock-key", AWS_REGION: "us-east-1" },
170
+ controlClient: {
171
+ send: async () => {
172
+ throw new Error("not authorized");
173
+ }
174
+ },
175
+ runtimeClient: { send: async () => ({}) }
176
+ });
177
+ const discovered = await runRouteKitEffect(source.discovery.discoverModels());
178
+ assert.deepEqual(discovered.map((model) => model.id), [...BEDROCK_OPENAI_ALLOWLIST]);
179
+ });
180
+ test("Bedrock routes OpenAI models through mantle and leaves Anthropic on Converse", async () => {
181
+ const runtimeCalls = [];
182
+ const mantleCalls = [];
183
+ const source = new BedrockProviderSource({
184
+ env: { AWS_BEARER_TOKEN_BEDROCK: "bedrock-key", AWS_REGION: "us-east-1" },
185
+ controlClient: { send: async () => ({}) },
186
+ runtimeClient: {
187
+ send: async (value) => {
188
+ runtimeCalls.push(value);
189
+ return {
190
+ $metadata: { requestId: "req-1" },
191
+ output: { message: { role: "assistant", content: [{ text: "claude" }] } },
192
+ stopReason: "end_turn"
193
+ };
194
+ }
195
+ },
196
+ mantleBackend: {
197
+ chat: (body) => {
198
+ const model = typeof body.model === "string"
199
+ ? body.model
200
+ : undefined;
201
+ mantleCalls.push({ kind: "chat", ...(model !== undefined ? { model } : {}) });
202
+ return Effect.succeed(Response.json({ id: "chat", model }));
203
+ },
204
+ responses: (body) => {
205
+ const model = typeof body.model === "string"
206
+ ? body.model
207
+ : undefined;
208
+ mantleCalls.push({ kind: "responses", ...(model !== undefined ? { model } : {}) });
209
+ return Effect.succeed(Response.json({ id: "resp", model }));
210
+ }
211
+ }
212
+ });
213
+ assert.equal(source.responses.kind === "responses" && source.responses.supports("openai.gpt-5.6-sol"), true);
214
+ assert.equal(source.responses.kind === "responses" && source.responses.supports("anthropic.claude-3"), false);
215
+ assert.equal(source.capabilities.reasoningForModel("openai.gpt-5.6-sol")?.wireShape, "openai-responses");
216
+ const openaiChat = await runRouteKitEffect(source.requests.chat({
217
+ model: "openai.gpt-5.6-sol",
218
+ messages: [{ role: "user", content: "hi" }]
219
+ }));
220
+ assert.deepEqual(await openaiChat.json(), { id: "chat", model: "openai.gpt-5.6-sol" });
221
+ assert.equal(source.responses.kind, "responses");
222
+ if (source.responses.kind !== "responses")
223
+ throw new Error("expected Responses support");
224
+ const openaiResponses = await runRouteKitEffect(source.responses.execute({
225
+ model: "openai.gpt-5.6-terra",
226
+ input: "hi"
227
+ }));
228
+ assert.deepEqual(await openaiResponses.json(), {
229
+ id: "resp",
230
+ model: "openai.gpt-5.6-terra"
231
+ });
232
+ const anthropic = await runRouteKitEffect(source.requests.chat({
233
+ model: "anthropic.claude-3",
234
+ messages: [{ role: "user", content: "hi" }]
235
+ }));
236
+ assert.equal(anthropic.status, 200);
237
+ assert.equal(runtimeCalls.length, 1);
238
+ assert.equal(runtimeCalls[0] instanceof ConverseCommand, true);
239
+ assert.deepEqual(mantleCalls, [
240
+ { kind: "chat", model: "openai.gpt-5.6-sol" },
241
+ { kind: "responses", model: "openai.gpt-5.6-terra" }
242
+ ]);
243
+ const rejected = await runRouteKitEffect(source.responses.execute({
244
+ model: "anthropic.claude-3",
245
+ input: "hi"
246
+ }));
247
+ assert.equal(rejected.status, 501);
248
+ });
249
+ test("Bedrock mantle sanitizes Codex-only request fields before egress", async () => {
250
+ const forwarded = [];
251
+ const source = new BedrockProviderSource({
252
+ env: { AWS_BEARER_TOKEN_BEDROCK: "bedrock-key", AWS_REGION: "us-east-1" },
253
+ controlClient: { send: async () => ({}) },
254
+ runtimeClient: { send: async () => ({}) },
255
+ mantleBackend: {
256
+ chat: () => Effect.succeed(Response.json({})),
257
+ responses: (body) => {
258
+ forwarded.push(body);
259
+ return Effect.succeed(Response.json({ id: "resp" }));
260
+ }
261
+ }
262
+ });
263
+ const body = {
264
+ model: "openai.gpt-5.6-sol",
265
+ tools: [{ type: "web_search", search_content_types: ["text", "image"] }],
266
+ input: [
267
+ {
268
+ type: "agent_message",
269
+ author: "/root",
270
+ recipient: "/root/worker",
271
+ content: [
272
+ { type: "input_text", text: "hi" },
273
+ { type: "encrypted_content", encrypted_content: " " }
274
+ ]
275
+ },
276
+ { type: "compaction", encrypted_content: "not-a-prefix" },
277
+ { type: "reasoning", encrypted_content: "rsn_ok", summary: [] }
278
+ ]
279
+ };
280
+ assert.deepEqual(sanitizeBedrockMantleRequestBody(body), {
281
+ model: "openai.gpt-5.6-sol",
282
+ tools: [{ type: "web_search" }],
283
+ input: [
284
+ {
285
+ type: "message",
286
+ role: "user",
287
+ content: [{ type: "input_text", text: "hi" }]
288
+ },
289
+ { type: "reasoning", encrypted_content: "rsn_ok", summary: [] }
290
+ ]
291
+ });
292
+ assert.equal(source.responses.kind, "responses");
293
+ if (source.responses.kind !== "responses")
294
+ throw new Error("expected Responses support");
295
+ const response = await runRouteKitEffect(source.responses.execute(body));
296
+ assert.equal(response.status, 200);
297
+ assert.deepEqual(forwarded, [sanitizeBedrockMantleRequestBody(body)]);
298
+ });
299
+ test("Bedrock OpenAI chat without a mantle key returns 400 and skips Converse", async () => {
300
+ const runtimeCalls = [];
301
+ const source = new BedrockProviderSource({
302
+ env: { AWS_REGION: "us-east-1" },
303
+ controlClient: { send: async () => ({}) },
304
+ runtimeClient: {
305
+ send: async (value) => {
306
+ runtimeCalls.push(value);
307
+ return {};
308
+ }
309
+ }
310
+ });
311
+ const response = await runRouteKitEffect(source.requests.chat({
312
+ model: "openai.gpt-5.6-luna",
313
+ messages: [{ role: "user", content: "hi" }]
314
+ }));
315
+ assert.equal(response.status, 400);
316
+ assert.match(await response.text(), /AWS_BEARER_TOKEN_BEDROCK/);
317
+ assert.equal(runtimeCalls.length, 0);
318
+ });
319
+ test("Bedrock request translation covers system, image, tools, results, and inference config", () => {
320
+ const input = toBedrockConverseInput({
321
+ model: "us.anthropic.claude-3",
322
+ messages: [
323
+ { role: "system", content: "Be terse" },
324
+ {
325
+ role: "user",
326
+ content: [
327
+ { type: "text", text: "inspect" },
328
+ { type: "image_url", image_url: { url: "data:image/png;base64,aGk=" } }
329
+ ]
330
+ },
331
+ {
332
+ role: "assistant",
333
+ content: "",
334
+ tool_calls: [{ id: "call_1", function: { name: "lookup", arguments: '{"q":"x"}' } }]
335
+ },
336
+ { role: "tool", tool_call_id: "call_1", content: "found" }
337
+ ],
338
+ tools: [
339
+ {
340
+ type: "function",
341
+ function: { name: "lookup", description: "Lookup", parameters: { type: "object" } }
342
+ }
343
+ ],
344
+ tool_choice: { type: "function", function: { name: "lookup" } },
345
+ max_completion_tokens: 256,
346
+ temperature: 0.2,
347
+ top_p: 0.9,
348
+ top_k: 40,
349
+ stop: ["END"]
350
+ });
351
+ assert.equal(input.modelId, "us.anthropic.claude-3");
352
+ assert.deepEqual(input.system, [{ text: "Be terse" }]);
353
+ assert.equal(input.messages?.length, 3);
354
+ const image = input.messages?.[0]?.content?.[1];
355
+ assert.equal(image !== undefined && "image" in image, true);
356
+ assert.deepEqual(input.toolConfig?.toolChoice, { tool: { name: "lookup" } });
357
+ assert.deepEqual(input.inferenceConfig, {
358
+ maxTokens: 256,
359
+ temperature: 0.2,
360
+ topP: 0.9,
361
+ stopSequences: ["END"]
362
+ });
363
+ assert.deepEqual(input.additionalModelRequestFields, { top_k: 40 });
364
+ });
365
+ test("Bedrock Opus 5 translates effort and adaptive reasoning controls", () => {
366
+ assert.deepEqual(toBedrockConverseInput({
367
+ model: "anthropic.claude-opus-5",
368
+ messages: [],
369
+ reasoning_effort: "high"
370
+ }).additionalModelRequestFields, { thinking: { type: "adaptive" }, output_config: { effort: "high" } });
371
+ assert.deepEqual(toBedrockConverseInput({
372
+ model: "us.anthropic.claude-opus-5",
373
+ messages: [],
374
+ x_routekit: { version: 1, selection: { mode: "adaptive" } }
375
+ }).additionalModelRequestFields, { thinking: { type: "adaptive" } });
376
+ assert.deepEqual(toBedrockConverseInput({
377
+ model: "anthropic.claude-opus-5",
378
+ messages: [],
379
+ x_routekit: { version: 1, selection: { mode: "budget", budgetTokens: 2048 } }
380
+ }).additionalModelRequestFields, { thinking: { type: "enabled", budget_tokens: 2048 } });
381
+ assert.deepEqual(toBedrockConverseInput({
382
+ model: "anthropic.claude-opus-5",
383
+ messages: [],
384
+ x_routekit: { version: 1, selection: { mode: "disabled" } }
385
+ }).additionalModelRequestFields, { thinking: { type: "disabled" } });
386
+ assert.equal(toBedrockConverseInput({
387
+ model: "anthropic.claude-opus-5",
388
+ messages: []
389
+ }).additionalModelRequestFields, undefined);
390
+ });
391
+ test("Bedrock maps profile-required Opus 5 foundations to a discovered inference profile", async () => {
392
+ let command;
393
+ const source = new BedrockProviderSource({
394
+ controlClient: {
395
+ send: async (value) => {
396
+ if (value instanceof ListFoundationModelsCommand) {
397
+ return {
398
+ modelSummaries: [
399
+ {
400
+ modelId: "anthropic.claude-opus-5",
401
+ providerName: "Anthropic",
402
+ modelLifecycle: { status: "ACTIVE" }
403
+ }
404
+ ]
405
+ };
406
+ }
407
+ return {
408
+ inferenceProfileSummaries: [
409
+ {
410
+ inferenceProfileId: "us.anthropic.claude-opus-5",
411
+ status: "ACTIVE",
412
+ models: [
413
+ {
414
+ modelArn: "arn:aws:bedrock:us-west-2::foundation-model/anthropic.claude-opus-5"
415
+ }
416
+ ]
417
+ }
418
+ ]
419
+ };
420
+ }
421
+ },
422
+ runtimeClient: {
423
+ send: async (value) => {
424
+ command = value;
425
+ return {
426
+ $metadata: { requestId: "req-opus-5" },
427
+ output: { message: { role: "assistant", content: [{ text: "OK" }] } },
428
+ stopReason: "end_turn"
429
+ };
430
+ }
431
+ }
432
+ });
433
+ await runRouteKitEffect(source.discovery.discoverModels());
434
+ const response = await runRouteKitEffect(source.requests.chat({
435
+ model: "anthropic.claude-opus-5",
436
+ messages: [{ role: "user", content: "Reply with exactly OK." }],
437
+ reasoning_effort: "low"
438
+ }));
439
+ assert.equal(response.status, 200);
440
+ assert.equal(command instanceof ConverseCommand, true);
441
+ assert.equal(command?.input.modelId, "us.anthropic.claude-opus-5");
442
+ assert.equal((await response.json()).model, "anthropic.claude-opus-5");
443
+ });
444
+ test("Bedrock Converse maps text, reasoning, tools, stop, and usage", async () => {
445
+ let command;
446
+ const source = new BedrockProviderSource({
447
+ controlClient: { send: async () => ({}) },
448
+ runtimeClient: {
449
+ send: async (value) => {
450
+ command = value;
451
+ return {
452
+ $metadata: { requestId: "req-1" },
453
+ output: {
454
+ message: {
455
+ role: "assistant",
456
+ content: [
457
+ { reasoningContent: { reasoningText: { text: "thinking", signature: "sig" } } },
458
+ { text: "answer" },
459
+ { toolUse: { toolUseId: "call_1", name: "lookup", input: { q: "x" } } }
460
+ ]
461
+ }
462
+ },
463
+ stopReason: "tool_use",
464
+ usage: { inputTokens: 5, outputTokens: 7, totalTokens: 12 }
465
+ };
466
+ }
467
+ }
468
+ });
469
+ const response = await runRouteKitEffect(source.requests.chat({
470
+ model: "anthropic.claude-3",
471
+ messages: [{ role: "user", content: "hi" }]
472
+ }));
473
+ assert.equal(command instanceof ConverseCommand, true);
474
+ const body = (await response.json());
475
+ assert.equal(body.choices[0].message.content, "answer");
476
+ assert.equal(body.choices[0].message.reasoning, "thinking");
477
+ assert.deepEqual(body.choices[0].message.reasoning_details, [
478
+ {
479
+ text: "thinking",
480
+ extensions: [{ namespace: "anthropic.reasoning", value: { index: 0, signature: "sig" } }]
481
+ }
482
+ ]);
483
+ assert.equal(body.choices[0].message.tool_calls[0].function.arguments, '{"q":"x"}');
484
+ assert.equal(body.choices[0].finish_reason, "tool_calls");
485
+ assert.deepEqual(body.usage, { prompt_tokens: 5, completion_tokens: 7, total_tokens: 12 });
486
+ });
487
+ test("Bedrock ConverseStream emits OpenAI SSE tool deltas, text, usage, stop, and done", async () => {
488
+ let command;
489
+ async function* events() {
490
+ yield { messageStart: { role: "assistant" } };
491
+ yield {
492
+ contentBlockStart: {
493
+ contentBlockIndex: 0,
494
+ start: { toolUse: { toolUseId: "call_1", name: "lookup" } }
495
+ }
496
+ };
497
+ yield { contentBlockDelta: { contentBlockIndex: 0, delta: { toolUse: { input: '{"q":' } } } };
498
+ yield { contentBlockDelta: { contentBlockIndex: 0, delta: { toolUse: { input: '"x"}' } } } };
499
+ yield { contentBlockDelta: { contentBlockIndex: 1, delta: { text: "answer" } } };
500
+ yield { messageStop: { stopReason: "tool_use" } };
501
+ yield { metadata: { usage: { inputTokens: 2, outputTokens: 3, totalTokens: 5 } } };
502
+ }
503
+ const source = new BedrockProviderSource({
504
+ controlClient: { send: async () => ({}) },
505
+ runtimeClient: {
506
+ send: async (value) => {
507
+ command = value;
508
+ return { stream: events() };
509
+ }
510
+ }
511
+ });
512
+ const response = await runRouteKitEffect(source.requests.chat({
513
+ model: "anthropic.claude-3",
514
+ stream: true,
515
+ messages: [{ role: "user", content: "hi" }]
516
+ }));
517
+ assert.equal(command instanceof ConverseStreamCommand, true);
518
+ const text = await response.text();
519
+ const data = text.split("\n\n").flatMap((event) => {
520
+ if (!event.startsWith("data: ") || event === "data: [DONE]")
521
+ return [];
522
+ return [JSON.parse(event.slice("data: ".length))];
523
+ });
524
+ const deltas = data.map((event) => event.choices?.[0]?.delta ?? {});
525
+ const toolDeltas = deltas.flatMap((delta) => delta.tool_calls ?? []);
526
+ assert.equal(toolDeltas[0]?.id, "call_1");
527
+ assert.equal(toolDeltas.map((delta) => delta.function?.arguments ?? "").join(""), '{"q":"x"}');
528
+ assert.equal(deltas.map((delta) => delta.content ?? "").join(""), "answer");
529
+ assert.equal(data.some((event) => event.choices?.[0]?.finish_reason === "tool_calls"), true);
530
+ assert.deepEqual(data.find((event) => event.usage !== undefined)?.usage, {
531
+ prompt_tokens: 2,
532
+ completion_tokens: 3,
533
+ total_tokens: 5
534
+ });
535
+ assert.match(text, /data: \[DONE\]/);
536
+ assert.equal((await runRouteKitEffect(source.requests.embeddings({}))).status, 501);
537
+ });
538
+ test("Bedrock source forwards abort signals to SDK clients", async () => {
539
+ const controller = new AbortController();
540
+ let observed;
541
+ const source = new BedrockProviderSource({
542
+ controlClient: {
543
+ send: async (_command, options) => {
544
+ observed = options?.abortSignal;
545
+ return {
546
+ modelSummaries: [
547
+ {
548
+ modelId: "anthropic.claude",
549
+ providerName: "Anthropic",
550
+ modelLifecycle: { status: "ACTIVE" }
551
+ }
552
+ ]
553
+ };
554
+ }
555
+ },
556
+ runtimeClient: { send: async () => ({}) }
557
+ });
558
+ await runRouteKitEffect(source.discovery.discoverModels(controller.signal));
559
+ assert.equal(observed, controller.signal);
560
+ });
561
+ test("Bedrock stream maps provider errors and honors abort without live AWS access", async () => {
562
+ async function* failingEvents() {
563
+ yield { serviceUnavailableException: { message: "temporarily unavailable" } };
564
+ }
565
+ const failing = new BedrockProviderSource({
566
+ controlClient: { send: async () => ({}) },
567
+ runtimeClient: { send: async () => ({ stream: failingEvents() }) }
568
+ });
569
+ const failed = await (await runRouteKitEffect(failing.requests.chat({ model: "anthropic.claude", stream: true, messages: [] }))).text();
570
+ assert.match(failed, /"type":"provider_error"/);
571
+ assert.match(failed, /temporarily unavailable/);
572
+ assert.match(failed, /data: \[DONE\]/);
573
+ const controller = new AbortController();
574
+ async function* waitingEvents() {
575
+ controller.abort();
576
+ yield { contentBlockDelta: { contentBlockIndex: 0, delta: { text: "ignored" } } };
577
+ }
578
+ const aborting = new BedrockProviderSource({
579
+ controlClient: { send: async () => ({}) },
580
+ runtimeClient: { send: async () => ({ stream: waitingEvents() }) }
581
+ });
582
+ const aborted = await (await runRouteKitEffect(aborting.requests.chat({ model: "anthropic.claude", stream: true, messages: [] }, controller.signal))).text();
583
+ assert.doesNotMatch(aborted, /ignored|provider_error|\[DONE\]/);
584
+ });
585
+ test("Bedrock buffered reasoning metadata replays exactly through tool continuation", async () => {
586
+ const inputs = [];
587
+ let invocation = 0;
588
+ const source = new BedrockProviderSource({
589
+ controlClient: { send: async () => ({}) },
590
+ runtimeClient: {
591
+ send: async (command) => {
592
+ inputs.push(command.input);
593
+ invocation += 1;
594
+ return invocation === 1
595
+ ? {
596
+ $metadata: {},
597
+ output: {
598
+ message: {
599
+ role: "assistant",
600
+ content: [
601
+ {
602
+ reasoningContent: {
603
+ reasoningText: { text: "private thought", signature: "bedrock-signature" }
604
+ }
605
+ },
606
+ { toolUse: { toolUseId: "call_1", name: "lookup", input: { q: "x" } } }
607
+ ]
608
+ }
609
+ },
610
+ stopReason: "tool_use"
611
+ }
612
+ : {
613
+ $metadata: {},
614
+ output: { message: { role: "assistant", content: [{ text: "done" }] } },
615
+ stopReason: "end_turn"
616
+ };
617
+ }
618
+ }
619
+ });
620
+ const first = (await (await runRouteKitEffect(source.requests.chat({
621
+ model: "anthropic.claude",
622
+ messages: [{ role: "user", content: "lookup" }]
623
+ }))).json());
624
+ const assistant = first.choices[0].message;
625
+ assert.deepEqual(assistant.reasoning_details, [
626
+ {
627
+ text: "private thought",
628
+ extensions: [
629
+ {
630
+ namespace: "anthropic.reasoning",
631
+ value: { index: 0, signature: "bedrock-signature" }
632
+ }
633
+ ]
634
+ }
635
+ ]);
636
+ await runRouteKitEffect(source.requests.chat({
637
+ model: "anthropic.claude",
638
+ messages: [
639
+ { role: "user", content: "lookup" },
640
+ assistant,
641
+ { role: "tool", tool_call_id: "call_1", content: "result" }
642
+ ]
643
+ }));
644
+ assert.deepEqual(inputs[1].messages[1].content[0], {
645
+ reasoningContent: { reasoningText: { text: "private thought", signature: "bedrock-signature" } }
646
+ });
647
+ assert.deepEqual(inputs[1].messages[1].content[1], {
648
+ toolUse: { toolUseId: "call_1", name: "lookup", input: { q: "x" } }
649
+ });
650
+ });
651
+ test("Bedrock streamed reasoning metadata assembles and replays exactly", async () => {
652
+ let secondInput;
653
+ async function* stream() {
654
+ yield {
655
+ contentBlockDelta: { contentBlockIndex: 0, delta: { reasoningContent: { text: "private " } } }
656
+ };
657
+ yield {
658
+ contentBlockDelta: { contentBlockIndex: 0, delta: { reasoningContent: { text: "thought" } } }
659
+ };
660
+ yield {
661
+ contentBlockDelta: {
662
+ contentBlockIndex: 0,
663
+ delta: { reasoningContent: { signature: "stream-signature" } }
664
+ }
665
+ };
666
+ yield { contentBlockStop: { contentBlockIndex: 0 } };
667
+ yield {
668
+ contentBlockStart: {
669
+ contentBlockIndex: 1,
670
+ start: { toolUse: { toolUseId: "call_1", name: "lookup" } }
671
+ }
672
+ };
673
+ yield { contentBlockDelta: { contentBlockIndex: 1, delta: { toolUse: { input: "{}" } } } };
674
+ yield { messageStop: { stopReason: "tool_use" } };
675
+ }
676
+ let invocation = 0;
677
+ const source = new BedrockProviderSource({
678
+ controlClient: { send: async () => ({}) },
679
+ runtimeClient: {
680
+ send: async (command) => {
681
+ invocation += 1;
682
+ if (invocation === 1)
683
+ return { stream: stream() };
684
+ secondInput = command.input;
685
+ return {
686
+ $metadata: {},
687
+ output: { message: { role: "assistant", content: [{ text: "done" }] } },
688
+ stopReason: "end_turn"
689
+ };
690
+ }
691
+ }
692
+ });
693
+ const wire = await (await runRouteKitEffect(source.requests.chat({
694
+ model: "anthropic.claude",
695
+ stream: true,
696
+ messages: [{ role: "user", content: "lookup" }]
697
+ }))).text();
698
+ const chunks = wire
699
+ .split("\n\n")
700
+ .flatMap((event) => (event.startsWith("data: {") ? [JSON.parse(event.slice(6))] : []));
701
+ const assembler = new (await import("@velum-labs/routekit-provider-core")).ChatStreamAssembler();
702
+ for (const chunk of chunks)
703
+ assembler.pushParsed(chunk);
704
+ const turn = assembler.result();
705
+ assert.equal(turn.reasoning, "private thought");
706
+ assert.deepEqual(turn.reasoningDetails, [
707
+ {
708
+ text: "private thought",
709
+ extensions: [
710
+ {
711
+ namespace: "anthropic.reasoning",
712
+ value: { index: 0, signature: "stream-signature" }
713
+ }
714
+ ]
715
+ }
716
+ ]);
717
+ await runRouteKitEffect(source.requests.chat({
718
+ model: "anthropic.claude",
719
+ messages: [
720
+ { role: "user", content: "lookup" },
721
+ {
722
+ role: "assistant",
723
+ content: turn.content,
724
+ reasoning: turn.reasoning,
725
+ reasoning_details: turn.reasoningDetails,
726
+ tool_calls: turn.toolCalls.map((call) => ({
727
+ id: call.id,
728
+ type: "function",
729
+ function: { name: call.name, arguments: call.arguments }
730
+ }))
731
+ },
732
+ { role: "tool", tool_call_id: "call_1", content: "result" }
733
+ ]
734
+ }));
735
+ assert.deepEqual(secondInput.messages[1].content[0], {
736
+ reasoningContent: { reasoningText: { text: "private thought", signature: "stream-signature" } }
737
+ });
738
+ });
739
+ test("Bedrock groups parallel tool results into one user message", () => {
740
+ const input = toBedrockConverseInput({
741
+ model: "anthropic.claude",
742
+ messages: [
743
+ { role: "user", content: "both" },
744
+ {
745
+ role: "assistant",
746
+ content: "",
747
+ tool_calls: [
748
+ { id: "call_1", function: { name: "one", arguments: "{}" } },
749
+ { id: "call_2", function: { name: "two", arguments: "{}" } }
750
+ ]
751
+ },
752
+ { role: "tool", tool_call_id: "call_1", content: "first" },
753
+ { role: "tool", tool_call_id: "call_2", content: "second" }
754
+ ]
755
+ });
756
+ assert.equal(input.messages?.length, 3);
757
+ assert.deepEqual(input.messages?.[2], {
758
+ role: "user",
759
+ content: [
760
+ { toolResult: { toolUseId: "call_1", content: [{ text: "first" }] } },
761
+ { toolResult: { toolUseId: "call_2", content: [{ text: "second" }] } }
762
+ ]
763
+ });
764
+ });
765
+ test("Bedrock defaults reasoning to unknown and ordinary requests omit thinking", () => {
766
+ const source = new BedrockProviderSource({
767
+ controlClient: { send: async () => ({}) },
768
+ runtimeClient: { send: async () => ({}) }
769
+ });
770
+ assert.equal(source.capabilities.reasoningForModel("anthropic.claude-opus-5")?.status, "supported");
771
+ assert.deepEqual(source.capabilities.reasoningForModel("unknown"), {
772
+ status: "unknown",
773
+ wireShape: "bedrock-converse",
774
+ provenance: "provider"
775
+ });
776
+ assert.equal(toBedrockConverseInput({
777
+ model: "anthropic.claude",
778
+ messages: [],
779
+ reasoning_effort: "high"
780
+ }).additionalModelRequestFields, undefined);
781
+ assert.equal(toBedrockConverseInput({
782
+ model: "anthropic.claude",
783
+ messages: [],
784
+ reasoning_effort: "none"
785
+ }).additionalModelRequestFields, undefined);
786
+ });