@agentproto/model-intel 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -105,6 +105,41 @@ declare class QuotaReaderRegistry {
105
105
  read(profile: QuotaProfile, ctx?: QuotaReadContext): Promise<QuotaStatus | undefined>;
106
106
  }
107
107
 
108
+ /**
109
+ * Catalog watch domain: pure diff of two catalog snapshots.
110
+ * No I/O — mirrors the pure-evaluator pattern of `build.ts`.
111
+ */
112
+ interface CatalogSnapshotEntry {
113
+ /** Catalog model id, e.g. "claude-sonnet-5-5". The snapshot's own key. */
114
+ id: string;
115
+ inputPer1M: number;
116
+ outputPer1M: number;
117
+ /** Derived: true when inputPer1M === 0 && outputPer1M === 0. */
118
+ free: boolean;
119
+ }
120
+ /** Keyed by CatalogSnapshotEntry.id. */
121
+ type CatalogSnapshot = Record<string, CatalogSnapshotEntry>;
122
+ interface CatalogPriceChange {
123
+ id: string;
124
+ prev: Pick<CatalogSnapshotEntry, "inputPer1M" | "outputPer1M">;
125
+ next: Pick<CatalogSnapshotEntry, "inputPer1M" | "outputPer1M">;
126
+ }
127
+ interface CatalogDiff {
128
+ /** ids present in `next`, absent from `prev`. */
129
+ appeared: string[];
130
+ /** ids present in `prev`, absent from `next`. */
131
+ removed: string[];
132
+ /** same id in both, inputPer1M or outputPer1M differs. Price-only — an id
133
+ * in `appeared`/`removed` never also appears here. */
134
+ priceChanged: CatalogPriceChange[];
135
+ /** id was free in prev, not free in next (and still present in both —
136
+ * already counted in priceChanged too if the price itself differs). */
137
+ freeToPaid: string[];
138
+ /** id was not free in prev, free in next. */
139
+ paidToFree: string[];
140
+ }
141
+ declare function diffCatalog(prev: CatalogSnapshot, next: CatalogSnapshot): CatalogDiff;
142
+
108
143
  interface AnthropicHeadersInput {
109
144
  profileRef: string;
110
145
  headers: Headers | Record<string, string | undefined>;
@@ -118,6 +153,47 @@ interface AnthropicHeadersInput {
118
153
  */
119
154
  declare function quotaFromAnthropicHeaders(input: AnthropicHeadersInput): QuotaStatus | undefined;
120
155
 
156
+ /** One side of a Codex `rate_limits` payload. */
157
+ interface CodexRateLimitWindow {
158
+ used_percent: number;
159
+ window_minutes?: number;
160
+ /** Epoch seconds. */
161
+ reset_at?: number;
162
+ }
163
+ interface CodexRateLimits {
164
+ primary?: CodexRateLimitWindow;
165
+ secondary?: CodexRateLimitWindow;
166
+ }
167
+ interface CodexCredits {
168
+ has_credits?: boolean;
169
+ unlimited?: boolean;
170
+ balance?: number;
171
+ }
172
+ interface CodexRateLimitsInput {
173
+ profileRef: string;
174
+ rateLimits: CodexRateLimits;
175
+ credits?: CodexCredits;
176
+ now?: () => Date;
177
+ }
178
+ /**
179
+ * Pure parser for a Codex `rate_limits` payload: each present side becomes one
180
+ * window and optional credits become a credits block. Never throws; a missing
181
+ * or non-numeric field is dropped, a valid value (0 included) is kept.
182
+ */
183
+ declare function quotaFromCodexRateLimits(input: CodexRateLimitsInput): QuotaStatus | undefined;
184
+
185
+ interface MoonshotQuotaReaderOptions {
186
+ baseUrl?: string;
187
+ timeoutMs?: number;
188
+ }
189
+ /**
190
+ * Moonshot balance reader: `GET /v1/users/me/balance` exposes the account's
191
+ * available USD balance, but no cap, so it is reported as credits-only (no
192
+ * window). The whole read never throws; a balance of 0 or below is real data
193
+ * and is kept as is.
194
+ */
195
+ declare function createMoonshotQuotaReader(opts?: MoonshotQuotaReaderOptions): QuotaReader;
196
+
121
197
  interface OpenRouterQuotaReaderOptions {
122
198
  baseUrl?: string;
123
199
  timeoutMs?: number;
@@ -141,4 +217,4 @@ declare function createOpenRouterQuotaReader(opts?: OpenRouterQuotaReaderOptions
141
217
  declare const SPEC_NAME: "quota/v1";
142
218
  declare const SPEC_VERSION: "1.0.0-alpha";
143
219
 
144
- export { type BuildQuotaStatusInput, QUOTA_BASES, type QuotaBasis, type QuotaCredits, type QuotaProfile, type QuotaReadContext, type QuotaReader, QuotaReaderRegistry, type QuotaStatus, type QuotaWindow, SPEC_NAME, SPEC_VERSION, buildQuotaStatus, createOpenRouterQuotaReader, defineQuotaReader, quotaCreditsSchema, quotaFromAnthropicHeaders, quotaStatusSchema, quotaWindowSchema, remainingPctFromCounts, remainingPctFromUsed };
220
+ export { type BuildQuotaStatusInput, type CatalogDiff, type CatalogPriceChange, type CatalogSnapshot, type CatalogSnapshotEntry, type CodexCredits, type CodexRateLimitWindow, type CodexRateLimits, type CodexRateLimitsInput, QUOTA_BASES, type QuotaBasis, type QuotaCredits, type QuotaProfile, type QuotaReadContext, type QuotaReader, QuotaReaderRegistry, type QuotaStatus, type QuotaWindow, SPEC_NAME, SPEC_VERSION, buildQuotaStatus, createMoonshotQuotaReader, createOpenRouterQuotaReader, defineQuotaReader, diffCatalog, quotaCreditsSchema, quotaFromAnthropicHeaders, quotaFromCodexRateLimits, quotaStatusSchema, quotaWindowSchema, remainingPctFromCounts, remainingPctFromUsed };
package/dist/index.mjs CHANGED
@@ -107,6 +107,47 @@ function safeSupports(reader, profile) {
107
107
  }
108
108
  }
109
109
 
110
+ // src/catalog-diff.ts
111
+ function isFree(entry) {
112
+ return entry.inputPer1M === 0 && entry.outputPer1M === 0;
113
+ }
114
+ function diffCatalog(prev, next) {
115
+ const appeared = [];
116
+ const removed = [];
117
+ const priceChanged = [];
118
+ const freeToPaid = [];
119
+ const paidToFree = [];
120
+ for (const id of Object.keys(next)) {
121
+ if (!(id in prev)) appeared.push(id);
122
+ }
123
+ for (const id of Object.keys(prev)) {
124
+ if (!(id in next)) removed.push(id);
125
+ }
126
+ for (const id of Object.keys(next)) {
127
+ const before = prev[id];
128
+ const after = next[id];
129
+ if (!before || !after) continue;
130
+ if (before.inputPer1M !== after.inputPer1M || before.outputPer1M !== after.outputPer1M) {
131
+ priceChanged.push({
132
+ id,
133
+ prev: { inputPer1M: before.inputPer1M, outputPer1M: before.outputPer1M },
134
+ next: { inputPer1M: after.inputPer1M, outputPer1M: after.outputPer1M }
135
+ });
136
+ }
137
+ const wasFree = isFree(before);
138
+ const isNowFree = isFree(after);
139
+ if (wasFree && !isNowFree) freeToPaid.push(id);
140
+ if (!wasFree && isNowFree) paidToFree.push(id);
141
+ }
142
+ return {
143
+ appeared: appeared.sort(),
144
+ removed: removed.sort(),
145
+ priceChanged: priceChanged.sort((a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0),
146
+ freeToPaid: freeToPaid.sort(),
147
+ paidToFree: paidToFree.sort()
148
+ };
149
+ }
150
+
110
151
  // src/readers/anthropic.ts
111
152
  var DIMENSIONS = [
112
153
  { id: "requests-1m", header: "requests", unit: "requests" },
@@ -145,12 +186,12 @@ function quotaFromAnthropicHeaders(input) {
145
186
  const limit = parseCount(headerGet(input.headers, `${prefix}-limit`));
146
187
  const remaining = parseCount(headerGet(input.headers, `${prefix}-remaining`));
147
188
  if (limit === void 0 || remaining === void 0) continue;
148
- const remainingPct = remainingPctFromCounts(remaining, limit);
149
- if (remainingPct === void 0) continue;
189
+ const remainingPct2 = remainingPctFromCounts(remaining, limit);
190
+ if (remainingPct2 === void 0) continue;
150
191
  const resetsAt = parseReset(headerGet(input.headers, `${prefix}-reset`));
151
192
  windows.push({
152
193
  id: dimension.id,
153
- remainingPct,
194
+ remainingPct: remainingPct2,
154
195
  limit,
155
196
  remaining,
156
197
  unit: dimension.unit,
@@ -165,8 +206,112 @@ function quotaFromAnthropicHeaders(input) {
165
206
  windows
166
207
  });
167
208
  }
168
- var DEFAULT_BASE_URL = "https://openrouter.ai/api/v1";
209
+
210
+ // src/readers/codex.ts
211
+ function remainingPct(used) {
212
+ return Math.max(0, Math.min(100, 100 - used));
213
+ }
214
+ function windowFromSide(id, side) {
215
+ if (!side || typeof side.used_percent !== "number" || !Number.isFinite(side.used_percent)) {
216
+ return void 0;
217
+ }
218
+ const window = { id, remainingPct: remainingPct(side.used_percent) };
219
+ if (typeof side.reset_at === "number" && Number.isFinite(side.reset_at)) {
220
+ const date = new Date(side.reset_at * 1e3);
221
+ if (!Number.isNaN(date.getTime())) window.resetsAt = date.toISOString();
222
+ }
223
+ return window;
224
+ }
225
+ function quotaFromCodexRateLimits(input) {
226
+ try {
227
+ const windows = [];
228
+ const primary = windowFromSide("primary", input.rateLimits?.primary);
229
+ if (primary) windows.push(primary);
230
+ const secondary = windowFromSide("secondary", input.rateLimits?.secondary);
231
+ if (secondary) windows.push(secondary);
232
+ let credits;
233
+ const raw = input.credits;
234
+ if (raw) {
235
+ const hasBalance = typeof raw.balance === "number" && Number.isFinite(raw.balance);
236
+ if (raw.unlimited === true || hasBalance) {
237
+ credits = { currency: "USD" };
238
+ if (raw.unlimited === true) credits.unlimited = true;
239
+ if (hasBalance) credits.balanceUsd = raw.balance;
240
+ }
241
+ }
242
+ return buildQuotaStatus({
243
+ profileRef: input.profileRef,
244
+ basis: "provider",
245
+ source: "codex:rate_limits",
246
+ now: input.now,
247
+ windows,
248
+ ...credits !== void 0 ? { credits } : {}
249
+ });
250
+ } catch {
251
+ return void 0;
252
+ }
253
+ }
254
+ var DEFAULT_BASE_URL = "https://api.moonshot.ai";
169
255
  var DEFAULT_TIMEOUT_MS = 4e3;
256
+ var MOONSHOT_BALANCE_SCHEMA = z.object({
257
+ data: z.object({
258
+ available_balance: z.number()
259
+ }).passthrough()
260
+ }).passthrough();
261
+ async function getBalanceJson(baseUrl, secret, ctx, timeoutMs) {
262
+ const controller = new AbortController();
263
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
264
+ const onAbort = () => controller.abort();
265
+ ctx.signal?.addEventListener("abort", onAbort);
266
+ try {
267
+ const res = await (ctx.fetchImpl ?? fetch)(`${baseUrl}/v1/users/me/balance`, {
268
+ method: "GET",
269
+ headers: { authorization: `Bearer ${secret}` },
270
+ signal: controller.signal
271
+ });
272
+ if (!res.ok) return void 0;
273
+ return await res.json();
274
+ } catch {
275
+ return void 0;
276
+ } finally {
277
+ clearTimeout(timer);
278
+ ctx.signal?.removeEventListener("abort", onAbort);
279
+ }
280
+ }
281
+ function createMoonshotQuotaReader(opts) {
282
+ const baseUrl = opts?.baseUrl ?? DEFAULT_BASE_URL;
283
+ const timeoutMs = opts?.timeoutMs ?? DEFAULT_TIMEOUT_MS;
284
+ return defineQuotaReader({
285
+ id: "moonshot-balance",
286
+ supports: (profile) => profile.endpoint === "moonshot" && profile.method === "api-key",
287
+ read: async (profile, ctx) => {
288
+ try {
289
+ if (!profile.credentialRef) return void 0;
290
+ if (!ctx.resolveCredential) return void 0;
291
+ const secret = await ctx.resolveCredential(profile.credentialRef).catch(() => void 0);
292
+ if (!secret) return void 0;
293
+ const body = await getBalanceJson(baseUrl, secret, ctx, timeoutMs);
294
+ if (body === void 0) return void 0;
295
+ const parsed = MOONSHOT_BALANCE_SCHEMA.safeParse(body);
296
+ if (!parsed.success) return void 0;
297
+ const balance = parsed.data.data.available_balance;
298
+ if (!Number.isFinite(balance)) return void 0;
299
+ return buildQuotaStatus({
300
+ profileRef: profile.profileRef,
301
+ basis: "provider",
302
+ source: "moonshot:/users/me/balance",
303
+ now: ctx.now,
304
+ windows: [],
305
+ credits: { balanceUsd: balance, currency: "USD" }
306
+ });
307
+ } catch {
308
+ return void 0;
309
+ }
310
+ }
311
+ });
312
+ }
313
+ var DEFAULT_BASE_URL2 = "https://openrouter.ai/api/v1";
314
+ var DEFAULT_TIMEOUT_MS2 = 4e3;
170
315
  var OPENROUTER_KEY_SCHEMA = z.object({
171
316
  data: z.object({
172
317
  limit: z.number().nullable().optional(),
@@ -194,8 +339,8 @@ async function getKeyJson(baseUrl, secret, ctx, timeoutMs) {
194
339
  }
195
340
  }
196
341
  function createOpenRouterQuotaReader(opts) {
197
- const baseUrl = opts?.baseUrl ?? DEFAULT_BASE_URL;
198
- const timeoutMs = opts?.timeoutMs ?? DEFAULT_TIMEOUT_MS;
342
+ const baseUrl = opts?.baseUrl ?? DEFAULT_BASE_URL2;
343
+ const timeoutMs = opts?.timeoutMs ?? DEFAULT_TIMEOUT_MS2;
199
344
  return defineQuotaReader({
200
345
  id: "openrouter-key",
201
346
  supports: (profile) => profile.endpoint === "openrouter" && profile.method === "api-key",
@@ -212,11 +357,11 @@ function createOpenRouterQuotaReader(opts) {
212
357
  const { limit, limit_remaining } = parsed.data.data;
213
358
  const windows = [];
214
359
  if (typeof limit === "number" && Number.isFinite(limit) && typeof limit_remaining === "number" && Number.isFinite(limit_remaining)) {
215
- const remainingPct = remainingPctFromCounts(limit_remaining, limit);
216
- if (remainingPct !== void 0) {
360
+ const remainingPct2 = remainingPctFromCounts(limit_remaining, limit);
361
+ if (remainingPct2 !== void 0) {
217
362
  windows.push({
218
363
  id: "key_limit",
219
- remainingPct,
364
+ remainingPct: remainingPct2,
220
365
  remaining: limit_remaining,
221
366
  limit,
222
367
  unit: "USD"
@@ -246,6 +391,6 @@ function createOpenRouterQuotaReader(opts) {
246
391
  var SPEC_NAME = "quota/v1";
247
392
  var SPEC_VERSION = "1.0.0-alpha";
248
393
 
249
- export { QUOTA_BASES, QuotaReaderRegistry, SPEC_NAME, SPEC_VERSION, buildQuotaStatus, createOpenRouterQuotaReader, defineQuotaReader, quotaCreditsSchema, quotaFromAnthropicHeaders, quotaStatusSchema, quotaWindowSchema, remainingPctFromCounts, remainingPctFromUsed };
394
+ export { QUOTA_BASES, QuotaReaderRegistry, SPEC_NAME, SPEC_VERSION, buildQuotaStatus, createMoonshotQuotaReader, createOpenRouterQuotaReader, defineQuotaReader, diffCatalog, quotaCreditsSchema, quotaFromAnthropicHeaders, quotaFromCodexRateLimits, quotaStatusSchema, quotaWindowSchema, remainingPctFromCounts, remainingPctFromUsed };
250
395
  //# sourceMappingURL=index.mjs.map
251
396
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/types.ts","../src/build.ts","../src/registry.ts","../src/readers/anthropic.ts","../src/readers/openrouter.ts","../src/index.ts"],"names":["z"],"mappings":"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","file":"index.mjs","sourcesContent":["import { z } from \"zod\"\n\n/**\n * Where a quota figure came from. `provider` is a documented provider\n * surface; `observed` is derived from this host's own traffic; `estimated`\n * is a local computation; `probe` is an undocumented surface reached through\n * a connector and MUST NOT be emitted by core readers.\n */\nexport const QUOTA_BASES = [\"provider\", \"probe\", \"observed\", \"estimated\"] as const\nexport type QuotaBasis = (typeof QUOTA_BASES)[number]\n\nconst isoTimestamp = z.string().refine((s) => !Number.isNaN(Date.parse(s)), {\n message: \"expected an ISO-8601 timestamp\",\n})\n\nexport const quotaWindowSchema = z.object({\n /** Open string: \"5h\", \"7d\", \"month\", \"minute\", \"day\", \"primary\", \"key_limit\", ... */\n id: z.string().min(1),\n /** 0-100. Required: a window that cannot state it is not emitted. */\n remainingPct: z.number().min(0).max(100),\n used: z.number().optional(),\n limit: z.number().optional(),\n remaining: z.number().optional(),\n unit: z.string().optional(),\n resetsAt: isoTimestamp.optional(),\n})\nexport type QuotaWindow = z.infer<typeof quotaWindowSchema>\n\nexport const quotaCreditsSchema = z.object({\n balanceUsd: z.number().optional(),\n unlimited: z.boolean().optional(),\n currency: z.string().min(1),\n})\nexport type QuotaCredits = z.infer<typeof quotaCreditsSchema>\n\nexport const quotaStatusSchema = z.object({\n profileRef: z.string().min(1),\n asOf: isoTimestamp,\n basis: z.enum(QUOTA_BASES),\n source: z.string().optional(),\n windows: z.array(quotaWindowSchema),\n credits: quotaCreditsSchema.optional(),\n})\nexport type QuotaStatus = z.infer<typeof quotaStatusSchema>\n\n/** The minimal auth-profile projection a reader needs. */\nexport interface QuotaProfile {\n profileRef: string\n endpoint: string\n method: \"oauth-bearer\" | \"api-key\"\n credentialRef?: string\n}\n\nexport interface QuotaReadContext {\n signal?: AbortSignal\n /** Resolve a profile's opaque credentialRef to a bearer secret. */\n resolveCredential?(ref: string): Promise<string | undefined>\n fetchImpl?: typeof fetch\n now?: () => Date\n}\n\n/**\n * Reads quota for a profile. MUST NOT throw: any failure or unknown value\n * resolves to `undefined`. Never fabricates a figure.\n */\nexport interface QuotaReader {\n readonly id: string\n supports(profile: QuotaProfile): boolean\n read(profile: QuotaProfile, ctx: QuotaReadContext): Promise<QuotaStatus | undefined>\n}\n","import type { QuotaBasis, QuotaCredits, QuotaStatus, QuotaWindow } from \"./types.js\"\nimport { quotaWindowSchema } from \"./types.js\"\n\n/** remainingPct from a used percentage, or undefined when it is not a finite 0-100 number. */\nexport function remainingPctFromUsed(usedPct: number | null | undefined): number | undefined {\n if (usedPct === null || usedPct === undefined || !Number.isFinite(usedPct)) return undefined\n if (usedPct < 0 || usedPct > 100) return undefined\n return 100 - usedPct\n}\n\n/** remainingPct from remaining/limit counts, or undefined when limit is not a positive finite number. */\nexport function remainingPctFromCounts(\n remaining: number | null | undefined,\n limit: number | null | undefined,\n): number | undefined {\n if (remaining === null || remaining === undefined || limit === null || limit === undefined) {\n return undefined\n }\n if (!Number.isFinite(remaining) || !Number.isFinite(limit) || limit <= 0) return undefined\n if (remaining < 0 || remaining > limit) return undefined\n return (remaining / limit) * 100\n}\n\nexport interface BuildQuotaStatusInput {\n profileRef: string\n basis: QuotaBasis\n source?: string\n now?: () => Date\n /** Candidate windows; any that fail validation are dropped, never repaired. */\n windows?: readonly unknown[]\n credits?: QuotaCredits\n}\n\n/**\n * Assemble a QuotaStatus. Invalid windows are omitted. Returns `undefined`\n * when there is nothing to report (no valid window and no credits).\n */\nexport function buildQuotaStatus(input: BuildQuotaStatusInput): QuotaStatus | undefined {\n const windows: QuotaWindow[] = []\n for (const candidate of input.windows ?? []) {\n const parsed = quotaWindowSchema.safeParse(candidate)\n if (parsed.success) windows.push(parsed.data)\n }\n if (windows.length === 0 && input.credits === undefined) return undefined\n return {\n profileRef: input.profileRef,\n asOf: (input.now ?? (() => new Date()))().toISOString(),\n basis: input.basis,\n ...(input.source !== undefined ? { source: input.source } : {}),\n windows,\n ...(input.credits !== undefined ? { credits: input.credits } : {}),\n }\n}\n","import type { QuotaProfile, QuotaReadContext, QuotaReader, QuotaStatus } from \"./types.js\"\n\n/** Identity helper that fixes the QuotaReader contract at definition time (AIP-43 handle). */\nexport function defineQuotaReader(reader: QuotaReader): QuotaReader {\n return reader\n}\n\nexport class QuotaReaderRegistry {\n private readonly readers = new Map<string, QuotaReader>()\n\n register(reader: QuotaReader): this {\n if (this.readers.has(reader.id)) {\n throw new Error(`quota reader already registered: ${reader.id}`)\n }\n this.readers.set(reader.id, reader)\n return this\n }\n\n list(): QuotaReader[] {\n return [...this.readers.values()]\n }\n\n /** First registered reader that supports the profile, in registration order. */\n find(profile: QuotaProfile): QuotaReader | undefined {\n for (const reader of this.readers.values()) {\n if (safeSupports(reader, profile)) return reader\n }\n return undefined\n }\n\n /** Read through the first supporting reader. Never throws; unknown -> undefined. */\n async read(profile: QuotaProfile, ctx: QuotaReadContext = {}): Promise<QuotaStatus | undefined> {\n const reader = this.find(profile)\n if (!reader) return undefined\n try {\n return await reader.read(profile, ctx)\n } catch {\n return undefined\n }\n }\n}\n\nfunction safeSupports(reader: QuotaReader, profile: QuotaProfile): boolean {\n try {\n return reader.supports(profile)\n } catch {\n return false\n }\n}\n","import { buildQuotaStatus, remainingPctFromCounts } from \"../build.js\"\nimport type { QuotaStatus } from \"../types.js\"\n\nexport interface AnthropicHeadersInput {\n profileRef: string\n headers: Headers | Record<string, string | undefined>\n now?: () => Date\n}\n\ninterface Dimension {\n id: string\n header: string\n unit: string\n}\n\nconst DIMENSIONS: readonly Dimension[] = [\n { id: \"requests-1m\", header: \"requests\", unit: \"requests\" },\n { id: \"tokens-1m\", header: \"tokens\", unit: \"tokens\" },\n { id: \"input-tokens-1m\", header: \"input-tokens\", unit: \"tokens\" },\n { id: \"output-tokens-1m\", header: \"output-tokens\", unit: \"tokens\" },\n]\n\n/** Read a header case-insensitively from a `Headers` or a plain record. */\nfunction headerGet(\n headers: Headers | Record<string, string | undefined>,\n name: string,\n): string | undefined {\n if (headers instanceof Headers) {\n return headers.get(name) ?? undefined\n }\n const lower = name.toLowerCase()\n for (const [key, value] of Object.entries(headers)) {\n if (key.toLowerCase() === lower) return value\n }\n return undefined\n}\n\n/** Parse a numeric rate-limit header; empty/garbage → undefined. */\nfunction parseCount(raw: string | undefined): number | undefined {\n if (raw === undefined) return undefined\n const trimmed = raw.trim()\n if (!trimmed) return undefined\n const n = Number(trimmed)\n return Number.isFinite(n) ? n : undefined\n}\n\n/** Normalize an RFC 3339 reset header to an ISO string; unparseable → undefined. */\nfunction parseReset(raw: string | undefined): string | undefined {\n if (raw === undefined) return undefined\n const trimmed = raw.trim()\n if (!trimmed) return undefined\n const date = new Date(trimmed)\n return Number.isNaN(date.getTime()) ? undefined : date.toISOString()\n}\n\n/**\n * PURE: build a QuotaStatus from Anthropic's DOCUMENTED rate-limit headers\n * `anthropic-ratelimit-{requests,tokens,input-tokens,output-tokens}-{limit,remaining,reset}`.\n * The undocumented `anthropic-ratelimit-unified-*` family is never read.\n * A dimension with a missing/non-numeric limit or remaining is skipped.\n */\nexport function quotaFromAnthropicHeaders(input: AnthropicHeadersInput): QuotaStatus | undefined {\n const windows: Record<string, unknown>[] = []\n for (const dimension of DIMENSIONS) {\n const prefix = `anthropic-ratelimit-${dimension.header}`\n const limit = parseCount(headerGet(input.headers, `${prefix}-limit`))\n const remaining = parseCount(headerGet(input.headers, `${prefix}-remaining`))\n if (limit === undefined || remaining === undefined) continue\n const remainingPct = remainingPctFromCounts(remaining, limit)\n if (remainingPct === undefined) continue\n const resetsAt = parseReset(headerGet(input.headers, `${prefix}-reset`))\n windows.push({\n id: dimension.id,\n remainingPct,\n limit,\n remaining,\n unit: dimension.unit,\n ...(resetsAt !== undefined ? { resetsAt } : {}),\n })\n }\n return buildQuotaStatus({\n profileRef: input.profileRef,\n basis: \"provider\",\n source: \"anthropic:ratelimit-headers\",\n now: input.now,\n windows,\n })\n}\n","import { z } from \"zod\"\nimport { buildQuotaStatus, remainingPctFromCounts } from \"../build.js\"\nimport { defineQuotaReader } from \"../registry.js\"\nimport type { QuotaCredits, QuotaProfile, QuotaReadContext, QuotaReader, QuotaStatus, QuotaWindow } from \"../types.js\"\n\nconst DEFAULT_BASE_URL = \"https://openrouter.ai/api/v1\"\nconst DEFAULT_TIMEOUT_MS = 4000\n\nconst OPENROUTER_KEY_SCHEMA = z\n .object({\n data: z\n .object({\n limit: z.number().nullable().optional(),\n limit_remaining: z.number().nullable().optional(),\n })\n .passthrough(),\n })\n .passthrough()\n\nexport interface OpenRouterQuotaReaderOptions {\n baseUrl?: string\n timeoutMs?: number\n}\n\n/** GET `${baseUrl}/key` and parse the body, never throwing. */\nasync function getKeyJson(\n baseUrl: string,\n secret: string,\n ctx: QuotaReadContext,\n timeoutMs: number,\n): Promise<unknown | undefined> {\n const controller = new AbortController()\n const timer = setTimeout(() => controller.abort(), timeoutMs)\n const onAbort = () => controller.abort()\n ctx.signal?.addEventListener(\"abort\", onAbort)\n try {\n const res = await (ctx.fetchImpl ?? fetch)(`${baseUrl}/key`, {\n method: \"GET\",\n headers: { authorization: `Bearer ${secret}` },\n signal: controller.signal,\n })\n if (!res.ok) return undefined\n return await res.json()\n } catch {\n return undefined\n } finally {\n clearTimeout(timer)\n ctx.signal?.removeEventListener(\"abort\", onAbort)\n }\n}\n\n/**\n * OpenRouter quota reader: `GET /key` exposes the key's USD cap and remaining\n * headroom. The whole read is wrapped so it never throws; an uncapped key\n * (`limit_remaining` null/absent) yields no window and no credits.\n */\nexport function createOpenRouterQuotaReader(opts?: OpenRouterQuotaReaderOptions): QuotaReader {\n const baseUrl = opts?.baseUrl ?? DEFAULT_BASE_URL\n const timeoutMs = opts?.timeoutMs ?? DEFAULT_TIMEOUT_MS\n\n return defineQuotaReader({\n id: \"openrouter-key\",\n supports: (profile: QuotaProfile): boolean =>\n profile.endpoint === \"openrouter\" && profile.method === \"api-key\",\n read: async (\n profile: QuotaProfile,\n ctx: QuotaReadContext,\n ): Promise<QuotaStatus | undefined> => {\n try {\n if (!profile.credentialRef) return undefined\n if (!ctx.resolveCredential) return undefined\n const secret = await ctx.resolveCredential(profile.credentialRef).catch(() => undefined)\n if (!secret) return undefined\n\n const body = await getKeyJson(baseUrl, secret, ctx, timeoutMs)\n if (body === undefined) return undefined\n const parsed = OPENROUTER_KEY_SCHEMA.safeParse(body)\n if (!parsed.success) return undefined\n\n const { limit, limit_remaining } = parsed.data.data\n const windows: QuotaWindow[] = []\n if (\n typeof limit === \"number\" &&\n Number.isFinite(limit) &&\n typeof limit_remaining === \"number\" &&\n Number.isFinite(limit_remaining)\n ) {\n const remainingPct = remainingPctFromCounts(limit_remaining, limit)\n if (remainingPct !== undefined) {\n windows.push({\n id: \"key_limit\",\n remainingPct,\n remaining: limit_remaining,\n limit,\n unit: \"USD\",\n })\n }\n }\n let credits: QuotaCredits | undefined\n if (typeof limit_remaining === \"number\" && Number.isFinite(limit_remaining)) {\n credits = { balanceUsd: limit_remaining, currency: \"USD\" }\n }\n\n return buildQuotaStatus({\n profileRef: profile.profileRef,\n basis: \"provider\",\n source: \"openrouter:/key\",\n now: ctx.now,\n windows,\n ...(credits !== undefined ? { credits } : {}),\n })\n } catch {\n return undefined\n }\n },\n })\n}\n","/**\n * @agentproto/model-intel — model intelligence primitives.\n *\n * Unified, provider-neutral quota status (AIP-67): one QuotaStatus shape with a\n * mandatory `basis`, a never-throw QuotaReader contract, and a reader registry.\n * Unknown is omitted, never fabricated.\n *\n * @see https://agentproto.sh/docs/aip-67\n */\n\nexport const SPEC_NAME = \"quota/v1\" as const\nexport const SPEC_VERSION = \"1.0.0-alpha\" as const\n\nexport {\n QUOTA_BASES,\n quotaCreditsSchema,\n quotaStatusSchema,\n quotaWindowSchema,\n} from \"./types.js\"\nexport type {\n QuotaBasis,\n QuotaCredits,\n QuotaProfile,\n QuotaReadContext,\n QuotaReader,\n QuotaStatus,\n QuotaWindow,\n} from \"./types.js\"\nexport {\n buildQuotaStatus,\n remainingPctFromCounts,\n remainingPctFromUsed,\n} from \"./build.js\"\nexport type { BuildQuotaStatusInput } from \"./build.js\"\nexport { QuotaReaderRegistry, defineQuotaReader } from \"./registry.js\"\nexport { quotaFromAnthropicHeaders } from \"./readers/anthropic.js\"\nexport { createOpenRouterQuotaReader } from \"./readers/openrouter.js\"\n"]}
1
+ {"version":3,"sources":["../src/types.ts","../src/build.ts","../src/registry.ts","../src/catalog-diff.ts","../src/readers/anthropic.ts","../src/readers/codex.ts","../src/readers/moonshot.ts","../src/readers/openrouter.ts","../src/index.ts"],"names":["remainingPct","z","DEFAULT_BASE_URL","DEFAULT_TIMEOUT_MS"],"mappings":"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","file":"index.mjs","sourcesContent":["import { z } from \"zod\"\n\n/**\n * Where a quota figure came from. `provider` is a documented provider\n * surface; `observed` is derived from this host's own traffic; `estimated`\n * is a local computation; `probe` is an undocumented surface reached through\n * a connector and MUST NOT be emitted by core readers.\n */\nexport const QUOTA_BASES = [\"provider\", \"probe\", \"observed\", \"estimated\"] as const\nexport type QuotaBasis = (typeof QUOTA_BASES)[number]\n\nconst isoTimestamp = z.string().refine((s) => !Number.isNaN(Date.parse(s)), {\n message: \"expected an ISO-8601 timestamp\",\n})\n\nexport const quotaWindowSchema = z.object({\n /** Open string: \"5h\", \"7d\", \"month\", \"minute\", \"day\", \"primary\", \"key_limit\", ... */\n id: z.string().min(1),\n /** 0-100. Required: a window that cannot state it is not emitted. */\n remainingPct: z.number().min(0).max(100),\n used: z.number().optional(),\n limit: z.number().optional(),\n remaining: z.number().optional(),\n unit: z.string().optional(),\n resetsAt: isoTimestamp.optional(),\n})\nexport type QuotaWindow = z.infer<typeof quotaWindowSchema>\n\nexport const quotaCreditsSchema = z.object({\n balanceUsd: z.number().optional(),\n unlimited: z.boolean().optional(),\n currency: z.string().min(1),\n})\nexport type QuotaCredits = z.infer<typeof quotaCreditsSchema>\n\nexport const quotaStatusSchema = z.object({\n profileRef: z.string().min(1),\n asOf: isoTimestamp,\n basis: z.enum(QUOTA_BASES),\n source: z.string().optional(),\n windows: z.array(quotaWindowSchema),\n credits: quotaCreditsSchema.optional(),\n})\nexport type QuotaStatus = z.infer<typeof quotaStatusSchema>\n\n/** The minimal auth-profile projection a reader needs. */\nexport interface QuotaProfile {\n profileRef: string\n endpoint: string\n method: \"oauth-bearer\" | \"api-key\"\n credentialRef?: string\n}\n\nexport interface QuotaReadContext {\n signal?: AbortSignal\n /** Resolve a profile's opaque credentialRef to a bearer secret. */\n resolveCredential?(ref: string): Promise<string | undefined>\n fetchImpl?: typeof fetch\n now?: () => Date\n}\n\n/**\n * Reads quota for a profile. MUST NOT throw: any failure or unknown value\n * resolves to `undefined`. Never fabricates a figure.\n */\nexport interface QuotaReader {\n readonly id: string\n supports(profile: QuotaProfile): boolean\n read(profile: QuotaProfile, ctx: QuotaReadContext): Promise<QuotaStatus | undefined>\n}\n","import type { QuotaBasis, QuotaCredits, QuotaStatus, QuotaWindow } from \"./types.js\"\nimport { quotaWindowSchema } from \"./types.js\"\n\n/** remainingPct from a used percentage, or undefined when it is not a finite 0-100 number. */\nexport function remainingPctFromUsed(usedPct: number | null | undefined): number | undefined {\n if (usedPct === null || usedPct === undefined || !Number.isFinite(usedPct)) return undefined\n if (usedPct < 0 || usedPct > 100) return undefined\n return 100 - usedPct\n}\n\n/** remainingPct from remaining/limit counts, or undefined when limit is not a positive finite number. */\nexport function remainingPctFromCounts(\n remaining: number | null | undefined,\n limit: number | null | undefined,\n): number | undefined {\n if (remaining === null || remaining === undefined || limit === null || limit === undefined) {\n return undefined\n }\n if (!Number.isFinite(remaining) || !Number.isFinite(limit) || limit <= 0) return undefined\n if (remaining < 0 || remaining > limit) return undefined\n return (remaining / limit) * 100\n}\n\nexport interface BuildQuotaStatusInput {\n profileRef: string\n basis: QuotaBasis\n source?: string\n now?: () => Date\n /** Candidate windows; any that fail validation are dropped, never repaired. */\n windows?: readonly unknown[]\n credits?: QuotaCredits\n}\n\n/**\n * Assemble a QuotaStatus. Invalid windows are omitted. Returns `undefined`\n * when there is nothing to report (no valid window and no credits).\n */\nexport function buildQuotaStatus(input: BuildQuotaStatusInput): QuotaStatus | undefined {\n const windows: QuotaWindow[] = []\n for (const candidate of input.windows ?? []) {\n const parsed = quotaWindowSchema.safeParse(candidate)\n if (parsed.success) windows.push(parsed.data)\n }\n if (windows.length === 0 && input.credits === undefined) return undefined\n return {\n profileRef: input.profileRef,\n asOf: (input.now ?? (() => new Date()))().toISOString(),\n basis: input.basis,\n ...(input.source !== undefined ? { source: input.source } : {}),\n windows,\n ...(input.credits !== undefined ? { credits: input.credits } : {}),\n }\n}\n","import type { QuotaProfile, QuotaReadContext, QuotaReader, QuotaStatus } from \"./types.js\"\n\n/** Identity helper that fixes the QuotaReader contract at definition time (AIP-43 handle). */\nexport function defineQuotaReader(reader: QuotaReader): QuotaReader {\n return reader\n}\n\nexport class QuotaReaderRegistry {\n private readonly readers = new Map<string, QuotaReader>()\n\n register(reader: QuotaReader): this {\n if (this.readers.has(reader.id)) {\n throw new Error(`quota reader already registered: ${reader.id}`)\n }\n this.readers.set(reader.id, reader)\n return this\n }\n\n list(): QuotaReader[] {\n return [...this.readers.values()]\n }\n\n /** First registered reader that supports the profile, in registration order. */\n find(profile: QuotaProfile): QuotaReader | undefined {\n for (const reader of this.readers.values()) {\n if (safeSupports(reader, profile)) return reader\n }\n return undefined\n }\n\n /** Read through the first supporting reader. Never throws; unknown -> undefined. */\n async read(profile: QuotaProfile, ctx: QuotaReadContext = {}): Promise<QuotaStatus | undefined> {\n const reader = this.find(profile)\n if (!reader) return undefined\n try {\n return await reader.read(profile, ctx)\n } catch {\n return undefined\n }\n }\n}\n\nfunction safeSupports(reader: QuotaReader, profile: QuotaProfile): boolean {\n try {\n return reader.supports(profile)\n } catch {\n return false\n }\n}\n","/**\n * Catalog watch domain: pure diff of two catalog snapshots.\n * No I/O — mirrors the pure-evaluator pattern of `build.ts`.\n */\n\nexport interface CatalogSnapshotEntry {\n /** Catalog model id, e.g. \"claude-sonnet-5-5\". The snapshot's own key. */\n id: string\n inputPer1M: number\n outputPer1M: number\n /** Derived: true when inputPer1M === 0 && outputPer1M === 0. */\n free: boolean\n}\n\n/** Keyed by CatalogSnapshotEntry.id. */\nexport type CatalogSnapshot = Record<string, CatalogSnapshotEntry>\n\nexport interface CatalogPriceChange {\n id: string\n prev: Pick<CatalogSnapshotEntry, \"inputPer1M\" | \"outputPer1M\">\n next: Pick<CatalogSnapshotEntry, \"inputPer1M\" | \"outputPer1M\">\n}\n\nexport interface CatalogDiff {\n /** ids present in `next`, absent from `prev`. */\n appeared: string[]\n /** ids present in `prev`, absent from `next`. */\n removed: string[]\n /** same id in both, inputPer1M or outputPer1M differs. Price-only — an id\n * in `appeared`/`removed` never also appears here. */\n priceChanged: CatalogPriceChange[]\n /** id was free in prev, not free in next (and still present in both —\n * already counted in priceChanged too if the price itself differs). */\n freeToPaid: string[]\n /** id was not free in prev, free in next. */\n paidToFree: string[]\n}\n\nfunction isFree(entry: CatalogSnapshotEntry): boolean {\n return entry.inputPer1M === 0 && entry.outputPer1M === 0\n}\n\nexport function diffCatalog(prev: CatalogSnapshot, next: CatalogSnapshot): CatalogDiff {\n const appeared: string[] = []\n const removed: string[] = []\n const priceChanged: CatalogPriceChange[] = []\n const freeToPaid: string[] = []\n const paidToFree: string[] = []\n\n for (const id of Object.keys(next)) {\n if (!(id in prev)) appeared.push(id)\n }\n for (const id of Object.keys(prev)) {\n if (!(id in next)) removed.push(id)\n }\n\n for (const id of Object.keys(next)) {\n const before = prev[id]\n // Object.keys(next) guarantees `after` exists; the guard satisfies noUncheckedIndexedAccess.\n const after = next[id]\n if (!before || !after) continue\n if (before.inputPer1M !== after.inputPer1M || before.outputPer1M !== after.outputPer1M) {\n priceChanged.push({\n id,\n prev: { inputPer1M: before.inputPer1M, outputPer1M: before.outputPer1M },\n next: { inputPer1M: after.inputPer1M, outputPer1M: after.outputPer1M },\n })\n }\n const wasFree = isFree(before)\n const isNowFree = isFree(after)\n if (wasFree && !isNowFree) freeToPaid.push(id)\n if (!wasFree && isNowFree) paidToFree.push(id)\n }\n\n return {\n appeared: appeared.sort(),\n removed: removed.sort(),\n priceChanged: priceChanged.sort((a, b) => (a.id < b.id ? -1 : a.id > b.id ? 1 : 0)),\n freeToPaid: freeToPaid.sort(),\n paidToFree: paidToFree.sort(),\n }\n}\n","import { buildQuotaStatus, remainingPctFromCounts } from \"../build.js\"\nimport type { QuotaStatus } from \"../types.js\"\n\nexport interface AnthropicHeadersInput {\n profileRef: string\n headers: Headers | Record<string, string | undefined>\n now?: () => Date\n}\n\ninterface Dimension {\n id: string\n header: string\n unit: string\n}\n\nconst DIMENSIONS: readonly Dimension[] = [\n { id: \"requests-1m\", header: \"requests\", unit: \"requests\" },\n { id: \"tokens-1m\", header: \"tokens\", unit: \"tokens\" },\n { id: \"input-tokens-1m\", header: \"input-tokens\", unit: \"tokens\" },\n { id: \"output-tokens-1m\", header: \"output-tokens\", unit: \"tokens\" },\n]\n\n/** Read a header case-insensitively from a `Headers` or a plain record. */\nfunction headerGet(\n headers: Headers | Record<string, string | undefined>,\n name: string,\n): string | undefined {\n if (headers instanceof Headers) {\n return headers.get(name) ?? undefined\n }\n const lower = name.toLowerCase()\n for (const [key, value] of Object.entries(headers)) {\n if (key.toLowerCase() === lower) return value\n }\n return undefined\n}\n\n/** Parse a numeric rate-limit header; empty/garbage → undefined. */\nfunction parseCount(raw: string | undefined): number | undefined {\n if (raw === undefined) return undefined\n const trimmed = raw.trim()\n if (!trimmed) return undefined\n const n = Number(trimmed)\n return Number.isFinite(n) ? n : undefined\n}\n\n/** Normalize an RFC 3339 reset header to an ISO string; unparseable → undefined. */\nfunction parseReset(raw: string | undefined): string | undefined {\n if (raw === undefined) return undefined\n const trimmed = raw.trim()\n if (!trimmed) return undefined\n const date = new Date(trimmed)\n return Number.isNaN(date.getTime()) ? undefined : date.toISOString()\n}\n\n/**\n * PURE: build a QuotaStatus from Anthropic's DOCUMENTED rate-limit headers\n * `anthropic-ratelimit-{requests,tokens,input-tokens,output-tokens}-{limit,remaining,reset}`.\n * The undocumented `anthropic-ratelimit-unified-*` family is never read.\n * A dimension with a missing/non-numeric limit or remaining is skipped.\n */\nexport function quotaFromAnthropicHeaders(input: AnthropicHeadersInput): QuotaStatus | undefined {\n const windows: Record<string, unknown>[] = []\n for (const dimension of DIMENSIONS) {\n const prefix = `anthropic-ratelimit-${dimension.header}`\n const limit = parseCount(headerGet(input.headers, `${prefix}-limit`))\n const remaining = parseCount(headerGet(input.headers, `${prefix}-remaining`))\n if (limit === undefined || remaining === undefined) continue\n const remainingPct = remainingPctFromCounts(remaining, limit)\n if (remainingPct === undefined) continue\n const resetsAt = parseReset(headerGet(input.headers, `${prefix}-reset`))\n windows.push({\n id: dimension.id,\n remainingPct,\n limit,\n remaining,\n unit: dimension.unit,\n ...(resetsAt !== undefined ? { resetsAt } : {}),\n })\n }\n return buildQuotaStatus({\n profileRef: input.profileRef,\n basis: \"provider\",\n source: \"anthropic:ratelimit-headers\",\n now: input.now,\n windows,\n })\n}\n","import { buildQuotaStatus } from \"../build.js\"\nimport type { QuotaCredits, QuotaStatus, QuotaWindow } from \"../types.js\"\n\n/** One side of a Codex `rate_limits` payload. */\nexport interface CodexRateLimitWindow {\n used_percent: number\n window_minutes?: number\n /** Epoch seconds. */\n reset_at?: number\n}\n\nexport interface CodexRateLimits {\n primary?: CodexRateLimitWindow\n secondary?: CodexRateLimitWindow\n}\n\nexport interface CodexCredits {\n has_credits?: boolean\n unlimited?: boolean\n balance?: number\n}\n\nexport interface CodexRateLimitsInput {\n profileRef: string\n rateLimits: CodexRateLimits\n credits?: CodexCredits\n now?: () => Date\n}\n\n/** remainingPct clamped to 0..100; a used percentage >100 maps to 0, kept. */\nfunction remainingPct(used: number): number {\n return Math.max(0, Math.min(100, 100 - used))\n}\n\nfunction windowFromSide(id: string, side: CodexRateLimitWindow | undefined): QuotaWindow | undefined {\n if (!side || typeof side.used_percent !== \"number\" || !Number.isFinite(side.used_percent)) {\n return undefined\n }\n const window: QuotaWindow = { id, remainingPct: remainingPct(side.used_percent) }\n if (typeof side.reset_at === \"number\" && Number.isFinite(side.reset_at)) {\n const date = new Date(side.reset_at * 1000)\n if (!Number.isNaN(date.getTime())) window.resetsAt = date.toISOString()\n }\n return window\n}\n\n/**\n * Pure parser for a Codex `rate_limits` payload: each present side becomes one\n * window and optional credits become a credits block. Never throws; a missing\n * or non-numeric field is dropped, a valid value (0 included) is kept.\n */\nexport function quotaFromCodexRateLimits(\n input: CodexRateLimitsInput,\n): QuotaStatus | undefined {\n try {\n const windows: QuotaWindow[] = []\n const primary = windowFromSide(\"primary\", input.rateLimits?.primary)\n if (primary) windows.push(primary)\n const secondary = windowFromSide(\"secondary\", input.rateLimits?.secondary)\n if (secondary) windows.push(secondary)\n\n let credits: QuotaCredits | undefined\n const raw = input.credits\n if (raw) {\n const hasBalance = typeof raw.balance === \"number\" && Number.isFinite(raw.balance)\n if (raw.unlimited === true || hasBalance) {\n credits = { currency: \"USD\" }\n if (raw.unlimited === true) credits.unlimited = true\n if (hasBalance) credits.balanceUsd = raw.balance\n }\n }\n\n return buildQuotaStatus({\n profileRef: input.profileRef,\n basis: \"provider\",\n source: \"codex:rate_limits\",\n now: input.now,\n windows,\n ...(credits !== undefined ? { credits } : {}),\n })\n } catch {\n return undefined\n }\n}\n","import { z } from \"zod\"\nimport { buildQuotaStatus } from \"../build.js\"\nimport { defineQuotaReader } from \"../registry.js\"\nimport type { QuotaProfile, QuotaReadContext, QuotaReader, QuotaStatus } from \"../types.js\"\n\nconst DEFAULT_BASE_URL = \"https://api.moonshot.ai\"\nconst DEFAULT_TIMEOUT_MS = 4000\n\nconst MOONSHOT_BALANCE_SCHEMA = z\n .object({\n data: z\n .object({\n available_balance: z.number(),\n })\n .passthrough(),\n })\n .passthrough()\n\nexport interface MoonshotQuotaReaderOptions {\n baseUrl?: string\n timeoutMs?: number\n}\n\n/** GET `${baseUrl}/v1/users/me/balance` and parse the body, never throwing. */\nasync function getBalanceJson(\n baseUrl: string,\n secret: string,\n ctx: QuotaReadContext,\n timeoutMs: number,\n): Promise<unknown | undefined> {\n const controller = new AbortController()\n const timer = setTimeout(() => controller.abort(), timeoutMs)\n const onAbort = () => controller.abort()\n ctx.signal?.addEventListener(\"abort\", onAbort)\n try {\n const res = await (ctx.fetchImpl ?? fetch)(`${baseUrl}/v1/users/me/balance`, {\n method: \"GET\",\n headers: { authorization: `Bearer ${secret}` },\n signal: controller.signal,\n })\n if (!res.ok) return undefined\n return await res.json()\n } catch {\n return undefined\n } finally {\n clearTimeout(timer)\n ctx.signal?.removeEventListener(\"abort\", onAbort)\n }\n}\n\n/**\n * Moonshot balance reader: `GET /v1/users/me/balance` exposes the account's\n * available USD balance, but no cap, so it is reported as credits-only (no\n * window). The whole read never throws; a balance of 0 or below is real data\n * and is kept as is.\n */\nexport function createMoonshotQuotaReader(opts?: MoonshotQuotaReaderOptions): QuotaReader {\n const baseUrl = opts?.baseUrl ?? DEFAULT_BASE_URL\n const timeoutMs = opts?.timeoutMs ?? DEFAULT_TIMEOUT_MS\n\n return defineQuotaReader({\n id: \"moonshot-balance\",\n supports: (profile: QuotaProfile): boolean =>\n profile.endpoint === \"moonshot\" && profile.method === \"api-key\",\n read: async (\n profile: QuotaProfile,\n ctx: QuotaReadContext,\n ): Promise<QuotaStatus | undefined> => {\n try {\n if (!profile.credentialRef) return undefined\n if (!ctx.resolveCredential) return undefined\n const secret = await ctx.resolveCredential(profile.credentialRef).catch(() => undefined)\n if (!secret) return undefined\n\n const body = await getBalanceJson(baseUrl, secret, ctx, timeoutMs)\n if (body === undefined) return undefined\n const parsed = MOONSHOT_BALANCE_SCHEMA.safeParse(body)\n if (!parsed.success) return undefined\n\n const balance = parsed.data.data.available_balance\n if (!Number.isFinite(balance)) return undefined\n\n return buildQuotaStatus({\n profileRef: profile.profileRef,\n basis: \"provider\",\n source: \"moonshot:/users/me/balance\",\n now: ctx.now,\n windows: [],\n credits: { balanceUsd: balance, currency: \"USD\" },\n })\n } catch {\n return undefined\n }\n },\n })\n}\n","import { z } from \"zod\"\nimport { buildQuotaStatus, remainingPctFromCounts } from \"../build.js\"\nimport { defineQuotaReader } from \"../registry.js\"\nimport type { QuotaCredits, QuotaProfile, QuotaReadContext, QuotaReader, QuotaStatus, QuotaWindow } from \"../types.js\"\n\nconst DEFAULT_BASE_URL = \"https://openrouter.ai/api/v1\"\nconst DEFAULT_TIMEOUT_MS = 4000\n\nconst OPENROUTER_KEY_SCHEMA = z\n .object({\n data: z\n .object({\n limit: z.number().nullable().optional(),\n limit_remaining: z.number().nullable().optional(),\n })\n .passthrough(),\n })\n .passthrough()\n\nexport interface OpenRouterQuotaReaderOptions {\n baseUrl?: string\n timeoutMs?: number\n}\n\n/** GET `${baseUrl}/key` and parse the body, never throwing. */\nasync function getKeyJson(\n baseUrl: string,\n secret: string,\n ctx: QuotaReadContext,\n timeoutMs: number,\n): Promise<unknown | undefined> {\n const controller = new AbortController()\n const timer = setTimeout(() => controller.abort(), timeoutMs)\n const onAbort = () => controller.abort()\n ctx.signal?.addEventListener(\"abort\", onAbort)\n try {\n const res = await (ctx.fetchImpl ?? fetch)(`${baseUrl}/key`, {\n method: \"GET\",\n headers: { authorization: `Bearer ${secret}` },\n signal: controller.signal,\n })\n if (!res.ok) return undefined\n return await res.json()\n } catch {\n return undefined\n } finally {\n clearTimeout(timer)\n ctx.signal?.removeEventListener(\"abort\", onAbort)\n }\n}\n\n/**\n * OpenRouter quota reader: `GET /key` exposes the key's USD cap and remaining\n * headroom. The whole read is wrapped so it never throws; an uncapped key\n * (`limit_remaining` null/absent) yields no window and no credits.\n */\nexport function createOpenRouterQuotaReader(opts?: OpenRouterQuotaReaderOptions): QuotaReader {\n const baseUrl = opts?.baseUrl ?? DEFAULT_BASE_URL\n const timeoutMs = opts?.timeoutMs ?? DEFAULT_TIMEOUT_MS\n\n return defineQuotaReader({\n id: \"openrouter-key\",\n supports: (profile: QuotaProfile): boolean =>\n profile.endpoint === \"openrouter\" && profile.method === \"api-key\",\n read: async (\n profile: QuotaProfile,\n ctx: QuotaReadContext,\n ): Promise<QuotaStatus | undefined> => {\n try {\n if (!profile.credentialRef) return undefined\n if (!ctx.resolveCredential) return undefined\n const secret = await ctx.resolveCredential(profile.credentialRef).catch(() => undefined)\n if (!secret) return undefined\n\n const body = await getKeyJson(baseUrl, secret, ctx, timeoutMs)\n if (body === undefined) return undefined\n const parsed = OPENROUTER_KEY_SCHEMA.safeParse(body)\n if (!parsed.success) return undefined\n\n const { limit, limit_remaining } = parsed.data.data\n const windows: QuotaWindow[] = []\n if (\n typeof limit === \"number\" &&\n Number.isFinite(limit) &&\n typeof limit_remaining === \"number\" &&\n Number.isFinite(limit_remaining)\n ) {\n const remainingPct = remainingPctFromCounts(limit_remaining, limit)\n if (remainingPct !== undefined) {\n windows.push({\n id: \"key_limit\",\n remainingPct,\n remaining: limit_remaining,\n limit,\n unit: \"USD\",\n })\n }\n }\n let credits: QuotaCredits | undefined\n if (typeof limit_remaining === \"number\" && Number.isFinite(limit_remaining)) {\n credits = { balanceUsd: limit_remaining, currency: \"USD\" }\n }\n\n return buildQuotaStatus({\n profileRef: profile.profileRef,\n basis: \"provider\",\n source: \"openrouter:/key\",\n now: ctx.now,\n windows,\n ...(credits !== undefined ? { credits } : {}),\n })\n } catch {\n return undefined\n }\n },\n })\n}\n","/**\n * @agentproto/model-intel — model intelligence primitives.\n *\n * Unified, provider-neutral quota status (AIP-67): one QuotaStatus shape with a\n * mandatory `basis`, a never-throw QuotaReader contract, and a reader registry.\n * Unknown is omitted, never fabricated.\n *\n * @see https://agentproto.sh/docs/aip-67\n */\n\nexport const SPEC_NAME = \"quota/v1\" as const\nexport const SPEC_VERSION = \"1.0.0-alpha\" as const\n\nexport {\n QUOTA_BASES,\n quotaCreditsSchema,\n quotaStatusSchema,\n quotaWindowSchema,\n} from \"./types.js\"\nexport type {\n QuotaBasis,\n QuotaCredits,\n QuotaProfile,\n QuotaReadContext,\n QuotaReader,\n QuotaStatus,\n QuotaWindow,\n} from \"./types.js\"\nexport {\n buildQuotaStatus,\n remainingPctFromCounts,\n remainingPctFromUsed,\n} from \"./build.js\"\nexport type { BuildQuotaStatusInput } from \"./build.js\"\nexport { QuotaReaderRegistry, defineQuotaReader } from \"./registry.js\"\n\n/**\n * Catalog watch domain: pure diff of two priced catalog snapshots.\n */\nexport { diffCatalog } from \"./catalog-diff.js\"\nexport type {\n CatalogDiff,\n CatalogPriceChange,\n CatalogSnapshot,\n CatalogSnapshotEntry,\n} from \"./catalog-diff.js\"\nexport { quotaFromAnthropicHeaders } from \"./readers/anthropic.js\"\nexport { quotaFromCodexRateLimits } from \"./readers/codex.js\"\nexport type {\n CodexCredits,\n CodexRateLimits,\n CodexRateLimitsInput,\n CodexRateLimitWindow,\n} from \"./readers/codex.js\"\nexport { createMoonshotQuotaReader } from \"./readers/moonshot.js\"\nexport { createOpenRouterQuotaReader } from \"./readers/openrouter.js\"\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentproto/model-intel",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "description": "@agentproto/model-intel — model intelligence primitives. Unified, provider-neutral quota status (AIP-67): a QuotaStatus shape with a mandatory basis, a never-throw QuotaReader contract, and a reader registry. Unknown is omitted, never fabricated.",
5
5
  "keywords": [
6
6
  "agentproto",