zidane 5.13.0 → 5.13.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -0
- package/dist/acp-BqIU2mo-.js +1410 -0
- package/dist/acp-BqIU2mo-.js.map +1 -0
- package/dist/acp-cli.d.ts +1 -0
- package/dist/acp-cli.js +713 -0
- package/dist/acp-cli.js.map +1 -0
- package/dist/acp.d.ts +655 -0
- package/dist/acp.d.ts.map +1 -0
- package/dist/acp.js +2 -0
- package/dist/{agent-Db4ojCSV.d.ts → agent-D7ZL8B2X.d.ts} +2 -2
- package/dist/{agent-Db4ojCSV.d.ts.map → agent-D7ZL8B2X.d.ts.map} +1 -1
- package/dist/chat/pure.d.ts +3 -3
- package/dist/chat.d.ts +6 -6
- package/dist/chat.js +3 -2
- package/dist/chat.js.map +1 -1
- package/dist/contexts/daytona.d.ts +3 -3
- package/dist/contexts/docker.d.ts +1 -1
- package/dist/contexts/docker.d.ts.map +1 -1
- package/dist/contexts/docker.js +4 -1
- package/dist/contexts/docker.js.map +1 -1
- package/dist/contexts/e2b.d.ts +2 -2
- package/dist/{contexts-VhV4Af8x.js → contexts-DHi8LPCp.js} +25 -9
- package/dist/contexts-DHi8LPCp.js.map +1 -0
- package/dist/contexts.d.ts +3 -3
- package/dist/contexts.js +1 -1
- package/dist/eval.d.ts +1 -1
- package/dist/eval.js +3 -3
- package/dist/glob-DCWXy_tr.js +128 -0
- package/dist/glob-DCWXy_tr.js.map +1 -0
- package/dist/{headless-tVN-g6IR.js → headless-0O6HMNBQ.js} +6 -6
- package/dist/{headless-tVN-g6IR.js.map → headless-0O6HMNBQ.js.map} +1 -1
- package/dist/headless.d.ts +1 -1
- package/dist/headless.js +1 -1
- package/dist/{index-BEblm0Hu.d.ts → index-BsyPeCSL.d.ts} +3 -3
- package/dist/{index-BEblm0Hu.d.ts.map → index-BsyPeCSL.d.ts.map} +1 -1
- package/dist/{index-CJ-2g7bY.d.ts → index-CDcQW-2S.d.ts} +3 -3
- package/dist/index-CDcQW-2S.d.ts.map +1 -0
- package/dist/{index-CrMb8jCE.d.ts → index-CF15aqlk.d.ts} +3 -3
- package/dist/{index-CrMb8jCE.d.ts.map → index-CF15aqlk.d.ts.map} +1 -1
- package/dist/index.d.ts +7 -7
- package/dist/index.js +7 -7
- package/dist/lazy-DLOurOC_.js +20 -0
- package/dist/lazy-DLOurOC_.js.map +1 -0
- package/dist/{logger-Dcrj48qY.d.ts → logger-DItaCwPw.d.ts} +2 -2
- package/dist/{logger-Dcrj48qY.d.ts.map → logger-DItaCwPw.d.ts.map} +1 -1
- package/dist/mcp.d.ts +1 -1
- package/dist/{messages-CGazSyTL.js → messages-DEsLGBB9.js} +2 -2
- package/dist/{messages-CGazSyTL.js.map → messages-DEsLGBB9.js.map} +1 -1
- package/dist/output/stream-json.d.ts +2 -2
- package/dist/output/stream-json.js +1 -1
- package/dist/output/terminal.d.ts +2 -2
- package/dist/{presets-kPEMOCmE.js → presets-HDIxliiq.js} +2 -2
- package/dist/{presets-kPEMOCmE.js.map → presets-HDIxliiq.js.map} +1 -1
- package/dist/presets.d.ts +2 -2
- package/dist/presets.js +1 -1
- package/dist/{providers-Bo2biCyT.js → providers-Cz-RNYZO.js} +7 -13
- package/dist/providers-Cz-RNYZO.js.map +1 -0
- package/dist/providers.d.ts +1 -1
- package/dist/providers.js +2 -2
- package/dist/restate.d.ts +2 -2
- package/dist/session/sqlite.d.ts +1 -1
- package/dist/{session-B69BQSn1.js → session-BDWZZaYa.js} +2 -2
- package/dist/{session-B69BQSn1.js.map → session-BDWZZaYa.js.map} +1 -1
- package/dist/session.d.ts +1 -1
- package/dist/session.js +2 -2
- package/dist/skills.d.ts +2 -2
- package/dist/{tool-formatters-CkqBgPH4.d.ts → tool-formatters-CNSMadtp.d.ts} +2 -2
- package/dist/{tool-formatters-CkqBgPH4.d.ts.map → tool-formatters-CNSMadtp.d.ts.map} +1 -1
- package/dist/tools/fetch-url.d.ts +1 -1
- package/dist/tools/web-search.d.ts +1 -1
- package/dist/{tools-5Bnlq68O.js → tools-DhzKzB1y.js} +39 -56
- package/dist/tools-DhzKzB1y.js.map +1 -0
- package/dist/tools.d.ts +2 -2
- package/dist/tools.js +1 -1
- package/dist/{transcript-anchors-D4PwUMyO.js → transcript-anchors-Cq-8gx8u.js} +9 -1417
- package/dist/transcript-anchors-Cq-8gx8u.js.map +1 -0
- package/dist/{transcript-anchors-BnLZmASt.d.ts → transcript-anchors-EG-SmZRu.d.ts} +4 -4
- package/dist/{transcript-anchors-BnLZmASt.d.ts.map → transcript-anchors-EG-SmZRu.d.ts.map} +1 -1
- package/dist/tui.d.ts +3 -3
- package/dist/tui.js +7 -6
- package/dist/tui.js.map +1 -1
- package/dist/{turn-operations-B6FaQAZN.d.ts → turn-operations-DwtWRYr1.d.ts} +3 -3
- package/dist/{turn-operations-B6FaQAZN.d.ts.map → turn-operations-DwtWRYr1.d.ts.map} +1 -1
- package/dist/{types-B39tBba1.d.ts → types-Bs2oY7Ux.d.ts} +27 -4
- package/dist/types-Bs2oY7Ux.d.ts.map +1 -0
- package/dist/types.d.ts +4 -4
- package/dist/xdg-zlSeVBhQ.js +1417 -0
- package/dist/xdg-zlSeVBhQ.js.map +1 -0
- package/docs/ACP.md +221 -0
- package/package.json +11 -1
- package/dist/contexts-VhV4Af8x.js.map +0 -1
- package/dist/index-CJ-2g7bY.d.ts.map +0 -1
- package/dist/providers-Bo2biCyT.js.map +0 -1
- package/dist/tools-5Bnlq68O.js.map +0 -1
- package/dist/transcript-anchors-D4PwUMyO.js.map +0 -1
- package/dist/types-B39tBba1.d.ts.map +0 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { A as joinSystemPrompt } from "./messages-
|
|
2
|
-
import { a as multiEdit, c as grep, i as readFile$1, l as glob
|
|
1
|
+
import { A as joinSystemPrompt } from "./messages-DEsLGBB9.js";
|
|
2
|
+
import { a as multiEdit, c as grep, i as readFile$1, l as glob, n as createSpawnTool, o as listFiles, t as writeFile$1, u as edit, y as shell } from "./tools-DhzKzB1y.js";
|
|
3
3
|
import { c as shortId, r as fmtTokens, s as previewLine } from "./format-BNOXpl-1.js";
|
|
4
4
|
import { o as toolResultToText } from "./types-DxHDaqN7.js";
|
|
5
5
|
import { l as errorMessage } from "./errors-BpPfMo_4.js";
|
|
@@ -7,8 +7,8 @@ import { t as writeFileAtomic } from "./atomic-write-Bgtr5JPu.js";
|
|
|
7
7
|
import { o as discoverSkills } from "./interpolate-ConAiXGy.js";
|
|
8
8
|
import { r as formatTokenUsage } from "./stats-DAKBEKjc.js";
|
|
9
9
|
import { a as normalizeMcpServers, n as connectMcpServers } from "./mcp-C_TIj91j.js";
|
|
10
|
-
import { n as definePreset, t as composePresets } from "./presets-
|
|
11
|
-
import {
|
|
10
|
+
import { n as definePreset, t as composePresets } from "./presets-HDIxliiq.js";
|
|
11
|
+
import { C as restoreModelOptions, f as credKeyOf, i as readCredentials, l as BUILTIN_PROVIDERS, n as applyApiKeyEnv, r as credentialsPath, t as resolveStorageDirs, v as modelSupportsReasoning, y as modelsForDescriptor } from "./xdg-zlSeVBhQ.js";
|
|
12
12
|
import { r as fetchUrl } from "./fetch-url-Cgbq-HYx.js";
|
|
13
13
|
import { webSearch } from "./tools/web-search.js";
|
|
14
14
|
import { D as extractEditPayload, I as parseEditOutcomesFromResult, J as collectReferences, Y as findActiveTrigger, q as applyInsert, u as ownerOf } from "./turn-operations-DLWN2J7f.js";
|
|
@@ -17,11 +17,8 @@ import { dirname, isAbsolute, join, posix, relative, resolve, sep } from "node:p
|
|
|
17
17
|
import { chmodSync, createReadStream, createWriteStream, existsSync, mkdirSync, mkdtempSync, readFileSync, realpathSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
18
18
|
import { readdir, stat, writeFile } from "node:fs/promises";
|
|
19
19
|
import { Buffer as Buffer$1 } from "node:buffer";
|
|
20
|
-
import { createHash
|
|
21
|
-
import { getModel, getModels } from "@earendil-works/pi-ai";
|
|
20
|
+
import { createHash } from "node:crypto";
|
|
22
21
|
import { homedir, tmpdir } from "node:os";
|
|
23
|
-
import { createServer } from "node:http";
|
|
24
|
-
import { refreshAnthropicToken, refreshOpenAICodexToken, registerOAuthProvider } from "@earendil-works/pi-ai/oauth";
|
|
25
22
|
import { spawn } from "node:child_process";
|
|
26
23
|
import { createGunzip } from "node:zlib";
|
|
27
24
|
import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
|
|
@@ -943,7 +940,7 @@ function accentColor(accent, COLOR) {
|
|
|
943
940
|
const READ_ONLY_TOOLS = {
|
|
944
941
|
readFile: readFile$1,
|
|
945
942
|
listFiles,
|
|
946
|
-
glob
|
|
943
|
+
glob,
|
|
947
944
|
grep,
|
|
948
945
|
webSearch,
|
|
949
946
|
fetchUrl
|
|
@@ -956,7 +953,7 @@ const BUILD_TOOLS = {
|
|
|
956
953
|
listFiles,
|
|
957
954
|
edit,
|
|
958
955
|
multiEdit,
|
|
959
|
-
glob
|
|
956
|
+
glob,
|
|
960
957
|
grep,
|
|
961
958
|
webSearch,
|
|
962
959
|
fetchUrl
|
|
@@ -1389,1331 +1386,6 @@ function readIfPresent(path, source) {
|
|
|
1389
1386
|
};
|
|
1390
1387
|
}
|
|
1391
1388
|
//#endregion
|
|
1392
|
-
//#region src/chat/oauth-page/server.ts
|
|
1393
|
-
const CALLBACK_HOST = process.env.PI_OAUTH_CALLBACK_HOST || "127.0.0.1";
|
|
1394
|
-
function buildRequestHandler(opts, pending) {
|
|
1395
|
-
return (req, res) => {
|
|
1396
|
-
try {
|
|
1397
|
-
const url = new URL(req.url || "", "http://localhost");
|
|
1398
|
-
if (url.pathname !== opts.path) {
|
|
1399
|
-
respondError(res, 404, opts, "Callback route not found.");
|
|
1400
|
-
return;
|
|
1401
|
-
}
|
|
1402
|
-
const error = url.searchParams.get("error");
|
|
1403
|
-
if (error) {
|
|
1404
|
-
respondError(res, 400, opts, `${opts.providerName} authentication did not complete.`, `Error: ${error}`);
|
|
1405
|
-
return;
|
|
1406
|
-
}
|
|
1407
|
-
const code = url.searchParams.get("code");
|
|
1408
|
-
const state = url.searchParams.get("state");
|
|
1409
|
-
if (!code || !state) {
|
|
1410
|
-
respondError(res, 400, opts, "Missing code or state parameter.");
|
|
1411
|
-
return;
|
|
1412
|
-
}
|
|
1413
|
-
if (state !== opts.expectedState) {
|
|
1414
|
-
respondError(res, 400, opts, "State mismatch.");
|
|
1415
|
-
return;
|
|
1416
|
-
}
|
|
1417
|
-
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
1418
|
-
res.end(opts.renderPage({
|
|
1419
|
-
kind: "success",
|
|
1420
|
-
provider: opts.providerName,
|
|
1421
|
-
message: opts.successMessage
|
|
1422
|
-
}));
|
|
1423
|
-
pending.settle({
|
|
1424
|
-
code,
|
|
1425
|
-
state
|
|
1426
|
-
});
|
|
1427
|
-
} catch {
|
|
1428
|
-
respondError(res, 500, opts, "Internal error while processing the callback.");
|
|
1429
|
-
}
|
|
1430
|
-
};
|
|
1431
|
-
}
|
|
1432
|
-
function respondError(res, status, opts, message, details) {
|
|
1433
|
-
res.writeHead(status, { "Content-Type": "text/html; charset=utf-8" });
|
|
1434
|
-
res.end(opts.renderPage({
|
|
1435
|
-
kind: "error",
|
|
1436
|
-
provider: opts.providerName,
|
|
1437
|
-
message,
|
|
1438
|
-
details
|
|
1439
|
-
}));
|
|
1440
|
-
}
|
|
1441
|
-
function buildStubHandle(redirectUri, server) {
|
|
1442
|
-
return {
|
|
1443
|
-
redirectUri,
|
|
1444
|
-
waitForCode: async () => null,
|
|
1445
|
-
cancelWait: () => {},
|
|
1446
|
-
close: () => {
|
|
1447
|
-
try {
|
|
1448
|
-
server?.close();
|
|
1449
|
-
} catch {}
|
|
1450
|
-
}
|
|
1451
|
-
};
|
|
1452
|
-
}
|
|
1453
|
-
/**
|
|
1454
|
-
* Start the loopback HTTP callback server. The returned handle is the
|
|
1455
|
-
* caller's contract — they `await waitForCode()`, race it against manual
|
|
1456
|
-
* paste, then `close()` in a `finally`.
|
|
1457
|
-
*/
|
|
1458
|
-
async function startCallbackServer(opts) {
|
|
1459
|
-
const onListenError = opts.onListenError ?? "reject";
|
|
1460
|
-
const redirectUri = `http://localhost:${opts.port}${opts.path}`;
|
|
1461
|
-
return new Promise((resolve, reject) => {
|
|
1462
|
-
let settled = false;
|
|
1463
|
-
const pending = { settle: () => {} };
|
|
1464
|
-
const waitPromise = new Promise((resolveWait) => {
|
|
1465
|
-
pending.settle = (value) => {
|
|
1466
|
-
if (settled) return;
|
|
1467
|
-
settled = true;
|
|
1468
|
-
resolveWait(value);
|
|
1469
|
-
};
|
|
1470
|
-
});
|
|
1471
|
-
const server = createServer(buildRequestHandler(opts, pending));
|
|
1472
|
-
server.on("error", (err) => {
|
|
1473
|
-
if (onListenError === "resolveWithStub") {
|
|
1474
|
-
pending.settle(null);
|
|
1475
|
-
resolve(buildStubHandle(redirectUri, server));
|
|
1476
|
-
return;
|
|
1477
|
-
}
|
|
1478
|
-
reject(err);
|
|
1479
|
-
});
|
|
1480
|
-
server.listen(opts.port, CALLBACK_HOST, () => {
|
|
1481
|
-
resolve({
|
|
1482
|
-
redirectUri,
|
|
1483
|
-
waitForCode: () => waitPromise,
|
|
1484
|
-
cancelWait: () => pending.settle(null),
|
|
1485
|
-
close: () => {
|
|
1486
|
-
try {
|
|
1487
|
-
server.close();
|
|
1488
|
-
} catch {}
|
|
1489
|
-
}
|
|
1490
|
-
});
|
|
1491
|
-
});
|
|
1492
|
-
});
|
|
1493
|
-
}
|
|
1494
|
-
//#endregion
|
|
1495
|
-
//#region src/chat/oauth-page/anthropic.ts
|
|
1496
|
-
const CLIENT_ID$1 = atob("OWQxYzI1MGEtZTYxYi00NGQ5LTg4ZWQtNTk0NGQxOTYyZjVl");
|
|
1497
|
-
const AUTHORIZE_URL$1 = "https://claude.ai/oauth/authorize";
|
|
1498
|
-
const TOKEN_URL$1 = "https://platform.claude.com/v1/oauth/token";
|
|
1499
|
-
const CALLBACK_PORT$1 = 53692;
|
|
1500
|
-
const CALLBACK_PATH$1 = "/callback";
|
|
1501
|
-
const SCOPES = "org:create_api_key user:profile user:inference user:sessions:claude_code user:mcp_servers user:file_upload";
|
|
1502
|
-
const PROVIDER_NAME$1 = "Anthropic";
|
|
1503
|
-
/**
|
|
1504
|
-
* Parse what the user pasted into the manual-code prompt. Accepts:
|
|
1505
|
-
* - the bare authorization code
|
|
1506
|
-
* - the full `redirect_uri?code=...&state=...` URL
|
|
1507
|
-
* - the `code#state` shorthand Anthropic surfaces on the page
|
|
1508
|
-
* - a raw query string with `code=...&state=...`
|
|
1509
|
-
*
|
|
1510
|
-
* Matches pi-ai's parse table so an existing user's muscle memory still works.
|
|
1511
|
-
*/
|
|
1512
|
-
function parseAuthorizationInput$1(input) {
|
|
1513
|
-
const value = input.trim();
|
|
1514
|
-
if (!value) return {};
|
|
1515
|
-
try {
|
|
1516
|
-
const url = new URL(value);
|
|
1517
|
-
return {
|
|
1518
|
-
code: url.searchParams.get("code") ?? void 0,
|
|
1519
|
-
state: url.searchParams.get("state") ?? void 0
|
|
1520
|
-
};
|
|
1521
|
-
} catch {}
|
|
1522
|
-
if (value.includes("#")) {
|
|
1523
|
-
const [code, state] = value.split("#", 2);
|
|
1524
|
-
return {
|
|
1525
|
-
code,
|
|
1526
|
-
state
|
|
1527
|
-
};
|
|
1528
|
-
}
|
|
1529
|
-
if (value.includes("code=")) {
|
|
1530
|
-
const params = new URLSearchParams(value);
|
|
1531
|
-
return {
|
|
1532
|
-
code: params.get("code") ?? void 0,
|
|
1533
|
-
state: params.get("state") ?? void 0
|
|
1534
|
-
};
|
|
1535
|
-
}
|
|
1536
|
-
return { code: value };
|
|
1537
|
-
}
|
|
1538
|
-
async function postJson(url, body) {
|
|
1539
|
-
const response = await fetch(url, {
|
|
1540
|
-
method: "POST",
|
|
1541
|
-
headers: {
|
|
1542
|
-
"Content-Type": "application/json",
|
|
1543
|
-
"Accept": "application/json"
|
|
1544
|
-
},
|
|
1545
|
-
body: JSON.stringify(body),
|
|
1546
|
-
signal: AbortSignal.timeout(3e4)
|
|
1547
|
-
});
|
|
1548
|
-
const responseBody = await response.text();
|
|
1549
|
-
if (!response.ok) throw new Error(`HTTP request failed. status=${response.status}; url=${url}; body=${responseBody}`);
|
|
1550
|
-
return responseBody;
|
|
1551
|
-
}
|
|
1552
|
-
async function exchangeAuthorizationCode$1(code, state, verifier, redirectUri) {
|
|
1553
|
-
const responseBody = await postJson(TOKEN_URL$1, {
|
|
1554
|
-
grant_type: "authorization_code",
|
|
1555
|
-
client_id: CLIENT_ID$1,
|
|
1556
|
-
code,
|
|
1557
|
-
state,
|
|
1558
|
-
redirect_uri: redirectUri,
|
|
1559
|
-
code_verifier: verifier
|
|
1560
|
-
});
|
|
1561
|
-
const tokenData = JSON.parse(responseBody);
|
|
1562
|
-
return {
|
|
1563
|
-
refresh: tokenData.refresh_token,
|
|
1564
|
-
access: tokenData.access_token,
|
|
1565
|
-
expires: Date.now() + tokenData.expires_in * 1e3 - 300 * 1e3
|
|
1566
|
-
};
|
|
1567
|
-
}
|
|
1568
|
-
async function loginAnthropicWithCustomPage(options) {
|
|
1569
|
-
const { verifier, challenge } = await generatePkce();
|
|
1570
|
-
const server = await startCallbackServer({
|
|
1571
|
-
port: CALLBACK_PORT$1,
|
|
1572
|
-
path: CALLBACK_PATH$1,
|
|
1573
|
-
expectedState: verifier,
|
|
1574
|
-
providerName: PROVIDER_NAME$1,
|
|
1575
|
-
renderPage: options.renderPage,
|
|
1576
|
-
successMessage: "Anthropic authentication completed. You can close this window.",
|
|
1577
|
-
onListenError: "reject"
|
|
1578
|
-
});
|
|
1579
|
-
let code;
|
|
1580
|
-
let state;
|
|
1581
|
-
try {
|
|
1582
|
-
const authParams = new URLSearchParams({
|
|
1583
|
-
code: "true",
|
|
1584
|
-
client_id: CLIENT_ID$1,
|
|
1585
|
-
response_type: "code",
|
|
1586
|
-
redirect_uri: server.redirectUri,
|
|
1587
|
-
scope: SCOPES,
|
|
1588
|
-
code_challenge: challenge,
|
|
1589
|
-
code_challenge_method: "S256",
|
|
1590
|
-
state: verifier
|
|
1591
|
-
});
|
|
1592
|
-
options.onAuth({
|
|
1593
|
-
url: `${AUTHORIZE_URL$1}?${authParams.toString()}`,
|
|
1594
|
-
instructions: "Complete login in your browser. If the browser is on another machine, paste the final redirect URL here."
|
|
1595
|
-
});
|
|
1596
|
-
if (options.onManualCodeInput) {
|
|
1597
|
-
let manualInput;
|
|
1598
|
-
let manualError;
|
|
1599
|
-
const manualPromise = options.onManualCodeInput().then((input) => {
|
|
1600
|
-
manualInput = input;
|
|
1601
|
-
server.cancelWait();
|
|
1602
|
-
}).catch((err) => {
|
|
1603
|
-
manualError = err instanceof Error ? err : new Error(String(err));
|
|
1604
|
-
server.cancelWait();
|
|
1605
|
-
});
|
|
1606
|
-
const result = await server.waitForCode();
|
|
1607
|
-
if (manualError) throw manualError;
|
|
1608
|
-
if (result?.code) {
|
|
1609
|
-
code = result.code;
|
|
1610
|
-
state = result.state;
|
|
1611
|
-
} else if (manualInput) {
|
|
1612
|
-
const parsed = parseAuthorizationInput$1(manualInput);
|
|
1613
|
-
if (parsed.state && parsed.state !== verifier) throw new Error("OAuth state mismatch");
|
|
1614
|
-
code = parsed.code;
|
|
1615
|
-
state = parsed.state ?? verifier;
|
|
1616
|
-
}
|
|
1617
|
-
if (!code) {
|
|
1618
|
-
await manualPromise;
|
|
1619
|
-
if (manualError) throw manualError;
|
|
1620
|
-
if (manualInput) {
|
|
1621
|
-
const parsed = parseAuthorizationInput$1(manualInput);
|
|
1622
|
-
if (parsed.state && parsed.state !== verifier) throw new Error("OAuth state mismatch");
|
|
1623
|
-
code = parsed.code;
|
|
1624
|
-
state = parsed.state ?? verifier;
|
|
1625
|
-
}
|
|
1626
|
-
}
|
|
1627
|
-
} else {
|
|
1628
|
-
const result = await server.waitForCode();
|
|
1629
|
-
if (result?.code) {
|
|
1630
|
-
code = result.code;
|
|
1631
|
-
state = result.state;
|
|
1632
|
-
}
|
|
1633
|
-
}
|
|
1634
|
-
if (!code) {
|
|
1635
|
-
const parsed = parseAuthorizationInput$1(await options.onPrompt({
|
|
1636
|
-
message: "Paste the authorization code or full redirect URL:",
|
|
1637
|
-
placeholder: server.redirectUri
|
|
1638
|
-
}));
|
|
1639
|
-
if (parsed.state && parsed.state !== verifier) throw new Error("OAuth state mismatch");
|
|
1640
|
-
code = parsed.code;
|
|
1641
|
-
state = parsed.state ?? verifier;
|
|
1642
|
-
}
|
|
1643
|
-
if (!code) throw new Error("Missing authorization code");
|
|
1644
|
-
if (!state) throw new Error("Missing OAuth state");
|
|
1645
|
-
options.onProgress?.("Exchanging authorization code for tokens...");
|
|
1646
|
-
return await exchangeAuthorizationCode$1(code, state, verifier, server.redirectUri);
|
|
1647
|
-
} finally {
|
|
1648
|
-
server.close();
|
|
1649
|
-
}
|
|
1650
|
-
}
|
|
1651
|
-
/**
|
|
1652
|
-
* Build an `OAuthProviderInterface` that behaves identically to pi-ai's
|
|
1653
|
-
* `anthropicOAuthProvider` except for the callback page HTML. Drop this
|
|
1654
|
-
* onto `ProviderDescriptor.oauthProvider` to override.
|
|
1655
|
-
*/
|
|
1656
|
-
function createAnthropicOAuthProviderWithCustomPage(renderPage) {
|
|
1657
|
-
return {
|
|
1658
|
-
id: "anthropic",
|
|
1659
|
-
name: "Anthropic (Claude Pro/Max)",
|
|
1660
|
-
usesCallbackServer: true,
|
|
1661
|
-
async login(callbacks) {
|
|
1662
|
-
return loginAnthropicWithCustomPage({
|
|
1663
|
-
renderPage,
|
|
1664
|
-
onAuth: callbacks.onAuth,
|
|
1665
|
-
onPrompt: callbacks.onPrompt,
|
|
1666
|
-
onProgress: callbacks.onProgress,
|
|
1667
|
-
onManualCodeInput: callbacks.onManualCodeInput
|
|
1668
|
-
});
|
|
1669
|
-
},
|
|
1670
|
-
async refreshToken(credentials) {
|
|
1671
|
-
return refreshAnthropicToken(credentials.refresh);
|
|
1672
|
-
},
|
|
1673
|
-
getApiKey(credentials) {
|
|
1674
|
-
return credentials.access;
|
|
1675
|
-
}
|
|
1676
|
-
};
|
|
1677
|
-
}
|
|
1678
|
-
//#endregion
|
|
1679
|
-
//#region src/chat/oauth-page/openai-codex.ts
|
|
1680
|
-
const CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann";
|
|
1681
|
-
const AUTHORIZE_URL = "https://auth.openai.com/oauth/authorize";
|
|
1682
|
-
const TOKEN_URL = "https://auth.openai.com/oauth/token";
|
|
1683
|
-
const CALLBACK_PORT = 1455;
|
|
1684
|
-
const CALLBACK_PATH = "/auth/callback";
|
|
1685
|
-
const SCOPE = "openid profile email offline_access";
|
|
1686
|
-
const JWT_CLAIM_PATH = "https://api.openai.com/auth";
|
|
1687
|
-
const PROVIDER_NAME = "OpenAI";
|
|
1688
|
-
function createState() {
|
|
1689
|
-
return randomBytes(16).toString("hex");
|
|
1690
|
-
}
|
|
1691
|
-
function parseAuthorizationInput(input) {
|
|
1692
|
-
const value = input.trim();
|
|
1693
|
-
if (!value) return {};
|
|
1694
|
-
try {
|
|
1695
|
-
const url = new URL(value);
|
|
1696
|
-
return {
|
|
1697
|
-
code: url.searchParams.get("code") ?? void 0,
|
|
1698
|
-
state: url.searchParams.get("state") ?? void 0
|
|
1699
|
-
};
|
|
1700
|
-
} catch {}
|
|
1701
|
-
if (value.includes("#")) {
|
|
1702
|
-
const [code, state] = value.split("#", 2);
|
|
1703
|
-
return {
|
|
1704
|
-
code,
|
|
1705
|
-
state
|
|
1706
|
-
};
|
|
1707
|
-
}
|
|
1708
|
-
if (value.includes("code=")) {
|
|
1709
|
-
const params = new URLSearchParams(value);
|
|
1710
|
-
return {
|
|
1711
|
-
code: params.get("code") ?? void 0,
|
|
1712
|
-
state: params.get("state") ?? void 0
|
|
1713
|
-
};
|
|
1714
|
-
}
|
|
1715
|
-
return { code: value };
|
|
1716
|
-
}
|
|
1717
|
-
function decodeJwt(token) {
|
|
1718
|
-
try {
|
|
1719
|
-
const parts = token.split(".");
|
|
1720
|
-
if (parts.length !== 3) return null;
|
|
1721
|
-
const payload = parts[1] ?? "";
|
|
1722
|
-
const decoded = atob(payload);
|
|
1723
|
-
return JSON.parse(decoded);
|
|
1724
|
-
} catch {
|
|
1725
|
-
return null;
|
|
1726
|
-
}
|
|
1727
|
-
}
|
|
1728
|
-
function getAccountId(accessToken) {
|
|
1729
|
-
const accountId = (decodeJwt(accessToken)?.[JWT_CLAIM_PATH])?.chatgpt_account_id;
|
|
1730
|
-
return typeof accountId === "string" && accountId.length > 0 ? accountId : null;
|
|
1731
|
-
}
|
|
1732
|
-
async function exchangeAuthorizationCode(code, verifier, redirectUri) {
|
|
1733
|
-
const response = await fetch(TOKEN_URL, {
|
|
1734
|
-
method: "POST",
|
|
1735
|
-
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
1736
|
-
body: new URLSearchParams({
|
|
1737
|
-
grant_type: "authorization_code",
|
|
1738
|
-
client_id: CLIENT_ID,
|
|
1739
|
-
code,
|
|
1740
|
-
code_verifier: verifier,
|
|
1741
|
-
redirect_uri: redirectUri
|
|
1742
|
-
})
|
|
1743
|
-
});
|
|
1744
|
-
if (!response.ok) {
|
|
1745
|
-
const text = await response.text().catch(() => "");
|
|
1746
|
-
return {
|
|
1747
|
-
type: "failed",
|
|
1748
|
-
message: `OpenAI Codex token exchange failed (${response.status}): ${text || response.statusText}`
|
|
1749
|
-
};
|
|
1750
|
-
}
|
|
1751
|
-
const json = await response.json();
|
|
1752
|
-
if (!json.access_token || !json.refresh_token || typeof json.expires_in !== "number") return {
|
|
1753
|
-
type: "failed",
|
|
1754
|
-
message: `OpenAI Codex token exchange response missing fields: ${JSON.stringify(json)}`
|
|
1755
|
-
};
|
|
1756
|
-
return {
|
|
1757
|
-
type: "success",
|
|
1758
|
-
access: json.access_token,
|
|
1759
|
-
refresh: json.refresh_token,
|
|
1760
|
-
expires: Date.now() + json.expires_in * 1e3
|
|
1761
|
-
};
|
|
1762
|
-
}
|
|
1763
|
-
async function loginOpenAICodexWithCustomPage(options) {
|
|
1764
|
-
const { verifier, challenge } = await generatePkce();
|
|
1765
|
-
const state = createState();
|
|
1766
|
-
const originator = options.originator ?? "pi";
|
|
1767
|
-
const server = await startCallbackServer({
|
|
1768
|
-
port: CALLBACK_PORT,
|
|
1769
|
-
path: CALLBACK_PATH,
|
|
1770
|
-
expectedState: state,
|
|
1771
|
-
providerName: PROVIDER_NAME,
|
|
1772
|
-
renderPage: options.renderPage,
|
|
1773
|
-
successMessage: "OpenAI authentication completed. You can close this window.",
|
|
1774
|
-
onListenError: "resolveWithStub"
|
|
1775
|
-
});
|
|
1776
|
-
const authUrl = new URL(AUTHORIZE_URL);
|
|
1777
|
-
authUrl.searchParams.set("response_type", "code");
|
|
1778
|
-
authUrl.searchParams.set("client_id", CLIENT_ID);
|
|
1779
|
-
authUrl.searchParams.set("redirect_uri", server.redirectUri);
|
|
1780
|
-
authUrl.searchParams.set("scope", SCOPE);
|
|
1781
|
-
authUrl.searchParams.set("code_challenge", challenge);
|
|
1782
|
-
authUrl.searchParams.set("code_challenge_method", "S256");
|
|
1783
|
-
authUrl.searchParams.set("state", state);
|
|
1784
|
-
authUrl.searchParams.set("id_token_add_organizations", "true");
|
|
1785
|
-
authUrl.searchParams.set("codex_cli_simplified_flow", "true");
|
|
1786
|
-
authUrl.searchParams.set("originator", originator);
|
|
1787
|
-
options.onAuth({
|
|
1788
|
-
url: authUrl.toString(),
|
|
1789
|
-
instructions: "A browser window should open. Complete login to finish."
|
|
1790
|
-
});
|
|
1791
|
-
let code;
|
|
1792
|
-
try {
|
|
1793
|
-
if (options.onManualCodeInput) {
|
|
1794
|
-
let manualCode;
|
|
1795
|
-
let manualError;
|
|
1796
|
-
const manualPromise = options.onManualCodeInput().then((input) => {
|
|
1797
|
-
manualCode = input;
|
|
1798
|
-
server.cancelWait();
|
|
1799
|
-
}).catch((err) => {
|
|
1800
|
-
manualError = err instanceof Error ? err : new Error(String(err));
|
|
1801
|
-
server.cancelWait();
|
|
1802
|
-
});
|
|
1803
|
-
const result = await server.waitForCode();
|
|
1804
|
-
if (manualError) throw manualError;
|
|
1805
|
-
if (result?.code) code = result.code;
|
|
1806
|
-
else if (manualCode) {
|
|
1807
|
-
const parsed = parseAuthorizationInput(manualCode);
|
|
1808
|
-
if (parsed.state && parsed.state !== state) throw new Error("State mismatch");
|
|
1809
|
-
code = parsed.code;
|
|
1810
|
-
}
|
|
1811
|
-
if (!code) {
|
|
1812
|
-
await manualPromise;
|
|
1813
|
-
if (manualError) throw manualError;
|
|
1814
|
-
if (manualCode) {
|
|
1815
|
-
const parsed = parseAuthorizationInput(manualCode);
|
|
1816
|
-
if (parsed.state && parsed.state !== state) throw new Error("State mismatch");
|
|
1817
|
-
code = parsed.code;
|
|
1818
|
-
}
|
|
1819
|
-
}
|
|
1820
|
-
} else {
|
|
1821
|
-
const result = await server.waitForCode();
|
|
1822
|
-
if (result?.code) code = result.code;
|
|
1823
|
-
}
|
|
1824
|
-
if (!code) {
|
|
1825
|
-
const parsed = parseAuthorizationInput(await options.onPrompt({ message: "Paste the authorization code (or full redirect URL):" }));
|
|
1826
|
-
if (parsed.state && parsed.state !== state) throw new Error("State mismatch");
|
|
1827
|
-
code = parsed.code;
|
|
1828
|
-
}
|
|
1829
|
-
if (!code) throw new Error("Missing authorization code");
|
|
1830
|
-
const tokenResult = await exchangeAuthorizationCode(code, verifier, server.redirectUri);
|
|
1831
|
-
if (tokenResult.type !== "success") throw new Error(tokenResult.message);
|
|
1832
|
-
const accountId = getAccountId(tokenResult.access);
|
|
1833
|
-
if (!accountId) throw new Error("Failed to extract accountId from token");
|
|
1834
|
-
return {
|
|
1835
|
-
access: tokenResult.access,
|
|
1836
|
-
refresh: tokenResult.refresh,
|
|
1837
|
-
expires: tokenResult.expires,
|
|
1838
|
-
accountId
|
|
1839
|
-
};
|
|
1840
|
-
} finally {
|
|
1841
|
-
server.close();
|
|
1842
|
-
}
|
|
1843
|
-
}
|
|
1844
|
-
/**
|
|
1845
|
-
* Build an `OAuthProviderInterface` that behaves identically to pi-ai's
|
|
1846
|
-
* `openaiCodexOAuthProvider` except for the callback page HTML.
|
|
1847
|
-
*/
|
|
1848
|
-
function createOpenAICodexOAuthProviderWithCustomPage(renderPage) {
|
|
1849
|
-
return {
|
|
1850
|
-
id: "openai-codex",
|
|
1851
|
-
name: "ChatGPT Plus/Pro (Codex Subscription)",
|
|
1852
|
-
usesCallbackServer: true,
|
|
1853
|
-
async login(callbacks) {
|
|
1854
|
-
return loginOpenAICodexWithCustomPage({
|
|
1855
|
-
renderPage,
|
|
1856
|
-
onAuth: callbacks.onAuth,
|
|
1857
|
-
onPrompt: callbacks.onPrompt,
|
|
1858
|
-
onProgress: callbacks.onProgress,
|
|
1859
|
-
onManualCodeInput: callbacks.onManualCodeInput
|
|
1860
|
-
});
|
|
1861
|
-
},
|
|
1862
|
-
async refreshToken(credentials) {
|
|
1863
|
-
return refreshOpenAICodexToken(credentials.refresh);
|
|
1864
|
-
},
|
|
1865
|
-
getApiKey(credentials) {
|
|
1866
|
-
return credentials.access;
|
|
1867
|
-
}
|
|
1868
|
-
};
|
|
1869
|
-
}
|
|
1870
|
-
//#endregion
|
|
1871
|
-
//#region src/chat/oauth-page/render.ts
|
|
1872
|
-
function escapeHtml(value) {
|
|
1873
|
-
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll("\"", """).replaceAll("'", "'");
|
|
1874
|
-
}
|
|
1875
|
-
/**
|
|
1876
|
-
* Default zidane-themed page. Visually neutral but distinguishable from
|
|
1877
|
-
* pi-ai's stock page — dark background, mono headings, no logo (host can
|
|
1878
|
-
* pass a custom renderer to add one).
|
|
1879
|
-
*/
|
|
1880
|
-
const renderDefaultCallbackPage = (page) => {
|
|
1881
|
-
const heading = escapeHtml(page.kind === "success" ? `Signed in to ${page.provider}` : `Could not sign in to ${page.provider}`);
|
|
1882
|
-
const message = escapeHtml(page.message);
|
|
1883
|
-
const details = page.details ? escapeHtml(page.details) : void 0;
|
|
1884
|
-
return `<!doctype html>
|
|
1885
|
-
<html lang="en">
|
|
1886
|
-
<head>
|
|
1887
|
-
<meta charset="utf-8" />
|
|
1888
|
-
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
1889
|
-
<title>${heading}</title>
|
|
1890
|
-
<style>
|
|
1891
|
-
:root {
|
|
1892
|
-
--text: #f4f4f5;
|
|
1893
|
-
--text-dim: #a1a1aa;
|
|
1894
|
-
--accent: ${page.kind === "success" ? "#22d3ee" : "#f87171"};
|
|
1895
|
-
--page-bg: #0a0a0a;
|
|
1896
|
-
--panel-bg: #131316;
|
|
1897
|
-
--border: #27272a;
|
|
1898
|
-
--font-sans: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
|
1899
|
-
--font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
|
1900
|
-
}
|
|
1901
|
-
* { box-sizing: border-box; }
|
|
1902
|
-
html { color-scheme: dark; }
|
|
1903
|
-
body {
|
|
1904
|
-
margin: 0;
|
|
1905
|
-
min-height: 100vh;
|
|
1906
|
-
display: flex;
|
|
1907
|
-
align-items: center;
|
|
1908
|
-
justify-content: center;
|
|
1909
|
-
padding: 24px;
|
|
1910
|
-
background: var(--page-bg);
|
|
1911
|
-
color: var(--text);
|
|
1912
|
-
font-family: var(--font-sans);
|
|
1913
|
-
}
|
|
1914
|
-
main {
|
|
1915
|
-
width: 100%;
|
|
1916
|
-
max-width: 520px;
|
|
1917
|
-
padding: 32px;
|
|
1918
|
-
background: var(--panel-bg);
|
|
1919
|
-
border: 1px solid var(--border);
|
|
1920
|
-
border-radius: 12px;
|
|
1921
|
-
}
|
|
1922
|
-
.label {
|
|
1923
|
-
font-family: var(--font-mono);
|
|
1924
|
-
font-size: 12px;
|
|
1925
|
-
letter-spacing: 0.08em;
|
|
1926
|
-
text-transform: uppercase;
|
|
1927
|
-
color: var(--accent);
|
|
1928
|
-
margin-bottom: 12px;
|
|
1929
|
-
}
|
|
1930
|
-
h1 {
|
|
1931
|
-
margin: 0 0 12px;
|
|
1932
|
-
font-size: 22px;
|
|
1933
|
-
font-weight: 600;
|
|
1934
|
-
letter-spacing: -0.01em;
|
|
1935
|
-
color: var(--text);
|
|
1936
|
-
}
|
|
1937
|
-
p {
|
|
1938
|
-
margin: 0;
|
|
1939
|
-
line-height: 1.6;
|
|
1940
|
-
color: var(--text-dim);
|
|
1941
|
-
font-size: 14px;
|
|
1942
|
-
}
|
|
1943
|
-
.details {
|
|
1944
|
-
margin-top: 18px;
|
|
1945
|
-
padding: 12px 14px;
|
|
1946
|
-
background: var(--page-bg);
|
|
1947
|
-
border: 1px solid var(--border);
|
|
1948
|
-
border-radius: 8px;
|
|
1949
|
-
font-family: var(--font-mono);
|
|
1950
|
-
font-size: 12px;
|
|
1951
|
-
color: var(--text-dim);
|
|
1952
|
-
white-space: pre-wrap;
|
|
1953
|
-
word-break: break-word;
|
|
1954
|
-
}
|
|
1955
|
-
</style>
|
|
1956
|
-
</head>
|
|
1957
|
-
<body>
|
|
1958
|
-
<main>
|
|
1959
|
-
<div class="label">zidane · OAuth · ${escapeHtml(page.provider)}</div>
|
|
1960
|
-
<h1>${heading}</h1>
|
|
1961
|
-
<p>${message}</p>
|
|
1962
|
-
${details ? `<div class="details">${details}</div>` : ""}
|
|
1963
|
-
</main>
|
|
1964
|
-
</body>
|
|
1965
|
-
</html>`;
|
|
1966
|
-
};
|
|
1967
|
-
//#endregion
|
|
1968
|
-
//#region src/chat/oauth-page/index.ts
|
|
1969
|
-
/**
|
|
1970
|
-
* Bundle helper — returns both Anthropic + OpenAI Codex providers wired
|
|
1971
|
-
* with the same renderer. Hosts that want different renderers per provider
|
|
1972
|
-
* should call the individual `create…WithCustomPage` factories instead.
|
|
1973
|
-
*/
|
|
1974
|
-
function createCustomCallbackOAuthProviders(renderPage) {
|
|
1975
|
-
return {
|
|
1976
|
-
anthropic: createAnthropicOAuthProviderWithCustomPage(renderPage),
|
|
1977
|
-
openaiCodex: createOpenAICodexOAuthProviderWithCustomPage(renderPage)
|
|
1978
|
-
};
|
|
1979
|
-
}
|
|
1980
|
-
let cursorRegistered = false;
|
|
1981
|
-
/**
|
|
1982
|
-
* Register Cursor with pi-ai's OAuth registry.
|
|
1983
|
-
*
|
|
1984
|
-
* Unlike Anthropic / Codex, Cursor is **not** built into pi-ai, so
|
|
1985
|
-
* `getOAuthApiKey('cursor', …)` (used by `resolveOAuthApiKey` for lazy
|
|
1986
|
-
* token refresh) would throw "Unknown OAuth provider" until we register it.
|
|
1987
|
-
* Call once at startup from both the CLI auth path and the TUI. Idempotent.
|
|
1988
|
-
*/
|
|
1989
|
-
function registerCursorOAuthProvider() {
|
|
1990
|
-
const provider = createCursorOAuthProvider();
|
|
1991
|
-
if (!cursorRegistered) {
|
|
1992
|
-
registerOAuthProvider(provider);
|
|
1993
|
-
cursorRegistered = true;
|
|
1994
|
-
}
|
|
1995
|
-
return provider;
|
|
1996
|
-
}
|
|
1997
|
-
let xaiRegistered = false;
|
|
1998
|
-
/**
|
|
1999
|
-
* Register xAI Grok with pi-ai's OAuth registry.
|
|
2000
|
-
*
|
|
2001
|
-
* pi-ai ships an xAI *API-key* provider, but no OAuth one — so
|
|
2002
|
-
* `getOAuthApiKey('xai-oauth', …)` (lazy refresh in `resolveOAuthApiKey`)
|
|
2003
|
-
* would throw "Unknown OAuth provider" until we register our flow. The
|
|
2004
|
-
* callback page routes through the supplied renderer so the post-redirect
|
|
2005
|
-
* HTML matches zidane's theme. Call once at startup; idempotent.
|
|
2006
|
-
*/
|
|
2007
|
-
function registerXaiOAuthProvider(renderPage) {
|
|
2008
|
-
const provider = createXaiOAuthProvider(renderPage);
|
|
2009
|
-
if (!xaiRegistered) {
|
|
2010
|
-
registerOAuthProvider(provider);
|
|
2011
|
-
xaiRegistered = true;
|
|
2012
|
-
}
|
|
2013
|
-
return provider;
|
|
2014
|
-
}
|
|
2015
|
-
//#endregion
|
|
2016
|
-
//#region src/chat/providers.ts
|
|
2017
|
-
/**
|
|
2018
|
-
* pi-ai's stock Anthropic + Codex OAuth providers bake their own callback
|
|
2019
|
-
* HTML; we route both through `renderDefaultCallbackPage` so the post-redirect
|
|
2020
|
-
* page matches zidane's theme. The override lives in `src/chat/oauth-page/`
|
|
2021
|
-
* — see that folder's `index.ts` for the deletion path once pi-ai exposes
|
|
2022
|
-
* a `renderCallbackPage` hook upstream.
|
|
2023
|
-
*/
|
|
2024
|
-
const { anthropic: anthropicOAuthProvider, openaiCodex: openaiCodexOAuthProvider } = createCustomCallbackOAuthProviders(renderDefaultCallbackPage);
|
|
2025
|
-
registerCursorOAuthProvider();
|
|
2026
|
-
/**
|
|
2027
|
-
* xAI Grok isn't built into pi-ai's OAuth registry (only its API-key provider
|
|
2028
|
-
* is), so register our loopback-PKCE flow — themed via `renderDefaultCallbackPage`
|
|
2029
|
-
* — so lazy token refresh resolves through it.
|
|
2030
|
-
*/
|
|
2031
|
-
const xaiOAuthProvider = registerXaiOAuthProvider(renderDefaultCallbackPage);
|
|
2032
|
-
/** Convenience accessor — returns `credentialFileKey ?? key`. */
|
|
2033
|
-
function credKeyOf(desc) {
|
|
2034
|
-
return desc.credentialFileKey ?? desc.key;
|
|
2035
|
-
}
|
|
2036
|
-
/** Convenience accessor — returns `piProviderId ?? key`. */
|
|
2037
|
-
function piIdOf(desc) {
|
|
2038
|
-
return desc.piProviderId ?? desc.key;
|
|
2039
|
-
}
|
|
2040
|
-
const anthropicDescriptor = {
|
|
2041
|
-
key: "anthropic",
|
|
2042
|
-
label: "Anthropic",
|
|
2043
|
-
factory: anthropic,
|
|
2044
|
-
defaultModel: "claude-opus-4-8",
|
|
2045
|
-
envKey: "ANTHROPIC_API_KEY",
|
|
2046
|
-
apiKeyPlaceholder: "sk-ant-…",
|
|
2047
|
-
oauthProvider: anthropicOAuthProvider,
|
|
2048
|
-
oauthHint: "Claude Pro/Max subscription",
|
|
2049
|
-
extraModels: ANTHROPIC_EXTRA_MODELS,
|
|
2050
|
-
optionsFor: (id) => FAST_MODE_OPTIONS[id] ? [FAST_MODE_OPTIONS[id]] : void 0
|
|
2051
|
-
};
|
|
2052
|
-
const openaiDescriptor = {
|
|
2053
|
-
key: "openai",
|
|
2054
|
-
label: "OpenAI",
|
|
2055
|
-
factory: openai,
|
|
2056
|
-
defaultModel: "gpt-5.5",
|
|
2057
|
-
envKey: "OPENAI_CODEX_API_KEY",
|
|
2058
|
-
credentialFileKey: "openai-codex",
|
|
2059
|
-
piProviderId: "openai-codex",
|
|
2060
|
-
apiKeyPlaceholder: "sk-… or eyJ… (Codex)",
|
|
2061
|
-
oauthProvider: openaiCodexOAuthProvider
|
|
2062
|
-
};
|
|
2063
|
-
const openrouterDescriptor = {
|
|
2064
|
-
key: "openrouter",
|
|
2065
|
-
label: "OpenRouter",
|
|
2066
|
-
factory: openrouter,
|
|
2067
|
-
defaultModel: "anthropic/claude-sonnet-4-6",
|
|
2068
|
-
envKey: "OPENROUTER_API_KEY",
|
|
2069
|
-
apiKeyPlaceholder: "sk-or-…"
|
|
2070
|
-
};
|
|
2071
|
-
const cerebrasDescriptor = {
|
|
2072
|
-
key: "cerebras",
|
|
2073
|
-
label: "Cerebras",
|
|
2074
|
-
factory: cerebras,
|
|
2075
|
-
defaultModel: "zai-glm-4.7",
|
|
2076
|
-
envKey: "CEREBRAS_API_KEY",
|
|
2077
|
-
apiKeyPlaceholder: "csk-…"
|
|
2078
|
-
};
|
|
2079
|
-
/**
|
|
2080
|
-
* xAI Grok. Supports BOTH a static API key (`XAI_API_KEY`) and the
|
|
2081
|
-
* SuperGrok / X Premium+ OAuth subscription, so the wizard offers both methods.
|
|
2082
|
-
* Models come from pi-ai's `xai` registry (OpenAI-compatible endpoint), shared
|
|
2083
|
-
* by both auth modes.
|
|
2084
|
-
*/
|
|
2085
|
-
const xaiDescriptor = {
|
|
2086
|
-
key: "xai",
|
|
2087
|
-
label: "xAI Grok",
|
|
2088
|
-
factory: xai,
|
|
2089
|
-
defaultModel: "grok-4.3",
|
|
2090
|
-
envKey: "XAI_API_KEY",
|
|
2091
|
-
apiKeyPlaceholder: "xai-…",
|
|
2092
|
-
oauthProvider: xaiOAuthProvider,
|
|
2093
|
-
oauthHint: "SuperGrok / X Premium+ subscription",
|
|
2094
|
-
credentialFileKey: "xai-oauth",
|
|
2095
|
-
piProviderId: "xai"
|
|
2096
|
-
};
|
|
2097
|
-
const arceeDescriptor = {
|
|
2098
|
-
key: "arcee",
|
|
2099
|
-
label: "Arcee",
|
|
2100
|
-
factory: arcee,
|
|
2101
|
-
defaultModel: "trinity-large-thinking",
|
|
2102
|
-
envKey: "ARCEE_API_KEY",
|
|
2103
|
-
apiKeyPlaceholder: "arcee-…",
|
|
2104
|
-
models: [
|
|
2105
|
-
{
|
|
2106
|
-
id: "trinity-large-thinking",
|
|
2107
|
-
name: "Trinity Large (Thinking)",
|
|
2108
|
-
contextWindow: 256e3,
|
|
2109
|
-
reasoning: true,
|
|
2110
|
-
input: ["text"],
|
|
2111
|
-
cost: {
|
|
2112
|
-
input: .25,
|
|
2113
|
-
output: .8
|
|
2114
|
-
}
|
|
2115
|
-
},
|
|
2116
|
-
{
|
|
2117
|
-
id: "trinity-large-preview",
|
|
2118
|
-
name: "Trinity Large (Preview)",
|
|
2119
|
-
contextWindow: 128e3,
|
|
2120
|
-
input: ["text"],
|
|
2121
|
-
cost: {
|
|
2122
|
-
input: .45,
|
|
2123
|
-
output: .15
|
|
2124
|
-
}
|
|
2125
|
-
},
|
|
2126
|
-
{
|
|
2127
|
-
id: "trinity-mini",
|
|
2128
|
-
name: "Trinity Mini",
|
|
2129
|
-
contextWindow: 128e3,
|
|
2130
|
-
input: ["text"],
|
|
2131
|
-
cost: {
|
|
2132
|
-
input: .045,
|
|
2133
|
-
output: .15
|
|
2134
|
-
}
|
|
2135
|
-
}
|
|
2136
|
-
]
|
|
2137
|
-
};
|
|
2138
|
-
const basetenDescriptor = {
|
|
2139
|
-
key: "baseten",
|
|
2140
|
-
label: "Baseten",
|
|
2141
|
-
factory: baseten,
|
|
2142
|
-
defaultModel: "zai-org/GLM-5.1",
|
|
2143
|
-
envKey: "BASETEN_API_KEY",
|
|
2144
|
-
apiKeyPlaceholder: "baseten-api-key…",
|
|
2145
|
-
models: [
|
|
2146
|
-
{
|
|
2147
|
-
id: "zai-org/GLM-5.2",
|
|
2148
|
-
name: "GLM 5.2",
|
|
2149
|
-
contextWindow: 1e6,
|
|
2150
|
-
reasoning: true,
|
|
2151
|
-
input: ["text"]
|
|
2152
|
-
},
|
|
2153
|
-
{
|
|
2154
|
-
id: "zai-org/GLM-5.1",
|
|
2155
|
-
name: "GLM 5.1",
|
|
2156
|
-
contextWindow: 2e5,
|
|
2157
|
-
reasoning: true,
|
|
2158
|
-
input: ["text"]
|
|
2159
|
-
},
|
|
2160
|
-
{
|
|
2161
|
-
id: "zai-org/GLM-5",
|
|
2162
|
-
name: "GLM 5",
|
|
2163
|
-
contextWindow: 2e5,
|
|
2164
|
-
reasoning: true,
|
|
2165
|
-
input: ["text"],
|
|
2166
|
-
cost: {
|
|
2167
|
-
input: .95,
|
|
2168
|
-
output: 3.15,
|
|
2169
|
-
cacheRead: .2
|
|
2170
|
-
}
|
|
2171
|
-
},
|
|
2172
|
-
{
|
|
2173
|
-
id: "zai-org/GLM-4.7",
|
|
2174
|
-
name: "GLM 4.7",
|
|
2175
|
-
contextWindow: 2e5,
|
|
2176
|
-
reasoning: true,
|
|
2177
|
-
input: ["text"],
|
|
2178
|
-
cost: {
|
|
2179
|
-
input: .6,
|
|
2180
|
-
output: 2.2,
|
|
2181
|
-
cacheRead: .12
|
|
2182
|
-
}
|
|
2183
|
-
},
|
|
2184
|
-
{
|
|
2185
|
-
id: "moonshotai/Kimi-K2.6",
|
|
2186
|
-
name: "Kimi K2.6",
|
|
2187
|
-
contextWindow: 256e3,
|
|
2188
|
-
reasoning: true,
|
|
2189
|
-
input: ["text", "image"],
|
|
2190
|
-
cost: {
|
|
2191
|
-
input: 1,
|
|
2192
|
-
output: 3.9,
|
|
2193
|
-
cacheRead: .2
|
|
2194
|
-
}
|
|
2195
|
-
},
|
|
2196
|
-
{
|
|
2197
|
-
id: "moonshotai/Kimi-K2.5",
|
|
2198
|
-
name: "Kimi K2.5",
|
|
2199
|
-
contextWindow: 256e3,
|
|
2200
|
-
reasoning: true,
|
|
2201
|
-
input: ["text", "image"],
|
|
2202
|
-
cost: {
|
|
2203
|
-
input: .6,
|
|
2204
|
-
output: 3,
|
|
2205
|
-
cacheRead: .12
|
|
2206
|
-
}
|
|
2207
|
-
},
|
|
2208
|
-
{
|
|
2209
|
-
id: "deepseek-ai/DeepSeek-V4-Pro",
|
|
2210
|
-
name: "DeepSeek V4 Pro",
|
|
2211
|
-
contextWindow: 131e3,
|
|
2212
|
-
reasoning: true,
|
|
2213
|
-
input: ["text"],
|
|
2214
|
-
cost: {
|
|
2215
|
-
input: 1.74,
|
|
2216
|
-
output: 3.48,
|
|
2217
|
-
cacheRead: .145
|
|
2218
|
-
}
|
|
2219
|
-
},
|
|
2220
|
-
{
|
|
2221
|
-
id: "nvidia/Nemotron-120B-A12B",
|
|
2222
|
-
name: "Nemotron Super",
|
|
2223
|
-
contextWindow: 2e5,
|
|
2224
|
-
reasoning: true,
|
|
2225
|
-
input: ["text"],
|
|
2226
|
-
cost: {
|
|
2227
|
-
input: .3,
|
|
2228
|
-
output: .75,
|
|
2229
|
-
cacheRead: .06
|
|
2230
|
-
}
|
|
2231
|
-
},
|
|
2232
|
-
{
|
|
2233
|
-
id: "nvidia/NVIDIA-Nemotron-3-Ultra-550B-A55B",
|
|
2234
|
-
name: "Nemotron Ultra",
|
|
2235
|
-
contextWindow: 2e5,
|
|
2236
|
-
reasoning: true,
|
|
2237
|
-
input: ["text"]
|
|
2238
|
-
},
|
|
2239
|
-
{
|
|
2240
|
-
id: "openai/gpt-oss-120b",
|
|
2241
|
-
name: "GPT-OSS 120B",
|
|
2242
|
-
contextWindow: 128e3,
|
|
2243
|
-
reasoning: true,
|
|
2244
|
-
input: ["text"],
|
|
2245
|
-
cost: {
|
|
2246
|
-
input: .1,
|
|
2247
|
-
output: .5
|
|
2248
|
-
}
|
|
2249
|
-
}
|
|
2250
|
-
]
|
|
2251
|
-
};
|
|
2252
|
-
/**
|
|
2253
|
-
* Conservative context-window assumption for a user-configured local model.
|
|
2254
|
-
* Local runtimes expose no reliable per-model metadata over the OpenAI-compat
|
|
2255
|
-
* `/v1/models` endpoint (it returns ids, not context sizes), so we pick a
|
|
2256
|
-
* floor that fits the smallest mainstream OSS models (Llama 3.x 8B, Qwen2.5)
|
|
2257
|
-
* without over-promising. The footer's context indicator and auto-compaction
|
|
2258
|
-
* lean on this — under-estimating is the safe direction (compact early rather
|
|
2259
|
-
* than overflow the server's real window).
|
|
2260
|
-
*
|
|
2261
|
-
* Users running larger-context models (e.g. a 128K Qwen) can raise it via the
|
|
2262
|
-
* `LOCAL_LLM_CONTEXT_WINDOW` env var / customField, which supports a single
|
|
2263
|
-
* value or a per-model map (resolved by {@link resolveContextWindow}).
|
|
2264
|
-
*/
|
|
2265
|
-
const LOCAL_DEFAULT_CONTEXT_WINDOW = 8192;
|
|
2266
|
-
/**
|
|
2267
|
-
* Local OpenAI-compatible LLM (Ollama, vLLM, LM Studio, Lemonade, llama.cpp).
|
|
2268
|
-
*
|
|
2269
|
-
* No fixed base URL or model catalogue — both come from the user via
|
|
2270
|
-
* `customFields`. No `envKey`, so the wizard skips the API-key prompt and
|
|
2271
|
-
* treats the customFields-only credential as the auth signal (see
|
|
2272
|
-
* `applyApiKeyEnv` + `detectAuth`).
|
|
2273
|
-
*
|
|
2274
|
-
* Vision / cache / reasoning are off by default in the factory — flip them
|
|
2275
|
-
* by passing a custom descriptor that calls `local({ capabilities })`.
|
|
2276
|
-
*
|
|
2277
|
-
* `models` is a getter, not a static list: there's no fixed catalogue to ship,
|
|
2278
|
-
* but once the user has configured a default model (mirrored into
|
|
2279
|
-
* `LOCAL_LLM_DEFAULT_MODEL` by `applyApiKeyEnv`), we surface it as a
|
|
2280
|
-
* single-entry list so the model picker + footer aren't empty. Resolved at
|
|
2281
|
-
* access time because the env var is populated at TUI launch, after this
|
|
2282
|
-
* module loads. Empty list when unconfigured — the picker hides, the user
|
|
2283
|
-
* types a slug at the prompt as before.
|
|
2284
|
-
*/
|
|
2285
|
-
const localDescriptor = {
|
|
2286
|
-
key: "local",
|
|
2287
|
-
label: "Local LLM",
|
|
2288
|
-
factory: local,
|
|
2289
|
-
get models() {
|
|
2290
|
-
const configured = process.env.LOCAL_LLM_DEFAULT_MODEL?.trim();
|
|
2291
|
-
if (!configured) return [];
|
|
2292
|
-
return [{
|
|
2293
|
-
id: configured,
|
|
2294
|
-
name: configured,
|
|
2295
|
-
contextWindow: resolveContextWindow(process.env.LOCAL_LLM_CONTEXT_WINDOW, configured) ?? LOCAL_DEFAULT_CONTEXT_WINDOW,
|
|
2296
|
-
input: ["text"]
|
|
2297
|
-
}];
|
|
2298
|
-
},
|
|
2299
|
-
customFields: [
|
|
2300
|
-
{
|
|
2301
|
-
key: "baseURL",
|
|
2302
|
-
label: "Base URL",
|
|
2303
|
-
envVar: "LOCAL_LLM_BASE_URL",
|
|
2304
|
-
placeholder: "http://localhost:11434/v1",
|
|
2305
|
-
hint: "Where your local LLM server is reachable. Examples: Ollama → http://localhost:11434/v1, vLLM → http://localhost:8000/v1, LM Studio → http://localhost:1234/v1.",
|
|
2306
|
-
required: true
|
|
2307
|
-
},
|
|
2308
|
-
{
|
|
2309
|
-
key: "apiKey",
|
|
2310
|
-
label: "API key",
|
|
2311
|
-
envVar: "LOCAL_LLM_API_KEY",
|
|
2312
|
-
placeholder: "leave blank if your server is unauthenticated"
|
|
2313
|
-
},
|
|
2314
|
-
{
|
|
2315
|
-
key: "model",
|
|
2316
|
-
label: "Default model",
|
|
2317
|
-
envVar: "LOCAL_LLM_DEFAULT_MODEL",
|
|
2318
|
-
placeholder: "e.g. llama3.1:8b, qwen2.5-coder, mistral-small",
|
|
2319
|
-
hint: "Model id your server exposes. Leave blank to pick later from the model picker."
|
|
2320
|
-
},
|
|
2321
|
-
{
|
|
2322
|
-
key: "contextWindow",
|
|
2323
|
-
label: "Context window (tokens)",
|
|
2324
|
-
envVar: "LOCAL_LLM_CONTEXT_WINDOW",
|
|
2325
|
-
placeholder: "e.g. 32768 · or per-model: llama3.1:8b=32768, qwen2.5-coder=128k, 64k",
|
|
2326
|
-
hint: "Context length(s) for your local model(s) — local runtimes don't advertise this, so set it here to enable the context indicator and auto-compaction. Use a single value (32768, 128k) for all models, or a comma-separated map of `model=window` with an optional bare value as the fallback. Press"
|
|
2327
|
-
}
|
|
2328
|
-
],
|
|
2329
|
-
contextWindowEnvVar: "LOCAL_LLM_CONTEXT_WINDOW"
|
|
2330
|
-
};
|
|
2331
|
-
/**
|
|
2332
|
-
* Default provider registry. Passed verbatim when `runTui` is invoked without
|
|
2333
|
-
* an explicit `providers` option. Hosts that want to override per-provider
|
|
2334
|
-
* metadata can spread this and replace specific entries:
|
|
2335
|
-
*
|
|
2336
|
-
* ```ts
|
|
2337
|
-
* runTui({ providers: { ...BUILTIN_PROVIDERS, anthropic: myOwnAnthropicDescriptor } })
|
|
2338
|
-
* ```
|
|
2339
|
-
*
|
|
2340
|
-
* `cursor` is intentionally NOT registered here: OAuth works, but inference
|
|
2341
|
-
* isn't wired (`cursor.stream()` throws — Cursor speaks a protobuf/HTTP-2
|
|
2342
|
-
* agent protocol, not OpenAI-compat). Shipping it in the picker only lets users
|
|
2343
|
-
* select a model that errors every turn. The descriptor stays exported so a
|
|
2344
|
-
* host can opt in (`{ ...BUILTIN_PROVIDERS, cursor: cursorDescriptor }`) once
|
|
2345
|
-
* the transport lands — re-add it here at that point.
|
|
2346
|
-
*/
|
|
2347
|
-
const BUILTIN_PROVIDERS = {
|
|
2348
|
-
anthropic: anthropicDescriptor,
|
|
2349
|
-
openai: openaiDescriptor,
|
|
2350
|
-
openrouter: openrouterDescriptor,
|
|
2351
|
-
cerebras: cerebrasDescriptor,
|
|
2352
|
-
xai: xaiDescriptor,
|
|
2353
|
-
arcee: arceeDescriptor,
|
|
2354
|
-
baseten: basetenDescriptor,
|
|
2355
|
-
local: localDescriptor
|
|
2356
|
-
};
|
|
2357
|
-
/**
|
|
2358
|
-
* Resolve the model list for a given provider. Honors `descriptor.models`
|
|
2359
|
-
* when set; otherwise queries pi-ai via `descriptor.piProviderId`. Returns
|
|
2360
|
-
* `[]` for descriptors with no known mapping (custom providers without a
|
|
2361
|
-
* model list) — callers should hide the model picker in that case.
|
|
2362
|
-
*/
|
|
2363
|
-
function modelsForDescriptor(descriptor) {
|
|
2364
|
-
if (descriptor.models) return descriptor.models;
|
|
2365
|
-
let bundled;
|
|
2366
|
-
try {
|
|
2367
|
-
bundled = getModels(piIdOf(descriptor));
|
|
2368
|
-
} catch {
|
|
2369
|
-
bundled = [];
|
|
2370
|
-
}
|
|
2371
|
-
if (descriptor.extraModels?.length) {
|
|
2372
|
-
const bundledIds = new Set(bundled.map((m) => m.id));
|
|
2373
|
-
bundled = [...descriptor.extraModels.filter((m) => !bundledIds.has(m.id)), ...bundled];
|
|
2374
|
-
}
|
|
2375
|
-
return descriptor.optionsFor ? bundled.map((m) => decorateOptions(m, descriptor)) : bundled;
|
|
2376
|
-
}
|
|
2377
|
-
/**
|
|
2378
|
-
* Attach descriptor-resolved {@link ModelOption}s to a model that doesn't
|
|
2379
|
-
* already declare its own. Pure / non-mutating — returns the input untouched
|
|
2380
|
-
* when the model has options already or the descriptor resolves none.
|
|
2381
|
-
*/
|
|
2382
|
-
function decorateOptions(model, descriptor) {
|
|
2383
|
-
if (model.options?.length || !descriptor.optionsFor) return model;
|
|
2384
|
-
const options = descriptor.optionsFor(model.id);
|
|
2385
|
-
return options?.length ? {
|
|
2386
|
-
...model,
|
|
2387
|
-
options
|
|
2388
|
-
} : model;
|
|
2389
|
-
}
|
|
2390
|
-
/**
|
|
2391
|
-
* Resolve a single model's metadata via the descriptor's model source.
|
|
2392
|
-
* Mirrors {@link modelsForDescriptor} routing: descriptor's own list wins,
|
|
2393
|
-
* pi-ai's registry is the fallback. Returns `null` when the model isn't
|
|
2394
|
-
* known.
|
|
2395
|
-
*/
|
|
2396
|
-
function getModelInfo(descriptor, modelId) {
|
|
2397
|
-
if (descriptor.models) return descriptor.models.find((m) => m.id === modelId) ?? null;
|
|
2398
|
-
try {
|
|
2399
|
-
const bundled = getModel(piIdOf(descriptor), modelId);
|
|
2400
|
-
if (bundled) return decorateOptions(bundled, descriptor);
|
|
2401
|
-
} catch {}
|
|
2402
|
-
const extra = descriptor.extraModels?.find((m) => m.id === modelId);
|
|
2403
|
-
return extra ? decorateOptions(extra, descriptor) : null;
|
|
2404
|
-
}
|
|
2405
|
-
/**
|
|
2406
|
-
* Parse a user-supplied context-window string into a token count.
|
|
2407
|
-
*
|
|
2408
|
-
* Accepts a plain integer (`"32768"`), or a number with a `k`/`m` suffix
|
|
2409
|
-
* (×1_000 / ×1_000_000, case-insensitive, optional space) — so `"128k"`
|
|
2410
|
-
* yields `128_000`, matching how the footer renders windows (`fmtTokens`).
|
|
2411
|
-
* Fractional values are floored (`"1.5k"` → `1500`). Returns `null` for
|
|
2412
|
-
* empty, malformed, zero, or negative input so callers fall through to
|
|
2413
|
-
* "window unknown" rather than a bogus ceiling.
|
|
2414
|
-
*/
|
|
2415
|
-
function parseContextWindow(raw) {
|
|
2416
|
-
if (raw == null) return null;
|
|
2417
|
-
const trimmed = raw.trim();
|
|
2418
|
-
if (trimmed.length === 0) return null;
|
|
2419
|
-
const match = /^(\d+(?:\.\d+)?)\s*([km])?$/i.exec(trimmed);
|
|
2420
|
-
if (!match) return null;
|
|
2421
|
-
const suffix = match[2]?.toLowerCase();
|
|
2422
|
-
const mult = suffix === "k" ? 1e3 : suffix === "m" ? 1e6 : 1;
|
|
2423
|
-
const value = Math.floor(Number.parseFloat(match[1]) * mult);
|
|
2424
|
-
return value >= 1 ? value : null;
|
|
2425
|
-
}
|
|
2426
|
-
/**
|
|
2427
|
-
* Resolve a per-model context window from a user-supplied spec string.
|
|
2428
|
-
*
|
|
2429
|
-
* A spec is a comma-separated list of entries. An entry is either:
|
|
2430
|
-
* - `model=window` — a per-model override (split on the **first** `=`, so
|
|
2431
|
-
* model ids containing `:` or `/` survive), or
|
|
2432
|
-
* - a bare `window` — the fallback applied to any model not named above.
|
|
2433
|
-
*
|
|
2434
|
-
* Windows are parsed by {@link parseContextWindow} (plain int or k/m suffix).
|
|
2435
|
-
* Whitespace around entries and the `=` is ignored; malformed entries are
|
|
2436
|
-
* skipped rather than poisoning the whole spec; a later duplicate key wins.
|
|
2437
|
-
*
|
|
2438
|
-
* Lookup order for `modelId`: exact map entry → bare fallback → `null`. A
|
|
2439
|
-
* lone bare value (`"32768"`) therefore behaves as a single global window,
|
|
2440
|
-
* keeping the simple single-model config working.
|
|
2441
|
-
*
|
|
2442
|
-
* @example resolveContextWindow('llama3.1:8b=32768, qwen=128k, 64k', 'qwen') // 128000
|
|
2443
|
-
*/
|
|
2444
|
-
function resolveContextWindow(spec, modelId) {
|
|
2445
|
-
if (spec == null) return null;
|
|
2446
|
-
const overrides = /* @__PURE__ */ new Map();
|
|
2447
|
-
let fallback = null;
|
|
2448
|
-
for (const entry of spec.split(",")) {
|
|
2449
|
-
const eq = entry.indexOf("=");
|
|
2450
|
-
if (eq === -1) {
|
|
2451
|
-
const bare = parseContextWindow(entry);
|
|
2452
|
-
if (bare != null) fallback = bare;
|
|
2453
|
-
continue;
|
|
2454
|
-
}
|
|
2455
|
-
const key = entry.slice(0, eq).trim();
|
|
2456
|
-
const window = parseContextWindow(entry.slice(eq + 1));
|
|
2457
|
-
if (key.length > 0 && window != null) overrides.set(key, window);
|
|
2458
|
-
}
|
|
2459
|
-
return overrides.get(modelId) ?? fallback;
|
|
2460
|
-
}
|
|
2461
|
-
/**
|
|
2462
|
-
* Look up the model's max context window via the descriptor's model source.
|
|
2463
|
-
* Falls back to {@link ProviderDescriptor.contextWindowEnvVar} (parsed via
|
|
2464
|
-
* {@link resolveContextWindow}, which supports a per-model map) when the
|
|
2465
|
-
* registry has nothing — this is how local LLMs, whose runtimes don't
|
|
2466
|
-
* advertise a window, get one. Returns `null` when neither source resolves a
|
|
2467
|
-
* value (custom slugs, providers without a registry or a configured window);
|
|
2468
|
-
* callers should hide the context indicator and skip auto-compaction then.
|
|
2469
|
-
*/
|
|
2470
|
-
function getContextWindow(descriptor, modelId) {
|
|
2471
|
-
const fromRegistry = getModelInfo(descriptor, modelId)?.contextWindow;
|
|
2472
|
-
if (fromRegistry != null) return fromRegistry;
|
|
2473
|
-
if (descriptor.contextWindowEnvVar) return resolveContextWindow(process.env[descriptor.contextWindowEnvVar], modelId);
|
|
2474
|
-
return null;
|
|
2475
|
-
}
|
|
2476
|
-
/**
|
|
2477
|
-
* Whether the given model exposes a reasoning / extended-thinking knob.
|
|
2478
|
-
* Drives the TUI's effort picker visibility — the `ctrl+n` shortcut and
|
|
2479
|
-
* the bottom-bar `effortName` segment only surface when this is `true`.
|
|
2480
|
-
* Returns `false` for unknown models (no registry entry → no advertised
|
|
2481
|
-
* capability).
|
|
2482
|
-
*/
|
|
2483
|
-
function modelSupportsReasoning(descriptor, modelId) {
|
|
2484
|
-
return getModelInfo(descriptor, modelId)?.reasoning === true;
|
|
2485
|
-
}
|
|
2486
|
-
/**
|
|
2487
|
-
* Custom {@link ModelOption}s the given model supports (e.g. fast mode), or `[]`
|
|
2488
|
-
* when none. Drives the TUI/GUI options picker visibility — surfaces only when
|
|
2489
|
-
* non-empty.
|
|
2490
|
-
*/
|
|
2491
|
-
function modelOptionsFor(descriptor, modelId) {
|
|
2492
|
-
return getModelInfo(descriptor, modelId)?.options ?? [];
|
|
2493
|
-
}
|
|
2494
|
-
/**
|
|
2495
|
-
* Normalize a model-options blob to an enabled-only `{ id: true }` map.
|
|
2496
|
-
*
|
|
2497
|
-
* Single source of truth shared by every surface that reads or persists model
|
|
2498
|
-
* options — the TUI run path, the GUI engine reader, and the GUI IPC handlers.
|
|
2499
|
-
* Tolerant of arbitrary input (persisted JSON metadata may be anything): a
|
|
2500
|
-
* non-object → `{}`, and only entries strictly equal to `true` survive. This
|
|
2501
|
-
* keeps the persisted shape minimal (no `{ fast: false }` noise) and means
|
|
2502
|
-
* callers never forward a disabled option to `agent.run`.
|
|
2503
|
-
*/
|
|
2504
|
-
function enabledModelOptions(raw) {
|
|
2505
|
-
if (!raw || typeof raw !== "object") return {};
|
|
2506
|
-
const out = {};
|
|
2507
|
-
for (const [id, on] of Object.entries(raw)) if (on === true) out[id] = true;
|
|
2508
|
-
return out;
|
|
2509
|
-
}
|
|
2510
|
-
/**
|
|
2511
|
-
* Resolve a model's remembered options for a (re)pick: keep only options the
|
|
2512
|
-
* model still declares AND that are enabled, dropping any that no longer apply
|
|
2513
|
-
* (e.g. switching away from a model that supported `fast`). Returns `undefined`
|
|
2514
|
-
* when nothing applies so callers can omit the field entirely.
|
|
2515
|
-
*
|
|
2516
|
-
* Shared by the TUI pick path and the launch-time resume path so "which options
|
|
2517
|
-
* survive a model switch / relaunch" is decided in exactly one place.
|
|
2518
|
-
*/
|
|
2519
|
-
function restoreModelOptions(descriptor, modelId, remembered) {
|
|
2520
|
-
const validIds = new Set(modelOptionsFor(descriptor, modelId).map((o) => o.id));
|
|
2521
|
-
if (validIds.size === 0) return void 0;
|
|
2522
|
-
const enabled = enabledModelOptions(remembered?.[modelId]);
|
|
2523
|
-
const filtered = {};
|
|
2524
|
-
for (const id of Object.keys(enabled)) if (validIds.has(id)) filtered[id] = true;
|
|
2525
|
-
return Object.keys(filtered).length > 0 ? filtered : void 0;
|
|
2526
|
-
}
|
|
2527
|
-
//#endregion
|
|
2528
|
-
//#region src/chat/credentials.ts
|
|
2529
|
-
/** POSIX mode for the credentials file. Ignored on Windows. */
|
|
2530
|
-
const FILE_MODE$1 = 384;
|
|
2531
|
-
/**
|
|
2532
|
-
* Resolve the credentials file path given the resolved TUI data directory
|
|
2533
|
-
* (typically `~/.zidane`, i.e. `config.paths.dir`).
|
|
2534
|
-
*
|
|
2535
|
-
* Matches the convention used elsewhere in the TUI (sessions.db, state.json)
|
|
2536
|
-
* so a single `ZIDANE_STORAGE_DIR` override moves the entire data root.
|
|
2537
|
-
*/
|
|
2538
|
-
function credentialsPath(dataDir) {
|
|
2539
|
-
return resolve(dataDir, "credentials.json");
|
|
2540
|
-
}
|
|
2541
|
-
/**
|
|
2542
|
-
* Read credentials from disk.
|
|
2543
|
-
*
|
|
2544
|
-
* Returns `{}` when the file is missing or corrupt (last-ditch tolerance —
|
|
2545
|
-
* a hand-edit gone wrong shouldn't lock the user out of re-authing). On first
|
|
2546
|
-
* call with no file present, attempts a migration from `cwd/.credentials.json`
|
|
2547
|
-
* (the legacy location used by `bun run auth`).
|
|
2548
|
-
*/
|
|
2549
|
-
function readCredentials(dataDir) {
|
|
2550
|
-
const path = credentialsPath(dataDir);
|
|
2551
|
-
if (!existsSync(path)) {
|
|
2552
|
-
const migrated = migrateLegacyFile(path);
|
|
2553
|
-
if (migrated) return migrated;
|
|
2554
|
-
return {};
|
|
2555
|
-
}
|
|
2556
|
-
try {
|
|
2557
|
-
const raw = readFileSync(path, "utf-8");
|
|
2558
|
-
const parsed = JSON.parse(raw);
|
|
2559
|
-
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return {};
|
|
2560
|
-
return parsed;
|
|
2561
|
-
} catch {
|
|
2562
|
-
return {};
|
|
2563
|
-
}
|
|
2564
|
-
}
|
|
2565
|
-
/** Read a single provider's credential (translating via the descriptor). */
|
|
2566
|
-
function readProviderCredential(dataDir, descriptor) {
|
|
2567
|
-
return readCredentials(dataDir)[credKeyOf(descriptor)];
|
|
2568
|
-
}
|
|
2569
|
-
/**
|
|
2570
|
-
* Write credentials atomically (write-then-rename) with mode 0o600.
|
|
2571
|
-
*
|
|
2572
|
-
* Atomic on the same filesystem — readers either see the previous file or the
|
|
2573
|
-
* new one, never a half-written intermediate. Creates the parent dir if needed
|
|
2574
|
-
* (first launch on a fresh machine: `~/.zidane/` may not exist yet).
|
|
2575
|
-
*/
|
|
2576
|
-
function writeCredentials(dataDir, creds) {
|
|
2577
|
-
writeFileAtomic(credentialsPath(dataDir), `${JSON.stringify(creds, null, 2)}\n`, {
|
|
2578
|
-
ensureDir: true,
|
|
2579
|
-
mode: FILE_MODE$1
|
|
2580
|
-
});
|
|
2581
|
-
}
|
|
2582
|
-
function setProviderCredential(dataDir, descriptor, cred) {
|
|
2583
|
-
const all = readCredentials(dataDir);
|
|
2584
|
-
all[credKeyOf(descriptor)] = cred;
|
|
2585
|
-
writeCredentials(dataDir, all);
|
|
2586
|
-
}
|
|
2587
|
-
function removeProviderCredential(dataDir, descriptor) {
|
|
2588
|
-
const all = readCredentials(dataDir);
|
|
2589
|
-
const fileKey = credKeyOf(descriptor);
|
|
2590
|
-
if (!(fileKey in all)) return;
|
|
2591
|
-
delete all[fileKey];
|
|
2592
|
-
writeCredentials(dataDir, all);
|
|
2593
|
-
}
|
|
2594
|
-
/**
|
|
2595
|
-
* Reconcile `process.env` with the stored credentials so the harness providers
|
|
2596
|
-
* pick up the wizard's output via their existing env-var resolution. Called
|
|
2597
|
-
* once at TUI launch after the credentials file has been resolved.
|
|
2598
|
-
*
|
|
2599
|
-
* **Precedence: stored credential > ambient env.** When the user has run the
|
|
2600
|
-
* auth wizard, that's an explicit, recent action — it MUST win over whatever
|
|
2601
|
-
* ambient value is already in `process.env`. Otherwise an upgrade reliably
|
|
2602
|
-
* breaks for anyone whose cwd happens to contain a stale `.env`: Bun
|
|
2603
|
-
* auto-loads `.env` from cwd into `process.env` before our code runs, the
|
|
2604
|
-
* legacy `bun run auth` used to upsert tokens into `cwd/.env`, and even a
|
|
2605
|
-
* one-off `export ANTHROPIC_API_KEY=…` from months ago in the user's shell rc
|
|
2606
|
-
* can outlive its usefulness. None of those should override a key the user
|
|
2607
|
-
* just typed into the wizard.
|
|
2608
|
-
*
|
|
2609
|
-
* Three cases per registered provider with an `envKey`:
|
|
2610
|
-
*
|
|
2611
|
-
* 1. Stored `kind: 'apikey'` → overwrite env with the stored value. Wizard
|
|
2612
|
-
* wins. A debug log fires when this clobbers a different ambient value.
|
|
2613
|
-
* 2. Stored `kind: 'oauth'` → DELETE env, so {@link resolveOAuthApiKey}
|
|
2614
|
-
* falls through to the file-read + refresh path. A stale OAuth token
|
|
2615
|
-
* in env would otherwise short-circuit refresh and get rejected.
|
|
2616
|
-
* 3. No stored entry → leave env alone. Env-only setups (no wizard run yet,
|
|
2617
|
-
* hosts driving zidane purely from secrets management) keep working.
|
|
2618
|
-
*
|
|
2619
|
-
* To opt out and force ambient env to win again, the user signs the provider
|
|
2620
|
-
* out via the wizard (which deletes the stored entry → case 3 above).
|
|
2621
|
-
*
|
|
2622
|
-
* Descriptors without an `envKey` (OAuth-only providers, custom providers
|
|
2623
|
-
* that bypass env-var resolution) are skipped silently.
|
|
2624
|
-
*/
|
|
2625
|
-
function applyApiKeyEnv(dataDir, registry) {
|
|
2626
|
-
const creds = readCredentials(dataDir);
|
|
2627
|
-
for (const descriptor of Object.values(registry)) {
|
|
2628
|
-
const cred = creds[credKeyOf(descriptor)];
|
|
2629
|
-
if (cred?.kind === "apikey" && descriptor.customFields) {
|
|
2630
|
-
const stored = cred.customFields ?? {};
|
|
2631
|
-
for (const field of descriptor.customFields) {
|
|
2632
|
-
const value = stored[field.key];
|
|
2633
|
-
if (typeof value === "string" && value.length > 0) {
|
|
2634
|
-
const ambient = process.env[field.envVar];
|
|
2635
|
-
if (ambient && ambient !== value && process.env.ZIDANE_DEBUG) process.stderr.write(`[zidane/chat] applyApiKeyEnv: overriding ambient \`${field.envVar}\` with stored value from credentials.json (provider=${descriptor.key}, field=${field.key}).\n`);
|
|
2636
|
-
process.env[field.envVar] = value;
|
|
2637
|
-
}
|
|
2638
|
-
}
|
|
2639
|
-
}
|
|
2640
|
-
if (!descriptor.envKey) continue;
|
|
2641
|
-
const envKey = descriptor.envKey;
|
|
2642
|
-
const ambient = process.env[envKey];
|
|
2643
|
-
if (cred?.kind === "apikey" && cred.value) {
|
|
2644
|
-
if (ambient && ambient !== cred.value && process.env.ZIDANE_DEBUG) process.stderr.write(`[zidane/chat] applyApiKeyEnv: overriding ambient \`${envKey}\` with stored API key from credentials.json (provider=${descriptor.key}). Sign out via the auth wizard if the ambient value was intended to win.\n`);
|
|
2645
|
-
process.env[envKey] = cred.value;
|
|
2646
|
-
continue;
|
|
2647
|
-
}
|
|
2648
|
-
if (cred?.kind === "oauth" && cred.access) {
|
|
2649
|
-
if (ambient !== void 0 && process.env.ZIDANE_DEBUG) process.stderr.write(`[zidane/chat] applyApiKeyEnv: clearing ambient \`${envKey}\` because credentials.json has stored OAuth for ${descriptor.key} — refresh path needs the file.\n`);
|
|
2650
|
-
delete process.env[envKey];
|
|
2651
|
-
continue;
|
|
2652
|
-
}
|
|
2653
|
-
}
|
|
2654
|
-
}
|
|
2655
|
-
/**
|
|
2656
|
-
* `bun run auth` (pre-TUI) wrote `cwd/.credentials.json` with an entry per
|
|
2657
|
-
* provider mapping directly to an OAuthCredentials payload, e.g.:
|
|
2658
|
-
*
|
|
2659
|
-
* {
|
|
2660
|
-
* "anthropic": { "access": "...", "refresh": "...", "expires": 123 },
|
|
2661
|
-
* "openai-codex": { "access": "...", "refresh": "...", "expires": 123, "accountId": "..." }
|
|
2662
|
-
* }
|
|
2663
|
-
*
|
|
2664
|
-
* We don't delete the legacy file — it might still be used by a host that
|
|
2665
|
-
* imports the harness directly. We just copy its contents into the new
|
|
2666
|
-
* location under the kind-tagged shape so the TUI picks them up.
|
|
2667
|
-
*
|
|
2668
|
-
* Migration is provider-agnostic: any top-level entry with an `access` field
|
|
2669
|
-
* is preserved verbatim (extras included), under the same key. The TUI's
|
|
2670
|
-
* detection then looks them up via the matching descriptor's `credentialFileKey`.
|
|
2671
|
-
*
|
|
2672
|
-
* Returns the migrated credentials when the migration ran, or `null` when
|
|
2673
|
-
* there's no legacy file to migrate.
|
|
2674
|
-
*/
|
|
2675
|
-
function migrateLegacyFile(targetPath) {
|
|
2676
|
-
const legacyPath = resolve(process.cwd(), ".credentials.json");
|
|
2677
|
-
const real = (p) => {
|
|
2678
|
-
try {
|
|
2679
|
-
return realpathSync(p);
|
|
2680
|
-
} catch {
|
|
2681
|
-
return p;
|
|
2682
|
-
}
|
|
2683
|
-
};
|
|
2684
|
-
const legacyDir = real(dirname(legacyPath));
|
|
2685
|
-
if (legacyDir !== real(homedir()) && legacyDir !== real(dirname(resolve(targetPath)))) return null;
|
|
2686
|
-
if (!existsSync(legacyPath)) return null;
|
|
2687
|
-
let legacy;
|
|
2688
|
-
try {
|
|
2689
|
-
legacy = JSON.parse(readFileSync(legacyPath, "utf-8"));
|
|
2690
|
-
} catch {
|
|
2691
|
-
return null;
|
|
2692
|
-
}
|
|
2693
|
-
if (!legacy || typeof legacy !== "object" || Array.isArray(legacy)) return null;
|
|
2694
|
-
const migrated = {};
|
|
2695
|
-
for (const [fileKey, value] of Object.entries(legacy)) {
|
|
2696
|
-
if (!isOAuthLegacy(value)) continue;
|
|
2697
|
-
const { access, refresh, expires, ...extras } = value;
|
|
2698
|
-
migrated[fileKey] = {
|
|
2699
|
-
kind: "oauth",
|
|
2700
|
-
access,
|
|
2701
|
-
...typeof refresh === "string" ? { refresh } : {},
|
|
2702
|
-
...typeof expires === "number" ? { expires } : {},
|
|
2703
|
-
...extras
|
|
2704
|
-
};
|
|
2705
|
-
}
|
|
2706
|
-
if (Object.keys(migrated).length === 0) return null;
|
|
2707
|
-
writeFileAtomic(targetPath, `${JSON.stringify(migrated, null, 2)}\n`, {
|
|
2708
|
-
ensureDir: true,
|
|
2709
|
-
mode: FILE_MODE$1
|
|
2710
|
-
});
|
|
2711
|
-
return migrated;
|
|
2712
|
-
}
|
|
2713
|
-
function isOAuthLegacy(value) {
|
|
2714
|
-
return typeof value === "object" && value !== null && "access" in value && typeof value.access === "string";
|
|
2715
|
-
}
|
|
2716
|
-
//#endregion
|
|
2717
1389
|
//#region src/chat/auth.ts
|
|
2718
1390
|
/**
|
|
2719
1391
|
* Detect available auth for every registered provider.
|
|
@@ -4971,86 +3643,6 @@ function normalizeUserConfig(raw) {
|
|
|
4971
3643
|
return out;
|
|
4972
3644
|
}
|
|
4973
3645
|
//#endregion
|
|
4974
|
-
//#region src/chat/xdg.ts
|
|
4975
|
-
/**
|
|
4976
|
-
* XDG Base Directory resolution for zidane.
|
|
4977
|
-
*
|
|
4978
|
-
* The user's storage spreads across four logical slots:
|
|
4979
|
-
*
|
|
4980
|
-
* - **config** — credentials, mcp-credentials, keybindings, config.json,
|
|
4981
|
-
* mcps.json, skills (user-editable surface).
|
|
4982
|
-
* - **data** — sessions.db (long-lived chat history).
|
|
4983
|
-
* - **state** — state.json (per-machine UI bookkeeping: last provider,
|
|
4984
|
-
* last model, last agent, settings).
|
|
4985
|
-
* - **cache** — persisted tool results, background-task logs,
|
|
4986
|
-
* auto-update registry cache (regenerable, safe to wipe).
|
|
4987
|
-
*
|
|
4988
|
-
* Historically all four collapsed to `~/.zidane/`. This module adds
|
|
4989
|
-
* XDG-Base-Directory-aware defaults so a Linux user with no existing
|
|
4990
|
-
* `~/.zidane/` lands in the conventional `~/.config/zidane/`,
|
|
4991
|
-
* `~/.local/share/zidane/`, `~/.local/state/zidane/`, `~/.cache/zidane/`
|
|
4992
|
-
* trio. Existing setups (anyone with a `~/.zidane/` directory or
|
|
4993
|
-
* `ZIDANE_STORAGE_DIR` set) keep the single-dir layout unchanged.
|
|
4994
|
-
*
|
|
4995
|
-
* Resolution flow — applied in this order:
|
|
4996
|
-
*
|
|
4997
|
-
* 1. Caller passed an explicit `storageDir` (`ChatOptions.storageDir`)
|
|
4998
|
-
* OR `ZIDANE_STORAGE_DIR` is set → **legacy-explicit** single-dir
|
|
4999
|
-
* layout under `<storageDir>/<prefix>`. The four slots collapse to
|
|
5000
|
-
* the same directory.
|
|
5001
|
-
* 2. `~/<prefix>/` already exists on disk → **legacy-existing**
|
|
5002
|
-
* single-dir layout under that directory. Upgrades never silently
|
|
5003
|
-
* shift files around.
|
|
5004
|
-
* 3. Any `XDG_*_HOME` env var is set OR `process.platform === 'linux'`
|
|
5005
|
-
* → **XDG split**. Each slot lands in its own dir. Per-slot
|
|
5006
|
-
* `ZIDANE_{CONFIG,DATA,CACHE,STATE}_DIR` overrides beat the XDG
|
|
5007
|
-
* vars for surgical tweaks.
|
|
5008
|
-
* 4. Otherwise (macOS/Windows, no XDG signal, no existing dir) →
|
|
5009
|
-
* **default** single-dir layout at `~/<prefix>/`. Day-one behavior
|
|
5010
|
-
* on these platforms is unchanged.
|
|
5011
|
-
*
|
|
5012
|
-
* The `userDir` legacy alias always equals `configDir` so existing code
|
|
5013
|
-
* that still references `paths.userDir` keeps reading credentials, mcps,
|
|
5014
|
-
* etc. from the right place.
|
|
5015
|
-
*/
|
|
5016
|
-
/**
|
|
5017
|
-
* Resolve the four storage slots according to the precedence above.
|
|
5018
|
-
*
|
|
5019
|
-
* Pure aside from one filesystem probe (`existsSync(home/<prefix>)`)
|
|
5020
|
-
* — the same probe `resolveStoragePaths` would do anyway when
|
|
5021
|
-
* computing `paths.userDir`. No directories are created here; the
|
|
5022
|
-
* relevant write paths (state store, credentials writer, persist
|
|
5023
|
-
* dir) handle their own lazy `mkdirSync` already.
|
|
5024
|
-
*/
|
|
5025
|
-
function resolveStorageDirs(opts = {}) {
|
|
5026
|
-
const env = opts.env ?? process.env;
|
|
5027
|
-
const home = opts.home ?? homedir();
|
|
5028
|
-
const platform = opts.platform ?? process.platform;
|
|
5029
|
-
const rawPrefix = opts.prefix ?? ".zidane";
|
|
5030
|
-
const prefixBare = rawPrefix.replace(/^\./, "");
|
|
5031
|
-
const explicitStorageDir = opts.storageDir ?? env.ZIDANE_STORAGE_DIR;
|
|
5032
|
-
if (explicitStorageDir) return single(resolve(explicitStorageDir, rawPrefix), "legacy-explicit");
|
|
5033
|
-
const legacyHomeDir = resolve(home, rawPrefix);
|
|
5034
|
-
if (existsSync(legacyHomeDir)) return single(legacyHomeDir, "legacy-existing");
|
|
5035
|
-
if (platform === "linux" || !!env.XDG_CONFIG_HOME || !!env.XDG_DATA_HOME || !!env.XDG_CACHE_HOME || !!env.XDG_STATE_HOME || !!env.ZIDANE_CONFIG_DIR || !!env.ZIDANE_DATA_DIR || !!env.ZIDANE_STATE_DIR || !!env.ZIDANE_CACHE_DIR) return {
|
|
5036
|
-
configDir: env.ZIDANE_CONFIG_DIR ?? resolve(env.XDG_CONFIG_HOME || resolve(home, ".config"), prefixBare),
|
|
5037
|
-
dataDir: env.ZIDANE_DATA_DIR ?? resolve(env.XDG_DATA_HOME || resolve(home, ".local", "share"), prefixBare),
|
|
5038
|
-
stateDir: env.ZIDANE_STATE_DIR ?? resolve(env.XDG_STATE_HOME || resolve(home, ".local", "state"), prefixBare),
|
|
5039
|
-
cacheDir: env.ZIDANE_CACHE_DIR ?? resolve(env.XDG_CACHE_HOME || resolve(home, ".cache"), prefixBare),
|
|
5040
|
-
mode: "xdg"
|
|
5041
|
-
};
|
|
5042
|
-
return single(legacyHomeDir, "default");
|
|
5043
|
-
}
|
|
5044
|
-
function single(base, mode) {
|
|
5045
|
-
return {
|
|
5046
|
-
configDir: base,
|
|
5047
|
-
dataDir: base,
|
|
5048
|
-
stateDir: base,
|
|
5049
|
-
cacheDir: base,
|
|
5050
|
-
mode
|
|
5051
|
-
};
|
|
5052
|
-
}
|
|
5053
|
-
//#endregion
|
|
5054
3646
|
//#region src/chat/config.ts
|
|
5055
3647
|
/**
|
|
5056
3648
|
* Resolve user options into a fully-bound runtime config.
|
|
@@ -10378,6 +8970,6 @@ function computeTurnAnchors(items) {
|
|
|
10378
8970
|
};
|
|
10379
8971
|
}
|
|
10380
8972
|
//#endregion
|
|
10381
|
-
export { useMcpAuthDispatch as $,
|
|
8973
|
+
export { useMcpAuthDispatch as $, resolveAgentId as $n, createStateStore as $t, runOAuthLogin as A, bootTick as An, SettingsProvider as At, refreshMcpToolsCatalog as B, resolvePlatformPackage as Bn, CATPPUCCIN_FRAPPE as Bt, projectsFilePath as C, mergeKeybindings as Cn, SUBAGENT_GUIDANCE as Cr, shortChord as Ct, formatPathForCwd as D, useCompletion as Dn, envSection as Dr, SETTINGS_CATEGORIES as Dt, writeProjects as E, stripJsonComments as En, buildPlanSystem as Er, DEFAULT_SETTINGS as Et, parseMcpsFile as F, detectLibc as Fn, resolveChipColor as Ft, useMcpToolToggleSet as G, findGitRoot$1 as Gn, DiscoveryProvider as Gt, subscribeMcpToolsCache as H, detectAuth as Hn, CATPPUCCIN_MACCHIATO as Ht, projectUserPaths as I, detectPackageManager as In, resolveTheme as It, parentServerName as J, DEFAULT_AGENT_ID as Jn, ConfigProvider as Jt, buildVisibleMcpRows as K, BUILD_AGENT as Kn, useDiscovery as Kt, clearMcpToolsCache as L, parseSemver as Ln, VAPORWAVE_THEME as Lt, buildMcpServers as M, useUpdateCheck as Mn, useSettings as Mt, defaultMcpsConfigPaths as N, checkForUpdate as Nn, BUILTIN_THEMES as Nt, fetchOAuthRedirect as O, tryOpenBrowser as On, SETTINGS_CHOICES as Ot, discoverProjectMcps as P, compareSemver as Pn, DEFAULT_THEME as Pt, McpAuthProvider as Q, accentColor as Qn, compactSummaryEvent as Qt, loadMcpToolsCache as R, performInPlaceSelfUpdate as Rn, GRUVBOX_DARK as Rt, matchesSafelistEntry as S, matchesBinding as Sn, PLAN_MODE_DOCTRINE_NO_PROMPTS as Sr, buildHints as St, suggestSafelistEntry as T, readKeybindings as Tn, buildBuildSystem as Tr, useEnabledToggleSet as Tt, buildToolToggle as U, discoverAgentsMd as Un, CATPPUCCIN_MOCHA as Ut, saveMcpToolsCache as V, chatAutoCompactBehavior as Vn, CATPPUCCIN_LATTE as Vt, useMcpToolToggleMap as W, renderAgentsMdBlock as Wn, createDiscoverySlot as Wt, mcpCredentialsPath as X, DEFAULT_PERSIST_EXCLUDE_TOOLS as Xn, resolveConfig as Xt, createFileMcpCredentialStore as Y, DEFAULT_BUDGET_EXCLUDE_TOOLS as Yn, useConfig as Yt, patchMcpCredential as Z, PLAN_AGENT as Zn, resolveStoragePaths as Zt, useSafeModeQueue as _, KEYBINDING_KEY_COL_WIDTH as _n, DOING_TASKS_DOCTRINE as _r, clipHintsToWidth as _t, useSurfaces as a, marginTopFor as an, TODO_WRITE_COUNTS_METADATA_KEY as ar, ASK_USER_TOOL as at, getSafelist as b, groupBindings as bn, INTERACTION_GUIDANCE_NO_PROMPTS as br, cleanTitle as bt, useStreamBuffer as c, stripSpawnTokensLine as cn, getTodosForRun as cr, buildResumedToolResultsTurn as ct, discoverProjectSkills as d, toolCallPreview as dn, pruneTodosByRun as dr, makeRequestInteraction as dt, deriveSessionTitle as en, singleAgentRegistry as er, useMcpAuthState as et, renderSession as f, toolResultText as fn, selectActiveTodos as fr, pendingInteractionsFromTurns as ft, useSafeModeActions as g, KEYBINDING_DEF_BY_ACTION as gn, COMMUNICATION_DOCTRINE as gr, EMPTY_HINTS as gt, SafeModeProvider as h, KEYBINDING_DEFS as hn, ACTIONS_WITH_CARE_DOCTRINE as hr, useInteractionsQueue as ht, useSelectStyle as i, loadState as in, TODO_STATUS_GLYPHS as ir, splitMarkdownCodeBlocks as it, supportsOAuth as j, buildUpdateHint as jn, clampFps as jt, oauthUsesManualCodePaste as k, bootProfileEnabled as kn, SETTINGS_TOGGLES as kt, buildSkillsConfig as l, sumRunCosts as ln, isTodoTool as lr, createInteractionTools as lt, writeSessionExport as m, DEFAULT_KEYBINDINGS as mn, useActiveTodos as mr, useInteractionsActions as mt, ThemeProvider as n, lastContextSizeFromTurns as nn, TODOS_METADATA_KEY as nr, reduceMcpAuth as nt, useSyntaxStyles as o, saveState as on, createTodoTools as or, InteractionsProvider as ot, resolveSessionExportTarget as p, updateToolEventOutcomes as pn, setTodosForRun as pr, serializeInteractionResponse as pt, indexOfServerRow as q, BUILTIN_AGENTS as qn, useDiscoveryOptional as qt, useColors as r, listSessionMeta as rn, TODOWRITE_TOOL as rr, normalizeMarkdownCodeFences as rt, useTheme as s, serverToolResultSummary as sn, getArchivedTodosForRun as sr, PRESENT_PLAN_TOOL as st, computeTurnAnchors as t, eventsFromTurns as tn, TODOREAD_TOOL as tr, getMcpAuthStatus as tt, defaultSkillScanPaths as u, titleFromTurns as un, pickActiveRunId as ur, isInteractionTool as ut, IMPLICITLY_SAFE_TOOLS as v, ensureKeybindingsFile as vn, IDENTITY_PREFIX as vr, hintsLength as vt, readProjects as w, parseBindingSpec as wn, TOKEN_DISCIPLINE_DOCTRINE as wr, listProjectFiles as wt, isOnSafelist as x, keybindingsPath as xn, PLAN_MODE_DOCTRINE as xr, generateSessionTitle as xt, addToSafelist as y, formatBindingForDisplay as yn, INTERACTION_GUIDANCE as yr, truncateTrailing as yt, mcpToolsCachePath as z, performSelfUpdate as zn, GRUVBOX_LIGHT as zt };
|
|
10382
8974
|
|
|
10383
|
-
//# sourceMappingURL=transcript-anchors-
|
|
8975
|
+
//# sourceMappingURL=transcript-anchors-Cq-8gx8u.js.map
|