pi-ui-extend 0.1.28 → 0.1.31
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/app/app.d.ts +2 -0
- package/dist/app/app.js +31 -8
- package/dist/app/cli/update.d.ts +5 -0
- package/dist/app/cli/update.js +29 -1
- package/dist/app/model/model-usage-status.d.ts +2 -0
- package/dist/app/model/model-usage-status.js +90 -20
- package/dist/app/session/session-event-controller.d.ts +17 -1
- package/dist/app/session/session-event-controller.js +28 -0
- package/dist/app/session/tabs-controller.d.ts +10 -0
- package/dist/app/session/tabs-controller.js +65 -28
- package/external/pi-tools-suite/package.json +0 -3
- package/external/pi-tools-suite/src/async-subagents/commands.ts +1 -1
- package/external/pi-tools-suite/src/async-subagents/core/tool-guard.ts +1 -1
- package/external/pi-tools-suite/src/async-subagents/index.ts +1 -1
- package/external/pi-tools-suite/src/async-subagents/render.ts +1 -1
- package/external/pi-tools-suite/src/async-subagents/tools/cleanup.ts +2 -2
- package/external/pi-tools-suite/src/async-subagents/tools/result.ts +2 -2
- package/external/pi-tools-suite/src/async-subagents/tools/spawn.ts +3 -3
- package/external/pi-tools-suite/src/async-subagents/tools/status.ts +3 -3
- package/external/pi-tools-suite/src/async-subagents/tools/stop.ts +2 -2
- package/external/pi-tools-suite/src/async-subagents/tools/subagents.ts +3 -3
- package/external/pi-tools-suite/src/async-subagents/tools/wait.ts +2 -2
- package/external/pi-tools-suite/src/async-subagents/ui.ts +1 -1
- package/external/pi-tools-suite/src/dcp/commands.ts +2 -2
- package/external/pi-tools-suite/src/dcp/compress-tool.ts +1 -1
- package/external/pi-tools-suite/src/dcp/index.ts +1 -1
- package/external/pi-tools-suite/src/lsp/constants.ts +1 -0
- package/external/pi-tools-suite/src/lsp/manager.ts +120 -71
- package/external/pi-tools-suite/src/model-tools/apply-patch.ts +1 -1
- package/external/pi-tools-suite/src/model-tools/index.ts +1 -1
- package/external/pi-tools-suite/src/todo/tool/response-envelope.ts +2 -2
- package/external/pi-tools-suite/src/tool-descriptions.ts +2 -2
- package/external/pi-tools-suite/src/usage/lib/google.ts +39 -4
- package/package.json +4 -7
package/dist/app/app.d.ts
CHANGED
|
@@ -81,6 +81,8 @@ export declare class PiUiExtendApp {
|
|
|
81
81
|
private setVoicePartialTranscript;
|
|
82
82
|
private addVoiceSystemMessage;
|
|
83
83
|
private resetSessionView;
|
|
84
|
+
private captureSessionView;
|
|
85
|
+
private restoreSessionView;
|
|
84
86
|
private loadSessionHistory;
|
|
85
87
|
private openSearchResultInNewTab;
|
|
86
88
|
private scrollToUserMessageJumpTarget;
|
package/dist/app/app.js
CHANGED
|
@@ -41,7 +41,7 @@ import { TabLineRenderer } from "./rendering/tab-line-renderer.js";
|
|
|
41
41
|
import { AppTerminalController } from "./terminal/terminal-controller.js";
|
|
42
42
|
import { TerminalBellSoundController } from "./terminal/terminal-bell-sound-controller.js";
|
|
43
43
|
import { AppToastController } from "./rendering/toast-controller.js";
|
|
44
|
-
import { checkPixUpdate, formatPixStartupUpdateDialog } from "./cli/update.js";
|
|
44
|
+
import { checkPiUpdate, checkPixUpdate, formatPixStartupUpdateDialog, formatPiStartupUpdateToast } from "./cli/update.js";
|
|
45
45
|
import { AppVoiceController } from "./input/voice-controller.js";
|
|
46
46
|
import { createIsolatedExtensionEventBus } from "./extensions/extension-event-bus.js";
|
|
47
47
|
import { setAppIconTheme } from "./icons.js";
|
|
@@ -181,6 +181,8 @@ export class PiUiExtendApp {
|
|
|
181
181
|
resetSessionView: () => this.resetSessionView(),
|
|
182
182
|
loadSessionHistory: () => this.loadSessionHistory(),
|
|
183
183
|
loadSessionHistoryAsync: (options) => this.loadSessionHistoryAsync(options),
|
|
184
|
+
captureSessionView: () => this.captureSessionView(),
|
|
185
|
+
restoreSessionView: (view) => this.restoreSessionView(view),
|
|
184
186
|
syncUserSessionEntryMetadata: () => this.workspaceActions.syncUserSessionEntryMetadata(),
|
|
185
187
|
captureInputState: () => this.inputEditor.draftState,
|
|
186
188
|
restoreInputState: (state) => this.restoreTabInputState(state),
|
|
@@ -785,18 +787,27 @@ export class PiUiExtendApp {
|
|
|
785
787
|
await this.sessionLifecycle.start();
|
|
786
788
|
this.modelUsageController.startPolling();
|
|
787
789
|
this.nerdFontController.ensureInstalledOnStartup();
|
|
788
|
-
|
|
790
|
+
this.checkPixUpdateOnStartup();
|
|
789
791
|
}
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
792
|
+
checkPixUpdateOnStartup() {
|
|
793
|
+
void checkPiUpdate()
|
|
794
|
+
.then((result) => {
|
|
795
|
+
if (result.status !== "newer")
|
|
796
|
+
return;
|
|
797
|
+
this.showToast(formatPiStartupUpdateToast(result), "warning");
|
|
798
|
+
})
|
|
799
|
+
.catch(() => {
|
|
800
|
+
// Startup update checks should never interrupt the TUI.
|
|
801
|
+
});
|
|
802
|
+
void checkPixUpdate()
|
|
803
|
+
.then((result) => {
|
|
793
804
|
if (result.status !== "newer")
|
|
794
805
|
return;
|
|
795
806
|
this.showToast(formatPixStartupUpdateDialog(result), "warning", { variant: "dialog" });
|
|
796
|
-
}
|
|
797
|
-
|
|
807
|
+
})
|
|
808
|
+
.catch(() => {
|
|
798
809
|
// Startup update checks should never interrupt the TUI.
|
|
799
|
-
}
|
|
810
|
+
});
|
|
800
811
|
}
|
|
801
812
|
async bindCurrentSession(options) {
|
|
802
813
|
await this.sessionLifecycle.bindCurrentSession(options);
|
|
@@ -889,6 +900,18 @@ export class PiUiExtendApp {
|
|
|
889
900
|
resetSessionView() {
|
|
890
901
|
this.sessionLifecycle.resetSessionView();
|
|
891
902
|
}
|
|
903
|
+
captureSessionView() {
|
|
904
|
+
return {
|
|
905
|
+
entries: [...this.entries],
|
|
906
|
+
eventState: this.sessionEvents.snapshotState(),
|
|
907
|
+
};
|
|
908
|
+
}
|
|
909
|
+
restoreSessionView(view) {
|
|
910
|
+
this.entries.splice(0, this.entries.length, ...view.entries);
|
|
911
|
+
this.sessionEvents.restoreState(view.eventState);
|
|
912
|
+
this.conversationViewport.clear();
|
|
913
|
+
this.workspaceActions.syncUserSessionEntryMetadata();
|
|
914
|
+
}
|
|
892
915
|
loadSessionHistory() {
|
|
893
916
|
void this.sessionEvents.loadSessionHistoryAsync({
|
|
894
917
|
isCancelled: () => !this.running,
|
package/dist/app/cli/update.d.ts
CHANGED
|
@@ -33,12 +33,17 @@ export type PixUpdateCheckOptions = {
|
|
|
33
33
|
packageRoot?: string;
|
|
34
34
|
fetchLatestVersion?: (packageName: string, currentVersion: string, timeoutMs: number) => Promise<string | undefined>;
|
|
35
35
|
};
|
|
36
|
+
export type PiUpdateCheckOptions = PixUpdateCheckOptions & {
|
|
37
|
+
pixPackageRoot?: string;
|
|
38
|
+
};
|
|
36
39
|
export declare function pixUpdateUsage(): string;
|
|
37
40
|
export declare function parsePixUpdateArgs(argv: readonly string[]): PixUpdateCliOptions;
|
|
38
41
|
export declare function getPixPackageVersion(packageRoot?: string): string;
|
|
39
42
|
export declare function checkPixUpdate(options?: PixUpdateCheckOptions): Promise<PixUpdateCheckResult>;
|
|
43
|
+
export declare function checkPiUpdate(options?: PiUpdateCheckOptions): Promise<PixUpdateCheckResult>;
|
|
40
44
|
export declare function formatPixUpdateCheck(result: PixUpdateCheckResult): string;
|
|
41
45
|
export declare function formatPixStartupUpdateDialog(result: PixUpdateCheckResult): string;
|
|
46
|
+
export declare function formatPiStartupUpdateToast(result: PixUpdateCheckResult): string;
|
|
42
47
|
export declare function getPixSelfUpdateCommand(packageName: string, latestVersion?: string, packageRoot?: string): PixSelfUpdateCommand | undefined;
|
|
43
48
|
export declare function runPixUpdateCli(argv?: readonly string[]): Promise<number>;
|
|
44
49
|
declare function runCommand(command: PixSelfUpdateCommand): Promise<void>;
|
package/dist/app/cli/update.js
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
2
|
import { existsSync, readFileSync } from "node:fs";
|
|
3
|
+
import { createRequire } from "node:module";
|
|
3
4
|
import { dirname, join, resolve } from "node:path";
|
|
4
5
|
import { fileURLToPath } from "node:url";
|
|
5
6
|
import { getAgentDir, SettingsManager } from "@earendil-works/pi-coding-agent";
|
|
6
7
|
const DEFAULT_UPDATE_TIMEOUT_MS = 10_000;
|
|
7
8
|
const NPM_REGISTRY_URL = "https://registry.npmjs.org";
|
|
9
|
+
const PI_PACKAGE_NAME = "@earendil-works/pi-coding-agent";
|
|
10
|
+
const requireFromUpdateModule = createRequire(import.meta.url);
|
|
8
11
|
const defaultPixUpdateDeps = {
|
|
9
12
|
checkPixUpdate,
|
|
10
13
|
runCommand,
|
|
@@ -53,6 +56,14 @@ export function getPixPackageVersion(packageRoot) {
|
|
|
53
56
|
}
|
|
54
57
|
export async function checkPixUpdate(options = {}) {
|
|
55
58
|
const packageInfo = readPixPackageInfo(options.packageRoot);
|
|
59
|
+
return await checkPackageUpdate(packageInfo, options);
|
|
60
|
+
}
|
|
61
|
+
export async function checkPiUpdate(options = {}) {
|
|
62
|
+
const packageRoot = options.packageRoot ?? findPiPackageRoot(options.pixPackageRoot);
|
|
63
|
+
const packageInfo = readPackageInfo(packageRoot, PI_PACKAGE_NAME);
|
|
64
|
+
return await checkPackageUpdate(packageInfo, options);
|
|
65
|
+
}
|
|
66
|
+
async function checkPackageUpdate(packageInfo, options) {
|
|
56
67
|
const base = {
|
|
57
68
|
packageName: packageInfo.name,
|
|
58
69
|
currentVersion: packageInfo.version,
|
|
@@ -124,6 +135,9 @@ export function formatPixStartupUpdateDialog(result) {
|
|
|
124
135
|
`current: ${result.packageName} v${result.currentVersion}`,
|
|
125
136
|
...(result.latestVersion ? [`latest: ${result.latestVersion}`] : []),
|
|
126
137
|
"",
|
|
138
|
+
"Pix includes the pinned Pi SDK/dependencies used by this renderer.",
|
|
139
|
+
"Updating only the global `pi` CLI is not enough for Pix.",
|
|
140
|
+
"",
|
|
127
141
|
"To update:",
|
|
128
142
|
"1. Exit Pix.",
|
|
129
143
|
"2. Run `pix update` in your shell.",
|
|
@@ -131,6 +145,11 @@ export function formatPixStartupUpdateDialog(result) {
|
|
|
131
145
|
];
|
|
132
146
|
return lines.join("\n");
|
|
133
147
|
}
|
|
148
|
+
export function formatPiStartupUpdateToast(result) {
|
|
149
|
+
return result.latestVersion
|
|
150
|
+
? `Pi ${result.latestVersion} is available; Pix bundles Pi ${result.currentVersion}. Waiting for a matching Pix update.`
|
|
151
|
+
: `Pi update detected; Pix bundles Pi ${result.currentVersion}. Waiting for a matching Pix update.`;
|
|
152
|
+
}
|
|
134
153
|
export function getPixSelfUpdateCommand(packageName, latestVersion, packageRoot = readPixPackageInfo().packageRoot) {
|
|
135
154
|
if (!packageRootLooksPackageManaged(packageRoot))
|
|
136
155
|
return undefined;
|
|
@@ -191,9 +210,12 @@ export async function runPixUpdateCli(argv = process.argv.slice(2)) {
|
|
|
191
210
|
}
|
|
192
211
|
}
|
|
193
212
|
function readPixPackageInfo(packageRoot = findPixPackageRoot()) {
|
|
213
|
+
return readPackageInfo(packageRoot, "pi-ui-extend");
|
|
214
|
+
}
|
|
215
|
+
function readPackageInfo(packageRoot, fallbackName) {
|
|
194
216
|
const packageJsonPath = join(packageRoot, "package.json");
|
|
195
217
|
const raw = JSON.parse(readFileSync(packageJsonPath, "utf8"));
|
|
196
|
-
const name = typeof raw.name === "string" && raw.name.trim() ? raw.name.trim() :
|
|
218
|
+
const name = typeof raw.name === "string" && raw.name.trim() ? raw.name.trim() : fallbackName;
|
|
197
219
|
const version = typeof raw.version === "string" && raw.version.trim() ? raw.version.trim() : "0.0.0";
|
|
198
220
|
return {
|
|
199
221
|
name,
|
|
@@ -214,6 +236,12 @@ function findPixPackageRoot() {
|
|
|
214
236
|
currentDir = nextDir;
|
|
215
237
|
}
|
|
216
238
|
}
|
|
239
|
+
function findPiPackageRoot(pixPackageRoot = readPixPackageInfo().packageRoot) {
|
|
240
|
+
const packageJsonPath = requireFromUpdateModule.resolve(`${PI_PACKAGE_NAME}/package.json`, {
|
|
241
|
+
paths: [pixPackageRoot],
|
|
242
|
+
});
|
|
243
|
+
return dirname(packageJsonPath);
|
|
244
|
+
}
|
|
217
245
|
async function fetchLatestNpmVersion(packageName, currentVersion, timeoutMs) {
|
|
218
246
|
const response = await fetch(`${NPM_REGISTRY_URL}/${encodeURIComponent(packageName)}/latest`, {
|
|
219
247
|
headers: {
|
|
@@ -89,6 +89,8 @@ type AntigravityQuotaAccount = {
|
|
|
89
89
|
readonly email?: string;
|
|
90
90
|
readonly refreshToken: string;
|
|
91
91
|
readonly accessToken?: string;
|
|
92
|
+
readonly clientId?: string;
|
|
93
|
+
readonly clientSecret?: string;
|
|
92
94
|
readonly cachedQuota?: AntigravityCachedQuota;
|
|
93
95
|
readonly cachedQuotaUpdatedAt?: number;
|
|
94
96
|
readonly projectId: string;
|
|
@@ -8,6 +8,8 @@ const OPENAI_USAGE_URL = "https://chatgpt.com/backend-api/wham/usage";
|
|
|
8
8
|
const ZAI_QUOTA_URL = "https://api.z.ai/api/monitor/usage/quota/limit";
|
|
9
9
|
const ZHIPU_QUOTA_URL = "https://bigmodel.cn/api/monitor/usage/quota/limit";
|
|
10
10
|
const GOOGLE_QUOTA_API_URL = "https://cloudcode-pa.googleapis.com/v1internal:fetchAvailableModels";
|
|
11
|
+
const GOOGLE_TOKEN_REFRESH_URL = "https://oauth2.googleapis.com/token";
|
|
12
|
+
const GOOGLE_ANTIGRAVITY_USER_AGENT = "antigravity/1.11.9 windows/amd64";
|
|
11
13
|
const REQUEST_TIMEOUT_MS = 10_000;
|
|
12
14
|
const DAY_SECONDS = 86_400;
|
|
13
15
|
const HOUR_SECONDS = 3_600;
|
|
@@ -383,11 +385,11 @@ export function resolveAntigravityQuotaModelKey(model) {
|
|
|
383
385
|
}
|
|
384
386
|
export function googleAntigravityUsageStatusFromResponse(data, descriptor, now = Date.now()) {
|
|
385
387
|
const quotaInfo = data.models[descriptor.quotaModelKey]?.quotaInfo;
|
|
386
|
-
if (!quotaInfo
|
|
388
|
+
if (!quotaInfo)
|
|
387
389
|
return undefined;
|
|
388
390
|
const resetAt = parseResetTime(quotaInfo.resetTime, now);
|
|
389
391
|
const window = {
|
|
390
|
-
remainingPercent:
|
|
392
|
+
remainingPercent: quotaRemainingPercent(quotaInfo),
|
|
391
393
|
resetAt,
|
|
392
394
|
windowSeconds: Math.max(0, Math.round((resetAt - now) / 1000)),
|
|
393
395
|
};
|
|
@@ -404,13 +406,8 @@ export function googleAntigravityUsageStatusFromResponse(data, descriptor, now =
|
|
|
404
406
|
}
|
|
405
407
|
async function queryGoogleAntigravityModelUsage(descriptor) {
|
|
406
408
|
const now = Date.now();
|
|
407
|
-
const
|
|
408
|
-
|
|
409
|
-
return googleAntigravityUsageStatusFromResponse(cachedResponse, descriptor, now);
|
|
410
|
-
if (!descriptor.account.accessToken)
|
|
411
|
-
return undefined;
|
|
412
|
-
const response = await fetchGoogleAntigravityQuota(descriptor.account.accessToken, descriptor.account.projectId);
|
|
413
|
-
return googleAntigravityUsageStatusFromResponse(response, descriptor);
|
|
409
|
+
const response = await fetchGoogleAntigravityQuotaForAccount(descriptor.account, now);
|
|
410
|
+
return googleAntigravityUsageStatusFromResponse(response, descriptor, now);
|
|
414
411
|
}
|
|
415
412
|
const GOOGLE_ACCOUNT_QUOTA_WINDOWS = [
|
|
416
413
|
{ label: "Claude Opus", quotaModelKey: "claude-opus-4-6-thinking" },
|
|
@@ -424,12 +421,8 @@ async function queryGoogleAntigravityAccountUsage(now) {
|
|
|
424
421
|
const results = await Promise.all(accounts.map(async (account) => {
|
|
425
422
|
const accountLabel = account.email ?? maskCredential(account.refreshToken);
|
|
426
423
|
try {
|
|
427
|
-
const response =
|
|
428
|
-
const windows =
|
|
429
|
-
if (windows.length === 0 && account.accessToken) {
|
|
430
|
-
const liveResponse = await fetchGoogleAntigravityQuota(account.accessToken, account.projectId);
|
|
431
|
-
windows.push(...googleAccountWindowsFromResponse(liveResponse, now));
|
|
432
|
-
}
|
|
424
|
+
const response = await fetchGoogleAntigravityQuotaForAccount(account, now);
|
|
425
|
+
const windows = googleAccountWindowsFromResponse(response, now);
|
|
433
426
|
return {
|
|
434
427
|
account: accountLabel,
|
|
435
428
|
windows,
|
|
@@ -456,12 +449,14 @@ function readAllAntigravityQuotaAccounts() {
|
|
|
456
449
|
const credential = readPiAuthSync().antigravity;
|
|
457
450
|
if (!credential)
|
|
458
451
|
return [];
|
|
452
|
+
const credentialClient = getGoogleOAuthClientCredentials(credential);
|
|
459
453
|
const accounts = storedAntigravityAccounts(credential);
|
|
460
454
|
if (accounts.length > 0) {
|
|
461
455
|
const activeIndex = clampAccountIndex(credential.activeIndex, accounts.length);
|
|
462
456
|
const activeAccess = antigravityAccessFromCredential(credential);
|
|
463
457
|
return accounts.map((account, accountIndex) => antigravityQuotaAccount(account, {
|
|
464
458
|
...(credential.email ? { fallbackEmail: credential.email } : {}),
|
|
459
|
+
...(credentialClient ? { clientCredentials: credentialClient } : {}),
|
|
465
460
|
...(accountIndex === activeIndex && activeAccess ? { accessToken: activeAccess.accessToken } : {}),
|
|
466
461
|
accountIndex,
|
|
467
462
|
accountCount: accounts.length,
|
|
@@ -471,6 +466,7 @@ function readAllAntigravityQuotaAccounts() {
|
|
|
471
466
|
const fallbackAccess = antigravityAccessFromCredential(credential);
|
|
472
467
|
const account = fallbackAccount ? antigravityQuotaAccount(fallbackAccount, {
|
|
473
468
|
...(credential.email ? { fallbackEmail: credential.email } : {}),
|
|
469
|
+
...(credentialClient ? { clientCredentials: credentialClient } : {}),
|
|
474
470
|
...(fallbackAccess ? { accessToken: fallbackAccess.accessToken } : {}),
|
|
475
471
|
}) : undefined;
|
|
476
472
|
return account ? [account] : [];
|
|
@@ -483,9 +479,41 @@ function readPiAuthSync() {
|
|
|
483
479
|
return {};
|
|
484
480
|
}
|
|
485
481
|
}
|
|
482
|
+
function getAccountRefreshToken(account) {
|
|
483
|
+
if (account.refreshToken)
|
|
484
|
+
return account.refreshToken;
|
|
485
|
+
if (!account.refresh)
|
|
486
|
+
return undefined;
|
|
487
|
+
return splitAntigravityRefresh(account.refresh).refreshToken;
|
|
488
|
+
}
|
|
489
|
+
function stringProperty(source, keys) {
|
|
490
|
+
if (!source || typeof source !== "object")
|
|
491
|
+
return undefined;
|
|
492
|
+
const record = source;
|
|
493
|
+
for (const key of keys) {
|
|
494
|
+
const value = record[key];
|
|
495
|
+
if (typeof value === "string" && value)
|
|
496
|
+
return value;
|
|
497
|
+
}
|
|
498
|
+
return undefined;
|
|
499
|
+
}
|
|
500
|
+
function getGoogleOAuthClientCredentials(...sources) {
|
|
501
|
+
for (const source of sources) {
|
|
502
|
+
const nested = source && typeof source === "object"
|
|
503
|
+
? source.oauthClient
|
|
504
|
+
: undefined;
|
|
505
|
+
const nestedClientId = stringProperty(nested, ["clientId", "client_id", "id"]);
|
|
506
|
+
const nestedClientSecret = stringProperty(nested, ["clientSecret", "client_secret", "secret"]);
|
|
507
|
+
const clientId = nestedClientId ?? stringProperty(source, ["clientId", "client_id", "googleClientId", "google_client_id", "oauthClientId", "oauth_client_id"]);
|
|
508
|
+
const clientSecret = nestedClientSecret ?? stringProperty(source, ["clientSecret", "client_secret", "googleClientSecret", "google_client_secret", "oauthClientSecret", "oauth_client_secret"]);
|
|
509
|
+
if (clientId)
|
|
510
|
+
return { clientId, ...(clientSecret ? { clientSecret } : {}) };
|
|
511
|
+
}
|
|
512
|
+
return undefined;
|
|
513
|
+
}
|
|
486
514
|
function storedAntigravityAccounts(credential) {
|
|
487
515
|
return Array.isArray(credential.accounts)
|
|
488
|
-
? credential.accounts.filter((account) => account.enabled !== false && !!account
|
|
516
|
+
? credential.accounts.filter((account) => account.enabled !== false && !!getAccountRefreshToken(account))
|
|
489
517
|
: [];
|
|
490
518
|
}
|
|
491
519
|
function antigravityAccountFromCredential(credential) {
|
|
@@ -506,16 +534,19 @@ function antigravityAccountFromCredential(credential) {
|
|
|
506
534
|
};
|
|
507
535
|
}
|
|
508
536
|
function antigravityQuotaAccount(account, options = {}) {
|
|
509
|
-
const refreshToken = account
|
|
537
|
+
const refreshToken = getAccountRefreshToken(account);
|
|
510
538
|
if (!refreshToken)
|
|
511
539
|
return undefined;
|
|
512
540
|
const email = account.email || options.fallbackEmail;
|
|
513
541
|
const projectId = account.projectId || account.managedProjectId || DEFAULT_ANTIGRAVITY_PROJECT_ID;
|
|
542
|
+
const clientCredentials = getGoogleOAuthClientCredentials(account, options.clientCredentials);
|
|
514
543
|
return {
|
|
515
544
|
refreshToken,
|
|
516
545
|
projectId,
|
|
517
546
|
cacheKey: email ? email.toLowerCase() : shortHash(refreshToken),
|
|
518
547
|
...(options.accessToken ? { accessToken: options.accessToken } : {}),
|
|
548
|
+
...(clientCredentials ? { clientId: clientCredentials.clientId } : {}),
|
|
549
|
+
...(clientCredentials?.clientSecret ? { clientSecret: clientCredentials.clientSecret } : {}),
|
|
519
550
|
...(account.cachedQuota ? { cachedQuota: account.cachedQuota } : {}),
|
|
520
551
|
...(typeof account.cachedQuotaUpdatedAt === "number" ? { cachedQuotaUpdatedAt: account.cachedQuotaUpdatedAt } : {}),
|
|
521
552
|
...(email ? { email } : {}),
|
|
@@ -590,13 +621,49 @@ function clampAccountIndex(index, accountCount) {
|
|
|
590
621
|
function shortHash(value) {
|
|
591
622
|
return createHash("sha256").update(value).digest("hex").slice(0, 12);
|
|
592
623
|
}
|
|
624
|
+
async function fetchGoogleAntigravityQuotaForAccount(account, now = Date.now()) {
|
|
625
|
+
if (account.accessToken)
|
|
626
|
+
return await fetchGoogleAntigravityQuota(account.accessToken, account.projectId);
|
|
627
|
+
if (account.clientId) {
|
|
628
|
+
const { accessToken } = await refreshGoogleAntigravityAccessToken(account);
|
|
629
|
+
return await fetchGoogleAntigravityQuota(accessToken, account.projectId);
|
|
630
|
+
}
|
|
631
|
+
const cachedResponse = googleQuotaResponseFromCachedQuota(account.cachedQuota, account.cachedQuotaUpdatedAt, now);
|
|
632
|
+
if (cachedResponse)
|
|
633
|
+
return cachedResponse;
|
|
634
|
+
throw new Error("Missing Google OAuth client credentials, cannot query live Antigravity quota.");
|
|
635
|
+
}
|
|
636
|
+
async function refreshGoogleAntigravityAccessToken(account) {
|
|
637
|
+
if (!account.clientId)
|
|
638
|
+
throw new Error("Missing Google OAuth client id, cannot refresh Antigravity access token.");
|
|
639
|
+
const params = new URLSearchParams({
|
|
640
|
+
client_id: account.clientId,
|
|
641
|
+
refresh_token: account.refreshToken,
|
|
642
|
+
grant_type: "refresh_token",
|
|
643
|
+
});
|
|
644
|
+
if (account.clientSecret)
|
|
645
|
+
params.set("client_secret", account.clientSecret);
|
|
646
|
+
const response = await fetchWithTimeout(GOOGLE_TOKEN_REFRESH_URL, {
|
|
647
|
+
method: "POST",
|
|
648
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
649
|
+
body: params,
|
|
650
|
+
});
|
|
651
|
+
if (!response.ok) {
|
|
652
|
+
const errorText = await response.text();
|
|
653
|
+
throw new Error(`Google token refresh failed (${response.status}): ${errorText}`);
|
|
654
|
+
}
|
|
655
|
+
const data = await response.json();
|
|
656
|
+
if (!data.access_token)
|
|
657
|
+
throw new Error("Google token refresh did not return an access token.");
|
|
658
|
+
return { accessToken: data.access_token };
|
|
659
|
+
}
|
|
593
660
|
async function fetchGoogleAntigravityQuota(accessToken, projectId) {
|
|
594
661
|
const response = await fetchWithTimeout(GOOGLE_QUOTA_API_URL, {
|
|
595
662
|
method: "POST",
|
|
596
663
|
headers: {
|
|
597
664
|
"Content-Type": "application/json",
|
|
598
665
|
Authorization: `Bearer ${accessToken}`,
|
|
599
|
-
"User-Agent":
|
|
666
|
+
"User-Agent": GOOGLE_ANTIGRAVITY_USER_AGENT,
|
|
600
667
|
},
|
|
601
668
|
body: JSON.stringify({ project: projectId }),
|
|
602
669
|
});
|
|
@@ -757,16 +824,19 @@ function accountWindowFromRateLimit(window, now) {
|
|
|
757
824
|
}
|
|
758
825
|
function googleAccountWindowFromResponse(data, label, quotaModelKey, now) {
|
|
759
826
|
const quotaInfo = data.models[quotaModelKey]?.quotaInfo;
|
|
760
|
-
if (!quotaInfo
|
|
827
|
+
if (!quotaInfo)
|
|
761
828
|
return undefined;
|
|
762
829
|
const resetAt = parseResetTime(quotaInfo.resetTime, now);
|
|
763
830
|
return {
|
|
764
831
|
label,
|
|
765
|
-
remainingPercent:
|
|
832
|
+
remainingPercent: quotaRemainingPercent(quotaInfo),
|
|
766
833
|
resetAt,
|
|
767
834
|
windowSeconds: Math.max(0, Math.round((resetAt - now) / 1000)),
|
|
768
835
|
};
|
|
769
836
|
}
|
|
837
|
+
function quotaRemainingPercent(quotaInfo) {
|
|
838
|
+
return clampPercent(Math.round((Number.isFinite(quotaInfo.remainingFraction) ? quotaInfo.remainingFraction : 0) * 100));
|
|
839
|
+
}
|
|
770
840
|
function googleAccountWindowsFromResponse(data, now) {
|
|
771
841
|
return GOOGLE_ACCOUNT_QUOTA_WINDOWS
|
|
772
842
|
.map((window) => googleAccountWindowFromResponse(data, window.label, window.quotaModelKey, now))
|
|
@@ -1,8 +1,22 @@
|
|
|
1
1
|
import type { AgentSessionEvent, AgentSessionRuntime } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import type { ConversationViewport } from "../rendering/conversation-viewport.js";
|
|
3
|
-
import { type LoadOlderSessionHistoryOptions } from "./session-history.js";
|
|
3
|
+
import { type LoadOlderSessionHistoryOptions, type SessionHistoryOlderLoader } from "./session-history.js";
|
|
4
4
|
import type { Entry, SessionActivity } from "../types.js";
|
|
5
5
|
import type { WorkspaceMutation, WorkspaceMutationPreparation } from "../workspace/workspace-undo.js";
|
|
6
|
+
export type AppSessionEventControllerState = {
|
|
7
|
+
toolEntryIdsByCallId: Map<string, string>;
|
|
8
|
+
toolMutationPreparationsByCallId: Map<string, {
|
|
9
|
+
userEntryId: string;
|
|
10
|
+
args: unknown;
|
|
11
|
+
preparation?: WorkspaceMutationPreparation;
|
|
12
|
+
}>;
|
|
13
|
+
olderHistoryLoader: SessionHistoryOlderLoader | undefined;
|
|
14
|
+
currentUserEntryId: string | undefined;
|
|
15
|
+
currentAssistantEntryId: string | undefined;
|
|
16
|
+
currentThinkingEntryId: string | undefined;
|
|
17
|
+
assistantTextBuffer: string;
|
|
18
|
+
entryRenderVersions: Map<string, number>;
|
|
19
|
+
};
|
|
6
20
|
export type AppSessionEventControllerHost = {
|
|
7
21
|
readonly entries: Entry[];
|
|
8
22
|
runtime(): AgentSessionRuntime | undefined;
|
|
@@ -45,6 +59,8 @@ export declare class AppSessionEventController {
|
|
|
45
59
|
private currentThinkingEntryId;
|
|
46
60
|
private assistantTextBuffer;
|
|
47
61
|
constructor(host: AppSessionEventControllerHost);
|
|
62
|
+
snapshotState(): AppSessionEventControllerState;
|
|
63
|
+
restoreState(state: AppSessionEventControllerState): void;
|
|
48
64
|
reset(): void;
|
|
49
65
|
loadSessionHistory(): void;
|
|
50
66
|
loadSessionHistoryAsync(options: {
|
|
@@ -20,6 +20,34 @@ export class AppSessionEventController {
|
|
|
20
20
|
constructor(host) {
|
|
21
21
|
this.host = host;
|
|
22
22
|
}
|
|
23
|
+
snapshotState() {
|
|
24
|
+
return {
|
|
25
|
+
toolEntryIdsByCallId: new Map(this.toolEntryIdsByCallId),
|
|
26
|
+
toolMutationPreparationsByCallId: new Map(this.toolMutationPreparationsByCallId),
|
|
27
|
+
olderHistoryLoader: this.olderHistoryLoader,
|
|
28
|
+
currentUserEntryId: this.currentUserEntryId,
|
|
29
|
+
currentAssistantEntryId: this.currentAssistantEntryId,
|
|
30
|
+
currentThinkingEntryId: this.currentThinkingEntryId,
|
|
31
|
+
assistantTextBuffer: this.assistantTextBuffer,
|
|
32
|
+
entryRenderVersions: new Map(this.entryRenderVersions),
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
restoreState(state) {
|
|
36
|
+
this.toolEntryIdsByCallId.clear();
|
|
37
|
+
for (const [key, value] of state.toolEntryIdsByCallId)
|
|
38
|
+
this.toolEntryIdsByCallId.set(key, value);
|
|
39
|
+
this.toolMutationPreparationsByCallId.clear();
|
|
40
|
+
for (const [key, value] of state.toolMutationPreparationsByCallId)
|
|
41
|
+
this.toolMutationPreparationsByCallId.set(key, value);
|
|
42
|
+
this.olderHistoryLoader = state.olderHistoryLoader;
|
|
43
|
+
this.currentUserEntryId = state.currentUserEntryId;
|
|
44
|
+
this.currentAssistantEntryId = state.currentAssistantEntryId;
|
|
45
|
+
this.currentThinkingEntryId = state.currentThinkingEntryId;
|
|
46
|
+
this.assistantTextBuffer = state.assistantTextBuffer;
|
|
47
|
+
this.entryRenderVersions.clear();
|
|
48
|
+
for (const [key, value] of state.entryRenderVersions)
|
|
49
|
+
this.entryRenderVersions.set(key, value);
|
|
50
|
+
}
|
|
23
51
|
reset() {
|
|
24
52
|
this.toolEntryIdsByCallId.clear();
|
|
25
53
|
this.toolMutationPreparationsByCallId.clear();
|
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
import { type AgentSession, type AgentSessionRuntime } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import type { BindCurrentSessionOptions } from "./session-lifecycle-controller.js";
|
|
3
|
+
import type { AppSessionEventControllerState } from "./session-event-controller.js";
|
|
3
4
|
import type { InputEditorDraftState } from "../../input-editor.js";
|
|
4
5
|
import type { AppBlinkController } from "../screen/blink-controller.js";
|
|
5
6
|
import type { AppOptions, Entry, SessionActivity, SessionTab, SubmittedUserMessage } from "../types.js";
|
|
7
|
+
type TabSessionView = {
|
|
8
|
+
entries: Entry[];
|
|
9
|
+
eventState: AppSessionEventControllerState;
|
|
10
|
+
};
|
|
6
11
|
export type TabInputState = InputEditorDraftState;
|
|
7
12
|
export type AppTabsControllerHost = {
|
|
8
13
|
readonly options: AppOptions;
|
|
@@ -24,6 +29,8 @@ export type AppTabsControllerHost = {
|
|
|
24
29
|
render: () => void;
|
|
25
30
|
lazyOlderHistory?: boolean;
|
|
26
31
|
}): Promise<boolean>;
|
|
32
|
+
captureSessionView?(): TabSessionView;
|
|
33
|
+
restoreSessionView?(view: TabSessionView): void;
|
|
27
34
|
syncUserSessionEntryMetadata(): void;
|
|
28
35
|
captureInputState(): TabInputState;
|
|
29
36
|
restoreInputState(state: TabInputState): void;
|
|
@@ -44,6 +51,7 @@ export declare class AppTabsController {
|
|
|
44
51
|
private readonly historyReloadTimersByTabId;
|
|
45
52
|
private readonly inputStatesByTabId;
|
|
46
53
|
private readonly deferredUserMessagesByTabId;
|
|
54
|
+
private readonly sessionViewsByTabId;
|
|
47
55
|
private readonly tabIdsNeedingHistoryReload;
|
|
48
56
|
private activeTabId;
|
|
49
57
|
private pendingActiveTabId;
|
|
@@ -83,6 +91,7 @@ export declare class AppTabsController {
|
|
|
83
91
|
private activeTab;
|
|
84
92
|
private clearStartupTabPlaceholders;
|
|
85
93
|
private storeActiveRuntime;
|
|
94
|
+
private storeActiveSessionView;
|
|
86
95
|
private setRuntimeForTab;
|
|
87
96
|
private deleteRuntimeForTab;
|
|
88
97
|
private clearRuntimeSubscriptions;
|
|
@@ -138,3 +147,4 @@ export declare class AppTabsController {
|
|
|
138
147
|
private preservedSessionPaths;
|
|
139
148
|
private maxProjectSessions;
|
|
140
149
|
}
|
|
150
|
+
export {};
|