offrouter-core 0.2.1 → 0.2.2

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.
Files changed (139) hide show
  1. package/dist/audit.d.ts +137 -0
  2. package/dist/audit.d.ts.map +1 -0
  3. package/dist/audit.js +320 -0
  4. package/dist/audit.js.map +1 -0
  5. package/dist/auth/credential-chain.d.ts +84 -0
  6. package/dist/auth/credential-chain.d.ts.map +1 -0
  7. package/dist/auth/credential-chain.js +150 -0
  8. package/dist/auth/credential-chain.js.map +1 -0
  9. package/dist/auth/index.d.ts +10 -0
  10. package/dist/auth/index.d.ts.map +1 -0
  11. package/dist/auth/index.js +7 -0
  12. package/dist/auth/index.js.map +1 -0
  13. package/dist/auth/keychain.d.ts +60 -0
  14. package/dist/auth/keychain.d.ts.map +1 -0
  15. package/dist/auth/keychain.js +104 -0
  16. package/dist/auth/keychain.js.map +1 -0
  17. package/dist/auth/oauth-pkce.d.ts +138 -0
  18. package/dist/auth/oauth-pkce.d.ts.map +1 -0
  19. package/dist/auth/oauth-pkce.js +375 -0
  20. package/dist/auth/oauth-pkce.js.map +1 -0
  21. package/dist/auth/oauth-server.d.ts +36 -0
  22. package/dist/auth/oauth-server.d.ts.map +1 -0
  23. package/dist/auth/oauth-server.js +210 -0
  24. package/dist/auth/oauth-server.js.map +1 -0
  25. package/dist/config.d.ts +76 -0
  26. package/dist/config.d.ts.map +1 -0
  27. package/dist/config.js +355 -0
  28. package/dist/config.js.map +1 -0
  29. package/dist/delegation.d.ts +123 -0
  30. package/dist/delegation.d.ts.map +1 -0
  31. package/dist/delegation.js +455 -0
  32. package/dist/delegation.js.map +1 -0
  33. package/dist/index.d.ts +41 -0
  34. package/dist/index.d.ts.map +1 -0
  35. package/dist/index.js +26 -0
  36. package/dist/index.js.map +1 -0
  37. package/dist/policy.d.ts +45 -0
  38. package/dist/policy.d.ts.map +1 -0
  39. package/dist/policy.js +318 -0
  40. package/dist/policy.js.map +1 -0
  41. package/{src/protocol.ts → dist/protocol.d.ts} +2 -2
  42. package/dist/protocol.d.ts.map +1 -0
  43. package/dist/protocol.js +6 -0
  44. package/dist/protocol.js.map +1 -0
  45. package/dist/providers/adapter.d.ts +137 -0
  46. package/dist/providers/adapter.d.ts.map +1 -0
  47. package/dist/providers/adapter.js +163 -0
  48. package/dist/providers/adapter.js.map +1 -0
  49. package/dist/providers/catalog-data.d.ts +128 -0
  50. package/dist/providers/catalog-data.d.ts.map +1 -0
  51. package/dist/providers/catalog-data.js +397 -0
  52. package/dist/providers/catalog-data.js.map +1 -0
  53. package/dist/providers/catalog.d.ts +63 -0
  54. package/dist/providers/catalog.d.ts.map +1 -0
  55. package/dist/providers/catalog.js +394 -0
  56. package/dist/providers/catalog.js.map +1 -0
  57. package/dist/providers/fake.d.ts +46 -0
  58. package/dist/providers/fake.d.ts.map +1 -0
  59. package/dist/providers/fake.js +234 -0
  60. package/dist/providers/fake.js.map +1 -0
  61. package/dist/providers/openai-compatible.d.ts +65 -0
  62. package/dist/providers/openai-compatible.d.ts.map +1 -0
  63. package/dist/providers/openai-compatible.js +434 -0
  64. package/dist/providers/openai-compatible.js.map +1 -0
  65. package/dist/proxy/responses-mapper.d.ts +366 -0
  66. package/dist/proxy/responses-mapper.d.ts.map +1 -0
  67. package/dist/proxy/responses-mapper.js +517 -0
  68. package/dist/proxy/responses-mapper.js.map +1 -0
  69. package/dist/proxy/responses-server.d.ts +29 -0
  70. package/dist/proxy/responses-server.d.ts.map +1 -0
  71. package/dist/proxy/responses-server.js +360 -0
  72. package/dist/proxy/responses-server.js.map +1 -0
  73. package/dist/router.d.ts +18 -0
  74. package/dist/router.d.ts.map +1 -0
  75. package/dist/router.js +296 -0
  76. package/dist/router.js.map +1 -0
  77. package/dist/schemas.d.ts +560 -0
  78. package/dist/schemas.d.ts.map +1 -0
  79. package/{src/schemas.ts → dist/schemas.js} +83 -103
  80. package/dist/schemas.js.map +1 -0
  81. package/dist/secrets.d.ts +112 -0
  82. package/dist/secrets.d.ts.map +1 -0
  83. package/dist/secrets.js +326 -0
  84. package/dist/secrets.js.map +1 -0
  85. package/dist/types.d.ts +117 -0
  86. package/dist/types.d.ts.map +1 -0
  87. package/dist/types.js +6 -0
  88. package/dist/types.js.map +1 -0
  89. package/dist/usage-file.d.ts +59 -0
  90. package/dist/usage-file.d.ts.map +1 -0
  91. package/dist/usage-file.js +316 -0
  92. package/dist/usage-file.js.map +1 -0
  93. package/dist/usage.d.ts +235 -0
  94. package/dist/usage.d.ts.map +1 -0
  95. package/dist/usage.js +517 -0
  96. package/dist/usage.js.map +1 -0
  97. package/package.json +7 -2
  98. package/src/audit.test.ts +0 -302
  99. package/src/audit.ts +0 -553
  100. package/src/auth/credential-chain.test.ts +0 -326
  101. package/src/auth/credential-chain.ts +0 -235
  102. package/src/auth/index.ts +0 -45
  103. package/src/auth/keychain.test.ts +0 -265
  104. package/src/auth/keychain.ts +0 -168
  105. package/src/auth/oauth-pkce.test.ts +0 -329
  106. package/src/auth/oauth-pkce.ts +0 -571
  107. package/src/auth/oauth-server.test.ts +0 -83
  108. package/src/auth/oauth-server.ts +0 -296
  109. package/src/config.test.ts +0 -479
  110. package/src/config.ts +0 -505
  111. package/src/delegation.test.ts +0 -368
  112. package/src/delegation.ts +0 -605
  113. package/src/index.ts +0 -280
  114. package/src/policy.test.ts +0 -634
  115. package/src/policy.ts +0 -420
  116. package/src/providers/adapter.test.ts +0 -293
  117. package/src/providers/adapter.ts +0 -328
  118. package/src/providers/catalog-data.test.ts +0 -258
  119. package/src/providers/catalog-data.ts +0 -498
  120. package/src/providers/catalog.test.ts +0 -84
  121. package/src/providers/catalog.ts +0 -483
  122. package/src/providers/fake.ts +0 -312
  123. package/src/providers/openai-compatible.test.ts +0 -366
  124. package/src/providers/openai-compatible.ts +0 -554
  125. package/src/proxy/responses-mapper.test.ts +0 -290
  126. package/src/proxy/responses-mapper.ts +0 -736
  127. package/src/proxy/responses-server.test.ts +0 -322
  128. package/src/proxy/responses-server.ts +0 -469
  129. package/src/router.test.ts +0 -699
  130. package/src/router.ts +0 -352
  131. package/src/schemas.test.ts +0 -291
  132. package/src/secrets.test.ts +0 -271
  133. package/src/secrets.ts +0 -461
  134. package/src/types.ts +0 -173
  135. package/src/usage-file.test.ts +0 -243
  136. package/src/usage-file.ts +0 -435
  137. package/src/usage.test.ts +0 -335
  138. package/src/usage.ts +0 -859
  139. package/tsconfig.json +0 -9
package/dist/policy.js ADDED
@@ -0,0 +1,318 @@
1
+ const HEALTHY_SUBSCRIPTION_STATUSES = new Set([
2
+ "active",
3
+ ]);
4
+ const UNAVAILABLE_SUBSCRIPTION_STATUSES = new Set(["exhausted", "rate-limited", "expired", "unconfigured", "unknown"]);
5
+ function candidateId(c) {
6
+ return `${c.providerId}:${c.modelId}`;
7
+ }
8
+ function matchesGlob(pattern, value) {
9
+ // Escape regex special chars except *, then turn * into .*
10
+ const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
11
+ const re = new RegExp(`^${escaped.replace(/\*/g, ".*")}$`);
12
+ return re.test(value);
13
+ }
14
+ function isWorkProfileDenied(profile, patterns) {
15
+ // Work profiles are denied by default. Custom patterns are additive.
16
+ // Align with the Claude installer: a profile is work-like if it ends with
17
+ // "-work" (e.g. claude-work) or contains "-work-" (e.g. claude-work-staging).
18
+ const effectivePatterns = ["*-work", ...(patterns ?? [])];
19
+ return (profile.endsWith("-work") ||
20
+ profile.includes("-work-") ||
21
+ effectivePatterns.some((pattern) => matchesGlob(pattern, profile)));
22
+ }
23
+ function isAllowedProfile(profile, config) {
24
+ return config.allowlistedProfiles.includes(profile);
25
+ }
26
+ /**
27
+ * Whether a candidate meets hard capability requirements from the task.
28
+ */
29
+ export function meetsCapabilities(candidate, request) {
30
+ const { task } = request;
31
+ if (task.requiresTools && !candidate.supportsTools)
32
+ return false;
33
+ if (task.requiresImages && !candidate.supportsImages)
34
+ return false;
35
+ if (task.requiresStreaming && !candidate.supportsStreaming)
36
+ return false;
37
+ if (task.requiresJson && !candidate.supportsJson)
38
+ return false;
39
+ // Task kind shortcuts when requires* not set explicitly
40
+ if (task.kind === "tools" && task.requiresTools !== false && !candidate.supportsTools) {
41
+ return false;
42
+ }
43
+ if (task.kind === "image" && task.requiresImages !== false && !candidate.supportsImages) {
44
+ return false;
45
+ }
46
+ return true;
47
+ }
48
+ function missingCapabilityMessage(candidate, request) {
49
+ const missing = [];
50
+ if ((request.task.requiresTools || request.task.kind === "tools") &&
51
+ !candidate.supportsTools) {
52
+ missing.push("tools");
53
+ }
54
+ if ((request.task.requiresImages || request.task.kind === "image") &&
55
+ !candidate.supportsImages) {
56
+ missing.push("images");
57
+ }
58
+ if (request.task.requiresStreaming && !candidate.supportsStreaming) {
59
+ missing.push("streaming");
60
+ }
61
+ if (request.task.requiresJson && !candidate.supportsJson) {
62
+ missing.push("json");
63
+ }
64
+ return `Candidate ${candidateId(candidate)} missing required capabilities: ${missing.join(", ") || "unknown"}.`;
65
+ }
66
+ /**
67
+ * Healthy subscription capacity: active status (or omitted treated carefully)
68
+ * with non-dead health, and policy/capability eligible.
69
+ *
70
+ * Degraded health is still "usable" for routing later but is not treated as
71
+ * fully healthy capacity for blocking API-key candidates when another healthy
72
+ * subscription exists — that nuance lives in the router score. For policy
73
+ * gatekeeping "can a healthy subscription satisfy the task?", we accept
74
+ * health === "healthy" and subscriptionStatus in active (defaulting missing
75
+ * status on subscription tier to unknown, not healthy).
76
+ */
77
+ export function isHealthySubscriptionCapacity(candidate, request, config) {
78
+ if (candidate.authTier !== "subscription")
79
+ return false;
80
+ if (candidate.health !== "healthy")
81
+ return false;
82
+ const status = candidate.subscriptionStatus ?? "unknown";
83
+ if (!HEALTHY_SUBSCRIPTION_STATUSES.has(status))
84
+ return false;
85
+ if (candidate.authScope === "third-party" &&
86
+ !config.allowThirdPartySubscriptionAdapters) {
87
+ return false;
88
+ }
89
+ if (candidate.authScope === "unknown") {
90
+ return false;
91
+ }
92
+ if (!meetsCapabilities(candidate, request))
93
+ return false;
94
+ // Subscription positions are never local; localOnly excludes them.
95
+ if (request.constraints.localOnly) {
96
+ return false;
97
+ }
98
+ if (request.constraints.maxCostUsd !== undefined &&
99
+ candidate.estimatedCostUsd !== undefined &&
100
+ candidate.estimatedCostUsd > request.constraints.maxCostUsd) {
101
+ return false;
102
+ }
103
+ if (config.deniedProviders?.includes(candidate.providerId)) {
104
+ return false;
105
+ }
106
+ return true;
107
+ }
108
+ /**
109
+ * Whether any remaining subscription candidate has healthy capacity after hard
110
+ * filters, so API-key stays blocked only while a healthy subscription route can
111
+ * satisfy the task.
112
+ */
113
+ function hasUsableSubscriptionForTask(candidates, request, config) {
114
+ return candidates.some((c) => {
115
+ if (c.authTier !== "subscription")
116
+ return false;
117
+ if (c.health !== "healthy")
118
+ return false;
119
+ const status = c.subscriptionStatus ?? "unknown";
120
+ // Only active + healthy subscription capacity blocks API-key candidates
121
+ // under subscription-first.
122
+ if (!HEALTHY_SUBSCRIPTION_STATUSES.has(status))
123
+ return false;
124
+ if (c.authScope === "third-party" &&
125
+ !config.allowThirdPartySubscriptionAdapters) {
126
+ return false;
127
+ }
128
+ if (c.authScope === "unknown")
129
+ return false;
130
+ if (!meetsCapabilities(c, request))
131
+ return false;
132
+ if (request.constraints.localOnly)
133
+ return false;
134
+ if (request.constraints.maxCostUsd !== undefined &&
135
+ c.estimatedCostUsd !== undefined &&
136
+ c.estimatedCostUsd > request.constraints.maxCostUsd) {
137
+ return false;
138
+ }
139
+ if (config.deniedProviders?.includes(c.providerId))
140
+ return false;
141
+ return true;
142
+ });
143
+ }
144
+ function hardFilterDenial(candidate, request, config) {
145
+ const id = candidateId(candidate);
146
+ if (config.deniedProviders?.includes(candidate.providerId)) {
147
+ return {
148
+ code: "provider_denied",
149
+ target: "provider",
150
+ id: candidate.providerId,
151
+ message: `Provider ${candidate.providerId} is denied by policy.`,
152
+ };
153
+ }
154
+ if (request.constraints.localOnly && candidate.authTier !== "local") {
155
+ return {
156
+ code: "local_only_required",
157
+ target: "candidate",
158
+ id,
159
+ message: `Candidate ${id} is not a local runtime; localOnly is required.`,
160
+ };
161
+ }
162
+ if (request.constraints.maxCostUsd !== undefined &&
163
+ candidate.authTier === "api-key" &&
164
+ candidate.estimatedCostUsd === undefined) {
165
+ return {
166
+ code: "cost_cap_exceeded",
167
+ target: "candidate",
168
+ id,
169
+ message: `API-key candidate ${id} has unknown cost and cannot satisfy maxCostUsd ${request.constraints.maxCostUsd}.`,
170
+ };
171
+ }
172
+ if (request.constraints.maxCostUsd !== undefined &&
173
+ candidate.estimatedCostUsd !== undefined &&
174
+ candidate.estimatedCostUsd > request.constraints.maxCostUsd) {
175
+ return {
176
+ code: "cost_cap_exceeded",
177
+ target: "candidate",
178
+ id,
179
+ message: `Candidate ${id} estimated cost ${candidate.estimatedCostUsd} exceeds maxCostUsd ${request.constraints.maxCostUsd}.`,
180
+ };
181
+ }
182
+ if (!meetsCapabilities(candidate, request)) {
183
+ return {
184
+ code: "capability_missing",
185
+ target: "candidate",
186
+ id,
187
+ message: missingCapabilityMessage(candidate, request),
188
+ };
189
+ }
190
+ // First-party subscription surface via third-party adapter
191
+ if (candidate.authTier === "subscription" &&
192
+ candidate.authScope === "third-party" &&
193
+ !config.allowThirdPartySubscriptionAdapters) {
194
+ return {
195
+ code: "subscription_scope_denied",
196
+ target: "candidate",
197
+ id,
198
+ message: `Subscription candidate ${id} uses a third-party adapter surface without policy allow.`,
199
+ };
200
+ }
201
+ if (candidate.authTier === "subscription" &&
202
+ candidate.authScope === "unknown") {
203
+ return {
204
+ code: "subscription_scope_denied",
205
+ target: "candidate",
206
+ id,
207
+ message: `Subscription candidate ${id} has unknown auth scope and cannot route until the adapter proves support.`,
208
+ };
209
+ }
210
+ // Dead candidates never route
211
+ if (candidate.health === "dead") {
212
+ // Treat as policy-ineligible for selection; keep as structured denial so
213
+ // explain paths can show why. Prefer provider_denied-ish message without
214
+ // inventing a new code — map to provider_denied for dead health is
215
+ // slightly imprecise; capability isn't right. Use provider_denied for
216
+ // explicit denylist only. For dead, we skip without adding denial so
217
+ // they simply don't appear as allowed. Router will then handle empty set.
218
+ // Actually: don't allow them, no denial needed for "dead" — just exclude.
219
+ return {
220
+ code: "provider_denied",
221
+ target: "candidate",
222
+ id,
223
+ message: `Candidate ${id} is dead and cannot be selected.`,
224
+ };
225
+ }
226
+ // Exhausted/rate-limited/expired subscription tiers are not allowed as primary
227
+ if (candidate.authTier === "subscription") {
228
+ const status = candidate.subscriptionStatus ?? "unknown";
229
+ if (UNAVAILABLE_SUBSCRIPTION_STATUSES.has(status)) {
230
+ return {
231
+ code: "provider_denied",
232
+ target: "candidate",
233
+ id,
234
+ message: `Subscription candidate ${id} is ${status} and cannot satisfy the task.`,
235
+ };
236
+ }
237
+ }
238
+ return null;
239
+ }
240
+ /**
241
+ * Evaluate profile and candidate policy.
242
+ * Returns allowed candidates plus structured denials (never string-only).
243
+ */
244
+ export function evaluatePolicy(request, candidates, config) {
245
+ const denials = [];
246
+ const profile = request.harness.profile;
247
+ // 1. Profile gates
248
+ if (isWorkProfileDenied(profile, config.deniedProfilePatterns)) {
249
+ denials.push({
250
+ code: "work_profile_denied",
251
+ target: "profile",
252
+ id: profile,
253
+ message: `Work profile ${profile} is denied by default. OffRouter V1 only routes allowlisted personal profiles.`,
254
+ });
255
+ return { allowed: [], denials };
256
+ }
257
+ if (!isAllowedProfile(profile, config)) {
258
+ denials.push({
259
+ code: "profile_not_allowlisted",
260
+ target: "profile",
261
+ id: profile,
262
+ message: `Profile ${profile} is not allowlisted. Add it explicitly before installing or routing.`,
263
+ });
264
+ return { allowed: [], denials };
265
+ }
266
+ // Workspace trust is a hard filter when untrusted
267
+ if (!request.workspace.trusted) {
268
+ denials.push({
269
+ code: "project_untrusted",
270
+ target: "workspace",
271
+ id: request.workspace.cwd,
272
+ message: `Workspace ${request.workspace.cwd} is untrusted; routing is blocked.`,
273
+ });
274
+ return { allowed: [], denials };
275
+ }
276
+ // 2. Hard filters per candidate
277
+ const hardPassed = [];
278
+ for (const c of candidates) {
279
+ const denial = hardFilterDenial(c, request, config);
280
+ if (denial) {
281
+ denials.push(denial);
282
+ }
283
+ else {
284
+ hardPassed.push(c);
285
+ }
286
+ }
287
+ // 3. Subscription-first: gate API-key when usable subscription capacity exists,
288
+ // or when allowApiKeyFallback is false.
289
+ const subscriptionFirst = request.constraints.subscriptionFirst !== false;
290
+ const allowApiKeyFallback = request.constraints.allowApiKeyFallback === true;
291
+ const usableSubscription = hasUsableSubscriptionForTask(hardPassed, request, config);
292
+ const allowed = [];
293
+ for (const c of hardPassed) {
294
+ if (c.authTier === "api-key") {
295
+ if (subscriptionFirst && usableSubscription) {
296
+ denials.push({
297
+ code: "api_key_blocked_by_subscription_first_policy",
298
+ target: "candidate",
299
+ id: candidateId(c),
300
+ message: `API-key candidate ${candidateId(c)} blocked while healthy subscription capacity remains.`,
301
+ });
302
+ continue;
303
+ }
304
+ if (subscriptionFirst && !allowApiKeyFallback) {
305
+ denials.push({
306
+ code: "api_key_blocked_by_subscription_first_policy",
307
+ target: "candidate",
308
+ id: candidateId(c),
309
+ message: `API-key candidate ${candidateId(c)} blocked: allowApiKeyFallback is false.`,
310
+ });
311
+ continue;
312
+ }
313
+ }
314
+ allowed.push(c);
315
+ }
316
+ return { allowed, denials };
317
+ }
318
+ //# sourceMappingURL=policy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"policy.js","sourceRoot":"","sources":["../src/policy.ts"],"names":[],"mappings":"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"}
@@ -2,6 +2,6 @@
2
2
  * OffRouter V1 route protocol constants.
3
3
  * Wire vocabulary: docs/contracts/v1-routing-contract.md
4
4
  */
5
- export const ROUTE_PROTOCOL_VERSION = "offrouter.route.v1" as const;
6
-
5
+ export declare const ROUTE_PROTOCOL_VERSION: "offrouter.route.v1";
7
6
  export type RouteProtocolVersion = typeof ROUTE_PROTOCOL_VERSION;
7
+ //# sourceMappingURL=protocol.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../src/protocol.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,eAAO,MAAM,sBAAsB,EAAG,oBAA6B,CAAC;AAEpE,MAAM,MAAM,oBAAoB,GAAG,OAAO,sBAAsB,CAAC"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * OffRouter V1 route protocol constants.
3
+ * Wire vocabulary: docs/contracts/v1-routing-contract.md
4
+ */
5
+ export const ROUTE_PROTOCOL_VERSION = "offrouter.route.v1";
6
+ //# sourceMappingURL=protocol.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"protocol.js","sourceRoot":"","sources":["../src/protocol.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG,oBAA6B,CAAC"}
@@ -0,0 +1,137 @@
1
+ /**
2
+ * Provider adapter contract shared by fake and OpenAI-compatible backends.
3
+ * Keeps harness-specific shapes out of the core router.
4
+ */
5
+ import type { AuthScope, AuthTier, ProviderCandidate, ProviderHealth, RouteRequest, SubscriptionStatus } from "../types.js";
6
+ export type ProviderErrorCode = "auth_failed" | "rate_limited" | "quota_exhausted" | "billing" | "overload" | "model_not_found" | "context_window" | "safety_refusal" | "stream_interrupted" | "cancelled" | "timeout" | "network" | "invalid_request" | "unknown";
7
+ export interface ProviderErrorOptions {
8
+ code: ProviderErrorCode;
9
+ message: string;
10
+ retryable?: boolean;
11
+ httpStatus?: number;
12
+ providerCode?: string;
13
+ cause?: unknown;
14
+ }
15
+ export declare class ProviderError extends Error {
16
+ readonly code: ProviderErrorCode;
17
+ readonly retryable: boolean;
18
+ readonly httpStatus?: number;
19
+ readonly providerCode?: string;
20
+ constructor(options: ProviderErrorOptions);
21
+ }
22
+ export declare function isProviderError(value: unknown): value is ProviderError;
23
+ /**
24
+ * Map HTTP status + optional provider body into a ProviderError.
25
+ * Presence-only; never logs credential material.
26
+ */
27
+ export declare function mapHttpToProviderError(status: number, bodyText?: string, providerCode?: string): ProviderError;
28
+ export interface ModelCapability {
29
+ modelId: string;
30
+ displayName?: string;
31
+ supportsTools: boolean;
32
+ supportsStreaming: boolean;
33
+ supportsJson: boolean;
34
+ supportsImages: boolean;
35
+ contextWindowTokens?: number;
36
+ maxOutputTokens?: number;
37
+ estimatedCostUsd?: number;
38
+ }
39
+ export interface AuthStatus {
40
+ authTier: AuthTier;
41
+ authScope: AuthScope;
42
+ configured: boolean;
43
+ /** Presence-only; never returns key material. */
44
+ hasApiKey: boolean;
45
+ subscriptionStatus?: SubscriptionStatus;
46
+ limitsDiscoverable: boolean;
47
+ health: ProviderHealth;
48
+ accountId?: string;
49
+ detail?: string;
50
+ /**
51
+ * Which credential source satisfied auth (if any).
52
+ * "none" when no credential was found.
53
+ */
54
+ credentialSource?: "env" | "config" | "keychain" | "oauth" | "none";
55
+ /** Whether this auth path uses a subscription-backed credential. */
56
+ subscriptionEligible: boolean;
57
+ }
58
+ /**
59
+ * Limit snapshot. Opaque providers report discoverable=false and omit remaining.
60
+ */
61
+ export interface LimitSnapshot {
62
+ authTier: AuthTier;
63
+ /** When false, remaining/reset fields are unknown; rely on errors for fallback. */
64
+ discoverable: boolean;
65
+ opaque: boolean;
66
+ remaining?: number;
67
+ limit?: number;
68
+ resetAt?: string;
69
+ unit?: string;
70
+ accountId?: string;
71
+ metadata?: Record<string, string | number | boolean | null>;
72
+ }
73
+ export interface CostEstimate {
74
+ model: string;
75
+ authTier: AuthTier;
76
+ estimatedCostUsd: number;
77
+ currency: "USD";
78
+ inputTokensEstimate?: number;
79
+ outputTokensEstimate?: number;
80
+ note?: string;
81
+ }
82
+ export interface ProviderInvokeMessage {
83
+ role: "system" | "user" | "assistant" | "tool";
84
+ content: string;
85
+ }
86
+ export interface ProviderInvokeRequest {
87
+ callId: string;
88
+ model: string;
89
+ messages: ProviderInvokeMessage[];
90
+ stream?: boolean;
91
+ maxOutputTokens?: number;
92
+ temperature?: number;
93
+ /** Optional abort signal for cancellation races. */
94
+ signal?: AbortSignal;
95
+ }
96
+ export type ProviderEvent = {
97
+ type: "start";
98
+ callId: string;
99
+ model: string;
100
+ } | {
101
+ type: "text-delta";
102
+ callId: string;
103
+ text: string;
104
+ } | {
105
+ type: "tool-call";
106
+ callId: string;
107
+ name: string;
108
+ arguments: string;
109
+ } | {
110
+ type: "usage";
111
+ callId: string;
112
+ inputTokens?: number;
113
+ outputTokens?: number;
114
+ totalTokens?: number;
115
+ } | {
116
+ type: "done";
117
+ callId: string;
118
+ finishReason?: string;
119
+ } | {
120
+ type: "error";
121
+ callId: string;
122
+ error: ProviderError;
123
+ };
124
+ export interface ProviderAdapter {
125
+ readonly id: string;
126
+ listModels(): Promise<ModelCapability[]>;
127
+ checkAuth(): Promise<AuthStatus>;
128
+ queryLimits(authTier: AuthTier): Promise<LimitSnapshot>;
129
+ estimate(request: RouteRequest, model: string, authTier: AuthTier): Promise<CostEstimate>;
130
+ invoke(request: ProviderInvokeRequest): AsyncIterable<ProviderEvent>;
131
+ cancel(callId: string): Promise<void>;
132
+ }
133
+ /** Required method names on every adapter. */
134
+ export declare const PROVIDER_ADAPTER_METHODS: readonly ["listModels", "checkAuth", "queryLimits", "estimate", "invoke", "cancel"];
135
+ /** Convert catalog models to ProviderCandidate rows for routing tests. */
136
+ export declare function capabilityToCandidate(providerId: string, capability: ModelCapability, extras: Pick<ProviderCandidate, "authTier" | "authScope"> & Partial<ProviderCandidate>): ProviderCandidate;
137
+ //# sourceMappingURL=adapter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../../src/providers/adapter.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,163 @@
1
+ /**
2
+ * Provider adapter contract shared by fake and OpenAI-compatible backends.
3
+ * Keeps harness-specific shapes out of the core router.
4
+ */
5
+ import { redact } from "../secrets.js";
6
+ export class ProviderError extends Error {
7
+ code;
8
+ retryable;
9
+ httpStatus;
10
+ providerCode;
11
+ constructor(options) {
12
+ super(options.message, options.cause !== undefined ? { cause: options.cause } : undefined);
13
+ this.name = "ProviderError";
14
+ this.code = options.code;
15
+ this.retryable = options.retryable ?? false;
16
+ this.httpStatus = options.httpStatus;
17
+ this.providerCode = options.providerCode;
18
+ }
19
+ }
20
+ export function isProviderError(value) {
21
+ return value instanceof ProviderError;
22
+ }
23
+ /**
24
+ * Map HTTP status + optional provider body into a ProviderError.
25
+ * Presence-only; never logs credential material.
26
+ */
27
+ export function mapHttpToProviderError(status, bodyText, providerCode) {
28
+ const snippet = typeof bodyText === "string" && bodyText.length > 0
29
+ ? redact(bodyText.slice(0, 240))
30
+ : undefined;
31
+ const base = snippet ?? `HTTP ${status}`;
32
+ if (status === 401 || status === 403) {
33
+ return new ProviderError({
34
+ code: "auth_failed",
35
+ message: base,
36
+ retryable: false,
37
+ httpStatus: status,
38
+ providerCode,
39
+ });
40
+ }
41
+ if (status === 404) {
42
+ return new ProviderError({
43
+ code: "model_not_found",
44
+ message: base,
45
+ retryable: false,
46
+ httpStatus: status,
47
+ providerCode,
48
+ });
49
+ }
50
+ if (status === 408 || status === 504) {
51
+ return new ProviderError({
52
+ code: "timeout",
53
+ message: base,
54
+ retryable: true,
55
+ httpStatus: status,
56
+ providerCode,
57
+ });
58
+ }
59
+ if (status === 429) {
60
+ const lower = (bodyText ?? "").toLowerCase();
61
+ if (lower.includes("quota") ||
62
+ lower.includes("billing") ||
63
+ lower.includes("insufficient")) {
64
+ return new ProviderError({
65
+ code: "quota_exhausted",
66
+ message: base,
67
+ retryable: false,
68
+ httpStatus: status,
69
+ providerCode,
70
+ });
71
+ }
72
+ return new ProviderError({
73
+ code: "rate_limited",
74
+ message: base,
75
+ retryable: true,
76
+ httpStatus: status,
77
+ providerCode,
78
+ });
79
+ }
80
+ if (status === 402) {
81
+ return new ProviderError({
82
+ code: "billing",
83
+ message: base,
84
+ retryable: false,
85
+ httpStatus: status,
86
+ providerCode,
87
+ });
88
+ }
89
+ if (status === 413) {
90
+ return new ProviderError({
91
+ code: "context_window",
92
+ message: base,
93
+ retryable: false,
94
+ httpStatus: status,
95
+ providerCode,
96
+ });
97
+ }
98
+ if (status === 503 || status === 529) {
99
+ return new ProviderError({
100
+ code: "overload",
101
+ message: base,
102
+ retryable: true,
103
+ httpStatus: status,
104
+ providerCode,
105
+ });
106
+ }
107
+ if (status >= 500) {
108
+ return new ProviderError({
109
+ code: "network",
110
+ message: base,
111
+ retryable: true,
112
+ httpStatus: status,
113
+ providerCode,
114
+ });
115
+ }
116
+ if (status >= 400) {
117
+ return new ProviderError({
118
+ code: "invalid_request",
119
+ message: base,
120
+ retryable: false,
121
+ httpStatus: status,
122
+ providerCode,
123
+ });
124
+ }
125
+ return new ProviderError({
126
+ code: "unknown",
127
+ message: base,
128
+ retryable: false,
129
+ httpStatus: status,
130
+ providerCode,
131
+ });
132
+ }
133
+ /** Required method names on every adapter. */
134
+ export const PROVIDER_ADAPTER_METHODS = [
135
+ "listModels",
136
+ "checkAuth",
137
+ "queryLimits",
138
+ "estimate",
139
+ "invoke",
140
+ "cancel",
141
+ ];
142
+ /** Convert catalog models to ProviderCandidate rows for routing tests. */
143
+ export function capabilityToCandidate(providerId, capability, extras) {
144
+ return {
145
+ providerId,
146
+ modelId: capability.modelId,
147
+ displayName: capability.displayName,
148
+ authTier: extras.authTier,
149
+ authScope: extras.authScope,
150
+ subscriptionStatus: extras.subscriptionStatus,
151
+ health: extras.health ?? "healthy",
152
+ supportsTools: capability.supportsTools,
153
+ supportsStreaming: capability.supportsStreaming,
154
+ supportsJson: capability.supportsJson,
155
+ supportsImages: capability.supportsImages,
156
+ contextWindowTokens: capability.contextWindowTokens,
157
+ maxOutputTokens: capability.maxOutputTokens,
158
+ estimatedCostUsd: extras.estimatedCostUsd ?? capability.estimatedCostUsd,
159
+ accountId: extras.accountId,
160
+ subscriptionQuotaPercentRemaining: extras.subscriptionQuotaPercentRemaining,
161
+ };
162
+ }
163
+ //# sourceMappingURL=adapter.js.map