@cortexkit/opencode-openai-auth 0.11.1 → 1.0.1
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/README.md +15 -26
- package/dist/index-766sfrh5.js +1 -0
- package/dist/index-8kj7sa1p.js +12 -0
- package/dist/index-b3q3j39x.js +2 -0
- package/dist/index-bmd7x5aq.js +2 -0
- package/dist/index-eqh53k8v.js +2 -0
- package/dist/{index-sdt2d2ra.js → index-hy9030jh.js} +1 -1
- package/dist/index-jyr8y8k4.js +7 -0
- package/dist/index-rv08jmd4.js +2 -0
- package/dist/index-ww91hyzr.js +221 -0
- package/dist/index.d.ts +578 -145
- package/dist/index.js +1 -110
- package/dist/pool-lifecycle-DbDmgabd.d.ts +2430 -0
- package/dist/port-file-Do3lQIEk.d.ts +29 -0
- package/dist/protocol-BOBdkqqm.d.ts +198 -0
- package/dist/rpc/port-file.d.ts +4 -17
- package/dist/rpc/port-file.js +1 -1
- package/dist/rpc/protocol.d.ts +2 -2
- package/dist/rpc/protocol.js +1 -0
- package/dist/rpc/rpc-client.d.ts +5 -4
- package/dist/rpc/rpc-client.js +1 -1
- package/dist/rpc/rpc-dir.d.ts +2 -0
- package/dist/rpc/rpc-dir.js +1 -1
- package/dist/sidebar-state-D6cUKgkm.d.ts +385 -0
- package/dist/sidebar-state.d.ts +2 -2
- package/dist/sidebar-state.js +1 -1
- package/dist/token-util-wdza526f.js +1 -0
- package/dist/{token-9823y0pc.js → token-ypk61xj0.js} +1 -1
- package/dist/tui-preferences.js +5 -2
- package/dist/tui.d.ts +9 -4
- package/dist/v2/server.d.ts +54 -0
- package/dist/v2/server.js +4 -0
- package/package.json +21 -9
- package/src/tui/entry.mjs +26 -25
- package/src/tui-compiled/raw/logger.js +24 -0
- package/src/tui-compiled/raw/rpc/protocol.js +3 -0
- package/src/tui-compiled/raw/rpc/rpc-client.js +7 -0
- package/src/tui-compiled/raw/rpc/rpc-dir.js +27 -0
- package/src/tui-compiled/raw/selector.js +21 -0
- package/src/tui-compiled/raw/shared/0332701351b3eeec-format.js +138 -0
- package/src/tui-compiled/raw/shared/04a6357627de5ed0-errors.js +38 -0
- package/src/tui-compiled/raw/shared/053cc17b5419261e-codec.js +12 -0
- package/src/tui-compiled/raw/shared/0766d78c6f481f18-watcher.js +157 -0
- package/src/tui-compiled/raw/shared/08eef45626a94992-reserved.js +13 -0
- package/src/tui-compiled/raw/shared/39c3cb05738fcf6d-projection.js +134 -0
- package/src/tui-compiled/raw/shared/3c2d4169d00f907e-tui-support.js +5 -0
- package/src/tui-compiled/raw/shared/3e7d45a77c6ad4d5-index.js +4 -0
- package/src/tui-compiled/raw/shared/415792e85d80819e-capture-sink.js +13 -0
- package/src/tui-compiled/raw/shared/42ae93ab554fcec2-open-url.js +28 -0
- package/src/tui-compiled/raw/shared/43fc9e6b994519c7-refresh-lock.js +136 -0
- package/src/tui-compiled/raw/shared/458b934e92490331-mutate.js +500 -0
- package/src/tui-compiled/raw/shared/45bf5343bbbf9add-admission.js +142 -0
- package/src/tui-compiled/raw/shared/46f3dff1266160e8-client.js +2 -0
- package/src/tui-compiled/raw/shared/4825e03209d6ae3e-with-lock.js +48 -0
- package/src/tui-compiled/raw/shared/4f36d333d081d040-merge.js +162 -0
- package/src/tui-compiled/raw/shared/54eba94fd64c8c3c-index.js +7 -0
- package/src/tui-compiled/raw/shared/7211cfec06a298bc-atomic-write.js +80 -0
- package/src/tui-compiled/raw/shared/754d6017f8e419cf-builtins.js +534 -0
- package/src/tui-compiled/raw/shared/75bd8c3bd63b315f-identity.js +79 -0
- package/src/tui-compiled/raw/shared/7a91bb630e6f4804-index.js +1 -0
- package/src/tui-compiled/raw/shared/826342a4b6a8b89b-model.js +16 -0
- package/src/tui-compiled/raw/shared/83068c0dbe8a6f13-publication.js +304 -0
- package/src/tui-compiled/raw/shared/836fc615de6afbd9-settings.js +120 -0
- package/src/tui-compiled/raw/shared/83bc3ba9d0fe2761-runtime.js +68 -0
- package/src/tui-compiled/raw/shared/8a18abe4fa2977c8-menu.js +300 -0
- package/src/tui-compiled/raw/shared/901e149c5a0dbbff-sticky.js +339 -0
- package/src/tui-compiled/raw/shared/9020c407ce64f672-ordered.js +58 -0
- package/src/tui-compiled/raw/shared/905bffc0220f43d0-map.js +105 -0
- package/src/tui-compiled/raw/shared/94820e324f2b4c09-seam.js +301 -0
- package/src/tui-compiled/raw/shared/97484d9bb98c705b-provider-state.js +226 -0
- package/src/tui-compiled/raw/shared/9968e3cade6969a1-index.js +2 -0
- package/src/tui-compiled/raw/shared/9b4da614c45f9cd0-lock-constants.js +14 -0
- package/src/tui-compiled/raw/shared/9d3fc9e37456504e-refresh.js +225 -0
- package/src/tui-compiled/raw/shared/9deae3c4da4fd63b-hooks.js +34 -0
- package/src/tui-compiled/raw/shared/a09b6a533c11858f-tui-preferences.js +77 -0
- package/src/tui-compiled/raw/shared/a62a27a204266acd-attribution.js +60 -0
- package/src/tui-compiled/raw/shared/a910277ac43a8626-pull.js +143 -0
- package/src/tui-compiled/raw/shared/b85c5301fddcb98a-paths.js +5 -0
- package/src/tui-compiled/raw/shared/c14988621dc60a75-port-file.js +217 -0
- package/src/tui-compiled/raw/shared/c64d7afbc14ca125-sidebar-file.js +157 -0
- package/src/tui-compiled/raw/shared/c6b176708c446d6d-pins.js +34 -0
- package/src/tui-compiled/raw/shared/c7d252bedee01600-pi.js +206 -0
- package/src/tui-compiled/raw/shared/c852a15cfe489d12-pool.js +120 -0
- package/src/tui-compiled/raw/shared/cc9e0702a752cea7-index.js +3 -0
- package/src/tui-compiled/raw/shared/cd94316a888b0a8e-schema.js +754 -0
- package/src/tui-compiled/raw/shared/d319517e3ea6ad70-redact.js +83 -0
- package/src/tui-compiled/raw/shared/d5a8f1061ee2f114-index.js +5 -0
- package/src/tui-compiled/raw/shared/d669d72c40e9e116-logger.js +19 -0
- package/src/tui-compiled/raw/shared/d7dcba96fea1685c-engine.js +246 -0
- package/src/tui-compiled/raw/shared/dbef697d1df1cd84-index.js +5 -0
- package/src/tui-compiled/raw/shared/e4acc4cb713f300c-protocol.js +3 -0
- package/src/tui-compiled/raw/shared/e8cd4929507508d8-refresh-file-lock.js +501 -0
- package/src/tui-compiled/raw/shared/ec971142548df90e-index.js +6 -0
- package/src/tui-compiled/raw/shared/f37fcefc7aedbf25-error.js +11 -0
- package/src/tui-compiled/raw/shared/fbcdcd0f925e74c0-rpc-client.js +253 -0
- package/src/tui-compiled/raw/shared/fd45e82f2f9c0a0d-torn.js +294 -0
- package/src/tui-compiled/raw/shared/fe02abeadc535f77-rows.js +1258 -0
- package/src/tui-compiled/raw/sidebar-state.js +960 -0
- package/src/tui-compiled/raw/tui/command-dialogs.tsx +424 -0
- package/src/tui-compiled/raw/tui-preferences.js +135 -0
- package/src/tui-compiled/raw/tui.tsx +135 -143
- package/src/tui-compiled/runtime/logger.js +24 -0
- package/src/tui-compiled/runtime/rpc/protocol.js +3 -0
- package/src/tui-compiled/runtime/rpc/rpc-client.js +7 -0
- package/src/tui-compiled/runtime/rpc/rpc-dir.js +27 -0
- package/src/tui-compiled/runtime/selector.js +21 -0
- package/src/tui-compiled/runtime/shared/0332701351b3eeec-format.js +138 -0
- package/src/tui-compiled/runtime/shared/04a6357627de5ed0-errors.js +38 -0
- package/src/tui-compiled/runtime/shared/053cc17b5419261e-codec.js +12 -0
- package/src/tui-compiled/runtime/shared/0766d78c6f481f18-watcher.js +157 -0
- package/src/tui-compiled/runtime/shared/08eef45626a94992-reserved.js +13 -0
- package/src/tui-compiled/runtime/shared/39c3cb05738fcf6d-projection.js +134 -0
- package/src/tui-compiled/runtime/shared/3c2d4169d00f907e-tui-support.js +5 -0
- package/src/tui-compiled/runtime/shared/3e7d45a77c6ad4d5-index.js +4 -0
- package/src/tui-compiled/runtime/shared/415792e85d80819e-capture-sink.js +13 -0
- package/src/tui-compiled/runtime/shared/42ae93ab554fcec2-open-url.js +28 -0
- package/src/tui-compiled/runtime/shared/43fc9e6b994519c7-refresh-lock.js +136 -0
- package/src/tui-compiled/runtime/shared/458b934e92490331-mutate.js +500 -0
- package/src/tui-compiled/runtime/shared/45bf5343bbbf9add-admission.js +142 -0
- package/src/tui-compiled/runtime/shared/46f3dff1266160e8-client.js +2 -0
- package/src/tui-compiled/runtime/shared/4825e03209d6ae3e-with-lock.js +48 -0
- package/src/tui-compiled/runtime/shared/4f36d333d081d040-merge.js +162 -0
- package/src/tui-compiled/runtime/shared/54eba94fd64c8c3c-index.js +7 -0
- package/src/tui-compiled/runtime/shared/7211cfec06a298bc-atomic-write.js +80 -0
- package/src/tui-compiled/runtime/shared/754d6017f8e419cf-builtins.js +534 -0
- package/src/tui-compiled/runtime/shared/75bd8c3bd63b315f-identity.js +79 -0
- package/src/tui-compiled/runtime/shared/7a91bb630e6f4804-index.js +1 -0
- package/src/tui-compiled/runtime/shared/826342a4b6a8b89b-model.js +16 -0
- package/src/tui-compiled/runtime/shared/83068c0dbe8a6f13-publication.js +304 -0
- package/src/tui-compiled/runtime/shared/836fc615de6afbd9-settings.js +120 -0
- package/src/tui-compiled/runtime/shared/83bc3ba9d0fe2761-runtime.js +68 -0
- package/src/tui-compiled/runtime/shared/8a18abe4fa2977c8-menu.js +300 -0
- package/src/tui-compiled/runtime/shared/901e149c5a0dbbff-sticky.js +339 -0
- package/src/tui-compiled/runtime/shared/9020c407ce64f672-ordered.js +58 -0
- package/src/tui-compiled/runtime/shared/905bffc0220f43d0-map.js +105 -0
- package/src/tui-compiled/runtime/shared/94820e324f2b4c09-seam.js +301 -0
- package/src/tui-compiled/runtime/shared/97484d9bb98c705b-provider-state.js +226 -0
- package/src/tui-compiled/runtime/shared/9968e3cade6969a1-index.js +2 -0
- package/src/tui-compiled/runtime/shared/9b4da614c45f9cd0-lock-constants.js +14 -0
- package/src/tui-compiled/runtime/shared/9d3fc9e37456504e-refresh.js +225 -0
- package/src/tui-compiled/runtime/shared/9deae3c4da4fd63b-hooks.js +34 -0
- package/src/tui-compiled/runtime/shared/a09b6a533c11858f-tui-preferences.js +77 -0
- package/src/tui-compiled/runtime/shared/a62a27a204266acd-attribution.js +60 -0
- package/src/tui-compiled/runtime/shared/a910277ac43a8626-pull.js +143 -0
- package/src/tui-compiled/runtime/shared/b85c5301fddcb98a-paths.js +5 -0
- package/src/tui-compiled/runtime/shared/c14988621dc60a75-port-file.js +217 -0
- package/src/tui-compiled/runtime/shared/c64d7afbc14ca125-sidebar-file.js +157 -0
- package/src/tui-compiled/runtime/shared/c6b176708c446d6d-pins.js +34 -0
- package/src/tui-compiled/runtime/shared/c7d252bedee01600-pi.js +206 -0
- package/src/tui-compiled/runtime/shared/c852a15cfe489d12-pool.js +120 -0
- package/src/tui-compiled/runtime/shared/cc9e0702a752cea7-index.js +3 -0
- package/src/tui-compiled/runtime/shared/cd94316a888b0a8e-schema.js +754 -0
- package/src/tui-compiled/runtime/shared/d319517e3ea6ad70-redact.js +83 -0
- package/src/tui-compiled/runtime/shared/d5a8f1061ee2f114-index.js +5 -0
- package/src/tui-compiled/runtime/shared/d669d72c40e9e116-logger.js +19 -0
- package/src/tui-compiled/runtime/shared/d7dcba96fea1685c-engine.js +246 -0
- package/src/tui-compiled/runtime/shared/dbef697d1df1cd84-index.js +5 -0
- package/src/tui-compiled/runtime/shared/e4acc4cb713f300c-protocol.js +3 -0
- package/src/tui-compiled/runtime/shared/e8cd4929507508d8-refresh-file-lock.js +501 -0
- package/src/tui-compiled/runtime/shared/ec971142548df90e-index.js +6 -0
- package/src/tui-compiled/runtime/shared/f37fcefc7aedbf25-error.js +11 -0
- package/src/tui-compiled/runtime/shared/fbcdcd0f925e74c0-rpc-client.js +253 -0
- package/src/tui-compiled/runtime/shared/fd45e82f2f9c0a0d-torn.js +294 -0
- package/src/tui-compiled/runtime/shared/fe02abeadc535f77-rows.js +1258 -0
- package/src/tui-compiled/runtime/sidebar-state.js +960 -0
- package/src/tui-compiled/runtime/tui/command-dialogs.js +378 -0
- package/src/tui-compiled/runtime/tui-preferences.js +135 -0
- package/src/tui-compiled/runtime/{tui.tsx → tui.js} +288 -316
- package/dist/index-4effjmr6.js +0 -2
- package/dist/index-9ehhb051.js +0 -2
- package/dist/index-g3hsdjh4.js +0 -2
- package/dist/index-hh52v8sb.js +0 -2
- package/dist/index-jwn4r3sw.js +0 -1
- package/dist/index-sq36b120.js +0 -211
- package/dist/internal-BTndPmW2.d.ts +0 -914
- package/dist/quota-normalize-08hpp51v.js +0 -1
- package/dist/sidebar-state-CoKYKcLn.d.ts +0 -220
- package/dist/token-util-ypgz7rge.js +0 -1
- package/src/tui-compiled/raw/core-account-paths.ts +0 -114
- package/src/tui-compiled/raw/core-custody-state.ts +0 -243
- package/src/tui-compiled/raw/logger.ts +0 -53
- package/src/tui-compiled/raw/rpc-port-file.ts +0 -177
- package/src/tui-compiled/raw/rpc-protocol.ts +0 -12
- package/src/tui-compiled/raw/rpc-rpc-client.ts +0 -82
- package/src/tui-compiled/raw/rpc-rpc-dir.ts +0 -46
- package/src/tui-compiled/raw/sidebar-state.ts +0 -1659
- package/src/tui-compiled/raw/tui-command-dialogs.tsx +0 -1096
- package/src/tui-compiled/raw/tui-preferences.ts +0 -328
- package/src/tui-compiled/runtime/core-account-paths.ts +0 -114
- package/src/tui-compiled/runtime/core-custody-state.ts +0 -243
- package/src/tui-compiled/runtime/logger.ts +0 -53
- package/src/tui-compiled/runtime/rpc-port-file.ts +0 -177
- package/src/tui-compiled/runtime/rpc-protocol.ts +0 -12
- package/src/tui-compiled/runtime/rpc-rpc-client.ts +0 -82
- package/src/tui-compiled/runtime/rpc-rpc-dir.ts +0 -46
- package/src/tui-compiled/runtime/sidebar-state.ts +0 -1659
- package/src/tui-compiled/runtime/tui-command-dialogs.tsx +0 -855
- package/src/tui-compiled/runtime/tui-preferences.ts +0 -328
- package/src/tui-compiled/shared/internal.ts +0 -6
- package/src/tui-compiled/shared/logger.ts +0 -264
- package/src/tui-compiled/shared/paths.ts +0 -33
- package/src/tui-compiled/shared/refresh-file-lock.ts +0 -384
- package/src/tui-compiled/shared/util/error.ts +0 -9
- package/src/tui-compiled/shared/util/open-url.ts +0 -30
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { recordQuota } from "./a62a27a204266acd-attribution.js";
|
|
2
|
+
import { PoolOperationError } from "./04a6357627de5ed0-errors.js";
|
|
3
|
+
import { toFailure, withTransaction } from "./458b934e92490331-mutate.js";
|
|
4
|
+
import { LockStack } from "./43fc9e6b994519c7-refresh-lock.js";
|
|
5
|
+
import { requireBound } from "./83bc3ba9d0fe2761-runtime.js";
|
|
6
|
+
/**
|
|
7
|
+
* Fires quota pulls without ever making a caller wait for one. A pull first
|
|
8
|
+
* gives a row without a per-row entry its entry at epoch 1 (its own locked
|
|
9
|
+
* config write), then captures the credential and the attribution tuple in
|
|
10
|
+
* one locked read, requests the observation, and records it only if
|
|
11
|
+
* attribution still holds. Failures go to the store's pull failure hook.
|
|
12
|
+
*/
|
|
13
|
+
export class PullScheduler {
|
|
14
|
+
rt;
|
|
15
|
+
hook;
|
|
16
|
+
onFailure;
|
|
17
|
+
firedAtLoad;
|
|
18
|
+
logger;
|
|
19
|
+
inflight = new Set();
|
|
20
|
+
constructor(rt, hook, onFailure,
|
|
21
|
+
/** Rows a load-time pull has fired for in this process. */
|
|
22
|
+
firedAtLoad, logger) {
|
|
23
|
+
this.rt = rt;
|
|
24
|
+
this.hook = hook;
|
|
25
|
+
this.onFailure = onFailure;
|
|
26
|
+
this.firedAtLoad = firedAtLoad;
|
|
27
|
+
this.logger = logger;
|
|
28
|
+
}
|
|
29
|
+
fire(id, reason) {
|
|
30
|
+
if (!this.hook)
|
|
31
|
+
return;
|
|
32
|
+
if (reason === 'load') {
|
|
33
|
+
if (this.firedAtLoad.has(id))
|
|
34
|
+
return;
|
|
35
|
+
this.firedAtLoad.add(id);
|
|
36
|
+
}
|
|
37
|
+
const run = this.run(id, reason);
|
|
38
|
+
this.inflight.add(run);
|
|
39
|
+
void run.finally(() => this.inflight.delete(run));
|
|
40
|
+
}
|
|
41
|
+
/** Resolves once every pull fired so far has settled. */
|
|
42
|
+
async settled() {
|
|
43
|
+
while (this.inflight.size > 0)
|
|
44
|
+
await Promise.allSettled([...this.inflight]);
|
|
45
|
+
}
|
|
46
|
+
async run(id, reason) {
|
|
47
|
+
const rt = this.rt();
|
|
48
|
+
const { ctx } = rt;
|
|
49
|
+
const locks = new LockStack(ctx.lockDefaults, ctx.lockEnv);
|
|
50
|
+
const progress = { writes: 0 };
|
|
51
|
+
try {
|
|
52
|
+
const request = await withTransaction(ctx, locks, progress, { operation: 'pull', rowId: id }, async (tx) => {
|
|
53
|
+
// Even a skipped capture must not repair another row on a reserved
|
|
54
|
+
// row's behalf. Decide reservation before pool-wide torn repair.
|
|
55
|
+
if (tx.reservation(id) !== undefined)
|
|
56
|
+
return undefined;
|
|
57
|
+
await tx.completeTorn();
|
|
58
|
+
const row = tx.row(id);
|
|
59
|
+
// A row that would pull but for its unbound credential refuses, so
|
|
60
|
+
// the failure hook hears of it instead of the pull vanishing.
|
|
61
|
+
if (row?.unbound &&
|
|
62
|
+
row.enabled &&
|
|
63
|
+
row.type === 'oauth' &&
|
|
64
|
+
row.credential &&
|
|
65
|
+
!row.invalid)
|
|
66
|
+
requireBound('pull', row);
|
|
67
|
+
// Disabled, API-key and credential-less rows never pull.
|
|
68
|
+
if (!row?.candidate || row.type !== 'oauth')
|
|
69
|
+
return undefined;
|
|
70
|
+
if (!row.hasEntry) {
|
|
71
|
+
tx.setEntry(id, { credentialEpoch: 1, needsFirstReading: true });
|
|
72
|
+
await tx.commitConfig();
|
|
73
|
+
}
|
|
74
|
+
const current = tx.row(id);
|
|
75
|
+
if (!current?.credential || current.credentialEpoch === undefined)
|
|
76
|
+
return undefined;
|
|
77
|
+
return {
|
|
78
|
+
id,
|
|
79
|
+
credential: current.credential,
|
|
80
|
+
credentialEpoch: current.credentialEpoch,
|
|
81
|
+
...(current.identity !== undefined
|
|
82
|
+
? { identity: current.identity }
|
|
83
|
+
: {}),
|
|
84
|
+
reason,
|
|
85
|
+
};
|
|
86
|
+
}, { completeTorn: false });
|
|
87
|
+
await locks.releaseAll();
|
|
88
|
+
if (!request) {
|
|
89
|
+
this.firedAtLoad.delete(id);
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
await ctx.hold?.('pull-before-request', id);
|
|
93
|
+
let observation;
|
|
94
|
+
try {
|
|
95
|
+
observation = await this.hook?.(request);
|
|
96
|
+
}
|
|
97
|
+
catch (cause) {
|
|
98
|
+
throw new PoolOperationError({
|
|
99
|
+
operation: 'pull',
|
|
100
|
+
rowId: id,
|
|
101
|
+
phase: 'pull',
|
|
102
|
+
retryable: true,
|
|
103
|
+
kind: 'pull',
|
|
104
|
+
message: 'the quota pull rejected',
|
|
105
|
+
cause,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
await recordQuota(rt, id, {
|
|
109
|
+
credentialEpoch: request.credentialEpoch,
|
|
110
|
+
...(request.identity !== undefined
|
|
111
|
+
? { identity: request.identity }
|
|
112
|
+
: {}),
|
|
113
|
+
}, observation);
|
|
114
|
+
}
|
|
115
|
+
catch (error) {
|
|
116
|
+
// Needs-first-reading stays set; a later load may fire again.
|
|
117
|
+
this.firedAtLoad.delete(id);
|
|
118
|
+
const failure = toFailure(error, 'pull', id, progress);
|
|
119
|
+
if (this.onFailure) {
|
|
120
|
+
try {
|
|
121
|
+
await this.onFailure(id, failure);
|
|
122
|
+
}
|
|
123
|
+
catch (hookError) {
|
|
124
|
+
this.logger?.warn('pull failure hook threw', {
|
|
125
|
+
rowId: id,
|
|
126
|
+
error: hookError instanceof Error
|
|
127
|
+
? hookError.message
|
|
128
|
+
: String(hookError),
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
this.logger?.warn('quota pull failed', {
|
|
134
|
+
rowId: id,
|
|
135
|
+
kind: failure.kind,
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
finally {
|
|
140
|
+
await locks.releaseAll();
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { basename, dirname, join } from "node:path";
|
|
2
|
+
export const ACCOUNT_FILE_NAME = "openai-auth.json", ACCOUNT_STATE_FILE_NAME = "openai-auth-state.json";
|
|
3
|
+
export function deriveStatePath(configPath) {
|
|
4
|
+
return basename(configPath) === ACCOUNT_FILE_NAME ? join(dirname(configPath), ACCOUNT_STATE_FILE_NAME) : `${configPath}.state.json`;
|
|
5
|
+
}
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
+
import { chmod, mkdir, open, readdir, readFile, rename, rmdir, stat, unlink, } from "node:fs/promises";
|
|
3
|
+
import { join, resolve } from "node:path";
|
|
4
|
+
/**
|
|
5
|
+
* A PID a liveness probe may address. `process.kill(0 | negative, 0)` signals
|
|
6
|
+
* a process group rather than a process, and an unsafe integer cannot name a
|
|
7
|
+
* real process, so neither is ever probed.
|
|
8
|
+
*/
|
|
9
|
+
function isProbeablePid(pid) {
|
|
10
|
+
return typeof pid === 'number' && Number.isSafeInteger(pid) && pid > 0;
|
|
11
|
+
}
|
|
12
|
+
function pidAlive(pid) {
|
|
13
|
+
if (!isProbeablePid(pid))
|
|
14
|
+
return false;
|
|
15
|
+
try {
|
|
16
|
+
process.kill(pid, 0);
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
catch (error) {
|
|
20
|
+
return error.code === 'EPERM';
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
export function createManagedRpcStateDirPredicate(directoryPrefix) {
|
|
24
|
+
return (name) => isManagedRpcStateDir(name, directoryPrefix);
|
|
25
|
+
}
|
|
26
|
+
export function isManagedRpcStateDir(name, directoryPrefix) {
|
|
27
|
+
if (/^[0-9a-f]{16}$/.test(name))
|
|
28
|
+
return true;
|
|
29
|
+
return (name.startsWith(directoryPrefix) &&
|
|
30
|
+
/^[0-9a-f]{16}$/.test(name.slice(directoryPrefix.length)));
|
|
31
|
+
}
|
|
32
|
+
export function getRpcDir(rpcRoot, directoryPrefix, projectDirectory) {
|
|
33
|
+
return join(rpcRoot, directoryPrefix +
|
|
34
|
+
createHash('sha256').update(projectDirectory).digest('hex').slice(0, 16));
|
|
35
|
+
}
|
|
36
|
+
/** The PID a port file's name claims, or undefined for a malformed name. */
|
|
37
|
+
function filenamePid(name) {
|
|
38
|
+
const match = /^port-(\d+)\.json$/.exec(name);
|
|
39
|
+
return match ? Number(match[1]) : undefined;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* An entry a client may connect to: its PID is probeable and matches the PID
|
|
43
|
+
* in its file name (a mismatch means the file was not written by the server
|
|
44
|
+
* it names), its port is a real TCP port, and its token is non-empty (an
|
|
45
|
+
* absent token would otherwise be sent as `Bearer undefined`).
|
|
46
|
+
*/
|
|
47
|
+
function isUsablePortFileEntry(value, name) {
|
|
48
|
+
if (value === null || typeof value !== 'object' || Array.isArray(value))
|
|
49
|
+
return false;
|
|
50
|
+
const { pid, port, token } = value;
|
|
51
|
+
return (isProbeablePid(pid) &&
|
|
52
|
+
filenamePid(name) === pid &&
|
|
53
|
+
typeof port === 'number' &&
|
|
54
|
+
Number.isInteger(port) &&
|
|
55
|
+
port >= 1 &&
|
|
56
|
+
port <= 65_535 &&
|
|
57
|
+
typeof token === 'string' &&
|
|
58
|
+
token.length > 0);
|
|
59
|
+
}
|
|
60
|
+
async function removeCorruptPortFile(portFile, log) {
|
|
61
|
+
log?.debug('rpc corrupt port file', { pid: process.pid, portFile });
|
|
62
|
+
await unlink(portFile).catch(() => { });
|
|
63
|
+
}
|
|
64
|
+
export async function writePortFile(dir, entry, options = {}) {
|
|
65
|
+
// The directory can be removed by another project's sweep between our
|
|
66
|
+
// mkdir and the first open/rename, so the whole create-then-rename
|
|
67
|
+
// unit is retried once on ENOENT. The retry recreates the directory; a
|
|
68
|
+
// persistent ENOENT (e.g. permission, read-only parent) will surface on
|
|
69
|
+
// the second attempt — failing fast beats an unbounded loop.
|
|
70
|
+
const writeOnce = async () => {
|
|
71
|
+
await mkdir(dir, {
|
|
72
|
+
recursive: true,
|
|
73
|
+
mode: options.secureDir ? 0o700 : undefined,
|
|
74
|
+
});
|
|
75
|
+
if (options.secureDir)
|
|
76
|
+
await chmod(dir, 0o700);
|
|
77
|
+
await options.beforeWrite?.();
|
|
78
|
+
const full = { ...entry, startedAt: Date.now() };
|
|
79
|
+
const target = join(dir, `port-${entry.pid}.json`);
|
|
80
|
+
const tmp = `${target}.${(options.stageName ?? randomUUID)()}.tmp`;
|
|
81
|
+
let handle;
|
|
82
|
+
let created = false;
|
|
83
|
+
try {
|
|
84
|
+
handle = await open(tmp, 'wx', 0o600);
|
|
85
|
+
created = true;
|
|
86
|
+
await handle.writeFile(JSON.stringify(full), 'utf8');
|
|
87
|
+
await handle.chmod(0o600);
|
|
88
|
+
await handle.close();
|
|
89
|
+
handle = undefined;
|
|
90
|
+
await rename(tmp, target);
|
|
91
|
+
created = false;
|
|
92
|
+
}
|
|
93
|
+
finally {
|
|
94
|
+
await handle?.close().catch(() => { });
|
|
95
|
+
// Remove token-bearing staging bytes only if this call created them.
|
|
96
|
+
if (created)
|
|
97
|
+
await unlink(tmp).catch(() => { });
|
|
98
|
+
}
|
|
99
|
+
return target;
|
|
100
|
+
};
|
|
101
|
+
try {
|
|
102
|
+
return await writeOnce();
|
|
103
|
+
}
|
|
104
|
+
catch (error) {
|
|
105
|
+
if (error.code === 'ENOENT') {
|
|
106
|
+
return await writeOnce();
|
|
107
|
+
}
|
|
108
|
+
throw error;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
export async function sweepRpcState(root, activeDir, isManagedDir, log) {
|
|
112
|
+
let projectDirs;
|
|
113
|
+
try {
|
|
114
|
+
projectDirs = await readdir(root, { withFileTypes: true });
|
|
115
|
+
}
|
|
116
|
+
catch (error) {
|
|
117
|
+
if (error.code === 'ENOENT')
|
|
118
|
+
return;
|
|
119
|
+
throw error;
|
|
120
|
+
}
|
|
121
|
+
const active = resolve(activeDir);
|
|
122
|
+
for (const projectDir of projectDirs) {
|
|
123
|
+
if (!projectDir.isDirectory() || !isManagedDir(projectDir.name)) {
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
const dir = join(root, projectDir.name);
|
|
127
|
+
let names;
|
|
128
|
+
try {
|
|
129
|
+
names = await readdir(dir);
|
|
130
|
+
}
|
|
131
|
+
catch (error) {
|
|
132
|
+
if (error.code === 'ENOENT')
|
|
133
|
+
continue;
|
|
134
|
+
throw error;
|
|
135
|
+
}
|
|
136
|
+
for (const name of names) {
|
|
137
|
+
if (!name.startsWith('port-') || !name.endsWith('.json'))
|
|
138
|
+
continue;
|
|
139
|
+
const portFile = join(dir, name);
|
|
140
|
+
let raw;
|
|
141
|
+
try {
|
|
142
|
+
raw = await readFile(portFile, 'utf8');
|
|
143
|
+
}
|
|
144
|
+
catch {
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
if (raw === undefined)
|
|
148
|
+
continue;
|
|
149
|
+
let parsed;
|
|
150
|
+
try {
|
|
151
|
+
parsed = JSON.parse(raw);
|
|
152
|
+
}
|
|
153
|
+
catch {
|
|
154
|
+
await removeCorruptPortFile(portFile, log);
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
if (!isUsablePortFileEntry(parsed, name)) {
|
|
158
|
+
await removeCorruptPortFile(portFile, log);
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
const entry = parsed;
|
|
162
|
+
if (!pidAlive(entry.pid))
|
|
163
|
+
await unlink(portFile).catch(() => { });
|
|
164
|
+
}
|
|
165
|
+
if (resolve(dir) !== active)
|
|
166
|
+
await rmdir(dir).catch(() => { });
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
/** Internal cache validation: probe only the selected file and its PID. */
|
|
170
|
+
export async function portFileIdentity(dir, entry) {
|
|
171
|
+
if (!pidAlive(entry.pid))
|
|
172
|
+
return null;
|
|
173
|
+
const path = join(resolve(dir), `port-${entry.pid}.json`);
|
|
174
|
+
try {
|
|
175
|
+
const info = await stat(path);
|
|
176
|
+
return `${path}:${info.dev}:${info.ino}:${info.mtimeMs}:${info.size}`;
|
|
177
|
+
}
|
|
178
|
+
catch {
|
|
179
|
+
return null;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
export async function discoverPortFile(dir, expectedPid, options = {}) {
|
|
183
|
+
let names;
|
|
184
|
+
try {
|
|
185
|
+
names = await readdir(dir);
|
|
186
|
+
}
|
|
187
|
+
catch {
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
const live = [];
|
|
191
|
+
for (const name of names) {
|
|
192
|
+
if (!name.startsWith('port-') || !name.endsWith('.json'))
|
|
193
|
+
continue;
|
|
194
|
+
try {
|
|
195
|
+
const parsed = JSON.parse(await readFile(join(dir, name), 'utf8'));
|
|
196
|
+
if (isUsablePortFileEntry(parsed, name)) {
|
|
197
|
+
if (pidAlive(parsed.pid))
|
|
198
|
+
live.push(parsed);
|
|
199
|
+
else
|
|
200
|
+
await unlink(join(dir, name)).catch(() => { });
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
catch { }
|
|
204
|
+
}
|
|
205
|
+
if (live.length === 0)
|
|
206
|
+
return null;
|
|
207
|
+
const candidates = expectedPid !== undefined && expectedPid >= 1
|
|
208
|
+
? live.filter((entry) => entry.pid === expectedPid)
|
|
209
|
+
: [];
|
|
210
|
+
if (options.exactPid === true && candidates.length === 0)
|
|
211
|
+
return null;
|
|
212
|
+
const entries = candidates.length > 0 ? candidates : live;
|
|
213
|
+
const sortTime = (entry) => typeof entry.startedAt === 'number' && Number.isFinite(entry.startedAt)
|
|
214
|
+
? entry.startedAt
|
|
215
|
+
: -Infinity;
|
|
216
|
+
return entries.sort((a, b) => sortTime(b) - sortTime(a))[0] ?? null;
|
|
217
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { chmod, mkdir, readFile } from "node:fs/promises";
|
|
2
|
+
import { dirname } from "node:path";
|
|
3
|
+
import { LockContentionError, LockOwnershipError, WRITER_LOCK_CONSTANTS, withLock, writeJsonAtomic, } from "./ec971142548df90e-index.js";
|
|
4
|
+
/** Binds generic persistence to a caller's path and normalization policy. */
|
|
5
|
+
export function createSidebarFile(options) {
|
|
6
|
+
const { path, defaultValue, normalize } = options;
|
|
7
|
+
let chain = Promise.resolve();
|
|
8
|
+
const parse = (raw) => {
|
|
9
|
+
if (raw === '')
|
|
10
|
+
return defaultValue;
|
|
11
|
+
try {
|
|
12
|
+
return normalize(JSON.parse(raw));
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return defaultValue;
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
const readRaw = async () => {
|
|
19
|
+
try {
|
|
20
|
+
return await readFile(path, 'utf8');
|
|
21
|
+
}
|
|
22
|
+
catch (error) {
|
|
23
|
+
if (error.code === 'ENOENT')
|
|
24
|
+
return '';
|
|
25
|
+
throw error;
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
const read = async () => {
|
|
29
|
+
try {
|
|
30
|
+
return parse(await readRaw());
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return defaultValue;
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
const enqueue = (operation, writeOptions) => {
|
|
37
|
+
const result = chain
|
|
38
|
+
.then(operation)
|
|
39
|
+
.then((outcome) => writeOptions?.onResult?.(outcome));
|
|
40
|
+
// Keep the next operation runnable without hiding this caller's rejection.
|
|
41
|
+
chain = result.catch(() => { });
|
|
42
|
+
return result;
|
|
43
|
+
};
|
|
44
|
+
const lockOptions = {
|
|
45
|
+
...WRITER_LOCK_CONSTANTS.sidebar,
|
|
46
|
+
name: options.lockName ?? WRITER_LOCK_CONSTANTS.sidebar.name,
|
|
47
|
+
timeoutMs: options.timeoutMs ?? WRITER_LOCK_CONSTANTS.sidebar.timeoutMs,
|
|
48
|
+
};
|
|
49
|
+
/**
|
|
50
|
+
* Rename `value` into place under `lock`, then report whether the lock was
|
|
51
|
+
* still held. The fence before the rename stops a write whose lock is
|
|
52
|
+
* already gone; the check after it catches a lock lost while the rename
|
|
53
|
+
* itself was in flight, which no check before it can see.
|
|
54
|
+
*/
|
|
55
|
+
const commitUnder = async (lock, value, hooks) => {
|
|
56
|
+
await writeJsonAtomic(path, value, {
|
|
57
|
+
serialize: JSON.stringify,
|
|
58
|
+
beforeRename: async () => {
|
|
59
|
+
await hooks?.beforeCommit?.();
|
|
60
|
+
await lock.assertOwned();
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
await hooks?.afterRename?.();
|
|
64
|
+
try {
|
|
65
|
+
await lock.assertOwned();
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
if (error instanceof LockOwnershipError)
|
|
70
|
+
return false;
|
|
71
|
+
throw error;
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
/**
|
|
75
|
+
* One repair, under a new lock, of a write that lost its lock during the
|
|
76
|
+
* rename. Bounded to a single attempt: if the repair loses its lock too,
|
|
77
|
+
* the newer holder is writing and its state stands.
|
|
78
|
+
*/
|
|
79
|
+
const repairLostWrite = async (repair, written, hooks) => {
|
|
80
|
+
options.logger?.warn('sidebar lock lost after rename; repairing write', {
|
|
81
|
+
path,
|
|
82
|
+
});
|
|
83
|
+
try {
|
|
84
|
+
const outcome = await withLock(path, lockOptions, async (lock) => {
|
|
85
|
+
const next = repair(await read(), written);
|
|
86
|
+
if (next === undefined)
|
|
87
|
+
return 'skipped';
|
|
88
|
+
return (await commitUnder(lock, next, hooks))
|
|
89
|
+
? 'written'
|
|
90
|
+
: 'lost-again';
|
|
91
|
+
});
|
|
92
|
+
if (outcome === 'lost-again') {
|
|
93
|
+
options.logger?.warn('sidebar repair lost its lock after rename', {
|
|
94
|
+
path,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
return { status: 'lost-after-rename', repair: outcome };
|
|
98
|
+
}
|
|
99
|
+
catch (error) {
|
|
100
|
+
if (!(error instanceof LockContentionError))
|
|
101
|
+
throw error;
|
|
102
|
+
options.logger?.warn('sidebar repair lock unavailable; repair skipped', {
|
|
103
|
+
path,
|
|
104
|
+
});
|
|
105
|
+
return { status: 'lost-after-rename', repair: 'lock-unavailable' };
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
const persist = async (merge, writeOptions) => {
|
|
109
|
+
const hooks = writeOptions;
|
|
110
|
+
const parent = dirname(path);
|
|
111
|
+
const secureDir = options.secureDir ?? true;
|
|
112
|
+
await mkdir(parent, { recursive: true, mode: 0o700 });
|
|
113
|
+
if (secureDir) {
|
|
114
|
+
await chmod(parent, 0o700).catch((error) => {
|
|
115
|
+
options.logger?.warn('sidebar directory permission remediation failed', { error: error instanceof Error ? error.message : String(error) });
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
const committed = await withLock(path, lockOptions, async (lock) => {
|
|
119
|
+
const commit = async (value) => ({
|
|
120
|
+
value,
|
|
121
|
+
held: await commitUnder(lock, value, hooks),
|
|
122
|
+
});
|
|
123
|
+
// Older processes may ignore the lock; remerge if their bytes changed.
|
|
124
|
+
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
125
|
+
const raw = await readRaw();
|
|
126
|
+
const next = merge(parse(raw));
|
|
127
|
+
if (next === undefined)
|
|
128
|
+
return undefined;
|
|
129
|
+
if (attempt === 0)
|
|
130
|
+
await hooks?.beforeRecheck?.();
|
|
131
|
+
if ((await readRaw()) !== raw)
|
|
132
|
+
continue;
|
|
133
|
+
return await commit(next);
|
|
134
|
+
}
|
|
135
|
+
const next = merge(await read());
|
|
136
|
+
return next === undefined ? undefined : await commit(next);
|
|
137
|
+
});
|
|
138
|
+
if (committed === undefined)
|
|
139
|
+
return { status: 'skipped' };
|
|
140
|
+
if (committed.held)
|
|
141
|
+
return { status: 'written' };
|
|
142
|
+
// The first lock is released by now; the repair takes its own.
|
|
143
|
+
const repair = writeOptions?.repair ?? options.repair;
|
|
144
|
+
if (!repair) {
|
|
145
|
+
options.logger?.warn('sidebar lock lost after rename; write not repaired', {
|
|
146
|
+
path,
|
|
147
|
+
});
|
|
148
|
+
return { status: 'lost-after-rename', repair: 'none' };
|
|
149
|
+
}
|
|
150
|
+
return await repairLostWrite(repair, committed.value, hooks);
|
|
151
|
+
};
|
|
152
|
+
return {
|
|
153
|
+
read,
|
|
154
|
+
write: (value, writeOptions) => enqueue(() => persist(() => value, writeOptions), writeOptions),
|
|
155
|
+
update: (merge, writeOptions) => enqueue(() => persist(merge, writeOptions), writeOptions),
|
|
156
|
+
};
|
|
157
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// Session pins for `sticky-balanced` routing.
|
|
2
|
+
//
|
|
3
|
+
// A pin records which row a session was placed on so its prompt cache stays
|
|
4
|
+
// warm. The library persists no pin: the caller holds pins (in memory, or in
|
|
5
|
+
// a cross-process file it owns) and passes the relevant ones in per call.
|
|
6
|
+
/**
|
|
7
|
+
* A pin survives while its row id is in `validIds` and the row still holds
|
|
8
|
+
* the account it was placed on. Only two known, differing identities prove
|
|
9
|
+
* the account changed: an unknown identity on either side keeps the pin,
|
|
10
|
+
* because re-placing a session on missing evidence throws its cache away.
|
|
11
|
+
*/
|
|
12
|
+
export function isPinValid(pin, validIds, currentIdentity) {
|
|
13
|
+
if (!validIds.has(pin.accountId))
|
|
14
|
+
return false;
|
|
15
|
+
return !(typeof pin.wireIdentity === 'string' &&
|
|
16
|
+
typeof currentIdentity === 'string' &&
|
|
17
|
+
pin.wireIdentity !== currentIdentity);
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Sums the request bytes of every other session's pin per row, as openai-auth
|
|
21
|
+
* does. A pin counts only while it was judged on the row's current
|
|
22
|
+
* projection time, so a fresh reading resets the row's pending load.
|
|
23
|
+
*/
|
|
24
|
+
export function pendingBytesForPins(pins, quotaCheckedAtById, excludedSessionKey) {
|
|
25
|
+
const pending = new Map();
|
|
26
|
+
for (const [sessionKey, pin] of pins) {
|
|
27
|
+
if (sessionKey === excludedSessionKey)
|
|
28
|
+
continue;
|
|
29
|
+
if (pin.quotaCheckedAt !== quotaCheckedAtById.get(pin.accountId))
|
|
30
|
+
continue;
|
|
31
|
+
pending.set(pin.accountId, (pending.get(pin.accountId) ?? 0) + (pin.inputBytes ?? 0));
|
|
32
|
+
}
|
|
33
|
+
return pending;
|
|
34
|
+
}
|