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
@@ -1,368 +0,0 @@
1
- import { describe, expect, it, vi } from "vitest";
2
- import { InMemoryAuditStore } from "./audit.js";
3
- import { DelegationRuntime, type DelegationRequest } from "./delegation.js";
4
- import type { PolicyConfig } from "./policy.js";
5
- import { createFakeProvider } from "./providers/fake.js";
6
- import type { ProviderCandidate, RouteRequest } from "./types.js";
7
-
8
- const personalConfig: PolicyConfig = {
9
- allowlistedProfiles: ["claude-personal", "codex-personal", "gemini-personal"],
10
- deniedProfilePatterns: ["*-work"],
11
- };
12
-
13
- function routeBase(
14
- overrides: Partial<RouteRequest> & {
15
- harness?: Partial<RouteRequest["harness"]>;
16
- task?: Partial<RouteRequest["task"]>;
17
- workspace?: Partial<RouteRequest["workspace"]>;
18
- constraints?: Partial<RouteRequest["constraints"]>;
19
- } = {},
20
- ): RouteRequest {
21
- return {
22
- protocolVersion: "offrouter.route.v1",
23
- requestId: overrides.requestId ?? "req_delegate_1",
24
- harness: {
25
- kind: "claude",
26
- profile: "claude-personal",
27
- ...overrides.harness,
28
- },
29
- task: {
30
- promptPreview: "short preview",
31
- promptDigest: "sha256:deadbeef",
32
- kind: "explain",
33
- risk: "low",
34
- ...overrides.task,
35
- },
36
- workspace: {
37
- cwd: "/tmp/project",
38
- trusted: true,
39
- ...overrides.workspace,
40
- },
41
- constraints: {
42
- maxCostUsd: 1,
43
- localOnly: false,
44
- subscriptionFirst: true,
45
- allowApiKeyFallback: true,
46
- ...overrides.constraints,
47
- },
48
- };
49
- }
50
-
51
- function candidate(
52
- partial: Partial<ProviderCandidate> &
53
- Pick<ProviderCandidate, "providerId" | "modelId" | "authTier">,
54
- ): ProviderCandidate {
55
- return {
56
- authScope: "third-party",
57
- health: "healthy",
58
- supportsTools: true,
59
- supportsStreaming: true,
60
- supportsJson: true,
61
- supportsImages: false,
62
- estimatedCostUsd: 0.01,
63
- ...partial,
64
- };
65
- }
66
-
67
- function makeRequest(
68
- partial: Partial<DelegationRequest> & {
69
- route?: Partial<RouteRequest>;
70
- } = {},
71
- ): DelegationRequest {
72
- const route = routeBase(partial.route ?? {});
73
- return {
74
- taskId: partial.taskId ?? "task_1",
75
- mode: partial.mode ?? "oneshot",
76
- output: partial.output ?? "text",
77
- prompt: partial.prompt ?? "FULL SECRET PROMPT TEXT FOR DELEGATION",
78
- route,
79
- ...partial,
80
- route,
81
- };
82
- }
83
-
84
- describe("DelegationRuntime", () => {
85
- it("returns a one-shot fake provider response", async () => {
86
- const adapter = createFakeProvider({
87
- id: "fake",
88
- responseText: "one-shot body",
89
- });
90
- const candidates = [
91
- candidate({
92
- providerId: "fake",
93
- modelId: "fake-coder",
94
- authTier: "api-key",
95
- }),
96
- ];
97
- const runtime = new DelegationRuntime({
98
- adapters: { fake: adapter },
99
- candidates,
100
- policy: personalConfig,
101
- audit: new InMemoryAuditStore(),
102
- });
103
-
104
- const result = await runtime.delegate(makeRequest({ mode: "oneshot" }));
105
-
106
- expect(result.status).toBe("completed");
107
- expect(result.text).toBe("one-shot body");
108
- expect(result.events.some((e) => e.type === "done")).toBe(true);
109
- });
110
-
111
- it("streams fake provider events", async () => {
112
- const adapter = createFakeProvider({
113
- id: "fake",
114
- streamChunks: ["hello ", "world"],
115
- });
116
- const candidates = [
117
- candidate({
118
- providerId: "fake",
119
- modelId: "fake-coder",
120
- authTier: "api-key",
121
- }),
122
- ];
123
- const runtime = new DelegationRuntime({
124
- adapters: { fake: adapter },
125
- candidates,
126
- policy: personalConfig,
127
- });
128
-
129
- const events = [];
130
- for await (const event of runtime.stream(
131
- makeRequest({ mode: "stream", taskId: "task_stream" }),
132
- )) {
133
- events.push(event);
134
- }
135
-
136
- const deltas = events.filter((e) => e.type === "text-delta");
137
- expect(deltas.map((e) => ("text" in e ? e.text : "")).join("")).toBe(
138
- "hello world",
139
- );
140
- expect(events.some((e) => e.type === "done")).toBe(true);
141
- });
142
-
143
- it("tracks background task status", async () => {
144
- const adapter = createFakeProvider({
145
- id: "fake",
146
- responseText: "bg-done",
147
- streamDelayMs: 5,
148
- streamChunks: ["a", "b"],
149
- });
150
- const candidates = [
151
- candidate({
152
- providerId: "fake",
153
- modelId: "fake-coder",
154
- authTier: "api-key",
155
- }),
156
- ];
157
- const runtime = new DelegationRuntime({
158
- adapters: { fake: adapter },
159
- candidates,
160
- policy: personalConfig,
161
- });
162
-
163
- const task = await runtime.startBackground(
164
- makeRequest({ mode: "background", taskId: "task_bg" }),
165
- );
166
- expect(task.status === "running" || task.status === "queued").toBe(true);
167
-
168
- const settled = await runtime.wait(task.taskId);
169
- expect(settled.status).toBe("completed");
170
- expect(settled.text).toBe("ab");
171
- expect(runtime.getTask(task.taskId)?.status).toBe("completed");
172
- });
173
-
174
- it("cancels an active same-process task via adapter.cancel", async () => {
175
- const adapter = createFakeProvider({
176
- id: "fake",
177
- hangUntilCancel: true,
178
- });
179
- const cancelSpy = vi.spyOn(adapter, "cancel");
180
- const candidates = [
181
- candidate({
182
- providerId: "fake",
183
- modelId: "fake-coder",
184
- authTier: "api-key",
185
- }),
186
- ];
187
- const runtime = new DelegationRuntime({
188
- adapters: { fake: adapter },
189
- candidates,
190
- policy: personalConfig,
191
- });
192
-
193
- const task = await runtime.startBackground(
194
- makeRequest({ mode: "background", taskId: "task_cancel" }),
195
- );
196
- expect(task.status).toBe("running");
197
-
198
- const cancelled = await runtime.cancel(task.taskId);
199
- expect(cancelled?.status).toBe("cancelled");
200
- expect(cancelSpy).toHaveBeenCalledWith(task.callId);
201
-
202
- const settled = await runtime.wait(task.taskId);
203
- expect(settled.status).toBe("cancelled");
204
- });
205
-
206
- it("rejects full prompt text when policy denies the provider (before invoke)", async () => {
207
- let invokeCount = 0;
208
- const base = createFakeProvider({ id: "evil" });
209
- const adapter = {
210
- ...base,
211
- id: "evil",
212
- listModels: () => base.listModels(),
213
- checkAuth: () => base.checkAuth(),
214
- queryLimits: (t: Parameters<typeof base.queryLimits>[0]) =>
215
- base.queryLimits(t),
216
- estimate: (
217
- r: Parameters<typeof base.estimate>[0],
218
- m: string,
219
- t: Parameters<typeof base.estimate>[2],
220
- ) => base.estimate(r, m, t),
221
- async *invoke(
222
- req: Parameters<typeof base.invoke>[0],
223
- ): ReturnType<typeof base.invoke> {
224
- invokeCount += 1;
225
- yield* base.invoke(req);
226
- },
227
- cancel: (id: string) => base.cancel(id),
228
- };
229
-
230
- const candidates = [
231
- candidate({
232
- providerId: "evil",
233
- modelId: "evil-model",
234
- authTier: "api-key",
235
- }),
236
- ];
237
- const runtime = new DelegationRuntime({
238
- adapters: { evil: adapter },
239
- candidates,
240
- policy: {
241
- ...personalConfig,
242
- deniedProviders: ["evil"],
243
- },
244
- audit: new InMemoryAuditStore(),
245
- });
246
-
247
- const secret = "TOP_SECRET_PROMPT_NEVER_LEAVE";
248
- await expect(
249
- runtime.delegate(
250
- makeRequest({
251
- taskId: "task_denied",
252
- prompt: secret,
253
- }),
254
- ),
255
- ).rejects.toThrow(/policy|denied|blocked/i);
256
-
257
- expect(invokeCount).toBe(0);
258
- });
259
-
260
- it("rejects provider/model overrides that do not match the policy-selected route", async () => {
261
- let evilInvokeCount = 0;
262
- const fake = createFakeProvider({ id: "fake" });
263
- const evilBase = createFakeProvider({ id: "evil" });
264
- const evil = {
265
- ...evilBase,
266
- id: "evil",
267
- listModels: () => evilBase.listModels(),
268
- checkAuth: () => evilBase.checkAuth(),
269
- queryLimits: (t: Parameters<typeof evilBase.queryLimits>[0]) =>
270
- evilBase.queryLimits(t),
271
- estimate: (
272
- r: Parameters<typeof evilBase.estimate>[0],
273
- m: string,
274
- t: Parameters<typeof evilBase.estimate>[2],
275
- ) => evilBase.estimate(r, m, t),
276
- async *invoke(
277
- req: Parameters<typeof evilBase.invoke>[0],
278
- ): ReturnType<typeof evilBase.invoke> {
279
- evilInvokeCount += 1;
280
- yield* evilBase.invoke(req);
281
- },
282
- cancel: (id: string) => evilBase.cancel(id),
283
- };
284
-
285
- const candidates = [
286
- candidate({
287
- providerId: "fake",
288
- modelId: "fake-coder",
289
- authTier: "api-key",
290
- }),
291
- ];
292
- const runtime = new DelegationRuntime({
293
- adapters: { fake, evil },
294
- candidates,
295
- policy: personalConfig,
296
- audit: new InMemoryAuditStore(),
297
- });
298
-
299
- await expect(
300
- runtime.delegate(
301
- makeRequest({
302
- taskId: "task_override",
303
- prompt: "FULL PROMPT MUST NOT REACH EVIL",
304
- providerId: "evil",
305
- model: "evil-model",
306
- }),
307
- ),
308
- ).rejects.toThrow(/override|policy|selected/i);
309
-
310
- expect(evilInvokeCount).toBe(0);
311
- expect(runtime.getTask("task_override")?.status).toBe("failed");
312
- });
313
-
314
- it("writes audit with digest and redacted preview, never the raw prompt", async () => {
315
- const adapter = createFakeProvider({
316
- id: "fake",
317
- responseText: "ok",
318
- });
319
- const candidates = [
320
- candidate({
321
- providerId: "fake",
322
- modelId: "fake-coder",
323
- authTier: "api-key",
324
- }),
325
- ];
326
- const audit = new InMemoryAuditStore();
327
- const runtime = new DelegationRuntime({
328
- adapters: { fake: adapter },
329
- candidates,
330
- policy: personalConfig,
331
- audit,
332
- });
333
-
334
- const raw = "FULL RAW PROMPT sk-abcdefghijklmnopqrstuvwxyz0123456789SECRET";
335
- await runtime.delegate(
336
- makeRequest({
337
- taskId: "task_audit",
338
- prompt: raw,
339
- route: {
340
- requestId: "req_audit",
341
- task: {
342
- promptPreview: raw.slice(0, 40),
343
- promptDigest: "sha256:audit_digest",
344
- kind: "explain",
345
- risk: "low",
346
- },
347
- },
348
- }),
349
- );
350
-
351
- const records = await audit.list({ requestId: "req_audit" });
352
- expect(records.length).toBeGreaterThan(0);
353
-
354
- const joined = JSON.stringify(records);
355
- expect(joined).not.toContain(raw);
356
- expect(joined).not.toContain("sk-abcdefghijklmnopqrstuvwxyz0123456789");
357
-
358
- const withDigest = records.find((r) => r.promptDigest);
359
- expect(withDigest?.promptDigest).toBe("sha256:audit_digest");
360
-
361
- const withPreview = records.find((r) => r.promptPreviewRedacted);
362
- expect(withPreview?.promptPreviewRedacted).toBeDefined();
363
- expect(withPreview?.promptPreviewRedacted).not.toBe(raw);
364
- expect(withPreview?.promptPreviewRedacted).not.toContain(
365
- "sk-abcdefghijklmnopqrstuvwxyz0123456789",
366
- );
367
- });
368
- });