@ashx-j/lunr 0.1.4 → 0.1.6
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/dist/builtin-extensions/index.d.ts +13 -4
- package/dist/builtin-extensions/index.d.ts.map +1 -1
- package/dist/builtin-extensions/index.js +30 -21
- package/dist/builtin-extensions/index.js.map +1 -1
- package/dist/builtin-extensions/lunr-local-providers/index.d.ts.map +1 -1
- package/dist/builtin-extensions/lunr-local-providers/index.js +25 -3
- package/dist/builtin-extensions/lunr-local-providers/index.js.map +1 -1
- package/dist/builtin-extensions/lunr-local-providers/local-servers.d.ts.map +1 -1
- package/dist/builtin-extensions/lunr-local-providers/local-servers.js +4 -2
- package/dist/builtin-extensions/lunr-local-providers/local-servers.js.map +1 -1
- package/dist/builtin-extensions/pi-ollama-cloud/index.d.ts +2 -3
- package/dist/builtin-extensions/pi-ollama-cloud/index.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-ollama-cloud/index.js +4 -25
- package/dist/builtin-extensions/pi-ollama-cloud/index.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/async-execution.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/async-execution.js +3 -0
- package/dist/builtin-extensions/pi-subagents/src/runs/background/async-execution.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/subagent-runner.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/subagent-runner.js +4 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/subagent-runner.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/foreground/execution.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/foreground/execution.js +4 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/foreground/execution.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/foreground/subagent-executor.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/foreground/subagent-executor.js +20 -0
- package/dist/builtin-extensions/pi-subagents/src/runs/foreground/subagent-executor.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/dynamic-fanout.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/dynamic-fanout.js +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/dynamic-fanout.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/parallel-utils.d.ts +2 -0
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/parallel-utils.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/parallel-utils.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/pi-args.d.ts +2 -0
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/pi-args.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/pi-args.js +2 -0
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/pi-args.js.map +1 -1
- package/dist/catalog/models.json +1 -1
- package/dist/catalog/providers/amazon-bedrock.json +1 -1
- package/dist/catalog/providers/cloudflare-workers-ai.json +1 -1
- package/dist/catalog/providers/huggingface.json +1 -1
- package/dist/catalog/providers/opencode-go.json +1 -1
- package/dist/catalog/providers/openrouter.json +1 -1
- package/dist/catalog/providers/together.json +1 -1
- package/dist/catalog/providers/vercel-ai-gateway.json +1 -1
- package/dist/catalog/publication.json +3 -3
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +19 -4
- package/dist/cli.js.map +1 -1
- package/dist/core/agent-session-services.d.ts.map +1 -1
- package/dist/core/agent-session-services.js +7 -0
- package/dist/core/agent-session-services.js.map +1 -1
- package/dist/core/agent-session.d.ts +9 -0
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +74 -0
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/auth-storage.d.ts.map +1 -1
- package/dist/core/auth-storage.js +39 -7
- package/dist/core/auth-storage.js.map +1 -1
- package/dist/core/catalog-merge.d.ts.map +1 -1
- package/dist/core/catalog-merge.js +4 -1
- package/dist/core/catalog-merge.js.map +1 -1
- package/dist/core/extensions/index.d.ts +1 -1
- package/dist/core/extensions/index.d.ts.map +1 -1
- package/dist/core/extensions/index.js.map +1 -1
- package/dist/core/extensions/runner.d.ts +2 -0
- package/dist/core/extensions/runner.d.ts.map +1 -1
- package/dist/core/extensions/runner.js +12 -0
- package/dist/core/extensions/runner.js.map +1 -1
- package/dist/core/extensions/types.d.ts +2 -1
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/grok-cli-auth.d.ts +13 -3
- package/dist/core/grok-cli-auth.d.ts.map +1 -1
- package/dist/core/grok-cli-auth.js +130 -21
- package/dist/core/grok-cli-auth.js.map +1 -1
- package/dist/core/model-runtime.d.ts +9 -1
- package/dist/core/model-runtime.d.ts.map +1 -1
- package/dist/core/model-runtime.js +40 -12
- package/dist/core/model-runtime.js.map +1 -1
- package/dist/core/official-catalog.d.ts +8 -1
- package/dist/core/official-catalog.d.ts.map +1 -1
- package/dist/core/official-catalog.js +38 -11
- package/dist/core/official-catalog.js.map +1 -1
- package/dist/core/package-manager.d.ts.map +1 -1
- package/dist/core/package-manager.js +28 -4
- package/dist/core/package-manager.js.map +1 -1
- package/dist/core/plan-mode.d.ts +2 -0
- package/dist/core/plan-mode.d.ts.map +1 -1
- package/dist/core/plan-mode.js +7 -0
- package/dist/core/plan-mode.js.map +1 -1
- package/dist/core/resource-loader.d.ts +2 -0
- package/dist/core/resource-loader.d.ts.map +1 -1
- package/dist/core/resource-loader.js +27 -3
- package/dist/core/resource-loader.js.map +1 -1
- package/dist/core/subagent-permission-inherit.d.ts +38 -0
- package/dist/core/subagent-permission-inherit.d.ts.map +1 -0
- package/dist/core/subagent-permission-inherit.js +110 -0
- package/dist/core/subagent-permission-inherit.js.map +1 -0
- package/dist/core/usage-service.d.ts.map +1 -1
- package/dist/core/usage-service.js +5 -5
- package/dist/core/usage-service.js.map +1 -1
- package/dist/gateway/agent-bridge.d.ts.map +1 -1
- package/dist/gateway/agent-bridge.js +2 -2
- package/dist/gateway/agent-bridge.js.map +1 -1
- package/dist/gateway/command.d.ts.map +1 -1
- package/dist/gateway/command.js +2 -1
- package/dist/gateway/command.js.map +1 -1
- package/dist/gateway/cron.d.ts.map +1 -1
- package/dist/gateway/cron.js +2 -2
- package/dist/gateway/cron.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +25 -4
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +7 -0
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +35 -8
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/gondolin/package-lock.json +2 -2
- package/examples/extensions/gondolin/package.json +1 -1
- package/examples/extensions/sandbox/package-lock.json +2 -2
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/package.json +4 -4
|
@@ -2,17 +2,18 @@
|
|
|
2
2
|
* Official Grok CLI SuperGrok session (`~/.grok/auth.json`).
|
|
3
3
|
*
|
|
4
4
|
* lunR and Grok CLI share xAI client `b1a00492-073a-47ea-816f-4c329264a828`.
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* CLIs do not fight.
|
|
5
|
+
* xAI rotates refresh tokens and revokes the family on reuse, so this module
|
|
6
|
+
* must never write a rotated-out refresh back over a newer one.
|
|
8
7
|
*/
|
|
9
|
-
import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
8
|
+
import { chmodSync, existsSync, mkdirSync, readFileSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
|
|
10
9
|
import { homedir } from "node:os";
|
|
11
10
|
import { dirname, join } from "node:path";
|
|
12
11
|
import lockfile from "proper-lockfile";
|
|
13
12
|
export const XAI_PROVIDER_ID = "xai";
|
|
14
13
|
/** Same client as packages/ai/src/auth/oauth/xai.ts `XAI_CLIENT_ID`. */
|
|
15
14
|
export const XAI_GROK_CLI_CLIENT_ID = "b1a00492-073a-47ea-816f-4c329264a828";
|
|
15
|
+
/** Matches packages/ai/src/auth/oauth/xai.ts `REFRESH_SKEW_MS`. */
|
|
16
|
+
const REFRESH_SKEW_MS = 5 * 60 * 1000;
|
|
16
17
|
const AUTH_FILE_WRITE_OPTIONS = { encoding: "utf-8", mode: 0o600 };
|
|
17
18
|
export function grokHomeDir(grokHome) {
|
|
18
19
|
if (grokHome)
|
|
@@ -43,6 +44,10 @@ export function parseGrokExpiresAt(value) {
|
|
|
43
44
|
}
|
|
44
45
|
return undefined;
|
|
45
46
|
}
|
|
47
|
+
/** Grok CLI deserializes `expires_at` as RFC3339, not epoch ms. */
|
|
48
|
+
export function formatGrokExpiresAt(expires) {
|
|
49
|
+
return new Date(expires).toISOString();
|
|
50
|
+
}
|
|
46
51
|
export function grokEntryToOAuth(entry) {
|
|
47
52
|
if (!entry || typeof entry !== "object" || Array.isArray(entry))
|
|
48
53
|
return undefined;
|
|
@@ -69,12 +74,34 @@ export function readGrokCliXaiOAuth(options = {}) {
|
|
|
69
74
|
export function hasGrokCliXaiAuth(options = {}) {
|
|
70
75
|
return readGrokCliXaiOAuth(options) !== undefined;
|
|
71
76
|
}
|
|
77
|
+
/**
|
|
78
|
+
* Pick the live SuperGrok session.
|
|
79
|
+
*
|
|
80
|
+
* lunR stores `expires` 5 minutes early. Grok CLI stores the server expiry.
|
|
81
|
+
* A raw `>=` compare therefore treats a just-rotated lunR token as older than
|
|
82
|
+
* the still-unrotated Grok file and writes the dead refresh back. When both
|
|
83
|
+
* look live and the refresh tokens differ, keep lunR; `modify` retries Grok
|
|
84
|
+
* only after `invalid_grant`.
|
|
85
|
+
*/
|
|
72
86
|
export function chooseXaiOAuth(lunr, grok) {
|
|
73
87
|
if (lunr?.type === "api_key")
|
|
74
88
|
return lunr;
|
|
75
89
|
const lunrOAuth = lunr?.type === "oauth" ? lunr : undefined;
|
|
76
|
-
if (lunrOAuth && grok)
|
|
77
|
-
|
|
90
|
+
if (lunrOAuth && grok) {
|
|
91
|
+
if (lunrOAuth.refresh === grok.refresh) {
|
|
92
|
+
return grok.expires >= lunrOAuth.expires ? grok : lunrOAuth;
|
|
93
|
+
}
|
|
94
|
+
const now = Date.now();
|
|
95
|
+
const lunrLive = now < lunrOAuth.expires;
|
|
96
|
+
const grokLive = now < grok.expires;
|
|
97
|
+
if (grokLive && !lunrLive)
|
|
98
|
+
return grok;
|
|
99
|
+
if (lunrLive && !grokLive)
|
|
100
|
+
return lunrOAuth;
|
|
101
|
+
if (!lunrLive && !grokLive)
|
|
102
|
+
return grok.expires >= lunrOAuth.expires ? grok : lunrOAuth;
|
|
103
|
+
return lunrOAuth;
|
|
104
|
+
}
|
|
78
105
|
return grok ?? lunrOAuth ?? lunr;
|
|
79
106
|
}
|
|
80
107
|
function oauthRefreshFailed(error) {
|
|
@@ -90,7 +117,10 @@ function oauthRefreshFailed(error) {
|
|
|
90
117
|
}
|
|
91
118
|
break;
|
|
92
119
|
}
|
|
93
|
-
|
|
120
|
+
const chain = parts.join(" ");
|
|
121
|
+
if (/timed out|aborted|cancelled|ECONN|ENOTFOUND|network/i.test(chain))
|
|
122
|
+
return false;
|
|
123
|
+
return /invalid_grant|refresh token revoked/i.test(chain);
|
|
94
124
|
}
|
|
95
125
|
function shouldAdopt(lunr, chosen) {
|
|
96
126
|
if (!chosen || chosen.type !== "oauth")
|
|
@@ -99,6 +129,62 @@ function shouldAdopt(lunr, chosen) {
|
|
|
99
129
|
return true;
|
|
100
130
|
return lunr.access !== chosen.access || lunr.refresh !== chosen.refresh;
|
|
101
131
|
}
|
|
132
|
+
function writeAuthFileAtomic(authPath, contents) {
|
|
133
|
+
const tmp = `${authPath}.${process.pid}.${Date.now()}.tmp`;
|
|
134
|
+
writeFileSync(tmp, contents, AUTH_FILE_WRITE_OPTIONS);
|
|
135
|
+
try {
|
|
136
|
+
chmodSync(tmp, 0o600);
|
|
137
|
+
}
|
|
138
|
+
catch {
|
|
139
|
+
// mode bits are best-effort on Windows.
|
|
140
|
+
}
|
|
141
|
+
try {
|
|
142
|
+
if (process.platform === "win32" && existsSync(authPath)) {
|
|
143
|
+
writeFileSync(authPath, contents, AUTH_FILE_WRITE_OPTIONS);
|
|
144
|
+
chmodSync(authPath, 0o600);
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
renameSync(tmp, authPath);
|
|
148
|
+
try {
|
|
149
|
+
chmodSync(authPath, 0o600);
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
// mode bits are best-effort on Windows.
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
finally {
|
|
156
|
+
try {
|
|
157
|
+
unlinkSync(tmp);
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
// tmp already renamed, or never created.
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
function acquireGrokLock(authPath) {
|
|
165
|
+
const maxAttempts = 10;
|
|
166
|
+
const delayMs = 20;
|
|
167
|
+
let lastError;
|
|
168
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
169
|
+
try {
|
|
170
|
+
return lockfile.lockSync(authPath, { realpath: false, stale: 120_000 });
|
|
171
|
+
}
|
|
172
|
+
catch (error) {
|
|
173
|
+
const code = typeof error === "object" && error !== null && "code" in error
|
|
174
|
+
? String(error.code)
|
|
175
|
+
: undefined;
|
|
176
|
+
if (code !== "ELOCKED" || attempt === maxAttempts) {
|
|
177
|
+
throw error;
|
|
178
|
+
}
|
|
179
|
+
lastError = error;
|
|
180
|
+
const start = Date.now();
|
|
181
|
+
while (Date.now() - start < delayMs) {
|
|
182
|
+
// Sleep synchronously to match AuthStorage.
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
throw lastError ?? new Error("Failed to acquire Grok CLI auth lock");
|
|
187
|
+
}
|
|
102
188
|
export function writeGrokCliXaiOAuth(credential, options = {}) {
|
|
103
189
|
const authPath = grokAuthPath(options.grokHome);
|
|
104
190
|
if (options.onlyIfFileExists !== false && !existsSync(authPath))
|
|
@@ -107,34 +193,43 @@ export function writeGrokCliXaiOAuth(credential, options = {}) {
|
|
|
107
193
|
if (!existsSync(dir))
|
|
108
194
|
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
109
195
|
if (!existsSync(authPath)) {
|
|
110
|
-
|
|
111
|
-
chmodSync(authPath, 0o600);
|
|
196
|
+
writeAuthFileAtomic(authPath, "{}\n");
|
|
112
197
|
}
|
|
113
198
|
let release;
|
|
114
199
|
try {
|
|
115
|
-
release =
|
|
200
|
+
release = acquireGrokLock(authPath);
|
|
116
201
|
let data = {};
|
|
117
202
|
try {
|
|
118
203
|
const parsed = JSON.parse(readFileSync(authPath, "utf-8"));
|
|
119
204
|
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
120
205
|
data = parsed;
|
|
121
206
|
}
|
|
207
|
+
else {
|
|
208
|
+
return false;
|
|
209
|
+
}
|
|
122
210
|
}
|
|
123
211
|
catch {
|
|
124
|
-
|
|
212
|
+
// Never replace a corrupt Grok file with a single-entry stub.
|
|
213
|
+
return false;
|
|
125
214
|
}
|
|
126
215
|
const key = grokOidcEntryKey();
|
|
127
216
|
const previous = data[key] && typeof data[key] === "object" && !Array.isArray(data[key])
|
|
128
217
|
? data[key]
|
|
129
218
|
: {};
|
|
219
|
+
const existing = grokEntryToOAuth(previous);
|
|
220
|
+
if (existing &&
|
|
221
|
+
existing.refresh !== credential.refresh &&
|
|
222
|
+
existing.expires > credential.expires + REFRESH_SKEW_MS) {
|
|
223
|
+
// Disk already has a newer family (usually a later `grok login`).
|
|
224
|
+
return false;
|
|
225
|
+
}
|
|
130
226
|
data[key] = {
|
|
131
227
|
...previous,
|
|
132
228
|
key: credential.access,
|
|
133
229
|
refresh_token: credential.refresh,
|
|
134
|
-
expires_at: credential.expires,
|
|
230
|
+
expires_at: formatGrokExpiresAt(credential.expires),
|
|
135
231
|
};
|
|
136
|
-
|
|
137
|
-
chmodSync(authPath, 0o600);
|
|
232
|
+
writeAuthFileAtomic(authPath, `${JSON.stringify(data, null, 2)}\n`);
|
|
138
233
|
return true;
|
|
139
234
|
}
|
|
140
235
|
finally {
|
|
@@ -161,7 +256,16 @@ export function wrapXaiGrokCliCredentials(store, options = {}) {
|
|
|
161
256
|
const chosen = chooseXaiOAuth(stored, grok);
|
|
162
257
|
if (shouldAdopt(stored, chosen) && chosen?.type === "oauth") {
|
|
163
258
|
try {
|
|
164
|
-
await store.modify(providerId, async () =>
|
|
259
|
+
await store.modify(providerId, async (current) => {
|
|
260
|
+
if (current?.type === "api_key")
|
|
261
|
+
return undefined;
|
|
262
|
+
const latestGrok = readGrokCliXaiOAuth({ grokHome });
|
|
263
|
+
const latestChosen = chooseXaiOAuth(current, latestGrok);
|
|
264
|
+
if (shouldAdopt(current, latestChosen) && latestChosen?.type === "oauth") {
|
|
265
|
+
return latestChosen;
|
|
266
|
+
}
|
|
267
|
+
return undefined;
|
|
268
|
+
});
|
|
165
269
|
}
|
|
166
270
|
catch {
|
|
167
271
|
// Read still returns the live Grok token if persist fails.
|
|
@@ -189,25 +293,30 @@ export function wrapXaiGrokCliCredentials(store, options = {}) {
|
|
|
189
293
|
next = await fn(chosen);
|
|
190
294
|
}
|
|
191
295
|
catch (error) {
|
|
192
|
-
|
|
296
|
+
const retryGrok = readGrokCliXaiOAuth({ grokHome });
|
|
297
|
+
const canRetry = oauthRefreshFailed(error) &&
|
|
298
|
+
retryGrok &&
|
|
299
|
+
!(chosen?.type === "oauth" && retryGrok.refresh === chosen.refresh);
|
|
300
|
+
if (!canRetry)
|
|
193
301
|
throw error;
|
|
194
302
|
try {
|
|
195
|
-
next = await fn(
|
|
303
|
+
next = await fn(retryGrok);
|
|
196
304
|
}
|
|
197
305
|
catch {
|
|
198
306
|
throw error;
|
|
199
307
|
}
|
|
200
308
|
}
|
|
201
|
-
|
|
202
|
-
|
|
309
|
+
// `undefined` means "leave the lunR row unchanged". Adopting `chosen`
|
|
310
|
+
// here wrote a rotated-out Grok refresh over a just-rotated lunR token.
|
|
311
|
+
if (next?.type === "oauth") {
|
|
203
312
|
try {
|
|
204
|
-
writeGrokCliXaiOAuth(
|
|
313
|
+
writeGrokCliXaiOAuth(next, { grokHome, onlyIfFileExists: true });
|
|
205
314
|
}
|
|
206
315
|
catch {
|
|
207
316
|
// lunR persist still happens; Grok file update is best-effort.
|
|
208
317
|
}
|
|
209
318
|
}
|
|
210
|
-
return
|
|
319
|
+
return next;
|
|
211
320
|
});
|
|
212
321
|
},
|
|
213
322
|
delete(providerId) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"grok-cli-auth.js","sourceRoot":"","sources":["../../src/core/grok-cli-auth.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Official Grok CLI SuperGrok session (`~/.grok/auth.json`).\n *\n * lunR and Grok CLI share xAI client `b1a00492-073a-47ea-816f-4c329264a828`.\n * A later `grok login` revokes lunR's refresh token; this module reads the\n * still-live Grok CLI entry and write-throughs rotated tokens so the two\n * CLIs do not fight.\n */\n\nimport { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\nimport type { Credential, CredentialInfo, CredentialStore, OAuthCredential } from \"@ashx-j/lunr-ai\";\nimport lockfile from \"proper-lockfile\";\n\nexport const XAI_PROVIDER_ID = \"xai\";\n/** Same client as packages/ai/src/auth/oauth/xai.ts `XAI_CLIENT_ID`. */\nexport const XAI_GROK_CLI_CLIENT_ID = \"b1a00492-073a-47ea-816f-4c329264a828\";\n\nconst AUTH_FILE_WRITE_OPTIONS = { encoding: \"utf-8\", mode: 0o600 } as const;\n\nexport function grokHomeDir(grokHome?: string): string {\n\tif (grokHome) return grokHome;\n\tconst fromEnv = process.env.GROK_HOME?.trim();\n\tif (fromEnv) return fromEnv;\n\treturn join(homedir(), \".grok\");\n}\n\nexport function grokAuthPath(grokHome?: string): string {\n\treturn join(grokHomeDir(grokHome), \"auth.json\");\n}\n\nexport function grokOidcEntryKey(clientId: string = XAI_GROK_CLI_CLIENT_ID): string {\n\treturn `https://auth.x.ai::${clientId}`;\n}\n\nexport function parseGrokExpiresAt(value: unknown): number | undefined {\n\tif (typeof value === \"number\" && Number.isFinite(value) && value > 0) {\n\t\treturn value < 1e12 ? Math.round(value * 1000) : Math.round(value);\n\t}\n\tif (typeof value === \"string\" && value.trim()) {\n\t\tconst numeric = Number(value);\n\t\tif (Number.isFinite(numeric) && numeric > 0) {\n\t\t\treturn numeric < 1e12 ? Math.round(numeric * 1000) : Math.round(numeric);\n\t\t}\n\t\tconst parsed = Date.parse(value);\n\t\tif (Number.isFinite(parsed)) return parsed;\n\t}\n\treturn undefined;\n}\n\nexport function grokEntryToOAuth(entry: unknown): OAuthCredential | undefined {\n\tif (!entry || typeof entry !== \"object\" || Array.isArray(entry)) return undefined;\n\tconst record = entry as Record<string, unknown>;\n\tconst access = typeof record.key === \"string\" ? record.key : undefined;\n\tconst refresh = typeof record.refresh_token === \"string\" ? record.refresh_token : undefined;\n\tconst expires = parseGrokExpiresAt(record.expires_at);\n\tif (!access || !refresh || expires === undefined) return undefined;\n\treturn { type: \"oauth\", access, refresh, expires };\n}\n\nexport function readGrokCliXaiOAuth(options: { grokHome?: string } = {}): OAuthCredential | undefined {\n\ttry {\n\t\tconst raw = readFileSync(grokAuthPath(options.grokHome), \"utf-8\");\n\t\tconst parsed = JSON.parse(raw) as unknown;\n\t\tif (!parsed || typeof parsed !== \"object\" || Array.isArray(parsed)) return undefined;\n\t\treturn grokEntryToOAuth((parsed as Record<string, unknown>)[grokOidcEntryKey()]);\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nexport function hasGrokCliXaiAuth(options: { grokHome?: string } = {}): boolean {\n\treturn readGrokCliXaiOAuth(options) !== undefined;\n}\n\nexport function chooseXaiOAuth(\n\tlunr: Credential | undefined,\n\tgrok: OAuthCredential | undefined,\n): Credential | undefined {\n\tif (lunr?.type === \"api_key\") return lunr;\n\tconst lunrOAuth = lunr?.type === \"oauth\" ? lunr : undefined;\n\tif (lunrOAuth && grok) return grok.expires >= lunrOAuth.expires ? grok : lunrOAuth;\n\treturn grok ?? lunrOAuth ?? lunr;\n}\n\nfunction oauthRefreshFailed(error: unknown): boolean {\n\tconst parts: string[] = [];\n\tlet current: unknown = error;\n\tconst seen = new Set<unknown>();\n\twhile (current && typeof current === \"object\" && !seen.has(current)) {\n\t\tseen.add(current);\n\t\tif (current instanceof Error) {\n\t\t\tparts.push(current.message);\n\t\t\tcurrent = current.cause;\n\t\t\tcontinue;\n\t\t}\n\t\tbreak;\n\t}\n\treturn /invalid_grant|oauth refresh failed/i.test(parts.join(\" \"));\n}\n\nfunction shouldAdopt(lunr: Credential | undefined, chosen: Credential | undefined): boolean {\n\tif (!chosen || chosen.type !== \"oauth\") return false;\n\tif (!lunr || lunr.type !== \"oauth\") return true;\n\treturn lunr.access !== chosen.access || lunr.refresh !== chosen.refresh;\n}\n\nexport function writeGrokCliXaiOAuth(\n\tcredential: OAuthCredential,\n\toptions: { grokHome?: string; onlyIfFileExists?: boolean } = {},\n): boolean {\n\tconst authPath = grokAuthPath(options.grokHome);\n\tif (options.onlyIfFileExists !== false && !existsSync(authPath)) return false;\n\n\tconst dir = dirname(authPath);\n\tif (!existsSync(dir)) mkdirSync(dir, { recursive: true, mode: 0o700 });\n\tif (!existsSync(authPath)) {\n\t\twriteFileSync(authPath, \"{}\\n\", AUTH_FILE_WRITE_OPTIONS);\n\t\tchmodSync(authPath, 0o600);\n\t}\n\n\tlet release: (() => void) | undefined;\n\ttry {\n\t\trelease = lockfile.lockSync(authPath, { realpath: false });\n\t\tlet data: Record<string, unknown> = {};\n\t\ttry {\n\t\t\tconst parsed = JSON.parse(readFileSync(authPath, \"utf-8\")) as unknown;\n\t\t\tif (parsed && typeof parsed === \"object\" && !Array.isArray(parsed)) {\n\t\t\t\tdata = parsed as Record<string, unknown>;\n\t\t\t}\n\t\t} catch {\n\t\t\tdata = {};\n\t\t}\n\t\tconst key = grokOidcEntryKey();\n\t\tconst previous =\n\t\t\tdata[key] && typeof data[key] === \"object\" && !Array.isArray(data[key])\n\t\t\t\t? (data[key] as Record<string, unknown>)\n\t\t\t\t: {};\n\t\tdata[key] = {\n\t\t\t...previous,\n\t\t\tkey: credential.access,\n\t\t\trefresh_token: credential.refresh,\n\t\t\texpires_at: credential.expires,\n\t\t};\n\t\twriteFileSync(authPath, `${JSON.stringify(data, null, 2)}\\n`, AUTH_FILE_WRITE_OPTIONS);\n\t\tchmodSync(authPath, 0o600);\n\t\treturn true;\n\t} finally {\n\t\tif (release) {\n\t\t\ttry {\n\t\t\t\trelease();\n\t\t\t} catch {\n\t\t\t\t// Unlock is best-effort.\n\t\t\t}\n\t\t}\n\t}\n}\n\nexport function wrapXaiGrokCliCredentials(\n\tstore: CredentialStore,\n\toptions: { grokHome?: string } = {},\n): CredentialStore {\n\tconst grokHome = options.grokHome;\n\treturn {\n\t\tasync read(providerId: string): Promise<Credential | undefined> {\n\t\t\tconst stored = await store.read(providerId);\n\t\t\tif (providerId !== XAI_PROVIDER_ID) return stored;\n\t\t\tif (stored?.type === \"api_key\") return stored;\n\t\t\tconst grok = readGrokCliXaiOAuth({ grokHome });\n\t\t\tconst chosen = chooseXaiOAuth(stored, grok);\n\t\t\tif (shouldAdopt(stored, chosen) && chosen?.type === \"oauth\") {\n\t\t\t\ttry {\n\t\t\t\t\tawait store.modify(providerId, async () => chosen);\n\t\t\t\t} catch {\n\t\t\t\t\t// Read still returns the live Grok token if persist fails.\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn chosen;\n\t\t},\n\t\tasync list(): Promise<readonly CredentialInfo[]> {\n\t\t\tconst entries = new Map((await store.list()).map((entry) => [entry.providerId, entry]));\n\t\t\tif (!entries.has(XAI_PROVIDER_ID) && hasGrokCliXaiAuth({ grokHome })) {\n\t\t\t\tentries.set(XAI_PROVIDER_ID, { providerId: XAI_PROVIDER_ID, type: \"oauth\" });\n\t\t\t}\n\t\t\treturn [...entries.values()];\n\t\t},\n\t\tmodify(providerId, fn) {\n\t\t\tif (providerId !== XAI_PROVIDER_ID) return store.modify(providerId, fn);\n\t\t\treturn store.modify(providerId, async (lunrCurrent) => {\n\t\t\t\tif (lunrCurrent?.type === \"api_key\") return fn(lunrCurrent);\n\t\t\t\tconst grok = readGrokCliXaiOAuth({ grokHome });\n\t\t\t\tconst chosen = chooseXaiOAuth(lunrCurrent, grok);\n\t\t\t\tlet next: Credential | undefined;\n\t\t\t\ttry {\n\t\t\t\t\tnext = await fn(chosen);\n\t\t\t\t} catch (error) {\n\t\t\t\t\tif (!oauthRefreshFailed(error) || !grok || chosen === grok) throw error;\n\t\t\t\t\ttry {\n\t\t\t\t\t\tnext = await fn(grok);\n\t\t\t\t\t} catch {\n\t\t\t\t\t\tthrow error;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tconst persist = next ?? (shouldAdopt(lunrCurrent, chosen) ? chosen : undefined);\n\t\t\t\tif (persist?.type === \"oauth\") {\n\t\t\t\t\ttry {\n\t\t\t\t\t\twriteGrokCliXaiOAuth(persist, { grokHome, onlyIfFileExists: true });\n\t\t\t\t\t} catch {\n\t\t\t\t\t\t// lunR persist still happens; Grok file update is best-effort.\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn persist;\n\t\t\t});\n\t\t},\n\t\tdelete(providerId: string): Promise<void> {\n\t\t\treturn store.delete(providerId);\n\t\t},\n\t};\n}\n"]}
|
|
1
|
+
{"version":3,"file":"grok-cli-auth.js","sourceRoot":"","sources":["../../src/core/grok-cli-auth.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Official Grok CLI SuperGrok session (`~/.grok/auth.json`).\n *\n * lunR and Grok CLI share xAI client `b1a00492-073a-47ea-816f-4c329264a828`.\n * xAI rotates refresh tokens and revokes the family on reuse, so this module\n * must never write a rotated-out refresh back over a newer one.\n */\n\nimport { chmodSync, existsSync, mkdirSync, readFileSync, renameSync, unlinkSync, writeFileSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\nimport type { Credential, CredentialInfo, CredentialStore, OAuthCredential } from \"@ashx-j/lunr-ai\";\nimport lockfile from \"proper-lockfile\";\n\nexport const XAI_PROVIDER_ID = \"xai\";\n/** Same client as packages/ai/src/auth/oauth/xai.ts `XAI_CLIENT_ID`. */\nexport const XAI_GROK_CLI_CLIENT_ID = \"b1a00492-073a-47ea-816f-4c329264a828\";\n/** Matches packages/ai/src/auth/oauth/xai.ts `REFRESH_SKEW_MS`. */\nconst REFRESH_SKEW_MS = 5 * 60 * 1000;\n\nconst AUTH_FILE_WRITE_OPTIONS = { encoding: \"utf-8\", mode: 0o600 } as const;\n\nexport function grokHomeDir(grokHome?: string): string {\n\tif (grokHome) return grokHome;\n\tconst fromEnv = process.env.GROK_HOME?.trim();\n\tif (fromEnv) return fromEnv;\n\treturn join(homedir(), \".grok\");\n}\n\nexport function grokAuthPath(grokHome?: string): string {\n\treturn join(grokHomeDir(grokHome), \"auth.json\");\n}\n\nexport function grokOidcEntryKey(clientId: string = XAI_GROK_CLI_CLIENT_ID): string {\n\treturn `https://auth.x.ai::${clientId}`;\n}\n\nexport function parseGrokExpiresAt(value: unknown): number | undefined {\n\tif (typeof value === \"number\" && Number.isFinite(value) && value > 0) {\n\t\treturn value < 1e12 ? Math.round(value * 1000) : Math.round(value);\n\t}\n\tif (typeof value === \"string\" && value.trim()) {\n\t\tconst numeric = Number(value);\n\t\tif (Number.isFinite(numeric) && numeric > 0) {\n\t\t\treturn numeric < 1e12 ? Math.round(numeric * 1000) : Math.round(numeric);\n\t\t}\n\t\tconst parsed = Date.parse(value);\n\t\tif (Number.isFinite(parsed)) return parsed;\n\t}\n\treturn undefined;\n}\n\n/** Grok CLI deserializes `expires_at` as RFC3339, not epoch ms. */\nexport function formatGrokExpiresAt(expires: number): string {\n\treturn new Date(expires).toISOString();\n}\n\nexport function grokEntryToOAuth(entry: unknown): OAuthCredential | undefined {\n\tif (!entry || typeof entry !== \"object\" || Array.isArray(entry)) return undefined;\n\tconst record = entry as Record<string, unknown>;\n\tconst access = typeof record.key === \"string\" ? record.key : undefined;\n\tconst refresh = typeof record.refresh_token === \"string\" ? record.refresh_token : undefined;\n\tconst expires = parseGrokExpiresAt(record.expires_at);\n\tif (!access || !refresh || expires === undefined) return undefined;\n\treturn { type: \"oauth\", access, refresh, expires };\n}\n\nexport function readGrokCliXaiOAuth(options: { grokHome?: string } = {}): OAuthCredential | undefined {\n\ttry {\n\t\tconst raw = readFileSync(grokAuthPath(options.grokHome), \"utf-8\");\n\t\tconst parsed = JSON.parse(raw) as unknown;\n\t\tif (!parsed || typeof parsed !== \"object\" || Array.isArray(parsed)) return undefined;\n\t\treturn grokEntryToOAuth((parsed as Record<string, unknown>)[grokOidcEntryKey()]);\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nexport function hasGrokCliXaiAuth(options: { grokHome?: string } = {}): boolean {\n\treturn readGrokCliXaiOAuth(options) !== undefined;\n}\n\n/**\n * Pick the live SuperGrok session.\n *\n * lunR stores `expires` 5 minutes early. Grok CLI stores the server expiry.\n * A raw `>=` compare therefore treats a just-rotated lunR token as older than\n * the still-unrotated Grok file and writes the dead refresh back. When both\n * look live and the refresh tokens differ, keep lunR; `modify` retries Grok\n * only after `invalid_grant`.\n */\nexport function chooseXaiOAuth(\n\tlunr: Credential | undefined,\n\tgrok: OAuthCredential | undefined,\n): Credential | undefined {\n\tif (lunr?.type === \"api_key\") return lunr;\n\tconst lunrOAuth = lunr?.type === \"oauth\" ? lunr : undefined;\n\tif (lunrOAuth && grok) {\n\t\tif (lunrOAuth.refresh === grok.refresh) {\n\t\t\treturn grok.expires >= lunrOAuth.expires ? grok : lunrOAuth;\n\t\t}\n\t\tconst now = Date.now();\n\t\tconst lunrLive = now < lunrOAuth.expires;\n\t\tconst grokLive = now < grok.expires;\n\t\tif (grokLive && !lunrLive) return grok;\n\t\tif (lunrLive && !grokLive) return lunrOAuth;\n\t\tif (!lunrLive && !grokLive) return grok.expires >= lunrOAuth.expires ? grok : lunrOAuth;\n\t\treturn lunrOAuth;\n\t}\n\treturn grok ?? lunrOAuth ?? lunr;\n}\n\nfunction oauthRefreshFailed(error: unknown): boolean {\n\tconst parts: string[] = [];\n\tlet current: unknown = error;\n\tconst seen = new Set<unknown>();\n\twhile (current && typeof current === \"object\" && !seen.has(current)) {\n\t\tseen.add(current);\n\t\tif (current instanceof Error) {\n\t\t\tparts.push(current.message);\n\t\t\tcurrent = current.cause;\n\t\t\tcontinue;\n\t\t}\n\t\tbreak;\n\t}\n\tconst chain = parts.join(\" \");\n\tif (/timed out|aborted|cancelled|ECONN|ENOTFOUND|network/i.test(chain)) return false;\n\treturn /invalid_grant|refresh token revoked/i.test(chain);\n}\n\nfunction shouldAdopt(lunr: Credential | undefined, chosen: Credential | undefined): boolean {\n\tif (!chosen || chosen.type !== \"oauth\") return false;\n\tif (!lunr || lunr.type !== \"oauth\") return true;\n\treturn lunr.access !== chosen.access || lunr.refresh !== chosen.refresh;\n}\n\nfunction writeAuthFileAtomic(authPath: string, contents: string): void {\n\tconst tmp = `${authPath}.${process.pid}.${Date.now()}.tmp`;\n\twriteFileSync(tmp, contents, AUTH_FILE_WRITE_OPTIONS);\n\ttry {\n\t\tchmodSync(tmp, 0o600);\n\t} catch {\n\t\t// mode bits are best-effort on Windows.\n\t}\n\ttry {\n\t\tif (process.platform === \"win32\" && existsSync(authPath)) {\n\t\t\twriteFileSync(authPath, contents, AUTH_FILE_WRITE_OPTIONS);\n\t\t\tchmodSync(authPath, 0o600);\n\t\t\treturn;\n\t\t}\n\t\trenameSync(tmp, authPath);\n\t\ttry {\n\t\t\tchmodSync(authPath, 0o600);\n\t\t} catch {\n\t\t\t// mode bits are best-effort on Windows.\n\t\t}\n\t} finally {\n\t\ttry {\n\t\t\tunlinkSync(tmp);\n\t\t} catch {\n\t\t\t// tmp already renamed, or never created.\n\t\t}\n\t}\n}\n\nfunction acquireGrokLock(authPath: string): () => void {\n\tconst maxAttempts = 10;\n\tconst delayMs = 20;\n\tlet lastError: unknown;\n\tfor (let attempt = 1; attempt <= maxAttempts; attempt++) {\n\t\ttry {\n\t\t\treturn lockfile.lockSync(authPath, { realpath: false, stale: 120_000 });\n\t\t} catch (error) {\n\t\t\tconst code =\n\t\t\t\ttypeof error === \"object\" && error !== null && \"code\" in error\n\t\t\t\t\t? String((error as { code?: unknown }).code)\n\t\t\t\t\t: undefined;\n\t\t\tif (code !== \"ELOCKED\" || attempt === maxAttempts) {\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t\tlastError = error;\n\t\t\tconst start = Date.now();\n\t\t\twhile (Date.now() - start < delayMs) {\n\t\t\t\t// Sleep synchronously to match AuthStorage.\n\t\t\t}\n\t\t}\n\t}\n\tthrow (lastError as Error) ?? new Error(\"Failed to acquire Grok CLI auth lock\");\n}\n\nexport function writeGrokCliXaiOAuth(\n\tcredential: OAuthCredential,\n\toptions: { grokHome?: string; onlyIfFileExists?: boolean } = {},\n): boolean {\n\tconst authPath = grokAuthPath(options.grokHome);\n\tif (options.onlyIfFileExists !== false && !existsSync(authPath)) return false;\n\n\tconst dir = dirname(authPath);\n\tif (!existsSync(dir)) mkdirSync(dir, { recursive: true, mode: 0o700 });\n\tif (!existsSync(authPath)) {\n\t\twriteAuthFileAtomic(authPath, \"{}\\n\");\n\t}\n\n\tlet release: (() => void) | undefined;\n\ttry {\n\t\trelease = acquireGrokLock(authPath);\n\t\tlet data: Record<string, unknown> = {};\n\t\ttry {\n\t\t\tconst parsed = JSON.parse(readFileSync(authPath, \"utf-8\")) as unknown;\n\t\t\tif (parsed && typeof parsed === \"object\" && !Array.isArray(parsed)) {\n\t\t\t\tdata = parsed as Record<string, unknown>;\n\t\t\t} else {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t} catch {\n\t\t\t// Never replace a corrupt Grok file with a single-entry stub.\n\t\t\treturn false;\n\t\t}\n\t\tconst key = grokOidcEntryKey();\n\t\tconst previous =\n\t\t\tdata[key] && typeof data[key] === \"object\" && !Array.isArray(data[key])\n\t\t\t\t? (data[key] as Record<string, unknown>)\n\t\t\t\t: {};\n\t\tconst existing = grokEntryToOAuth(previous);\n\t\tif (\n\t\t\texisting &&\n\t\t\texisting.refresh !== credential.refresh &&\n\t\t\texisting.expires > credential.expires + REFRESH_SKEW_MS\n\t\t) {\n\t\t\t// Disk already has a newer family (usually a later `grok login`).\n\t\t\treturn false;\n\t\t}\n\t\tdata[key] = {\n\t\t\t...previous,\n\t\t\tkey: credential.access,\n\t\t\trefresh_token: credential.refresh,\n\t\t\texpires_at: formatGrokExpiresAt(credential.expires),\n\t\t};\n\t\twriteAuthFileAtomic(authPath, `${JSON.stringify(data, null, 2)}\\n`);\n\t\treturn true;\n\t} finally {\n\t\tif (release) {\n\t\t\ttry {\n\t\t\t\trelease();\n\t\t\t} catch {\n\t\t\t\t// Unlock is best-effort.\n\t\t\t}\n\t\t}\n\t}\n}\n\nexport function wrapXaiGrokCliCredentials(\n\tstore: CredentialStore,\n\toptions: { grokHome?: string } = {},\n): CredentialStore {\n\tconst grokHome = options.grokHome;\n\treturn {\n\t\tasync read(providerId: string): Promise<Credential | undefined> {\n\t\t\tconst stored = await store.read(providerId);\n\t\t\tif (providerId !== XAI_PROVIDER_ID) return stored;\n\t\t\tif (stored?.type === \"api_key\") return stored;\n\t\t\tconst grok = readGrokCliXaiOAuth({ grokHome });\n\t\t\tconst chosen = chooseXaiOAuth(stored, grok);\n\t\t\tif (shouldAdopt(stored, chosen) && chosen?.type === \"oauth\") {\n\t\t\t\ttry {\n\t\t\t\t\tawait store.modify(providerId, async (current) => {\n\t\t\t\t\t\tif (current?.type === \"api_key\") return undefined;\n\t\t\t\t\t\tconst latestGrok = readGrokCliXaiOAuth({ grokHome });\n\t\t\t\t\t\tconst latestChosen = chooseXaiOAuth(current, latestGrok);\n\t\t\t\t\t\tif (shouldAdopt(current, latestChosen) && latestChosen?.type === \"oauth\") {\n\t\t\t\t\t\t\treturn latestChosen;\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn undefined;\n\t\t\t\t\t});\n\t\t\t\t} catch {\n\t\t\t\t\t// Read still returns the live Grok token if persist fails.\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn chosen;\n\t\t},\n\t\tasync list(): Promise<readonly CredentialInfo[]> {\n\t\t\tconst entries = new Map((await store.list()).map((entry) => [entry.providerId, entry]));\n\t\t\tif (!entries.has(XAI_PROVIDER_ID) && hasGrokCliXaiAuth({ grokHome })) {\n\t\t\t\tentries.set(XAI_PROVIDER_ID, { providerId: XAI_PROVIDER_ID, type: \"oauth\" });\n\t\t\t}\n\t\t\treturn [...entries.values()];\n\t\t},\n\t\tmodify(providerId, fn) {\n\t\t\tif (providerId !== XAI_PROVIDER_ID) return store.modify(providerId, fn);\n\t\t\treturn store.modify(providerId, async (lunrCurrent) => {\n\t\t\t\tif (lunrCurrent?.type === \"api_key\") return fn(lunrCurrent);\n\t\t\t\tconst grok = readGrokCliXaiOAuth({ grokHome });\n\t\t\t\tconst chosen = chooseXaiOAuth(lunrCurrent, grok);\n\t\t\t\tlet next: Credential | undefined;\n\t\t\t\ttry {\n\t\t\t\t\tnext = await fn(chosen);\n\t\t\t\t} catch (error) {\n\t\t\t\t\tconst retryGrok = readGrokCliXaiOAuth({ grokHome });\n\t\t\t\t\tconst canRetry =\n\t\t\t\t\t\toauthRefreshFailed(error) &&\n\t\t\t\t\t\tretryGrok &&\n\t\t\t\t\t\t!(chosen?.type === \"oauth\" && retryGrok.refresh === chosen.refresh);\n\t\t\t\t\tif (!canRetry) throw error;\n\t\t\t\t\ttry {\n\t\t\t\t\t\tnext = await fn(retryGrok);\n\t\t\t\t\t} catch {\n\t\t\t\t\t\tthrow error;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t// `undefined` means \"leave the lunR row unchanged\". Adopting `chosen`\n\t\t\t\t// here wrote a rotated-out Grok refresh over a just-rotated lunR token.\n\t\t\t\tif (next?.type === \"oauth\") {\n\t\t\t\t\ttry {\n\t\t\t\t\t\twriteGrokCliXaiOAuth(next, { grokHome, onlyIfFileExists: true });\n\t\t\t\t\t} catch {\n\t\t\t\t\t\t// lunR persist still happens; Grok file update is best-effort.\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn next;\n\t\t\t});\n\t\t},\n\t\tdelete(providerId: string): Promise<void> {\n\t\t\treturn store.delete(providerId);\n\t\t},\n\t};\n}\n"]}
|
|
@@ -57,6 +57,9 @@ export declare class ModelRuntime implements Models {
|
|
|
57
57
|
private readonly userModels;
|
|
58
58
|
private readonly officialCachePath;
|
|
59
59
|
private lastCatalogRefresh;
|
|
60
|
+
private lastOfficial;
|
|
61
|
+
private catalogRefresh;
|
|
62
|
+
private catalogRefreshAllowsNetwork;
|
|
60
63
|
private readonly grokHome;
|
|
61
64
|
private constructor();
|
|
62
65
|
static create(options?: CreateModelRuntimeOptions): Promise<ModelRuntime>;
|
|
@@ -95,7 +98,9 @@ export declare class ModelRuntime implements Models {
|
|
|
95
98
|
getAuth(model: Model<Api>, overrides?: ModelRuntimeAuthOverrides): Promise<AuthResult | undefined>;
|
|
96
99
|
/** lunr: whether a runtime --api-key override is active for the provider (it shadows auth.json). */
|
|
97
100
|
hasRuntimeApiKey(providerId: string): boolean;
|
|
98
|
-
setRuntimeApiKey(providerId: string, apiKey: string
|
|
101
|
+
setRuntimeApiKey(providerId: string, apiKey: string, options?: {
|
|
102
|
+
allowNetwork?: boolean;
|
|
103
|
+
}): Promise<void>;
|
|
99
104
|
removeRuntimeApiKey(providerId: string): Promise<void>;
|
|
100
105
|
/** lunr: whether network model-catalog fetches are allowed (offline mode disables them). */
|
|
101
106
|
isNetworkAllowed(): boolean;
|
|
@@ -112,6 +117,7 @@ export declare class ModelRuntime implements Models {
|
|
|
112
117
|
importGrokCliXaiAuth(): Promise<Credential | undefined>;
|
|
113
118
|
reloadConfig(): Promise<void>;
|
|
114
119
|
refresh(options?: ModelsRefreshOptions): Promise<CatalogRefreshResult>;
|
|
120
|
+
private executeCatalogRefresh;
|
|
115
121
|
getLastCatalogRefresh(): CatalogRefreshResult;
|
|
116
122
|
persistUserModels(rows: readonly UserModelEntry[]): void;
|
|
117
123
|
private refreshOfficialCatalog;
|
|
@@ -121,4 +127,6 @@ export declare class ModelRuntime implements Models {
|
|
|
121
127
|
registerProvider(providerId: string, config: ProviderConfigInput): void;
|
|
122
128
|
unregisterProvider(providerId: string): void;
|
|
123
129
|
}
|
|
130
|
+
/** TUI init / footer / rebind: cache-only. Live refresh is `/refresh` and the model picker. */
|
|
131
|
+
export declare function refreshModelCandidatesForInit(runtime: Pick<ModelRuntime, "refresh" | "getAvailableSnapshot">): Promise<readonly Model<Api>[]>;
|
|
124
132
|
//# sourceMappingURL=model-runtime.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"model-runtime.d.ts","sourceRoot":"","sources":["../../src/core/model-runtime.ts"],"names":[],"mappings":"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","sourcesContent":["import { dirname, join } from \"node:path\";\nimport {\n\ttype Api,\n\ttype ApiStreamOptions,\n\ttype AssistantMessage,\n\ttype AssistantMessageEventStream,\n\ttype AuthCheck,\n\ttype AuthInteraction,\n\ttype AuthResult,\n\ttype AuthType,\n\ttype Context,\n\ttype Credential,\n\ttype CredentialInfo,\n\ttype CredentialStore,\n\tcreateModels,\n\tlazyStream,\n\ttype Model,\n\ttype Models,\n\ttype ModelsApiStreamOptions,\n\tModelsError,\n\ttype ModelsRefreshOptions,\n\ttype ModelsRefreshResult,\n\ttype ModelsSimpleStreamOptions,\n\ttype ModelsStore,\n\ttype ModelsStreamTransforms,\n\ttype MutableModels,\n\ttype Provider,\n\ttype ProviderHeaders,\n\ttype SimpleStreamOptions,\n\ttype StreamOptions,\n} from \"@ashx-j/lunr-ai\";\nimport * as builtinProviderCatalog from \"@ashx-j/lunr-ai/providers/all\";\nimport { getAgentDir } from \"../config.ts\";\nimport { AuthStorage as DefaultAuthStorage } from \"./auth-storage.ts\";\nimport { CatalogOverlaySource, knownModelKeys, withCatalogOverlay } from \"./catalog-merge.ts\";\nimport { readGrokCliXaiOAuth, wrapXaiGrokCliCredentials } from \"./grok-cli-auth.ts\";\nimport {\n\tbuildLiveOverlay,\n\tfetchLiveProviderModels,\n\ttype IncompleteLiveModel,\n\tisLiveListProvider,\n\tLIVE_LIST_PROVIDER_IDS,\n\ttype LiveCatalogResult,\n} from \"./live-catalog.ts\";\nimport { ModelConfig } from \"./model-config.ts\";\nimport { FileModelsStore, InMemoryCodingAgentModelsStore } from \"./models-store.ts\";\nimport {\n\tloadOfficialCatalog,\n\ttype OfficialCatalogLoadResult,\n\tofficialCatalogCachePath,\n\tofficialEntryToModel,\n\tofficialModelKeys,\n} from \"./official-catalog.ts\";\nimport {\n\ttype AuthStatus,\n\ttype CompatibilityRequestConfig,\n\tcomposeModelProvider,\n\tconfiguredRequestAuthStatus,\n\ttype ProviderConfigInput,\n\tresolveCompatibilityRequestConfig,\n\tresolveConfiguredModelHeaders,\n\tvalidateExtensionProvider,\n} from \"./provider-composer.ts\";\nimport { RuntimeCredentials } from \"./runtime-credentials.ts\";\nimport { SubscriptionManager } from \"./subscriptions.ts\";\nimport { clearPlanUsageCache } from \"./usage-service.ts\";\nimport { type UserModelEntry, UserModelsStore, userEntryToModel, userModelsPath } from \"./user-models.ts\";\n\ninterface ModelRuntimeSnapshot {\n\tall: readonly Model<Api>[];\n\tavailable: readonly Model<Api>[];\n\tconfiguredProviders: ReadonlySet<string>;\n\tstoredProviders: ReadonlySet<string>;\n\tauth: ReadonlyMap<string, AuthCheck | undefined>;\n}\n\nexport interface CreateModelRuntimeOptions {\n\t/** Credential storage. Defaults to the file at authPath. */\n\tcredentials?: CredentialStore;\n\tauthPath?: string;\n\tmodelsPath?: string | null;\n\tmodelsStore?: ModelsStore;\n\tmodelsStorePath?: string;\n\tallowModelNetwork?: boolean;\n\t/** Applied by callers of `refresh()`, not by `create()` (startup is cache-only). */\n\tmodelRefreshTimeoutMs?: number;\n\t/** Unused. Kept so older callers compiling against this options bag still typecheck. */\n\tcatalogBaseUrl?: string;\n\t/** lunr: subscription-key pool. Defaults to subscriptions.json next to authPath (in-memory for custom stores). */\n\tsubscriptions?: SubscriptionManager;\n\t/**\n\t * Grok CLI home (`~/.grok`) used as a SuperGrok OAuth fallback.\n\t * `null` disables the fallback. Custom `credentials` default to disabled\n\t * unless this is an explicit path.\n\t */\n\tgrokHome?: string | null;\n}\n\nexport interface CatalogRefreshResult extends ModelsRefreshResult {\n\tofficial?: OfficialCatalogLoadResult & { evictedUserRows: number };\n\tlive: LiveCatalogResult[];\n\tincomplete: IncompleteLiveModel[];\n}\n\nexport interface ModelRuntimeAuthOverrides {\n\tapiKey?: string;\n\tenv?: Record<string, string>;\n}\n\nfunction mergeHeaders(\n\tbase: ProviderHeaders | undefined,\n\toverride: ProviderHeaders | undefined,\n): ProviderHeaders | undefined {\n\tif (!base && !override) return undefined;\n\tconst merged = { ...base };\n\tfor (const [name, value] of Object.entries(override ?? {})) {\n\t\tconst lowerName = name.toLowerCase();\n\t\tfor (const existingName of Object.keys(merged)) {\n\t\t\tif (existingName.toLowerCase() === lowerName) delete merged[existingName];\n\t\t}\n\t\tmerged[name] = value;\n\t}\n\treturn merged;\n}\n\n/** Configured pi-ai Models collection used by coding-agent and SDK consumers. */\nexport class ModelRuntime implements Models {\n\tprivate readonly models: MutableModels;\n\tprivate readonly credentials: RuntimeCredentials;\n\t/** lunr: per-provider subscription-key pools; rotation mirrors the active key into the credential store. */\n\treadonly subscriptionManager: SubscriptionManager;\n\tprivate readonly defaultBuiltins: ReadonlyMap<string, Provider>;\n\tprivate readonly builtins = new Map<string, Provider>();\n\tprivate readonly extensionProviders = new Map<string, ProviderConfigInput>();\n\tprivate readonly compositionErrors = new Map<string, string>();\n\tprivate readonly modelsPath: string | undefined;\n\tprivate readonly allowModelNetwork: boolean;\n\tprivate config: ModelConfig;\n\tprivate snapshot: ModelRuntimeSnapshot = {\n\t\tall: [],\n\t\tavailable: [],\n\t\tconfiguredProviders: new Set(),\n\t\tstoredProviders: new Set(),\n\t\tauth: new Map(),\n\t};\n\tprivate availabilityRefresh: Promise<void> | undefined;\n\tprivate availabilityError: string | undefined;\n\tprivate readonly modelsStore: ModelsStore;\n\tprivate readonly overlay = new CatalogOverlaySource();\n\tprivate readonly userModels: UserModelsStore;\n\tprivate readonly officialCachePath: string | undefined;\n\tprivate lastCatalogRefresh: CatalogRefreshResult = { aborted: false, errors: new Map(), live: [], incomplete: [] };\n\tprivate readonly grokHome: string | undefined;\n\n\tprivate constructor(\n\t\tcredentials: RuntimeCredentials,\n\t\tconfig: ModelConfig,\n\t\tmodelsPath: string | undefined,\n\t\tmodelsStore: ModelsStore,\n\t\tproviders: readonly Provider[],\n\t\tallowModelNetwork: boolean,\n\t\tsubscriptionManager: SubscriptionManager,\n\t\tcatalogDir: string | undefined,\n\t\tgrokHome: string | undefined,\n\t) {\n\t\tthis.credentials = credentials;\n\t\tthis.config = config;\n\t\tthis.modelsPath = modelsPath;\n\t\tthis.modelsStore = modelsStore;\n\t\tthis.allowModelNetwork = allowModelNetwork;\n\t\tthis.subscriptionManager = subscriptionManager;\n\t\tthis.userModels = new UserModelsStore(catalogDir ? userModelsPath(catalogDir) : undefined);\n\t\tthis.officialCachePath = catalogDir ? officialCatalogCachePath(catalogDir) : undefined;\n\t\tthis.grokHome = grokHome;\n\t\tthis.defaultBuiltins = new Map(providers.map((provider) => [provider.id, provider]));\n\t\tfor (const [providerId, provider] of this.defaultBuiltins) this.builtins.set(providerId, provider);\n\t\tthis.models = createModels({ credentials, modelsStore });\n\t\tthis.rebuildProviders();\n\t}\n\n\tstatic async create(options: CreateModelRuntimeOptions = {}): Promise<ModelRuntime> {\n\t\t// lunr: keep the underlying store — the SubscriptionManager mirrors rotated\n\t\t// keys into it directly, NOT through the RuntimeCredentials overlay (a runtime\n\t\t// --api-key override would shadow the mirror there).\n\t\tconst rawStore = options.credentials ?? DefaultAuthStorage.create(options.authPath);\n\t\tconst grokHomeDisabled =\n\t\t\toptions.grokHome === null || (options.grokHome === undefined && options.credentials !== undefined);\n\t\tconst grokHome = grokHomeDisabled ? undefined : (options.grokHome ?? undefined);\n\t\tconst baseStore = grokHomeDisabled ? rawStore : wrapXaiGrokCliCredentials(rawStore, { grokHome });\n\t\tconst credentials = new RuntimeCredentials(baseStore);\n\t\t// Pool persistence: subscriptions.json next to auth.json. A custom credential\n\t\t// store without an authPath (tests, SDK embeddings) gets a process-local pool\n\t\t// so nothing leaks into the real agent dir.\n\t\tconst subscriptionManager =\n\t\t\toptions.subscriptions ??\n\t\t\t(options.credentials && !options.authPath\n\t\t\t\t? SubscriptionManager.inMemory(baseStore)\n\t\t\t\t: SubscriptionManager.create(\n\t\t\t\t\t\tbaseStore,\n\t\t\t\t\t\toptions.authPath ? join(dirname(options.authPath), \"subscriptions.json\") : undefined,\n\t\t\t\t\t));\n\t\tconst modelsPath =\n\t\t\toptions.modelsPath === null ? undefined : (options.modelsPath ?? join(getAgentDir(), \"models.json\"));\n\t\tconst config = await ModelConfig.load(modelsPath);\n\t\tconst modelsStore =\n\t\t\toptions.modelsStore ??\n\t\t\t(modelsPath\n\t\t\t\t? new FileModelsStore(options.modelsStorePath ?? join(dirname(modelsPath), \"models-store.json\"))\n\t\t\t\t: new InMemoryCodingAgentModelsStore());\n\t\tconst providers = builtinProviderCatalog.builtinProviders();\n\t\tconst catalogDir = modelsPath ? dirname(modelsPath) : options.authPath ? dirname(options.authPath) : undefined;\n\t\tconst runtime = new ModelRuntime(\n\t\t\tcredentials,\n\t\t\tconfig,\n\t\t\tmodelsPath,\n\t\t\tmodelsStore,\n\t\t\tproviders,\n\t\t\toptions.allowModelNetwork ?? process.env.PI_OFFLINE === undefined,\n\t\t\tsubscriptionManager,\n\t\t\tcatalogDir,\n\t\t\tgrokHome,\n\t\t);\n\t\truntime.configureRadiusProviders();\n\t\truntime.rebuildProviders();\n\t\t// lunr: create() must not wait on GitHub / provider /v1/models / a dead NIC.\n\t\t// Disk cache + baked-in + bundled official overlay are enough to start;\n\t\t// /refresh and the model picker still do a live refresh.\n\t\tawait runtime.refresh({ allowNetwork: false });\n\t\treturn runtime;\n\t}\n\n\tprivate configureRadiusProviders(): void {\n\t\tthis.builtins.clear();\n\t\tfor (const [providerId, provider] of this.defaultBuiltins) this.builtins.set(providerId, provider);\n\t\tfor (const providerId of this.config.getProviderIds()) {\n\t\t\tconst config = this.config.getProvider(providerId);\n\t\t\tif (config?.oauth !== \"radius\" || !config.baseUrl) continue;\n\t\t\tthis.builtins.set(\n\t\t\t\tproviderId,\n\t\t\t\tbuiltinProviderCatalog.radiusProvider({\n\t\t\t\t\tid: providerId,\n\t\t\t\t\tname: config.name ?? providerId,\n\t\t\t\t\tgateway: config.baseUrl.replace(/\\/v1\\/?$/u, \"\"),\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\t}\n\n\tprivate providerIds(): Set<string> {\n\t\treturn new Set([...this.builtins.keys(), ...this.config.getProviderIds(), ...this.extensionProviders.keys()]);\n\t}\n\n\tprivate recomposeProvider(providerId: string): void {\n\t\tconst base = this.builtins.get(providerId);\n\t\tconst extension = this.extensionProviders.get(providerId);\n\t\tif (!base && !this.config.getProvider(providerId) && !extension) {\n\t\t\tthis.models.deleteProvider(providerId);\n\t\t\tthis.compositionErrors.delete(providerId);\n\t\t\treturn;\n\t\t}\n\t\tif (base && !this.config.getProvider(providerId) && !extension) {\n\t\t\t// No models.json/extension overlays: keep builtin auth/stream exact, then apply catalog layers.\n\t\t\tthis.models.setProvider(this.withLocalCatalog(base));\n\t\t\tthis.compositionErrors.delete(providerId);\n\t\t\treturn;\n\t\t}\n\t\ttry {\n\t\t\tthis.models.setProvider(this.withLocalCatalog(composeModelProvider(providerId, base, this.config, extension)));\n\t\t\tthis.compositionErrors.delete(providerId);\n\t\t} catch (error) {\n\t\t\tthis.compositionErrors.set(providerId, error instanceof Error ? error.message : String(error));\n\t\t\tif (base) this.models.setProvider(this.withLocalCatalog(base));\n\t\t\telse this.models.deleteProvider(providerId);\n\t\t}\n\t}\n\n\tprivate withLocalCatalog(provider: Provider): Provider {\n\t\treturn isLiveListProvider(provider.id) ? withCatalogOverlay(provider, this.overlay) : provider;\n\t}\n\n\tprivate bakedInModels(providerId: string): Model<Api>[] {\n\t\treturn [...(this.defaultBuiltins.get(providerId)?.getModels() ?? [])];\n\t}\n\n\tprivate rebuildProviders(): void {\n\t\tthis.models.clearProviders();\n\t\tthis.compositionErrors.clear();\n\t\tfor (const providerId of this.providerIds()) this.recomposeProvider(providerId);\n\t\tthis.updateModelSnapshot();\n\t}\n\n\tprivate updateModelSnapshot(): void {\n\t\tconst all = [...this.models.getModels()];\n\t\tthis.snapshot = {\n\t\t\t...this.snapshot,\n\t\t\tall,\n\t\t\tavailable: all.filter((model) => this.snapshot.configuredProviders.has(model.provider)),\n\t\t};\n\t}\n\n\tprivate async listStoredProviderIds(): Promise<Set<string>> {\n\t\tconst credentials = await this.credentials.list();\n\t\treturn new Set(credentials.map((entry) => entry.providerId));\n\t}\n\n\t/** `/model` lists stored creds + models.json/extension keys, never ambient envApiKeyAuth. */\n\tprivate configuredProviderIds(storedProviders: ReadonlySet<string>): Set<string> {\n\t\tconst configured = new Set(storedProviders);\n\t\tfor (const provider of this.models.getProviders()) {\n\t\t\tif (\n\t\t\t\tconfiguredRequestAuthStatus(this.config.getProvider(provider.id), this.extensionProviders.get(provider.id))\n\t\t\t\t\t?.configured\n\t\t\t) {\n\t\t\t\tconfigured.add(provider.id);\n\t\t\t}\n\t\t}\n\t\treturn configured;\n\t}\n\n\tprivate userModelsForStoredProviders(storedProviders: ReadonlySet<string>): Model<Api>[] {\n\t\treturn this.userModels\n\t\t\t.list()\n\t\t\t.filter((row) => storedProviders.has(row.provider))\n\t\t\t.map(userEntryToModel);\n\t}\n\n\tprivate async runAvailabilityRefresh(): Promise<void> {\n\t\tconst providers = this.models.getProviders();\n\t\tconst [available, checks, storedProviders] = await Promise.all([\n\t\t\tthis.models.getAvailable(),\n\t\t\tPromise.all(\n\t\t\t\tproviders.map(\n\t\t\t\t\tasync (provider): Promise<[string, AuthCheck | undefined]> => [\n\t\t\t\t\t\tprovider.id,\n\t\t\t\t\t\tawait this.models.checkAuth(provider.id),\n\t\t\t\t\t],\n\t\t\t\t),\n\t\t\t),\n\t\t\tthis.listStoredProviderIds(),\n\t\t]);\n\t\tconst auth = new Map(checks);\n\t\tconst configuredProviders = this.configuredProviderIds(storedProviders);\n\t\tthis.snapshot = {\n\t\t\tall: [...this.models.getModels()],\n\t\t\tavailable: available.filter((model) => configuredProviders.has(model.provider)),\n\t\t\tconfiguredProviders,\n\t\t\tstoredProviders,\n\t\t\tauth,\n\t\t};\n\t\tthis.availabilityError = undefined;\n\t}\n\n\tprivate queueAvailabilityRefresh(after: Promise<void> | undefined): Promise<void> {\n\t\tconst refresh = (after ?? Promise.resolve()).catch(() => {}).then(() => this.runAvailabilityRefresh());\n\t\tconst recorded = refresh.catch((error) => {\n\t\t\tthis.availabilityError = error instanceof Error ? error.message : String(error);\n\t\t\tthrow error;\n\t\t});\n\t\tconst tracked = recorded.finally(() => {\n\t\t\tif (this.availabilityRefresh === tracked) this.availabilityRefresh = undefined;\n\t\t});\n\t\tthis.availabilityRefresh = tracked;\n\t\treturn tracked;\n\t}\n\n\t/** Coalesce concurrent readers onto the pending refresh. */\n\tprivate refreshAvailability(): Promise<void> {\n\t\treturn this.availabilityRefresh ?? this.queueAvailabilityRefresh(undefined);\n\t}\n\n\t/** Mutations must not observe an in-flight refresh started before them. */\n\tprivate forceRefreshAvailability(): Promise<void> {\n\t\treturn this.queueAvailabilityRefresh(this.availabilityRefresh);\n\t}\n\n\tgetProviders(): readonly Provider[] {\n\t\treturn this.models.getProviders();\n\t}\n\n\tgetProvider(providerId: string): Provider | undefined {\n\t\treturn this.models.getProvider(providerId);\n\t}\n\n\tgetModels(providerId?: string): readonly Model<Api>[] {\n\t\treturn this.models.getModels(providerId);\n\t}\n\n\tgetModel(providerId: string, modelId: string): Model<Api> | undefined {\n\t\treturn this.models.getModel(providerId, modelId);\n\t}\n\n\tasync checkAuth(providerId: string): Promise<AuthCheck | undefined> {\n\t\treturn this.models.checkAuth(providerId);\n\t}\n\n\tasync getAvailable(providerId?: string): Promise<readonly Model<Api>[]> {\n\t\tif (providerId) {\n\t\t\tif (this.availabilityRefresh) {\n\t\t\t\tawait this.availabilityRefresh;\n\t\t\t\treturn this.snapshot.available.filter((model) => model.provider === providerId);\n\t\t\t}\n\t\t\ttry {\n\t\t\t\treturn await this.models.getAvailable(providerId);\n\t\t\t} catch (error) {\n\t\t\t\tthis.availabilityError = error instanceof Error ? error.message : String(error);\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t}\n\t\tawait this.refreshAvailability();\n\t\treturn this.snapshot.available;\n\t}\n\n\tgetAvailableSnapshot(): readonly Model<Api>[] {\n\t\treturn this.snapshot.available;\n\t}\n\n\tgetError(): string | undefined {\n\t\tconst errors: string[] = [];\n\t\tconst configError = this.config.getError();\n\t\tif (configError) errors.push(configError);\n\t\tfor (const [providerId, error] of this.compositionErrors) {\n\t\t\terrors.push(`Provider \"${providerId}\": ${error}`);\n\t\t}\n\t\tif (this.availabilityError) errors.push(`Availability refresh: ${this.availabilityError}`);\n\t\treturn errors.length > 0 ? errors.join(\"\\n\\n\") : undefined;\n\t}\n\n\tgetRegisteredProviderConfig(providerId: string): ProviderConfigInput | undefined {\n\t\treturn this.extensionProviders.get(providerId);\n\t}\n\n\tgetRegisteredProviderIds(): readonly string[] {\n\t\treturn [...this.extensionProviders.keys()];\n\t}\n\n\t/** @internal Compatibility fallback for ModelRegistry when provider auth is unconfigured. */\n\tgetCompatibilityRequestConfig(model: Model<Api>): CompatibilityRequestConfig {\n\t\treturn resolveCompatibilityRequestConfig(\n\t\t\tmodel,\n\t\t\tthis.config.getProvider(model.provider),\n\t\t\tthis.extensionProviders.get(model.provider),\n\t\t);\n\t}\n\n\tisUsingOAuth(providerId: string): boolean {\n\t\treturn this.snapshot.auth.get(providerId)?.type === \"oauth\";\n\t}\n\n\thasConfiguredAuth(providerId: string): boolean {\n\t\treturn this.snapshot.configuredProviders.has(providerId);\n\t}\n\n\tgetAuth(providerId: string, overrides?: ModelRuntimeAuthOverrides): Promise<AuthResult | undefined>;\n\tgetAuth(model: Model<Api>, overrides?: ModelRuntimeAuthOverrides): Promise<AuthResult | undefined>;\n\tasync getAuth(\n\t\tproviderOrModel: string | Model<Api>,\n\t\toverrides: ModelRuntimeAuthOverrides = {},\n\t): Promise<AuthResult | undefined> {\n\t\tif (typeof providerOrModel === \"string\") return this.models.getAuth(providerOrModel, overrides);\n\t\tconst resolution = await this.models.getAuth(providerOrModel, overrides);\n\t\tif (!resolution) return undefined;\n\t\tconst configuredHeaders = resolveConfiguredModelHeaders(\n\t\t\tproviderOrModel,\n\t\t\tthis.config.getProvider(providerOrModel.provider),\n\t\t\tthis.extensionProviders.get(providerOrModel.provider),\n\t\t\t{ ...(resolution.env ?? {}), ...(overrides.env ?? {}) },\n\t\t);\n\t\treturn {\n\t\t\t...resolution,\n\t\t\tauth: {\n\t\t\t\t...resolution.auth,\n\t\t\t\theaders: mergeHeaders(resolution.auth.headers, configuredHeaders),\n\t\t\t},\n\t\t};\n\t}\n\n\t/** lunr: whether a runtime --api-key override is active for the provider (it shadows auth.json). */\n\thasRuntimeApiKey(providerId: string): boolean {\n\t\treturn this.credentials.hasRuntimeApiKey(providerId);\n\t}\n\n\tasync setRuntimeApiKey(providerId: string, apiKey: string): Promise<void> {\n\t\tthis.credentials.setRuntimeApiKey(providerId, apiKey);\n\t\tconst auth = new Map(this.snapshot.auth).set(providerId, { type: \"api_key\", source: \"runtime API key\" });\n\t\tconst configuredProviders = new Set(this.snapshot.configuredProviders).add(providerId);\n\t\tconst storedProviders = new Set(this.snapshot.storedProviders).add(providerId);\n\t\tthis.snapshot = {\n\t\t\t...this.snapshot,\n\t\t\tauth,\n\t\t\tconfiguredProviders,\n\t\t\tstoredProviders,\n\t\t\tavailable: this.snapshot.all.filter((model) => configuredProviders.has(model.provider)),\n\t\t};\n\t\tawait this.refresh({ allowNetwork: this.allowModelNetwork });\n\t}\n\n\tasync removeRuntimeApiKey(providerId: string): Promise<void> {\n\t\tthis.credentials.removeRuntimeApiKey(providerId);\n\t\tawait this.refresh({ allowNetwork: this.allowModelNetwork });\n\t}\n\n\t/** lunr: whether network model-catalog fetches are allowed (offline mode disables them). */\n\tisNetworkAllowed(): boolean {\n\t\treturn this.allowModelNetwork;\n\t}\n\n\tlistCredentials(): Promise<readonly CredentialInfo[]> {\n\t\treturn this.credentials.list();\n\t}\n\n\tgetProviderAuthStatus(providerId: string): AuthStatus {\n\t\tif (this.credentials.hasRuntimeApiKey(providerId)) return { configured: true, source: \"runtime\" };\n\t\tif (this.snapshot.storedProviders.has(providerId)) return { configured: true, source: \"stored\" };\n\t\tconst configured = configuredRequestAuthStatus(\n\t\t\tthis.config.getProvider(providerId),\n\t\t\tthis.extensionProviders.get(providerId),\n\t\t);\n\t\tif (configured) return configured;\n\t\tconst check = this.snapshot.auth.get(providerId);\n\t\treturn check ? { configured: true, source: \"environment\", label: check.source } : { configured: false };\n\t}\n\n\tprivate async prepareRequest(\n\t\tmodel: Model<Api>,\n\t\toptions: (StreamOptions & ModelsStreamTransforms) | undefined,\n\t): Promise<{ provider: Provider; model: Model<Api>; options: StreamOptions }> {\n\t\tconst provider = this.models.getProvider(model.provider);\n\t\tif (!provider) throw new ModelsError(\"provider\", `Unknown provider: ${model.provider}`);\n\t\tconst resolution = await this.getAuth(model, { apiKey: options?.apiKey, env: options?.env });\n\t\tif (!resolution) throw new ModelsError(\"auth\", `Provider is not configured: ${model.provider}`);\n\n\t\tconst { transformHeaders, ...providerOptions } = options ?? {};\n\t\tlet headers = mergeHeaders(resolution.auth.headers, providerOptions.headers);\n\t\tif (transformHeaders) headers = await transformHeaders(headers ?? {});\n\t\tconst env =\n\t\t\tresolution.env || providerOptions.env\n\t\t\t\t? { ...(resolution.env ?? {}), ...(providerOptions.env ?? {}) }\n\t\t\t\t: undefined;\n\t\treturn {\n\t\t\tprovider,\n\t\t\tmodel: resolution.auth.baseUrl ? { ...model, baseUrl: resolution.auth.baseUrl } : model,\n\t\t\toptions: {\n\t\t\t\t...providerOptions,\n\t\t\t\tapiKey: providerOptions.apiKey ?? resolution.auth.apiKey,\n\t\t\t\theaders,\n\t\t\t\tenv,\n\t\t\t},\n\t\t};\n\t}\n\n\tstream<TApi extends Api>(\n\t\tmodel: Model<TApi>,\n\t\tcontext: Context,\n\t\toptions?: ModelsApiStreamOptions<TApi>,\n\t): AssistantMessageEventStream {\n\t\treturn lazyStream(model, async () => {\n\t\t\tconst prepared = await this.prepareRequest(\n\t\t\t\tmodel,\n\t\t\t\toptions as (StreamOptions & ModelsStreamTransforms) | undefined,\n\t\t\t);\n\t\t\treturn prepared.provider.stream(\n\t\t\t\tprepared.model as Model<TApi>,\n\t\t\t\tcontext,\n\t\t\t\tprepared.options as ApiStreamOptions<TApi>,\n\t\t\t);\n\t\t});\n\t}\n\n\tcomplete<TApi extends Api>(\n\t\tmodel: Model<TApi>,\n\t\tcontext: Context,\n\t\toptions?: ModelsApiStreamOptions<TApi>,\n\t): Promise<AssistantMessage> {\n\t\treturn this.stream(model, context, options).result();\n\t}\n\n\tstreamSimple(model: Model<Api>, context: Context, options?: ModelsSimpleStreamOptions): AssistantMessageEventStream {\n\t\treturn lazyStream(model, async () => {\n\t\t\tconst prepared = await this.prepareRequest(model, options);\n\t\t\treturn prepared.provider.streamSimple(prepared.model, context, prepared.options as SimpleStreamOptions);\n\t\t});\n\t}\n\n\tcompleteSimple(model: Model<Api>, context: Context, options?: ModelsSimpleStreamOptions): Promise<AssistantMessage> {\n\t\treturn this.streamSimple(model, context, options).result();\n\t}\n\n\tasync login(providerId: string, type: AuthType, interaction: AuthInteraction): Promise<Credential> {\n\t\tconst credential = await this.models.login(providerId, type, interaction);\n\t\tclearPlanUsageCache();\n\t\tawait this.refresh({ allowNetwork: this.allowModelNetwork });\n\t\treturn credential;\n\t}\n\n\tasync logout(providerId: string): Promise<void> {\n\t\tawait this.models.logout(providerId);\n\t\tawait this.modelsStore.delete(providerId);\n\t\tthis.userModels.evictProvider(providerId);\n\t\tthis.overlay.setLive(providerId, []);\n\t\t// Reset credential-dependent compatibility projections before the unconfigured provider is skipped by refresh.\n\t\tthis.recomposeProvider(providerId);\n\t\tclearPlanUsageCache();\n\t\tawait this.refresh({ allowNetwork: this.allowModelNetwork });\n\t}\n\n\t/** Copy a live Grok CLI SuperGrok session into lunR `auth.json`. */\n\tasync importGrokCliXaiAuth(): Promise<Credential | undefined> {\n\t\tconst grok = readGrokCliXaiOAuth({ grokHome: this.grokHome });\n\t\tif (!grok) return undefined;\n\t\tconst stored = await this.credentials.modify(\"xai\", async () => grok);\n\t\tclearPlanUsageCache();\n\t\tawait this.refresh({ allowNetwork: this.allowModelNetwork });\n\t\treturn stored;\n\t}\n\n\tasync reloadConfig(): Promise<void> {\n\t\tthis.config = await ModelConfig.load(this.modelsPath);\n\t\tthis.configureRadiusProviders();\n\t\tthis.rebuildProviders();\n\t\tawait this.refresh({ allowNetwork: this.allowModelNetwork });\n\t}\n\n\tasync refresh(options: ModelsRefreshOptions = {}): Promise<CatalogRefreshResult> {\n\t\tconst refreshOptions = {\n\t\t\t...options,\n\t\t\tallowNetwork: options.allowNetwork ?? this.allowModelNetwork,\n\t\t};\n\t\tconst official = await this.refreshOfficialCatalog(refreshOptions.allowNetwork, refreshOptions.signal);\n\t\tconst live = refreshOptions.allowNetwork\n\t\t\t? await this.refreshLiveCatalogs(\n\t\t\t\t\tofficial ? officialModelKeys(official.catalog) : new Set(),\n\t\t\t\t\trefreshOptions.signal,\n\t\t\t\t)\n\t\t\t: await this.restoreLiveCatalogs();\n\n\t\t// Radius / local-provider / extension refreshModels hooks. Live-list\n\t\t// providers have no refreshModels, so this does not hit pi.dev.\n\t\tconst result = ((await this.models.refresh(refreshOptions)) as ModelsRefreshResult | undefined) ?? {\n\t\t\taborted: refreshOptions.signal?.aborted ?? false,\n\t\t\terrors: new Map(),\n\t\t};\n\t\tconst errors = new Map(result.errors);\n\t\tfor (const entry of live) {\n\t\t\tif (entry.status === \"error\" || entry.status === \"timeout\") {\n\t\t\t\terrors.set(entry.providerId, new Error(entry.error ?? entry.status));\n\t\t\t}\n\t\t}\n\t\tthis.updateModelSnapshot();\n\t\ttry {\n\t\t\tawait this.forceRefreshAvailability();\n\t\t} catch {\n\t\t\t// Availability errors are recorded by forceRefreshAvailability; refreshed models remain usable.\n\t\t}\n\t\tconst catalogResult: CatalogRefreshResult = {\n\t\t\taborted: result.aborted,\n\t\t\terrors,\n\t\t\tofficial,\n\t\t\tlive,\n\t\t\tincomplete: live.flatMap((entry) => entry.incomplete),\n\t\t};\n\t\tthis.lastCatalogRefresh = catalogResult;\n\t\treturn catalogResult;\n\t}\n\n\tgetLastCatalogRefresh(): CatalogRefreshResult {\n\t\treturn this.lastCatalogRefresh;\n\t}\n\n\tpersistUserModels(rows: readonly UserModelEntry[]): void {\n\t\tthis.userModels.upsert(rows);\n\t\tthis.overlay.setUser(this.userModelsForStoredProviders(this.snapshot.storedProviders));\n\t\tthis.updateModelSnapshot();\n\t}\n\n\tprivate async refreshOfficialCatalog(\n\t\tallowNetwork: boolean,\n\t\tsignal?: AbortSignal,\n\t): Promise<(OfficialCatalogLoadResult & { evictedUserRows: number }) | undefined> {\n\t\tconst storedProviders = await this.listStoredProviderIds();\n\t\tconst loaded = await loadOfficialCatalog({\n\t\t\tallowNetwork,\n\t\t\tcachePath: this.officialCachePath,\n\t\t\tsignal,\n\t\t\tproviderIds: [...storedProviders],\n\t\t});\n\t\tconst evicted = this.userModels.evictOfficial(officialModelKeys(loaded.catalog));\n\t\tconst templateByProvider = new Map<string, Model<Api> | undefined>();\n\t\tconst officialModels = loaded.catalog.models.map((entry) => {\n\t\t\tif (!templateByProvider.has(entry.provider)) {\n\t\t\t\ttemplateByProvider.set(entry.provider, this.bakedInModels(entry.provider)[0]);\n\t\t\t}\n\t\t\treturn officialEntryToModel(entry, templateByProvider.get(entry.provider));\n\t\t});\n\t\tthis.overlay.setOfficial(officialModels);\n\t\tthis.overlay.setUser(this.userModelsForStoredProviders(storedProviders));\n\t\treturn { ...loaded, evictedUserRows: evicted.length };\n\t}\n\n\tprivate async restoreLiveCatalogs(): Promise<LiveCatalogResult[]> {\n\t\tconst storedProviders = await this.listStoredProviderIds();\n\t\tconst results: LiveCatalogResult[] = [];\n\t\tfor (const providerId of LIVE_LIST_PROVIDER_IDS) {\n\t\t\tconst stored = storedProviders.has(providerId) ? await this.modelsStore.read(providerId) : undefined;\n\t\t\tif (stored?.models) this.overlay.setLive(providerId, stored.models);\n\t\t\telse this.overlay.setLive(providerId, []);\n\t\t\tresults.push({\n\t\t\t\tproviderId,\n\t\t\t\tstatus: \"skipped\",\n\t\t\t\tdiscoveries: [],\n\t\t\t\tadded: 0,\n\t\t\t\ttotal: stored?.models.length ?? 0,\n\t\t\t\tincomplete: [],\n\t\t\t});\n\t\t}\n\t\treturn results;\n\t}\n\n\tprivate async refreshLiveCatalogs(officialKeys: Set<string>, signal?: AbortSignal): Promise<LiveCatalogResult[]> {\n\t\tconst storedProviders = await this.listStoredProviderIds();\n\t\tconst known = knownModelKeys(\n\t\t\tLIVE_LIST_PROVIDER_IDS.flatMap((id) => this.bakedInModels(id)),\n\t\t\tofficialKeys,\n\t\t\tthis.userModels.keys(),\n\t\t);\n\n\t\tconst results = await Promise.all(\n\t\t\tLIVE_LIST_PROVIDER_IDS.map(async (providerId) => {\n\t\t\t\tif (!storedProviders.has(providerId)) {\n\t\t\t\t\tthis.overlay.setLive(providerId, []);\n\t\t\t\t\treturn {\n\t\t\t\t\t\tproviderId,\n\t\t\t\t\t\tstatus: \"skipped\" as const,\n\t\t\t\t\t\tdiscoveries: [],\n\t\t\t\t\t\tadded: 0,\n\t\t\t\t\t\ttotal: 0,\n\t\t\t\t\t\tincomplete: [],\n\t\t\t\t\t};\n\t\t\t\t}\n\n\t\t\t\tconst stored = await this.modelsStore.read(providerId);\n\t\t\t\tif (stored?.models) this.overlay.setLive(providerId, stored.models);\n\t\t\t\tif (signal?.aborted) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\tproviderId,\n\t\t\t\t\t\tstatus: \"skipped\" as const,\n\t\t\t\t\t\tdiscoveries: [],\n\t\t\t\t\t\tadded: 0,\n\t\t\t\t\t\ttotal: stored?.models.length ?? 0,\n\t\t\t\t\t\tincomplete: [],\n\t\t\t\t\t\terror: \"aborted\",\n\t\t\t\t\t};\n\t\t\t\t}\n\n\t\t\t\tconst bakedIn = this.bakedInModels(providerId);\n\t\t\t\tconst provider = this.models.getProvider(providerId) ?? this.defaultBuiltins.get(providerId);\n\t\t\t\tconst baseUrl = provider?.baseUrl ?? bakedIn[0]?.baseUrl;\n\t\t\t\tif (!baseUrl) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\tproviderId,\n\t\t\t\t\t\tstatus: \"skipped\" as const,\n\t\t\t\t\t\tdiscoveries: [],\n\t\t\t\t\t\tadded: 0,\n\t\t\t\t\t\ttotal: 0,\n\t\t\t\t\t\tincomplete: [],\n\t\t\t\t\t};\n\t\t\t\t}\n\n\t\t\t\tlet credential: Credential | undefined;\n\t\t\t\ttry {\n\t\t\t\t\tcredential = await this.refreshCredentialFor(providerId);\n\t\t\t\t} catch (error) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\tproviderId,\n\t\t\t\t\t\tstatus: \"error\" as const,\n\t\t\t\t\t\tdiscoveries: [],\n\t\t\t\t\t\tadded: 0,\n\t\t\t\t\t\ttotal: stored?.models.length ?? 0,\n\t\t\t\t\t\tincomplete: [],\n\t\t\t\t\t\terror: error instanceof Error ? error.message : String(error),\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t\tif (!credential) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\tproviderId,\n\t\t\t\t\t\tstatus: \"skipped\" as const,\n\t\t\t\t\t\tdiscoveries: [],\n\t\t\t\t\t\tadded: 0,\n\t\t\t\t\t\ttotal: stored?.models.length ?? 0,\n\t\t\t\t\t\tincomplete: [],\n\t\t\t\t\t};\n\t\t\t\t}\n\n\t\t\t\tconst result = await fetchLiveProviderModels({\n\t\t\t\t\tproviderId,\n\t\t\t\t\tbaseUrl,\n\t\t\t\t\tbakedIn,\n\t\t\t\t\tcredential,\n\t\t\t\t\tknownKeys: known,\n\t\t\t\t\tsignal,\n\t\t\t\t});\n\t\t\t\tif (result.status === \"ok\") {\n\t\t\t\t\tconst overlayModels = buildLiveOverlay(bakedIn, result.discoveries);\n\t\t\t\t\tthis.overlay.setLive(providerId, overlayModels);\n\t\t\t\t\ttry {\n\t\t\t\t\t\tawait this.modelsStore.write(providerId, { models: overlayModels, checkedAt: Date.now() });\n\t\t\t\t\t} catch {\n\t\t\t\t\t\t// Store write is best-effort.\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn result;\n\t\t\t}),\n\t\t);\n\t\treturn results;\n\t}\n\n\tprivate async refreshCredentialFor(providerId: string): Promise<Credential | undefined> {\n\t\tconst stored = await this.credentials.read(providerId);\n\t\t// Same lock + persist path as chat. Returning stored OAuth here skips\n\t\t// refresh and sends expired SuperGrok tokens to GET /models (HTTP 403).\n\t\tconst resolved = await this.getAuth(providerId);\n\t\tif (resolved?.auth.apiKey) return { type: \"api_key\", key: resolved.auth.apiKey, env: resolved.env };\n\t\tif (stored?.type === \"oauth\" && stored.access) return stored;\n\t\tif (stored?.type === \"api_key\" && stored.key) return stored;\n\t\treturn undefined;\n\t}\n\n\tregisterProvider(providerId: string, config: ProviderConfigInput): void {\n\t\t// Validate the incoming registration on its own, like the legacy registry:\n\t\t// a broken re-registration must throw without touching the stored config.\n\t\tvalidateExtensionProvider(providerId, this.builtins.get(providerId), this.config.getProvider(providerId), config);\n\t\t// Re-registration merges defined values over the previous registration and\n\t\t// preserves undefined ones, matching the legacy ModelRegistry contract.\n\t\tconst previous = this.extensionProviders.get(providerId);\n\t\tconst effective: ProviderConfigInput = { ...previous };\n\t\tfor (const [key, value] of Object.entries(config)) {\n\t\t\tif (value !== undefined) (effective as Record<string, unknown>)[key] = value;\n\t\t}\n\t\tthis.extensionProviders.set(providerId, effective);\n\t\tthis.recomposeProvider(providerId);\n\t\tthis.updateModelSnapshot();\n\t\tif (\n\t\t\tthis.snapshot.storedProviders.has(providerId) ||\n\t\t\tconfiguredRequestAuthStatus(this.config.getProvider(providerId), effective)?.configured\n\t\t) {\n\t\t\tconst configuredProviders = new Set(this.snapshot.configuredProviders).add(providerId);\n\t\t\tconst auth = new Map(this.snapshot.auth);\n\t\t\t// Provisional entry until the async refresh lands; never clobber a real check result.\n\t\t\tif (!auth.get(providerId)) {\n\t\t\t\tauth.set(providerId, {\n\t\t\t\t\ttype: effective.oauth && !effective.apiKey ? \"oauth\" : \"api_key\",\n\t\t\t\t\tsource: \"configured provider\",\n\t\t\t\t});\n\t\t\t}\n\t\t\tthis.snapshot = {\n\t\t\t\t...this.snapshot,\n\t\t\t\tauth,\n\t\t\t\tconfiguredProviders,\n\t\t\t\tavailable: this.snapshot.all.filter((model) => configuredProviders.has(model.provider)),\n\t\t\t};\n\t\t}\n\t\tvoid this.refresh({ allowNetwork: false });\n\t}\n\n\tunregisterProvider(providerId: string): void {\n\t\tthis.extensionProviders.delete(providerId);\n\t\tthis.recomposeProvider(providerId);\n\t\tthis.updateModelSnapshot();\n\t\tvoid this.refresh({ allowNetwork: false });\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"model-runtime.d.ts","sourceRoot":"","sources":["../../src/core/model-runtime.ts"],"names":[],"mappings":"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","sourcesContent":["import { dirname, join } from \"node:path\";\nimport {\n\ttype Api,\n\ttype ApiStreamOptions,\n\ttype AssistantMessage,\n\ttype AssistantMessageEventStream,\n\ttype AuthCheck,\n\ttype AuthInteraction,\n\ttype AuthResult,\n\ttype AuthType,\n\ttype Context,\n\ttype Credential,\n\ttype CredentialInfo,\n\ttype CredentialStore,\n\tcreateModels,\n\tlazyStream,\n\ttype Model,\n\ttype Models,\n\ttype ModelsApiStreamOptions,\n\tModelsError,\n\ttype ModelsRefreshOptions,\n\ttype ModelsRefreshResult,\n\ttype ModelsSimpleStreamOptions,\n\ttype ModelsStore,\n\ttype ModelsStreamTransforms,\n\ttype MutableModels,\n\ttype Provider,\n\ttype ProviderHeaders,\n\ttype SimpleStreamOptions,\n\ttype StreamOptions,\n} from \"@ashx-j/lunr-ai\";\nimport * as builtinProviderCatalog from \"@ashx-j/lunr-ai/providers/all\";\nimport { getAgentDir } from \"../config.ts\";\nimport { AuthStorage as DefaultAuthStorage } from \"./auth-storage.ts\";\nimport { CatalogOverlaySource, knownModelKeys, withCatalogOverlay } from \"./catalog-merge.ts\";\nimport { readGrokCliXaiOAuth, wrapXaiGrokCliCredentials } from \"./grok-cli-auth.ts\";\nimport {\n\tbuildLiveOverlay,\n\tfetchLiveProviderModels,\n\ttype IncompleteLiveModel,\n\tLIVE_LIST_PROVIDER_IDS,\n\ttype LiveCatalogResult,\n} from \"./live-catalog.ts\";\nimport { ModelConfig } from \"./model-config.ts\";\nimport { FileModelsStore, InMemoryCodingAgentModelsStore } from \"./models-store.ts\";\nimport {\n\tloadOfficialCatalog,\n\ttype OfficialCatalogLoadResult,\n\tofficialCatalogCachePath,\n\tofficialEntryToModel,\n\tofficialModelKeys,\n\tpreferOfficialCatalog,\n} from \"./official-catalog.ts\";\nimport {\n\ttype AuthStatus,\n\ttype CompatibilityRequestConfig,\n\tcomposeModelProvider,\n\tconfiguredRequestAuthStatus,\n\ttype ProviderConfigInput,\n\tresolveCompatibilityRequestConfig,\n\tresolveConfiguredModelHeaders,\n\tvalidateExtensionProvider,\n} from \"./provider-composer.ts\";\nimport { RuntimeCredentials } from \"./runtime-credentials.ts\";\nimport { SubscriptionManager } from \"./subscriptions.ts\";\nimport { clearPlanUsageCache } from \"./usage-service.ts\";\nimport { type UserModelEntry, UserModelsStore, userEntryToModel, userModelsPath } from \"./user-models.ts\";\n\ninterface ModelRuntimeSnapshot {\n\tall: readonly Model<Api>[];\n\tavailable: readonly Model<Api>[];\n\tconfiguredProviders: ReadonlySet<string>;\n\tstoredProviders: ReadonlySet<string>;\n\tauth: ReadonlyMap<string, AuthCheck | undefined>;\n}\n\nexport interface CreateModelRuntimeOptions {\n\t/** Credential storage. Defaults to the file at authPath. */\n\tcredentials?: CredentialStore;\n\tauthPath?: string;\n\tmodelsPath?: string | null;\n\tmodelsStore?: ModelsStore;\n\tmodelsStorePath?: string;\n\tallowModelNetwork?: boolean;\n\t/** Applied by callers of `refresh()`, not by `create()` (startup is cache-only). */\n\tmodelRefreshTimeoutMs?: number;\n\t/** Unused. Kept so older callers compiling against this options bag still typecheck. */\n\tcatalogBaseUrl?: string;\n\t/** lunr: subscription-key pool. Defaults to subscriptions.json next to authPath (in-memory for custom stores). */\n\tsubscriptions?: SubscriptionManager;\n\t/**\n\t * Grok CLI home (`~/.grok`) used as a SuperGrok OAuth fallback.\n\t * `null` disables the fallback. Custom `credentials` default to disabled\n\t * unless this is an explicit path.\n\t */\n\tgrokHome?: string | null;\n}\n\nexport interface CatalogRefreshResult extends ModelsRefreshResult {\n\tofficial?: OfficialCatalogLoadResult & { evictedUserRows: number };\n\tlive: LiveCatalogResult[];\n\tincomplete: IncompleteLiveModel[];\n}\n\nexport interface ModelRuntimeAuthOverrides {\n\tapiKey?: string;\n\tenv?: Record<string, string>;\n}\n\nfunction mergeHeaders(\n\tbase: ProviderHeaders | undefined,\n\toverride: ProviderHeaders | undefined,\n): ProviderHeaders | undefined {\n\tif (!base && !override) return undefined;\n\tconst merged = { ...base };\n\tfor (const [name, value] of Object.entries(override ?? {})) {\n\t\tconst lowerName = name.toLowerCase();\n\t\tfor (const existingName of Object.keys(merged)) {\n\t\t\tif (existingName.toLowerCase() === lowerName) delete merged[existingName];\n\t\t}\n\t\tmerged[name] = value;\n\t}\n\treturn merged;\n}\n\n/** Configured pi-ai Models collection used by coding-agent and SDK consumers. */\nexport class ModelRuntime implements Models {\n\tprivate readonly models: MutableModels;\n\tprivate readonly credentials: RuntimeCredentials;\n\t/** lunr: per-provider subscription-key pools; rotation mirrors the active key into the credential store. */\n\treadonly subscriptionManager: SubscriptionManager;\n\tprivate readonly defaultBuiltins: ReadonlyMap<string, Provider>;\n\tprivate readonly builtins = new Map<string, Provider>();\n\tprivate readonly extensionProviders = new Map<string, ProviderConfigInput>();\n\tprivate readonly compositionErrors = new Map<string, string>();\n\tprivate readonly modelsPath: string | undefined;\n\tprivate readonly allowModelNetwork: boolean;\n\tprivate config: ModelConfig;\n\tprivate snapshot: ModelRuntimeSnapshot = {\n\t\tall: [],\n\t\tavailable: [],\n\t\tconfiguredProviders: new Set(),\n\t\tstoredProviders: new Set(),\n\t\tauth: new Map(),\n\t};\n\tprivate availabilityRefresh: Promise<void> | undefined;\n\tprivate availabilityError: string | undefined;\n\tprivate readonly modelsStore: ModelsStore;\n\tprivate readonly overlay = new CatalogOverlaySource();\n\tprivate readonly userModels: UserModelsStore;\n\tprivate readonly officialCachePath: string | undefined;\n\tprivate lastCatalogRefresh: CatalogRefreshResult = { aborted: false, errors: new Map(), live: [], incomplete: [] };\n\tprivate lastOfficial: OfficialCatalogLoadResult | undefined;\n\tprivate catalogRefresh: Promise<CatalogRefreshResult> | undefined;\n\tprivate catalogRefreshAllowsNetwork = false;\n\tprivate readonly grokHome: string | undefined;\n\n\tprivate constructor(\n\t\tcredentials: RuntimeCredentials,\n\t\tconfig: ModelConfig,\n\t\tmodelsPath: string | undefined,\n\t\tmodelsStore: ModelsStore,\n\t\tproviders: readonly Provider[],\n\t\tallowModelNetwork: boolean,\n\t\tsubscriptionManager: SubscriptionManager,\n\t\tcatalogDir: string | undefined,\n\t\tgrokHome: string | undefined,\n\t) {\n\t\tthis.credentials = credentials;\n\t\tthis.config = config;\n\t\tthis.modelsPath = modelsPath;\n\t\tthis.modelsStore = modelsStore;\n\t\tthis.allowModelNetwork = allowModelNetwork;\n\t\tthis.subscriptionManager = subscriptionManager;\n\t\tthis.userModels = new UserModelsStore(catalogDir ? userModelsPath(catalogDir) : undefined);\n\t\tthis.officialCachePath = catalogDir ? officialCatalogCachePath(catalogDir) : undefined;\n\t\tthis.grokHome = grokHome;\n\t\tthis.defaultBuiltins = new Map(providers.map((provider) => [provider.id, provider]));\n\t\tfor (const [providerId, provider] of this.defaultBuiltins) this.builtins.set(providerId, provider);\n\t\tthis.models = createModels({ credentials, modelsStore });\n\t\tthis.rebuildProviders();\n\t}\n\n\tstatic async create(options: CreateModelRuntimeOptions = {}): Promise<ModelRuntime> {\n\t\t// lunr: keep the underlying store — the SubscriptionManager mirrors rotated\n\t\t// keys into it directly, NOT through the RuntimeCredentials overlay (a runtime\n\t\t// --api-key override would shadow the mirror there).\n\t\tconst rawStore = options.credentials ?? DefaultAuthStorage.create(options.authPath);\n\t\tconst grokHomeDisabled =\n\t\t\toptions.grokHome === null || (options.grokHome === undefined && options.credentials !== undefined);\n\t\tconst grokHome = grokHomeDisabled ? undefined : (options.grokHome ?? undefined);\n\t\tconst baseStore = grokHomeDisabled ? rawStore : wrapXaiGrokCliCredentials(rawStore, { grokHome });\n\t\tconst credentials = new RuntimeCredentials(baseStore);\n\t\t// Pool persistence: subscriptions.json next to auth.json. A custom credential\n\t\t// store without an authPath (tests, SDK embeddings) gets a process-local pool\n\t\t// so nothing leaks into the real agent dir.\n\t\tconst subscriptionManager =\n\t\t\toptions.subscriptions ??\n\t\t\t(options.credentials && !options.authPath\n\t\t\t\t? SubscriptionManager.inMemory(baseStore)\n\t\t\t\t: SubscriptionManager.create(\n\t\t\t\t\t\tbaseStore,\n\t\t\t\t\t\toptions.authPath ? join(dirname(options.authPath), \"subscriptions.json\") : undefined,\n\t\t\t\t\t));\n\t\tconst modelsPath =\n\t\t\toptions.modelsPath === null ? undefined : (options.modelsPath ?? join(getAgentDir(), \"models.json\"));\n\t\tconst config = await ModelConfig.load(modelsPath);\n\t\tconst modelsStore =\n\t\t\toptions.modelsStore ??\n\t\t\t(modelsPath\n\t\t\t\t? new FileModelsStore(options.modelsStorePath ?? join(dirname(modelsPath), \"models-store.json\"))\n\t\t\t\t: new InMemoryCodingAgentModelsStore());\n\t\tconst providers = builtinProviderCatalog.builtinProviders();\n\t\tconst catalogDir = modelsPath ? dirname(modelsPath) : options.authPath ? dirname(options.authPath) : undefined;\n\t\tconst runtime = new ModelRuntime(\n\t\t\tcredentials,\n\t\t\tconfig,\n\t\t\tmodelsPath,\n\t\t\tmodelsStore,\n\t\t\tproviders,\n\t\t\toptions.allowModelNetwork ?? process.env.PI_OFFLINE === undefined,\n\t\t\tsubscriptionManager,\n\t\t\tcatalogDir,\n\t\t\tgrokHome,\n\t\t);\n\t\truntime.configureRadiusProviders();\n\t\truntime.rebuildProviders();\n\t\t// lunr: create() must not wait on GitHub / provider /v1/models / a dead NIC.\n\t\t// Disk cache + baked-in + bundled official overlay are enough to start;\n\t\t// /refresh and the model picker still do a live refresh.\n\t\tawait runtime.refresh({ allowNetwork: false });\n\t\treturn runtime;\n\t}\n\n\tprivate configureRadiusProviders(): void {\n\t\tthis.builtins.clear();\n\t\tfor (const [providerId, provider] of this.defaultBuiltins) this.builtins.set(providerId, provider);\n\t\tfor (const providerId of this.config.getProviderIds()) {\n\t\t\tconst config = this.config.getProvider(providerId);\n\t\t\tif (config?.oauth !== \"radius\" || !config.baseUrl) continue;\n\t\t\tthis.builtins.set(\n\t\t\t\tproviderId,\n\t\t\t\tbuiltinProviderCatalog.radiusProvider({\n\t\t\t\t\tid: providerId,\n\t\t\t\t\tname: config.name ?? providerId,\n\t\t\t\t\tgateway: config.baseUrl.replace(/\\/v1\\/?$/u, \"\"),\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\t}\n\n\tprivate providerIds(): Set<string> {\n\t\treturn new Set([...this.builtins.keys(), ...this.config.getProviderIds(), ...this.extensionProviders.keys()]);\n\t}\n\n\tprivate recomposeProvider(providerId: string): void {\n\t\tconst base = this.builtins.get(providerId);\n\t\tconst extension = this.extensionProviders.get(providerId);\n\t\tif (!base && !this.config.getProvider(providerId) && !extension) {\n\t\t\tthis.models.deleteProvider(providerId);\n\t\t\tthis.compositionErrors.delete(providerId);\n\t\t\treturn;\n\t\t}\n\t\tif (base && !this.config.getProvider(providerId) && !extension) {\n\t\t\t// No models.json/extension overlays: keep builtin auth/stream exact, then apply catalog layers.\n\t\t\tthis.models.setProvider(this.withLocalCatalog(base));\n\t\t\tthis.compositionErrors.delete(providerId);\n\t\t\treturn;\n\t\t}\n\t\ttry {\n\t\t\tthis.models.setProvider(this.withLocalCatalog(composeModelProvider(providerId, base, this.config, extension)));\n\t\t\tthis.compositionErrors.delete(providerId);\n\t\t} catch (error) {\n\t\t\tthis.compositionErrors.set(providerId, error instanceof Error ? error.message : String(error));\n\t\t\tif (base) this.models.setProvider(this.withLocalCatalog(base));\n\t\t\telse this.models.deleteProvider(providerId);\n\t\t}\n\t}\n\n\tprivate withLocalCatalog(provider: Provider): Provider {\n\t\treturn withCatalogOverlay(provider, this.overlay);\n\t}\n\n\tprivate bakedInModels(providerId: string): Model<Api>[] {\n\t\treturn [...(this.defaultBuiltins.get(providerId)?.getModels() ?? [])];\n\t}\n\n\tprivate rebuildProviders(): void {\n\t\tthis.models.clearProviders();\n\t\tthis.compositionErrors.clear();\n\t\tfor (const providerId of this.providerIds()) this.recomposeProvider(providerId);\n\t\tthis.updateModelSnapshot();\n\t}\n\n\tprivate updateModelSnapshot(): void {\n\t\tconst all = [...this.models.getModels()];\n\t\tthis.snapshot = {\n\t\t\t...this.snapshot,\n\t\t\tall,\n\t\t\tavailable: all.filter((model) => this.snapshot.configuredProviders.has(model.provider)),\n\t\t};\n\t}\n\n\tprivate async listStoredProviderIds(): Promise<Set<string>> {\n\t\tconst credentials = await this.credentials.list();\n\t\treturn new Set(credentials.map((entry) => entry.providerId));\n\t}\n\n\t/** `/model` lists stored creds + models.json/extension keys, never ambient envApiKeyAuth. */\n\tprivate configuredProviderIds(storedProviders: ReadonlySet<string>): Set<string> {\n\t\tconst configured = new Set(storedProviders);\n\t\tfor (const provider of this.models.getProviders()) {\n\t\t\tif (\n\t\t\t\tconfiguredRequestAuthStatus(this.config.getProvider(provider.id), this.extensionProviders.get(provider.id))\n\t\t\t\t\t?.configured\n\t\t\t) {\n\t\t\t\tconfigured.add(provider.id);\n\t\t\t}\n\t\t}\n\t\treturn configured;\n\t}\n\n\tprivate userModelsForStoredProviders(storedProviders: ReadonlySet<string>): Model<Api>[] {\n\t\treturn this.userModels\n\t\t\t.list()\n\t\t\t.filter((row) => storedProviders.has(row.provider))\n\t\t\t.map(userEntryToModel);\n\t}\n\n\tprivate async runAvailabilityRefresh(): Promise<void> {\n\t\tconst providers = this.models.getProviders();\n\t\tconst [available, checks, storedProviders] = await Promise.all([\n\t\t\tthis.models.getAvailable(),\n\t\t\tPromise.all(\n\t\t\t\tproviders.map(\n\t\t\t\t\tasync (provider): Promise<[string, AuthCheck | undefined]> => [\n\t\t\t\t\t\tprovider.id,\n\t\t\t\t\t\tawait this.models.checkAuth(provider.id),\n\t\t\t\t\t],\n\t\t\t\t),\n\t\t\t),\n\t\t\tthis.listStoredProviderIds(),\n\t\t]);\n\t\tconst auth = new Map(checks);\n\t\tconst configuredProviders = this.configuredProviderIds(storedProviders);\n\t\tthis.snapshot = {\n\t\t\tall: [...this.models.getModels()],\n\t\t\tavailable: available.filter((model) => configuredProviders.has(model.provider)),\n\t\t\tconfiguredProviders,\n\t\t\tstoredProviders,\n\t\t\tauth,\n\t\t};\n\t\tthis.availabilityError = undefined;\n\t}\n\n\tprivate queueAvailabilityRefresh(after: Promise<void> | undefined): Promise<void> {\n\t\tconst refresh = (after ?? Promise.resolve()).catch(() => {}).then(() => this.runAvailabilityRefresh());\n\t\tconst recorded = refresh.catch((error) => {\n\t\t\tthis.availabilityError = error instanceof Error ? error.message : String(error);\n\t\t\tthrow error;\n\t\t});\n\t\tconst tracked = recorded.finally(() => {\n\t\t\tif (this.availabilityRefresh === tracked) this.availabilityRefresh = undefined;\n\t\t});\n\t\tthis.availabilityRefresh = tracked;\n\t\treturn tracked;\n\t}\n\n\t/** Coalesce concurrent readers onto the pending refresh. */\n\tprivate refreshAvailability(): Promise<void> {\n\t\treturn this.availabilityRefresh ?? this.queueAvailabilityRefresh(undefined);\n\t}\n\n\t/** Mutations must not observe an in-flight refresh started before them. */\n\tprivate forceRefreshAvailability(): Promise<void> {\n\t\treturn this.queueAvailabilityRefresh(this.availabilityRefresh);\n\t}\n\n\tgetProviders(): readonly Provider[] {\n\t\treturn this.models.getProviders();\n\t}\n\n\tgetProvider(providerId: string): Provider | undefined {\n\t\treturn this.models.getProvider(providerId);\n\t}\n\n\tgetModels(providerId?: string): readonly Model<Api>[] {\n\t\treturn this.models.getModels(providerId);\n\t}\n\n\tgetModel(providerId: string, modelId: string): Model<Api> | undefined {\n\t\treturn this.models.getModel(providerId, modelId);\n\t}\n\n\tasync checkAuth(providerId: string): Promise<AuthCheck | undefined> {\n\t\treturn this.models.checkAuth(providerId);\n\t}\n\n\tasync getAvailable(providerId?: string): Promise<readonly Model<Api>[]> {\n\t\tif (providerId) {\n\t\t\tif (this.availabilityRefresh) {\n\t\t\t\tawait this.availabilityRefresh;\n\t\t\t\treturn this.snapshot.available.filter((model) => model.provider === providerId);\n\t\t\t}\n\t\t\ttry {\n\t\t\t\treturn await this.models.getAvailable(providerId);\n\t\t\t} catch (error) {\n\t\t\t\tthis.availabilityError = error instanceof Error ? error.message : String(error);\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t}\n\t\tawait this.refreshAvailability();\n\t\treturn this.snapshot.available;\n\t}\n\n\tgetAvailableSnapshot(): readonly Model<Api>[] {\n\t\treturn this.snapshot.available;\n\t}\n\n\tgetError(): string | undefined {\n\t\tconst errors: string[] = [];\n\t\tconst configError = this.config.getError();\n\t\tif (configError) errors.push(configError);\n\t\tfor (const [providerId, error] of this.compositionErrors) {\n\t\t\terrors.push(`Provider \"${providerId}\": ${error}`);\n\t\t}\n\t\tif (this.availabilityError) errors.push(`Availability refresh: ${this.availabilityError}`);\n\t\treturn errors.length > 0 ? errors.join(\"\\n\\n\") : undefined;\n\t}\n\n\tgetRegisteredProviderConfig(providerId: string): ProviderConfigInput | undefined {\n\t\treturn this.extensionProviders.get(providerId);\n\t}\n\n\tgetRegisteredProviderIds(): readonly string[] {\n\t\treturn [...this.extensionProviders.keys()];\n\t}\n\n\t/** @internal Compatibility fallback for ModelRegistry when provider auth is unconfigured. */\n\tgetCompatibilityRequestConfig(model: Model<Api>): CompatibilityRequestConfig {\n\t\treturn resolveCompatibilityRequestConfig(\n\t\t\tmodel,\n\t\t\tthis.config.getProvider(model.provider),\n\t\t\tthis.extensionProviders.get(model.provider),\n\t\t);\n\t}\n\n\tisUsingOAuth(providerId: string): boolean {\n\t\treturn this.snapshot.auth.get(providerId)?.type === \"oauth\";\n\t}\n\n\thasConfiguredAuth(providerId: string): boolean {\n\t\treturn this.snapshot.configuredProviders.has(providerId);\n\t}\n\n\tgetAuth(providerId: string, overrides?: ModelRuntimeAuthOverrides): Promise<AuthResult | undefined>;\n\tgetAuth(model: Model<Api>, overrides?: ModelRuntimeAuthOverrides): Promise<AuthResult | undefined>;\n\tasync getAuth(\n\t\tproviderOrModel: string | Model<Api>,\n\t\toverrides: ModelRuntimeAuthOverrides = {},\n\t): Promise<AuthResult | undefined> {\n\t\tif (typeof providerOrModel === \"string\") return this.models.getAuth(providerOrModel, overrides);\n\t\tconst resolution = await this.models.getAuth(providerOrModel, overrides);\n\t\tif (!resolution) return undefined;\n\t\tconst configuredHeaders = resolveConfiguredModelHeaders(\n\t\t\tproviderOrModel,\n\t\t\tthis.config.getProvider(providerOrModel.provider),\n\t\t\tthis.extensionProviders.get(providerOrModel.provider),\n\t\t\t{ ...(resolution.env ?? {}), ...(overrides.env ?? {}) },\n\t\t);\n\t\treturn {\n\t\t\t...resolution,\n\t\t\tauth: {\n\t\t\t\t...resolution.auth,\n\t\t\t\theaders: mergeHeaders(resolution.auth.headers, configuredHeaders),\n\t\t\t},\n\t\t};\n\t}\n\n\t/** lunr: whether a runtime --api-key override is active for the provider (it shadows auth.json). */\n\thasRuntimeApiKey(providerId: string): boolean {\n\t\treturn this.credentials.hasRuntimeApiKey(providerId);\n\t}\n\n\tasync setRuntimeApiKey(\n\t\tproviderId: string,\n\t\tapiKey: string,\n\t\toptions?: { allowNetwork?: boolean },\n\t): Promise<void> {\n\t\tthis.credentials.setRuntimeApiKey(providerId, apiKey);\n\t\tconst auth = new Map(this.snapshot.auth).set(providerId, { type: \"api_key\", source: \"runtime API key\" });\n\t\tconst configuredProviders = new Set(this.snapshot.configuredProviders).add(providerId);\n\t\tconst storedProviders = new Set(this.snapshot.storedProviders).add(providerId);\n\t\tthis.snapshot = {\n\t\t\t...this.snapshot,\n\t\t\tauth,\n\t\t\tconfiguredProviders,\n\t\t\tstoredProviders,\n\t\t\tavailable: this.snapshot.all.filter((model) => configuredProviders.has(model.provider)),\n\t\t};\n\t\tawait this.refresh({ allowNetwork: options?.allowNetwork ?? this.allowModelNetwork });\n\t}\n\n\tasync removeRuntimeApiKey(providerId: string): Promise<void> {\n\t\tthis.credentials.removeRuntimeApiKey(providerId);\n\t\tawait this.refresh({ allowNetwork: this.allowModelNetwork });\n\t}\n\n\t/** lunr: whether network model-catalog fetches are allowed (offline mode disables them). */\n\tisNetworkAllowed(): boolean {\n\t\treturn this.allowModelNetwork;\n\t}\n\n\tlistCredentials(): Promise<readonly CredentialInfo[]> {\n\t\treturn this.credentials.list();\n\t}\n\n\tgetProviderAuthStatus(providerId: string): AuthStatus {\n\t\tif (this.credentials.hasRuntimeApiKey(providerId)) return { configured: true, source: \"runtime\" };\n\t\tif (this.snapshot.storedProviders.has(providerId)) return { configured: true, source: \"stored\" };\n\t\tconst configured = configuredRequestAuthStatus(\n\t\t\tthis.config.getProvider(providerId),\n\t\t\tthis.extensionProviders.get(providerId),\n\t\t);\n\t\tif (configured) return configured;\n\t\tconst check = this.snapshot.auth.get(providerId);\n\t\treturn check ? { configured: true, source: \"environment\", label: check.source } : { configured: false };\n\t}\n\n\tprivate async prepareRequest(\n\t\tmodel: Model<Api>,\n\t\toptions: (StreamOptions & ModelsStreamTransforms) | undefined,\n\t): Promise<{ provider: Provider; model: Model<Api>; options: StreamOptions }> {\n\t\tconst provider = this.models.getProvider(model.provider);\n\t\tif (!provider) throw new ModelsError(\"provider\", `Unknown provider: ${model.provider}`);\n\t\tconst resolution = await this.getAuth(model, { apiKey: options?.apiKey, env: options?.env });\n\t\tif (!resolution) throw new ModelsError(\"auth\", `Provider is not configured: ${model.provider}`);\n\n\t\tconst { transformHeaders, ...providerOptions } = options ?? {};\n\t\tlet headers = mergeHeaders(resolution.auth.headers, providerOptions.headers);\n\t\tif (transformHeaders) headers = await transformHeaders(headers ?? {});\n\t\tconst env =\n\t\t\tresolution.env || providerOptions.env\n\t\t\t\t? { ...(resolution.env ?? {}), ...(providerOptions.env ?? {}) }\n\t\t\t\t: undefined;\n\t\treturn {\n\t\t\tprovider,\n\t\t\tmodel: resolution.auth.baseUrl ? { ...model, baseUrl: resolution.auth.baseUrl } : model,\n\t\t\toptions: {\n\t\t\t\t...providerOptions,\n\t\t\t\tapiKey: providerOptions.apiKey ?? resolution.auth.apiKey,\n\t\t\t\theaders,\n\t\t\t\tenv,\n\t\t\t},\n\t\t};\n\t}\n\n\tstream<TApi extends Api>(\n\t\tmodel: Model<TApi>,\n\t\tcontext: Context,\n\t\toptions?: ModelsApiStreamOptions<TApi>,\n\t): AssistantMessageEventStream {\n\t\treturn lazyStream(model, async () => {\n\t\t\tconst prepared = await this.prepareRequest(\n\t\t\t\tmodel,\n\t\t\t\toptions as (StreamOptions & ModelsStreamTransforms) | undefined,\n\t\t\t);\n\t\t\treturn prepared.provider.stream(\n\t\t\t\tprepared.model as Model<TApi>,\n\t\t\t\tcontext,\n\t\t\t\tprepared.options as ApiStreamOptions<TApi>,\n\t\t\t);\n\t\t});\n\t}\n\n\tcomplete<TApi extends Api>(\n\t\tmodel: Model<TApi>,\n\t\tcontext: Context,\n\t\toptions?: ModelsApiStreamOptions<TApi>,\n\t): Promise<AssistantMessage> {\n\t\treturn this.stream(model, context, options).result();\n\t}\n\n\tstreamSimple(model: Model<Api>, context: Context, options?: ModelsSimpleStreamOptions): AssistantMessageEventStream {\n\t\treturn lazyStream(model, async () => {\n\t\t\tconst prepared = await this.prepareRequest(model, options);\n\t\t\treturn prepared.provider.streamSimple(prepared.model, context, prepared.options as SimpleStreamOptions);\n\t\t});\n\t}\n\n\tcompleteSimple(model: Model<Api>, context: Context, options?: ModelsSimpleStreamOptions): Promise<AssistantMessage> {\n\t\treturn this.streamSimple(model, context, options).result();\n\t}\n\n\tasync login(providerId: string, type: AuthType, interaction: AuthInteraction): Promise<Credential> {\n\t\tconst credential = await this.models.login(providerId, type, interaction);\n\t\tclearPlanUsageCache();\n\t\tawait this.refresh({ allowNetwork: this.allowModelNetwork });\n\t\treturn credential;\n\t}\n\n\tasync logout(providerId: string): Promise<void> {\n\t\tawait this.models.logout(providerId);\n\t\tawait this.modelsStore.delete(providerId);\n\t\tthis.userModels.evictProvider(providerId);\n\t\tthis.overlay.setLive(providerId, []);\n\t\t// Reset credential-dependent compatibility projections before the unconfigured provider is skipped by refresh.\n\t\tthis.recomposeProvider(providerId);\n\t\tclearPlanUsageCache();\n\t\tawait this.refresh({ allowNetwork: this.allowModelNetwork });\n\t}\n\n\t/** Copy a live Grok CLI SuperGrok session into lunR `auth.json`. */\n\tasync importGrokCliXaiAuth(): Promise<Credential | undefined> {\n\t\tconst grok = readGrokCliXaiOAuth({ grokHome: this.grokHome });\n\t\tif (!grok) return undefined;\n\t\tconst stored = await this.credentials.modify(\"xai\", async () => grok);\n\t\tclearPlanUsageCache();\n\t\tawait this.refresh({ allowNetwork: this.allowModelNetwork });\n\t\treturn stored;\n\t}\n\n\tasync reloadConfig(): Promise<void> {\n\t\tthis.config = await ModelConfig.load(this.modelsPath);\n\t\tthis.configureRadiusProviders();\n\t\tthis.rebuildProviders();\n\t\tawait this.refresh({ allowNetwork: this.allowModelNetwork });\n\t}\n\n\tasync refresh(options: ModelsRefreshOptions = {}): Promise<CatalogRefreshResult> {\n\t\tconst allowNetwork = options.allowNetwork ?? this.allowModelNetwork;\n\t\tif (this.catalogRefresh) {\n\t\t\t// Join an in-flight refresh that is at least as capable. A cache-only\n\t\t\t// flight must not swallow a later network refresh.\n\t\t\tif (this.catalogRefreshAllowsNetwork || !allowNetwork) {\n\t\t\t\treturn this.catalogRefresh;\n\t\t\t}\n\t\t\tawait this.catalogRefresh.catch(() => {});\n\t\t\treturn this.refresh({ ...options, allowNetwork });\n\t\t}\n\n\t\tconst run = this.executeCatalogRefresh({ ...options, allowNetwork }).finally(() => {\n\t\t\tif (this.catalogRefresh === run) {\n\t\t\t\tthis.catalogRefresh = undefined;\n\t\t\t\tthis.catalogRefreshAllowsNetwork = false;\n\t\t\t}\n\t\t});\n\t\tthis.catalogRefresh = run;\n\t\tthis.catalogRefreshAllowsNetwork = allowNetwork;\n\t\treturn run;\n\t}\n\n\tprivate async executeCatalogRefresh(\n\t\trefreshOptions: ModelsRefreshOptions & { allowNetwork: boolean },\n\t): Promise<CatalogRefreshResult> {\n\t\tconst official = await this.refreshOfficialCatalog(refreshOptions.allowNetwork, refreshOptions.signal);\n\t\tconst live = refreshOptions.allowNetwork\n\t\t\t? await this.refreshLiveCatalogs(\n\t\t\t\t\tofficial ? officialModelKeys(official.catalog) : new Set(),\n\t\t\t\t\trefreshOptions.signal,\n\t\t\t\t)\n\t\t\t: await this.restoreLiveCatalogs();\n\n\t\t// Radius / local-provider / extension refreshModels hooks. Live-list\n\t\t// providers have no refreshModels, so this does not hit pi.dev.\n\t\tconst result = ((await this.models.refresh(refreshOptions)) as ModelsRefreshResult | undefined) ?? {\n\t\t\taborted: refreshOptions.signal?.aborted ?? false,\n\t\t\terrors: new Map(),\n\t\t};\n\t\tconst errors = new Map(result.errors);\n\t\tfor (const entry of live) {\n\t\t\tif (entry.status === \"error\" || entry.status === \"timeout\") {\n\t\t\t\terrors.set(entry.providerId, new Error(entry.error ?? entry.status));\n\t\t\t}\n\t\t}\n\t\tthis.updateModelSnapshot();\n\t\ttry {\n\t\t\tawait this.forceRefreshAvailability();\n\t\t} catch {\n\t\t\t// Availability errors are recorded by forceRefreshAvailability; refreshed models remain usable.\n\t\t}\n\t\tconst catalogResult: CatalogRefreshResult = {\n\t\t\taborted: result.aborted,\n\t\t\terrors,\n\t\t\tofficial,\n\t\t\tlive,\n\t\t\tincomplete: live.flatMap((entry) => entry.incomplete),\n\t\t};\n\t\tthis.lastCatalogRefresh = catalogResult;\n\t\treturn catalogResult;\n\t}\n\n\tgetLastCatalogRefresh(): CatalogRefreshResult {\n\t\treturn this.lastCatalogRefresh;\n\t}\n\n\tpersistUserModels(rows: readonly UserModelEntry[]): void {\n\t\tthis.userModels.upsert(rows);\n\t\tthis.overlay.setUser(this.userModelsForStoredProviders(this.snapshot.storedProviders));\n\t\tthis.updateModelSnapshot();\n\t}\n\n\tprivate async refreshOfficialCatalog(\n\t\tallowNetwork: boolean,\n\t\tsignal?: AbortSignal,\n\t): Promise<(OfficialCatalogLoadResult & { evictedUserRows: number }) | undefined> {\n\t\tconst storedProviders = await this.listStoredProviderIds();\n\t\tconst loaded = preferOfficialCatalog(\n\t\t\tthis.lastOfficial,\n\t\t\tawait loadOfficialCatalog({\n\t\t\t\tallowNetwork,\n\t\t\t\tcachePath: this.officialCachePath,\n\t\t\t\tsignal,\n\t\t\t\tproviderIds: [...storedProviders],\n\t\t\t}),\n\t\t);\n\t\tthis.lastOfficial = loaded;\n\t\tconst evicted = this.userModels.evictOfficial(officialModelKeys(loaded.catalog));\n\t\tconst templateByProvider = new Map<string, Model<Api> | undefined>();\n\t\tconst officialModels = loaded.catalog.models\n\t\t\t.filter((entry) => storedProviders.has(entry.provider))\n\t\t\t.map((entry) => {\n\t\t\t\tif (!templateByProvider.has(entry.provider)) {\n\t\t\t\t\ttemplateByProvider.set(entry.provider, this.bakedInModels(entry.provider)[0]);\n\t\t\t\t}\n\t\t\t\treturn officialEntryToModel(entry, templateByProvider.get(entry.provider));\n\t\t\t});\n\t\tthis.overlay.setOfficial(officialModels);\n\t\tthis.overlay.setUser(this.userModelsForStoredProviders(storedProviders));\n\t\treturn { ...loaded, evictedUserRows: evicted.length };\n\t}\n\n\tprivate async restoreLiveCatalogs(): Promise<LiveCatalogResult[]> {\n\t\tconst storedProviders = await this.listStoredProviderIds();\n\t\tconst results: LiveCatalogResult[] = [];\n\t\tfor (const providerId of LIVE_LIST_PROVIDER_IDS) {\n\t\t\tconst stored = storedProviders.has(providerId) ? await this.modelsStore.read(providerId) : undefined;\n\t\t\tif (stored?.models) this.overlay.setLive(providerId, stored.models);\n\t\t\telse this.overlay.setLive(providerId, []);\n\t\t\tresults.push({\n\t\t\t\tproviderId,\n\t\t\t\tstatus: \"skipped\",\n\t\t\t\tdiscoveries: [],\n\t\t\t\tadded: 0,\n\t\t\t\ttotal: stored?.models.length ?? 0,\n\t\t\t\tincomplete: [],\n\t\t\t});\n\t\t}\n\t\treturn results;\n\t}\n\n\tprivate async refreshLiveCatalogs(officialKeys: Set<string>, signal?: AbortSignal): Promise<LiveCatalogResult[]> {\n\t\tconst storedProviders = await this.listStoredProviderIds();\n\t\tconst known = knownModelKeys(\n\t\t\tLIVE_LIST_PROVIDER_IDS.flatMap((id) => this.bakedInModels(id)),\n\t\t\tofficialKeys,\n\t\t\tthis.userModels.keys(),\n\t\t);\n\n\t\tconst results = await Promise.all(\n\t\t\tLIVE_LIST_PROVIDER_IDS.map(async (providerId) => {\n\t\t\t\tif (!storedProviders.has(providerId)) {\n\t\t\t\t\tthis.overlay.setLive(providerId, []);\n\t\t\t\t\treturn {\n\t\t\t\t\t\tproviderId,\n\t\t\t\t\t\tstatus: \"skipped\" as const,\n\t\t\t\t\t\tdiscoveries: [],\n\t\t\t\t\t\tadded: 0,\n\t\t\t\t\t\ttotal: 0,\n\t\t\t\t\t\tincomplete: [],\n\t\t\t\t\t};\n\t\t\t\t}\n\n\t\t\t\tconst stored = await this.modelsStore.read(providerId);\n\t\t\t\tif (stored?.models) this.overlay.setLive(providerId, stored.models);\n\t\t\t\tif (signal?.aborted) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\tproviderId,\n\t\t\t\t\t\tstatus: \"skipped\" as const,\n\t\t\t\t\t\tdiscoveries: [],\n\t\t\t\t\t\tadded: 0,\n\t\t\t\t\t\ttotal: stored?.models.length ?? 0,\n\t\t\t\t\t\tincomplete: [],\n\t\t\t\t\t\terror: \"aborted\",\n\t\t\t\t\t};\n\t\t\t\t}\n\n\t\t\t\tconst bakedIn = this.bakedInModels(providerId);\n\t\t\t\tconst provider = this.models.getProvider(providerId) ?? this.defaultBuiltins.get(providerId);\n\t\t\t\tconst baseUrl = provider?.baseUrl ?? bakedIn[0]?.baseUrl;\n\t\t\t\tif (!baseUrl) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\tproviderId,\n\t\t\t\t\t\tstatus: \"skipped\" as const,\n\t\t\t\t\t\tdiscoveries: [],\n\t\t\t\t\t\tadded: 0,\n\t\t\t\t\t\ttotal: 0,\n\t\t\t\t\t\tincomplete: [],\n\t\t\t\t\t};\n\t\t\t\t}\n\n\t\t\t\tlet credential: Credential | undefined;\n\t\t\t\ttry {\n\t\t\t\t\tcredential = await this.refreshCredentialFor(providerId);\n\t\t\t\t} catch (error) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\tproviderId,\n\t\t\t\t\t\tstatus: \"error\" as const,\n\t\t\t\t\t\tdiscoveries: [],\n\t\t\t\t\t\tadded: 0,\n\t\t\t\t\t\ttotal: stored?.models.length ?? 0,\n\t\t\t\t\t\tincomplete: [],\n\t\t\t\t\t\terror: error instanceof Error ? error.message : String(error),\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t\tif (!credential) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\tproviderId,\n\t\t\t\t\t\tstatus: \"skipped\" as const,\n\t\t\t\t\t\tdiscoveries: [],\n\t\t\t\t\t\tadded: 0,\n\t\t\t\t\t\ttotal: stored?.models.length ?? 0,\n\t\t\t\t\t\tincomplete: [],\n\t\t\t\t\t};\n\t\t\t\t}\n\n\t\t\t\tconst result = await fetchLiveProviderModels({\n\t\t\t\t\tproviderId,\n\t\t\t\t\tbaseUrl,\n\t\t\t\t\tbakedIn,\n\t\t\t\t\tcredential,\n\t\t\t\t\tknownKeys: known,\n\t\t\t\t\tsignal,\n\t\t\t\t});\n\t\t\t\tif (result.status === \"ok\") {\n\t\t\t\t\tconst overlayModels = buildLiveOverlay(bakedIn, result.discoveries);\n\t\t\t\t\tthis.overlay.setLive(providerId, overlayModels);\n\t\t\t\t\ttry {\n\t\t\t\t\t\tawait this.modelsStore.write(providerId, { models: overlayModels, checkedAt: Date.now() });\n\t\t\t\t\t} catch {\n\t\t\t\t\t\t// Store write is best-effort.\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn result;\n\t\t\t}),\n\t\t);\n\t\treturn results;\n\t}\n\n\tprivate async refreshCredentialFor(providerId: string): Promise<Credential | undefined> {\n\t\tconst stored = await this.credentials.read(providerId);\n\t\t// Same lock + persist path as chat. Returning stored OAuth here skips\n\t\t// refresh and sends expired SuperGrok tokens to GET /models (HTTP 403).\n\t\tconst resolved = await this.getAuth(providerId);\n\t\tif (resolved?.auth.apiKey) return { type: \"api_key\", key: resolved.auth.apiKey, env: resolved.env };\n\t\tif (stored?.type === \"oauth\" && stored.access) return stored;\n\t\tif (stored?.type === \"api_key\" && stored.key) return stored;\n\t\treturn undefined;\n\t}\n\n\tregisterProvider(providerId: string, config: ProviderConfigInput): void {\n\t\t// Validate the incoming registration on its own, like the legacy registry:\n\t\t// a broken re-registration must throw without touching the stored config.\n\t\tvalidateExtensionProvider(providerId, this.builtins.get(providerId), this.config.getProvider(providerId), config);\n\t\t// Re-registration merges defined values over the previous registration and\n\t\t// preserves undefined ones, matching the legacy ModelRegistry contract.\n\t\tconst previous = this.extensionProviders.get(providerId);\n\t\tconst effective: ProviderConfigInput = { ...previous };\n\t\tfor (const [key, value] of Object.entries(config)) {\n\t\t\tif (value !== undefined) (effective as Record<string, unknown>)[key] = value;\n\t\t}\n\t\tthis.extensionProviders.set(providerId, effective);\n\t\tthis.recomposeProvider(providerId);\n\t\tthis.updateModelSnapshot();\n\t\tif (\n\t\t\tthis.snapshot.storedProviders.has(providerId) ||\n\t\t\tconfiguredRequestAuthStatus(this.config.getProvider(providerId), effective)?.configured\n\t\t) {\n\t\t\tconst configuredProviders = new Set(this.snapshot.configuredProviders).add(providerId);\n\t\t\tconst auth = new Map(this.snapshot.auth);\n\t\t\t// Provisional entry until the async refresh lands; never clobber a real check result.\n\t\t\tif (!auth.get(providerId)) {\n\t\t\t\tauth.set(providerId, {\n\t\t\t\t\ttype: effective.oauth && !effective.apiKey ? \"oauth\" : \"api_key\",\n\t\t\t\t\tsource: \"configured provider\",\n\t\t\t\t});\n\t\t\t}\n\t\t\tthis.snapshot = {\n\t\t\t\t...this.snapshot,\n\t\t\t\tauth,\n\t\t\t\tconfiguredProviders,\n\t\t\t\tavailable: this.snapshot.all.filter((model) => configuredProviders.has(model.provider)),\n\t\t\t};\n\t\t}\n\t\tvoid this.refresh({ allowNetwork: false });\n\t}\n\n\tunregisterProvider(providerId: string): void {\n\t\tthis.extensionProviders.delete(providerId);\n\t\tthis.recomposeProvider(providerId);\n\t\tthis.updateModelSnapshot();\n\t\tvoid this.refresh({ allowNetwork: false });\n\t}\n}\n\n/** TUI init / footer / rebind: cache-only. Live refresh is `/refresh` and the model picker. */\nexport async function refreshModelCandidatesForInit(\n\truntime: Pick<ModelRuntime, \"refresh\" | \"getAvailableSnapshot\">,\n): Promise<readonly Model<Api>[]> {\n\tawait runtime.refresh({ allowNetwork: false });\n\treturn runtime.getAvailableSnapshot();\n}\n"]}
|