@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,555 @@
1
+ import { providerDefaultBaseUrl, subscriptionInfo } from "@velum-labs/routekit-registry";
2
+ import { executeWebRequest, RouteKitFailure, routeKitError, toRouteKitFailure } from "@velum-labs/routekit-runtime/effect";
3
+ import { Effect } from "effect";
4
+ import { resolvedCodexOriginator } from "@velum-labs/routekit-accounts";
5
+ import { loadSubscriptionCredential, persistSubscriptionCredential } from "@velum-labs/routekit-accounts";
6
+ import { decodeJsonBody } from "@velum-labs/routekit-accounts";
7
+ import { adminRequest, authenticationFailure, booleanValue, CREDENTIAL_ERROR_IDENTIFIERS, defineWindow, discoverSubscriptionModels, epochSeconds, errorMessage, isRecord, MODEL_ERROR_IDENTIFIERS, numeric, percentageUtilization, refreshNetworkFailure, refreshResponseBody, retryAfter, SubscriptionRefreshError, usageRequest, windowsFromUsagePayload } from "@velum-labs/routekit-accounts";
8
+ function codexErrorRecord(payload) {
9
+ if (!isRecord(payload))
10
+ return undefined;
11
+ const response = isRecord(payload.response) ? payload.response : undefined;
12
+ const error = isRecord(payload.error)
13
+ ? payload.error
14
+ : isRecord(response?.error)
15
+ ? response.error
16
+ : undefined;
17
+ return error;
18
+ }
19
+ function codexFailure(payload, fallback) {
20
+ const error = codexErrorRecord(payload);
21
+ const type = typeof error?.type === "string"
22
+ ? error.type
23
+ : typeof error?.error_type === "string"
24
+ ? error.error_type
25
+ : undefined;
26
+ const code = typeof error?.code === "string" ? error.code : undefined;
27
+ const identifiers = [type, code]
28
+ .filter((value) => value !== undefined)
29
+ .map((value) => value.toLowerCase());
30
+ const identity = identifiers.join(" ");
31
+ const credential = identifiers.some((value) => CREDENTIAL_ERROR_IDENTIFIERS.has(value));
32
+ const memberModel = identifiers.some((value) => MODEL_ERROR_IDENTIFIERS.has(value));
33
+ const quota = /usage[_ ]?limit|usagelimit|quota|insufficient_quota/.test(identity);
34
+ const transient = /rate[_ ]?limit|server_error|temporar|overload|timeout|unavailable/.test(identity);
35
+ const resetsAt = epochSeconds(error?.resets_at ?? error?.reset_at);
36
+ return {
37
+ category: credential
38
+ ? "auth_permanent"
39
+ : quota
40
+ ? "quota_exhausted"
41
+ : transient
42
+ ? "transient"
43
+ : "unknown",
44
+ ...(credential
45
+ ? { scope: "credential", status: 401 }
46
+ : memberModel
47
+ ? { scope: "member_model" }
48
+ : {}),
49
+ message: typeof error?.message === "string" ? error.message : fallback,
50
+ ...(type !== undefined ? { type } : {}),
51
+ ...(code !== undefined ? { code } : {}),
52
+ ...(resetsAt !== undefined ? { resetsAt } : {})
53
+ };
54
+ }
55
+ function codexStreamOutcome(event, payload) {
56
+ const record = isRecord(payload) ? payload : undefined;
57
+ const eventType = event ?? (typeof record?.type === "string" ? record.type : undefined);
58
+ if (eventType === "response.completed")
59
+ return { terminal: "success" };
60
+ if (eventType === "response.failed" || eventType === "error") {
61
+ return {
62
+ terminal: "failure",
63
+ failure: codexFailure(payload, "Codex response failed")
64
+ };
65
+ }
66
+ if (eventType === "response.incomplete") {
67
+ const response = isRecord(record?.response) ? record.response : undefined;
68
+ const details = isRecord(response?.incomplete_details)
69
+ ? response.incomplete_details
70
+ : undefined;
71
+ const reason = typeof details?.reason === "string" ? details.reason : undefined;
72
+ const failure = codexFailure(payload, "Codex response was incomplete");
73
+ return {
74
+ terminal: "failure",
75
+ failure: failure.type !== undefined || failure.code !== undefined
76
+ ? failure
77
+ : {
78
+ ...failure,
79
+ category: reason === "server_error" ? "transient" : "unknown",
80
+ ...(reason !== undefined ? { type: reason } : {})
81
+ }
82
+ };
83
+ }
84
+ const semanticOutput = eventType === "response.output_text.delta" ||
85
+ eventType === "response.reasoning_summary_text.delta" ||
86
+ eventType === "response.reasoning_text.delta" ||
87
+ eventType === "response.function_call_arguments.delta" ||
88
+ (eventType === "response.output_item.added" &&
89
+ isRecord(record?.item) &&
90
+ record.item.type === "function_call");
91
+ return semanticOutput ? { semanticOutput: true } : {};
92
+ }
93
+ export function codexProvider() {
94
+ const mode = "codex";
95
+ const info = subscriptionInfo(mode);
96
+ return {
97
+ mode,
98
+ upstreamBaseUrl: providerDefaultBaseUrl("codex") ?? "https://chatgpt.com/backend-api/codex",
99
+ requestPath: "/responses",
100
+ loadCredential: (path) => Effect.tryPromise({
101
+ try: () => loadSubscriptionCredential(mode, path),
102
+ catch: toRouteKitFailure
103
+ }),
104
+ persistCredential: (previous, next) => Effect.tryPromise({
105
+ try: () => persistSubscriptionCredential(previous, next),
106
+ catch: toRouteKitFailure
107
+ }),
108
+ discoverModels: (credential, signal) => discoverSubscriptionModels(mode, providerDefaultBaseUrl("codex") ?? "https://chatgpt.com/backend-api/codex", {
109
+ authorization: `Bearer ${credential.accessToken}`,
110
+ originator: resolvedCodexOriginator(),
111
+ ...(credential.accountId !== undefined
112
+ ? { "chatgpt-account-id": credential.accountId }
113
+ : {})
114
+ }, signal),
115
+ authHeaders: (credential) => ({
116
+ authorization: `Bearer ${credential.accessToken}`,
117
+ ...(credential.accountId !== undefined ? { "chatgpt-account-id": credential.accountId } : {}),
118
+ ...(info.defaultHeaders ?? {}),
119
+ originator: resolvedCodexOriginator()
120
+ }),
121
+ refresh: (credential, signal) => Effect.gen(function* () {
122
+ if (credential.refreshToken === undefined) {
123
+ return yield* Effect.fail(new SubscriptionRefreshError({
124
+ kind: "permanent",
125
+ reasonCode: "missing_refresh_token"
126
+ }));
127
+ }
128
+ const body = new URLSearchParams({
129
+ grant_type: "refresh_token",
130
+ refresh_token: credential.refreshToken,
131
+ client_id: credential.oauthClientId ?? info.oauth.clientId
132
+ });
133
+ const response = yield* executeWebRequest(info.oauth.tokenEndpoint, {
134
+ method: "POST",
135
+ headers: {
136
+ accept: "application/json",
137
+ "content-type": "application/x-www-form-urlencoded"
138
+ },
139
+ body,
140
+ ...(signal !== undefined ? { signal } : {})
141
+ }).pipe(Effect.mapError((error) => refreshNetworkFailure(error)));
142
+ const decoded = yield* Effect.tryPromise({
143
+ try: () => decodeJsonBody(response),
144
+ catch: (cause) => refreshNetworkFailure(cause)
145
+ });
146
+ if (response.ok && !decoded.hasJsonBody) {
147
+ return yield* Effect.fail(new SubscriptionRefreshError({
148
+ kind: "transient",
149
+ status: response.status,
150
+ failureKind: "protocol"
151
+ }));
152
+ }
153
+ return yield* Effect.try({
154
+ try: () => refreshResponseBody(response, decoded.body, credential),
155
+ catch: (cause) => refreshNetworkFailure(cause)
156
+ });
157
+ }).pipe(Effect.mapError(refreshNetworkFailure)),
158
+ fetchUsage: (credential, signal) => Effect.gen(function* () {
159
+ const payload = yield* usageRequest(info.oauth.usageEndpoint, {
160
+ authorization: `Bearer ${credential.accessToken}`,
161
+ ...(credential.accountId !== undefined
162
+ ? { "chatgpt-account-id": credential.accountId }
163
+ : {})
164
+ }, signal);
165
+ return yield* Effect.try({
166
+ try: () => codexUsageLimits(payload),
167
+ catch: toRouteKitFailure
168
+ });
169
+ }),
170
+ fetchResetCredits: (credential, signal) => Effect.gen(function* () {
171
+ const endpoint = info.oauth.resetCreditsEndpoint;
172
+ if (endpoint === undefined) {
173
+ return yield* new RouteKitFailure({
174
+ message: "Codex reset-credits endpoint is not configured"
175
+ });
176
+ }
177
+ const payload = yield* usageRequest(endpoint, {
178
+ authorization: `Bearer ${credential.accessToken}`,
179
+ ...(credential.accountId !== undefined
180
+ ? { "chatgpt-account-id": credential.accountId }
181
+ : {})
182
+ }, signal);
183
+ return yield* Effect.try({
184
+ try: () => parseResetCreditSnapshot(payload),
185
+ catch: toRouteKitFailure
186
+ });
187
+ }),
188
+ consumeResetCredit: (credential, input, signal) => Effect.gen(function* () {
189
+ const endpoint = info.oauth.resetCreditsEndpoint;
190
+ if (endpoint === undefined) {
191
+ return yield* new RouteKitFailure({
192
+ message: "Codex reset-credits endpoint is not configured"
193
+ });
194
+ }
195
+ if (input.redeemRequestId.trim().length === 0) {
196
+ return yield* new RouteKitFailure({ message: "redeemRequestId is required" });
197
+ }
198
+ const creditId = input.creditId?.trim();
199
+ if (input.creditId !== undefined && (creditId === undefined || creditId.length === 0)) {
200
+ return yield* new RouteKitFailure({ message: "creditId must not be empty" });
201
+ }
202
+ const response = yield* executeWebRequest(`${endpoint}/consume`, {
203
+ method: "POST",
204
+ headers: {
205
+ accept: "application/json",
206
+ "content-type": "application/json",
207
+ authorization: `Bearer ${credential.accessToken}`,
208
+ ...(credential.accountId !== undefined
209
+ ? { "chatgpt-account-id": credential.accountId }
210
+ : {})
211
+ },
212
+ body: JSON.stringify({
213
+ redeem_request_id: input.redeemRequestId,
214
+ ...(creditId !== undefined ? { credit_id: creditId } : {})
215
+ }),
216
+ ...(signal !== undefined ? { signal } : {})
217
+ }).pipe(Effect.mapError((error) => routeKitError(error)));
218
+ const body = yield* Effect.tryPromise({
219
+ try: async () => {
220
+ try {
221
+ return await response.json();
222
+ }
223
+ catch {
224
+ return undefined;
225
+ }
226
+ },
227
+ catch: toRouteKitFailure
228
+ });
229
+ if (!response.ok && body === undefined) {
230
+ return yield* new RouteKitFailure({
231
+ message: `Codex reset-credit consume returned ${response.status}`
232
+ });
233
+ }
234
+ const result = parseConsumeResetResult(body, input.redeemRequestId, response.ok);
235
+ if (!response.ok && !result.ok && result.code === "http_error") {
236
+ return yield* new RouteKitFailure({
237
+ message: `Codex reset-credit consume returned ${response.status}`
238
+ });
239
+ }
240
+ return result;
241
+ }),
242
+ parseLimits: (headers, body) => {
243
+ const fromHeaders = codexLimitsFromHeaders(headers);
244
+ if (fromHeaders !== undefined)
245
+ return fromHeaders;
246
+ if (isRecord(body) && isRecord(body.rate_limit)) {
247
+ return codexUsageLimits(body, "response", "partial");
248
+ }
249
+ return undefined;
250
+ },
251
+ parseStreamEvent: codexStreamLimits,
252
+ isEventStreamResponse: (response) => {
253
+ const contentType = response.headers.get("content-type")?.toLowerCase() ?? "";
254
+ return (contentType.includes("text/event-stream") ||
255
+ contentType.includes("application/octet-stream"));
256
+ },
257
+ parseStreamOutcome: codexStreamOutcome,
258
+ classify: (status, headers, body) => {
259
+ const authentication = authenticationFailure(status, body, `Codex returned ${status}`);
260
+ if (authentication !== undefined)
261
+ return authentication;
262
+ if (status !== 429 && status < 500)
263
+ return undefined;
264
+ const error = isRecord(body) && isRecord(body.error) ? body.error : undefined;
265
+ const errorType = typeof error?.type === "string"
266
+ ? error.type
267
+ : typeof error?.error_type === "string"
268
+ ? error.error_type
269
+ : undefined;
270
+ const quota = errorType === "usage_limit_reached" || errorType === "usageLimitExceeded";
271
+ const resetsAt = epochSeconds(error?.resets_at);
272
+ const retryAfterSeconds = retryAfter(headers, mode);
273
+ const code = typeof error?.code === "string" ? error.code : undefined;
274
+ return {
275
+ category: quota ? "quota_exhausted" : "transient",
276
+ status,
277
+ message: errorMessage(body, `Codex returned ${status}`),
278
+ ...(errorType !== undefined ? { type: errorType } : {}),
279
+ ...(code !== undefined ? { code } : {}),
280
+ ...(retryAfterSeconds !== undefined ? { retryAfter: retryAfterSeconds } : {}),
281
+ ...(resetsAt !== undefined ? { resetsAt } : {})
282
+ };
283
+ },
284
+ fetchAdminUsageCost: (adminKey, range, signal) => Effect.gen(function* () {
285
+ const query = new URLSearchParams({
286
+ start_time: String(Math.floor(range.startTime)),
287
+ bucket_width: "1d",
288
+ limit: "31"
289
+ });
290
+ if (range.endTime !== undefined)
291
+ query.set("end_time", String(Math.floor(range.endTime)));
292
+ const headers = { authorization: `Bearer ${adminKey}` };
293
+ const [usage, cost] = yield* Effect.all([
294
+ adminRequest(info.admin.usageEndpoint, query, headers, signal),
295
+ adminRequest(info.admin.costEndpoint, query, headers, signal)
296
+ ], { concurrency: "unbounded" });
297
+ return { usage, cost };
298
+ })
299
+ };
300
+ }
301
+ function codexWindowFromHeaders(headers, prefix, name, window, observedAt) {
302
+ const raw = headers.get(`${prefix}-${name}-used-percent`);
303
+ const used = percentageUtilization(raw);
304
+ if (used === undefined) {
305
+ return raw === null
306
+ ? {}
307
+ : {
308
+ diagnostic: {
309
+ code: "invalid_utilization",
310
+ window,
311
+ field: "used_percent"
312
+ }
313
+ };
314
+ }
315
+ const minutes = numeric(headers.get(`${prefix}-${name}-window-minutes`));
316
+ const resetsAt = epochSeconds(headers.get(`${prefix}-${name}-reset-at`));
317
+ const limitName = headers.get(`${prefix}-limit-name`);
318
+ return {
319
+ window: {
320
+ utilization: used,
321
+ ...(minutes !== undefined ? { windowSeconds: minutes * 60 } : {}),
322
+ ...(resetsAt !== undefined ? { resetsAt } : {}),
323
+ ...(limitName !== null ? { limitName } : {}),
324
+ observedAt,
325
+ source: "headers"
326
+ }
327
+ };
328
+ }
329
+ function codexCredits(headers) {
330
+ const hasCredits = booleanValue(headers.get("x-codex-credits-has-credits"));
331
+ const unlimited = booleanValue(headers.get("x-codex-credits-unlimited"));
332
+ const balance = headers.get("x-codex-credits-balance");
333
+ if (hasCredits === undefined && unlimited === undefined && balance === null)
334
+ return undefined;
335
+ return {
336
+ ...(hasCredits !== undefined ? { hasCredits } : {}),
337
+ ...(unlimited !== undefined ? { unlimited } : {}),
338
+ ...(balance !== null ? { balance } : {})
339
+ };
340
+ }
341
+ function codexLimitsFromHeaders(headers) {
342
+ const info = subscriptionInfo("codex").rateLimit;
343
+ const observedAt = Date.now() / 1000;
344
+ const defaultPrefix = info.headerPrefix.toLowerCase();
345
+ const prefixes = new Set();
346
+ for (const [name] of headers) {
347
+ const match = /^(.+)-(?:primary|secondary)-used-percent$/.exec(name.toLowerCase());
348
+ if (match?.[1]?.startsWith("x-") === true)
349
+ prefixes.add(match[1]);
350
+ }
351
+ const windows = Object.create(null);
352
+ const diagnostics = [];
353
+ for (const prefix of [...prefixes].sort((left, right) => {
354
+ if (left === defaultPrefix)
355
+ return -1;
356
+ if (right === defaultPrefix)
357
+ return 1;
358
+ return left.localeCompare(right);
359
+ })) {
360
+ const family = prefix === defaultPrefix ? "codex" : prefix.slice(2).replaceAll("-", "_");
361
+ for (const name of ["primary", "secondary"]) {
362
+ const key = `${family}:${name}`;
363
+ const parsed = codexWindowFromHeaders(headers, prefix, name, key, observedAt);
364
+ if (parsed.window !== undefined)
365
+ defineWindow(windows, key, parsed.window);
366
+ if (parsed.diagnostic !== undefined)
367
+ diagnostics.push(parsed.diagnostic);
368
+ }
369
+ }
370
+ const credits = codexCredits(headers);
371
+ if (Object.keys(windows).length === 0 && diagnostics.length === 0 && credits === undefined) {
372
+ return undefined;
373
+ }
374
+ return {
375
+ windows,
376
+ ...(diagnostics.length > 0 ? { diagnostics } : {}),
377
+ ...(credits !== undefined ? { credits } : {}),
378
+ observedAt,
379
+ source: "headers",
380
+ completeness: "partial"
381
+ };
382
+ }
383
+ function codexUsageLimits(payload, source = "usage", completeness = "snapshot") {
384
+ if (!isRecord(payload))
385
+ throw new Error("Codex usage endpoint returned an invalid payload");
386
+ const observedAt = Date.now() / 1000;
387
+ const rateLimit = isRecord(payload.rate_limit) ? payload.rate_limit : {};
388
+ const parsed = windowsFromUsagePayload("codex", {
389
+ primary: rateLimit.primary_window,
390
+ secondary: rateLimit.secondary_window
391
+ }, observedAt, source);
392
+ const rawCredits = isRecord(payload.credits) ? payload.credits : undefined;
393
+ const credits = rawCredits === undefined
394
+ ? undefined
395
+ : {
396
+ ...(booleanValue(rawCredits.has_credits) !== undefined
397
+ ? { hasCredits: booleanValue(rawCredits.has_credits) }
398
+ : {}),
399
+ ...(booleanValue(rawCredits.unlimited) !== undefined
400
+ ? { unlimited: booleanValue(rawCredits.unlimited) }
401
+ : {}),
402
+ ...(typeof rawCredits.balance === "string" ? { balance: rawCredits.balance } : {})
403
+ };
404
+ const resetCredits = codexResetCreditsFromUsage(payload, observedAt);
405
+ return {
406
+ windows: parsed.windows,
407
+ ...(parsed.diagnostics.length > 0 ? { diagnostics: parsed.diagnostics } : {}),
408
+ ...(typeof payload.plan_type === "string" ? { planType: payload.plan_type } : {}),
409
+ ...(credits !== undefined ? { credits } : {}),
410
+ ...(resetCredits !== undefined ? { resetCredits } : {}),
411
+ observedAt,
412
+ source,
413
+ completeness
414
+ };
415
+ }
416
+ function codexResetCreditsFromUsage(payload, observedAt) {
417
+ const raw = isRecord(payload.rate_limit_reset_credits)
418
+ ? payload.rate_limit_reset_credits
419
+ : isRecord(payload.rateLimitResetCredits)
420
+ ? payload.rateLimitResetCredits
421
+ : undefined;
422
+ if (raw === undefined)
423
+ return undefined;
424
+ const count = numeric(raw.available_count) ??
425
+ numeric(raw.availableCount) ??
426
+ (Array.isArray(raw.credits) ? raw.credits.length : undefined);
427
+ if (count === undefined)
428
+ return undefined;
429
+ const credits = Array.isArray(raw.credits)
430
+ ? raw.credits
431
+ .map((entry) => parseResetCredit(entry))
432
+ .filter((entry) => entry !== undefined)
433
+ : undefined;
434
+ return {
435
+ observedAt,
436
+ availableCount: Math.max(0, Math.floor(count)),
437
+ ...(credits !== undefined && credits.length > 0 ? { credits } : {})
438
+ };
439
+ }
440
+ function parseResetCredit(value) {
441
+ if (!isRecord(value))
442
+ return undefined;
443
+ const id = typeof value.id === "string" && value.id.length > 0
444
+ ? value.id
445
+ : typeof value.credit_id === "string" && value.credit_id.length > 0
446
+ ? value.credit_id
447
+ : typeof value.creditId === "string" && value.creditId.length > 0
448
+ ? value.creditId
449
+ : undefined;
450
+ if (id === undefined)
451
+ return undefined;
452
+ const status = typeof value.status === "string" ? value.status : undefined;
453
+ return {
454
+ id,
455
+ ...(typeof value.reset_type === "string"
456
+ ? { resetType: value.reset_type }
457
+ : typeof value.resetType === "string"
458
+ ? { resetType: value.resetType }
459
+ : {}),
460
+ ...(status !== undefined ? { status } : {}),
461
+ ...(epochSeconds(value.granted_at ?? value.grantedAt) !== undefined
462
+ ? { grantedAt: epochSeconds(value.granted_at ?? value.grantedAt) }
463
+ : {}),
464
+ ...(epochSeconds(value.expires_at ?? value.expiresAt) !== undefined
465
+ ? { expiresAt: epochSeconds(value.expires_at ?? value.expiresAt) }
466
+ : {}),
467
+ ...(typeof value.title === "string" ? { title: value.title } : {}),
468
+ ...(typeof value.description === "string" ? { description: value.description } : {})
469
+ };
470
+ }
471
+ function parseResetCreditSnapshot(payload) {
472
+ const observedAt = Date.now() / 1000;
473
+ if (!isRecord(payload))
474
+ throw new Error("Codex reset-credits endpoint returned an invalid payload");
475
+ const rows = Array.isArray(payload.credits)
476
+ ? payload.credits
477
+ : Array.isArray(payload.items)
478
+ ? payload.items
479
+ : Array.isArray(payload.data)
480
+ ? payload.data
481
+ : [];
482
+ const credits = rows
483
+ .map((entry) => parseResetCredit(entry))
484
+ .filter((entry) => entry !== undefined);
485
+ const available = credits.filter((credit) => {
486
+ const status = credit.status?.toLowerCase();
487
+ return status === undefined || status === "available" || status === "active";
488
+ });
489
+ const count = numeric(payload.available_count) ?? numeric(payload.availableCount) ?? available.length;
490
+ return {
491
+ observedAt,
492
+ availableCount: Math.max(0, Math.floor(count)),
493
+ ...(credits.length > 0 ? { credits } : {})
494
+ };
495
+ }
496
+ function normalizeResetConsumeCode(code) {
497
+ switch (code) {
498
+ case "alreadyRedeemed":
499
+ return "already_redeemed";
500
+ case "noCredit":
501
+ return "no_credit";
502
+ case "nothingToReset":
503
+ return "nothing_to_reset";
504
+ default:
505
+ return code;
506
+ }
507
+ }
508
+ function parseConsumeResetResult(payload, redeemRequestId, httpOk) {
509
+ const body = isRecord(payload) ? payload : undefined;
510
+ const rawCode = typeof body?.code === "string" ? body.code : httpOk ? "reset" : "http_error";
511
+ const code = normalizeResetConsumeCode(rawCode);
512
+ const credit = parseResetCredit(body?.credit ?? body?.rate_limit_reset_credit);
513
+ const windowsReset = numeric(body?.windows_reset ?? body?.windowsReset);
514
+ return {
515
+ ok: code === "reset",
516
+ code,
517
+ redeemRequestId,
518
+ ...(credit?.id !== undefined
519
+ ? { creditId: credit.id }
520
+ : typeof body?.credit_id === "string"
521
+ ? { creditId: body.credit_id }
522
+ : typeof body?.creditId === "string"
523
+ ? { creditId: body.creditId }
524
+ : {}),
525
+ ...(windowsReset !== undefined ? { windowsReset } : {})
526
+ };
527
+ }
528
+ function rateLimitsObject(value) {
529
+ if (!isRecord(value))
530
+ return undefined;
531
+ if (isRecord(value.rate_limits))
532
+ return value.rate_limits;
533
+ for (const child of Object.values(value)) {
534
+ const found = rateLimitsObject(child);
535
+ if (found !== undefined)
536
+ return found;
537
+ }
538
+ return undefined;
539
+ }
540
+ function codexStreamLimits(payload) {
541
+ const raw = rateLimitsObject(payload);
542
+ if (raw === undefined)
543
+ return undefined;
544
+ const observedAt = Date.now() / 1000;
545
+ const parsed = windowsFromUsagePayload("codex", raw, observedAt, "stream");
546
+ return Object.keys(parsed.windows).length > 0 || parsed.diagnostics.length > 0
547
+ ? {
548
+ windows: parsed.windows,
549
+ ...(parsed.diagnostics.length > 0 ? { diagnostics: parsed.diagnostics } : {}),
550
+ observedAt,
551
+ source: "stream",
552
+ completeness: "partial"
553
+ }
554
+ : undefined;
555
+ }