aisubs 0.3.7 → 0.3.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -0
- package/README.md +29 -29
- package/dist/auth.d.ts +3 -1
- package/dist/auth.js +123 -15
- package/dist/cli.js +5 -3
- package/dist/http.js +1 -0
- package/dist/index.d.ts +2 -7
- package/dist/index.js +1 -6
- package/dist/memory-store.d.ts +9 -0
- package/dist/memory-store.js +34 -0
- package/dist/node.d.ts +2 -0
- package/dist/node.js +2 -0
- package/dist/providers/chatgpt.d.ts +2 -1
- package/dist/providers/chatgpt.js +64 -2
- package/dist/providers/opencode.d.ts +6 -2
- package/dist/providers/opencode.js +65 -21
- package/dist/sqlite-store.d.ts +61 -0
- package/dist/sqlite-store.js +323 -0
- package/dist/store.d.ts +0 -23
- package/dist/store.js +1 -198
- package/dist/types.d.ts +40 -0
- package/dist/utils.js +14 -6
- package/examples/direct.mjs +8 -11
- package/examples/server.mjs +10 -15
- package/package.json +9 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.3.8 - 2026-09-19
|
|
4
|
+
|
|
5
|
+
- Breaking: `createSubscriptionAuth` now requires an explicit credential store.
|
|
6
|
+
- Breaking: provider factories use the `aisubs/providers/*` entrypoints so the core `aisubs` import stays runtime-portable.
|
|
7
|
+
- Add `aisubs/node` SQLite credential and API-key stores with shared-connection support and cross-process refresh ownership.
|
|
8
|
+
- Persist standalone AISubs records in `~/.aisubs/aisubs.db` without retaining the legacy file-store implementation.
|
|
9
|
+
|
|
3
10
|
## 0.3.7 - 2026-09-16
|
|
4
11
|
|
|
5
12
|
- Improve provider compatibility, proxy handling, and request normalization.
|
package/README.md
CHANGED
|
@@ -438,9 +438,14 @@ bun add aisubs
|
|
|
438
438
|
```
|
|
439
439
|
|
|
440
440
|
```js
|
|
441
|
-
import {
|
|
441
|
+
import { createSubscriptionAuth } from "aisubs";
|
|
442
|
+
import { SqliteCredentialStore } from "aisubs/node";
|
|
443
|
+
import { chatGptProvider } from "aisubs/providers/chatgpt";
|
|
442
444
|
|
|
443
|
-
const subscriptions = createSubscriptionAuth({
|
|
445
|
+
const subscriptions = createSubscriptionAuth({
|
|
446
|
+
store: new SqliteCredentialStore("./data/aisubs.db"),
|
|
447
|
+
providers: [chatGptProvider()],
|
|
448
|
+
});
|
|
444
449
|
const account = subscriptions.account("chatgpt", "personal");
|
|
445
450
|
|
|
446
451
|
if (!(await account.status()).authenticated) {
|
|
@@ -493,19 +498,16 @@ Useful account methods:
|
|
|
493
498
|
<summary><strong>Configure every provider and custom credential storage</strong></summary>
|
|
494
499
|
|
|
495
500
|
```js
|
|
496
|
-
import {
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
openCodeGoProvider,
|
|
504
|
-
openCodeZenProvider,
|
|
505
|
-
} from "aisubs";
|
|
501
|
+
import { createSubscriptionAuth } from "aisubs";
|
|
502
|
+
import { SqliteCredentialStore } from "aisubs/node";
|
|
503
|
+
import { chatGptProvider } from "aisubs/providers/chatgpt";
|
|
504
|
+
import { claudeProvider } from "aisubs/providers/claude";
|
|
505
|
+
import { copilotProvider } from "aisubs/providers/copilot";
|
|
506
|
+
import { grokProvider } from "aisubs/providers/grok";
|
|
507
|
+
import { openCodeGoProvider, openCodeZenProvider } from "aisubs/providers/opencode";
|
|
506
508
|
|
|
507
509
|
const subscriptions = createSubscriptionAuth({
|
|
508
|
-
store: new
|
|
510
|
+
store: new SqliteCredentialStore("./data/aisubs.db"),
|
|
509
511
|
providers: [
|
|
510
512
|
chatGptProvider(),
|
|
511
513
|
claudeProvider(),
|
|
@@ -517,9 +519,10 @@ const subscriptions = createSubscriptionAuth({
|
|
|
517
519
|
});
|
|
518
520
|
```
|
|
519
521
|
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
522
|
+
Storage is always explicit. The core SDK never discovers a home directory,
|
|
523
|
+
creates files, starts a server, or opens a browser. The standalone CLI composes
|
|
524
|
+
the SQLite adapter at `~/.aisubs/aisubs.db`. Select an account with its provider
|
|
525
|
+
ID and a local account name:
|
|
523
526
|
|
|
524
527
|
```js
|
|
525
528
|
const chatgpt = subscriptions.account("chatgpt", "personal");
|
|
@@ -596,25 +599,23 @@ const response = await selected.proxy("responses", requestOptions);
|
|
|
596
599
|
<summary><strong>Run the local HTTP server from Node.js</strong></summary>
|
|
597
600
|
|
|
598
601
|
This is the programmatic equivalent of `aisubs dashboard`. The API key is
|
|
599
|
-
created once and reused across restarts;
|
|
600
|
-
|
|
602
|
+
created once and reused across restarts; regenerate it when clients should
|
|
603
|
+
receive a new key.
|
|
601
604
|
|
|
602
605
|
```js
|
|
603
606
|
import { homedir } from "node:os";
|
|
604
607
|
import { join } from "node:path";
|
|
605
|
-
import {
|
|
606
|
-
FileApiKeyStore,
|
|
607
|
-
FileCredentialStore,
|
|
608
|
-
chatGptProvider,
|
|
609
|
-
claudeProvider,
|
|
610
|
-
createSubscriptionAuth,
|
|
611
|
-
} from "aisubs";
|
|
608
|
+
import { createSubscriptionAuth } from "aisubs";
|
|
612
609
|
import { createSubscriptionAuthServer } from "aisubs/http";
|
|
610
|
+
import { SqliteApiKeyStore, SqliteCredentialStore } from "aisubs/node";
|
|
611
|
+
import { chatGptProvider } from "aisubs/providers/chatgpt";
|
|
612
|
+
import { claudeProvider } from "aisubs/providers/claude";
|
|
613
613
|
|
|
614
614
|
const directory = join(homedir(), ".aisubs");
|
|
615
|
-
const
|
|
615
|
+
const database = join(directory, "aisubs.db");
|
|
616
|
+
const apiKey = await new SqliteApiKeyStore(database).readOrCreate();
|
|
616
617
|
const auth = createSubscriptionAuth({
|
|
617
|
-
store: new
|
|
618
|
+
store: new SqliteCredentialStore(database),
|
|
618
619
|
providers: [chatGptProvider(), claudeProvider()],
|
|
619
620
|
});
|
|
620
621
|
|
|
@@ -674,8 +675,7 @@ the old key.
|
|
|
674
675
|
|
|
675
676
|
## Storage and security
|
|
676
677
|
|
|
677
|
-
- Credentials: `~/.aisubs/
|
|
678
|
-
- Persistent local API key: `~/.aisubs/api-key`.
|
|
678
|
+
- Credentials and persistent local API key: `~/.aisubs/aisubs.db`.
|
|
679
679
|
- Optional Codex catalog: `~/.codex/aisubs-catalog.json`.
|
|
680
680
|
- Codex integration stores the local AISubs key in the user-private Codex config.
|
|
681
681
|
- State directories and files use private permissions where the platform supports them.
|
package/dist/auth.d.ts
CHANGED
|
@@ -29,6 +29,7 @@ export declare class SubscriptionAuth {
|
|
|
29
29
|
private readonly attempts;
|
|
30
30
|
private readonly generations;
|
|
31
31
|
private readonly refreshes;
|
|
32
|
+
private readonly coordinatedRefreshes;
|
|
32
33
|
private readonly usageCache;
|
|
33
34
|
private readonly modelsCache;
|
|
34
35
|
private readonly usageInflight;
|
|
@@ -62,6 +63,7 @@ export declare class SubscriptionAuth {
|
|
|
62
63
|
listAccounts(provider: ProviderId): Promise<Session[]>;
|
|
63
64
|
signOut(provider: ProviderId, account?: string): Promise<void>;
|
|
64
65
|
private credential;
|
|
66
|
+
private coordinatedCredential;
|
|
65
67
|
getAccessToken(provider: ProviderId, account?: string): Promise<string>;
|
|
66
68
|
credentialSummary(provider: ProviderId, account?: string): Promise<CredentialSummary>;
|
|
67
69
|
details(provider: ProviderId, account?: string, signal?: AbortSignal): Promise<SubscriptionAccountDetails>;
|
|
@@ -74,6 +76,6 @@ export declare class SubscriptionAuth {
|
|
|
74
76
|
account(provider: ProviderId, account: string): SubscriptionAccount;
|
|
75
77
|
}
|
|
76
78
|
export declare function createSubscriptionAuth(options: {
|
|
77
|
-
store
|
|
79
|
+
store: CredentialStore;
|
|
78
80
|
providers: readonly ProviderAdapter[];
|
|
79
81
|
} & SubscriptionAuthOptions): SubscriptionAuth;
|
package/dist/auth.js
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
|
-
import { defaultAiSubsDataDir, FileCredentialStore } from "./store.js";
|
|
2
1
|
import { errorMessage } from "./utils.js";
|
|
3
|
-
import { join } from "node:path";
|
|
4
2
|
export const DEFAULT_ACCOUNT = "default";
|
|
5
3
|
const ACCOUNT_STORAGE_PREFIX = "$subscription-account$";
|
|
6
4
|
const LOGIN_ATTEMPT_RETENTION_MS = 5 * 60_000;
|
|
5
|
+
function isCoordinatedStore(store) {
|
|
6
|
+
return ("readVersioned" in store &&
|
|
7
|
+
"replaceCredential" in store &&
|
|
8
|
+
"claimRefresh" in store &&
|
|
9
|
+
"commitRefresh" in store);
|
|
10
|
+
}
|
|
7
11
|
function normalizeAccountKey(value) {
|
|
8
12
|
if (value == null)
|
|
9
13
|
return DEFAULT_ACCOUNT;
|
|
@@ -63,6 +67,7 @@ export class SubscriptionAuth {
|
|
|
63
67
|
attempts = new Map();
|
|
64
68
|
generations = new Map();
|
|
65
69
|
refreshes = new Map();
|
|
70
|
+
coordinatedRefreshes = new Map();
|
|
66
71
|
usageCache = new Map();
|
|
67
72
|
modelsCache = new Map();
|
|
68
73
|
usageInflight = new Map();
|
|
@@ -157,7 +162,10 @@ export class SubscriptionAuth {
|
|
|
157
162
|
const accountKey = normalizeAccountKey(options?.account);
|
|
158
163
|
const scope = credentialKey(provider, accountKey);
|
|
159
164
|
const replace = options?.replace !== false;
|
|
160
|
-
|
|
165
|
+
const durable = isCoordinatedStore(this.store)
|
|
166
|
+
? await this.store.readVersioned(scope)
|
|
167
|
+
: undefined;
|
|
168
|
+
if (!replace && (durable?.credential ?? (await this.store.read(scope)))) {
|
|
161
169
|
throw new Error(`Account name ${accountKey} is already connected for ${provider}`);
|
|
162
170
|
}
|
|
163
171
|
const epoch = this.advance(scope);
|
|
@@ -166,6 +174,7 @@ export class SubscriptionAuth {
|
|
|
166
174
|
this.cancelAttempt(attempt);
|
|
167
175
|
}
|
|
168
176
|
const abort = new AbortController();
|
|
177
|
+
const id = crypto.randomUUID();
|
|
169
178
|
const providerOptions = { ...options };
|
|
170
179
|
delete providerOptions.account;
|
|
171
180
|
delete providerOptions.replace;
|
|
@@ -175,18 +184,31 @@ export class SubscriptionAuth {
|
|
|
175
184
|
void login.complete.catch(() => { });
|
|
176
185
|
throw new Error("Login cancelled");
|
|
177
186
|
}
|
|
178
|
-
const id = crypto.randomUUID();
|
|
179
187
|
let record;
|
|
180
188
|
const promise = login.complete
|
|
181
189
|
.then(async (credential) => {
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
+
let saved;
|
|
191
|
+
if (isCoordinatedStore(this.store)) {
|
|
192
|
+
const replacement = await this.store.replaceCredential({
|
|
193
|
+
provider: scope,
|
|
194
|
+
credential,
|
|
195
|
+
expectedGeneration: durable?.generation ?? 0,
|
|
196
|
+
operationId: `login:${id}`,
|
|
197
|
+
});
|
|
198
|
+
if (!replacement.applied)
|
|
199
|
+
throw new Error("Login cancelled");
|
|
200
|
+
saved = replacement.record.credential;
|
|
201
|
+
}
|
|
202
|
+
else {
|
|
203
|
+
saved = await this.store.modify(scope, (current) => {
|
|
204
|
+
if (this.generation(scope) !== epoch)
|
|
205
|
+
return current;
|
|
206
|
+
if (current && !replace) {
|
|
207
|
+
throw new Error(`Account name ${accountKey} is already connected for ${provider}`);
|
|
208
|
+
}
|
|
209
|
+
return credential;
|
|
210
|
+
});
|
|
211
|
+
}
|
|
190
212
|
if (this.generation(scope) !== epoch || !saved)
|
|
191
213
|
throw new Error("Login cancelled");
|
|
192
214
|
this.clearMetadata(scope);
|
|
@@ -290,12 +312,31 @@ export class SubscriptionAuth {
|
|
|
290
312
|
if (attempt.scope === scope)
|
|
291
313
|
this.cancelAttempt(attempt);
|
|
292
314
|
}
|
|
293
|
-
|
|
315
|
+
if (isCoordinatedStore(this.store)) {
|
|
316
|
+
await this.store.deleteCredential({
|
|
317
|
+
provider: scope,
|
|
318
|
+
operationId: `logout:${crypto.randomUUID()}`,
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
else {
|
|
322
|
+
await this.store.delete(scope);
|
|
323
|
+
}
|
|
294
324
|
}
|
|
295
325
|
async credential(provider, account, forceRefresh = false) {
|
|
296
326
|
const adapter = this.adapter(provider);
|
|
297
327
|
const accountKey = normalizeAccountKey(account);
|
|
298
328
|
const scope = credentialKey(provider, accountKey);
|
|
329
|
+
if (isCoordinatedStore(this.store)) {
|
|
330
|
+
const pending = this.coordinatedRefreshes.get(scope);
|
|
331
|
+
if (pending)
|
|
332
|
+
return pending;
|
|
333
|
+
const operation = this.coordinatedCredential(this.store, adapter, provider, accountKey, scope, forceRefresh).finally(() => {
|
|
334
|
+
if (this.coordinatedRefreshes.get(scope) === operation)
|
|
335
|
+
this.coordinatedRefreshes.delete(scope);
|
|
336
|
+
});
|
|
337
|
+
this.coordinatedRefreshes.set(scope, operation);
|
|
338
|
+
return operation;
|
|
339
|
+
}
|
|
299
340
|
const observed = await this.store.read(scope);
|
|
300
341
|
if (!observed)
|
|
301
342
|
throw new Error(`Not authenticated with ${provider} account ${accountKey}`);
|
|
@@ -350,6 +391,74 @@ export class SubscriptionAuth {
|
|
|
350
391
|
this.clearMetadata(scope);
|
|
351
392
|
return refreshed;
|
|
352
393
|
}
|
|
394
|
+
async coordinatedCredential(store, adapter, provider, accountKey, scope, forceRefresh) {
|
|
395
|
+
const observed = await store.readVersioned(scope);
|
|
396
|
+
const credential = observed.credential;
|
|
397
|
+
if (!credential)
|
|
398
|
+
throw new Error(`Not authenticated with ${provider} account ${accountKey}`);
|
|
399
|
+
if (credential.metadata?.reauthRequired === true) {
|
|
400
|
+
throw new Error(`Session expired for ${provider} account ${accountKey}; sign in again`);
|
|
401
|
+
}
|
|
402
|
+
if (!forceRefresh && credential.expiresAt > Date.now())
|
|
403
|
+
return credential;
|
|
404
|
+
const claimId = crypto.randomUUID();
|
|
405
|
+
const claim = await store.claimRefresh({
|
|
406
|
+
provider: scope,
|
|
407
|
+
expectedVersion: observed.version,
|
|
408
|
+
expectedGeneration: observed.generation,
|
|
409
|
+
claimId,
|
|
410
|
+
operationId: `refresh-claim:${claimId}`,
|
|
411
|
+
});
|
|
412
|
+
if (claim === "missing") {
|
|
413
|
+
throw new Error(`Not authenticated with ${provider} account ${accountKey}`);
|
|
414
|
+
}
|
|
415
|
+
if (claim !== "claimed") {
|
|
416
|
+
const deadline = Date.now() + this.refreshTimeoutMs;
|
|
417
|
+
while (Date.now() < deadline) {
|
|
418
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
419
|
+
const latest = await store.readVersioned(scope);
|
|
420
|
+
if (latest.version === observed.version && latest.generation === observed.generation)
|
|
421
|
+
continue;
|
|
422
|
+
if (!latest.credential || latest.credential.metadata?.reauthRequired === true)
|
|
423
|
+
break;
|
|
424
|
+
if (latest.credential.expiresAt > Date.now())
|
|
425
|
+
return latest.credential;
|
|
426
|
+
break;
|
|
427
|
+
}
|
|
428
|
+
throw new Error(`Refresh outcome is uncertain for ${provider} account ${accountKey}; sign in again`);
|
|
429
|
+
}
|
|
430
|
+
let next;
|
|
431
|
+
try {
|
|
432
|
+
next = await adapter.refresh(credential, AbortSignal.timeout(this.refreshTimeoutMs));
|
|
433
|
+
}
|
|
434
|
+
catch (error) {
|
|
435
|
+
if (!adapter.isPermanentRefreshError?.(error))
|
|
436
|
+
throw error;
|
|
437
|
+
next = {
|
|
438
|
+
accessToken: "",
|
|
439
|
+
expiresAt: 0,
|
|
440
|
+
account: credential.account,
|
|
441
|
+
metadata: { ...credential.metadata, reauthRequired: true },
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
const committed = await store.commitRefresh({
|
|
445
|
+
provider: scope,
|
|
446
|
+
claimId,
|
|
447
|
+
expectedGeneration: observed.generation,
|
|
448
|
+
credential: next,
|
|
449
|
+
operationId: `refresh-commit:${claimId}`,
|
|
450
|
+
});
|
|
451
|
+
if (!committed.applied) {
|
|
452
|
+
throw new Error(`Session changed while refreshing ${provider} account ${accountKey}`);
|
|
453
|
+
}
|
|
454
|
+
const saved = committed.record.credential;
|
|
455
|
+
if (!saved || saved.metadata?.reauthRequired === true) {
|
|
456
|
+
this.clearMetadata(scope);
|
|
457
|
+
throw new Error(`Session expired for ${provider} account ${accountKey}; sign in again`);
|
|
458
|
+
}
|
|
459
|
+
this.clearMetadata(scope);
|
|
460
|
+
return saved;
|
|
461
|
+
}
|
|
353
462
|
async getAccessToken(provider, account = DEFAULT_ACCOUNT) {
|
|
354
463
|
const credential = await this.credential(provider, account);
|
|
355
464
|
if (credential.metadata?.delegatedCli === true) {
|
|
@@ -512,6 +621,5 @@ export class SubscriptionAuth {
|
|
|
512
621
|
}
|
|
513
622
|
}
|
|
514
623
|
export function createSubscriptionAuth(options) {
|
|
515
|
-
|
|
516
|
-
return new SubscriptionAuth(store, options.providers, options);
|
|
624
|
+
return new SubscriptionAuth(options.store, options.providers, options);
|
|
517
625
|
}
|
package/dist/cli.js
CHANGED
|
@@ -8,7 +8,8 @@ import { claudeProvider } from "./providers/claude.js";
|
|
|
8
8
|
import { copilotProvider } from "./providers/copilot.js";
|
|
9
9
|
import { grokProvider } from "./providers/grok.js";
|
|
10
10
|
import { openCodeGoProvider, openCodeZenProvider } from "./providers/opencode.js";
|
|
11
|
-
import { defaultAiSubsDataDir
|
|
11
|
+
import { defaultAiSubsDataDir } from "./store.js";
|
|
12
|
+
import { SqliteApiKeyStore, SqliteCredentialStore } from "./sqlite-store.js";
|
|
12
13
|
const DEFAULT_DASHBOARD_PORT = 4319;
|
|
13
14
|
function usage() {
|
|
14
15
|
console.log(`AI Subs
|
|
@@ -61,8 +62,9 @@ async function main() {
|
|
|
61
62
|
}
|
|
62
63
|
if (!Number.isInteger(port) || port < 0 || port > 65535)
|
|
63
64
|
throw new Error("Invalid port");
|
|
64
|
-
const
|
|
65
|
-
const
|
|
65
|
+
const database = join(dataDirectory, "aisubs.db");
|
|
66
|
+
const store = new SqliteCredentialStore(database);
|
|
67
|
+
const apiKeys = new SqliteApiKeyStore(database);
|
|
66
68
|
const auth = createSubscriptionAuth({
|
|
67
69
|
store,
|
|
68
70
|
providers: [
|
package/dist/http.js
CHANGED
|
@@ -112,6 +112,7 @@ function openAiModel(provider, model) {
|
|
|
112
112
|
capabilities: {
|
|
113
113
|
endpoints: model.endpoints ?? [],
|
|
114
114
|
input_modalities: model.inputModalities ?? ["text"],
|
|
115
|
+
output_modalities: model.outputModalities ?? ["text"],
|
|
115
116
|
reasoning_efforts: model.reasoningEfforts ?? [],
|
|
116
117
|
tools: model.supportsToolCall ?? false,
|
|
117
118
|
},
|
package/dist/index.d.ts
CHANGED
|
@@ -1,9 +1,4 @@
|
|
|
1
1
|
export { createSubscriptionAuth, DEFAULT_ACCOUNT, SubscriptionAuth, type SubscriptionAccount, type SubscriptionAuthOptions, } from "./auth.js";
|
|
2
|
-
export {
|
|
3
|
-
export { chatGptProvider, type ChatGptProviderOptions } from "./providers/chatgpt.js";
|
|
4
|
-
export { claudeProvider, type ClaudeProviderOptions } from "./providers/claude.js";
|
|
5
|
-
export { copilotProvider, type CopilotProviderOptions } from "./providers/copilot.js";
|
|
6
|
-
export { grokProvider, type GrokProviderOptions } from "./providers/grok.js";
|
|
7
|
-
export { openCodeGoProvider, openCodeZenProvider } from "./providers/opencode.js";
|
|
2
|
+
export { MemoryCredentialStore } from "./memory-store.js";
|
|
8
3
|
export { parseChatGptUsage, parseCopilotUsage, parseGrokUsage } from "./usage.js";
|
|
9
|
-
export type { CredentialStore, CredentialSummary, BrowserLoginPrompt, DeviceLoginPrompt, ImmediateLoginPrompt, LoginAttempt, LoginMode, LoginPrompt, LoginState, OAuthCredential, ProviderAdapter, ProviderId, ProviderLogin, ProviderLoginField, ProviderModel, ProviderModels, ProviderSummary, ProviderUsage, ProviderUsageContext, ProviderUsageData, Session, SubscriptionAccountDetails, UsageFact, UsageMeter, UsageResetCredit, UsageResetCredits, } from "./types.js";
|
|
4
|
+
export type { CredentialStore, CoordinatedCredentialStore, CredentialSummary, BrowserLoginPrompt, DeviceLoginPrompt, ImmediateLoginPrompt, LoginAttempt, LoginMode, LoginPrompt, LoginState, OAuthCredential, VersionedCredential, ProviderAdapter, ProviderId, ProviderLogin, ProviderLoginField, ProviderModel, ProviderModels, ProviderSummary, ProviderUsage, ProviderUsageContext, ProviderUsageData, Session, SubscriptionAccountDetails, UsageFact, UsageMeter, UsageResetCredit, UsageResetCredits, } from "./types.js";
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,3 @@
|
|
|
1
1
|
export { createSubscriptionAuth, DEFAULT_ACCOUNT, SubscriptionAuth, } from "./auth.js";
|
|
2
|
-
export {
|
|
3
|
-
export { chatGptProvider } from "./providers/chatgpt.js";
|
|
4
|
-
export { claudeProvider } from "./providers/claude.js";
|
|
5
|
-
export { copilotProvider } from "./providers/copilot.js";
|
|
6
|
-
export { grokProvider } from "./providers/grok.js";
|
|
7
|
-
export { openCodeGoProvider, openCodeZenProvider } from "./providers/opencode.js";
|
|
2
|
+
export { MemoryCredentialStore } from "./memory-store.js";
|
|
8
3
|
export { parseChatGptUsage, parseCopilotUsage, parseGrokUsage } from "./usage.js";
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { CredentialStore, OAuthCredential, ProviderId } from "./types.js";
|
|
2
|
+
export declare class MemoryCredentialStore implements CredentialStore {
|
|
3
|
+
private readonly values;
|
|
4
|
+
private readonly queues;
|
|
5
|
+
read(provider: ProviderId): Promise<OAuthCredential | null>;
|
|
6
|
+
listKeys(): Promise<string[]>;
|
|
7
|
+
modify(provider: ProviderId, update: (current: OAuthCredential | null) => OAuthCredential | null | Promise<OAuthCredential | null>): Promise<OAuthCredential | null>;
|
|
8
|
+
delete(provider: ProviderId): Promise<void>;
|
|
9
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export class MemoryCredentialStore {
|
|
2
|
+
values = new Map();
|
|
3
|
+
queues = new Map();
|
|
4
|
+
async read(provider) {
|
|
5
|
+
return this.values.get(provider) ?? null;
|
|
6
|
+
}
|
|
7
|
+
async listKeys() {
|
|
8
|
+
return [...this.values.keys()];
|
|
9
|
+
}
|
|
10
|
+
async modify(provider, update) {
|
|
11
|
+
const previous = this.queues.get(provider) ?? Promise.resolve();
|
|
12
|
+
let result = null;
|
|
13
|
+
const current = previous.then(async () => {
|
|
14
|
+
result = await update(this.values.get(provider) ?? null);
|
|
15
|
+
if (result)
|
|
16
|
+
this.values.set(provider, result);
|
|
17
|
+
else
|
|
18
|
+
this.values.delete(provider);
|
|
19
|
+
});
|
|
20
|
+
const settled = current.catch(() => { });
|
|
21
|
+
this.queues.set(provider, settled);
|
|
22
|
+
try {
|
|
23
|
+
await current;
|
|
24
|
+
}
|
|
25
|
+
finally {
|
|
26
|
+
if (this.queues.get(provider) === settled)
|
|
27
|
+
this.queues.delete(provider);
|
|
28
|
+
}
|
|
29
|
+
return result;
|
|
30
|
+
}
|
|
31
|
+
async delete(provider) {
|
|
32
|
+
await this.modify(provider, () => null);
|
|
33
|
+
}
|
|
34
|
+
}
|
package/dist/node.d.ts
ADDED
package/dist/node.js
ADDED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type { ProviderAdapter } from "../types.js";
|
|
1
|
+
import type { ProviderAdapter, ProviderModel } from "../types.js";
|
|
2
|
+
export declare function parseOpenAiImageModels(html: string): ProviderModel[];
|
|
2
3
|
export interface ChatGptProviderOptions {
|
|
3
4
|
clientId?: string;
|
|
4
5
|
compatibilityVersion?: string;
|
|
@@ -12,6 +12,7 @@ const VERIFICATION_URL = `${ISSUER}/codex/device`;
|
|
|
12
12
|
const USAGE_URL = "https://chatgpt.com/backend-api/wham/usage";
|
|
13
13
|
const RESET_CREDITS_URL = "https://chatgpt.com/backend-api/wham/rate-limit-reset-credits";
|
|
14
14
|
const MODELS_URL = "https://chatgpt.com/backend-api/codex/models";
|
|
15
|
+
const OPENAI_MODELS_URL = "https://developers.openai.com/api/docs/models";
|
|
15
16
|
const EXPIRY_SKEW_MS = 5 * 60_000;
|
|
16
17
|
const BROWSER_LOGIN_TIMEOUT_MS = 10 * 60_000;
|
|
17
18
|
const BROWSER_CALLBACK_PORTS = [1455, 1457];
|
|
@@ -84,6 +85,7 @@ function normalizeModel(value) {
|
|
|
84
85
|
})
|
|
85
86
|
: [];
|
|
86
87
|
const visibility = stringValue(value.visibility);
|
|
88
|
+
const outputModalities = stringArray(value.output_modalities);
|
|
87
89
|
return {
|
|
88
90
|
id,
|
|
89
91
|
name: stringValue(value.display_name) ?? stringValue(value.name),
|
|
@@ -92,12 +94,65 @@ function normalizeModel(value) {
|
|
|
92
94
|
maxOutputTokens: numberValue(value.max_output_tokens),
|
|
93
95
|
reasoningEfforts: levels.length ? levels : stringArray(value.supported_reasoning_efforts),
|
|
94
96
|
inputModalities: stringArray(value.input_modalities),
|
|
95
|
-
|
|
97
|
+
outputModalities,
|
|
98
|
+
endpoints: outputModalities?.includes("image")
|
|
99
|
+
? ["images/generations", "images/edits"]
|
|
100
|
+
: ["responses"],
|
|
96
101
|
supportsToolCall: value.supports_tool_calls === false || value.supports_tools === false ? false : true,
|
|
97
102
|
available: visibility !== "hide" && value.supported_in_api !== false,
|
|
98
103
|
priority: numberValue(value.priority) ?? Number.MAX_SAFE_INTEGER,
|
|
99
104
|
};
|
|
100
105
|
}
|
|
106
|
+
function imageModelName(id) {
|
|
107
|
+
const [prefix, suffix] = id.startsWith("chatgpt-image-")
|
|
108
|
+
? ["ChatGPT Image", id.slice("chatgpt-image-".length)]
|
|
109
|
+
: id.startsWith("dall-e-")
|
|
110
|
+
? ["DALL-E", id.slice("dall-e-".length)]
|
|
111
|
+
: ["GPT Image", id.slice("gpt-image-".length)];
|
|
112
|
+
const label = suffix
|
|
113
|
+
.split("-")
|
|
114
|
+
.map((part) => (/^[a-z]/.test(part) ? part[0].toUpperCase() + part.slice(1) : part))
|
|
115
|
+
.join(" ");
|
|
116
|
+
return `${prefix} ${label}`.trim();
|
|
117
|
+
}
|
|
118
|
+
export function parseOpenAiImageModels(html) {
|
|
119
|
+
const ids = new Set();
|
|
120
|
+
const links = html.matchAll(/href=["'](?:https:\/\/developers\.openai\.com)?\/api\/docs\/models\/([^"'/?#]+)[^"']*["']/gi);
|
|
121
|
+
for (const link of links) {
|
|
122
|
+
const id = decodeURIComponent(link[1] ?? "").toLowerCase();
|
|
123
|
+
if (/^(?:gpt-image-|chatgpt-image-|dall-e-)/.test(id))
|
|
124
|
+
ids.add(id);
|
|
125
|
+
}
|
|
126
|
+
return [...ids].map(openAiImageModel);
|
|
127
|
+
}
|
|
128
|
+
function openAiImageModel(id) {
|
|
129
|
+
return {
|
|
130
|
+
id,
|
|
131
|
+
name: imageModelName(id),
|
|
132
|
+
inputModalities: ["text", "image"],
|
|
133
|
+
outputModalities: ["image"],
|
|
134
|
+
endpoints: ["images/generations", "images/edits"],
|
|
135
|
+
supportsToolCall: false,
|
|
136
|
+
available: true,
|
|
137
|
+
selectable: true,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
async function discoverOpenAiImageModels(fetcher, signal) {
|
|
141
|
+
try {
|
|
142
|
+
const response = await fetcher(OPENAI_MODELS_URL, {
|
|
143
|
+
headers: { accept: "text/html" },
|
|
144
|
+
signal,
|
|
145
|
+
});
|
|
146
|
+
if (!response.ok)
|
|
147
|
+
return [];
|
|
148
|
+
return parseOpenAiImageModels(await response.text());
|
|
149
|
+
}
|
|
150
|
+
catch (error) {
|
|
151
|
+
if (signal.aborted)
|
|
152
|
+
throw error;
|
|
153
|
+
return [];
|
|
154
|
+
}
|
|
155
|
+
}
|
|
101
156
|
async function normalizeChatGptRequest(request) {
|
|
102
157
|
if (request.method !== "POST" || !new URL(request.url).pathname.endsWith("/responses")) {
|
|
103
158
|
return request;
|
|
@@ -115,6 +170,7 @@ async function normalizeChatGptRequest(request) {
|
|
|
115
170
|
headers.set("session-id", sessionId);
|
|
116
171
|
// Preserve instruction placement and message boundaries. The native Codex
|
|
117
172
|
// client sends top-level instructions too; moving them does not enable caching.
|
|
173
|
+
delete body.max_output_tokens;
|
|
118
174
|
delete body.prompt_cache_options;
|
|
119
175
|
delete body.prompt_cache_retention;
|
|
120
176
|
const stripBreakpoints = (value) => Array.isArray(value)
|
|
@@ -459,12 +515,18 @@ export function chatGptProvider(options = {}) {
|
|
|
459
515
|
const response = await fetch(url, { headers: { accept: "application/json" }, signal });
|
|
460
516
|
const raw = await responseJson(response, "ChatGPT models");
|
|
461
517
|
const models = Array.isArray(raw.models) ? raw.models : [];
|
|
462
|
-
|
|
518
|
+
const languageModels = models
|
|
463
519
|
.map(normalizeModel)
|
|
464
520
|
.filter((model) => Boolean(model))
|
|
465
521
|
.filter((model) => model.available !== false)
|
|
466
522
|
.sort((left, right) => left.priority - right.priority)
|
|
467
523
|
.map(({ priority: _priority, ...model }) => model);
|
|
524
|
+
const listed = new Map(languageModels.map((model) => [model.id, model]));
|
|
525
|
+
for (const model of await discoverOpenAiImageModels(fetcher, signal)) {
|
|
526
|
+
if (!listed.has(model.id))
|
|
527
|
+
listed.set(model.id, model);
|
|
528
|
+
}
|
|
529
|
+
return [...listed.values()];
|
|
468
530
|
},
|
|
469
531
|
isPermanentRefreshError(error) {
|
|
470
532
|
return (error instanceof ChatGptTokenError &&
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import type { ProviderAdapter, ProviderUsageData } from "../types.js";
|
|
2
|
+
export interface OpenCodeProviderOptions {
|
|
3
|
+
compatibilityVersion?: string;
|
|
4
|
+
fetch?: typeof globalThis.fetch;
|
|
5
|
+
}
|
|
2
6
|
export declare function parseOpenCodeGoUsage(raw: unknown): ProviderUsageData | null;
|
|
3
|
-
export declare function openCodeGoProvider(): ProviderAdapter;
|
|
4
|
-
export declare function openCodeZenProvider(): ProviderAdapter;
|
|
7
|
+
export declare function openCodeGoProvider(options?: OpenCodeProviderOptions): ProviderAdapter;
|
|
8
|
+
export declare function openCodeZenProvider(options?: OpenCodeProviderOptions): ProviderAdapter;
|