@demicodes/provider 0.10.2 → 0.10.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -11,21 +11,9 @@ writing providers.
11
11
  `withProviderId`.
12
12
  - **HTTP helpers** — `redactSecretText`, `httpErrorCode`, `normalizeErrorCode`,
13
13
  `providerErrorFromUnknown`, `authStatusFromKey`, `httpRequestFailedEvent`.
14
- - **Quota** — optional `Provider.quota` (`ProviderQuota` / `ProviderQuotaSnapshot`),
15
- `createProviderQuota`, `ensureQuota`. See
16
- [docs/provider-quota.md](../../docs/provider-quota.md).
17
- - **Credentials** — optional `Provider.credentials` for multi-account pool + global
18
- `setActive` (subscription CLIs). See
19
- [docs/provider-global-credentials.md](../../docs/provider-global-credentials.md).
20
14
 
21
15
  ```ts
22
- import {
23
- defineProvider,
24
- createProviderQuota,
25
- ensureQuota,
26
- type ProviderCredentials,
27
- type ProviderQuota,
28
- } from '@demicodes/provider'
16
+ import { defineProvider, zeroUsage } from '@demicodes/provider'
29
17
  ```
30
18
 
31
19
  See [Add a Provider](../../docs/guides/add-a-provider.md). Part of
package/dist/index.d.mts CHANGED
@@ -1,12 +1,6 @@
1
- import { A as ProviderQuota, B as ProviderQuotaUnsupportedError, C as ProviderRuntimeFactory, D as ToolDefinition, E as ProviderServiceTier, F as ProviderQuotaProbeOptions, G as ensureQuota, H as ProviderQuotaWindowUnit, I as ProviderQuotaProbeResult, J as usedPercentFromRatio, K as severityFromUsedPercent, L as ProviderQuotaSeverity, M as ProviderQuotaObserveInput, N as ProviderQuotaPlan, O as CreateProviderQuotaOptions, P as ProviderQuotaProbeCost, R as ProviderQuotaSnapshot, S as ProviderRun, T as ProviderSelection, U as clampUsedPercent, V as ProviderQuotaWindow, W as createProviderQuota, _ as ProviderEvent, a as ModelPolicy, b as ProviderModelCost, c as ProviderAuthState, d as ProviderCredentialInfo, f as ProviderCredentialLoginOptions, g as ProviderCredentialsCapability, h as ProviderCredentials, i as InferenceSteer, j as ProviderQuotaCapability, k as EnsureQuotaOptions, l as ProviderCredentialActive, m as ProviderCredentialLoginResult, n as InferenceItem, o as Provider, p as ProviderCredentialLoginPending, q as unixSecondsToIso, r as InferenceRequest, s as ProviderAuth, t as AgentProvider, u as ProviderCredentialAddInput, v as ProviderFactoryDefinition, w as ProviderRuntimeState, x as ProviderModelList, y as ProviderModel, z as ProviderQuotaSource } from "./types-CYSnFAuT.mjs";
2
- import { FileExtension, ModelSelection, ThinkingCapability, ThinkingConfig, ToolResultContentBlock } from "@demicodes/core";
3
- //#region src/content.d.ts
4
- /**
5
- * Flattens a tool result to plain text for wire formats without native media
6
- * blocks; non-text blocks become `[<type>:<mediaType>]` placeholders.
7
- */
8
- declare function toolResultContentToText(output: ToolResultContentBlock[]): string;
9
- //#endregion
1
+ import { A as ProviderQuotaSnapshot, B as usedPercentFromRatio, C as ProviderQuotaCapability, D as ProviderQuotaProbeOptions, E as ProviderQuotaProbeCost, F as clampUsedPercent, I as createProviderQuota, L as ensureQuota, M as ProviderQuotaUnsupportedError, N as ProviderQuotaWindow, O as ProviderQuotaProbeResult, P as ProviderQuotaWindowUnit, R as severityFromUsedPercent, S as ProviderQuota, T as ProviderQuotaPlan, _ as ProviderSelection, a as ModelPolicy, b as CreateProviderQuotaOptions, c as ProviderAuthState, d as ProviderModel, f as ProviderModelCost, g as ProviderRuntimeState, h as ProviderRuntimeFactory, i as InferenceSteer, j as ProviderQuotaSource, k as ProviderQuotaSeverity, l as ProviderEvent, m as ProviderRun, n as InferenceItem, o as Provider, p as ProviderModelList, r as InferenceRequest, s as ProviderAuth, t as AgentProvider, u as ProviderFactoryDefinition, v as ProviderServiceTier, w as ProviderQuotaObserveInput, x as EnsureQuotaOptions, y as ToolDefinition, z as unixSecondsToIso } from "./types-jho3Wgsx.mjs";
2
+ import { FileExtension, ModelSelection, ThinkingCapability, ThinkingConfig } from "@demicodes/core";
3
+
10
4
  //#region src/provider.d.ts
11
5
  declare function defineProvider(definition: ProviderFactoryDefinition): Provider;
12
6
  declare function providerRuntime(provider: Provider, selection: ProviderSelection): Promise<AgentProvider> | AgentProvider;
@@ -47,8 +41,6 @@ type HeadersResolver = () => Record<string, string> | Promise<Record<string, str
47
41
  declare function clampPromptCacheKey(value: string): string;
48
42
  /** Replaces every occurrence of `secret` in `value` with a redaction marker. */
49
43
  declare function redactSecretText(value: string, secret: string | null | undefined): string;
50
- /** Masks bearer tokens and named credential fields in free-form auth error text. */
51
- declare function redactCredentialText(text: string, extraFieldPatterns?: readonly string[]): string;
52
44
  /** Maps an HTTP status (and response text) to a coarse provider error code. */
53
45
  declare function httpErrorCode(status: number, message: string): string | null;
54
46
  /** Classifies a provider error code/message into a coarse category, falling back to `code`. */
@@ -59,9 +51,7 @@ declare function providerErrorFromUnknown(error: unknown, secret: string | null
59
51
  declare function authStatusFromKey(resolveKey: SecretResolver, resolveHeaders: HeadersResolver | undefined, authHeader: string, providerLabel: string): Promise<ProviderAuthState>;
60
52
  /** Parses a Retry-After header (delta-seconds or HTTP-date) into milliseconds. */
61
53
  declare function retryAfterMsFromHeader(value: string | null): number | undefined;
62
- /** Reads a header as a finite number, or null when absent/blank/non-numeric. */
63
- declare function numberHeader(headers: Headers, name: string): number | null;
64
54
  /** Builds a redacted provider `error` event from a failed HTTP response. */
65
55
  declare function httpRequestFailedEvent(response: Response, secret: string | null | undefined, providerLabel: string): Promise<ProviderEvent>;
66
56
  //#endregion
67
- export { AgentProvider, type CreateProviderQuotaOptions, DEFAULT_ATTACHMENT_EXTENSIONS, type EnsureQuotaOptions, InferenceItem, InferenceRequest, InferenceSteer, ModelPolicy, type ModelSelectionFromCatalogOptions, Provider, ProviderAuth, ProviderAuthState, ProviderCredentialActive, ProviderCredentialAddInput, ProviderCredentialInfo, ProviderCredentialLoginOptions, ProviderCredentialLoginPending, ProviderCredentialLoginResult, ProviderCredentials, ProviderCredentialsCapability, ProviderEvent, ProviderFactoryDefinition, ProviderModel, ProviderModelCost, ProviderModelList, type ProviderQuota, type ProviderQuotaCapability, type ProviderQuotaObserveInput, type ProviderQuotaPlan, type ProviderQuotaProbeCost, type ProviderQuotaProbeOptions, type ProviderQuotaProbeResult, type ProviderQuotaSeverity, type ProviderQuotaSnapshot, type ProviderQuotaSource, ProviderQuotaUnsupportedError, type ProviderQuotaWindow, type ProviderQuotaWindowUnit, ProviderRun, ProviderRuntimeFactory, ProviderRuntimeState, ProviderSelection, ProviderServiceTier, ToolDefinition, applyModelPolicy, authStatusFromKey, clampPromptCacheKey, clampUsedPercent, createProviderQuota, defineProvider, ensureQuota, httpErrorCode, httpRequestFailedEvent, modelSelectionFromCatalog, normalizeErrorCode, numberHeader, providerErrorFromUnknown, providerRuntime, redactCredentialText, redactSecretText, retryAfterMsFromHeader, severityFromUsedPercent, thinkingCapabilitiesFromProviderModel, toolResultContentToText, unixSecondsToIso, usedPercentFromRatio, withProviderId };
57
+ export { AgentProvider, type CreateProviderQuotaOptions, DEFAULT_ATTACHMENT_EXTENSIONS, type EnsureQuotaOptions, InferenceItem, InferenceRequest, InferenceSteer, ModelPolicy, type ModelSelectionFromCatalogOptions, Provider, ProviderAuth, ProviderAuthState, ProviderEvent, ProviderFactoryDefinition, ProviderModel, ProviderModelCost, ProviderModelList, type ProviderQuota, type ProviderQuotaCapability, type ProviderQuotaObserveInput, type ProviderQuotaPlan, type ProviderQuotaProbeCost, type ProviderQuotaProbeOptions, type ProviderQuotaProbeResult, type ProviderQuotaSeverity, type ProviderQuotaSnapshot, type ProviderQuotaSource, ProviderQuotaUnsupportedError, type ProviderQuotaWindow, type ProviderQuotaWindowUnit, ProviderRun, ProviderRuntimeFactory, ProviderRuntimeState, ProviderSelection, ProviderServiceTier, ToolDefinition, applyModelPolicy, authStatusFromKey, clampPromptCacheKey, clampUsedPercent, createProviderQuota, defineProvider, ensureQuota, httpErrorCode, httpRequestFailedEvent, modelSelectionFromCatalog, normalizeErrorCode, providerErrorFromUnknown, providerRuntime, redactSecretText, retryAfterMsFromHeader, severityFromUsedPercent, thinkingCapabilitiesFromProviderModel, unixSecondsToIso, usedPercentFromRatio, withProviderId };
package/dist/index.mjs CHANGED
@@ -1,14 +1,4 @@
1
- import { VIDEO_FILE_EXTENSIONS } from "@demicodes/core";
2
1
  import { shortHash } from "@demicodes/utils";
3
- //#region src/content.ts
4
- /**
5
- * Flattens a tool result to plain text for wire formats without native media
6
- * blocks; non-text blocks become `[<type>:<mediaType>]` placeholders.
7
- */
8
- function toolResultContentToText(output) {
9
- return output.map((block) => block.type === "text" ? block.text : `[${block.type}:${block.source.mediaType}]`).join("\n");
10
- }
11
- //#endregion
12
2
  //#region src/provider.ts
13
3
  const runtimeFactorySymbol = Symbol("demi.provider.runtimeFactory");
14
4
  function defineProvider(definition) {
@@ -99,7 +89,7 @@ function modelSelectionFromCatalog(providerId, model, options = {}) {
99
89
  contextWindow: model?.contextWindow ?? 0,
100
90
  inputLimit: null,
101
91
  thinking: thinkingCapabilitiesFromProviderModel(model),
102
- acceptedExtensions: [...model?.supportsAttachments ? accepted : [], ...model?.supportsVideo ? VIDEO_FILE_EXTENSIONS : []]
92
+ acceptedExtensions: model?.supportsAttachments ? [...accepted] : []
103
93
  },
104
94
  thinking: options.thinking ?? null,
105
95
  serviceTierId: options.serviceTierId ?? null
@@ -118,21 +108,10 @@ function clampPromptCacheKey(value) {
118
108
  function redactSecretText(value, secret) {
119
109
  return secret ? value.split(secret).join("[redacted]") : value;
120
110
  }
121
- /** Masks bearer tokens and named credential fields in free-form auth error text. */
122
- function redactCredentialText(text, extraFieldPatterns = []) {
123
- const fields = [
124
- "access_token",
125
- "refresh_token",
126
- "id_token",
127
- ...extraFieldPatterns
128
- ];
129
- return text.replace(/Bearer\s+[A-Za-z0-9._~+/=-]+/g, "Bearer [REDACTED]").replace(new RegExp(`(${fields.join("|")})["'=:\\s]+[A-Za-z0-9._~+/=-]+`, "gi"), "$1=[REDACTED]");
130
- }
131
111
  /** Maps an HTTP status (and response text) to a coarse provider error code. */
132
112
  function httpErrorCode(status, message) {
133
113
  if (status === 401 || status === 403) return "auth_expired";
134
- if (status === 429) return "rate_limit";
135
- if (status === 408 || status === 409 || status === 425 || status >= 500) return "overloaded";
114
+ if (status === 408 || status === 409 || status === 425 || status === 429 || status >= 500) return "rate_limit";
136
115
  if (status === 400 && /context|too long|token/i.test(message)) return "context_length_exceeded";
137
116
  return null;
138
117
  }
@@ -140,9 +119,9 @@ function httpErrorCode(status, message) {
140
119
  function normalizeErrorCode(code, message) {
141
120
  const value = `${code ?? ""} ${message}`.toLowerCase();
142
121
  if (/context|too long|max.*token/.test(value)) return "context_length_exceeded";
143
- if (/rate|quota|usage|billing|balance|limit/.test(value)) return "rate_limit";
144
- if (/\bauth(?:entication|orization)?\b/.test(value) || /(?:invalid|expired).*(?:api|access|auth)[_\s-]*(?:key|token)/.test(value) || /(?:api|access|auth)[_\s-]*(?:key|token).*(?:invalid|expired)/.test(value)) return "auth_expired";
145
- if (/overload|unavailable|(?:server|internal|api)[_\s-]*error|timed?\s?out|fetch failed|network|socket|econn/.test(value)) return "overloaded";
122
+ if (/rate|quota|billing|limit/.test(value)) return "rate_limit";
123
+ if (/auth|unauth|invalid.*key|expired/.test(value)) return "auth_expired";
124
+ if (/overload|unavailable|timeout/.test(value)) return "overloaded";
146
125
  return code;
147
126
  }
148
127
  /** Builds a provider `error` event from an unknown thrown value, redacting `secret`. */
@@ -171,13 +150,6 @@ function retryAfterMsFromHeader(value) {
171
150
  const dateMs = Date.parse(value);
172
151
  if (Number.isFinite(dateMs)) return Math.max(0, dateMs - Date.now());
173
152
  }
174
- /** Reads a header as a finite number, or null when absent/blank/non-numeric. */
175
- function numberHeader(headers, name) {
176
- const raw = headers.get(name);
177
- if (raw == null || raw === "") return null;
178
- const n = Number(raw);
179
- return Number.isFinite(n) ? n : null;
180
- }
181
153
  /** Builds a redacted provider `error` event from a failed HTTP response. */
182
154
  async function httpRequestFailedEvent(response, secret, providerLabel) {
183
155
  const text = await response.text().catch(() => "");
@@ -235,14 +207,13 @@ function createProviderQuota(options) {
235
207
  };
236
208
  };
237
209
  const materialize = (partial, source) => {
238
- const previous = source === "observation" ? latest : null;
239
210
  const snapshot = {
240
211
  providerId: options.providerId,
241
212
  observedAt: (/* @__PURE__ */ new Date()).toISOString(),
242
213
  source,
243
- plan: partial.plan === void 0 ? previous?.plan ?? null : partial.plan,
244
- accountLabel: partial.accountLabel === void 0 ? previous?.accountLabel ?? null : partial.accountLabel,
245
- windows: previous ? mergeQuotaWindows(previous.windows, partial.windows) : partial.windows,
214
+ plan: partial.plan ?? null,
215
+ accountLabel: partial.accountLabel ?? null,
216
+ windows: partial.windows,
246
217
  raw: partial.raw
247
218
  };
248
219
  latest = snapshot;
@@ -251,13 +222,9 @@ function createProviderQuota(options) {
251
222
  const quota = {
252
223
  capability,
253
224
  latest: () => latest,
254
- clearLatest: () => {
255
- latest = null;
256
- },
257
225
  async probe(probeOptions = {}) {
258
226
  if (!options.canProbe) throw new ProviderQuotaUnsupportedError(options.providerId);
259
- const partial = await options.probe(probeOptions);
260
- return materialize(partial, "probe");
227
+ return materialize(await options.probe(probeOptions), "probe");
261
228
  }
262
229
  };
263
230
  if (options.observe) {
@@ -270,13 +237,6 @@ function createProviderQuota(options) {
270
237
  }
271
238
  return quota;
272
239
  }
273
- function mergeQuotaWindows(previous, observed) {
274
- const observedById = new Map(observed.map((window) => [window.id, window]));
275
- const merged = previous.map((window) => observedById.get(window.id) ?? window);
276
- const previousIds = new Set(previous.map((window) => window.id));
277
- for (const window of observed) if (!previousIds.has(window.id)) merged.push(window);
278
- return merged;
279
- }
280
240
  /** Clamp percent into 0–100 or null. */
281
241
  function clampUsedPercent(value) {
282
242
  if (typeof value !== "number" || !Number.isFinite(value)) return null;
@@ -309,4 +269,4 @@ function severityFromUsedPercent(usedPercent) {
309
269
  return "normal";
310
270
  }
311
271
  //#endregion
312
- export { DEFAULT_ATTACHMENT_EXTENSIONS, ProviderQuotaUnsupportedError, applyModelPolicy, authStatusFromKey, clampPromptCacheKey, clampUsedPercent, createProviderQuota, defineProvider, ensureQuota, httpErrorCode, httpRequestFailedEvent, modelSelectionFromCatalog, normalizeErrorCode, numberHeader, providerErrorFromUnknown, providerRuntime, redactCredentialText, redactSecretText, retryAfterMsFromHeader, severityFromUsedPercent, thinkingCapabilitiesFromProviderModel, toolResultContentToText, unixSecondsToIso, usedPercentFromRatio, withProviderId };
272
+ export { DEFAULT_ATTACHMENT_EXTENSIONS, ProviderQuotaUnsupportedError, applyModelPolicy, authStatusFromKey, clampPromptCacheKey, clampUsedPercent, createProviderQuota, defineProvider, ensureQuota, httpErrorCode, httpRequestFailedEvent, modelSelectionFromCatalog, normalizeErrorCode, providerErrorFromUnknown, providerRuntime, redactSecretText, retryAfterMsFromHeader, severityFromUsedPercent, thinkingCapabilitiesFromProviderModel, unixSecondsToIso, usedPercentFromRatio, withProviderId };
@@ -1,4 +1,5 @@
1
- import { S as ProviderRun, _ as ProviderEvent, i as InferenceSteer, r as InferenceRequest, t as AgentProvider } from "./types-CYSnFAuT.mjs";
1
+ import { i as InferenceSteer, l as ProviderEvent, m as ProviderRun, r as InferenceRequest, t as AgentProvider } from "./types-jho3Wgsx.mjs";
2
+
2
3
  //#region src/testing.d.ts
3
4
  /**
4
5
  * Scripted provider for testing. Each "turn" is a list of events to yield.
@@ -1,4 +1,5 @@
1
- import { ModelSelection, ProviderErrorDiagnostics, ThinkingConfig, ThinkingEffort, TokenUsage, ToolResultContentBlock, UserContentBlock } from "@demicodes/core";
1
+ import { ModelSelection, ThinkingConfig, ThinkingEffort, TokenUsage, ToolResultContentBlock, UserContentBlock } from "@demicodes/core";
2
+
2
3
  //#region src/quota.d.ts
3
4
  /**
4
5
  * Unified subscription / rate-limit quota surface for concrete providers.
@@ -49,8 +50,7 @@ type ProviderQuotaCapability = {
49
50
  } | {
50
51
  mode: 'supported';
51
52
  canProbe: boolean;
52
- canObserve: boolean;
53
- /** free = dedicated usage API; minimal_request = burns a tiny inference */
53
+ canObserve: boolean; /** free = dedicated usage API; minimal_request = burns a tiny inference */
54
54
  probeCost?: ProviderQuotaProbeCost;
55
55
  staleAfterMs?: number;
56
56
  };
@@ -73,11 +73,6 @@ interface ProviderQuota {
73
73
  capability(): ProviderQuotaCapability;
74
74
  probe(options?: ProviderQuotaProbeOptions): Promise<ProviderQuotaSnapshot>;
75
75
  latest(): ProviderQuotaSnapshot | null;
76
- /**
77
- * Drop the in-memory latest snapshot (e.g. after credentials.setActive so the
78
- * next ensureQuota/probe does not show the previous account).
79
- */
80
- clearLatest?(): void;
81
76
  /**
82
77
  * Optional passive update from a vendor HTTP response (headers, etc.).
83
78
  * Returns the new snapshot when observation succeeded; otherwise null.
@@ -206,24 +201,13 @@ type ProviderEvent = {
206
201
  toolUseId: string;
207
202
  toolName: string;
208
203
  input: unknown;
209
- } |
210
- /**
211
- * End of a model response. `usage` MUST be the usage of a single API request
212
- * — the final one when the provider made several internally (e.g. a CLI turn
213
- * with tool calls). The agent anchors its context-size estimation on it
214
- * (input + output + cache reads/writes ≈ the context the next request will
215
- * carry), so a turn-cumulative total here inflates the estimate and triggers
216
- * spurious compaction.
217
- */
218
- {
204
+ } | {
219
205
  type: 'response';
220
206
  usage: TokenUsage;
221
207
  } | {
222
208
  type: 'error';
223
209
  message: string;
224
- code: string | null;
225
- diagnostics?: ProviderErrorDiagnostics;
226
- /** Server-suggested retry delay (e.g. from a Retry-After header), if any. */
210
+ code: string | null; /** Server-suggested retry delay (e.g. from a Retry-After header), if any. */
227
211
  retryAfterMs?: number;
228
212
  } | {
229
213
  type: 'abort';
@@ -265,113 +249,6 @@ type ProviderAuthState = {
265
249
  interface ProviderAuth {
266
250
  status(): Promise<ProviderAuthState> | ProviderAuthState;
267
251
  }
268
- /** Public metadata only — never tokens, cookies, or raw auth files. */
269
- interface ProviderCredentialInfo {
270
- /** Stable id within this provider (not globally unique across providers). */
271
- id: string;
272
- /** Human label: email, account id, or import tag. */
273
- label: string;
274
- /** Optional secondary display (plan name, issuer, …). */
275
- detail?: string | null;
276
- /** ISO-8601 when this entry was last imported or refreshed in the pool. */
277
- updatedAt?: string | null;
278
- }
279
- interface ProviderCredentialActive {
280
- credentialId: string | null;
281
- /** Same shape as auth status, for the active credential. */
282
- status: ProviderAuthState;
283
- }
284
- /**
285
- * User-facing material issued mid-flow by a device-code login: the product
286
- * relays it to the user, who completes login from any browser on any device.
287
- */
288
- interface ProviderCredentialLoginPending {
289
- /** URL the user opens to confirm the login. */
290
- verificationUrl: string;
291
- /** One-time code the user enters at the verification URL (device-code flows). */
292
- userCode?: string | null;
293
- /** ISO-8601 expiry of the code, when the vendor exposes one. */
294
- expiresAt?: string | null;
295
- /**
296
- * True when the vendor displays a code AFTER approval that the user must
297
- * bring back; the product collects it via `promptForCode`.
298
- */
299
- requiresCodeInput?: boolean;
300
- }
301
- interface ProviderCredentialLoginOptions {
302
- /** Abort the login flow. */
303
- signal?: AbortSignal;
304
- /** Fires once when the flow issues user-facing material (device-code login). */
305
- onPending?: (pending: ProviderCredentialLoginPending) => void;
306
- /** Collects the code the user copied back from the vendor page (`requiresCodeInput` flows). */
307
- promptForCode?: () => Promise<string>;
308
- }
309
- /**
310
- * Login invoke result. Every login flow runs the vendor's public protocol
311
- * natively, imports the resulting material into the pool, and returns the
312
- * pool `credentialId`.
313
- */
314
- type ProviderCredentialLoginResult = {
315
- status: 'completed';
316
- credentialId: string;
317
- } | {
318
- status: 'cancelled';
319
- } | {
320
- status: 'unavailable';
321
- message: string;
322
- } | {
323
- status: 'failed';
324
- message: string;
325
- };
326
- /**
327
- * Provider-specific add payloads. Concrete packages document accepted variants.
328
- * Do not put secrets on browser-visible control protocols.
329
- */
330
- type ProviderCredentialAddInput = {
331
- [key: string]: unknown;
332
- };
333
- type ProviderCredentialsCapability = {
334
- mode: 'none';
335
- } | {
336
- mode: 'supported';
337
- /** Can run the vendor's native login flow (`beginLogin`). */
338
- canBeginLogin?: boolean;
339
- /** Can import from the vendor default location into the pool. */
340
- canImportDefault?: boolean;
341
- /** Can register externally supplied material (`add`). */
342
- canAdd?: boolean;
343
- /** Pool can hold more than one credential. */
344
- multi?: boolean;
345
- };
346
- /**
347
- * Multi-credential pool + process-global active switch.
348
- * See `docs/provider-global-credentials.md`.
349
- */
350
- interface ProviderCredentials {
351
- capability(): ProviderCredentialsCapability;
352
- list(): Promise<ProviderCredentialInfo[]> | ProviderCredentialInfo[];
353
- getActive(): Promise<ProviderCredentialActive> | ProviderCredentialActive;
354
- /**
355
- * Make `credentialId` the process-global active credential for this provider.
356
- * Subsequent auth / quota / inference use it.
357
- */
358
- setActive(credentialId: string): Promise<ProviderCredentialActive> | ProviderCredentialActive;
359
- /**
360
- * Run the vendor's public login protocol natively (device code or copy-back
361
- * OAuth), surfacing user-facing material via `onPending`. On completion the
362
- * material is imported into the pool and its `credentialId` is returned.
363
- */
364
- beginLogin?(options?: ProviderCredentialLoginOptions): Promise<ProviderCredentialLoginResult>;
365
- /**
366
- * Snapshot current vendor-default material into the demi pool.
367
- * Assigns a stable `id` and returns public metadata (no secrets).
368
- */
369
- importDefault?(): Promise<ProviderCredentialInfo>;
370
- /** Register material supplied by the product. */
371
- add?(input: ProviderCredentialAddInput): Promise<ProviderCredentialInfo>;
372
- /** Remove a pool entry. */
373
- remove?(credentialId: string): Promise<void>;
374
- }
375
252
  type ProviderRuntimeState = {
376
253
  status: 'unknown';
377
254
  message?: string;
@@ -391,8 +268,6 @@ interface Provider {
391
268
  auth?: ProviderAuth;
392
269
  /** Optional subscription / rate-limit quota surface (`@demicodes/provider` quota helpers). */
393
270
  quota?: ProviderQuota;
394
- /** Optional multi-credential pool + global active switch. */
395
- credentials?: ProviderCredentials;
396
271
  state?(): Promise<ProviderRuntimeState> | ProviderRuntimeState;
397
272
  listModels?(): Promise<ProviderModelList> | ProviderModelList;
398
273
  }
@@ -422,10 +297,6 @@ interface ProviderModel {
422
297
  outputLimit: number | null;
423
298
  supportsTools: boolean | null;
424
299
  supportsAttachments: boolean | null;
425
- /** Whether the model accepts native video input (not frame extraction). Most models
426
- * (all current Anthropic/Claude Code models) do not — their API has no video block.
427
- * Optional: unset/undefined means "no video", so existing catalogs need no change. */
428
- supportsVideo?: boolean | null;
429
300
  supportsReasoning: boolean | null;
430
301
  supportedThinkingEfforts: ThinkingEffort[] | null;
431
302
  defaultThinkingEffort: ThinkingEffort | null;
@@ -453,4 +324,4 @@ type ModelPolicy = {
453
324
  default?: string;
454
325
  };
455
326
  //#endregion
456
- export { ProviderQuota as A, ProviderQuotaUnsupportedError as B, ProviderRuntimeFactory as C, ToolDefinition as D, ProviderServiceTier as E, ProviderQuotaProbeOptions as F, ensureQuota as G, ProviderQuotaWindowUnit as H, ProviderQuotaProbeResult as I, usedPercentFromRatio as J, severityFromUsedPercent as K, ProviderQuotaSeverity as L, ProviderQuotaObserveInput as M, ProviderQuotaPlan as N, CreateProviderQuotaOptions as O, ProviderQuotaProbeCost as P, ProviderQuotaSnapshot as R, ProviderRun as S, ProviderSelection as T, clampUsedPercent as U, ProviderQuotaWindow as V, createProviderQuota as W, ProviderEvent as _, ModelPolicy as a, ProviderModelCost as b, ProviderAuthState as c, ProviderCredentialInfo as d, ProviderCredentialLoginOptions as f, ProviderCredentialsCapability as g, ProviderCredentials as h, InferenceSteer as i, ProviderQuotaCapability as j, EnsureQuotaOptions as k, ProviderCredentialActive as l, ProviderCredentialLoginResult as m, InferenceItem as n, Provider as o, ProviderCredentialLoginPending as p, unixSecondsToIso as q, InferenceRequest as r, ProviderAuth as s, AgentProvider as t, ProviderCredentialAddInput as u, ProviderFactoryDefinition as v, ProviderRuntimeState as w, ProviderModelList as x, ProviderModel as y, ProviderQuotaSource as z };
327
+ export { ProviderQuotaSnapshot as A, usedPercentFromRatio as B, ProviderQuotaCapability as C, ProviderQuotaProbeOptions as D, ProviderQuotaProbeCost as E, clampUsedPercent as F, createProviderQuota as I, ensureQuota as L, ProviderQuotaUnsupportedError as M, ProviderQuotaWindow as N, ProviderQuotaProbeResult as O, ProviderQuotaWindowUnit as P, severityFromUsedPercent as R, ProviderQuota as S, ProviderQuotaPlan as T, ProviderSelection as _, ModelPolicy as a, CreateProviderQuotaOptions as b, ProviderAuthState as c, ProviderModel as d, ProviderModelCost as f, ProviderRuntimeState as g, ProviderRuntimeFactory as h, InferenceSteer as i, ProviderQuotaSource as j, ProviderQuotaSeverity as k, ProviderEvent as l, ProviderRun as m, InferenceItem as n, Provider as o, ProviderModelList as p, InferenceRequest as r, ProviderAuth as s, AgentProvider as t, ProviderFactoryDefinition as u, ProviderServiceTier as v, ProviderQuotaObserveInput as w, EnsureQuotaOptions as x, ToolDefinition as y, unixSecondsToIso as z };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@demicodes/provider",
3
3
  "description": "Provider contract and shared building blocks for Demi inference adapters.",
4
- "version": "0.10.2",
4
+ "version": "0.10.3",
5
5
  "private": false,
6
6
  "type": "module",
7
7
  "exports": {
@@ -12,15 +12,11 @@
12
12
  "./testing": {
13
13
  "types": "./dist/testing.d.mts",
14
14
  "import": "./dist/testing.mjs"
15
- },
16
- "./credentials-pool": {
17
- "types": "./dist/credentials-pool.d.mts",
18
- "import": "./dist/credentials-pool.mjs"
19
15
  }
20
16
  },
21
17
  "dependencies": {
22
- "@demicodes/core": "^0.10.2",
23
- "@demicodes/utils": "^0.10.2"
18
+ "@demicodes/core": "^0.10.3",
19
+ "@demicodes/utils": "^0.10.3"
24
20
  },
25
21
  "license": "Apache-2.0",
26
22
  "main": "./dist/index.mjs",
@@ -1,60 +0,0 @@
1
- import { d as ProviderCredentialInfo } from "./types-CYSnFAuT.mjs";
2
- //#region src/credentials-pool.d.ts
3
- interface CredentialEntryMeta {
4
- id: string;
5
- label: string;
6
- detail?: string | null;
7
- updatedAt: string;
8
- source?: string | null;
9
- /** Stable account key for upsert on re-import (email, accountId, entryKey, …). */
10
- identityKey?: string | null;
11
- }
12
- interface FileCredentialPoolOptions {
13
- /** Demi state root ($DEMI_HOME / ~/.demi). */
14
- stateDir?: string;
15
- /** Subdir under credentials/ (e.g. codex, grok-build, claude-code). */
16
- providerKey: string;
17
- /** Secret filename inside each entry (auth.json, oauth.json). */
18
- secretFileName: string;
19
- }
20
- /**
21
- * Demi local state root (`$DEMI_HOME` / `~/.demi`). Canonical copy — host-local
22
- * re-exports this for its bridge layout.
23
- */
24
- declare function resolveDemiHome(explicit?: string): string;
25
- declare function credentialIdFromIdentity(identityKey: string | null | undefined, fallbackLabel: string): string;
26
- declare function newCredentialId(): string;
27
- declare class FileCredentialPool {
28
- readonly root: string;
29
- readonly secretFileName: string;
30
- constructor(options: FileCredentialPoolOptions);
31
- entriesDir(): string;
32
- entryDir(id: string): string;
33
- metaPath(id: string): string;
34
- secretPath(id: string): string;
35
- activePath(): string;
36
- list(): Promise<ProviderCredentialInfo[]>;
37
- listMeta(): Promise<CredentialEntryMeta[]>;
38
- readMeta(id: string): Promise<CredentialEntryMeta | null>;
39
- getActiveId(): Promise<string | null>;
40
- setActiveId(id: string): Promise<void>;
41
- clearActive(): Promise<void>;
42
- writeEntry(meta: CredentialEntryMeta, secretText: string): Promise<CredentialEntryMeta>;
43
- readSecretText(id: string): Promise<string>;
44
- remove(id: string): Promise<void>;
45
- private tmpPath;
46
- /**
47
- * Serializes pool mutations across processes with a create-exclusive lock
48
- * file; stale locks (mtime older than 30s) are removed.
49
- */
50
- private withWriteLock;
51
- findByIdentityKey(identityKey: string): Promise<CredentialEntryMeta | null>;
52
- /** If active missing but entries exist, pick first and repair active pointer. */
53
- ensureActivePointer(): Promise<string | null>;
54
- }
55
- declare class CredentialPoolError extends Error {
56
- readonly code: 'credential_not_found' | 'credential_invalid';
57
- constructor(code: 'credential_not_found' | 'credential_invalid', message: string);
58
- }
59
- //#endregion
60
- export { CredentialEntryMeta, CredentialPoolError, FileCredentialPool, FileCredentialPoolOptions, credentialIdFromIdentity, newCredentialId, resolveDemiHome };
@@ -1,205 +0,0 @@
1
- import { errorCode, isRecord, nonEmptyString } from "@demicodes/utils";
2
- import { createHash, randomUUID } from "node:crypto";
3
- import { chmod, mkdir, readFile, readdir, rename, rm, stat, writeFile } from "node:fs/promises";
4
- import { homedir } from "node:os";
5
- import { join, resolve } from "node:path";
6
- //#region src/credentials-pool.ts
7
- /**
8
- * Demi multi-credential pool on disk:
9
- * <stateDir>/credentials/<providerKey>/{active,entries/<id>/{meta.json,secret}}
10
- */
11
- /**
12
- * Demi local state root (`$DEMI_HOME` / `~/.demi`). Canonical copy — host-local
13
- * re-exports this for its bridge layout.
14
- */
15
- function resolveDemiHome(explicit) {
16
- if (explicit && explicit.trim()) return resolve(explicit.trim());
17
- const fromEnv = process.env.DEMI_HOME;
18
- if (fromEnv && fromEnv.trim()) return resolve(fromEnv.trim());
19
- return join(homedir(), ".demi");
20
- }
21
- function credentialIdFromIdentity(identityKey, fallbackLabel) {
22
- const basis = nonEmptyString(identityKey) ?? fallbackLabel;
23
- return `cred-${createHash("sha256").update(basis).digest("hex").slice(0, 16)}`;
24
- }
25
- function newCredentialId() {
26
- return `cred-${randomUUID().replace(/-/g, "").slice(0, 16)}`;
27
- }
28
- var FileCredentialPool = class {
29
- root;
30
- secretFileName;
31
- constructor(options) {
32
- const stateDir = resolveDemiHome(options.stateDir);
33
- this.root = join(stateDir, "credentials", options.providerKey);
34
- this.secretFileName = options.secretFileName;
35
- }
36
- entriesDir() {
37
- return join(this.root, "entries");
38
- }
39
- entryDir(id) {
40
- return join(this.entriesDir(), id);
41
- }
42
- metaPath(id) {
43
- return join(this.entryDir(id), "meta.json");
44
- }
45
- secretPath(id) {
46
- return join(this.entryDir(id), this.secretFileName);
47
- }
48
- activePath() {
49
- return join(this.root, "active");
50
- }
51
- async list() {
52
- return (await this.listMeta()).map((m) => ({
53
- id: m.id,
54
- label: m.label,
55
- detail: m.detail ?? null,
56
- updatedAt: m.updatedAt
57
- }));
58
- }
59
- async listMeta() {
60
- let names;
61
- try {
62
- names = await readdir(this.entriesDir());
63
- } catch {
64
- return [];
65
- }
66
- const out = [];
67
- for (const name of names) {
68
- const meta = await this.readMeta(name);
69
- if (meta) out.push(meta);
70
- }
71
- out.sort((a, b) => a.id.localeCompare(b.id));
72
- return out;
73
- }
74
- async readMeta(id) {
75
- try {
76
- const raw = JSON.parse(await readFile(this.metaPath(id), "utf8"));
77
- if (!isRecord(raw)) return null;
78
- const entryId = nonEmptyString(raw.id) ?? id;
79
- const label = nonEmptyString(raw.label);
80
- if (!label) return null;
81
- return {
82
- id: entryId,
83
- label,
84
- detail: nonEmptyString(raw.detail) ?? null,
85
- updatedAt: nonEmptyString(raw.updatedAt) ?? (/* @__PURE__ */ new Date(0)).toISOString(),
86
- source: nonEmptyString(raw.source) ?? null,
87
- identityKey: nonEmptyString(raw.identityKey) ?? null
88
- };
89
- } catch {
90
- return null;
91
- }
92
- }
93
- async getActiveId() {
94
- try {
95
- const id = (await readFile(this.activePath(), "utf8")).trim();
96
- if (!id) return null;
97
- return await this.readMeta(id) ? id : null;
98
- } catch {
99
- return null;
100
- }
101
- }
102
- async setActiveId(id) {
103
- if (!await this.readMeta(id)) throw new CredentialPoolError("credential_not_found", `Credential "${id}" not found`);
104
- try {
105
- await readFile(this.secretPath(id), "utf8");
106
- } catch {
107
- throw new CredentialPoolError("credential_not_found", `Credential "${id}" has no secret material`);
108
- }
109
- await this.withWriteLock(async () => {
110
- const tmp = this.tmpPath(this.activePath());
111
- await writeFile(tmp, `${id}\n`, { mode: 384 });
112
- await rename(tmp, this.activePath());
113
- });
114
- }
115
- async clearActive() {
116
- await rm(this.activePath(), { force: true }).catch(() => void 0);
117
- }
118
- async writeEntry(meta, secretText) {
119
- return this.withWriteLock(async () => {
120
- await mkdir(this.entryDir(meta.id), {
121
- recursive: true,
122
- mode: 448
123
- });
124
- const secretTmp = this.tmpPath(this.secretPath(meta.id));
125
- const metaTmp = this.tmpPath(this.metaPath(meta.id));
126
- await writeFile(secretTmp, secretText, { mode: 384 });
127
- await chmod(secretTmp, 384).catch(() => void 0);
128
- await rename(secretTmp, this.secretPath(meta.id));
129
- await writeFile(metaTmp, `${JSON.stringify(meta, null, 2)}\n`, { mode: 384 });
130
- await rename(metaTmp, this.metaPath(meta.id));
131
- return meta;
132
- });
133
- }
134
- async readSecretText(id) {
135
- return readFile(this.secretPath(id), "utf8");
136
- }
137
- async remove(id) {
138
- await this.withWriteLock(async () => {
139
- const active = await this.getActiveId();
140
- await rm(this.entryDir(id), {
141
- recursive: true,
142
- force: true
143
- });
144
- if (active === id) await this.clearActive();
145
- });
146
- }
147
- tmpPath(target) {
148
- return `${target}.${process.pid}.${randomUUID().slice(0, 8)}.tmp`;
149
- }
150
- /**
151
- * Serializes pool mutations across processes with a create-exclusive lock
152
- * file; stale locks (mtime older than 30s) are removed.
153
- */
154
- async withWriteLock(fn) {
155
- await mkdir(this.root, {
156
- recursive: true,
157
- mode: 448
158
- });
159
- const lockPath = join(this.root, ".lock");
160
- const started = Date.now();
161
- while (true) try {
162
- await writeFile(lockPath, `${process.pid}\n`, {
163
- flag: "wx",
164
- mode: 384
165
- });
166
- break;
167
- } catch (error) {
168
- if (errorCode(error) !== "EEXIST") throw error;
169
- const info = await stat(lockPath).catch(() => null);
170
- if (info && Date.now() - info.mtimeMs > 3e4) {
171
- await rm(lockPath, { force: true }).catch(() => void 0);
172
- continue;
173
- }
174
- if (Date.now() - started > 5e3) throw new CredentialPoolError("credential_invalid", `Timed out waiting for credential pool lock ${lockPath}`);
175
- await new Promise((resolve) => setTimeout(resolve, 25));
176
- }
177
- try {
178
- return await fn();
179
- } finally {
180
- await rm(lockPath, { force: true }).catch(() => void 0);
181
- }
182
- }
183
- async findByIdentityKey(identityKey) {
184
- return (await this.listMeta()).find((m) => m.identityKey === identityKey) ?? null;
185
- }
186
- /** If active missing but entries exist, pick first and repair active pointer. */
187
- async ensureActivePointer() {
188
- const active = await this.getActiveId();
189
- if (active) return active;
190
- const all = await this.listMeta();
191
- if (all.length === 0) return null;
192
- await this.setActiveId(all[0].id);
193
- return all[0].id;
194
- }
195
- };
196
- var CredentialPoolError = class extends Error {
197
- code;
198
- constructor(code, message) {
199
- super(message);
200
- this.code = code;
201
- this.name = "CredentialPoolError";
202
- }
203
- };
204
- //#endregion
205
- export { CredentialPoolError, FileCredentialPool, credentialIdFromIdentity, newCredentialId, resolveDemiHome };