@yolk-sdk/agent 0.1.0-canary.57 → 0.1.0-canary.58

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -10,7 +10,7 @@ Root export is intentionally tiny. Import feature APIs from explicit subpaths.
10
10
  pnpm add @yolk-sdk/agent@canary effect
11
11
  ```
12
12
 
13
- Add `react` if you use `@yolk-sdk/agent/react`.
13
+ Add `react` if you use `@yolk-sdk/agent/react` or `@yolk-sdk/agent/voice/react`.
14
14
 
15
15
  Canary APIs are unstable. Keep all `@yolk-sdk/*` packages on the same version.
16
16
  Published package metadata requires Node.js 22+.
@@ -30,13 +30,16 @@ Published package metadata requires Node.js 22+.
30
30
  | `@yolk-sdk/agent/oauth` | Provider-neutral OAuth token and broker contracts |
31
31
  | `@yolk-sdk/agent/providers/openai` | OpenAI/Codex OAuth and broker helpers |
32
32
  | `@yolk-sdk/agent/providers/openai/codex` | OpenAI Codex request and auth helpers |
33
+ | `@yolk-sdk/agent/providers/openai/codex-usage` | Codex subscription-allowance snapshots |
33
34
  | `@yolk-sdk/agent/providers/openai/codex-provider` | Codex LLM provider factory |
34
35
  | `@yolk-sdk/agent/providers/openai/provider` | OpenAI-compatible LLM provider factory |
35
36
  | `@yolk-sdk/agent/providers/openai/realtime` | OpenAI Realtime session config and event codecs |
36
37
  | `@yolk-sdk/agent/providers/openai/speech` | OpenAI text-to-speech and transcription adapters |
37
38
  | `@yolk-sdk/agent/providers/anthropic` | Anthropic/Claude OAuth and broker helpers |
38
39
  | `@yolk-sdk/agent/providers/anthropic/claude` | Claude request and auth helpers |
40
+ | `@yolk-sdk/agent/providers/anthropic/usage` | Claude subscription-allowance snapshots |
39
41
  | `@yolk-sdk/agent/providers/anthropic/claude-provider` | Claude LLM provider factory |
42
+ | `@yolk-sdk/agent/providers/subscription-usage` | Shared allowance snapshot and safe error schemas |
40
43
  | `@yolk-sdk/agent/skillset` | Portable skill and slash-command parsing/catalogs |
41
44
  | `@yolk-sdk/agent/voice` | Voice protocol, controller, tool handler, projection, speech |
42
45
  | `@yolk-sdk/agent/voice/browser` | Browser WebRTC voice transport |
@@ -180,6 +183,37 @@ policy and emits protocol-visible retry/error state:
180
183
  Raw provider response bodies stay out of protocol/UI. Hosts own durable persistence and display of
181
184
  typed retry/error state.
182
185
 
186
+ ## Subscription allowance snapshots
187
+
188
+ The Claude and Codex usage adapters read best-effort consumer subscription allowance percentages and
189
+ reset windows. Claude currently normalizes its aggregate five-hour and seven-day windows; Codex
190
+ normalizes its primary and secondary rate-limit windows. Additional provider-specific buckets are
191
+ ignored. These private provider endpoints may change without notice. Pass a fresh
192
+ host-owned `OAuthAccessToken` and provide an Effect `HttpClient`:
193
+
194
+ ```ts
195
+ import { Effect } from 'effect'
196
+ import { FetchHttpClient } from 'effect/unstable/http'
197
+ import { fetchOpenAiCodexSubscriptionUsage } from '@yolk-sdk/agent/providers/openai/codex-usage'
198
+
199
+ const snapshot = await fetchOpenAiCodexSubscriptionUsage(hostOAuthAccessToken).pipe(
200
+ Effect.provide(FetchHttpClient.layer),
201
+ Effect.runPromise
202
+ )
203
+ ```
204
+
205
+ Use `fetchAnthropicClaudeSubscriptionUsage` from
206
+ `@yolk-sdk/agent/providers/anthropic/usage` for Claude. Provider adapters return an immutable Effect
207
+ `Chunk` of semantic window ids, percentages, and optional reset/duration fields; hosts own labels,
208
+ polling, persistence, stale-data
209
+ policy, alert thresholds, billing interpretation, and UI. Configure `requestTimeoutMs` at the
210
+ server integration boundary. Endpoint origins stay fixed, and `FetchHttpClient.layer` is configured
211
+ for manual redirects. A custom `HttpClient` must not follow redirects for these credential-bearing
212
+ requests.
213
+
214
+ Subscription allowance snapshots are separate from protocol `AgentUsage`, which accounts for tokens
215
+ used by model requests and nested model work.
216
+
183
217
  ## Usage accounting
184
218
 
185
219
  Provider `LLMUsage` events are additive deltas. Adapters normalize vendor counters before emitting;
@@ -0,0 +1,15 @@
1
+ import { ProviderSubscriptionUsageError, ProviderSubscriptionUsageResponseError, ProviderSubscriptionUsageSnapshot } from "../subscription-usage.mjs";
2
+ import { Effect } from "effect";
3
+ import { HttpClient } from "effect/unstable/http";
4
+ import { OAuthAccessToken } from "@yolk-sdk/agent/oauth";
5
+
6
+ //#region src/providers/anthropic/usage.d.ts
7
+ declare const anthropicClaudeSubscriptionUsageUrl = "https://api.anthropic.com/api/oauth/usage";
8
+ type AnthropicClaudeSubscriptionUsageOptions = {
9
+ readonly requestTimeoutMs?: number;
10
+ };
11
+ declare const parseAnthropicClaudeSubscriptionUsage: (value: unknown, fetchedAt: string) => Effect.Effect<ProviderSubscriptionUsageSnapshot, ProviderSubscriptionUsageResponseError>;
12
+ declare const fetchAnthropicClaudeSubscriptionUsage: (token: OAuthAccessToken, options?: AnthropicClaudeSubscriptionUsageOptions) => Effect.Effect<ProviderSubscriptionUsageSnapshot, ProviderSubscriptionUsageError, HttpClient.HttpClient>;
13
+ //#endregion
14
+ export { AnthropicClaudeSubscriptionUsageOptions, anthropicClaudeSubscriptionUsageUrl, fetchAnthropicClaudeSubscriptionUsage, parseAnthropicClaudeSubscriptionUsage };
15
+ //# sourceMappingURL=usage.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"usage.d.mts","names":[],"sources":["../../../src/providers/anthropic/usage.ts"],"mappings":";;;;;;cAqBa,mCAAA;AAAA,KAED,uCAAA;EAAA,SACD,gBAAgB;AAAA;AAAA,cAoBd,qCAAA,GACX,KAAA,WACA,SAAA,aACC,MAAA,CAAO,MAAA,CAAO,iCAAA,EAAmC,sCAAA;AAAA,cAuDvC,qCAAA,GACX,KAAA,EAAO,gBAAA,EACP,OAAA,GAAS,uCAAA,KACR,MAAA,CAAO,MAAA,CACR,iCAAA,EACA,8BAAA,EACA,UAAA,CAAW,UAAA"}
@@ -0,0 +1,73 @@
1
+ import { ProviderSubscriptionUsageConfigurationError, ProviderSubscriptionUsageResponseError, ProviderSubscriptionUsageSnapshot, ProviderSubscriptionUsageWindow } from "../subscription-usage.mjs";
2
+ import { canonicalSubscriptionUsageInstant, executeProviderSubscriptionUsageRequest, readProviderSubscriptionUsageJson, validProviderSubscriptionUsageTimeout, validSubscriptionUsagePercent } from "../subscription-usage-internal.mjs";
3
+ import { anthropicClaudeAuthorizationHeaders, anthropicClaudeProviderId } from "./claude.mjs";
4
+ import { Chunk, Clock, Effect } from "effect";
5
+ import { HttpClient, HttpClientRequest } from "effect/unstable/http";
6
+ import * as Schema from "effect/Schema";
7
+ //#region src/providers/anthropic/usage.ts
8
+ const anthropicClaudeSubscriptionUsageUrl = "https://api.anthropic.com/api/oauth/usage";
9
+ const NullableNumber = Schema.NullOr(Schema.Number);
10
+ const NullableString = Schema.NullOr(Schema.String);
11
+ var AnthropicClaudeSubscriptionUsageWindowWire = class extends Schema.Class("AnthropicClaudeSubscriptionUsageWindowWire")({
12
+ utilization: Schema.optional(NullableNumber),
13
+ resets_at: Schema.optional(NullableString)
14
+ }) {};
15
+ var AnthropicClaudeSubscriptionUsageWire = class extends Schema.Class("AnthropicClaudeSubscriptionUsageWire")({
16
+ five_hour: Schema.optional(Schema.NullOr(AnthropicClaudeSubscriptionUsageWindowWire)),
17
+ seven_day: Schema.optional(Schema.NullOr(AnthropicClaudeSubscriptionUsageWindowWire))
18
+ }) {};
19
+ const parseAnthropicClaudeSubscriptionUsage = (value, fetchedAt) => {
20
+ const canonicalFetchedAt = canonicalSubscriptionUsageInstant(fetchedAt);
21
+ if (canonicalFetchedAt === void 0) return Effect.fail(ProviderSubscriptionUsageResponseError.make({
22
+ provider: anthropicClaudeProviderId,
23
+ category: "invalid_response"
24
+ }));
25
+ return Schema.decodeUnknownEffect(AnthropicClaudeSubscriptionUsageWire)(value).pipe(Effect.map((decoded) => {
26
+ const windows = [];
27
+ const append = (wire, id) => {
28
+ if (!validSubscriptionUsagePercent(wire?.utilization)) return;
29
+ const resetValue = wire.resets_at === null ? void 0 : wire.resets_at;
30
+ const resetsAt = resetValue === void 0 ? void 0 : canonicalSubscriptionUsageInstant(resetValue);
31
+ windows.push(ProviderSubscriptionUsageWindow.make({
32
+ id,
33
+ usedPercent: wire.utilization,
34
+ ...resetsAt === void 0 ? {} : { resetsAt }
35
+ }));
36
+ };
37
+ append(decoded.five_hour, "five-hour");
38
+ append(decoded.seven_day, "seven-day");
39
+ return ProviderSubscriptionUsageSnapshot.make({
40
+ provider: anthropicClaudeProviderId,
41
+ fetchedAt: canonicalFetchedAt,
42
+ windows: Chunk.fromIterable(windows)
43
+ });
44
+ }), Effect.mapError(() => ProviderSubscriptionUsageResponseError.make({
45
+ provider: anthropicClaudeProviderId,
46
+ category: "invalid_response"
47
+ })), Effect.withSpan("AnthropicClaudeSubscriptionUsage.parse"));
48
+ };
49
+ const fetchAnthropicClaudeSubscriptionUsage = (token, options = {}) => Effect.gen(function* () {
50
+ if (token.provider !== "anthropic-claude") return yield* Effect.fail(ProviderSubscriptionUsageConfigurationError.make({
51
+ provider: anthropicClaudeProviderId,
52
+ reason: "provider_mismatch"
53
+ }));
54
+ const requestTimeoutMs = options.requestTimeoutMs ?? 1e4;
55
+ if (!validProviderSubscriptionUsageTimeout(requestTimeoutMs)) return yield* Effect.fail(ProviderSubscriptionUsageConfigurationError.make({
56
+ provider: anthropicClaudeProviderId,
57
+ reason: "invalid_request_timeout"
58
+ }));
59
+ return yield* parseAnthropicClaudeSubscriptionUsage(yield* readProviderSubscriptionUsageJson(anthropicClaudeProviderId, yield* executeProviderSubscriptionUsageRequest({
60
+ provider: anthropicClaudeProviderId,
61
+ client: yield* HttpClient.HttpClient,
62
+ request: HttpClientRequest.get(anthropicClaudeSubscriptionUsageUrl).pipe(HttpClientRequest.setHeaders({
63
+ accept: "application/json",
64
+ ...anthropicClaudeAuthorizationHeaders(token),
65
+ "anthropic-beta": "oauth-2025-04-20"
66
+ })),
67
+ timeoutMs: requestTimeoutMs
68
+ })), new Date(yield* Clock.currentTimeMillis).toISOString());
69
+ }).pipe(Effect.withSpan("AnthropicClaudeSubscriptionUsage.fetch"));
70
+ //#endregion
71
+ export { anthropicClaudeSubscriptionUsageUrl, fetchAnthropicClaudeSubscriptionUsage, parseAnthropicClaudeSubscriptionUsage };
72
+
73
+ //# sourceMappingURL=usage.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"usage.mjs","names":[],"sources":["../../../src/providers/anthropic/usage.ts"],"sourcesContent":["import { Chunk, Clock, Effect } from 'effect'\nimport * as Schema from 'effect/Schema'\nimport { HttpClient, HttpClientRequest } from 'effect/unstable/http'\nimport type { OAuthAccessToken } from '@yolk-sdk/agent/oauth'\nimport { anthropicClaudeAuthorizationHeaders, anthropicClaudeProviderId } from './claude.ts'\nimport {\n canonicalSubscriptionUsageInstant,\n defaultProviderSubscriptionUsageTimeoutMs,\n executeProviderSubscriptionUsageRequest,\n readProviderSubscriptionUsageJson,\n validProviderSubscriptionUsageTimeout,\n validSubscriptionUsagePercent\n} from '../subscription-usage-internal.ts'\nimport {\n ProviderSubscriptionUsageConfigurationError,\n type ProviderSubscriptionUsageError,\n ProviderSubscriptionUsageResponseError,\n ProviderSubscriptionUsageSnapshot,\n ProviderSubscriptionUsageWindow\n} from '../subscription-usage.ts'\n\nexport const anthropicClaudeSubscriptionUsageUrl = 'https://api.anthropic.com/api/oauth/usage'\n\nexport type AnthropicClaudeSubscriptionUsageOptions = {\n readonly requestTimeoutMs?: number\n}\n\nconst NullableNumber = Schema.NullOr(Schema.Number)\nconst NullableString = Schema.NullOr(Schema.String)\n\nclass AnthropicClaudeSubscriptionUsageWindowWire extends Schema.Class<AnthropicClaudeSubscriptionUsageWindowWire>(\n 'AnthropicClaudeSubscriptionUsageWindowWire'\n)({\n utilization: Schema.optional(NullableNumber),\n resets_at: Schema.optional(NullableString)\n}) {}\n\nclass AnthropicClaudeSubscriptionUsageWire extends Schema.Class<AnthropicClaudeSubscriptionUsageWire>(\n 'AnthropicClaudeSubscriptionUsageWire'\n)({\n five_hour: Schema.optional(Schema.NullOr(AnthropicClaudeSubscriptionUsageWindowWire)),\n seven_day: Schema.optional(Schema.NullOr(AnthropicClaudeSubscriptionUsageWindowWire))\n}) {}\n\nexport const parseAnthropicClaudeSubscriptionUsage = (\n value: unknown,\n fetchedAt: string\n): Effect.Effect<ProviderSubscriptionUsageSnapshot, ProviderSubscriptionUsageResponseError> => {\n const canonicalFetchedAt = canonicalSubscriptionUsageInstant(fetchedAt)\n\n if (canonicalFetchedAt === undefined) {\n return Effect.fail(\n ProviderSubscriptionUsageResponseError.make({\n provider: anthropicClaudeProviderId,\n category: 'invalid_response'\n })\n )\n }\n\n return Schema.decodeUnknownEffect(AnthropicClaudeSubscriptionUsageWire)(value).pipe(\n Effect.map(decoded => {\n const windows: Array<ProviderSubscriptionUsageWindow> = []\n const append = (\n wire: AnthropicClaudeSubscriptionUsageWindowWire | null | undefined,\n id: 'five-hour' | 'seven-day'\n ) => {\n if (!validSubscriptionUsagePercent(wire?.utilization)) {\n return\n }\n\n const resetValue = wire.resets_at === null ? undefined : wire.resets_at\n const resetsAt =\n resetValue === undefined ? undefined : canonicalSubscriptionUsageInstant(resetValue)\n\n windows.push(\n ProviderSubscriptionUsageWindow.make({\n id,\n usedPercent: wire.utilization,\n ...(resetsAt === undefined ? {} : { resetsAt })\n })\n )\n }\n\n append(decoded.five_hour, 'five-hour')\n append(decoded.seven_day, 'seven-day')\n\n return ProviderSubscriptionUsageSnapshot.make({\n provider: anthropicClaudeProviderId,\n fetchedAt: canonicalFetchedAt,\n windows: Chunk.fromIterable(windows)\n })\n }),\n Effect.mapError(() =>\n ProviderSubscriptionUsageResponseError.make({\n provider: anthropicClaudeProviderId,\n category: 'invalid_response'\n })\n ),\n Effect.withSpan('AnthropicClaudeSubscriptionUsage.parse')\n )\n}\n\nexport const fetchAnthropicClaudeSubscriptionUsage = (\n token: OAuthAccessToken,\n options: AnthropicClaudeSubscriptionUsageOptions = {}\n): Effect.Effect<\n ProviderSubscriptionUsageSnapshot,\n ProviderSubscriptionUsageError,\n HttpClient.HttpClient\n> =>\n Effect.gen(function* () {\n if (token.provider !== anthropicClaudeProviderId) {\n return yield* Effect.fail(\n ProviderSubscriptionUsageConfigurationError.make({\n provider: anthropicClaudeProviderId,\n reason: 'provider_mismatch'\n })\n )\n }\n\n const requestTimeoutMs = options.requestTimeoutMs ?? defaultProviderSubscriptionUsageTimeoutMs\n\n if (!validProviderSubscriptionUsageTimeout(requestTimeoutMs)) {\n return yield* Effect.fail(\n ProviderSubscriptionUsageConfigurationError.make({\n provider: anthropicClaudeProviderId,\n reason: 'invalid_request_timeout'\n })\n )\n }\n\n const client = yield* HttpClient.HttpClient\n const request = HttpClientRequest.get(anthropicClaudeSubscriptionUsageUrl).pipe(\n HttpClientRequest.setHeaders({\n accept: 'application/json',\n ...anthropicClaudeAuthorizationHeaders(token),\n 'anthropic-beta': 'oauth-2025-04-20'\n })\n )\n const response = yield* executeProviderSubscriptionUsageRequest({\n provider: anthropicClaudeProviderId,\n client,\n request,\n timeoutMs: requestTimeoutMs\n })\n const json = yield* readProviderSubscriptionUsageJson(anthropicClaudeProviderId, response)\n const fetchedAt = new Date(yield* Clock.currentTimeMillis).toISOString()\n\n return yield* parseAnthropicClaudeSubscriptionUsage(json, fetchedAt)\n }).pipe(Effect.withSpan('AnthropicClaudeSubscriptionUsage.fetch'))\n"],"mappings":";;;;;;;AAqBA,MAAa,sCAAsC;AAMnD,MAAM,iBAAiB,OAAO,OAAO,OAAO,MAAM;AAClD,MAAM,iBAAiB,OAAO,OAAO,OAAO,MAAM;AAElD,IAAM,6CAAN,cAAyD,OAAO,MAC9D,4CACF,EAAE;CACA,aAAa,OAAO,SAAS,cAAc;CAC3C,WAAW,OAAO,SAAS,cAAc;AAC3C,CAAC,EAAE,CAAC;AAEJ,IAAM,uCAAN,cAAmD,OAAO,MACxD,sCACF,EAAE;CACA,WAAW,OAAO,SAAS,OAAO,OAAO,0CAA0C,CAAC;CACpF,WAAW,OAAO,SAAS,OAAO,OAAO,0CAA0C,CAAC;AACtF,CAAC,EAAE,CAAC;AAEJ,MAAa,yCACX,OACA,cAC6F;CAC7F,MAAM,qBAAqB,kCAAkC,SAAS;CAEtE,IAAI,uBAAuB,KAAA,GACzB,OAAO,OAAO,KACZ,uCAAuC,KAAK;EAC1C,UAAU;EACV,UAAU;CACZ,CAAC,CACH;CAGF,OAAO,OAAO,oBAAoB,oCAAoC,EAAE,KAAK,EAAE,KAC7E,OAAO,KAAI,YAAW;EACpB,MAAM,UAAkD,CAAC;EACzD,MAAM,UACJ,MACA,OACG;GACH,IAAI,CAAC,8BAA8B,MAAM,WAAW,GAClD;GAGF,MAAM,aAAa,KAAK,cAAc,OAAO,KAAA,IAAY,KAAK;GAC9D,MAAM,WACJ,eAAe,KAAA,IAAY,KAAA,IAAY,kCAAkC,UAAU;GAErF,QAAQ,KACN,gCAAgC,KAAK;IACnC;IACA,aAAa,KAAK;IAClB,GAAI,aAAa,KAAA,IAAY,CAAC,IAAI,EAAE,SAAS;GAC/C,CAAC,CACH;EACF;EAEA,OAAO,QAAQ,WAAW,WAAW;EACrC,OAAO,QAAQ,WAAW,WAAW;EAErC,OAAO,kCAAkC,KAAK;GAC5C,UAAU;GACV,WAAW;GACX,SAAS,MAAM,aAAa,OAAO;EACrC,CAAC;CACH,CAAC,GACD,OAAO,eACL,uCAAuC,KAAK;EAC1C,UAAU;EACV,UAAU;CACZ,CAAC,CACH,GACA,OAAO,SAAS,wCAAwC,CAC1D;AACF;AAEA,MAAa,yCACX,OACA,UAAmD,CAAC,MAMpD,OAAO,IAAI,aAAa;CACtB,IAAI,MAAM,aAAA,oBACR,OAAO,OAAO,OAAO,KACnB,4CAA4C,KAAK;EAC/C,UAAU;EACV,QAAQ;CACV,CAAC,CACH;CAGF,MAAM,mBAAmB,QAAQ,oBAAA;CAEjC,IAAI,CAAC,sCAAsC,gBAAgB,GACzD,OAAO,OAAO,OAAO,KACnB,4CAA4C,KAAK;EAC/C,UAAU;EACV,QAAQ;CACV,CAAC,CACH;CAoBF,OAAO,OAAO,sCAAsC,OAHhC,kCAAkC,2BAA2B,OANzD,wCAAwC;EAC9D,UAAU;EACV,QAAA,OAVoB,WAAW;EAW/B,SAVc,kBAAkB,IAAI,mCAAmC,EAAE,KACzE,kBAAkB,WAAW;GAC3B,QAAQ;GACR,GAAG,oCAAoC,KAAK;GAC5C,kBAAkB;EACpB,CAAC,CAKK;EACN,WAAW;CACb,CAAC,CACwF,GACvE,IAAI,KAAK,OAAO,MAAM,iBAAiB,EAAE,YAEO,CAAC;AACrE,CAAC,EAAE,KAAK,OAAO,SAAS,wCAAwC,CAAC"}
@@ -0,0 +1,15 @@
1
+ import { ProviderSubscriptionUsageError, ProviderSubscriptionUsageResponseError, ProviderSubscriptionUsageSnapshot } from "../subscription-usage.mjs";
2
+ import { Effect } from "effect";
3
+ import { HttpClient } from "effect/unstable/http";
4
+ import { OAuthAccessToken } from "@yolk-sdk/agent/oauth";
5
+
6
+ //#region src/providers/openai/codex-usage.d.ts
7
+ declare const openAiCodexSubscriptionUsageUrl = "https://chatgpt.com/backend-api/wham/usage";
8
+ type OpenAiCodexSubscriptionUsageOptions = {
9
+ readonly requestTimeoutMs?: number;
10
+ };
11
+ declare const parseOpenAiCodexSubscriptionUsage: (value: unknown, fetchedAt: string) => Effect.Effect<ProviderSubscriptionUsageSnapshot, ProviderSubscriptionUsageResponseError>;
12
+ declare const fetchOpenAiCodexSubscriptionUsage: (token: OAuthAccessToken, options?: OpenAiCodexSubscriptionUsageOptions) => Effect.Effect<ProviderSubscriptionUsageSnapshot, ProviderSubscriptionUsageError, HttpClient.HttpClient>;
13
+ //#endregion
14
+ export { OpenAiCodexSubscriptionUsageOptions, fetchOpenAiCodexSubscriptionUsage, openAiCodexSubscriptionUsageUrl, parseOpenAiCodexSubscriptionUsage };
15
+ //# sourceMappingURL=codex-usage.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"codex-usage.d.mts","names":[],"sources":["../../../src/providers/openai/codex-usage.ts"],"mappings":";;;;;;cAsBa,+BAAA;AAAA,KAED,mCAAA;EAAA,SACD,gBAAgB;AAAA;AAAA,cA2Bd,iCAAA,GACX,KAAA,WACA,SAAA,aACC,MAAA,CAAO,MAAA,CAAO,iCAAA,EAAmC,sCAAA;AAAA,cAiEvC,iCAAA,GACX,KAAA,EAAO,gBAAA,EACP,OAAA,GAAS,mCAAA,KACR,MAAA,CAAO,MAAA,CACR,iCAAA,EACA,8BAAA,EACA,UAAA,CAAW,UAAA"}
@@ -0,0 +1,84 @@
1
+ import { ProviderSubscriptionUsageConfigurationError, ProviderSubscriptionUsageResponseError, ProviderSubscriptionUsageSnapshot, ProviderSubscriptionUsageWindow } from "../subscription-usage.mjs";
2
+ import { canonicalSubscriptionUsageInstant, executeProviderSubscriptionUsageRequest, positiveSubscriptionUsageNumber, readProviderSubscriptionUsageJson, validProviderSubscriptionUsageTimeout, validSubscriptionUsagePercent } from "../subscription-usage-internal.mjs";
3
+ import { openAiCodexProviderId } from "./codex.mjs";
4
+ import { Chunk, Clock, Effect } from "effect";
5
+ import { HttpClient, HttpClientRequest } from "effect/unstable/http";
6
+ import * as Schema from "effect/Schema";
7
+ //#region src/providers/openai/codex-usage.ts
8
+ const openAiCodexSubscriptionUsageUrl = "https://chatgpt.com/backend-api/wham/usage";
9
+ const NullableNumber = Schema.NullOr(Schema.Number);
10
+ var OpenAiCodexSubscriptionUsageWindowWire = class extends Schema.Class("OpenAiCodexSubscriptionUsageWindowWire")({
11
+ used_percent: Schema.optional(NullableNumber),
12
+ limit_window_seconds: Schema.optional(NullableNumber),
13
+ reset_after_seconds: Schema.optional(NullableNumber),
14
+ reset_at: Schema.optional(NullableNumber)
15
+ }) {};
16
+ var OpenAiCodexSubscriptionRateLimitWire = class extends Schema.Class("OpenAiCodexSubscriptionRateLimitWire")({
17
+ primary_window: Schema.optional(Schema.NullOr(OpenAiCodexSubscriptionUsageWindowWire)),
18
+ secondary_window: Schema.optional(Schema.NullOr(OpenAiCodexSubscriptionUsageWindowWire))
19
+ }) {};
20
+ var OpenAiCodexSubscriptionUsageWire = class extends Schema.Class("OpenAiCodexSubscriptionUsageWire")({ rate_limit: Schema.optional(Schema.NullOr(OpenAiCodexSubscriptionRateLimitWire)) }) {};
21
+ const parseOpenAiCodexSubscriptionUsage = (value, fetchedAt) => {
22
+ const canonicalFetchedAt = canonicalSubscriptionUsageInstant(fetchedAt);
23
+ if (canonicalFetchedAt === void 0) return Effect.fail(ProviderSubscriptionUsageResponseError.make({
24
+ provider: openAiCodexProviderId,
25
+ category: "invalid_response"
26
+ }));
27
+ return Schema.decodeUnknownEffect(OpenAiCodexSubscriptionUsageWire)(value).pipe(Effect.map((decoded) => {
28
+ const windows = [];
29
+ const append = (wire, id) => {
30
+ if (!validSubscriptionUsagePercent(wire?.used_percent)) return;
31
+ const resetAt = wire.reset_at;
32
+ const resetsAt = resetAt === null || resetAt === void 0 ? void 0 : canonicalSubscriptionUsageInstant(resetAt);
33
+ const resetsAfterSeconds = positiveSubscriptionUsageNumber(wire.reset_after_seconds) ? wire.reset_after_seconds : void 0;
34
+ const windowDurationMinutes = positiveSubscriptionUsageNumber(wire.limit_window_seconds) ? wire.limit_window_seconds / 60 : void 0;
35
+ windows.push(ProviderSubscriptionUsageWindow.make({
36
+ id,
37
+ usedPercent: wire.used_percent,
38
+ ...resetsAt === void 0 ? {} : { resetsAt },
39
+ ...resetsAfterSeconds === void 0 ? {} : { resetsAfterSeconds },
40
+ ...windowDurationMinutes === void 0 ? {} : { windowDurationMinutes }
41
+ }));
42
+ };
43
+ append(decoded.rate_limit?.primary_window, "primary");
44
+ append(decoded.rate_limit?.secondary_window, "secondary");
45
+ return ProviderSubscriptionUsageSnapshot.make({
46
+ provider: openAiCodexProviderId,
47
+ fetchedAt: canonicalFetchedAt,
48
+ windows: Chunk.fromIterable(windows)
49
+ });
50
+ }), Effect.mapError(() => ProviderSubscriptionUsageResponseError.make({
51
+ provider: openAiCodexProviderId,
52
+ category: "invalid_response"
53
+ })), Effect.withSpan("OpenAiCodexSubscriptionUsage.parse"));
54
+ };
55
+ const fetchOpenAiCodexSubscriptionUsage = (token, options = {}) => Effect.gen(function* () {
56
+ if (token.provider !== "openai-codex") return yield* Effect.fail(ProviderSubscriptionUsageConfigurationError.make({
57
+ provider: openAiCodexProviderId,
58
+ reason: "provider_mismatch"
59
+ }));
60
+ const accountId = token.accountId?.trim();
61
+ if (accountId === void 0 || accountId.length === 0) return yield* Effect.fail(ProviderSubscriptionUsageConfigurationError.make({
62
+ provider: openAiCodexProviderId,
63
+ reason: "missing_account_id"
64
+ }));
65
+ const requestTimeoutMs = options.requestTimeoutMs ?? 1e4;
66
+ if (!validProviderSubscriptionUsageTimeout(requestTimeoutMs)) return yield* Effect.fail(ProviderSubscriptionUsageConfigurationError.make({
67
+ provider: openAiCodexProviderId,
68
+ reason: "invalid_request_timeout"
69
+ }));
70
+ return yield* parseOpenAiCodexSubscriptionUsage(yield* readProviderSubscriptionUsageJson(openAiCodexProviderId, yield* executeProviderSubscriptionUsageRequest({
71
+ provider: openAiCodexProviderId,
72
+ client: yield* HttpClient.HttpClient,
73
+ request: HttpClientRequest.get(openAiCodexSubscriptionUsageUrl).pipe(HttpClientRequest.setHeaders({
74
+ accept: "application/json",
75
+ authorization: `Bearer ${token.accessToken}`,
76
+ "chatgpt-account-id": accountId
77
+ })),
78
+ timeoutMs: requestTimeoutMs
79
+ })), new Date(yield* Clock.currentTimeMillis).toISOString());
80
+ }).pipe(Effect.withSpan("OpenAiCodexSubscriptionUsage.fetch"));
81
+ //#endregion
82
+ export { fetchOpenAiCodexSubscriptionUsage, openAiCodexSubscriptionUsageUrl, parseOpenAiCodexSubscriptionUsage };
83
+
84
+ //# sourceMappingURL=codex-usage.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"codex-usage.mjs","names":[],"sources":["../../../src/providers/openai/codex-usage.ts"],"sourcesContent":["import { Chunk, Clock, Effect } from 'effect'\nimport * as Schema from 'effect/Schema'\nimport { HttpClient, HttpClientRequest } from 'effect/unstable/http'\nimport type { OAuthAccessToken } from '@yolk-sdk/agent/oauth'\nimport { openAiCodexProviderId } from './codex.ts'\nimport {\n canonicalSubscriptionUsageInstant,\n defaultProviderSubscriptionUsageTimeoutMs,\n executeProviderSubscriptionUsageRequest,\n positiveSubscriptionUsageNumber,\n readProviderSubscriptionUsageJson,\n validProviderSubscriptionUsageTimeout,\n validSubscriptionUsagePercent\n} from '../subscription-usage-internal.ts'\nimport {\n ProviderSubscriptionUsageConfigurationError,\n type ProviderSubscriptionUsageError,\n ProviderSubscriptionUsageResponseError,\n ProviderSubscriptionUsageSnapshot,\n ProviderSubscriptionUsageWindow\n} from '../subscription-usage.ts'\n\nexport const openAiCodexSubscriptionUsageUrl = 'https://chatgpt.com/backend-api/wham/usage'\n\nexport type OpenAiCodexSubscriptionUsageOptions = {\n readonly requestTimeoutMs?: number\n}\n\nconst NullableNumber = Schema.NullOr(Schema.Number)\n\nclass OpenAiCodexSubscriptionUsageWindowWire extends Schema.Class<OpenAiCodexSubscriptionUsageWindowWire>(\n 'OpenAiCodexSubscriptionUsageWindowWire'\n)({\n used_percent: Schema.optional(NullableNumber),\n limit_window_seconds: Schema.optional(NullableNumber),\n reset_after_seconds: Schema.optional(NullableNumber),\n reset_at: Schema.optional(NullableNumber)\n}) {}\n\nclass OpenAiCodexSubscriptionRateLimitWire extends Schema.Class<OpenAiCodexSubscriptionRateLimitWire>(\n 'OpenAiCodexSubscriptionRateLimitWire'\n)({\n primary_window: Schema.optional(Schema.NullOr(OpenAiCodexSubscriptionUsageWindowWire)),\n secondary_window: Schema.optional(Schema.NullOr(OpenAiCodexSubscriptionUsageWindowWire))\n}) {}\n\nclass OpenAiCodexSubscriptionUsageWire extends Schema.Class<OpenAiCodexSubscriptionUsageWire>(\n 'OpenAiCodexSubscriptionUsageWire'\n)({\n rate_limit: Schema.optional(Schema.NullOr(OpenAiCodexSubscriptionRateLimitWire))\n}) {}\n\nexport const parseOpenAiCodexSubscriptionUsage = (\n value: unknown,\n fetchedAt: string\n): Effect.Effect<ProviderSubscriptionUsageSnapshot, ProviderSubscriptionUsageResponseError> => {\n const canonicalFetchedAt = canonicalSubscriptionUsageInstant(fetchedAt)\n\n if (canonicalFetchedAt === undefined) {\n return Effect.fail(\n ProviderSubscriptionUsageResponseError.make({\n provider: openAiCodexProviderId,\n category: 'invalid_response'\n })\n )\n }\n\n return Schema.decodeUnknownEffect(OpenAiCodexSubscriptionUsageWire)(value).pipe(\n Effect.map(decoded => {\n const windows: Array<ProviderSubscriptionUsageWindow> = []\n const append = (\n wire: OpenAiCodexSubscriptionUsageWindowWire | null | undefined,\n id: 'primary' | 'secondary'\n ) => {\n if (!validSubscriptionUsagePercent(wire?.used_percent)) {\n return\n }\n\n const resetAt = wire.reset_at\n const resetsAt =\n resetAt === null || resetAt === undefined\n ? undefined\n : canonicalSubscriptionUsageInstant(resetAt)\n const resetsAfterSeconds = positiveSubscriptionUsageNumber(wire.reset_after_seconds)\n ? wire.reset_after_seconds\n : undefined\n const windowDurationMinutes = positiveSubscriptionUsageNumber(wire.limit_window_seconds)\n ? wire.limit_window_seconds / 60\n : undefined\n\n windows.push(\n ProviderSubscriptionUsageWindow.make({\n id,\n usedPercent: wire.used_percent,\n ...(resetsAt === undefined ? {} : { resetsAt }),\n ...(resetsAfterSeconds === undefined ? {} : { resetsAfterSeconds }),\n ...(windowDurationMinutes === undefined ? {} : { windowDurationMinutes })\n })\n )\n }\n\n append(decoded.rate_limit?.primary_window, 'primary')\n append(decoded.rate_limit?.secondary_window, 'secondary')\n\n return ProviderSubscriptionUsageSnapshot.make({\n provider: openAiCodexProviderId,\n fetchedAt: canonicalFetchedAt,\n windows: Chunk.fromIterable(windows)\n })\n }),\n Effect.mapError(() =>\n ProviderSubscriptionUsageResponseError.make({\n provider: openAiCodexProviderId,\n category: 'invalid_response'\n })\n ),\n Effect.withSpan('OpenAiCodexSubscriptionUsage.parse')\n )\n}\n\nexport const fetchOpenAiCodexSubscriptionUsage = (\n token: OAuthAccessToken,\n options: OpenAiCodexSubscriptionUsageOptions = {}\n): Effect.Effect<\n ProviderSubscriptionUsageSnapshot,\n ProviderSubscriptionUsageError,\n HttpClient.HttpClient\n> =>\n Effect.gen(function* () {\n if (token.provider !== openAiCodexProviderId) {\n return yield* Effect.fail(\n ProviderSubscriptionUsageConfigurationError.make({\n provider: openAiCodexProviderId,\n reason: 'provider_mismatch'\n })\n )\n }\n\n const accountId = token.accountId?.trim()\n\n if (accountId === undefined || accountId.length === 0) {\n return yield* Effect.fail(\n ProviderSubscriptionUsageConfigurationError.make({\n provider: openAiCodexProviderId,\n reason: 'missing_account_id'\n })\n )\n }\n\n const requestTimeoutMs = options.requestTimeoutMs ?? defaultProviderSubscriptionUsageTimeoutMs\n\n if (!validProviderSubscriptionUsageTimeout(requestTimeoutMs)) {\n return yield* Effect.fail(\n ProviderSubscriptionUsageConfigurationError.make({\n provider: openAiCodexProviderId,\n reason: 'invalid_request_timeout'\n })\n )\n }\n\n const client = yield* HttpClient.HttpClient\n const request = HttpClientRequest.get(openAiCodexSubscriptionUsageUrl).pipe(\n HttpClientRequest.setHeaders({\n accept: 'application/json',\n authorization: `Bearer ${token.accessToken}`,\n 'chatgpt-account-id': accountId\n })\n )\n const response = yield* executeProviderSubscriptionUsageRequest({\n provider: openAiCodexProviderId,\n client,\n request,\n timeoutMs: requestTimeoutMs\n })\n const json = yield* readProviderSubscriptionUsageJson(openAiCodexProviderId, response)\n const fetchedAt = new Date(yield* Clock.currentTimeMillis).toISOString()\n\n return yield* parseOpenAiCodexSubscriptionUsage(json, fetchedAt)\n }).pipe(Effect.withSpan('OpenAiCodexSubscriptionUsage.fetch'))\n"],"mappings":";;;;;;;AAsBA,MAAa,kCAAkC;AAM/C,MAAM,iBAAiB,OAAO,OAAO,OAAO,MAAM;AAElD,IAAM,yCAAN,cAAqD,OAAO,MAC1D,wCACF,EAAE;CACA,cAAc,OAAO,SAAS,cAAc;CAC5C,sBAAsB,OAAO,SAAS,cAAc;CACpD,qBAAqB,OAAO,SAAS,cAAc;CACnD,UAAU,OAAO,SAAS,cAAc;AAC1C,CAAC,EAAE,CAAC;AAEJ,IAAM,uCAAN,cAAmD,OAAO,MACxD,sCACF,EAAE;CACA,gBAAgB,OAAO,SAAS,OAAO,OAAO,sCAAsC,CAAC;CACrF,kBAAkB,OAAO,SAAS,OAAO,OAAO,sCAAsC,CAAC;AACzF,CAAC,EAAE,CAAC;AAEJ,IAAM,mCAAN,cAA+C,OAAO,MACpD,kCACF,EAAE,EACA,YAAY,OAAO,SAAS,OAAO,OAAO,oCAAoC,CAAC,EACjF,CAAC,EAAE,CAAC;AAEJ,MAAa,qCACX,OACA,cAC6F;CAC7F,MAAM,qBAAqB,kCAAkC,SAAS;CAEtE,IAAI,uBAAuB,KAAA,GACzB,OAAO,OAAO,KACZ,uCAAuC,KAAK;EAC1C,UAAU;EACV,UAAU;CACZ,CAAC,CACH;CAGF,OAAO,OAAO,oBAAoB,gCAAgC,EAAE,KAAK,EAAE,KACzE,OAAO,KAAI,YAAW;EACpB,MAAM,UAAkD,CAAC;EACzD,MAAM,UACJ,MACA,OACG;GACH,IAAI,CAAC,8BAA8B,MAAM,YAAY,GACnD;GAGF,MAAM,UAAU,KAAK;GACrB,MAAM,WACJ,YAAY,QAAQ,YAAY,KAAA,IAC5B,KAAA,IACA,kCAAkC,OAAO;GAC/C,MAAM,qBAAqB,gCAAgC,KAAK,mBAAmB,IAC/E,KAAK,sBACL,KAAA;GACJ,MAAM,wBAAwB,gCAAgC,KAAK,oBAAoB,IACnF,KAAK,uBAAuB,KAC5B,KAAA;GAEJ,QAAQ,KACN,gCAAgC,KAAK;IACnC;IACA,aAAa,KAAK;IAClB,GAAI,aAAa,KAAA,IAAY,CAAC,IAAI,EAAE,SAAS;IAC7C,GAAI,uBAAuB,KAAA,IAAY,CAAC,IAAI,EAAE,mBAAmB;IACjE,GAAI,0BAA0B,KAAA,IAAY,CAAC,IAAI,EAAE,sBAAsB;GACzE,CAAC,CACH;EACF;EAEA,OAAO,QAAQ,YAAY,gBAAgB,SAAS;EACpD,OAAO,QAAQ,YAAY,kBAAkB,WAAW;EAExD,OAAO,kCAAkC,KAAK;GAC5C,UAAU;GACV,WAAW;GACX,SAAS,MAAM,aAAa,OAAO;EACrC,CAAC;CACH,CAAC,GACD,OAAO,eACL,uCAAuC,KAAK;EAC1C,UAAU;EACV,UAAU;CACZ,CAAC,CACH,GACA,OAAO,SAAS,oCAAoC,CACtD;AACF;AAEA,MAAa,qCACX,OACA,UAA+C,CAAC,MAMhD,OAAO,IAAI,aAAa;CACtB,IAAI,MAAM,aAAA,gBACR,OAAO,OAAO,OAAO,KACnB,4CAA4C,KAAK;EAC/C,UAAU;EACV,QAAQ;CACV,CAAC,CACH;CAGF,MAAM,YAAY,MAAM,WAAW,KAAK;CAExC,IAAI,cAAc,KAAA,KAAa,UAAU,WAAW,GAClD,OAAO,OAAO,OAAO,KACnB,4CAA4C,KAAK;EAC/C,UAAU;EACV,QAAQ;CACV,CAAC,CACH;CAGF,MAAM,mBAAmB,QAAQ,oBAAA;CAEjC,IAAI,CAAC,sCAAsC,gBAAgB,GACzD,OAAO,OAAO,OAAO,KACnB,4CAA4C,KAAK;EAC/C,UAAU;EACV,QAAQ;CACV,CAAC,CACH;CAoBF,OAAO,OAAO,kCAAkC,OAH5B,kCAAkC,uBAAuB,OANrD,wCAAwC;EAC9D,UAAU;EACV,QAAA,OAVoB,WAAW;EAW/B,SAVc,kBAAkB,IAAI,+BAA+B,EAAE,KACrE,kBAAkB,WAAW;GAC3B,QAAQ;GACR,eAAe,UAAU,MAAM;GAC/B,sBAAsB;EACxB,CAAC,CAKK;EACN,WAAW;CACb,CAAC,CACoF,GACnE,IAAI,KAAK,OAAO,MAAM,iBAAiB,EAAE,YAEG,CAAC;AACjE,CAAC,EAAE,KAAK,OAAO,SAAS,oCAAoC,CAAC"}
@@ -0,0 +1,20 @@
1
+ import { ProviderSubscriptionUsageError, ProviderSubscriptionUsageResponseError } from "./subscription-usage.mjs";
2
+ import { Effect } from "effect";
3
+ import { HttpClient, HttpClientRequest, HttpClientResponse } from "effect/unstable/http";
4
+
5
+ //#region src/providers/subscription-usage-internal.d.ts
6
+ declare const defaultProviderSubscriptionUsageTimeoutMs = 10000;
7
+ declare const canonicalSubscriptionUsageInstant: (value: string | number) => string | undefined;
8
+ declare const validSubscriptionUsagePercent: (value: number | null | undefined) => value is number;
9
+ declare const positiveSubscriptionUsageNumber: (value: number | null | undefined) => value is number;
10
+ declare const validProviderSubscriptionUsageTimeout: (value: number) => boolean;
11
+ declare const executeProviderSubscriptionUsageRequest: (input: {
12
+ readonly provider: string;
13
+ readonly client: HttpClient.HttpClient;
14
+ readonly request: HttpClientRequest.HttpClientRequest;
15
+ readonly timeoutMs: number;
16
+ }) => Effect.Effect<HttpClientResponse.HttpClientResponse, ProviderSubscriptionUsageError, never>;
17
+ declare const readProviderSubscriptionUsageJson: (provider: string, response: HttpClientResponse.HttpClientResponse) => Effect.Effect<unknown, ProviderSubscriptionUsageResponseError, never>;
18
+ //#endregion
19
+ export { canonicalSubscriptionUsageInstant, defaultProviderSubscriptionUsageTimeoutMs, executeProviderSubscriptionUsageRequest, positiveSubscriptionUsageNumber, readProviderSubscriptionUsageJson, validProviderSubscriptionUsageTimeout, validSubscriptionUsagePercent };
20
+ //# sourceMappingURL=subscription-usage-internal.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"subscription-usage-internal.d.mts","names":[],"sources":["../../src/providers/subscription-usage-internal.ts"],"mappings":";;;;;cAiBa,yCAAA;AAAA,cAEA,iCAAA,GAAqC,KAAsB;AAAA,cAM3D,6BAAA,GAAiC,KAAA,gCAAmC,KAAK;AAAA,cAGzE,+BAAA,GACX,KAAA,gCACC,KAAK;AAAA,cAEK,qCAAA,GAAyC,KAAa;AAAA,cAetD,uCAAA,GAA2C,KAAA;EAAA,SAC7C,QAAA;EAAA,SACA,MAAA,EAAQ,UAAA,CAAW,UAAA;EAAA,SACnB,OAAA,EAAS,iBAAA,CAAkB,iBAAA;EAAA,SAC3B,SAAA;AAAA,MACV,MAAA,CAAA,MAAA,CAAA,kBAAA,CAAA,kBAAA,EAAA,8BAAA;AAAA,cAuCY,iCAAA,GACX,QAAA,UACA,QAAA,EAAU,kBAAA,CAAmB,kBAAA,KAAkB,MAAA,CAAA,MAAA,UAAA,sCAAA"}
@@ -0,0 +1,52 @@
1
+ import { retryAfterMsFromHeaders } from "./provider-error.mjs";
2
+ import { ProviderSubscriptionUsageAuthError, ProviderSubscriptionUsageRateLimitError, ProviderSubscriptionUsageRequestError, ProviderSubscriptionUsageResponseError } from "./subscription-usage.mjs";
3
+ import { Duration, Effect } from "effect";
4
+ import { FetchHttpClient, HttpClientResponse } from "effect/unstable/http";
5
+ import * as Schema from "effect/Schema";
6
+ //#region src/providers/subscription-usage-internal.ts
7
+ const defaultProviderSubscriptionUsageTimeoutMs = 1e4;
8
+ const canonicalSubscriptionUsageInstant = (value) => {
9
+ const date = new Date(typeof value === "number" ? value * 1e3 : value);
10
+ return Number.isFinite(date.getTime()) ? date.toISOString() : void 0;
11
+ };
12
+ const validSubscriptionUsagePercent = (value) => value !== null && value !== void 0 && Number.isFinite(value) && value >= 0 && value <= 100;
13
+ const positiveSubscriptionUsageNumber = (value) => value !== null && value !== void 0 && Number.isFinite(value) && value > 0;
14
+ const validProviderSubscriptionUsageTimeout = (value) => Number.isFinite(value) && value > 0;
15
+ const responseError = (provider, status) => {
16
+ if (status === 401 || status === 403) return ProviderSubscriptionUsageAuthError.make({
17
+ provider,
18
+ status
19
+ });
20
+ return ProviderSubscriptionUsageResponseError.make({
21
+ provider,
22
+ category: status >= 300 && status < 400 ? "redirect" : "http",
23
+ status
24
+ });
25
+ };
26
+ const executeProviderSubscriptionUsageRequest = (input) => Effect.gen(function* () {
27
+ const response = yield* input.client.execute(input.request).pipe(Effect.provideService(FetchHttpClient.RequestInit, { redirect: "manual" }), Effect.withTracerEnabled(false), Effect.mapError(() => ProviderSubscriptionUsageRequestError.make({
28
+ provider: input.provider,
29
+ category: "network"
30
+ })), Effect.timeoutOrElse({
31
+ duration: Duration.millis(input.timeoutMs),
32
+ orElse: () => Effect.fail(ProviderSubscriptionUsageRequestError.make({
33
+ provider: input.provider,
34
+ category: "timeout"
35
+ }))
36
+ }));
37
+ if (response.status === 429) return yield* Effect.fail(ProviderSubscriptionUsageRateLimitError.make({
38
+ provider: input.provider,
39
+ retryAfterMs: retryAfterMsFromHeaders(response.headers)
40
+ }));
41
+ if (response.status < 200 || response.status >= 300) return yield* Effect.fail(responseError(input.provider, response.status));
42
+ return response;
43
+ });
44
+ const readProviderSubscriptionUsageJson = (provider, response) => HttpClientResponse.schemaBodyJson(Schema.Unknown)(response).pipe(Effect.withTracerEnabled(false), Effect.mapError(() => ProviderSubscriptionUsageResponseError.make({
45
+ provider,
46
+ category: "invalid_response",
47
+ status: response.status
48
+ })));
49
+ //#endregion
50
+ export { canonicalSubscriptionUsageInstant, defaultProviderSubscriptionUsageTimeoutMs, executeProviderSubscriptionUsageRequest, positiveSubscriptionUsageNumber, readProviderSubscriptionUsageJson, validProviderSubscriptionUsageTimeout, validSubscriptionUsagePercent };
51
+
52
+ //# sourceMappingURL=subscription-usage-internal.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"subscription-usage-internal.mjs","names":[],"sources":["../../src/providers/subscription-usage-internal.ts"],"sourcesContent":["import { Duration, Effect } from 'effect'\nimport * as Schema from 'effect/Schema'\nimport {\n FetchHttpClient,\n HttpClientResponse,\n type HttpClient,\n type HttpClientRequest\n} from 'effect/unstable/http'\nimport { retryAfterMsFromHeaders } from './provider-error.ts'\nimport {\n ProviderSubscriptionUsageAuthError,\n type ProviderSubscriptionUsageError,\n ProviderSubscriptionUsageRateLimitError,\n ProviderSubscriptionUsageRequestError,\n ProviderSubscriptionUsageResponseError\n} from './subscription-usage.ts'\n\nexport const defaultProviderSubscriptionUsageTimeoutMs = 10_000\n\nexport const canonicalSubscriptionUsageInstant = (value: string | number): string | undefined => {\n const date = new Date(typeof value === 'number' ? value * 1000 : value)\n\n return Number.isFinite(date.getTime()) ? date.toISOString() : undefined\n}\n\nexport const validSubscriptionUsagePercent = (value: number | null | undefined): value is number =>\n value !== null && value !== undefined && Number.isFinite(value) && value >= 0 && value <= 100\n\nexport const positiveSubscriptionUsageNumber = (\n value: number | null | undefined\n): value is number => value !== null && value !== undefined && Number.isFinite(value) && value > 0\n\nexport const validProviderSubscriptionUsageTimeout = (value: number) =>\n Number.isFinite(value) && value > 0\n\nconst responseError = (provider: string, status: number): ProviderSubscriptionUsageError => {\n if (status === 401 || status === 403) {\n return ProviderSubscriptionUsageAuthError.make({ provider, status })\n }\n\n return ProviderSubscriptionUsageResponseError.make({\n provider,\n category: status >= 300 && status < 400 ? 'redirect' : 'http',\n status\n })\n}\n\nexport const executeProviderSubscriptionUsageRequest = (input: {\n readonly provider: string\n readonly client: HttpClient.HttpClient\n readonly request: HttpClientRequest.HttpClientRequest\n readonly timeoutMs: number\n}) =>\n Effect.gen(function* () {\n const response = yield* input.client.execute(input.request).pipe(\n Effect.provideService(FetchHttpClient.RequestInit, { redirect: 'manual' }),\n Effect.withTracerEnabled(false),\n Effect.mapError(() =>\n ProviderSubscriptionUsageRequestError.make({\n provider: input.provider,\n category: 'network'\n })\n ),\n Effect.timeoutOrElse({\n duration: Duration.millis(input.timeoutMs),\n orElse: () =>\n Effect.fail(\n ProviderSubscriptionUsageRequestError.make({\n provider: input.provider,\n category: 'timeout'\n })\n )\n })\n )\n\n if (response.status === 429) {\n return yield* Effect.fail(\n ProviderSubscriptionUsageRateLimitError.make({\n provider: input.provider,\n retryAfterMs: retryAfterMsFromHeaders(response.headers)\n })\n )\n }\n\n if (response.status < 200 || response.status >= 300) {\n return yield* Effect.fail(responseError(input.provider, response.status))\n }\n\n return response\n })\n\nexport const readProviderSubscriptionUsageJson = (\n provider: string,\n response: HttpClientResponse.HttpClientResponse\n) =>\n HttpClientResponse.schemaBodyJson(Schema.Unknown)(response).pipe(\n Effect.withTracerEnabled(false),\n Effect.mapError(() =>\n ProviderSubscriptionUsageResponseError.make({\n provider,\n category: 'invalid_response',\n status: response.status\n })\n )\n )\n"],"mappings":";;;;;;AAiBA,MAAa,4CAA4C;AAEzD,MAAa,qCAAqC,UAA+C;CAC/F,MAAM,OAAO,IAAI,KAAK,OAAO,UAAU,WAAW,QAAQ,MAAO,KAAK;CAEtE,OAAO,OAAO,SAAS,KAAK,QAAQ,CAAC,IAAI,KAAK,YAAY,IAAI,KAAA;AAChE;AAEA,MAAa,iCAAiC,UAC5C,UAAU,QAAQ,UAAU,KAAA,KAAa,OAAO,SAAS,KAAK,KAAK,SAAS,KAAK,SAAS;AAE5F,MAAa,mCACX,UACoB,UAAU,QAAQ,UAAU,KAAA,KAAa,OAAO,SAAS,KAAK,KAAK,QAAQ;AAEjG,MAAa,yCAAyC,UACpD,OAAO,SAAS,KAAK,KAAK,QAAQ;AAEpC,MAAM,iBAAiB,UAAkB,WAAmD;CAC1F,IAAI,WAAW,OAAO,WAAW,KAC/B,OAAO,mCAAmC,KAAK;EAAE;EAAU;CAAO,CAAC;CAGrE,OAAO,uCAAuC,KAAK;EACjD;EACA,UAAU,UAAU,OAAO,SAAS,MAAM,aAAa;EACvD;CACF,CAAC;AACH;AAEA,MAAa,2CAA2C,UAMtD,OAAO,IAAI,aAAa;CACtB,MAAM,WAAW,OAAO,MAAM,OAAO,QAAQ,MAAM,OAAO,EAAE,KAC1D,OAAO,eAAe,gBAAgB,aAAa,EAAE,UAAU,SAAS,CAAC,GACzE,OAAO,kBAAkB,KAAK,GAC9B,OAAO,eACL,sCAAsC,KAAK;EACzC,UAAU,MAAM;EAChB,UAAU;CACZ,CAAC,CACH,GACA,OAAO,cAAc;EACnB,UAAU,SAAS,OAAO,MAAM,SAAS;EACzC,cACE,OAAO,KACL,sCAAsC,KAAK;GACzC,UAAU,MAAM;GAChB,UAAU;EACZ,CAAC,CACH;CACJ,CAAC,CACH;CAEA,IAAI,SAAS,WAAW,KACtB,OAAO,OAAO,OAAO,KACnB,wCAAwC,KAAK;EAC3C,UAAU,MAAM;EAChB,cAAc,wBAAwB,SAAS,OAAO;CACxD,CAAC,CACH;CAGF,IAAI,SAAS,SAAS,OAAO,SAAS,UAAU,KAC9C,OAAO,OAAO,OAAO,KAAK,cAAc,MAAM,UAAU,SAAS,MAAM,CAAC;CAG1E,OAAO;AACT,CAAC;AAEH,MAAa,qCACX,UACA,aAEA,mBAAmB,eAAe,OAAO,OAAO,EAAE,QAAQ,EAAE,KAC1D,OAAO,kBAAkB,KAAK,GAC9B,OAAO,eACL,uCAAuC,KAAK;CAC1C;CACA,UAAU;CACV,QAAQ,SAAS;AACnB,CAAC,CACH,CACF"}
@@ -0,0 +1,49 @@
1
+ import * as Schema from "effect/Schema";
2
+
3
+ //#region src/providers/subscription-usage.d.ts
4
+ declare const ProviderSubscriptionUsageInstant: Schema.Trimmed;
5
+ declare const ProviderSubscriptionUsagePercent: Schema.Number;
6
+ declare const ProviderSubscriptionUsageWindow_base: Schema.Class<ProviderSubscriptionUsageWindow, Schema.Struct<{
7
+ readonly id: Schema.Trimmed;
8
+ readonly usedPercent: Schema.Number;
9
+ readonly resetsAt: Schema.optional<Schema.Trimmed>;
10
+ readonly resetsAfterSeconds: Schema.optional<Schema.Number>;
11
+ readonly windowDurationMinutes: Schema.optional<Schema.Number>;
12
+ }>, {}>;
13
+ declare class ProviderSubscriptionUsageWindow extends ProviderSubscriptionUsageWindow_base {}
14
+ declare const ProviderSubscriptionUsageSnapshot_base: Schema.Class<ProviderSubscriptionUsageSnapshot, Schema.Struct<{
15
+ readonly provider: Schema.Trimmed;
16
+ readonly fetchedAt: Schema.Trimmed;
17
+ readonly windows: Schema.Chunk<typeof ProviderSubscriptionUsageWindow>;
18
+ }>, {}>;
19
+ declare class ProviderSubscriptionUsageSnapshot extends ProviderSubscriptionUsageSnapshot_base {}
20
+ declare const ProviderSubscriptionUsageRequestError_base: Schema.Class<ProviderSubscriptionUsageRequestError, Schema.TaggedStruct<"ProviderSubscriptionUsageRequestError", {
21
+ readonly provider: Schema.String;
22
+ readonly category: Schema.Literals<readonly ["timeout", "network"]>;
23
+ }>, import("effect/Cause").YieldableError>;
24
+ declare class ProviderSubscriptionUsageRequestError extends ProviderSubscriptionUsageRequestError_base {}
25
+ declare const ProviderSubscriptionUsageResponseError_base: Schema.Class<ProviderSubscriptionUsageResponseError, Schema.TaggedStruct<"ProviderSubscriptionUsageResponseError", {
26
+ readonly provider: Schema.String;
27
+ readonly category: Schema.Literals<readonly ["http", "invalid_response", "redirect"]>;
28
+ readonly status: Schema.optional<Schema.Number>;
29
+ }>, import("effect/Cause").YieldableError>;
30
+ declare class ProviderSubscriptionUsageResponseError extends ProviderSubscriptionUsageResponseError_base {}
31
+ declare const ProviderSubscriptionUsageAuthError_base: Schema.Class<ProviderSubscriptionUsageAuthError, Schema.TaggedStruct<"ProviderSubscriptionUsageAuthError", {
32
+ readonly provider: Schema.String;
33
+ readonly status: Schema.Number;
34
+ }>, import("effect/Cause").YieldableError>;
35
+ declare class ProviderSubscriptionUsageAuthError extends ProviderSubscriptionUsageAuthError_base {}
36
+ declare const ProviderSubscriptionUsageRateLimitError_base: Schema.Class<ProviderSubscriptionUsageRateLimitError, Schema.TaggedStruct<"ProviderSubscriptionUsageRateLimitError", {
37
+ readonly provider: Schema.String;
38
+ readonly retryAfterMs: Schema.optional<Schema.Number>;
39
+ }>, import("effect/Cause").YieldableError>;
40
+ declare class ProviderSubscriptionUsageRateLimitError extends ProviderSubscriptionUsageRateLimitError_base {}
41
+ declare const ProviderSubscriptionUsageConfigurationError_base: Schema.Class<ProviderSubscriptionUsageConfigurationError, Schema.TaggedStruct<"ProviderSubscriptionUsageConfigurationError", {
42
+ readonly provider: Schema.String;
43
+ readonly reason: Schema.Literals<readonly ["invalid_request_timeout", "missing_account_id", "provider_mismatch"]>;
44
+ }>, import("effect/Cause").YieldableError>;
45
+ declare class ProviderSubscriptionUsageConfigurationError extends ProviderSubscriptionUsageConfigurationError_base {}
46
+ type ProviderSubscriptionUsageError = ProviderSubscriptionUsageRequestError | ProviderSubscriptionUsageResponseError | ProviderSubscriptionUsageAuthError | ProviderSubscriptionUsageRateLimitError | ProviderSubscriptionUsageConfigurationError;
47
+ //#endregion
48
+ export { ProviderSubscriptionUsageAuthError, ProviderSubscriptionUsageConfigurationError, ProviderSubscriptionUsageError, ProviderSubscriptionUsageInstant, ProviderSubscriptionUsagePercent, ProviderSubscriptionUsageRateLimitError, ProviderSubscriptionUsageRequestError, ProviderSubscriptionUsageResponseError, ProviderSubscriptionUsageSnapshot, ProviderSubscriptionUsageWindow };
49
+ //# sourceMappingURL=subscription-usage.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"subscription-usage.d.mts","names":[],"sources":["../../src/providers/subscription-usage.ts"],"mappings":";;;cAUa,gCAAA,EAAgC,MAAA,CAAA,OAM5C;AAAA,cAEY,gCAAA,EAAgC,MAAA,CAAA,MAG5C;AAAA,cAAA,oCAAA;;;;;;;cAEY,+BAAA,SAAwC,oCAQnD;AAAA,cAAG,sCAAA;;;;;cAEQ,iCAAA,SAA0C,sCAMrD;AAAA,cAAG,0CAAA;;;;cAEQ,qCAAA,SAA8C,0CAM1D;AAAA,cAAG,2CAAA;;;;;cAES,sCAAA,SAA+C,2CAO3D;AAAA,cAAG,uCAAA;;;;cAES,kCAAA,SAA2C,uCAMvD;AAAA,cAAG,4CAAA;;;;cAES,uCAAA,SAAgD,4CAM5D;AAAA,cAAG,gDAAA;;;;cAES,2CAAA,SAAoD,gDAMhE;AAAA,KAEW,8BAAA,GACR,qCAAA,GACA,sCAAA,GACA,kCAAA,GACA,uCAAA,GACA,2CAAA"}
@@ -0,0 +1,55 @@
1
+ import * as Schema from "effect/Schema";
2
+ //#region src/providers/subscription-usage.ts
3
+ const NonEmptyTrimmedString = Schema.Trimmed.pipe(Schema.check(Schema.isNonEmpty()));
4
+ const PositiveNumber = Schema.Number.pipe(Schema.check(Schema.isGreaterThan(0)));
5
+ const isCanonicalInstant = (value) => {
6
+ const date = new Date(value);
7
+ return Number.isFinite(date.getTime()) && date.toISOString() === value;
8
+ };
9
+ const ProviderSubscriptionUsageInstant = NonEmptyTrimmedString.pipe(Schema.check(Schema.makeFilter(isCanonicalInstant, { identifier: "ProviderSubscriptionUsageInstant" })));
10
+ const ProviderSubscriptionUsagePercent = Schema.Number.pipe(Schema.check(Schema.isGreaterThanOrEqualTo(0)), Schema.check(Schema.isLessThanOrEqualTo(100)));
11
+ var ProviderSubscriptionUsageWindow = class extends Schema.Class("ProviderSubscriptionUsageWindow")({
12
+ id: NonEmptyTrimmedString,
13
+ usedPercent: ProviderSubscriptionUsagePercent,
14
+ resetsAt: Schema.optional(ProviderSubscriptionUsageInstant),
15
+ resetsAfterSeconds: Schema.optional(PositiveNumber),
16
+ windowDurationMinutes: Schema.optional(PositiveNumber)
17
+ }) {};
18
+ var ProviderSubscriptionUsageSnapshot = class extends Schema.Class("ProviderSubscriptionUsageSnapshot")({
19
+ provider: NonEmptyTrimmedString,
20
+ fetchedAt: ProviderSubscriptionUsageInstant,
21
+ windows: Schema.Chunk(ProviderSubscriptionUsageWindow)
22
+ }) {};
23
+ var ProviderSubscriptionUsageRequestError = class extends Schema.TaggedErrorClass()("ProviderSubscriptionUsageRequestError", {
24
+ provider: Schema.String,
25
+ category: Schema.Literals(["timeout", "network"])
26
+ }) {};
27
+ var ProviderSubscriptionUsageResponseError = class extends Schema.TaggedErrorClass()("ProviderSubscriptionUsageResponseError", {
28
+ provider: Schema.String,
29
+ category: Schema.Literals([
30
+ "http",
31
+ "invalid_response",
32
+ "redirect"
33
+ ]),
34
+ status: Schema.optional(Schema.Number)
35
+ }) {};
36
+ var ProviderSubscriptionUsageAuthError = class extends Schema.TaggedErrorClass()("ProviderSubscriptionUsageAuthError", {
37
+ provider: Schema.String,
38
+ status: Schema.Number
39
+ }) {};
40
+ var ProviderSubscriptionUsageRateLimitError = class extends Schema.TaggedErrorClass()("ProviderSubscriptionUsageRateLimitError", {
41
+ provider: Schema.String,
42
+ retryAfterMs: Schema.optional(Schema.Number)
43
+ }) {};
44
+ var ProviderSubscriptionUsageConfigurationError = class extends Schema.TaggedErrorClass()("ProviderSubscriptionUsageConfigurationError", {
45
+ provider: Schema.String,
46
+ reason: Schema.Literals([
47
+ "invalid_request_timeout",
48
+ "missing_account_id",
49
+ "provider_mismatch"
50
+ ])
51
+ }) {};
52
+ //#endregion
53
+ export { ProviderSubscriptionUsageAuthError, ProviderSubscriptionUsageConfigurationError, ProviderSubscriptionUsageInstant, ProviderSubscriptionUsagePercent, ProviderSubscriptionUsageRateLimitError, ProviderSubscriptionUsageRequestError, ProviderSubscriptionUsageResponseError, ProviderSubscriptionUsageSnapshot, ProviderSubscriptionUsageWindow };
54
+
55
+ //# sourceMappingURL=subscription-usage.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"subscription-usage.mjs","names":[],"sources":["../../src/providers/subscription-usage.ts"],"sourcesContent":["import * as Schema from 'effect/Schema'\n\nconst NonEmptyTrimmedString = Schema.Trimmed.pipe(Schema.check(Schema.isNonEmpty()))\nconst PositiveNumber = Schema.Number.pipe(Schema.check(Schema.isGreaterThan(0)))\nconst isCanonicalInstant = (value: string) => {\n const date = new Date(value)\n\n return Number.isFinite(date.getTime()) && date.toISOString() === value\n}\n\nexport const ProviderSubscriptionUsageInstant = NonEmptyTrimmedString.pipe(\n Schema.check(\n Schema.makeFilter(isCanonicalInstant, {\n identifier: 'ProviderSubscriptionUsageInstant'\n })\n )\n)\n\nexport const ProviderSubscriptionUsagePercent = Schema.Number.pipe(\n Schema.check(Schema.isGreaterThanOrEqualTo(0)),\n Schema.check(Schema.isLessThanOrEqualTo(100))\n)\n\nexport class ProviderSubscriptionUsageWindow extends Schema.Class<ProviderSubscriptionUsageWindow>(\n 'ProviderSubscriptionUsageWindow'\n)({\n id: NonEmptyTrimmedString,\n usedPercent: ProviderSubscriptionUsagePercent,\n resetsAt: Schema.optional(ProviderSubscriptionUsageInstant),\n resetsAfterSeconds: Schema.optional(PositiveNumber),\n windowDurationMinutes: Schema.optional(PositiveNumber)\n}) {}\n\nexport class ProviderSubscriptionUsageSnapshot extends Schema.Class<ProviderSubscriptionUsageSnapshot>(\n 'ProviderSubscriptionUsageSnapshot'\n)({\n provider: NonEmptyTrimmedString,\n fetchedAt: ProviderSubscriptionUsageInstant,\n windows: Schema.Chunk(ProviderSubscriptionUsageWindow)\n}) {}\n\nexport class ProviderSubscriptionUsageRequestError extends Schema.TaggedErrorClass<ProviderSubscriptionUsageRequestError>()(\n 'ProviderSubscriptionUsageRequestError',\n {\n provider: Schema.String,\n category: Schema.Literals(['timeout', 'network'])\n }\n) {}\n\nexport class ProviderSubscriptionUsageResponseError extends Schema.TaggedErrorClass<ProviderSubscriptionUsageResponseError>()(\n 'ProviderSubscriptionUsageResponseError',\n {\n provider: Schema.String,\n category: Schema.Literals(['http', 'invalid_response', 'redirect']),\n status: Schema.optional(Schema.Number)\n }\n) {}\n\nexport class ProviderSubscriptionUsageAuthError extends Schema.TaggedErrorClass<ProviderSubscriptionUsageAuthError>()(\n 'ProviderSubscriptionUsageAuthError',\n {\n provider: Schema.String,\n status: Schema.Number\n }\n) {}\n\nexport class ProviderSubscriptionUsageRateLimitError extends Schema.TaggedErrorClass<ProviderSubscriptionUsageRateLimitError>()(\n 'ProviderSubscriptionUsageRateLimitError',\n {\n provider: Schema.String,\n retryAfterMs: Schema.optional(Schema.Number)\n }\n) {}\n\nexport class ProviderSubscriptionUsageConfigurationError extends Schema.TaggedErrorClass<ProviderSubscriptionUsageConfigurationError>()(\n 'ProviderSubscriptionUsageConfigurationError',\n {\n provider: Schema.String,\n reason: Schema.Literals(['invalid_request_timeout', 'missing_account_id', 'provider_mismatch'])\n }\n) {}\n\nexport type ProviderSubscriptionUsageError =\n | ProviderSubscriptionUsageRequestError\n | ProviderSubscriptionUsageResponseError\n | ProviderSubscriptionUsageAuthError\n | ProviderSubscriptionUsageRateLimitError\n | ProviderSubscriptionUsageConfigurationError\n"],"mappings":";;AAEA,MAAM,wBAAwB,OAAO,QAAQ,KAAK,OAAO,MAAM,OAAO,WAAW,CAAC,CAAC;AACnF,MAAM,iBAAiB,OAAO,OAAO,KAAK,OAAO,MAAM,OAAO,cAAc,CAAC,CAAC,CAAC;AAC/E,MAAM,sBAAsB,UAAkB;CAC5C,MAAM,OAAO,IAAI,KAAK,KAAK;CAE3B,OAAO,OAAO,SAAS,KAAK,QAAQ,CAAC,KAAK,KAAK,YAAY,MAAM;AACnE;AAEA,MAAa,mCAAmC,sBAAsB,KACpE,OAAO,MACL,OAAO,WAAW,oBAAoB,EACpC,YAAY,mCACd,CAAC,CACH,CACF;AAEA,MAAa,mCAAmC,OAAO,OAAO,KAC5D,OAAO,MAAM,OAAO,uBAAuB,CAAC,CAAC,GAC7C,OAAO,MAAM,OAAO,oBAAoB,GAAG,CAAC,CAC9C;AAEA,IAAa,kCAAb,cAAqD,OAAO,MAC1D,iCACF,EAAE;CACA,IAAI;CACJ,aAAa;CACb,UAAU,OAAO,SAAS,gCAAgC;CAC1D,oBAAoB,OAAO,SAAS,cAAc;CAClD,uBAAuB,OAAO,SAAS,cAAc;AACvD,CAAC,EAAE,CAAC;AAEJ,IAAa,oCAAb,cAAuD,OAAO,MAC5D,mCACF,EAAE;CACA,UAAU;CACV,WAAW;CACX,SAAS,OAAO,MAAM,+BAA+B;AACvD,CAAC,EAAE,CAAC;AAEJ,IAAa,wCAAb,cAA2D,OAAO,iBAAwD,EACxH,yCACA;CACE,UAAU,OAAO;CACjB,UAAU,OAAO,SAAS,CAAC,WAAW,SAAS,CAAC;AAClD,CACF,EAAE,CAAC;AAEH,IAAa,yCAAb,cAA4D,OAAO,iBAAyD,EAC1H,0CACA;CACE,UAAU,OAAO;CACjB,UAAU,OAAO,SAAS;EAAC;EAAQ;EAAoB;CAAU,CAAC;CAClE,QAAQ,OAAO,SAAS,OAAO,MAAM;AACvC,CACF,EAAE,CAAC;AAEH,IAAa,qCAAb,cAAwD,OAAO,iBAAqD,EAClH,sCACA;CACE,UAAU,OAAO;CACjB,QAAQ,OAAO;AACjB,CACF,EAAE,CAAC;AAEH,IAAa,0CAAb,cAA6D,OAAO,iBAA0D,EAC5H,2CACA;CACE,UAAU,OAAO;CACjB,cAAc,OAAO,SAAS,OAAO,MAAM;AAC7C,CACF,EAAE,CAAC;AAEH,IAAa,8CAAb,cAAiE,OAAO,iBAA8D,EACpI,+CACA;CACE,UAAU,OAAO;CACjB,QAAQ,OAAO,SAAS;EAAC;EAA2B;EAAsB;CAAmB,CAAC;AAChG,CACF,EAAE,CAAC"}
@@ -17,8 +17,8 @@ declare const mergeSkillsets: (sources: ReadonlyArray<SkillsetSource>) => Effect
17
17
  skills: {
18
18
  readonly name: string;
19
19
  readonly description: string;
20
- readonly location: string;
21
20
  readonly content: string;
21
+ readonly location: string;
22
22
  readonly source?: string | undefined;
23
23
  }[];
24
24
  commands: {
@@ -27,7 +27,6 @@ declare const mergeSkillsets: (sources: ReadonlyArray<SkillsetSource>) => Effect
27
27
  readonly template: string;
28
28
  readonly description?: string | undefined;
29
29
  readonly source?: string | undefined;
30
- readonly location?: string | undefined;
31
30
  readonly arguments?: readonly {
32
31
  readonly required: boolean;
33
32
  readonly name: string;
@@ -35,6 +34,7 @@ declare const mergeSkillsets: (sources: ReadonlyArray<SkillsetSource>) => Effect
35
34
  }[] | undefined;
36
35
  readonly access?: "read" | "write" | "destructive" | undefined;
37
36
  readonly fileRefs?: boolean | undefined;
37
+ readonly location?: string | undefined;
38
38
  }[];
39
39
  }, SkillsetError, never>;
40
40
  //#endregion
@@ -3,8 +3,8 @@ import { Effect } from "effect";
3
3
 
4
4
  //#region src/skillset/name.d.ts
5
5
  declare const isValidSkillsetName: (name: string) => boolean;
6
- declare const validateSkillsetName: (name: string) => Effect.Effect<never, SkillsetError, never> | Effect.Effect<string, never, never>;
7
- declare const validateDirectoryName: (expected: string, actual: string) => Effect.Effect<never, SkillsetError, never> | Effect.Effect<string, never, never>;
6
+ declare const validateSkillsetName: (name: string) => Effect.Effect<string, never, never> | Effect.Effect<never, SkillsetError, never>;
7
+ declare const validateDirectoryName: (expected: string, actual: string) => Effect.Effect<string, never, never> | Effect.Effect<never, SkillsetError, never>;
8
8
  //#endregion
9
9
  export { isValidSkillsetName, validateDirectoryName, validateSkillsetName };
10
10
  //# sourceMappingURL=name.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"name.d.mts","names":[],"sources":["../../src/skillset/name.ts"],"mappings":";;;;cAMa,mBAAA,GAAuB,IAAY;AAAA,cAGnC,oBAAA,GAAwB,IAAA,aAAY,MAAA,CAAA,MAAA,QAAA,aAAA,WAAA,MAAA,CAAA,MAAA;AAAA,cAUpC,qBAAA,GAAyB,QAAA,UAAkB,MAAA,aAAc,MAAA,CAAA,MAAA,QAAA,aAAA,WAAA,MAAA,CAAA,MAAA"}
1
+ {"version":3,"file":"name.d.mts","names":[],"sources":["../../src/skillset/name.ts"],"mappings":";;;;cAMa,mBAAA,GAAuB,IAAY;AAAA,cAGnC,oBAAA,GAAwB,IAAA,aAAY,MAAA,CAAA,MAAA,yBAAA,MAAA,CAAA,MAAA,QAAA,aAAA;AAAA,cAUpC,qBAAA,GAAyB,QAAA,UAAkB,MAAA,aAAc,MAAA,CAAA,MAAA,yBAAA,MAAA,CAAA,MAAA,QAAA,aAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yolk-sdk/agent",
3
- "version": "0.1.0-canary.57",
3
+ "version": "0.1.0-canary.58",
4
4
  "description": "Protocol, loop, runtime, client, compaction, tools, React, providers, OAuth, skillset, and voice primitives for building Yolk agents.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -87,6 +87,11 @@
87
87
  "import": "./dist/providers/openai/codex.mjs",
88
88
  "default": "./dist/providers/openai/codex.mjs"
89
89
  },
90
+ "./providers/openai/codex-usage": {
91
+ "types": "./dist/providers/openai/codex-usage.d.mts",
92
+ "import": "./dist/providers/openai/codex-usage.mjs",
93
+ "default": "./dist/providers/openai/codex-usage.mjs"
94
+ },
90
95
  "./providers/openai/codex-provider": {
91
96
  "types": "./dist/providers/openai/codex-provider.d.mts",
92
97
  "import": "./dist/providers/openai/codex-provider.mjs",
@@ -117,11 +122,21 @@
117
122
  "import": "./dist/providers/anthropic/claude.mjs",
118
123
  "default": "./dist/providers/anthropic/claude.mjs"
119
124
  },
125
+ "./providers/anthropic/usage": {
126
+ "types": "./dist/providers/anthropic/usage.d.mts",
127
+ "import": "./dist/providers/anthropic/usage.mjs",
128
+ "default": "./dist/providers/anthropic/usage.mjs"
129
+ },
120
130
  "./providers/anthropic/claude-provider": {
121
131
  "types": "./dist/providers/anthropic/claude-provider.d.mts",
122
132
  "import": "./dist/providers/anthropic/claude-provider.mjs",
123
133
  "default": "./dist/providers/anthropic/claude-provider.mjs"
124
134
  },
135
+ "./providers/subscription-usage": {
136
+ "types": "./dist/providers/subscription-usage.d.mts",
137
+ "import": "./dist/providers/subscription-usage.mjs",
138
+ "default": "./dist/providers/subscription-usage.mjs"
139
+ },
125
140
  "./skillset": {
126
141
  "types": "./dist/skillset/index.d.mts",
127
142
  "import": "./dist/skillset/index.mjs",
@@ -0,0 +1,150 @@
1
+ import { Chunk, Clock, Effect } from 'effect'
2
+ import * as Schema from 'effect/Schema'
3
+ import { HttpClient, HttpClientRequest } from 'effect/unstable/http'
4
+ import type { OAuthAccessToken } from '@yolk-sdk/agent/oauth'
5
+ import { anthropicClaudeAuthorizationHeaders, anthropicClaudeProviderId } from './claude.ts'
6
+ import {
7
+ canonicalSubscriptionUsageInstant,
8
+ defaultProviderSubscriptionUsageTimeoutMs,
9
+ executeProviderSubscriptionUsageRequest,
10
+ readProviderSubscriptionUsageJson,
11
+ validProviderSubscriptionUsageTimeout,
12
+ validSubscriptionUsagePercent
13
+ } from '../subscription-usage-internal.ts'
14
+ import {
15
+ ProviderSubscriptionUsageConfigurationError,
16
+ type ProviderSubscriptionUsageError,
17
+ ProviderSubscriptionUsageResponseError,
18
+ ProviderSubscriptionUsageSnapshot,
19
+ ProviderSubscriptionUsageWindow
20
+ } from '../subscription-usage.ts'
21
+
22
+ export const anthropicClaudeSubscriptionUsageUrl = 'https://api.anthropic.com/api/oauth/usage'
23
+
24
+ export type AnthropicClaudeSubscriptionUsageOptions = {
25
+ readonly requestTimeoutMs?: number
26
+ }
27
+
28
+ const NullableNumber = Schema.NullOr(Schema.Number)
29
+ const NullableString = Schema.NullOr(Schema.String)
30
+
31
+ class AnthropicClaudeSubscriptionUsageWindowWire extends Schema.Class<AnthropicClaudeSubscriptionUsageWindowWire>(
32
+ 'AnthropicClaudeSubscriptionUsageWindowWire'
33
+ )({
34
+ utilization: Schema.optional(NullableNumber),
35
+ resets_at: Schema.optional(NullableString)
36
+ }) {}
37
+
38
+ class AnthropicClaudeSubscriptionUsageWire extends Schema.Class<AnthropicClaudeSubscriptionUsageWire>(
39
+ 'AnthropicClaudeSubscriptionUsageWire'
40
+ )({
41
+ five_hour: Schema.optional(Schema.NullOr(AnthropicClaudeSubscriptionUsageWindowWire)),
42
+ seven_day: Schema.optional(Schema.NullOr(AnthropicClaudeSubscriptionUsageWindowWire))
43
+ }) {}
44
+
45
+ export const parseAnthropicClaudeSubscriptionUsage = (
46
+ value: unknown,
47
+ fetchedAt: string
48
+ ): Effect.Effect<ProviderSubscriptionUsageSnapshot, ProviderSubscriptionUsageResponseError> => {
49
+ const canonicalFetchedAt = canonicalSubscriptionUsageInstant(fetchedAt)
50
+
51
+ if (canonicalFetchedAt === undefined) {
52
+ return Effect.fail(
53
+ ProviderSubscriptionUsageResponseError.make({
54
+ provider: anthropicClaudeProviderId,
55
+ category: 'invalid_response'
56
+ })
57
+ )
58
+ }
59
+
60
+ return Schema.decodeUnknownEffect(AnthropicClaudeSubscriptionUsageWire)(value).pipe(
61
+ Effect.map(decoded => {
62
+ const windows: Array<ProviderSubscriptionUsageWindow> = []
63
+ const append = (
64
+ wire: AnthropicClaudeSubscriptionUsageWindowWire | null | undefined,
65
+ id: 'five-hour' | 'seven-day'
66
+ ) => {
67
+ if (!validSubscriptionUsagePercent(wire?.utilization)) {
68
+ return
69
+ }
70
+
71
+ const resetValue = wire.resets_at === null ? undefined : wire.resets_at
72
+ const resetsAt =
73
+ resetValue === undefined ? undefined : canonicalSubscriptionUsageInstant(resetValue)
74
+
75
+ windows.push(
76
+ ProviderSubscriptionUsageWindow.make({
77
+ id,
78
+ usedPercent: wire.utilization,
79
+ ...(resetsAt === undefined ? {} : { resetsAt })
80
+ })
81
+ )
82
+ }
83
+
84
+ append(decoded.five_hour, 'five-hour')
85
+ append(decoded.seven_day, 'seven-day')
86
+
87
+ return ProviderSubscriptionUsageSnapshot.make({
88
+ provider: anthropicClaudeProviderId,
89
+ fetchedAt: canonicalFetchedAt,
90
+ windows: Chunk.fromIterable(windows)
91
+ })
92
+ }),
93
+ Effect.mapError(() =>
94
+ ProviderSubscriptionUsageResponseError.make({
95
+ provider: anthropicClaudeProviderId,
96
+ category: 'invalid_response'
97
+ })
98
+ ),
99
+ Effect.withSpan('AnthropicClaudeSubscriptionUsage.parse')
100
+ )
101
+ }
102
+
103
+ export const fetchAnthropicClaudeSubscriptionUsage = (
104
+ token: OAuthAccessToken,
105
+ options: AnthropicClaudeSubscriptionUsageOptions = {}
106
+ ): Effect.Effect<
107
+ ProviderSubscriptionUsageSnapshot,
108
+ ProviderSubscriptionUsageError,
109
+ HttpClient.HttpClient
110
+ > =>
111
+ Effect.gen(function* () {
112
+ if (token.provider !== anthropicClaudeProviderId) {
113
+ return yield* Effect.fail(
114
+ ProviderSubscriptionUsageConfigurationError.make({
115
+ provider: anthropicClaudeProviderId,
116
+ reason: 'provider_mismatch'
117
+ })
118
+ )
119
+ }
120
+
121
+ const requestTimeoutMs = options.requestTimeoutMs ?? defaultProviderSubscriptionUsageTimeoutMs
122
+
123
+ if (!validProviderSubscriptionUsageTimeout(requestTimeoutMs)) {
124
+ return yield* Effect.fail(
125
+ ProviderSubscriptionUsageConfigurationError.make({
126
+ provider: anthropicClaudeProviderId,
127
+ reason: 'invalid_request_timeout'
128
+ })
129
+ )
130
+ }
131
+
132
+ const client = yield* HttpClient.HttpClient
133
+ const request = HttpClientRequest.get(anthropicClaudeSubscriptionUsageUrl).pipe(
134
+ HttpClientRequest.setHeaders({
135
+ accept: 'application/json',
136
+ ...anthropicClaudeAuthorizationHeaders(token),
137
+ 'anthropic-beta': 'oauth-2025-04-20'
138
+ })
139
+ )
140
+ const response = yield* executeProviderSubscriptionUsageRequest({
141
+ provider: anthropicClaudeProviderId,
142
+ client,
143
+ request,
144
+ timeoutMs: requestTimeoutMs
145
+ })
146
+ const json = yield* readProviderSubscriptionUsageJson(anthropicClaudeProviderId, response)
147
+ const fetchedAt = new Date(yield* Clock.currentTimeMillis).toISOString()
148
+
149
+ return yield* parseAnthropicClaudeSubscriptionUsage(json, fetchedAt)
150
+ }).pipe(Effect.withSpan('AnthropicClaudeSubscriptionUsage.fetch'))
@@ -0,0 +1,179 @@
1
+ import { Chunk, Clock, Effect } from 'effect'
2
+ import * as Schema from 'effect/Schema'
3
+ import { HttpClient, HttpClientRequest } from 'effect/unstable/http'
4
+ import type { OAuthAccessToken } from '@yolk-sdk/agent/oauth'
5
+ import { openAiCodexProviderId } from './codex.ts'
6
+ import {
7
+ canonicalSubscriptionUsageInstant,
8
+ defaultProviderSubscriptionUsageTimeoutMs,
9
+ executeProviderSubscriptionUsageRequest,
10
+ positiveSubscriptionUsageNumber,
11
+ readProviderSubscriptionUsageJson,
12
+ validProviderSubscriptionUsageTimeout,
13
+ validSubscriptionUsagePercent
14
+ } from '../subscription-usage-internal.ts'
15
+ import {
16
+ ProviderSubscriptionUsageConfigurationError,
17
+ type ProviderSubscriptionUsageError,
18
+ ProviderSubscriptionUsageResponseError,
19
+ ProviderSubscriptionUsageSnapshot,
20
+ ProviderSubscriptionUsageWindow
21
+ } from '../subscription-usage.ts'
22
+
23
+ export const openAiCodexSubscriptionUsageUrl = 'https://chatgpt.com/backend-api/wham/usage'
24
+
25
+ export type OpenAiCodexSubscriptionUsageOptions = {
26
+ readonly requestTimeoutMs?: number
27
+ }
28
+
29
+ const NullableNumber = Schema.NullOr(Schema.Number)
30
+
31
+ class OpenAiCodexSubscriptionUsageWindowWire extends Schema.Class<OpenAiCodexSubscriptionUsageWindowWire>(
32
+ 'OpenAiCodexSubscriptionUsageWindowWire'
33
+ )({
34
+ used_percent: Schema.optional(NullableNumber),
35
+ limit_window_seconds: Schema.optional(NullableNumber),
36
+ reset_after_seconds: Schema.optional(NullableNumber),
37
+ reset_at: Schema.optional(NullableNumber)
38
+ }) {}
39
+
40
+ class OpenAiCodexSubscriptionRateLimitWire extends Schema.Class<OpenAiCodexSubscriptionRateLimitWire>(
41
+ 'OpenAiCodexSubscriptionRateLimitWire'
42
+ )({
43
+ primary_window: Schema.optional(Schema.NullOr(OpenAiCodexSubscriptionUsageWindowWire)),
44
+ secondary_window: Schema.optional(Schema.NullOr(OpenAiCodexSubscriptionUsageWindowWire))
45
+ }) {}
46
+
47
+ class OpenAiCodexSubscriptionUsageWire extends Schema.Class<OpenAiCodexSubscriptionUsageWire>(
48
+ 'OpenAiCodexSubscriptionUsageWire'
49
+ )({
50
+ rate_limit: Schema.optional(Schema.NullOr(OpenAiCodexSubscriptionRateLimitWire))
51
+ }) {}
52
+
53
+ export const parseOpenAiCodexSubscriptionUsage = (
54
+ value: unknown,
55
+ fetchedAt: string
56
+ ): Effect.Effect<ProviderSubscriptionUsageSnapshot, ProviderSubscriptionUsageResponseError> => {
57
+ const canonicalFetchedAt = canonicalSubscriptionUsageInstant(fetchedAt)
58
+
59
+ if (canonicalFetchedAt === undefined) {
60
+ return Effect.fail(
61
+ ProviderSubscriptionUsageResponseError.make({
62
+ provider: openAiCodexProviderId,
63
+ category: 'invalid_response'
64
+ })
65
+ )
66
+ }
67
+
68
+ return Schema.decodeUnknownEffect(OpenAiCodexSubscriptionUsageWire)(value).pipe(
69
+ Effect.map(decoded => {
70
+ const windows: Array<ProviderSubscriptionUsageWindow> = []
71
+ const append = (
72
+ wire: OpenAiCodexSubscriptionUsageWindowWire | null | undefined,
73
+ id: 'primary' | 'secondary'
74
+ ) => {
75
+ if (!validSubscriptionUsagePercent(wire?.used_percent)) {
76
+ return
77
+ }
78
+
79
+ const resetAt = wire.reset_at
80
+ const resetsAt =
81
+ resetAt === null || resetAt === undefined
82
+ ? undefined
83
+ : canonicalSubscriptionUsageInstant(resetAt)
84
+ const resetsAfterSeconds = positiveSubscriptionUsageNumber(wire.reset_after_seconds)
85
+ ? wire.reset_after_seconds
86
+ : undefined
87
+ const windowDurationMinutes = positiveSubscriptionUsageNumber(wire.limit_window_seconds)
88
+ ? wire.limit_window_seconds / 60
89
+ : undefined
90
+
91
+ windows.push(
92
+ ProviderSubscriptionUsageWindow.make({
93
+ id,
94
+ usedPercent: wire.used_percent,
95
+ ...(resetsAt === undefined ? {} : { resetsAt }),
96
+ ...(resetsAfterSeconds === undefined ? {} : { resetsAfterSeconds }),
97
+ ...(windowDurationMinutes === undefined ? {} : { windowDurationMinutes })
98
+ })
99
+ )
100
+ }
101
+
102
+ append(decoded.rate_limit?.primary_window, 'primary')
103
+ append(decoded.rate_limit?.secondary_window, 'secondary')
104
+
105
+ return ProviderSubscriptionUsageSnapshot.make({
106
+ provider: openAiCodexProviderId,
107
+ fetchedAt: canonicalFetchedAt,
108
+ windows: Chunk.fromIterable(windows)
109
+ })
110
+ }),
111
+ Effect.mapError(() =>
112
+ ProviderSubscriptionUsageResponseError.make({
113
+ provider: openAiCodexProviderId,
114
+ category: 'invalid_response'
115
+ })
116
+ ),
117
+ Effect.withSpan('OpenAiCodexSubscriptionUsage.parse')
118
+ )
119
+ }
120
+
121
+ export const fetchOpenAiCodexSubscriptionUsage = (
122
+ token: OAuthAccessToken,
123
+ options: OpenAiCodexSubscriptionUsageOptions = {}
124
+ ): Effect.Effect<
125
+ ProviderSubscriptionUsageSnapshot,
126
+ ProviderSubscriptionUsageError,
127
+ HttpClient.HttpClient
128
+ > =>
129
+ Effect.gen(function* () {
130
+ if (token.provider !== openAiCodexProviderId) {
131
+ return yield* Effect.fail(
132
+ ProviderSubscriptionUsageConfigurationError.make({
133
+ provider: openAiCodexProviderId,
134
+ reason: 'provider_mismatch'
135
+ })
136
+ )
137
+ }
138
+
139
+ const accountId = token.accountId?.trim()
140
+
141
+ if (accountId === undefined || accountId.length === 0) {
142
+ return yield* Effect.fail(
143
+ ProviderSubscriptionUsageConfigurationError.make({
144
+ provider: openAiCodexProviderId,
145
+ reason: 'missing_account_id'
146
+ })
147
+ )
148
+ }
149
+
150
+ const requestTimeoutMs = options.requestTimeoutMs ?? defaultProviderSubscriptionUsageTimeoutMs
151
+
152
+ if (!validProviderSubscriptionUsageTimeout(requestTimeoutMs)) {
153
+ return yield* Effect.fail(
154
+ ProviderSubscriptionUsageConfigurationError.make({
155
+ provider: openAiCodexProviderId,
156
+ reason: 'invalid_request_timeout'
157
+ })
158
+ )
159
+ }
160
+
161
+ const client = yield* HttpClient.HttpClient
162
+ const request = HttpClientRequest.get(openAiCodexSubscriptionUsageUrl).pipe(
163
+ HttpClientRequest.setHeaders({
164
+ accept: 'application/json',
165
+ authorization: `Bearer ${token.accessToken}`,
166
+ 'chatgpt-account-id': accountId
167
+ })
168
+ )
169
+ const response = yield* executeProviderSubscriptionUsageRequest({
170
+ provider: openAiCodexProviderId,
171
+ client,
172
+ request,
173
+ timeoutMs: requestTimeoutMs
174
+ })
175
+ const json = yield* readProviderSubscriptionUsageJson(openAiCodexProviderId, response)
176
+ const fetchedAt = new Date(yield* Clock.currentTimeMillis).toISOString()
177
+
178
+ return yield* parseOpenAiCodexSubscriptionUsage(json, fetchedAt)
179
+ }).pipe(Effect.withSpan('OpenAiCodexSubscriptionUsage.fetch'))
@@ -0,0 +1,105 @@
1
+ import { Duration, Effect } from 'effect'
2
+ import * as Schema from 'effect/Schema'
3
+ import {
4
+ FetchHttpClient,
5
+ HttpClientResponse,
6
+ type HttpClient,
7
+ type HttpClientRequest
8
+ } from 'effect/unstable/http'
9
+ import { retryAfterMsFromHeaders } from './provider-error.ts'
10
+ import {
11
+ ProviderSubscriptionUsageAuthError,
12
+ type ProviderSubscriptionUsageError,
13
+ ProviderSubscriptionUsageRateLimitError,
14
+ ProviderSubscriptionUsageRequestError,
15
+ ProviderSubscriptionUsageResponseError
16
+ } from './subscription-usage.ts'
17
+
18
+ export const defaultProviderSubscriptionUsageTimeoutMs = 10_000
19
+
20
+ export const canonicalSubscriptionUsageInstant = (value: string | number): string | undefined => {
21
+ const date = new Date(typeof value === 'number' ? value * 1000 : value)
22
+
23
+ return Number.isFinite(date.getTime()) ? date.toISOString() : undefined
24
+ }
25
+
26
+ export const validSubscriptionUsagePercent = (value: number | null | undefined): value is number =>
27
+ value !== null && value !== undefined && Number.isFinite(value) && value >= 0 && value <= 100
28
+
29
+ export const positiveSubscriptionUsageNumber = (
30
+ value: number | null | undefined
31
+ ): value is number => value !== null && value !== undefined && Number.isFinite(value) && value > 0
32
+
33
+ export const validProviderSubscriptionUsageTimeout = (value: number) =>
34
+ Number.isFinite(value) && value > 0
35
+
36
+ const responseError = (provider: string, status: number): ProviderSubscriptionUsageError => {
37
+ if (status === 401 || status === 403) {
38
+ return ProviderSubscriptionUsageAuthError.make({ provider, status })
39
+ }
40
+
41
+ return ProviderSubscriptionUsageResponseError.make({
42
+ provider,
43
+ category: status >= 300 && status < 400 ? 'redirect' : 'http',
44
+ status
45
+ })
46
+ }
47
+
48
+ export const executeProviderSubscriptionUsageRequest = (input: {
49
+ readonly provider: string
50
+ readonly client: HttpClient.HttpClient
51
+ readonly request: HttpClientRequest.HttpClientRequest
52
+ readonly timeoutMs: number
53
+ }) =>
54
+ Effect.gen(function* () {
55
+ const response = yield* input.client.execute(input.request).pipe(
56
+ Effect.provideService(FetchHttpClient.RequestInit, { redirect: 'manual' }),
57
+ Effect.withTracerEnabled(false),
58
+ Effect.mapError(() =>
59
+ ProviderSubscriptionUsageRequestError.make({
60
+ provider: input.provider,
61
+ category: 'network'
62
+ })
63
+ ),
64
+ Effect.timeoutOrElse({
65
+ duration: Duration.millis(input.timeoutMs),
66
+ orElse: () =>
67
+ Effect.fail(
68
+ ProviderSubscriptionUsageRequestError.make({
69
+ provider: input.provider,
70
+ category: 'timeout'
71
+ })
72
+ )
73
+ })
74
+ )
75
+
76
+ if (response.status === 429) {
77
+ return yield* Effect.fail(
78
+ ProviderSubscriptionUsageRateLimitError.make({
79
+ provider: input.provider,
80
+ retryAfterMs: retryAfterMsFromHeaders(response.headers)
81
+ })
82
+ )
83
+ }
84
+
85
+ if (response.status < 200 || response.status >= 300) {
86
+ return yield* Effect.fail(responseError(input.provider, response.status))
87
+ }
88
+
89
+ return response
90
+ })
91
+
92
+ export const readProviderSubscriptionUsageJson = (
93
+ provider: string,
94
+ response: HttpClientResponse.HttpClientResponse
95
+ ) =>
96
+ HttpClientResponse.schemaBodyJson(Schema.Unknown)(response).pipe(
97
+ Effect.withTracerEnabled(false),
98
+ Effect.mapError(() =>
99
+ ProviderSubscriptionUsageResponseError.make({
100
+ provider,
101
+ category: 'invalid_response',
102
+ status: response.status
103
+ })
104
+ )
105
+ )
@@ -0,0 +1,88 @@
1
+ import * as Schema from 'effect/Schema'
2
+
3
+ const NonEmptyTrimmedString = Schema.Trimmed.pipe(Schema.check(Schema.isNonEmpty()))
4
+ const PositiveNumber = Schema.Number.pipe(Schema.check(Schema.isGreaterThan(0)))
5
+ const isCanonicalInstant = (value: string) => {
6
+ const date = new Date(value)
7
+
8
+ return Number.isFinite(date.getTime()) && date.toISOString() === value
9
+ }
10
+
11
+ export const ProviderSubscriptionUsageInstant = NonEmptyTrimmedString.pipe(
12
+ Schema.check(
13
+ Schema.makeFilter(isCanonicalInstant, {
14
+ identifier: 'ProviderSubscriptionUsageInstant'
15
+ })
16
+ )
17
+ )
18
+
19
+ export const ProviderSubscriptionUsagePercent = Schema.Number.pipe(
20
+ Schema.check(Schema.isGreaterThanOrEqualTo(0)),
21
+ Schema.check(Schema.isLessThanOrEqualTo(100))
22
+ )
23
+
24
+ export class ProviderSubscriptionUsageWindow extends Schema.Class<ProviderSubscriptionUsageWindow>(
25
+ 'ProviderSubscriptionUsageWindow'
26
+ )({
27
+ id: NonEmptyTrimmedString,
28
+ usedPercent: ProviderSubscriptionUsagePercent,
29
+ resetsAt: Schema.optional(ProviderSubscriptionUsageInstant),
30
+ resetsAfterSeconds: Schema.optional(PositiveNumber),
31
+ windowDurationMinutes: Schema.optional(PositiveNumber)
32
+ }) {}
33
+
34
+ export class ProviderSubscriptionUsageSnapshot extends Schema.Class<ProviderSubscriptionUsageSnapshot>(
35
+ 'ProviderSubscriptionUsageSnapshot'
36
+ )({
37
+ provider: NonEmptyTrimmedString,
38
+ fetchedAt: ProviderSubscriptionUsageInstant,
39
+ windows: Schema.Chunk(ProviderSubscriptionUsageWindow)
40
+ }) {}
41
+
42
+ export class ProviderSubscriptionUsageRequestError extends Schema.TaggedErrorClass<ProviderSubscriptionUsageRequestError>()(
43
+ 'ProviderSubscriptionUsageRequestError',
44
+ {
45
+ provider: Schema.String,
46
+ category: Schema.Literals(['timeout', 'network'])
47
+ }
48
+ ) {}
49
+
50
+ export class ProviderSubscriptionUsageResponseError extends Schema.TaggedErrorClass<ProviderSubscriptionUsageResponseError>()(
51
+ 'ProviderSubscriptionUsageResponseError',
52
+ {
53
+ provider: Schema.String,
54
+ category: Schema.Literals(['http', 'invalid_response', 'redirect']),
55
+ status: Schema.optional(Schema.Number)
56
+ }
57
+ ) {}
58
+
59
+ export class ProviderSubscriptionUsageAuthError extends Schema.TaggedErrorClass<ProviderSubscriptionUsageAuthError>()(
60
+ 'ProviderSubscriptionUsageAuthError',
61
+ {
62
+ provider: Schema.String,
63
+ status: Schema.Number
64
+ }
65
+ ) {}
66
+
67
+ export class ProviderSubscriptionUsageRateLimitError extends Schema.TaggedErrorClass<ProviderSubscriptionUsageRateLimitError>()(
68
+ 'ProviderSubscriptionUsageRateLimitError',
69
+ {
70
+ provider: Schema.String,
71
+ retryAfterMs: Schema.optional(Schema.Number)
72
+ }
73
+ ) {}
74
+
75
+ export class ProviderSubscriptionUsageConfigurationError extends Schema.TaggedErrorClass<ProviderSubscriptionUsageConfigurationError>()(
76
+ 'ProviderSubscriptionUsageConfigurationError',
77
+ {
78
+ provider: Schema.String,
79
+ reason: Schema.Literals(['invalid_request_timeout', 'missing_account_id', 'provider_mismatch'])
80
+ }
81
+ ) {}
82
+
83
+ export type ProviderSubscriptionUsageError =
84
+ | ProviderSubscriptionUsageRequestError
85
+ | ProviderSubscriptionUsageResponseError
86
+ | ProviderSubscriptionUsageAuthError
87
+ | ProviderSubscriptionUsageRateLimitError
88
+ | ProviderSubscriptionUsageConfigurationError