@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 +1 -13
- package/dist/index.d.mts +4 -14
- package/dist/index.mjs +10 -50
- package/dist/testing.d.mts +2 -1
- package/dist/{types-CYSnFAuT.d.mts → types-jho3Wgsx.d.mts} +6 -135
- package/package.json +3 -7
- package/dist/credentials-pool.d.mts +0 -60
- package/dist/credentials-pool.mjs +0 -205
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
|
|
2
|
-
import { FileExtension, ModelSelection, ThinkingCapability, ThinkingConfig
|
|
3
|
-
|
|
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,
|
|
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:
|
|
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|
|
|
144
|
-
if (
|
|
145
|
-
if (/overload|unavailable|
|
|
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
|
|
244
|
-
accountLabel: partial.accountLabel
|
|
245
|
-
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
|
-
|
|
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,
|
|
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 };
|
package/dist/testing.d.mts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
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,
|
|
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 {
|
|
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.
|
|
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.
|
|
23
|
-
"@demicodes/utils": "^0.10.
|
|
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 };
|