@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 +35 -1
- package/dist/providers/anthropic/usage.d.mts +15 -0
- package/dist/providers/anthropic/usage.d.mts.map +1 -0
- package/dist/providers/anthropic/usage.mjs +73 -0
- package/dist/providers/anthropic/usage.mjs.map +1 -0
- package/dist/providers/openai/codex-usage.d.mts +15 -0
- package/dist/providers/openai/codex-usage.d.mts.map +1 -0
- package/dist/providers/openai/codex-usage.mjs +84 -0
- package/dist/providers/openai/codex-usage.mjs.map +1 -0
- package/dist/providers/subscription-usage-internal.d.mts +20 -0
- package/dist/providers/subscription-usage-internal.d.mts.map +1 -0
- package/dist/providers/subscription-usage-internal.mjs +52 -0
- package/dist/providers/subscription-usage-internal.mjs.map +1 -0
- package/dist/providers/subscription-usage.d.mts +49 -0
- package/dist/providers/subscription-usage.d.mts.map +1 -0
- package/dist/providers/subscription-usage.mjs +55 -0
- package/dist/providers/subscription-usage.mjs.map +1 -0
- package/dist/skillset/merge.d.mts +2 -2
- package/dist/skillset/name.d.mts +2 -2
- package/dist/skillset/name.d.mts.map +1 -1
- package/package.json +16 -1
- package/src/providers/anthropic/usage.ts +150 -0
- package/src/providers/openai/codex-usage.ts +179 -0
- package/src/providers/subscription-usage-internal.ts +105 -0
- package/src/providers/subscription-usage.ts +88 -0
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
|
package/dist/skillset/name.d.mts
CHANGED
|
@@ -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<
|
|
7
|
-
declare const validateDirectoryName: (expected: string, actual: string) => Effect.Effect<
|
|
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,
|
|
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.
|
|
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
|