offrouter-core 0.2.0 → 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 +9 -4
  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
@@ -0,0 +1,137 @@
1
+ import type { FallbackChainEntry, FallbackReason, PolicyDenial, RouteDecision, RouteRequest, SelectedRoute } from "./types.js";
2
+ /** Default max chars kept for an opt-in redacted prompt preview. */
3
+ export declare const DEFAULT_PREVIEW_MAX_CHARS = 80;
4
+ /** Cancellation lifecycle for an audited request/decision in-process. */
5
+ export type CancellationState = "active" | "cancelled" | "completed";
6
+ /** High-level audit event kind. */
7
+ export type AuditRecordKind = "route_decision" | "api_key_fallback" | "cancellation";
8
+ /**
9
+ * Append-only audit record. Intentionally omits raw prompt text.
10
+ * `promptPreviewRedacted` appears only when explicitly requested at append.
11
+ */
12
+ export interface AuditRecord {
13
+ recordId: string;
14
+ kind: AuditRecordKind;
15
+ timestamp: string;
16
+ requestId: string;
17
+ decisionId?: string;
18
+ profile?: string;
19
+ harnessKind?: string;
20
+ /** Prompt content login digest (sha256:..., etc.). Never the prompt itself. */
21
+ promptDigest?: string;
22
+ /**
23
+ * Short, redacted preview. Present only when the append opted into previews.
24
+ * Never the full raw user prompt.
25
+ */
26
+ promptPreviewRedacted?: string;
27
+ /** Digest-oriented decision summary from routing. */
28
+ auditSummary?: string;
29
+ reason: string;
30
+ explanation: string;
31
+ route?: SelectedRoute | null;
32
+ fallbackChain?: FallbackChainEntry[];
33
+ /** Explicit API-key fallback reason when paid capacity was used. */
34
+ apiKeyFallbackReason?: FallbackReason | string;
35
+ policyDenials?: PolicyDenial[];
36
+ blocked?: boolean;
37
+ needsConfiguration?: boolean;
38
+ cancellationState?: CancellationState;
39
+ cancelledAt?: string;
40
+ }
41
+ export interface AppendDecisionOptions {
42
+ /**
43
+ * When true, store a short redacted preview derived from promptPreview.
44
+ * Default false — digests and summaries only.
45
+ */
46
+ includePromptPreview?: boolean;
47
+ /** Max chars of redacted preview when includePromptPreview is true. */
48
+ previewMaxChars?: number;
49
+ /**
50
+ * Known secret values to strip in addition to pattern-based redaction.
51
+ * Never stored; used only during redaction.
52
+ */
53
+ knownSecrets?: Iterable<string>;
54
+ /** Initial cancellation state; default "active" for successful routes. */
55
+ cancellationState?: CancellationState;
56
+ /** Override clock for tests. */
57
+ now?: () => Date;
58
+ }
59
+ export interface ListAuditOptions {
60
+ requestId?: string;
61
+ decisionId?: string;
62
+ kind?: AuditRecordKind;
63
+ /** Max records to return (most recent first). */
64
+ limit?: number;
65
+ }
66
+ export interface MarkCancelledOptions {
67
+ reason?: string;
68
+ now?: () => Date;
69
+ }
70
+ /**
71
+ * Audit log contract. Implementations must never persist raw secrets or full
72
+ * prompt bodies by default.
73
+ */
74
+ export interface AuditStore {
75
+ appendDecision(request: RouteRequest, decision: RouteDecision, options?: AppendDecisionOptions): Promise<AuditRecord>;
76
+ /**
77
+ * Record that an API-key fallback was used, with an audited reason.
78
+ * Always redacted; never log the delegated prompt or credential material.
79
+ */
80
+ recordApiKeyFallback(input: {
81
+ requestId: string;
82
+ decisionId?: string;
83
+ reason: FallbackReason | string;
84
+ explanation: string;
85
+ route?: SelectedRoute | null;
86
+ /** Optional digest already present on the route request/decision. */
87
+ promptDigest?: string;
88
+ knownSecrets?: Iterable<string>;
89
+ now?: () => Date;
90
+ }): Promise<AuditRecord>;
91
+ markCancelled(requestId: string, options?: MarkCancelledOptions): Promise<AuditRecord | undefined>;
92
+ markCompleted(requestId: string, options?: {
93
+ now?: () => Date;
94
+ }): Promise<AuditRecord | undefined>;
95
+ getCancellationState(requestId: string): Promise<CancellationState | undefined>;
96
+ list(options?: ListAuditOptions): Promise<AuditRecord[]>;
97
+ get(recordId: string): Promise<AuditRecord | undefined>;
98
+ /**
99
+ * JSON-safe diagnostic view. Must never include raw prompt text or secrets.
100
+ */
101
+ toJSON(): Record<string, unknown>;
102
+ }
103
+ export interface InMemoryAuditStoreOptions {
104
+ /** Optional hard cap on retained records (FIFO eviction of oldest). */
105
+ maxRecords?: number;
106
+ /** Default known secrets used for every redaction pass. */
107
+ knownSecrets?: Iterable<string>;
108
+ now?: () => Date;
109
+ }
110
+ /**
111
+ * In-memory audit store for tests and single-process V1 use.
112
+ * Appends are serialized so concurrent writers cannot interleave corruptly.
113
+ */
114
+ export declare class InMemoryAuditStore implements AuditStore {
115
+ #private;
116
+ constructor(options?: InMemoryAuditStoreOptions);
117
+ appendDecision(request: RouteRequest, decision: RouteDecision, options?: AppendDecisionOptions): Promise<AuditRecord>;
118
+ recordApiKeyFallback(input: {
119
+ requestId: string;
120
+ decisionId?: string;
121
+ reason: FallbackReason | string;
122
+ explanation: string;
123
+ route?: SelectedRoute | null;
124
+ promptDigest?: string;
125
+ knownSecrets?: Iterable<string>;
126
+ now?: () => Date;
127
+ }): Promise<AuditRecord>;
128
+ markCancelled(requestId: string, options?: MarkCancelledOptions): Promise<AuditRecord | undefined>;
129
+ markCompleted(requestId: string, options?: {
130
+ now?: () => Date;
131
+ }): Promise<AuditRecord | undefined>;
132
+ getCancellationState(requestId: string): Promise<CancellationState | undefined>;
133
+ list(options?: ListAuditOptions): Promise<AuditRecord[]>;
134
+ get(recordId: string): Promise<AuditRecord | undefined>;
135
+ toJSON(): Record<string, unknown>;
136
+ }
137
+ //# sourceMappingURL=audit.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audit.d.ts","sourceRoot":"","sources":["../src/audit.ts"],"names":[],"mappings":"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"}
package/dist/audit.js ADDED
@@ -0,0 +1,320 @@
1
+ /**
2
+ * Redacted audit log store for OffRouter V1.
3
+ *
4
+ * V1 keeps only digests, short redacted previews (opt-in), and decision
5
+ * summaries. Raw prompt text and secrets are never persisted by default.
6
+ *
7
+ * In-memory only for this task; file/JSONL backend comes after the interface
8
+ * stabilizes (see design: ~/.offrouter/audit/*.jsonl).
9
+ */
10
+ import { randomBytes } from "node:crypto";
11
+ import { redact, REDACTED } from "./secrets.js";
12
+ /** Default max chars kept for an opt-in redacted prompt preview. */
13
+ export const DEFAULT_PREVIEW_MAX_CHARS = 80;
14
+ function newId(prefix) {
15
+ return `${prefix}_${randomBytes(8).toString("hex")}`;
16
+ }
17
+ function clampPreview(text, maxChars) {
18
+ if (text.length <= maxChars)
19
+ return text;
20
+ return `${text.slice(0, Math.max(0, maxChars - 1))}…`;
21
+ }
22
+ /**
23
+ * Strip secret-like patterns and optionally known secrets from free text.
24
+ * Safe for empty/undefined values.
25
+ */
26
+ function safeRedact(value, knownSecrets) {
27
+ if (value == null || value === "")
28
+ return value ?? "";
29
+ return redact(value, knownSecrets);
30
+ }
31
+ function looksLikeApiKeyFallback(decision) {
32
+ if (decision.route?.authTier === "api-key") {
33
+ if (decision.reason === "api_key_fallback")
34
+ return true;
35
+ if (decision.fallbackChain?.some((e) => e.reason === "api_key_fallback" || e.authTier === "api-key")) {
36
+ return true;
37
+ }
38
+ // Any API-key selection with a subscription fallback reason counts.
39
+ if (decision.fallbackChain?.some((e) => e.reason === "subscription_exhausted" ||
40
+ e.reason === "subscription_degraded" ||
41
+ e.reason === "subscription_unconfigured" ||
42
+ e.reason === "provider_error")) {
43
+ return true;
44
+ }
45
+ }
46
+ return decision.reason === "api_key_fallback";
47
+ }
48
+ function extractApiKeyFallbackReason(decision) {
49
+ if (!looksLikeApiKeyFallback(decision))
50
+ return undefined;
51
+ const chainHit = decision.fallbackChain?.find((e) => e.reason === "api_key_fallback");
52
+ if (chainHit)
53
+ return chainHit.reason;
54
+ const exhausted = decision.fallbackChain?.find((e) => e.reason === "subscription_exhausted" ||
55
+ e.reason === "subscription_degraded" ||
56
+ e.reason === "subscription_unconfigured" ||
57
+ e.reason === "provider_error");
58
+ if (exhausted)
59
+ return exhausted.reason;
60
+ if (decision.route?.authTier === "api-key")
61
+ return "api_key_fallback";
62
+ return decision.reason;
63
+ }
64
+ /**
65
+ * In-memory audit store for tests and single-process V1 use.
66
+ * Appends are serialized so concurrent writers cannot interleave corruptly.
67
+ */
68
+ export class InMemoryAuditStore {
69
+ #records = [];
70
+ #byId = new Map();
71
+ /** Latest cancellation-bearing record per requestId. */
72
+ #latestByRequest = new Map();
73
+ #maxRecords;
74
+ #defaultKnownSecrets;
75
+ #now;
76
+ #chain = Promise.resolve();
77
+ constructor(options = {}) {
78
+ this.#maxRecords = options.maxRecords;
79
+ this.#defaultKnownSecrets = options.knownSecrets
80
+ ? [...options.knownSecrets].filter((s) => typeof s === "string" && s.length > 0)
81
+ : [];
82
+ this.#now = options.now ?? (() => new Date());
83
+ }
84
+ /** Serialize mutations so concurrent appends stay ordered and consistent. */
85
+ #exclusive(fn) {
86
+ const run = this.#chain.then(fn, fn);
87
+ this.#chain = run.then(() => undefined, () => undefined);
88
+ return run;
89
+ }
90
+ async appendDecision(request, decision, options = {}) {
91
+ return this.#exclusive(() => {
92
+ const known = mergeKnown(this.#defaultKnownSecrets, options.knownSecrets);
93
+ const now = (options.now ?? this.#now)();
94
+ const includePreview = options.includePromptPreview === true;
95
+ const maxChars = options.previewMaxChars ?? DEFAULT_PREVIEW_MAX_CHARS;
96
+ const apiKeyFallbackReason = extractApiKeyFallbackReason(decision);
97
+ const kind = apiKeyFallbackReason
98
+ ? "api_key_fallback"
99
+ : "route_decision";
100
+ const initialCancellation = options.cancellationState ??
101
+ (decision.blocked || decision.route == null ? "completed" : "active");
102
+ // Drop any raw-looking fields that might have leaked into reason/explanation.
103
+ const reason = safeRedact(decision.reason, known);
104
+ const explanation = safeRedact(decision.explanation, known);
105
+ const auditSummary = decision.auditSummary
106
+ ? safeRedact(decision.auditSummary, known)
107
+ : safeRedact(`decision=${decision.decisionId} digest=${request.task.promptDigest}`, known);
108
+ // Guard: never let raw promptPreview appear in stored free text.
109
+ const rawPreview = request.task.promptPreview;
110
+ const redactedStoredPreview = includePreview
111
+ ? clampPreview(safeRedact(rawPreview, known), maxChars)
112
+ : undefined;
113
+ const record = {
114
+ recordId: newId("aud"),
115
+ kind,
116
+ timestamp: now.toISOString(),
117
+ requestId: request.requestId,
118
+ decisionId: decision.decisionId,
119
+ profile: request.harness.profile,
120
+ harnessKind: request.harness.kind,
121
+ promptDigest: request.task.promptDigest,
122
+ auditSummary,
123
+ reason,
124
+ explanation,
125
+ route: decision.route ?? null,
126
+ fallbackChain: decision.fallbackChain
127
+ ? decision.fallbackChain.map((e) => ({ ...e }))
128
+ : undefined,
129
+ apiKeyFallbackReason,
130
+ policyDenials: decision.policyDenials
131
+ ? decision.policyDenials.map((d) => ({
132
+ ...d,
133
+ message: safeRedact(d.message, known),
134
+ }))
135
+ : undefined,
136
+ blocked: decision.blocked,
137
+ needsConfiguration: decision.needsConfiguration,
138
+ cancellationState: initialCancellation,
139
+ };
140
+ if (redactedStoredPreview !== undefined) {
141
+ // If redaction wiped the value entirely, keep a stable redacted token.
142
+ record.promptPreviewRedacted =
143
+ redactedStoredPreview.length > 0
144
+ ? redactedStoredPreview
145
+ : REDACTED;
146
+ }
147
+ // Assert default path never embeds raw preview (defensive for tests + misuse).
148
+ this.#assertNoRawLeak(record, rawPreview, known);
149
+ this.#push(record);
150
+ return structuredClone(record);
151
+ });
152
+ }
153
+ async recordApiKeyFallback(input) {
154
+ return this.#exclusive(() => {
155
+ const known = mergeKnown(this.#defaultKnownSecrets, input.knownSecrets);
156
+ const now = (input.now ?? this.#now)();
157
+ const record = {
158
+ recordId: newId("aud"),
159
+ kind: "api_key_fallback",
160
+ timestamp: now.toISOString(),
161
+ requestId: input.requestId,
162
+ decisionId: input.decisionId,
163
+ promptDigest: input.promptDigest,
164
+ reason: safeRedact(String(input.reason), known),
165
+ explanation: safeRedact(input.explanation, known),
166
+ route: input.route ?? null,
167
+ apiKeyFallbackReason: safeRedact(String(input.reason), known),
168
+ cancellationState: "active",
169
+ };
170
+ this.#push(record);
171
+ return structuredClone(record);
172
+ });
173
+ }
174
+ async markCancelled(requestId, options = {}) {
175
+ return this.#exclusive(() => {
176
+ const latest = this.#latestByRequest.get(requestId);
177
+ if (!latest)
178
+ return undefined;
179
+ if (latest.cancellationState === "cancelled") {
180
+ return structuredClone(latest);
181
+ }
182
+ const now = (options.now ?? this.#now)();
183
+ const known = this.#defaultKnownSecrets;
184
+ const update = {
185
+ ...latest,
186
+ recordId: newId("aud"),
187
+ kind: "cancellation",
188
+ timestamp: now.toISOString(),
189
+ cancellationState: "cancelled",
190
+ cancelledAt: now.toISOString(),
191
+ reason: options.reason
192
+ ? safeRedact(options.reason, known)
193
+ : "cancelled",
194
+ explanation: options.reason
195
+ ? safeRedact(options.reason, known)
196
+ : "Request cancelled in-process.",
197
+ };
198
+ this.#push(update);
199
+ return structuredClone(update);
200
+ });
201
+ }
202
+ async markCompleted(requestId, options = {}) {
203
+ return this.#exclusive(() => {
204
+ const latest = this.#latestByRequest.get(requestId);
205
+ if (!latest)
206
+ return undefined;
207
+ if (latest.cancellationState === "completed" ||
208
+ latest.cancellationState === "cancelled") {
209
+ return structuredClone(latest);
210
+ }
211
+ const now = (options.now ?? this.#now)();
212
+ const update = {
213
+ ...latest,
214
+ recordId: newId("aud"),
215
+ kind: latest.kind === "cancellation" ? "cancellation" : latest.kind,
216
+ timestamp: now.toISOString(),
217
+ cancellationState: "completed",
218
+ };
219
+ this.#push(update);
220
+ return structuredClone(update);
221
+ });
222
+ }
223
+ async getCancellationState(requestId) {
224
+ const latest = this.#latestByRequest.get(requestId);
225
+ return latest?.cancellationState;
226
+ }
227
+ async list(options = {}) {
228
+ let rows = this.#records.slice();
229
+ if (options.requestId) {
230
+ rows = rows.filter((r) => r.requestId === options.requestId);
231
+ }
232
+ if (options.decisionId) {
233
+ rows = rows.filter((r) => r.decisionId === options.decisionId);
234
+ }
235
+ if (options.kind) {
236
+ rows = rows.filter((r) => r.kind === options.kind);
237
+ }
238
+ // Most recent first.
239
+ rows = rows.slice().reverse();
240
+ if (options.limit !== undefined && options.limit >= 0) {
241
+ rows = rows.slice(0, options.limit);
242
+ }
243
+ return rows.map((r) => structuredClone(r));
244
+ }
245
+ async get(recordId) {
246
+ const hit = this.#byId.get(recordId);
247
+ return hit ? structuredClone(hit) : undefined;
248
+ }
249
+ toJSON() {
250
+ // Never surface free-text that might contain secrets beyond redacted fields.
251
+ return {
252
+ kind: "in-memory-audit",
253
+ recordCount: this.#records.length,
254
+ requestIds: [...new Set(this.#records.map((r) => r.requestId))],
255
+ // Digests only — no previews in the diagnostic surface.
256
+ digests: this.#records
257
+ .map((r) => r.promptDigest)
258
+ .filter((d) => typeof d === "string"),
259
+ kinds: this.#records.reduce((acc, r) => {
260
+ acc[r.kind] = (acc[r.kind] ?? 0) + 1;
261
+ return acc;
262
+ }, {}),
263
+ };
264
+ }
265
+ #push(record) {
266
+ this.#records.push(record);
267
+ this.#byId.set(record.recordId, record);
268
+ this.#latestByRequest.set(record.requestId, record);
269
+ if (this.#maxRecords !== undefined &&
270
+ this.#records.length > this.#maxRecords) {
271
+ const overflow = this.#records.length - this.#maxRecords;
272
+ const removed = this.#records.splice(0, overflow);
273
+ for (const old of removed) {
274
+ this.#byId.delete(old.recordId);
275
+ // Only drop latest mapping if it still points at the evicted record.
276
+ const latest = this.#latestByRequest.get(old.requestId);
277
+ if (latest?.recordId === old.recordId) {
278
+ this.#latestByRequest.delete(old.requestId);
279
+ }
280
+ }
281
+ }
282
+ }
283
+ #assertNoRawLeak(record, rawPreview, knownSecrets) {
284
+ if (!rawPreview || rawPreview.length < 8)
285
+ return;
286
+ const blobs = [
287
+ record.auditSummary,
288
+ record.reason,
289
+ record.explanation,
290
+ record.promptPreviewRedacted,
291
+ ...(record.policyDenials?.map((d) => d.message) ?? []),
292
+ ]
293
+ .filter((s) => typeof s === "string")
294
+ .join("\n");
295
+ // Allow the case where the "preview" is only a digest-like token or was fully redacted.
296
+ if (blobs.includes(rawPreview)) {
297
+ // Self-heal by scrubbing — should not happen under normal use.
298
+ if (record.auditSummary?.includes(rawPreview)) {
299
+ record.auditSummary = redact(record.auditSummary, knownSecrets).replaceAll(rawPreview, REDACTED);
300
+ }
301
+ if (record.explanation.includes(rawPreview)) {
302
+ record.explanation = redact(record.explanation, knownSecrets).replaceAll(rawPreview, REDACTED);
303
+ }
304
+ if (record.reason.includes(rawPreview)) {
305
+ record.reason = redact(record.reason, knownSecrets).replaceAll(rawPreview, REDACTED);
306
+ }
307
+ }
308
+ }
309
+ }
310
+ function mergeKnown(base, extra) {
311
+ const out = [...base];
312
+ if (extra) {
313
+ for (const s of extra) {
314
+ if (typeof s === "string" && s.length > 0)
315
+ out.push(s);
316
+ }
317
+ }
318
+ return out;
319
+ }
320
+ //# sourceMappingURL=audit.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audit.js","sourceRoot":"","sources":["../src/audit.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Credential-chain resolution for OffRouter.
3
+ *
4
+ * Tries each source in order: env -> config -> keychain -> (oauth, opt-in).
5
+ * Never logs secret values. Uses Redacted<T> wrapper for safe output.
6
+ * Never scrapes other CLI credential stores.
7
+ */
8
+ import type { SecretStore } from "../secrets.js";
9
+ /**
10
+ * Wrapper that hides secret material from JSON serialization and string
11
+ * conversion. The raw value is accessible via `.rawValue` for use in
12
+ * Authorization headers, never for logging.
13
+ */
14
+ export declare class Redacted<T extends string = string> {
15
+ #private;
16
+ constructor(value: T);
17
+ /** Access the raw secret value. Use only for provider API calls. */
18
+ get rawValue(): T;
19
+ /** Returns a constant redacted string for JSON serialization. */
20
+ toJSON(): string;
21
+ /** Returns a constant redacted string for debugging. */
22
+ toString(): string;
23
+ }
24
+ /**
25
+ * A single credential source in the chain.
26
+ * Each source is tried in order; the first non-empty value wins.
27
+ */
28
+ export type CredentialSource = {
29
+ type: "env";
30
+ name: string;
31
+ } | {
32
+ type: "config";
33
+ key: string;
34
+ } | {
35
+ type: "keychain";
36
+ providerId: string;
37
+ accountId?: string;
38
+ } | {
39
+ type: "oauth";
40
+ provider: string;
41
+ };
42
+ /**
43
+ * Result of a successful credential resolution.
44
+ */
45
+ export interface CredentialResult {
46
+ /**
47
+ * The resolved credential value, wrapped in Redacted so it cannot be
48
+ * accidentally logged or serialized.
49
+ */
50
+ value: Redacted<string>;
51
+ /** Which source type produced the value. */
52
+ source: "env" | "config" | "keychain" | "oauth";
53
+ }
54
+ /**
55
+ * Options for resolveCredential.
56
+ */
57
+ export interface ResolveCredentialOptions {
58
+ /** Secret store for keychain lookups. */
59
+ store: SecretStore;
60
+ /**
61
+ * Optional trusted config object. When provided, config sources can read
62
+ * provider-level apiKey values. Only use with trusted config from the
63
+ * user's OffRouter home directory.
64
+ */
65
+ config?: Record<string, unknown>;
66
+ /**
67
+ * Injectable fetch for any network calls during credential resolution
68
+ * (e.g. OAuth token refresh). Defaults to globalThis.fetch.
69
+ */
70
+ fetch?: typeof globalThis.fetch;
71
+ /**
72
+ * Optional OAuth token refresher. Given a provider id, refreshes the stored
73
+ * OAuth token (if possible) and returns the new access token, or null if
74
+ * refresh is not possible. When omitted, expired OAuth tokens resolve as
75
+ * null (no silent refresh during credential resolution).
76
+ */
77
+ refreshOAuthToken?: (provider: string) => Promise<string | null>;
78
+ }
79
+ /**
80
+ * Resolve a credential by trying each source in order.
81
+ * Returns the first non-empty value, or null if all sources are empty.
82
+ */
83
+ export declare function resolveCredential(sources: CredentialSource[], opts: ResolveCredentialOptions): Promise<CredentialResult | null>;
84
+ //# sourceMappingURL=credential-chain.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"credential-chain.d.ts","sourceRoot":"","sources":["../../src/auth/credential-chain.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAWjD;;;;GAIG;AACH,qBAAa,QAAQ,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM;;gBAGjC,KAAK,EAAE,CAAC;IAIpB,oEAAoE;IACpE,IAAI,QAAQ,IAAI,CAAC,CAEhB;IAED,iEAAiE;IACjE,MAAM,IAAI,MAAM;IAIhB,wDAAwD;IACxD,QAAQ,IAAI,MAAM;CAGnB;AAMD;;;GAGG;AACH,MAAM,MAAM,gBAAgB,GACxB;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC7B;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GAC/B;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,GAC5D;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC;AAExC;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B;;;OAGG;IACH,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IACxB,4CAA4C;IAC5C,MAAM,EAAE,KAAK,GAAG,QAAQ,GAAG,UAAU,GAAG,OAAO,CAAC;CACjD;AAED;;GAEG;AACH,MAAM,WAAW,wBAAwB;IACvC,yCAAyC;IACzC,KAAK,EAAE,WAAW,CAAC;IACnB;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC;;;OAGG;IACH,KAAK,CAAC,EAAE,OAAO,UAAU,CAAC,KAAK,CAAC;IAChC;;;;;OAKG;IACH,iBAAiB,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;CAClE;AAED;;;GAGG;AACH,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,gBAAgB,EAAE,EAC3B,IAAI,EAAE,wBAAwB,GAC7B,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAQlC"}