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,271 +0,0 @@
1
- import { afterEach, describe, expect, it } from "vitest";
2
- import {
3
- chmod,
4
- mkdtemp,
5
- readFile,
6
- readdir,
7
- rm,
8
- stat,
9
- writeFile,
10
- } from "node:fs/promises";
11
- import { tmpdir } from "node:os";
12
- import { join } from "node:path";
13
- import {
14
- FileSecretStore,
15
- KeychainSecretStore,
16
- KeychainUnavailableError,
17
- redact,
18
- type SecretStore,
19
- } from "./secrets.js";
20
-
21
- const tempDirs: string[] = [];
22
-
23
- afterEach(async () => {
24
- await Promise.all(
25
- tempDirs.splice(0).map((dir) => rm(dir, { recursive: true, force: true })),
26
- );
27
- });
28
-
29
- async function makeTempDir(): Promise<string> {
30
- const dir = await mkdtemp(join(tmpdir(), "offrouter-secrets-"));
31
- tempDirs.push(dir);
32
- return dir;
33
- }
34
-
35
- async function makeFileStore(): Promise<{
36
- dir: string;
37
- path: string;
38
- store: FileSecretStore;
39
- }> {
40
- const dir = await makeTempDir();
41
- const path = join(dir, "secrets.json");
42
- const store = new FileSecretStore({ filePath: path });
43
- return { dir, path, store };
44
- }
45
-
46
- describe("FileSecretStore", () => {
47
- it("stores and reads provider keys by provider id", async () => {
48
- const { store } = await makeFileStore();
49
-
50
- await store.set("openai", "sk-live-test-openai-key-001");
51
- await store.set("anthropic", "sk-ant-test-anthropic-key-002");
52
-
53
- await expect(store.get("openai")).resolves.toBe(
54
- "sk-live-test-openai-key-001",
55
- );
56
- await expect(store.get("anthropic")).resolves.toBe(
57
- "sk-ant-test-anthropic-key-002",
58
- );
59
- await expect(store.get("missing")).resolves.toBeUndefined();
60
-
61
- const providers = await store.listProviders();
62
- expect(providers.sort()).toEqual(["anthropic", "openai"]);
63
- });
64
-
65
- it("deletes provider keys by provider id", async () => {
66
- const { store } = await makeFileStore();
67
- await store.set("openai", "sk-delete-me");
68
- await store.delete("openai");
69
- await expect(store.get("openai")).resolves.toBeUndefined();
70
- await expect(store.listProviders()).resolves.toEqual([]);
71
- });
72
-
73
- it("persists across store instances sharing the same file", async () => {
74
- const { path, store } = await makeFileStore();
75
- await store.set("gemini", "AIzaSyTestpersistedkey1234567890");
76
-
77
- const reloaded = new FileSecretStore({ filePath: path });
78
- await expect(reloaded.get("gemini")).resolves.toBe(
79
- "AIzaSyTestpersistedkey1234567890",
80
- );
81
- });
82
-
83
- it("never returns secrets from toJSON", async () => {
84
- const secret = "sk-super-secret-value-never-json";
85
- const { store } = await makeFileStore();
86
- await store.set("openai", secret);
87
-
88
- const json = store.toJSON();
89
- const serialized = JSON.stringify(json);
90
-
91
- expect(serialized).not.toContain(secret);
92
- expect(serialized).not.toMatch(/sk-super-secret/);
93
- // Metadata about presence is fine; values are not.
94
- expect(json).toMatchObject({
95
- kind: "file",
96
- providers: ["openai"],
97
- });
98
- expect("secrets" in json).toBe(false);
99
- expect("values" in json).toBe(false);
100
- });
101
-
102
- it("JSON.stringify on the store itself never leaks secrets", async () => {
103
- const secret = "sk-json-stringify-leak-check-xyz";
104
- const { store } = await makeFileStore();
105
- await store.set("openai", secret);
106
-
107
- const out = JSON.stringify(store);
108
- expect(out).not.toContain(secret);
109
- expect(out).not.toMatch(/sk-json-stringify/);
110
- });
111
-
112
- it("writes secrets file with mode 0600 on POSIX", async () => {
113
- if (process.platform === "win32") {
114
- // Windows does not use POSIX permission bits the same way.
115
- return;
116
- }
117
-
118
- const { path, store } = await makeFileStore();
119
- await store.set("openai", "sk-mode-check-value");
120
-
121
- const info = await stat(path);
122
- expect(info.mode & 0o777).toBe(0o600);
123
- });
124
-
125
- it("preserves / reasserts 0600 even if file previously had looser perms", async () => {
126
- if (process.platform === "win32") {
127
- return;
128
- }
129
-
130
- const dir = await makeTempDir();
131
- const path = join(dir, "secrets.json");
132
- await writeFile(
133
- path,
134
- JSON.stringify({ version: 1, secrets: {} }),
135
- "utf8",
136
- );
137
- await chmod(path, 0o644);
138
-
139
- const store = new FileSecretStore({ filePath: path });
140
- await store.set("openai", "sk-tightened-perms");
141
-
142
- const info = await stat(path);
143
- expect(info.mode & 0o777).toBe(0o600);
144
- });
145
-
146
- it("does not write secrets as plain object dump keys other than provider map", async () => {
147
- const { path, store } = await makeFileStore();
148
- await store.set("openai", "sk-file-format-check");
149
-
150
- const raw = JSON.parse(await readFile(path, "utf8")) as {
151
- version: number;
152
- secrets: Record<string, string>;
153
- };
154
- expect(raw.version).toBe(1);
155
- expect(raw.secrets.openai).toBe("sk-file-format-check");
156
- });
157
- });
158
-
159
- describe("redaction", () => {
160
- it("replaces API key patterns in log strings via standalone redact()", () => {
161
- const log =
162
- 'provider error: invalid key sk-ant-api03-abcdefghijklmnopqrstuv and sk-live-abcdefghijklmnopqrstuv';
163
- const redacted = redact(log);
164
- expect(redacted).not.toContain("sk-ant-api03-abcdefghijklmnopqrstuv");
165
- expect(redacted).not.toContain("sk-live-abcdefghijklmnopqrstuv");
166
- expect(redacted).toMatch(/\[REDACTED\]/);
167
- });
168
-
169
- it("replaces stored provider secrets appearing in logs via SecretStore.redact", async () => {
170
- const { store } = await makeFileStore();
171
- const secret = "lr-custom-token-not-matching-prefix-patterns-zzzz";
172
- await store.set("local-provider", secret);
173
-
174
- const log = `downstream failed auth with ${secret} mid-message`;
175
- const redacted = store.redact(log);
176
-
177
- expect(redacted).not.toContain(secret);
178
- expect(redacted).toContain("[REDACTED]");
179
- expect(redacted).toContain("downstream failed auth with");
180
- });
181
-
182
- it("redacts file-backed secrets before the async cache is warm", async () => {
183
- const { path, store } = await makeFileStore();
184
- const secret = "lr-cold-cache-token-not-matching-prefix-patterns-zzzz";
185
- await store.set("local-provider", secret);
186
-
187
- const coldStore = new FileSecretStore({ filePath: path });
188
- const redacted = coldStore.redact(`downstream failed with ${secret}`);
189
-
190
- expect(redacted).not.toContain(secret);
191
- expect(redacted).toContain("[REDACTED]");
192
- });
193
-
194
- it("removes temporary files when persisting fails after writing the temp secret", async () => {
195
- const dir = await makeTempDir();
196
- const path = join(dir, "secrets.json");
197
- const store = new FileSecretStore({
198
- filePath: path,
199
- fileSystem: {
200
- async rename() {
201
- throw new Error("simulated rename failure");
202
- },
203
- },
204
- });
205
-
206
- await expect(store.set("openai", "sk-temp-cleanup-value")).rejects.toThrow();
207
-
208
- const entries = await readdir(dir);
209
- expect(entries.filter((entry) => entry.includes(".tmp"))).toEqual([]);
210
- });
211
-
212
- it("redacts bearer tokens and google-style api keys", () => {
213
- const log =
214
- "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.abc.def and key AIzaSyA-abcdefghijklmnopqrstuvwxyz012345";
215
- const redacted = redact(log);
216
- expect(redacted).not.toContain("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9");
217
- expect(redacted).not.toContain("AIzaSyA-abcdefghijklmnopqrstuvwxyz012345");
218
- expect(redacted).toMatch(/\[REDACTED\]/);
219
- });
220
-
221
- it("is idempotent on already-redacted text", () => {
222
- const once = redact("using sk-abcdefghijklmnopqrstuvwxyz0123 now");
223
- const twice = redact(once);
224
- expect(twice).toBe(once);
225
- });
226
- });
227
-
228
- describe("KeychainSecretStore", () => {
229
- it("exposes the SecretStore boundary without scraping CLI credentials", async () => {
230
- const store: SecretStore = new KeychainSecretStore({
231
- service: "offrouter-test",
232
- });
233
-
234
- // Stub must refuse get/set until a real OS keychain backend is wired.
235
- // It must never read foreign CLI credential stores.
236
- await expect(store.get("openai")).rejects.toBeInstanceOf(
237
- KeychainUnavailableError,
238
- );
239
- await expect(store.set("openai", "sk-should-not-store")).rejects.toBeInstanceOf(
240
- KeychainUnavailableError,
241
- );
242
- await expect(store.delete("openai")).rejects.toBeInstanceOf(
243
- KeychainUnavailableError,
244
- );
245
- await expect(store.listProviders()).rejects.toBeInstanceOf(
246
- KeychainUnavailableError,
247
- );
248
- });
249
-
250
- it("never returns secrets from toJSON", () => {
251
- const store = new KeychainSecretStore({ service: "offrouter-test" });
252
- const json = store.toJSON();
253
- const serialized = JSON.stringify(store);
254
-
255
- expect(json).toMatchObject({
256
- kind: "keychain",
257
- service: "offrouter-test",
258
- available: false,
259
- });
260
- expect(serialized).not.toMatch(/sk-/);
261
- expect("secrets" in json).toBe(false);
262
- expect("values" in json).toBe(false);
263
- });
264
-
265
- it("still redacts common API key patterns without stored secrets", () => {
266
- const store = new KeychainSecretStore({ service: "offrouter-test" });
267
- const log = "leak sk-abcdefghijklmnopqrstuvwxyz0123 here";
268
- expect(store.redact(log)).not.toContain("sk-abcdefghijklmnopqrstuvwxyz0123");
269
- expect(store.redact(log)).toContain("[REDACTED]");
270
- });
271
- });