@velum-labs/routekit-daemon 1.3.2 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (172) hide show
  1. package/dist/account-enroll-service.d.ts +1 -3
  2. package/dist/account-enroll-service.js +262 -262
  3. package/dist/account-mutation-service.d.ts +1 -3
  4. package/dist/account-mutation-service.js +302 -283
  5. package/dist/account-query-service.d.ts +1 -3
  6. package/dist/account-query-service.js +88 -86
  7. package/dist/call-attribution-store.js +544 -1
  8. package/dist/control-dispatch.js +9 -15
  9. package/dist/daemon-bootstrap-preflight.js +1 -1
  10. package/dist/daemon-control-handlers.d.ts +1 -1
  11. package/dist/daemon-control-handlers.js +24 -27
  12. package/dist/daemon-generations.d.ts +14 -28
  13. package/dist/daemon-generations.js +114 -53
  14. package/dist/daemon-lifecycle-service.d.ts +1 -3
  15. package/dist/daemon-lifecycle-service.js +88 -94
  16. package/dist/daemon-maintenance.d.ts +27 -49
  17. package/dist/daemon-maintenance.js +5 -1
  18. package/dist/daemon-options.d.ts +3 -2
  19. package/dist/daemon-policy-context.d.ts +1 -0
  20. package/dist/daemon-policy-context.js +1 -0
  21. package/dist/daemon-runtime-state.d.ts +1 -1
  22. package/dist/daemon-runtime-state.js +2 -3
  23. package/dist/daemon-state-context.d.ts +1 -1
  24. package/dist/daemon-state-context.js +1 -38
  25. package/dist/doctor-service.d.ts +1 -3
  26. package/dist/doctor-service.js +87 -91
  27. package/dist/effect/daemon-live.d.ts +7 -6
  28. package/dist/effect/daemon-live.js +57 -135
  29. package/dist/eval-routing-service.d.ts +1 -3
  30. package/dist/eval-routing-service.js +45 -69
  31. package/dist/eval-session-execution-store.d.ts +24 -0
  32. package/dist/eval-session-execution-store.js +253 -0
  33. package/dist/host-protocol.d.ts +1 -0
  34. package/dist/host.js +3 -0
  35. package/dist/internal-providers/provider-anthropic/index.d.ts +23 -0
  36. package/dist/internal-providers/provider-anthropic/index.js +42 -0
  37. package/dist/internal-providers/provider-anthropic/test/provider.test.js +33 -0
  38. package/dist/internal-providers/provider-azure/index.d.ts +25 -0
  39. package/dist/internal-providers/provider-azure/index.js +101 -0
  40. package/dist/internal-providers/provider-azure/test/provider.test.js +83 -0
  41. package/dist/internal-providers/provider-bedrock/codec.d.ts +11 -0
  42. package/dist/internal-providers/provider-bedrock/codec.js +484 -0
  43. package/dist/internal-providers/provider-bedrock/connection-settings.d.ts +6 -0
  44. package/dist/internal-providers/provider-bedrock/connection-settings.js +41 -0
  45. package/dist/internal-providers/provider-bedrock/connection.d.ts +17 -0
  46. package/dist/internal-providers/provider-bedrock/connection.js +143 -0
  47. package/dist/internal-providers/provider-bedrock/index.d.ts +27 -0
  48. package/dist/internal-providers/provider-bedrock/index.js +47 -0
  49. package/dist/internal-providers/provider-bedrock/source.d.ts +39 -0
  50. package/dist/internal-providers/provider-bedrock/source.js +454 -0
  51. package/dist/internal-providers/provider-bedrock/test/bedrock-source.test.d.ts +1 -0
  52. package/dist/internal-providers/provider-bedrock/test/bedrock-source.test.js +786 -0
  53. package/dist/internal-providers/provider-bedrock/test/connection.test.d.ts +1 -0
  54. package/dist/internal-providers/provider-bedrock/test/connection.test.js +249 -0
  55. package/dist/internal-providers/provider-bedrock/test/workload-credentials.test.d.ts +1 -0
  56. package/dist/internal-providers/provider-bedrock/test/workload-credentials.test.js +29 -0
  57. package/dist/internal-providers/provider-bedrock/workload-credentials.d.ts +5 -0
  58. package/dist/internal-providers/provider-bedrock/workload-credentials.js +14 -0
  59. package/dist/internal-providers/provider-claude-code/index.d.ts +6 -0
  60. package/dist/internal-providers/provider-claude-code/index.js +4 -0
  61. package/dist/internal-providers/provider-claude-code/module-api.d.ts +25 -0
  62. package/dist/internal-providers/provider-claude-code/module-api.js +31 -0
  63. package/dist/internal-providers/provider-claude-code/relay.d.ts +20 -0
  64. package/dist/internal-providers/provider-claude-code/relay.js +115 -0
  65. package/dist/internal-providers/provider-claude-code/subscription.d.ts +2 -0
  66. package/dist/internal-providers/provider-claude-code/subscription.js +194 -0
  67. package/dist/internal-providers/provider-claude-code/test/http-submission.test.d.ts +1 -0
  68. package/dist/internal-providers/provider-claude-code/test/http-submission.test.js +140 -0
  69. package/dist/internal-providers/provider-claude-code/test/relay.test.d.ts +1 -0
  70. package/dist/internal-providers/provider-claude-code/test/relay.test.js +379 -0
  71. package/dist/internal-providers/provider-claude-code/test/subscription.test.d.ts +1 -0
  72. package/dist/internal-providers/provider-claude-code/test/subscription.test.js +256 -0
  73. package/dist/internal-providers/provider-cliproxy/index.d.ts +9 -0
  74. package/dist/internal-providers/provider-cliproxy/index.js +15 -0
  75. package/dist/internal-providers/provider-codex/index.d.ts +6 -0
  76. package/dist/internal-providers/provider-codex/index.js +7 -0
  77. package/dist/internal-providers/provider-codex/module-api.d.ts +25 -0
  78. package/dist/internal-providers/provider-codex/module-api.js +31 -0
  79. package/dist/internal-providers/provider-codex/relay.d.ts +123 -0
  80. package/dist/internal-providers/provider-codex/relay.js +261 -0
  81. package/dist/internal-providers/provider-codex/subscription.d.ts +2 -0
  82. package/dist/internal-providers/provider-codex/subscription.js +555 -0
  83. package/dist/internal-providers/provider-codex/test/http-submission.test.d.ts +1 -0
  84. package/dist/internal-providers/provider-codex/test/http-submission.test.js +142 -0
  85. package/dist/internal-providers/provider-codex/test/relay.test.d.ts +1 -0
  86. package/dist/internal-providers/provider-codex/test/relay.test.js +600 -0
  87. package/dist/internal-providers/provider-codex/test/subscription.test.d.ts +1 -0
  88. package/dist/internal-providers/provider-codex/test/subscription.test.js +70 -0
  89. package/dist/internal-providers/provider-codex/test/translated-content.test.d.ts +1 -0
  90. package/dist/internal-providers/provider-codex/test/translated-content.test.js +91 -0
  91. package/dist/internal-providers/provider-custom/index.d.ts +9 -0
  92. package/dist/internal-providers/provider-custom/index.js +15 -0
  93. package/dist/internal-providers/provider-google/index.d.ts +23 -0
  94. package/dist/internal-providers/provider-google/index.js +43 -0
  95. package/dist/internal-providers/provider-google/test/provider.test.d.ts +1 -0
  96. package/dist/internal-providers/provider-google/test/provider.test.js +34 -0
  97. package/dist/internal-providers/provider-google-vertex/index.d.ts +28 -0
  98. package/dist/internal-providers/provider-google-vertex/index.js +137 -0
  99. package/dist/internal-providers/provider-google-vertex/test/provider.test.d.ts +1 -0
  100. package/dist/internal-providers/provider-google-vertex/test/provider.test.js +85 -0
  101. package/dist/internal-providers/provider-openai/documented-reasoning.d.ts +9 -0
  102. package/dist/internal-providers/provider-openai/documented-reasoning.js +26 -0
  103. package/dist/internal-providers/provider-openai/index.d.ts +23 -0
  104. package/dist/internal-providers/provider-openai/index.js +74 -0
  105. package/dist/internal-providers/provider-openai/test/provider.test.d.ts +1 -0
  106. package/dist/internal-providers/provider-openai/test/provider.test.js +268 -0
  107. package/dist/internal-providers/provider-openai-compatible/index.d.ts +9 -0
  108. package/dist/internal-providers/provider-openai-compatible/index.js +15 -0
  109. package/dist/internal-providers/provider-openai-compatible/test/provider.test.d.ts +1 -0
  110. package/dist/internal-providers/provider-openai-compatible/test/provider.test.js +40 -0
  111. package/dist/internal-providers/provider-openrouter/connection.d.ts +5 -0
  112. package/dist/internal-providers/provider-openrouter/connection.js +143 -0
  113. package/dist/internal-providers/provider-openrouter/index.d.ts +28 -0
  114. package/dist/internal-providers/provider-openrouter/index.js +42 -0
  115. package/dist/internal-providers/provider-openrouter/pricing.d.ts +4 -0
  116. package/dist/internal-providers/provider-openrouter/pricing.js +108 -0
  117. package/dist/internal-providers/provider-openrouter/test/connection.test.d.ts +1 -0
  118. package/dist/internal-providers/provider-openrouter/test/connection.test.js +71 -0
  119. package/dist/internal-providers/provider-openrouter/test/pricing.test.d.ts +1 -0
  120. package/dist/internal-providers/provider-openrouter/test/pricing.test.js +45 -0
  121. package/dist/internal-providers/provider-openrouter/test/provider.test.d.ts +1 -0
  122. package/dist/internal-providers/provider-openrouter/test/provider.test.js +141 -0
  123. package/dist/internal-providers/provider-openrouter/test/validation.test.d.ts +1 -0
  124. package/dist/internal-providers/provider-openrouter/test/validation.test.js +225 -0
  125. package/dist/launcher-service.d.ts +3 -7
  126. package/dist/launcher-service.js +144 -133
  127. package/dist/provider-modules.d.ts +6 -0
  128. package/dist/provider-modules.js +55 -0
  129. package/dist/provider-query-service.d.ts +1 -3
  130. package/dist/provider-query-service.js +140 -143
  131. package/dist/router-generation-service.d.ts +1 -3
  132. package/dist/router-generation-service.js +83 -78
  133. package/dist/services/eval-session/service.d.ts +20 -8
  134. package/dist/services/eval-session/service.js +410 -40
  135. package/dist/services/gateway-generation/service.d.ts +7 -3
  136. package/dist/services/gateway-generation/service.js +126 -63
  137. package/dist/services/generations/service.d.ts +3 -3
  138. package/dist/services/generations/service.js +5 -1
  139. package/dist/telemetry-application-service.d.ts +1 -3
  140. package/dist/telemetry-application-service.js +41 -45
  141. package/dist/test/call-attribution-store.test.js +186 -0
  142. package/dist/test/daemon-accounts.test.js +2 -2
  143. package/dist/test/daemon-data-plane.test.js +24 -45
  144. package/dist/test/daemon-fixtures.js +1 -1
  145. package/dist/test/daemon-generation-transaction.test.js +66 -2
  146. package/dist/test/daemon-runtime-state.test.js +28 -1
  147. package/dist/test/effect-daemon.test.js +38 -0
  148. package/dist/test/eval-routing-application-service.test.js +6 -6
  149. package/dist/test/eval-session-execution.test.d.ts +1 -0
  150. package/dist/test/eval-session-execution.test.js +356 -0
  151. package/dist/test/eval-session-service.test.js +70 -1
  152. package/dist/test/host-worker-session.test.js +1 -0
  153. package/dist/test/launcher-session.test.d.ts +1 -0
  154. package/dist/test/launcher-session.test.js +87 -0
  155. package/dist/test/leaderboard.test.js +25 -1
  156. package/dist/test/provider-modules.test.d.ts +1 -0
  157. package/dist/test/provider-modules.test.js +65 -0
  158. package/dist/test/routekit-upstream-sources.test.d.ts +1 -0
  159. package/dist/test/routekit-upstream-sources.test.js +40 -0
  160. package/dist/test/runtime-composition-types.test.js +11 -0
  161. package/dist/token-application-service.d.ts +1 -3
  162. package/dist/token-application-service.js +38 -44
  163. package/dist/worker.js +5 -3
  164. package/package.json +35 -11
  165. package/dist/application-services.d.ts +0 -10
  166. package/dist/application-services.js +0 -22
  167. package/dist/daemon-lifecycle.d.ts +0 -54
  168. package/dist/daemon-lifecycle.js +0 -177
  169. package/dist/test/application-services.test.js +0 -135
  170. package/dist/test/daemon-lifecycle.test.js +0 -182
  171. /package/dist/{test/application-services.test.d.ts → internal-providers/provider-anthropic/test/provider.test.d.ts} +0 -0
  172. /package/dist/{test/daemon-lifecycle.test.d.ts → internal-providers/provider-azure/test/provider.test.d.ts} +0 -0
@@ -1,3 +1,4 @@
1
+ import { attributeModelCosts, lookupModelPricing, routingCacheOutcome } from "@velum-labs/routekit-registry";
1
2
  export const DEFAULT_CALL_ATTRIBUTION_LIMIT = 1_000;
2
3
  export const DEFAULT_CALL_ATTRIBUTION_TTL_MS = 24 * 60 * 60 * 1_000;
3
4
  function record(value) {
@@ -14,12 +15,381 @@ function number(value) {
14
15
  function boolean(value) {
15
16
  return value === true;
16
17
  }
18
+ function routingHop(value) {
19
+ const hop = record(value);
20
+ const episodeId = string(hop?.episode_id);
21
+ const hopIndex = number(hop?.hop_index);
22
+ const role = string(hop?.role);
23
+ const model = string(hop?.model);
24
+ const previousModel = string(hop?.previous_model);
25
+ const cacheOutcome = string(hop?.cache_outcome);
26
+ if (episodeId === undefined ||
27
+ hopIndex === undefined ||
28
+ (role !== "classifier" &&
29
+ role !== "candidate" &&
30
+ role !== "failover" &&
31
+ role !== "explicit") ||
32
+ (cacheOutcome !== undefined &&
33
+ cacheOutcome !== "hit" &&
34
+ cacheOutcome !== "write" &&
35
+ cacheOutcome !== "miss_cold" &&
36
+ cacheOutcome !== "miss_switch")) {
37
+ return undefined;
38
+ }
39
+ return {
40
+ episodeId,
41
+ hopIndex,
42
+ role,
43
+ ...(model === undefined ? {} : { model }),
44
+ ...(previousModel === undefined ? {} : { previousModel }),
45
+ ...(cacheOutcome === undefined ? {} : { cacheOutcome })
46
+ };
47
+ }
17
48
  function stringArray(value) {
18
49
  if (!Array.isArray(value))
19
50
  return undefined;
20
51
  const values = value.map(string);
21
52
  return values.every((entry) => entry !== undefined) ? values : undefined;
22
53
  }
54
+ function unknownReasons(value) {
55
+ return stringArray(value);
56
+ }
57
+ function tokenUsage(value) {
58
+ const usage = record(value);
59
+ if (usage === undefined)
60
+ return undefined;
61
+ const inputTokens = number(usage.input_tokens);
62
+ const cachedInputTokens = number(usage.cached_input_tokens);
63
+ const cacheCreationInputTokens = number(usage.cache_creation_input_tokens);
64
+ const outputTokens = number(usage.output_tokens);
65
+ const reasoningTokens = number(usage.reasoning_tokens);
66
+ return {
67
+ ...(inputTokens === undefined ? {} : { inputTokens }),
68
+ ...(cachedInputTokens === undefined ? {} : { cachedInputTokens }),
69
+ ...(cacheCreationInputTokens === undefined ? {} : { cacheCreationInputTokens }),
70
+ ...(outputTokens === undefined ? {} : { outputTokens }),
71
+ ...(reasoningTokens === undefined ? {} : { reasoningTokens })
72
+ };
73
+ }
74
+ function subscriptionWindows(value) {
75
+ if (!Array.isArray(value))
76
+ return undefined;
77
+ const windows = value.flatMap((candidate) => {
78
+ const window = record(candidate);
79
+ const name = string(window?.window);
80
+ const observedAt = number(window?.observed_at);
81
+ const source = string(window?.source);
82
+ const completeness = string(window?.completeness);
83
+ const confidence = string(window?.confidence);
84
+ if (window === undefined ||
85
+ name === undefined ||
86
+ observedAt === undefined ||
87
+ (source !== "headers" &&
88
+ source !== "response" &&
89
+ source !== "usage" &&
90
+ source !== "stream") ||
91
+ (completeness !== "snapshot" && completeness !== "partial") ||
92
+ (confidence !== "observed" && confidence !== "estimated" && confidence !== "unknown")) {
93
+ return [];
94
+ }
95
+ return [
96
+ {
97
+ window: name,
98
+ ...(number(window.utilization) === undefined
99
+ ? {}
100
+ : { utilization: number(window.utilization) }),
101
+ ...(number(window.utilization_before) === undefined
102
+ ? {}
103
+ : { utilizationBefore: number(window.utilization_before) }),
104
+ ...(number(window.utilization_after) === undefined
105
+ ? {}
106
+ : { utilizationAfter: number(window.utilization_after) }),
107
+ ...(number(window.remaining_capacity) === undefined
108
+ ? {}
109
+ : { remainingCapacity: number(window.remaining_capacity) }),
110
+ ...(number(window.estimated_burden) === undefined
111
+ ? {}
112
+ : { estimatedBurden: number(window.estimated_burden) }),
113
+ observedAt,
114
+ ...(number(window.resets_at) === undefined ? {} : { resetsAt: number(window.resets_at) }),
115
+ ...(number(window.window_seconds) === undefined
116
+ ? {}
117
+ : { windowSeconds: number(window.window_seconds) }),
118
+ ...(string(window.limit_name) === undefined
119
+ ? {}
120
+ : { limitName: string(window.limit_name) }),
121
+ ...(string(window.status) === undefined ? {} : { status: string(window.status) }),
122
+ source,
123
+ completeness,
124
+ confidence
125
+ }
126
+ ];
127
+ });
128
+ return windows.length === value.length ? windows : undefined;
129
+ }
130
+ function apiEquivalent(value) {
131
+ const api = record(value);
132
+ const status = string(api?.status);
133
+ if (status === "known") {
134
+ const amount = number(api?.amount);
135
+ const currency = string(api?.currency);
136
+ const comparisonModel = string(api?.comparison_model);
137
+ const pricingKey = string(api?.pricing_key);
138
+ const pricingSource = string(api?.pricing_source);
139
+ const pricingRevision = string(api?.pricing_revision);
140
+ if (amount === undefined ||
141
+ currency === undefined ||
142
+ comparisonModel === undefined ||
143
+ pricingKey === undefined ||
144
+ pricingSource === undefined) {
145
+ return undefined;
146
+ }
147
+ return {
148
+ status,
149
+ amount,
150
+ currency,
151
+ comparisonModel,
152
+ pricingKey,
153
+ pricingSource,
154
+ ...(pricingRevision === undefined ? {} : { pricingRevision })
155
+ };
156
+ }
157
+ if (status === "unknown") {
158
+ const reasons = unknownReasons(api?.unknown_reasons);
159
+ if (reasons === undefined)
160
+ return undefined;
161
+ const comparisonModel = string(api?.comparison_model);
162
+ return {
163
+ status,
164
+ ...(comparisonModel === undefined ? {} : { comparisonModel }),
165
+ unknownReasons: reasons
166
+ };
167
+ }
168
+ return undefined;
169
+ }
170
+ function scarcity(value) {
171
+ const scarcity = record(value);
172
+ const status = string(scarcity?.status);
173
+ const confidence = string(scarcity?.confidence);
174
+ const reasons = unknownReasons(scarcity?.unknown_reasons);
175
+ if ((status !== "known" && status !== "partial" && status !== "unknown") ||
176
+ scarcity?.strategy !== "pressure-v1" ||
177
+ (confidence !== "observed" && confidence !== "estimated" && confidence !== "unknown") ||
178
+ reasons === undefined ||
179
+ !Array.isArray(scarcity.windows)) {
180
+ return undefined;
181
+ }
182
+ const windows = scarcity.windows.flatMap((candidate) => {
183
+ const window = record(candidate);
184
+ const name = string(window?.window);
185
+ const windowConfidence = string(window?.confidence);
186
+ const windowReasons = unknownReasons(window?.unknown_reasons);
187
+ if (window === undefined ||
188
+ name === undefined ||
189
+ (windowConfidence !== "observed" &&
190
+ windowConfidence !== "estimated" &&
191
+ windowConfidence !== "unknown") ||
192
+ windowReasons === undefined) {
193
+ return [];
194
+ }
195
+ return [
196
+ {
197
+ window: name,
198
+ ...(number(window.score) === undefined ? {} : { score: number(window.score) }),
199
+ ...(number(window.pressure) === undefined ? {} : { pressure: number(window.pressure) }),
200
+ ...(number(window.expected_demand_before_reset) === undefined
201
+ ? {}
202
+ : { expectedDemandBeforeReset: number(window.expected_demand_before_reset) }),
203
+ ...(number(window.remaining_capacity) === undefined
204
+ ? {}
205
+ : { remainingCapacity: number(window.remaining_capacity) }),
206
+ ...(number(window.burden) === undefined ? {} : { burden: number(window.burden) }),
207
+ ...(number(window.resets_at) === undefined ? {} : { resetsAt: number(window.resets_at) }),
208
+ confidence: windowConfidence,
209
+ unknownReasons: windowReasons
210
+ }
211
+ ];
212
+ });
213
+ if (windows.length !== scarcity.windows.length)
214
+ return undefined;
215
+ const level = string(scarcity.level);
216
+ const governingWindow = string(scarcity.governing_window);
217
+ const scarcityValue = record(scarcity.value);
218
+ const valueAmount = number(scarcityValue?.amount);
219
+ const valueCurrency = string(scarcityValue?.currency);
220
+ return {
221
+ status,
222
+ strategy: "pressure-v1",
223
+ ...(number(scarcity.score) === undefined ? {} : { score: number(scarcity.score) }),
224
+ ...(level === "low" || level === "medium" || level === "high" ? { level } : {}),
225
+ confidence,
226
+ ...(governingWindow === undefined ? {} : { governingWindow }),
227
+ ...(valueAmount === undefined ||
228
+ valueCurrency === undefined ||
229
+ scarcityValue?.basis !== "fallback-displacement"
230
+ ? {}
231
+ : {
232
+ value: {
233
+ amount: valueAmount,
234
+ currency: valueCurrency,
235
+ basis: "fallback-displacement"
236
+ }
237
+ }),
238
+ windows,
239
+ unknownReasons: reasons
240
+ };
241
+ }
242
+ function allocation(value) {
243
+ const allocation = record(value);
244
+ const status = string(allocation?.status);
245
+ const reasons = unknownReasons(allocation?.unknown_reasons);
246
+ const modelFactor = number(allocation?.model_factor);
247
+ if (reasons === undefined || modelFactor === undefined)
248
+ return undefined;
249
+ if (status === "provisional" && allocation?.method === "burden-scarcity-v1") {
250
+ return {
251
+ status,
252
+ ...(number(allocation.amount) === undefined ? {} : { amount: number(allocation.amount) }),
253
+ ...(string(allocation.currency) === undefined
254
+ ? {}
255
+ : { currency: string(allocation.currency) }),
256
+ ...(number(allocation.raw_weight) === undefined
257
+ ? {}
258
+ : { rawWeight: number(allocation.raw_weight) }),
259
+ ...(number(allocation.observed_burden) === undefined
260
+ ? {}
261
+ : { observedBurden: number(allocation.observed_burden) }),
262
+ modelFactor,
263
+ ...(number(allocation.temporal_scarcity_factor) === undefined
264
+ ? {}
265
+ : { temporalScarcityFactor: number(allocation.temporal_scarcity_factor) }),
266
+ method: "burden-scarcity-v1",
267
+ version: 1,
268
+ unknownReasons: reasons
269
+ };
270
+ }
271
+ if (status === "settled" && allocation?.method === "bounded-provisional-v1") {
272
+ const amount = number(allocation.amount);
273
+ const currency = string(allocation.currency);
274
+ const rawWeight = number(allocation.raw_weight);
275
+ const observedBurden = number(allocation.observed_burden);
276
+ const temporalScarcityFactor = number(allocation.temporal_scarcity_factor);
277
+ const settlementRevision = number(allocation.settlement_revision);
278
+ const settlementId = string(allocation.settlement_id);
279
+ if (amount === undefined ||
280
+ currency === undefined ||
281
+ rawWeight === undefined ||
282
+ observedBurden === undefined ||
283
+ temporalScarcityFactor === undefined ||
284
+ settlementRevision === undefined ||
285
+ settlementId === undefined) {
286
+ return undefined;
287
+ }
288
+ return {
289
+ status,
290
+ amount,
291
+ currency,
292
+ rawWeight,
293
+ observedBurden,
294
+ modelFactor,
295
+ temporalScarcityFactor,
296
+ method: "bounded-provisional-v1",
297
+ version: 1,
298
+ settlementRevision,
299
+ settlementId,
300
+ unknownReasons: reasons
301
+ };
302
+ }
303
+ return undefined;
304
+ }
305
+ function providerCash(value) {
306
+ const cash = record(value);
307
+ const status = string(cash?.status);
308
+ if (status === "known") {
309
+ const amount = number(cash?.amount);
310
+ const currency = string(cash?.currency);
311
+ if (amount === undefined || currency === undefined || cash?.source !== "provider") {
312
+ return undefined;
313
+ }
314
+ return { status, amount, currency, source: "provider" };
315
+ }
316
+ if (status === "unknown") {
317
+ const reasons = unknownReasons(cash?.unknown_reasons);
318
+ return reasons === undefined ? undefined : { status, unknownReasons: reasons };
319
+ }
320
+ return undefined;
321
+ }
322
+ function fallbackReference(value) {
323
+ const fallback = record(value);
324
+ const displacement = record(fallback?.displacement_value);
325
+ const route = string(fallback?.route);
326
+ const amount = number(displacement?.amount);
327
+ const currency = string(displacement?.currency);
328
+ if (route === undefined || amount === undefined || currency === undefined)
329
+ return undefined;
330
+ return { route, displacementValue: { amount, currency } };
331
+ }
332
+ function subscriptionInspection(value) {
333
+ const subscription = record(value);
334
+ const entitlement = record(subscription?.entitlement);
335
+ const period = record(subscription?.period);
336
+ const usage = tokenUsage(subscription?.usage);
337
+ const windows = subscriptionWindows(subscription?.windows);
338
+ const api = apiEquivalent(subscription?.api_equivalent);
339
+ const cash = providerCash(subscription?.provider_cash);
340
+ const scarcityValue = scarcity(subscription?.scarcity);
341
+ const allocationValue = allocation(subscription?.allocation);
342
+ const reasons = unknownReasons(subscription?.unknown_reasons);
343
+ const cacheOutcome = string(subscription?.cache_outcome);
344
+ const entitlementId = string(entitlement?.entitlement_id);
345
+ const accountId = string(entitlement?.account_id);
346
+ const periodId = string(period?.id);
347
+ const startsAt = number(period?.starts_at);
348
+ const endsAt = number(period?.ends_at);
349
+ if (entitlementId === undefined ||
350
+ accountId === undefined ||
351
+ periodId === undefined ||
352
+ startsAt === undefined ||
353
+ endsAt === undefined ||
354
+ usage === undefined ||
355
+ windows === undefined ||
356
+ api === undefined ||
357
+ cash === undefined ||
358
+ scarcityValue === undefined ||
359
+ allocationValue === undefined ||
360
+ reasons === undefined) {
361
+ return undefined;
362
+ }
363
+ const attributable = record(period?.attributable_cost);
364
+ const attributableAmount = number(attributable?.amount);
365
+ const attributableCurrency = string(attributable?.currency);
366
+ const fallback = fallbackReference(subscription?.fallback);
367
+ return {
368
+ entitlement: { entitlementId, accountId },
369
+ period: {
370
+ id: periodId,
371
+ startsAt,
372
+ endsAt,
373
+ ...(attributableAmount === undefined || attributableCurrency === undefined
374
+ ? {}
375
+ : { attributableCost: { amount: attributableAmount, currency: attributableCurrency } })
376
+ },
377
+ usage,
378
+ ...(cacheOutcome === "hit" ||
379
+ cacheOutcome === "write" ||
380
+ cacheOutcome === "miss_cold" ||
381
+ cacheOutcome === "miss_switch"
382
+ ? { cacheOutcome }
383
+ : {}),
384
+ providerCash: cash,
385
+ allocation: allocationValue,
386
+ apiEquivalent: api,
387
+ scarcity: scarcityValue,
388
+ windows,
389
+ ...(fallback === undefined ? {} : { fallback }),
390
+ unknownReasons: reasons
391
+ };
392
+ }
23
393
  function compositionalRoutingInspection(value) {
24
394
  const routing = record(value);
25
395
  if (routing?.version !== 2) {
@@ -174,11 +544,94 @@ export function callInspection(modelCall) {
174
544
  const evalRunId = string(evalAttribution?.run_id);
175
545
  const evalCaseId = string(evalAttribution?.case_id);
176
546
  const estimateUsd = number(metadata?.cost_estimate_usd);
547
+ const providerCashUsd = number(metadata?.provider_cash_usd);
548
+ const subscription = subscriptionInspection(metadata?.subscription_valuation);
549
+ const routingCost = routingHop(metadata?.routing_cost) ?? routingHop(attribution?.routing_cost);
177
550
  const attempts = number(attribution?.attempts) ?? 1;
178
551
  const retries = number(attribution?.retries) ?? Math.max(0, attempts - 1);
179
552
  const accountFailovers = number(attribution?.account_failovers) ?? 0;
553
+ const costUsage = subscription?.usage ?? {
554
+ ...(modelCall.usage?.prompt_tokens === undefined
555
+ ? {}
556
+ : { inputTokens: modelCall.usage.prompt_tokens }),
557
+ ...(modelCall.usage?.cached_input_tokens === undefined
558
+ ? {}
559
+ : { cachedInputTokens: modelCall.usage.cached_input_tokens }),
560
+ ...(modelCall.usage?.completion_tokens === undefined
561
+ ? {}
562
+ : { outputTokens: modelCall.usage.completion_tokens }),
563
+ ...(modelCall.usage?.reasoning_tokens === undefined
564
+ ? {}
565
+ : { reasoningTokens: modelCall.usage.reasoning_tokens })
566
+ };
567
+ const computedCacheOutcome = routingCost?.cacheOutcome ??
568
+ routingCacheOutcome(costUsage, lookupModelPricing(effectiveModel), effectiveModel, routingCost?.previousModel);
569
+ const actuals = attributeModelCosts([
570
+ {
571
+ episodeId: routingCost?.episodeId ?? modelCall.call_id,
572
+ hopIndex: routingCost?.hopIndex ?? 0,
573
+ role: routingCost?.role ?? "explicit",
574
+ model: effectiveModel,
575
+ ...(routingCost?.previousModel === undefined
576
+ ? {}
577
+ : { previousModel: routingCost.previousModel }),
578
+ ...(computedCacheOutcome === undefined ? {} : { cacheOutcome: computedCacheOutcome }),
579
+ usage: costUsage,
580
+ billingMode: billingMode === "api_key"
581
+ ? "metered-api"
582
+ : billingMode === "subscription"
583
+ ? "subscription"
584
+ : "upstream-managed",
585
+ ...(providerCashUsd === undefined ? {} : { providerCostUsd: providerCashUsd }),
586
+ ...(subscription === undefined ? {} : { subscriptionValuation: subscription })
587
+ }
588
+ ]);
589
+ const hopCost = actuals.episodes[0]?.hops[0];
180
590
  return {
181
591
  callId: modelCall.call_id,
592
+ episodeId: routingCost?.episodeId ?? modelCall.call_id,
593
+ hopIndex: routingCost?.hopIndex ?? 0,
594
+ role: routingCost?.role ?? "explicit",
595
+ ...(routingCost?.previousModel === undefined
596
+ ? {}
597
+ : { previousModel: routingCost.previousModel }),
598
+ ...(computedCacheOutcome === undefined ? {} : { cacheOutcome: computedCacheOutcome }),
599
+ episode: {
600
+ episodeId: routingCost?.episodeId ?? modelCall.call_id,
601
+ hopCount: 1,
602
+ models: [effectiveModel],
603
+ cacheOutcomes: computedCacheOutcome === undefined ? [] : [computedCacheOutcome],
604
+ ...(actuals.episodes[0]?.cashUsd === undefined
605
+ ? {}
606
+ : { cashUsd: actuals.episodes[0].cashUsd }),
607
+ ...(actuals.episodes[0]?.window === undefined
608
+ ? {}
609
+ : { window: actuals.episodes[0].window }),
610
+ subscriptionUnitCalls: actuals.episodes[0]?.subscriptionUnitCalls ?? 0,
611
+ ...(actuals.episodes[0]?.apiEquivalentUsd === undefined
612
+ ? {}
613
+ : { apiEquivalentUsd: actuals.episodes[0].apiEquivalentUsd }),
614
+ classifierUsd: actuals.episodes[0]?.classifierUsd ?? 0,
615
+ ...(actuals.episodes[0]?.classifierWindow === undefined
616
+ ? {}
617
+ : { classifierWindow: actuals.episodes[0].classifierWindow }),
618
+ failoverUsd: actuals.episodes[0]?.failoverUsd ?? 0,
619
+ ...(actuals.episodes[0]?.failoverWindow === undefined
620
+ ? {}
621
+ : { failoverWindow: actuals.episodes[0].failoverWindow }),
622
+ switchMissTokens: actuals.episodes[0]?.switchMissTokens ?? 0,
623
+ ...(actuals.episodes[0]?.taxCashUsd === undefined
624
+ ? {}
625
+ : { taxCashUsd: actuals.episodes[0].taxCashUsd }),
626
+ ...(actuals.episodes[0]?.taxWindow === undefined
627
+ ? {}
628
+ : { taxWindow: actuals.episodes[0].taxWindow }),
629
+ ...(actuals.episodes[0]?.taxApiEquivalentUsd === undefined
630
+ ? {}
631
+ : { taxApiEquivalentUsd: actuals.episodes[0].taxApiEquivalentUsd }),
632
+ switchMissPremiumUsd: actuals.episodes[0]?.switchMissPremiumUsd ?? 0,
633
+ stickyCounterfactuals: actuals.episodes[0]?.stickyCounterfactuals ?? []
634
+ },
182
635
  status: modelCall.status,
183
636
  ...(stopReason !== undefined ? { stopReason } : {}),
184
637
  ...(requestedModel !== undefined ? { requestedModel } : {}),
@@ -217,7 +670,23 @@ export function callInspection(modelCall) {
217
670
  cost: {
218
671
  ...(estimateUsd !== undefined ? { estimateUsd } : {}),
219
672
  unknownUsage: boolean(metadata?.unknown_usage),
220
- unknownCost: boolean(metadata?.unknown_cost)
673
+ unknownCost: boolean(metadata?.unknown_cost),
674
+ ...(hopCost?.cashUsd === undefined ? {} : { cashUsd: hopCost.cashUsd }),
675
+ ...(hopCost?.window === undefined ? {} : { window: hopCost.window }),
676
+ ...(hopCost?.apiEquivalentUsd === undefined
677
+ ? {}
678
+ : { apiEquivalentUsd: hopCost.apiEquivalentUsd }),
679
+ ...(hopCost?.taxCashUsd === undefined ? {} : { taxCashUsd: hopCost.taxCashUsd }),
680
+ ...(hopCost?.taxWindow === undefined ? {} : { taxWindow: hopCost.taxWindow }),
681
+ ...(hopCost?.taxApiEquivalentUsd === undefined
682
+ ? {}
683
+ : { taxApiEquivalentUsd: hopCost.taxApiEquivalentUsd }),
684
+ ...(hopCost?.stickyCounterfactual === undefined
685
+ ? {}
686
+ : { stickyCounterfactual: hopCost.stickyCounterfactual }),
687
+ switchMissPremiumUsd: hopCost?.switchMissPremiumUsd ?? 0,
688
+ ...(providerCashUsd === undefined ? {} : { providerCashUsd }),
689
+ ...(subscription === undefined ? {} : { subscription })
221
690
  },
222
691
  timing: {
223
692
  startedAt: modelCall.started_at,
@@ -271,6 +740,7 @@ export class CallAttributionStore {
271
740
  this.#prune(now);
272
741
  this.#entries.delete(inspection.callId);
273
742
  this.#entries.set(inspection.callId, { inspection, insertedAt: now });
743
+ this.#refreshEpisode(inspection.episodeId);
274
744
  while (this.#entries.size > this.#limit) {
275
745
  const oldest = this.#entries.keys().next().value;
276
746
  if (oldest === undefined)
@@ -279,6 +749,79 @@ export class CallAttributionStore {
279
749
  this.#evicted = true;
280
750
  }
281
751
  }
752
+ #refreshEpisode(episodeId) {
753
+ const entries = [...this.#entries.values()]
754
+ .filter((entry) => entry.inspection.episodeId === episodeId)
755
+ .sort((left, right) => left.inspection.hopIndex - right.inspection.hopIndex);
756
+ if (entries.length === 0)
757
+ return;
758
+ const actuals = attributeModelCosts(entries.map(({ inspection }) => ({
759
+ episodeId,
760
+ hopIndex: inspection.hopIndex,
761
+ role: inspection.role,
762
+ model: inspection.effectiveModel,
763
+ ...(inspection.previousModel === undefined
764
+ ? {}
765
+ : { previousModel: inspection.previousModel }),
766
+ ...(inspection.cacheOutcome === undefined
767
+ ? {}
768
+ : { cacheOutcome: inspection.cacheOutcome }),
769
+ usage: inspection.cost.subscription?.usage ??
770
+ (inspection.usage === undefined
771
+ ? undefined
772
+ : {
773
+ inputTokens: inspection.usage.prompt_tokens,
774
+ cachedInputTokens: inspection.usage.cached_input_tokens,
775
+ outputTokens: inspection.usage.completion_tokens,
776
+ reasoningTokens: inspection.usage.reasoning_tokens
777
+ }),
778
+ billingMode: inspection.billingMode === "api_key"
779
+ ? "metered-api"
780
+ : inspection.billingMode === "subscription"
781
+ ? "subscription"
782
+ : "upstream-managed",
783
+ ...(inspection.cost.providerCashUsd === undefined
784
+ ? {}
785
+ : { providerCostUsd: inspection.cost.providerCashUsd }),
786
+ ...(inspection.cost.subscription === undefined
787
+ ? {}
788
+ : { subscriptionValuation: inspection.cost.subscription })
789
+ })));
790
+ const rollup = actuals.episodes[0];
791
+ if (rollup === undefined)
792
+ return;
793
+ const episode = {
794
+ episodeId,
795
+ hopCount: rollup.hopCount,
796
+ models: rollup.models,
797
+ cacheOutcomes: rollup.hops.flatMap((hop) => hop.cacheOutcome === undefined ? [] : [hop.cacheOutcome]),
798
+ ...(rollup.cashUsd === undefined ? {} : { cashUsd: rollup.cashUsd }),
799
+ ...(rollup.window === undefined ? {} : { window: rollup.window }),
800
+ subscriptionUnitCalls: rollup.subscriptionUnitCalls,
801
+ ...(rollup.apiEquivalentUsd === undefined
802
+ ? {}
803
+ : { apiEquivalentUsd: rollup.apiEquivalentUsd }),
804
+ classifierUsd: rollup.classifierUsd,
805
+ ...(rollup.classifierWindow === undefined
806
+ ? {}
807
+ : { classifierWindow: rollup.classifierWindow }),
808
+ failoverUsd: rollup.failoverUsd,
809
+ ...(rollup.failoverWindow === undefined
810
+ ? {}
811
+ : { failoverWindow: rollup.failoverWindow }),
812
+ switchMissTokens: rollup.switchMissTokens,
813
+ ...(rollup.taxCashUsd === undefined ? {} : { taxCashUsd: rollup.taxCashUsd }),
814
+ ...(rollup.taxWindow === undefined ? {} : { taxWindow: rollup.taxWindow }),
815
+ ...(rollup.taxApiEquivalentUsd === undefined
816
+ ? {}
817
+ : { taxApiEquivalentUsd: rollup.taxApiEquivalentUsd }),
818
+ switchMissPremiumUsd: rollup.switchMissPremiumUsd,
819
+ stickyCounterfactuals: rollup.stickyCounterfactuals
820
+ };
821
+ for (const entry of entries) {
822
+ entry.inspection = { ...entry.inspection, episode };
823
+ }
824
+ }
282
825
  get(callId) {
283
826
  this.#prune(this.#now());
284
827
  return this.#entries.get(callId)?.inspection;
@@ -1,7 +1,6 @@
1
- import { controlOperation, createRouteKitControlHandler } from "@velum-labs/routekit-control";
1
+ import { controlAuthorization, controlIdempotency, controlMutation, controlOperation, createRouteKitControlHandler, isRouteKitControlMethod } from "@velum-labs/routekit-control";
2
2
  import { ControlError } from "@velum-labs/routekit-runtime/control";
3
3
  import { durationBucket } from "@velum-labs/routekit-telemetry-core";
4
- import { createDaemonControlMethodRegistry } from "./application-services.js";
5
4
  function captureOperation(options, method, params, outcome, durationMs) {
6
5
  const operation = controlOperation(method, params);
7
6
  if (operation === undefined)
@@ -13,21 +12,20 @@ function captureOperation(options, method, params, outcome, durationMs) {
13
12
  version: options.packageVersion
14
13
  });
15
14
  }
16
- function enforceDefinition(registry, runtimeState, method, context) {
17
- const definition = registry.definition(method);
18
- if (definition.authorization === "ephemeral" && context.principal?.role !== "ephemeral") {
15
+ function enforceDefinition(runtimeState, method, context) {
16
+ if (controlAuthorization(method) === "ephemeral" && context.principal?.role !== "ephemeral") {
19
17
  throw new ControlError({
20
18
  code: "unauthorized",
21
19
  message: `${method} requires the local service credential`
22
20
  });
23
21
  }
24
- if (definition.idempotency === "required" && context.idempotencyKey === undefined) {
22
+ if (controlIdempotency(method) === "required" && context.idempotencyKey === undefined) {
25
23
  throw new ControlError({
26
24
  code: "bad_request",
27
25
  message: `${method} requires an idempotency key`
28
26
  });
29
27
  }
30
- if (runtimeState.lifecycle === "paused" && definition.mutation === "mutation") {
28
+ if (runtimeState.lifecycle === "paused" && controlMutation(method) === "mutation") {
31
29
  throw new ControlError({
32
30
  code: "unavailable",
33
31
  message: "RouteKit daemon is synchronizing a replacement worker"
@@ -35,22 +33,18 @@ function enforceDefinition(registry, runtimeState, method, context) {
35
33
  }
36
34
  }
37
35
  export function createDaemonControlDispatch(options) {
38
- const registry = createDaemonControlMethodRegistry(options.handlers);
39
- const registeredMethods = new Set(registry.list().map((definition) => definition.method));
40
- const routeKitDispatch = createRouteKitControlHandler(registry.handlers(), {
41
- registry,
36
+ const routeKitDispatch = createRouteKitControlHandler(options.handlers, {
42
37
  onCommitted: (method, params, durationMs) => captureOperation(options, method, params, "success", durationMs),
43
38
  onControlError: (method, params, _code, durationMs) => captureOperation(options, method, params, "error", durationMs)
44
39
  });
45
40
  return async (rawMethod, params, context) => {
46
- if (!registeredMethods.has(rawMethod)) {
41
+ if (!isRouteKitControlMethod(rawMethod)) {
47
42
  return await routeKitDispatch(rawMethod, params, context);
48
43
  }
49
44
  const method = rawMethod;
50
- enforceDefinition(registry, options.runtimeState, method, context);
51
- const definition = registry.definition(method);
45
+ enforceDefinition(options.runtimeState, method, context);
52
46
  if (options.executeIdempotent !== undefined &&
53
- definition.mutation === "mutation" &&
47
+ controlMutation(method) === "mutation" &&
54
48
  context.idempotencyKey !== undefined) {
55
49
  return await options.executeIdempotent({
56
50
  method,
@@ -9,7 +9,7 @@ import { healthyControl, readDaemonRevisions } from "./daemon-state.js";
9
9
  export async function prepareDaemonBootstrap(options) {
10
10
  const env = options.env ?? process.env;
11
11
  const home = options.stateHome ?? routekitHome(env);
12
- const configPath = options.configPath ?? globalRouterConfigPath();
12
+ const configPath = options.configPath ?? globalRouterConfigPath(undefined, env);
13
13
  const drainGraceMs = options.drainGraceMs ?? 30_000;
14
14
  const store = createServiceRecordStore({ home, product: "routekit" });
15
15
  const hosted = options.hosted;
@@ -1,6 +1,6 @@
1
1
  import type { EffectRouteKitControlHandlers } from "@velum-labs/routekit-control/effect";
2
2
  /**
3
- * Composes owned application services into the daemon control handler map.
3
+ * Composes owned handler groups into the daemon control handler map.
4
4
  * Protocol policy lives in the method table; this function only binds use cases.
5
5
  * Daemon-lifetime state is yielded from `daemonLive`.
6
6
  */