paseo-acp-agy 1.2.3 → 1.2.5
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/acp-server.js +17 -8
- package/dist/asar.d.ts +11 -2
- package/dist/asar.js +65 -20
- package/dist/paseo-patcher.d.ts +8 -2
- package/dist/paseo-patcher.js +564 -69
- package/dist/protocol.js +6 -2
- package/dist/session.d.ts +1 -1
- package/dist/session.js +19 -4
- package/package.json +1 -1
package/dist/acp-server.js
CHANGED
|
@@ -85,12 +85,14 @@ export class ACPServer {
|
|
|
85
85
|
}
|
|
86
86
|
publishUsageUpdate(session) {
|
|
87
87
|
const costUsd = roundUsageCostUsd(session.usage.totalCostUsd);
|
|
88
|
+
const maxTokens = session.usage.contextWindowMaxTokens;
|
|
89
|
+
const usedTokens = Math.min(session.usage.contextWindowUsedTokens, maxTokens);
|
|
88
90
|
this.sendNotification(ACP_METHODS.SESSION_UPDATE, {
|
|
89
91
|
sessionId: session.id,
|
|
90
92
|
update: {
|
|
91
93
|
sessionUpdate: "usage_update",
|
|
92
|
-
size:
|
|
93
|
-
used:
|
|
94
|
+
size: maxTokens,
|
|
95
|
+
used: usedTokens,
|
|
94
96
|
cost: { amount: costUsd, currency: "USD" },
|
|
95
97
|
inputTokens: session.usage.inputTokens,
|
|
96
98
|
outputTokens: session.usage.outputTokens,
|
|
@@ -98,8 +100,8 @@ export class ACPServer {
|
|
|
98
100
|
cachedInputTokens: session.usage.cachedInputTokens,
|
|
99
101
|
totalTokens: session.usage.totalTokens,
|
|
100
102
|
totalCostUsd: costUsd,
|
|
101
|
-
contextWindowMaxTokens:
|
|
102
|
-
contextWindowUsedTokens:
|
|
103
|
+
contextWindowMaxTokens: maxTokens,
|
|
104
|
+
contextWindowUsedTokens: usedTokens,
|
|
103
105
|
},
|
|
104
106
|
});
|
|
105
107
|
}
|
|
@@ -138,7 +140,7 @@ export class ACPServer {
|
|
|
138
140
|
const thought = turnUsage?.thinking_tokens;
|
|
139
141
|
const total = turnUsage?.total_tokens ?? (input + output);
|
|
140
142
|
const maxTokens = getModelContextWindow(executingModel || session.model);
|
|
141
|
-
const usedTokens = session.usage.contextWindowUsedTokens;
|
|
143
|
+
const usedTokens = Math.min(session.usage.contextWindowUsedTokens, maxTokens);
|
|
142
144
|
const costUsd = roundUsageCostUsd(session.usage.totalCostUsd);
|
|
143
145
|
return {
|
|
144
146
|
inputTokens: input,
|
|
@@ -356,7 +358,10 @@ export class ACPServer {
|
|
|
356
358
|
break;
|
|
357
359
|
}
|
|
358
360
|
let hasSentNonNarration = false;
|
|
361
|
+
let latestStepContextTokens;
|
|
362
|
+
let stepCount = 0;
|
|
359
363
|
const onStepUpdate = (event) => {
|
|
364
|
+
stepCount++;
|
|
360
365
|
const step = event.step_update;
|
|
361
366
|
if (!step)
|
|
362
367
|
return;
|
|
@@ -437,15 +442,18 @@ export class ACPServer {
|
|
|
437
442
|
}
|
|
438
443
|
}
|
|
439
444
|
if (step.usage) {
|
|
440
|
-
const turnCost = calculateUsageCostUsd(executingModel, step.usage.input_tokens || 0, step.usage.output_tokens || 0, step.usage.cache_read_tokens || 0);
|
|
441
445
|
const inputTokens = step.usage.input_tokens ?? 0;
|
|
442
446
|
const outputTokens = step.usage.output_tokens ?? 0;
|
|
447
|
+
if (inputTokens > 0) {
|
|
448
|
+
latestStepContextTokens = inputTokens + outputTokens;
|
|
449
|
+
}
|
|
450
|
+
const turnCost = calculateUsageCostUsd(executingModel, inputTokens, outputTokens, step.usage.cache_read_tokens || 0);
|
|
443
451
|
const cachedReadTokens = step.usage.cache_read_tokens ?? 0;
|
|
444
452
|
const thoughtTokens = step.usage.thinking_tokens;
|
|
445
453
|
const totalTokens = step.usage.total_tokens ?? (inputTokens + outputTokens);
|
|
446
454
|
const totalCostUsd = roundUsageCostUsd(session.usage.totalCostUsd + turnCost);
|
|
447
455
|
const contextWindowMaxTokens = getModelContextWindow(executingModel);
|
|
448
|
-
const contextWindowUsedTokens = inputTokens + outputTokens;
|
|
456
|
+
const contextWindowUsedTokens = Math.min(inputTokens + outputTokens, contextWindowMaxTokens);
|
|
449
457
|
this.sendNotification(ACP_METHODS.SESSION_UPDATE, {
|
|
450
458
|
sessionId: session.id,
|
|
451
459
|
update: {
|
|
@@ -468,9 +476,10 @@ export class ACPServer {
|
|
|
468
476
|
try {
|
|
469
477
|
const resultEvent = await session.process.sendPrompt(promptText, onStepUpdate);
|
|
470
478
|
const turnUsage = resultEvent.result?.usage;
|
|
479
|
+
const numTurns = resultEvent.result?.num_turns || stepCount || 1;
|
|
471
480
|
// Usage is billable even when the turn terminates with ERROR
|
|
472
481
|
// after model/tool work. Record it before branching on status.
|
|
473
|
-
session.recordTurnUsage(turnUsage, executingModel);
|
|
482
|
+
session.recordTurnUsage(turnUsage, executingModel, latestStepContextTokens, numTurns);
|
|
474
483
|
const usagePayload = this.turnUsagePayload(session, turnUsage, executingModel);
|
|
475
484
|
this.publishUsageUpdate(session);
|
|
476
485
|
if (session.isCancelled) {
|
package/dist/asar.d.ts
CHANGED
|
@@ -4,6 +4,11 @@ export interface AsarNode {
|
|
|
4
4
|
offset?: string;
|
|
5
5
|
executable?: boolean;
|
|
6
6
|
unpacked?: boolean;
|
|
7
|
+
link?: string;
|
|
8
|
+
}
|
|
9
|
+
export interface ExtractResult {
|
|
10
|
+
unpackedPaths: string[];
|
|
11
|
+
originalHeader: AsarNode;
|
|
7
12
|
}
|
|
8
13
|
/**
|
|
9
14
|
* Reads and parses the ASAR archive header and returns the root node and base offset for data.
|
|
@@ -20,9 +25,13 @@ export declare function listPackage(archivePath: string): string[];
|
|
|
20
25
|
* Pure TypeScript, zero-dependency ASAR archive extractor.
|
|
21
26
|
* Compatible with all Node.js versions (Node 18, 20, 22, 24+) across Windows, macOS, and Linux.
|
|
22
27
|
*/
|
|
23
|
-
export declare function extractAll(archivePath: string, destDir: string):
|
|
28
|
+
export declare function extractAll(archivePath: string, destDir: string): ExtractResult;
|
|
29
|
+
export interface CreatePackageOptions {
|
|
30
|
+
unpackedPaths?: string[];
|
|
31
|
+
originalHeader?: AsarNode;
|
|
32
|
+
}
|
|
24
33
|
/**
|
|
25
34
|
* Pure TypeScript, zero-dependency ASAR archive builder.
|
|
26
35
|
* Compatible with all Node.js versions (Node 18, 20, 22, 24+) across Windows, macOS, and Linux.
|
|
27
36
|
*/
|
|
28
|
-
export declare function createPackage(srcDir: string, destFile: string): Promise<void>;
|
|
37
|
+
export declare function createPackage(srcDir: string, destFile: string, options?: CreatePackageOptions): Promise<void>;
|
package/dist/asar.js
CHANGED
|
@@ -61,6 +61,7 @@ export function listPackage(archivePath) {
|
|
|
61
61
|
export function extractAll(archivePath, destDir) {
|
|
62
62
|
const { header, dataBaseOffset } = readArchiveHeader(archivePath);
|
|
63
63
|
const fd = fs.openSync(archivePath, "r");
|
|
64
|
+
const unpackedPaths = [];
|
|
64
65
|
try {
|
|
65
66
|
function extractNode(node, currentPath, relParts = []) {
|
|
66
67
|
if (node.files) {
|
|
@@ -69,6 +70,20 @@ export function extractAll(archivePath, destDir) {
|
|
|
69
70
|
extractNode(child, path.join(currentPath, name), [...relParts, name]);
|
|
70
71
|
}
|
|
71
72
|
}
|
|
73
|
+
else if (node.link) {
|
|
74
|
+
fs.mkdirSync(path.dirname(currentPath), { recursive: true });
|
|
75
|
+
try {
|
|
76
|
+
fs.unlinkSync(currentPath);
|
|
77
|
+
}
|
|
78
|
+
catch { }
|
|
79
|
+
fs.symlinkSync(node.link, currentPath);
|
|
80
|
+
}
|
|
81
|
+
else if (node.unpacked) {
|
|
82
|
+
// Unpacked files belong in archive.unpacked and must NOT be copied into destDir,
|
|
83
|
+
// so that re-packing does not duplicate external binaries into the asar payload.
|
|
84
|
+
const relPath = relParts.join("/");
|
|
85
|
+
unpackedPaths.push(relPath);
|
|
86
|
+
}
|
|
72
87
|
else if (node.size !== undefined && node.offset !== undefined) {
|
|
73
88
|
const fileOffset = dataBaseOffset + parseInt(node.offset, 10);
|
|
74
89
|
const fileBuf = Buffer.alloc(node.size);
|
|
@@ -80,15 +95,9 @@ export function extractAll(archivePath, destDir) {
|
|
|
80
95
|
mode: node.executable ? 0o755 : 0o644,
|
|
81
96
|
});
|
|
82
97
|
}
|
|
83
|
-
else if (node.unpacked) {
|
|
84
|
-
const unpackedSrc = path.join(archivePath + ".unpacked", ...relParts);
|
|
85
|
-
if (fs.existsSync(unpackedSrc)) {
|
|
86
|
-
fs.mkdirSync(path.dirname(currentPath), { recursive: true });
|
|
87
|
-
fs.copyFileSync(unpackedSrc, currentPath);
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
98
|
}
|
|
91
99
|
extractNode(header, destDir);
|
|
100
|
+
return { unpackedPaths, originalHeader: header };
|
|
92
101
|
}
|
|
93
102
|
finally {
|
|
94
103
|
fs.closeSync(fd);
|
|
@@ -98,7 +107,7 @@ export function extractAll(archivePath, destDir) {
|
|
|
98
107
|
* Pure TypeScript, zero-dependency ASAR archive builder.
|
|
99
108
|
* Compatible with all Node.js versions (Node 18, 20, 22, 24+) across Windows, macOS, and Linux.
|
|
100
109
|
*/
|
|
101
|
-
export async function createPackage(srcDir, destFile) {
|
|
110
|
+
export async function createPackage(srcDir, destFile, options) {
|
|
102
111
|
const files = [];
|
|
103
112
|
function walk(dir, rel = "") {
|
|
104
113
|
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
@@ -106,16 +115,25 @@ export async function createPackage(srcDir, destFile) {
|
|
|
106
115
|
for (const e of entries) {
|
|
107
116
|
const entryRel = rel ? `${rel}/${e.name}` : e.name;
|
|
108
117
|
const full = path.join(dir, e.name);
|
|
109
|
-
|
|
118
|
+
const lstat = fs.lstatSync(full);
|
|
119
|
+
if (lstat.isSymbolicLink()) {
|
|
120
|
+
files.push({
|
|
121
|
+
relPath: entryRel,
|
|
122
|
+
fullPath: full,
|
|
123
|
+
size: 0,
|
|
124
|
+
isLink: true,
|
|
125
|
+
linkTarget: fs.readlinkSync(full),
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
else if (lstat.isDirectory()) {
|
|
110
129
|
walk(full, entryRel);
|
|
111
130
|
}
|
|
112
|
-
else if (
|
|
113
|
-
const
|
|
114
|
-
const executable = Boolean(stat.mode & 0o111);
|
|
131
|
+
else if (lstat.isFile()) {
|
|
132
|
+
const executable = Boolean(lstat.mode & 0o111);
|
|
115
133
|
files.push({
|
|
116
134
|
relPath: entryRel,
|
|
117
135
|
fullPath: full,
|
|
118
|
-
size:
|
|
136
|
+
size: lstat.size,
|
|
119
137
|
executable: executable || undefined,
|
|
120
138
|
});
|
|
121
139
|
}
|
|
@@ -134,12 +152,39 @@ export async function createPackage(srcDir, destFile) {
|
|
|
134
152
|
curr = curr[parts[i]].files;
|
|
135
153
|
}
|
|
136
154
|
const leaf = parts[parts.length - 1];
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
155
|
+
if (f.isLink) {
|
|
156
|
+
curr[leaf] = { link: f.linkTarget };
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
curr[leaf] = {
|
|
160
|
+
size: f.size,
|
|
161
|
+
offset: String(currentOffset),
|
|
162
|
+
executable: f.executable,
|
|
163
|
+
};
|
|
164
|
+
currentOffset += f.size;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
// Restore unpacked file references from original archive if provided
|
|
168
|
+
if (options?.unpackedPaths && options?.originalHeader) {
|
|
169
|
+
for (const relPath of options.unpackedPaths) {
|
|
170
|
+
const parts = relPath.split("/");
|
|
171
|
+
let origNode = options.originalHeader;
|
|
172
|
+
for (const p of parts) {
|
|
173
|
+
origNode = origNode?.files?.[p];
|
|
174
|
+
}
|
|
175
|
+
let curr = header.files;
|
|
176
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
177
|
+
if (!curr[parts[i]]) {
|
|
178
|
+
curr[parts[i]] = { files: {} };
|
|
179
|
+
}
|
|
180
|
+
curr = curr[parts[i]].files;
|
|
181
|
+
}
|
|
182
|
+
const leaf = parts[parts.length - 1];
|
|
183
|
+
curr[leaf] = {
|
|
184
|
+
size: origNode?.size ?? 0,
|
|
185
|
+
unpacked: true,
|
|
186
|
+
};
|
|
187
|
+
}
|
|
143
188
|
}
|
|
144
189
|
const jsonBuf = Buffer.from(JSON.stringify(header), "utf8");
|
|
145
190
|
const alignedJsonLen = (jsonBuf.length + 3) & ~3;
|
|
@@ -159,7 +204,7 @@ export async function createPackage(srcDir, destFile) {
|
|
|
159
204
|
fs.writeSync(fd, sizePickle);
|
|
160
205
|
fs.writeSync(fd, headerPickle);
|
|
161
206
|
for (const f of files) {
|
|
162
|
-
if (f.size > 0) {
|
|
207
|
+
if (!f.isLink && f.size > 0) {
|
|
163
208
|
const content = fs.readFileSync(f.fullPath);
|
|
164
209
|
fs.writeSync(fd, content);
|
|
165
210
|
}
|
package/dist/paseo-patcher.d.ts
CHANGED
|
@@ -16,10 +16,16 @@ export declare function findPaseoServerInstallations(): string[];
|
|
|
16
16
|
* injected into Paseo server's quota-fetcher providers.
|
|
17
17
|
*/
|
|
18
18
|
export declare function generateAntigravityQuotaProviderJs(): string;
|
|
19
|
+
/**
|
|
20
|
+
* Generates the TypeScript source for the Paseo 0.11+ Plugin Usage Source.
|
|
21
|
+
* This integrates Antigravity quota and balance metrics into Paseo 0.11+'s
|
|
22
|
+
* new Plugin UsageSource architecture (Settings -> Usage).
|
|
23
|
+
*/
|
|
24
|
+
export declare function generateAntigravityPluginUsageTs(): string;
|
|
19
25
|
/**
|
|
20
26
|
* Patches a Paseo server installation directory to enable Antigravity:
|
|
21
|
-
* 1.
|
|
22
|
-
* 2.
|
|
27
|
+
* 1. For Paseo 0.11+: patches builtin-plugins/antigravity-provider with usage source
|
|
28
|
+
* 2. For Paseo <= 0.10: patches quota-fetcher/manifest.js and writes providers/antigravity.js
|
|
23
29
|
* 3. Patches acp-agent.js to map context window tokens and emit usage updates
|
|
24
30
|
*/
|
|
25
31
|
export declare function patchPaseoServer(serverDir: string): {
|
package/dist/paseo-patcher.js
CHANGED
|
@@ -222,6 +222,9 @@ const execFileAsync = promisify(execFile);
|
|
|
222
222
|
let cachedAgyBin = null;
|
|
223
223
|
let cachedAgyBinTime = 0;
|
|
224
224
|
const BIN_CACHE_TTL_MS = 86400000; // 24 hours
|
|
225
|
+
let lastSuccessfulQuota = null;
|
|
226
|
+
let lastSuccessfulQuotaTime = 0;
|
|
227
|
+
const QUOTA_CACHE_TTL_MS = 60000; // 60 seconds
|
|
225
228
|
|
|
226
229
|
function resolveAgyBinary() {
|
|
227
230
|
if (process.env.AGY_BIN_PATH) return process.env.AGY_BIN_PATH;
|
|
@@ -346,6 +349,11 @@ export class AntigravityQuotaProvider {
|
|
|
346
349
|
}
|
|
347
350
|
|
|
348
351
|
async fetchUsage() {
|
|
352
|
+
const now = Date.now();
|
|
353
|
+
if (lastSuccessfulQuota && (now - lastSuccessfulQuotaTime < QUOTA_CACHE_TTL_MS)) {
|
|
354
|
+
return lastSuccessfulQuota;
|
|
355
|
+
}
|
|
356
|
+
|
|
349
357
|
try {
|
|
350
358
|
const isWin = process.platform === "win32";
|
|
351
359
|
const bin = resolveAgyBinary();
|
|
@@ -388,14 +396,23 @@ export class AntigravityQuotaProvider {
|
|
|
388
396
|
env.Path = newPath;
|
|
389
397
|
}
|
|
390
398
|
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
execFileAsync(bin, ["--print-timeout", "24h", "--print", "/
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
+
let rawUsageOut = "";
|
|
400
|
+
try {
|
|
401
|
+
const usageRes = await execFileAsync(bin, ["--print-timeout", "24h", "--print", "/usage"], {
|
|
402
|
+
timeout: 30000,
|
|
403
|
+
env,
|
|
404
|
+
shell: isBatch,
|
|
405
|
+
windowsHide: true,
|
|
406
|
+
});
|
|
407
|
+
rawUsageOut = usageRes.stdout || usageRes.stderr || "";
|
|
408
|
+
} catch (usageErr) {
|
|
409
|
+
if (lastSuccessfulQuota) {
|
|
410
|
+
if (this.logger && typeof this.logger.warn === "function") {
|
|
411
|
+
this.logger.warn("Transient failure fetching agy /usage; returning cached quota", { error: usageErr.message });
|
|
412
|
+
}
|
|
413
|
+
return lastSuccessfulQuota;
|
|
414
|
+
}
|
|
415
|
+
const msg = usageErr instanceof Error ? usageErr.message : String(usageErr);
|
|
399
416
|
return unavailableUsage({
|
|
400
417
|
providerId: this.providerId,
|
|
401
418
|
displayName: "Antigravity",
|
|
@@ -403,8 +420,16 @@ export class AntigravityQuotaProvider {
|
|
|
403
420
|
});
|
|
404
421
|
}
|
|
405
422
|
|
|
406
|
-
|
|
407
|
-
|
|
423
|
+
let rawCreditsOut = "";
|
|
424
|
+
try {
|
|
425
|
+
const creditsRes = await execFileAsync(bin, ["--print-timeout", "24h", "--print", "/credits"], {
|
|
426
|
+
timeout: 6000,
|
|
427
|
+
env,
|
|
428
|
+
shell: isBatch,
|
|
429
|
+
windowsHide: true,
|
|
430
|
+
});
|
|
431
|
+
rawCreditsOut = creditsRes.stdout || creditsRes.stderr || "";
|
|
432
|
+
} catch {}
|
|
408
433
|
|
|
409
434
|
const usageOut = (rawUsageOut || "").replace(/\\r\\n/g, "\\n");
|
|
410
435
|
const creditsOut = (rawCreditsOut || "").replace(/\\r\\n/g, "\\n");
|
|
@@ -496,7 +521,7 @@ export class AntigravityQuotaProvider {
|
|
|
496
521
|
tone: remainingCredits > 0 ? "ok" : "default",
|
|
497
522
|
});
|
|
498
523
|
|
|
499
|
-
|
|
524
|
+
const result = {
|
|
500
525
|
providerId: this.providerId,
|
|
501
526
|
displayName: "Antigravity",
|
|
502
527
|
status: "available",
|
|
@@ -506,7 +531,12 @@ export class AntigravityQuotaProvider {
|
|
|
506
531
|
details: [],
|
|
507
532
|
error: null,
|
|
508
533
|
};
|
|
534
|
+
|
|
535
|
+
lastSuccessfulQuota = result;
|
|
536
|
+
lastSuccessfulQuotaTime = Date.now();
|
|
537
|
+
return result;
|
|
509
538
|
} catch (err) {
|
|
539
|
+
if (lastSuccessfulQuota) return lastSuccessfulQuota;
|
|
510
540
|
return unavailableUsage({
|
|
511
541
|
providerId: this.providerId,
|
|
512
542
|
displayName: "Antigravity",
|
|
@@ -517,16 +547,387 @@ export class AntigravityQuotaProvider {
|
|
|
517
547
|
}
|
|
518
548
|
`;
|
|
519
549
|
}
|
|
550
|
+
/**
|
|
551
|
+
* Generates the TypeScript source for the Paseo 0.11+ Plugin Usage Source.
|
|
552
|
+
* This integrates Antigravity quota and balance metrics into Paseo 0.11+'s
|
|
553
|
+
* new Plugin UsageSource architecture (Settings -> Usage).
|
|
554
|
+
*/
|
|
555
|
+
export function generateAntigravityPluginUsageTs() {
|
|
556
|
+
return `import { execFileSync } from "node:child_process";
|
|
557
|
+
import fs from "node:fs";
|
|
558
|
+
import os from "node:os";
|
|
559
|
+
import path from "node:path";
|
|
560
|
+
import { z } from "zod";
|
|
561
|
+
|
|
562
|
+
let cachedAgyBin = null;
|
|
563
|
+
let lastAgyBinCheck = 0;
|
|
564
|
+
const RESOLVE_TTL_MS = 30000;
|
|
565
|
+
let lastSuccessfulPluginQuota = null;
|
|
566
|
+
let lastSuccessfulPluginQuotaTime = 0;
|
|
567
|
+
const PLUGIN_QUOTA_CACHE_TTL_MS = 60000;
|
|
568
|
+
|
|
569
|
+
function resolveAgyBinary() {
|
|
570
|
+
const now = Date.now();
|
|
571
|
+
if (cachedAgyBin && (now - lastAgyBinCheck) < RESOLVE_TTL_MS) {
|
|
572
|
+
return cachedAgyBin;
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
if (process.env.AGY_BIN_PATH && fs.existsSync(process.env.AGY_BIN_PATH)) {
|
|
576
|
+
cachedAgyBin = process.env.AGY_BIN_PATH;
|
|
577
|
+
lastAgyBinCheck = now;
|
|
578
|
+
return cachedAgyBin;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
const home = os.homedir();
|
|
582
|
+
const isWin = process.platform === "win32";
|
|
583
|
+
|
|
584
|
+
if (isWin) {
|
|
585
|
+
const localAppData = process.env.LOCALAPPDATA || (home ? path.join(home, "AppData", "Local") : "");
|
|
586
|
+
const programFiles = process.env.ProgramFiles || "C:\\\\Program Files";
|
|
587
|
+
const programFilesX86 = process.env["ProgramFiles(x86)"] || "C:\\\\Program Files (x86)";
|
|
588
|
+
|
|
589
|
+
const winCandidates = [
|
|
590
|
+
path.join(localAppData, "Programs", "Antigravity", "bin", "agy.exe"),
|
|
591
|
+
path.join(localAppData, "Programs", "antigravity", "bin", "agy.exe"),
|
|
592
|
+
path.join(localAppData, "Programs", "Antigravity", "agy.exe"),
|
|
593
|
+
path.join(localAppData, "Programs", "antigravity", "agy.exe"),
|
|
594
|
+
path.join(localAppData, "antigravity", "agy.exe"),
|
|
595
|
+
path.join(programFiles, "Antigravity", "bin", "agy.exe"),
|
|
596
|
+
path.join(programFiles, "antigravity", "bin", "agy.exe"),
|
|
597
|
+
path.join(programFiles, "Antigravity", "agy.exe"),
|
|
598
|
+
path.join(programFiles, "antigravity", "agy.exe"),
|
|
599
|
+
path.join(programFilesX86, "Antigravity", "agy.exe"),
|
|
600
|
+
path.join(localAppData, "Microsoft", "WindowsApps", "agy.exe"),
|
|
601
|
+
];
|
|
602
|
+
|
|
603
|
+
for (const cand of winCandidates) {
|
|
604
|
+
if (cand && fs.existsSync(cand)) {
|
|
605
|
+
cachedAgyBin = cand;
|
|
606
|
+
lastAgyBinCheck = now;
|
|
607
|
+
return cand;
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
try {
|
|
612
|
+
const out = execFileSync("where.exe", ["agy"], {
|
|
613
|
+
encoding: "utf-8",
|
|
614
|
+
timeout: 3000,
|
|
615
|
+
windowsHide: true,
|
|
616
|
+
});
|
|
617
|
+
const lines = out.split(/\\r?\\n/).map(l => l.trim()).filter(Boolean);
|
|
618
|
+
const exeMatch = lines.find(l => /\\.exe$/i.test(l) && fs.existsSync(l));
|
|
619
|
+
if (exeMatch) {
|
|
620
|
+
cachedAgyBin = exeMatch;
|
|
621
|
+
lastAgyBinCheck = now;
|
|
622
|
+
return exeMatch;
|
|
623
|
+
}
|
|
624
|
+
const scriptMatch = lines.find(l => /\\.(cmd|bat)$/i.test(l) && fs.existsSync(l));
|
|
625
|
+
if (scriptMatch) {
|
|
626
|
+
cachedAgyBin = scriptMatch;
|
|
627
|
+
lastAgyBinCheck = now;
|
|
628
|
+
return scriptMatch;
|
|
629
|
+
}
|
|
630
|
+
if (lines.length > 0 && fs.existsSync(lines[0])) {
|
|
631
|
+
cachedAgyBin = lines[0];
|
|
632
|
+
lastAgyBinCheck = now;
|
|
633
|
+
return lines[0];
|
|
634
|
+
}
|
|
635
|
+
} catch {}
|
|
636
|
+
|
|
637
|
+
cachedAgyBin = "agy.exe";
|
|
638
|
+
lastAgyBinCheck = now;
|
|
639
|
+
return cachedAgyBin;
|
|
640
|
+
} else {
|
|
641
|
+
const posixCandidates = [
|
|
642
|
+
path.join(home, ".local", "bin", "agy"),
|
|
643
|
+
"/usr/local/bin/agy",
|
|
644
|
+
"/usr/bin/agy",
|
|
645
|
+
"/opt/homebrew/bin/agy",
|
|
646
|
+
];
|
|
647
|
+
for (const cand of posixCandidates) {
|
|
648
|
+
if (fs.existsSync(cand)) {
|
|
649
|
+
cachedAgyBin = cand;
|
|
650
|
+
lastAgyBinCheck = now;
|
|
651
|
+
return cand;
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
try {
|
|
655
|
+
const out = execFileSync("which", ["agy"], {
|
|
656
|
+
encoding: "utf-8",
|
|
657
|
+
timeout: 3000,
|
|
658
|
+
}).trim();
|
|
659
|
+
if (out && fs.existsSync(out)) {
|
|
660
|
+
cachedAgyBin = out;
|
|
661
|
+
lastAgyBinCheck = now;
|
|
662
|
+
return out;
|
|
663
|
+
}
|
|
664
|
+
} catch {}
|
|
665
|
+
|
|
666
|
+
cachedAgyBin = "agy";
|
|
667
|
+
lastAgyBinCheck = now;
|
|
668
|
+
return cachedAgyBin;
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
function toneFromUsedPct(usedPct) {
|
|
673
|
+
if (usedPct >= 90) return "critical";
|
|
674
|
+
if (usedPct >= 75) return "warn";
|
|
675
|
+
return "ok";
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
export async function fetchAntigravityUsage() {
|
|
679
|
+
const now = Date.now();
|
|
680
|
+
if (lastSuccessfulPluginQuota && (now - lastSuccessfulPluginQuotaTime < PLUGIN_QUOTA_CACHE_TTL_MS)) {
|
|
681
|
+
return lastSuccessfulPluginQuota;
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
try {
|
|
685
|
+
const bin = resolveAgyBinary();
|
|
686
|
+
const isWin = process.platform === "win32";
|
|
687
|
+
const isBatch = isWin && /\\.(cmd|bat)$/i.test(bin);
|
|
688
|
+
|
|
689
|
+
let usageOut = "";
|
|
690
|
+
try {
|
|
691
|
+
usageOut = execFileSync(bin, ["--print-timeout", "24h", "--print", "/usage"], {
|
|
692
|
+
encoding: "utf-8",
|
|
693
|
+
timeout: 30000,
|
|
694
|
+
windowsHide: true,
|
|
695
|
+
shell: isBatch,
|
|
696
|
+
});
|
|
697
|
+
} catch (usageErr) {
|
|
698
|
+
if (lastSuccessfulPluginQuota) return lastSuccessfulPluginQuota;
|
|
699
|
+
throw usageErr;
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
let creditsOut = "";
|
|
703
|
+
try {
|
|
704
|
+
creditsOut = execFileSync(bin, ["--print-timeout", "24h", "--print", "/credits"], {
|
|
705
|
+
encoding: "utf-8",
|
|
706
|
+
timeout: 6000,
|
|
707
|
+
windowsHide: true,
|
|
708
|
+
shell: isBatch,
|
|
709
|
+
});
|
|
710
|
+
} catch {}
|
|
711
|
+
|
|
712
|
+
const normalizedUsage = usageOut.replace(/\\r\\n/g, "\\n");
|
|
713
|
+
const lines = normalizedUsage.split("\\n");
|
|
714
|
+
const rawWindows = [];
|
|
715
|
+
let currentModel = "";
|
|
716
|
+
|
|
717
|
+
for (const rawLine of lines) {
|
|
718
|
+
const line = rawLine.trim();
|
|
719
|
+
if (!line) continue;
|
|
720
|
+
|
|
721
|
+
if (line.endsWith(":") && !line.includes("%")) {
|
|
722
|
+
currentModel = line.slice(0, -1).trim();
|
|
723
|
+
continue;
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
const isGemini = /gemini/i.test(currentModel) || !/claude/i.test(currentModel);
|
|
727
|
+
const match = line.match(/^([A-Za-z0-9\\s_-]+):\\s*(\\d+)%\\s*(?:used)?(?:\\s*\\((?:resets?\\s+in\\s+)?([^)]+)\\))?/i);
|
|
728
|
+
if (match) {
|
|
729
|
+
const limitType = match[1].trim();
|
|
730
|
+
const usedPct = parseInt(match[2], 10);
|
|
731
|
+
const resetStr = match[3] ? match[3].trim() : null;
|
|
732
|
+
|
|
733
|
+
let resetsAt = null;
|
|
734
|
+
if (resetStr) {
|
|
735
|
+
const durMatch = resetStr.match(/(?:(\\d+)h)?\\s*(?:(\\d+)m)?/i);
|
|
736
|
+
if (durMatch && (durMatch[1] || durMatch[2])) {
|
|
737
|
+
const h = parseInt(durMatch[1] || "0", 10);
|
|
738
|
+
const m = parseInt(durMatch[2] || "0", 10);
|
|
739
|
+
resetsAt = new Date(Date.now() + (h * 3600 + m * 60) * 1000).toISOString();
|
|
740
|
+
} else {
|
|
741
|
+
const parsed = Date.parse(resetStr);
|
|
742
|
+
if (!isNaN(parsed)) resetsAt = new Date(parsed).toISOString();
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
const isFiveHour = /session|5-?hour|5h/i.test(limitType);
|
|
747
|
+
const isWeekly = /week|7-?day/i.test(limitType);
|
|
748
|
+
|
|
749
|
+
let id = isFiveHour ? "session" : isWeekly ? "weekly" : "quota";
|
|
750
|
+
let label = isFiveHour ? "Session" : isWeekly ? "Weekly" : limitType.replace(/\\s+Remaining$/i, "");
|
|
751
|
+
if (!isGemini) {
|
|
752
|
+
id = \`claude_\${id}\`;
|
|
753
|
+
label = \`Claude \${label}\`;
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
rawWindows.push({
|
|
757
|
+
id,
|
|
758
|
+
label,
|
|
759
|
+
shortLabel: isFiveHour ? "5h" : isWeekly ? "wk" : "",
|
|
760
|
+
summary: true,
|
|
761
|
+
usedPct,
|
|
762
|
+
remainingPct: Math.max(0, 100 - usedPct),
|
|
763
|
+
resetsAt,
|
|
764
|
+
tone: toneFromUsedPct(usedPct),
|
|
765
|
+
isFiveHour,
|
|
766
|
+
isGemini,
|
|
767
|
+
});
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
rawWindows.sort((a, b) => {
|
|
772
|
+
if (a.isGemini && !b.isGemini) return -1;
|
|
773
|
+
if (!a.isGemini && b.isGemini) return 1;
|
|
774
|
+
if (a.isFiveHour && !b.isFiveHour) return -1;
|
|
775
|
+
if (!a.isFiveHour && b.isFiveHour) return 1;
|
|
776
|
+
return 0;
|
|
777
|
+
});
|
|
778
|
+
|
|
779
|
+
const windows = rawWindows.map(w => ({
|
|
780
|
+
id: w.id,
|
|
781
|
+
label: w.label,
|
|
782
|
+
shortLabel: w.shortLabel,
|
|
783
|
+
summary: w.summary,
|
|
784
|
+
usedPct: w.usedPct,
|
|
785
|
+
remainingPct: w.remainingPct,
|
|
786
|
+
resetsAt: w.resetsAt,
|
|
787
|
+
tone: w.tone,
|
|
788
|
+
}));
|
|
789
|
+
|
|
790
|
+
const balances = [];
|
|
791
|
+
const credMatch = creditsOut.match(/Remaining\\s+credits\\s+([\\d.]+)/i);
|
|
792
|
+
const remainingCredits = credMatch ? parseFloat(credMatch[1]) : 0;
|
|
793
|
+
balances.push({
|
|
794
|
+
id: "credits",
|
|
795
|
+
label: "Credits",
|
|
796
|
+
remaining: remainingCredits,
|
|
797
|
+
unit: "usd",
|
|
798
|
+
tone: remainingCredits > 0 ? "ok" : "default",
|
|
799
|
+
});
|
|
800
|
+
|
|
801
|
+
const result = {
|
|
802
|
+
status: "available",
|
|
803
|
+
planLabel: "Google Gemini",
|
|
804
|
+
windows,
|
|
805
|
+
balances,
|
|
806
|
+
details: [],
|
|
807
|
+
};
|
|
808
|
+
lastSuccessfulPluginQuota = result;
|
|
809
|
+
lastSuccessfulPluginQuotaTime = Date.now();
|
|
810
|
+
return result;
|
|
811
|
+
} catch (err) {
|
|
812
|
+
if (lastSuccessfulPluginQuota) return lastSuccessfulPluginQuota;
|
|
813
|
+
return {
|
|
814
|
+
status: "error",
|
|
815
|
+
error: err instanceof Error ? err.message : String(err),
|
|
816
|
+
};
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
export function registerAntigravityUsageSource(server) {
|
|
821
|
+
if (!server || typeof server.registerUsageSource !== "function") return;
|
|
822
|
+
try {
|
|
823
|
+
server.registerUsageSource({
|
|
824
|
+
id: "antigravity",
|
|
825
|
+
label: "Antigravity",
|
|
826
|
+
icon: "icon.svg",
|
|
827
|
+
input: z.object({}).passthrough(),
|
|
828
|
+
discover: async (scope) => {
|
|
829
|
+
if (scope && scope.kind === "session" && scope.provider && scope.provider !== "antigravity") {
|
|
830
|
+
return [];
|
|
831
|
+
}
|
|
832
|
+
return [{ key: "default", label: "Antigravity", input: {} }];
|
|
833
|
+
},
|
|
834
|
+
fetch: async () => {
|
|
835
|
+
return fetchAntigravityUsage();
|
|
836
|
+
},
|
|
837
|
+
});
|
|
838
|
+
} catch (err) {
|
|
839
|
+
console.warn("[antigravity-provider] Failed to register usage source:", err);
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
`;
|
|
843
|
+
}
|
|
520
844
|
/**
|
|
521
845
|
* Patches a Paseo server installation directory to enable Antigravity:
|
|
522
|
-
* 1.
|
|
523
|
-
* 2.
|
|
846
|
+
* 1. For Paseo 0.11+: patches builtin-plugins/antigravity-provider with usage source
|
|
847
|
+
* 2. For Paseo <= 0.10: patches quota-fetcher/manifest.js and writes providers/antigravity.js
|
|
524
848
|
* 3. Patches acp-agent.js to map context window tokens and emit usage updates
|
|
525
849
|
*/
|
|
526
850
|
export function patchPaseoServer(serverDir) {
|
|
527
851
|
const changes = [];
|
|
528
852
|
try {
|
|
529
|
-
// 1.
|
|
853
|
+
// 1. Check for Paseo 0.11+ builtin-plugins/antigravity-provider
|
|
854
|
+
const pluginDirCandidates = [
|
|
855
|
+
path.join(serverDir, "dist", "server", "builtin-plugins", "antigravity-provider"),
|
|
856
|
+
path.join(serverDir, "dist", "builtin-plugins", "antigravity-provider"),
|
|
857
|
+
path.join(serverDir, "builtin-plugins", "antigravity-provider"),
|
|
858
|
+
];
|
|
859
|
+
let pluginDir = pluginDirCandidates.find((d) => fs.existsSync(d));
|
|
860
|
+
if (!pluginDir && fs.existsSync(path.join(serverDir, "dist"))) {
|
|
861
|
+
const findPlugin = (dir, depth = 0) => {
|
|
862
|
+
if (depth > 5)
|
|
863
|
+
return null;
|
|
864
|
+
try {
|
|
865
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
866
|
+
for (const e of entries) {
|
|
867
|
+
const full = path.join(dir, e.name);
|
|
868
|
+
if (e.isDirectory() && e.name !== "node_modules") {
|
|
869
|
+
if (e.name === "antigravity-provider")
|
|
870
|
+
return full;
|
|
871
|
+
const found = findPlugin(full, depth + 1);
|
|
872
|
+
if (found)
|
|
873
|
+
return found;
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
catch { }
|
|
878
|
+
return null;
|
|
879
|
+
};
|
|
880
|
+
pluginDir = findPlugin(path.join(serverDir, "dist")) || undefined;
|
|
881
|
+
}
|
|
882
|
+
if (pluginDir) {
|
|
883
|
+
const serverSubdir = path.join(pluginDir, "server");
|
|
884
|
+
if (!fs.existsSync(serverSubdir)) {
|
|
885
|
+
fs.mkdirSync(serverSubdir, { recursive: true });
|
|
886
|
+
}
|
|
887
|
+
// Write / update usage.ts
|
|
888
|
+
const usageTsPath = path.join(serverSubdir, "usage.ts");
|
|
889
|
+
fs.writeFileSync(usageTsPath, generateAntigravityPluginUsageTs(), "utf-8");
|
|
890
|
+
changes.push(`Created/Updated ${usageTsPath}`);
|
|
891
|
+
// Locate index.server.ts and/or index.server.js
|
|
892
|
+
const indexCandidates = [
|
|
893
|
+
path.join(pluginDir, "index.server.ts"),
|
|
894
|
+
path.join(pluginDir, "index.server.js"),
|
|
895
|
+
].filter((f) => fs.existsSync(f));
|
|
896
|
+
for (const indexFile of indexCandidates) {
|
|
897
|
+
let indexCode = fs.readFileSync(indexFile, "utf-8");
|
|
898
|
+
let indexModified = false;
|
|
899
|
+
if (!indexCode.includes("registerAntigravityUsageSource")) {
|
|
900
|
+
// Add import
|
|
901
|
+
if (!indexCode.includes('from "./server/usage.js"')) {
|
|
902
|
+
indexCode = `import { registerAntigravityUsageSource } from "./server/usage.js";\n` + indexCode;
|
|
903
|
+
indexModified = true;
|
|
904
|
+
}
|
|
905
|
+
// Inject registration call into contribute function
|
|
906
|
+
const contributeMatch = indexCode.match(/(export\s+default\s+function\s+contribute\s*\([^)]*\)\s*\{)([\s\S]*?)(\})/);
|
|
907
|
+
if (contributeMatch) {
|
|
908
|
+
const before = contributeMatch[1];
|
|
909
|
+
const body = contributeMatch[2];
|
|
910
|
+
const after = contributeMatch[3];
|
|
911
|
+
if (!body.includes("registerAntigravityUsageSource")) {
|
|
912
|
+
const patchedBody = body.replace(/return\s+\(\)\s*=>\s*\{[^}]*\};?/, (m) => `server.registerUsageSource ? registerAntigravityUsageSource(server) : null;\n ${m}`);
|
|
913
|
+
if (patchedBody !== body) {
|
|
914
|
+
indexCode = indexCode.replace(contributeMatch[0], `${before}${patchedBody}${after}`);
|
|
915
|
+
indexModified = true;
|
|
916
|
+
}
|
|
917
|
+
else {
|
|
918
|
+
indexCode = indexCode.replace(contributeMatch[0], `${before}${body}\n server.registerUsageSource ? registerAntigravityUsageSource(server) : null;\n${after}`);
|
|
919
|
+
indexModified = true;
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
if (indexModified) {
|
|
925
|
+
fs.writeFileSync(indexFile, indexCode, "utf-8");
|
|
926
|
+
changes.push(`Patched ${indexFile} with registerAntigravityUsageSource`);
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
// 2. Check for Paseo <= 0.10 legacy quota-fetcher/manifest.js
|
|
530
931
|
const manifestCandidates = [
|
|
531
932
|
path.join(serverDir, "dist", "server", "services", "quota-fetcher", "manifest.js"),
|
|
532
933
|
path.join(serverDir, "dist", "services", "quota-fetcher", "manifest.js"),
|
|
@@ -580,7 +981,7 @@ export function patchPaseoServer(serverDir) {
|
|
|
580
981
|
changes.push(`Patched ${manifestFile} with AntigravityQuotaProvider`);
|
|
581
982
|
}
|
|
582
983
|
}
|
|
583
|
-
//
|
|
984
|
+
// 3. Locate acp-agent.js
|
|
584
985
|
const acpCandidates = [
|
|
585
986
|
path.join(serverDir, "dist", "server", "server", "agent", "providers", "acp-agent.js"),
|
|
586
987
|
path.join(serverDir, "dist", "server", "agent", "providers", "acp-agent.js"),
|
|
@@ -609,19 +1010,26 @@ export function patchPaseoServer(serverDir) {
|
|
|
609
1010
|
let acpCode = fs.readFileSync(acpFile, "utf-8");
|
|
610
1011
|
let acpModified = false;
|
|
611
1012
|
// Patch mapACPUsage
|
|
612
|
-
if (!acpCode.includes("contextWindowMaxTokens: usage.contextWindowMaxTokens")) {
|
|
1013
|
+
if (!acpCode.includes("contextWindowMaxTokens: usage.contextWindowMaxTokens") || !acpCode.includes("Math.min(usedTokens, maxTokens)")) {
|
|
613
1014
|
const oldMapRegex = /export\s+function\s+mapACPUsage\s*\([^)]*\)\s*\{[\s\S]*?return\s*\{[\s\S]*?\};\s*\}/m;
|
|
614
1015
|
const newMap = `export function mapACPUsage(usage) {
|
|
615
1016
|
if (!usage) {
|
|
616
1017
|
return undefined;
|
|
617
1018
|
}
|
|
1019
|
+
const maxTokens = usage.contextWindowMaxTokens ?? usage.size ?? undefined;
|
|
1020
|
+
let usedTokens = usage.contextWindowUsedTokens ?? usage.used ?? undefined;
|
|
1021
|
+
if (typeof usedTokens === "number" && typeof maxTokens === "number" && maxTokens > 0) {
|
|
1022
|
+
if (usedTokens > maxTokens) {
|
|
1023
|
+
usedTokens = Math.min(usedTokens, maxTokens);
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
618
1026
|
return {
|
|
619
1027
|
inputTokens: usage.inputTokens ?? undefined,
|
|
620
1028
|
outputTokens: usage.outputTokens ?? undefined,
|
|
621
1029
|
cachedInputTokens: usage.cachedReadTokens ?? usage.cachedInputTokens ?? undefined,
|
|
622
1030
|
totalCostUsd: usage.totalCostUsd ?? (usage.cost?.amount !== undefined ? Number(usage.cost.amount) : undefined),
|
|
623
1031
|
contextWindowMaxTokens: usage.contextWindowMaxTokens ?? usage.size ?? undefined,
|
|
624
|
-
contextWindowUsedTokens:
|
|
1032
|
+
contextWindowUsedTokens: usedTokens,
|
|
625
1033
|
};
|
|
626
1034
|
}`;
|
|
627
1035
|
if (oldMapRegex.test(acpCode)) {
|
|
@@ -666,7 +1074,7 @@ export function patchPaseoServer(serverDir) {
|
|
|
666
1074
|
}
|
|
667
1075
|
if (depth === 0) {
|
|
668
1076
|
const currentMethodBody = acpCode.slice(startIdx, i);
|
|
669
|
-
const needsPatch = !currentMethodBody.includes(
|
|
1077
|
+
const needsPatch = !currentMethodBody.includes("this.currentTurnUsage =") || currentMethodBody.includes("this.notifySubscribers");
|
|
670
1078
|
if (needsPatch) {
|
|
671
1079
|
const indent = methodMatch[1] || " ";
|
|
672
1080
|
const newHandler = `${indent}handleUsageUpdate(update) {
|
|
@@ -798,7 +1206,7 @@ export async function patchPaseoAsar(asarPath) {
|
|
|
798
1206
|
return { success: false, changes: [], error: `Asar archive not found: ${asarPath}` };
|
|
799
1207
|
}
|
|
800
1208
|
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "paseo-asar-extract-"));
|
|
801
|
-
extractAll(asarPath, tempDir);
|
|
1209
|
+
const { unpackedPaths, originalHeader } = extractAll(asarPath, tempDir);
|
|
802
1210
|
// Look for server directory in extracted files
|
|
803
1211
|
const serverCandidates = [
|
|
804
1212
|
path.join(tempDir, "node_modules", "@getpaseo", "server"),
|
|
@@ -841,8 +1249,27 @@ export async function patchPaseoAsar(asarPath) {
|
|
|
841
1249
|
return { success: true, changes: [] };
|
|
842
1250
|
}
|
|
843
1251
|
changes.push(...patchResult.changes);
|
|
844
|
-
// Create backup if not already present
|
|
845
|
-
|
|
1252
|
+
// Create backup if not already present.
|
|
1253
|
+
// On macOS, never write backup inside .app bundle (e.g. app.asar.bak) because it breaks code signing seals.
|
|
1254
|
+
let backupPath;
|
|
1255
|
+
if (process.platform === "darwin" && asarPath.includes(".app")) {
|
|
1256
|
+
const backupDir = path.join(os.homedir(), ".paseo", "backups");
|
|
1257
|
+
if (!fs.existsSync(backupDir)) {
|
|
1258
|
+
fs.mkdirSync(backupDir, { recursive: true });
|
|
1259
|
+
}
|
|
1260
|
+
backupPath = path.join(backupDir, "app.asar.bak");
|
|
1261
|
+
// Clean up any orphan backup inside the .app bundle from previous runs
|
|
1262
|
+
const orphanBak = `${asarPath}.bak`;
|
|
1263
|
+
if (fs.existsSync(orphanBak)) {
|
|
1264
|
+
try {
|
|
1265
|
+
fs.unlinkSync(orphanBak);
|
|
1266
|
+
}
|
|
1267
|
+
catch { }
|
|
1268
|
+
}
|
|
1269
|
+
}
|
|
1270
|
+
else {
|
|
1271
|
+
backupPath = `${asarPath}.bak`;
|
|
1272
|
+
}
|
|
846
1273
|
if (!fs.existsSync(backupPath)) {
|
|
847
1274
|
try {
|
|
848
1275
|
fs.copyFileSync(asarPath, backupPath);
|
|
@@ -853,7 +1280,7 @@ export async function patchPaseoAsar(asarPath) {
|
|
|
853
1280
|
}
|
|
854
1281
|
}
|
|
855
1282
|
tempAsar = path.join(os.tmpdir(), `app-${Date.now()}.asar`);
|
|
856
|
-
await createPackage(tempDir, tempAsar);
|
|
1283
|
+
await createPackage(tempDir, tempAsar, { unpackedPaths, originalHeader });
|
|
857
1284
|
// Replace original archive with locked file handling for Windows
|
|
858
1285
|
try {
|
|
859
1286
|
fs.copyFileSync(tempAsar, asarPath);
|
|
@@ -875,6 +1302,21 @@ export async function patchPaseoAsar(asarPath) {
|
|
|
875
1302
|
throw copyErr;
|
|
876
1303
|
}
|
|
877
1304
|
}
|
|
1305
|
+
// On macOS, re-sign application bundle if inside an .app directory to satisfy Gatekeeper
|
|
1306
|
+
if (process.platform === "darwin" && asarPath.includes(".app")) {
|
|
1307
|
+
const appIndex = asarPath.indexOf(".app");
|
|
1308
|
+
const appBundlePath = asarPath.slice(0, appIndex + 4);
|
|
1309
|
+
try {
|
|
1310
|
+
execFileSync("codesign", ["--force", "--deep", "--sign", "-", appBundlePath], {
|
|
1311
|
+
stdio: "ignore",
|
|
1312
|
+
timeout: 15000,
|
|
1313
|
+
});
|
|
1314
|
+
changes.push(`Re-signed macOS bundle at ${appBundlePath}`);
|
|
1315
|
+
}
|
|
1316
|
+
catch (signErr) {
|
|
1317
|
+
logger.warn(`Could not re-sign ${appBundlePath}`, { error: String(signErr) });
|
|
1318
|
+
}
|
|
1319
|
+
}
|
|
878
1320
|
return { success: true, changes };
|
|
879
1321
|
}
|
|
880
1322
|
catch (err) {
|
|
@@ -950,43 +1392,15 @@ export function isPaseoServerPatched(serverDir) {
|
|
|
950
1392
|
try {
|
|
951
1393
|
if (!fs.existsSync(serverDir))
|
|
952
1394
|
return false;
|
|
953
|
-
// 1.
|
|
954
|
-
const
|
|
955
|
-
path.join(serverDir, "dist", "server", "
|
|
956
|
-
path.join(serverDir, "dist", "
|
|
957
|
-
|
|
958
|
-
let hasProvider = antigravityJsCandidates.some((p) => fs.existsSync(p));
|
|
959
|
-
if (!hasProvider && fs.existsSync(path.join(serverDir, "dist"))) {
|
|
960
|
-
const checkRecursive = (dir, depth = 0) => {
|
|
961
|
-
if (depth > 5)
|
|
962
|
-
return false;
|
|
963
|
-
try {
|
|
964
|
-
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
965
|
-
for (const e of entries) {
|
|
966
|
-
if (e.isDirectory() && e.name !== "node_modules") {
|
|
967
|
-
if (checkRecursive(path.join(dir, e.name), depth + 1))
|
|
968
|
-
return true;
|
|
969
|
-
}
|
|
970
|
-
else if (e.isFile() && e.name === "antigravity.js" && dir.replace(/\\/g, "/").includes("quota-fetcher")) {
|
|
971
|
-
return true;
|
|
972
|
-
}
|
|
973
|
-
}
|
|
974
|
-
}
|
|
975
|
-
catch { }
|
|
976
|
-
return false;
|
|
977
|
-
};
|
|
978
|
-
hasProvider = checkRecursive(path.join(serverDir, "dist"));
|
|
979
|
-
}
|
|
980
|
-
if (!hasProvider)
|
|
981
|
-
return false;
|
|
982
|
-
// 2. Manifest check
|
|
983
|
-
const manifestCandidates = [
|
|
984
|
-
path.join(serverDir, "dist", "server", "services", "quota-fetcher", "manifest.js"),
|
|
985
|
-
path.join(serverDir, "dist", "services", "quota-fetcher", "manifest.js"),
|
|
1395
|
+
// 1. Check for Paseo 0.11+ builtin-plugins/antigravity-provider
|
|
1396
|
+
const pluginDirCandidates = [
|
|
1397
|
+
path.join(serverDir, "dist", "server", "builtin-plugins", "antigravity-provider"),
|
|
1398
|
+
path.join(serverDir, "dist", "builtin-plugins", "antigravity-provider"),
|
|
1399
|
+
path.join(serverDir, "builtin-plugins", "antigravity-provider"),
|
|
986
1400
|
];
|
|
987
|
-
let
|
|
988
|
-
if (!
|
|
989
|
-
const
|
|
1401
|
+
let pluginDir = pluginDirCandidates.find((d) => fs.existsSync(d));
|
|
1402
|
+
if (!pluginDir && fs.existsSync(path.join(serverDir, "dist"))) {
|
|
1403
|
+
const findPlugin = (dir, depth = 0) => {
|
|
990
1404
|
if (depth > 5)
|
|
991
1405
|
return null;
|
|
992
1406
|
try {
|
|
@@ -994,27 +1408,104 @@ export function isPaseoServerPatched(serverDir) {
|
|
|
994
1408
|
for (const e of entries) {
|
|
995
1409
|
const full = path.join(dir, e.name);
|
|
996
1410
|
if (e.isDirectory() && e.name !== "node_modules") {
|
|
997
|
-
|
|
1411
|
+
if (e.name === "antigravity-provider")
|
|
1412
|
+
return full;
|
|
1413
|
+
const found = findPlugin(full, depth + 1);
|
|
998
1414
|
if (found)
|
|
999
1415
|
return found;
|
|
1000
1416
|
}
|
|
1001
|
-
else if (e.isFile() && e.name === "manifest.js" && dir.replace(/\\/g, "/").includes("quota-fetcher")) {
|
|
1002
|
-
return full;
|
|
1003
|
-
}
|
|
1004
1417
|
}
|
|
1005
1418
|
}
|
|
1006
1419
|
catch { }
|
|
1007
1420
|
return null;
|
|
1008
1421
|
};
|
|
1009
|
-
|
|
1422
|
+
pluginDir = findPlugin(path.join(serverDir, "dist")) || undefined;
|
|
1010
1423
|
}
|
|
1011
|
-
if (
|
|
1012
|
-
|
|
1013
|
-
|
|
1424
|
+
if (pluginDir) {
|
|
1425
|
+
// Paseo 0.11+ architecture
|
|
1426
|
+
const usageFile = [
|
|
1427
|
+
path.join(pluginDir, "server", "usage.ts"),
|
|
1428
|
+
path.join(pluginDir, "server", "usage.js"),
|
|
1429
|
+
].find((f) => fs.existsSync(f));
|
|
1430
|
+
if (!usageFile)
|
|
1014
1431
|
return false;
|
|
1432
|
+
const indexFile = [
|
|
1433
|
+
path.join(pluginDir, "index.server.ts"),
|
|
1434
|
+
path.join(pluginDir, "index.server.js"),
|
|
1435
|
+
].find((f) => fs.existsSync(f));
|
|
1436
|
+
if (!indexFile)
|
|
1437
|
+
return false;
|
|
1438
|
+
const indexContent = fs.readFileSync(indexFile, "utf-8");
|
|
1439
|
+
if (!indexContent.includes("registerAntigravityUsageSource"))
|
|
1440
|
+
return false;
|
|
1441
|
+
}
|
|
1442
|
+
else {
|
|
1443
|
+
// Paseo <= 0.10 legacy architecture
|
|
1444
|
+
const antigravityJsCandidates = [
|
|
1445
|
+
path.join(serverDir, "dist", "server", "services", "quota-fetcher", "providers", "antigravity.js"),
|
|
1446
|
+
path.join(serverDir, "dist", "services", "quota-fetcher", "providers", "antigravity.js"),
|
|
1447
|
+
];
|
|
1448
|
+
let hasProvider = antigravityJsCandidates.some((p) => fs.existsSync(p));
|
|
1449
|
+
if (!hasProvider && fs.existsSync(path.join(serverDir, "dist"))) {
|
|
1450
|
+
const checkRecursive = (dir, depth = 0) => {
|
|
1451
|
+
if (depth > 5)
|
|
1452
|
+
return false;
|
|
1453
|
+
try {
|
|
1454
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
1455
|
+
for (const e of entries) {
|
|
1456
|
+
if (e.isDirectory() && e.name !== "node_modules") {
|
|
1457
|
+
if (checkRecursive(path.join(dir, e.name), depth + 1))
|
|
1458
|
+
return true;
|
|
1459
|
+
}
|
|
1460
|
+
else if (e.isFile() && e.name === "antigravity.js" && dir.replace(/\\/g, "/").includes("quota-fetcher")) {
|
|
1461
|
+
return true;
|
|
1462
|
+
}
|
|
1463
|
+
}
|
|
1464
|
+
}
|
|
1465
|
+
catch { }
|
|
1466
|
+
return false;
|
|
1467
|
+
};
|
|
1468
|
+
hasProvider = checkRecursive(path.join(serverDir, "dist"));
|
|
1469
|
+
}
|
|
1470
|
+
if (!hasProvider)
|
|
1471
|
+
return false;
|
|
1472
|
+
// Manifest check
|
|
1473
|
+
const manifestCandidates = [
|
|
1474
|
+
path.join(serverDir, "dist", "server", "services", "quota-fetcher", "manifest.js"),
|
|
1475
|
+
path.join(serverDir, "dist", "services", "quota-fetcher", "manifest.js"),
|
|
1476
|
+
];
|
|
1477
|
+
let manifestFile = manifestCandidates.find((f) => fs.existsSync(f));
|
|
1478
|
+
if (!manifestFile && fs.existsSync(path.join(serverDir, "dist"))) {
|
|
1479
|
+
const findManifest = (dir, depth = 0) => {
|
|
1480
|
+
if (depth > 5)
|
|
1481
|
+
return null;
|
|
1482
|
+
try {
|
|
1483
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
1484
|
+
for (const e of entries) {
|
|
1485
|
+
const full = path.join(dir, e.name);
|
|
1486
|
+
if (e.isDirectory() && e.name !== "node_modules") {
|
|
1487
|
+
const found = findManifest(full, depth + 1);
|
|
1488
|
+
if (found)
|
|
1489
|
+
return found;
|
|
1490
|
+
}
|
|
1491
|
+
else if (e.isFile() && e.name === "manifest.js" && dir.replace(/\\/g, "/").includes("quota-fetcher")) {
|
|
1492
|
+
return full;
|
|
1493
|
+
}
|
|
1494
|
+
}
|
|
1495
|
+
}
|
|
1496
|
+
catch { }
|
|
1497
|
+
return null;
|
|
1498
|
+
};
|
|
1499
|
+
manifestFile = findManifest(path.join(serverDir, "dist")) || undefined;
|
|
1500
|
+
}
|
|
1501
|
+
if (manifestFile) {
|
|
1502
|
+
const content = fs.readFileSync(manifestFile, "utf-8");
|
|
1503
|
+
if (!content.includes('providerId: "antigravity"')) {
|
|
1504
|
+
return false;
|
|
1505
|
+
}
|
|
1015
1506
|
}
|
|
1016
1507
|
}
|
|
1017
|
-
//
|
|
1508
|
+
// 2. ACP Agent check (common to both architectures)
|
|
1018
1509
|
const acpCandidates = [
|
|
1019
1510
|
path.join(serverDir, "dist", "server", "server", "agent", "providers", "acp-agent.js"),
|
|
1020
1511
|
path.join(serverDir, "dist", "server", "agent", "providers", "acp-agent.js"),
|
|
@@ -1046,7 +1537,7 @@ export function isPaseoServerPatched(serverDir) {
|
|
|
1046
1537
|
}
|
|
1047
1538
|
if (acpFile) {
|
|
1048
1539
|
const content = fs.readFileSync(acpFile, "utf-8");
|
|
1049
|
-
if (!content.includes(
|
|
1540
|
+
if (!content.includes("this.currentTurnUsage =") || !content.includes("contextWindowMaxTokens")) {
|
|
1050
1541
|
return false;
|
|
1051
1542
|
}
|
|
1052
1543
|
}
|
|
@@ -1064,7 +1555,11 @@ export async function isPaseoAsarPatched(asarPath) {
|
|
|
1064
1555
|
if (!fs.existsSync(asarPath))
|
|
1065
1556
|
return false;
|
|
1066
1557
|
const files = listPackage(asarPath);
|
|
1067
|
-
return files.some((f) =>
|
|
1558
|
+
return files.some((f) => {
|
|
1559
|
+
const norm = f.replace(/\\/g, "/");
|
|
1560
|
+
return norm.includes("quota-fetcher/providers/antigravity.js") ||
|
|
1561
|
+
norm.includes("antigravity-provider/server/usage.");
|
|
1562
|
+
});
|
|
1068
1563
|
}
|
|
1069
1564
|
catch {
|
|
1070
1565
|
return false;
|
package/dist/protocol.js
CHANGED
|
@@ -308,14 +308,14 @@ export async function fetchAntigravityUsage(binaryPath = "agy", force = false) {
|
|
|
308
308
|
try {
|
|
309
309
|
const [usageResult, creditsResult] = await Promise.allSettled([
|
|
310
310
|
execFileAsync(cmd, ["--print", "/usage"], {
|
|
311
|
-
timeout:
|
|
311
|
+
timeout: 30_000,
|
|
312
312
|
maxBuffer: 1024 * 1024,
|
|
313
313
|
env: process.env,
|
|
314
314
|
shell: isBatch,
|
|
315
315
|
windowsHide: true,
|
|
316
316
|
}),
|
|
317
317
|
execFileAsync(cmd, ["--print", "/credits"], {
|
|
318
|
-
timeout:
|
|
318
|
+
timeout: 6_000,
|
|
319
319
|
maxBuffer: 1024 * 1024,
|
|
320
320
|
env: process.env,
|
|
321
321
|
shell: isBatch,
|
|
@@ -331,6 +331,10 @@ export async function fetchAntigravityUsage(binaryPath = "agy", force = false) {
|
|
|
331
331
|
// /usage is the primary provider-availability probe. allSettled never
|
|
332
332
|
// throws for an exec failure, so rejected results must be handled here.
|
|
333
333
|
if (usageResult.status === "rejected") {
|
|
334
|
+
if (cachedProviderUsage && cachedProviderUsage.status === "available" && cachedProviderUsageBinaryPath === binaryPath) {
|
|
335
|
+
logger.warn("Transient failure in /usage; returning cached provider usage", { error: errorMessage(usageResult.reason) });
|
|
336
|
+
return cachedProviderUsage;
|
|
337
|
+
}
|
|
334
338
|
const error = `Unable to fetch Antigravity quota: ${errorMessage(usageResult.reason)}`;
|
|
335
339
|
logger.warn("Failed to fetch Antigravity provider usage", { error });
|
|
336
340
|
return cacheProviderUsage(binaryPath, {
|
package/dist/session.d.ts
CHANGED
|
@@ -47,7 +47,7 @@ export declare class Session {
|
|
|
47
47
|
output_tokens?: number;
|
|
48
48
|
cache_read_tokens?: number;
|
|
49
49
|
total_tokens?: number;
|
|
50
|
-
}, executingModel?: string): void;
|
|
50
|
+
}, executingModel?: string, latestStepContextTokens?: number, numSteps?: number): void;
|
|
51
51
|
touch(): void;
|
|
52
52
|
cancelTurn(): boolean;
|
|
53
53
|
close(): Promise<void>;
|
package/dist/session.js
CHANGED
|
@@ -146,7 +146,7 @@ export class Session {
|
|
|
146
146
|
throw err;
|
|
147
147
|
}
|
|
148
148
|
}
|
|
149
|
-
recordTurnUsage(turnUsage, executingModel = this.model) {
|
|
149
|
+
recordTurnUsage(turnUsage, executingModel = this.model, latestStepContextTokens, numSteps = 1) {
|
|
150
150
|
if (!turnUsage)
|
|
151
151
|
return;
|
|
152
152
|
const input = turnUsage.input_tokens || 0;
|
|
@@ -159,10 +159,25 @@ export class Session {
|
|
|
159
159
|
this.usage.totalTokens = this.usage.inputTokens + this.usage.outputTokens;
|
|
160
160
|
// Keep the accumulator unrounded. Micro-costs must survive across turns.
|
|
161
161
|
this.usage.totalCostUsd += turnCost;
|
|
162
|
-
|
|
163
|
-
//
|
|
164
|
-
|
|
162
|
+
// Keep the stored meter aligned with the model currently selected for next turn,
|
|
163
|
+
// but evaluate turn occupancy against the model that executed this turn.
|
|
164
|
+
const executingMaxTokens = getModelContextWindow(executingModel);
|
|
165
165
|
this.usage.contextWindowMaxTokens = getModelContextWindow(this.model);
|
|
166
|
+
// Context window represents the active prompt memory buffer of the model.
|
|
167
|
+
// If we observed the latest step's context window size, use it directly.
|
|
168
|
+
// Otherwise, if input + output exceeds executingMaxTokens, it is an accumulation
|
|
169
|
+
// across multiple steps in the turn; derive the single-step context footprint.
|
|
170
|
+
let activeContextTokens = latestStepContextTokens;
|
|
171
|
+
if (!activeContextTokens || activeContextTokens <= 0) {
|
|
172
|
+
const rawTurnTokens = input + output;
|
|
173
|
+
if (rawTurnTokens > executingMaxTokens && numSteps > 1) {
|
|
174
|
+
activeContextTokens = Math.round(rawTurnTokens / numSteps);
|
|
175
|
+
}
|
|
176
|
+
else {
|
|
177
|
+
activeContextTokens = rawTurnTokens;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
this.usage.contextWindowUsedTokens = Math.min(activeContextTokens, executingMaxTokens);
|
|
166
181
|
this.persist();
|
|
167
182
|
}
|
|
168
183
|
touch() {
|