@eddyskywalker/dsh-chatgpt-subscription 0.2.2 → 0.2.4
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 +13 -3
- package/lib/client.js +2 -2
- package/lib/client.js.map +1 -1
- package/lib/index.js +410 -180
- package/lib/types/client/locales.d.ts +3 -3
- package/lib/types/host/antigravity/adapter.d.ts +4 -1
- package/lib/types/host/antigravity/adapter.d.ts.map +1 -1
- package/lib/types/host/antigravity/client.d.ts +4 -4
- package/lib/types/host/antigravity/client.d.ts.map +1 -1
- package/lib/types/host/antigravity/mapper.d.ts +9 -4
- package/lib/types/host/antigravity/mapper.d.ts.map +1 -1
- package/lib/types/host/antigravity/oauth.d.ts +6 -6
- package/lib/types/host/antigravity/oauth.d.ts.map +1 -1
- package/lib/types/host/antigravity/routes.d.ts +1 -1
- package/lib/types/host/antigravity/routes.d.ts.map +1 -1
- package/lib/types/host/antigravity/token-store.d.ts +8 -1
- package/lib/types/host/antigravity/token-store.d.ts.map +1 -1
- package/lib/types/host/credential-store-secret-service.d.ts +13 -0
- package/lib/types/host/credential-store-secret-service.d.ts.map +1 -0
- package/lib/types/host/token-store-macos.d.ts +9 -5
- package/lib/types/host/token-store-macos.d.ts.map +1 -1
- package/lib/types/host/token-store-windows.d.ts +9 -5
- package/lib/types/host/token-store-windows.d.ts.map +1 -1
- package/lib/types/host/token-store.d.ts +6 -4
- package/lib/types/host/token-store.d.ts.map +1 -1
- package/lib/types/index.d.ts.map +1 -1
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createRequire } from "node:module";
|
|
2
2
|
import * as LlmModule from "@deepseek-ai/dsh-llm";
|
|
3
|
-
import { HarnessError, LlmAdapter, LlmError, ProviderRequestId, ReasoningEffortId, attributionHeaders, resolveRetryPolicy } from "@deepseek-ai/dsh-llm";
|
|
3
|
+
import { CallId, HarnessError, LlmAdapter, LlmError, ProviderRequestId, ReasoningEffortId, attributionHeaders, resolveRetryPolicy } from "@deepseek-ai/dsh-llm";
|
|
4
4
|
import { WebError } from "@deepseek-ai/dsh-web";
|
|
5
5
|
import { createUserMessage } from "@deepseek-ai/dsh-llm/message";
|
|
6
6
|
import { defineTool } from "@deepseek-ai/dsh-tools";
|
|
@@ -14,6 +14,7 @@ import fs, { constants } from "node:fs";
|
|
|
14
14
|
import fsPromises, { chmod, lstat, mkdir, open, rename, stat, unlink } from "node:fs/promises";
|
|
15
15
|
import os, { homedir } from "node:os";
|
|
16
16
|
import path, { dirname, join } from "node:path";
|
|
17
|
+
import { isDeepStrictEqual } from "node:util";
|
|
17
18
|
import { URL as URL$1, URLSearchParams as URLSearchParams$1 } from "node:url";
|
|
18
19
|
//#region src/compat.ts
|
|
19
20
|
/**
|
|
@@ -3191,16 +3192,18 @@ const DEFAULT_ACCOUNT = "oauth";
|
|
|
3191
3192
|
* `security` command-line tool. The payload is encrypted at rest by the
|
|
3192
3193
|
* Keychain, so this store reports itself as encrypted like Windows DPAPI.
|
|
3193
3194
|
*/
|
|
3194
|
-
var
|
|
3195
|
+
var MacKeychainCredentialStore = class {
|
|
3195
3196
|
service;
|
|
3196
3197
|
account;
|
|
3198
|
+
parse;
|
|
3197
3199
|
storage = {
|
|
3198
3200
|
kind: "macos-keychain",
|
|
3199
3201
|
encrypted: true
|
|
3200
3202
|
};
|
|
3201
|
-
constructor(service
|
|
3203
|
+
constructor(service, account, parse) {
|
|
3202
3204
|
this.service = service;
|
|
3203
3205
|
this.account = account;
|
|
3206
|
+
this.parse = parse;
|
|
3204
3207
|
if (process.platform !== "darwin") throw new Error("macOS Keychain storage requires macOS");
|
|
3205
3208
|
}
|
|
3206
3209
|
async load() {
|
|
@@ -3216,7 +3219,7 @@ var MacKeychainTokenStore = class {
|
|
|
3216
3219
|
if (result.code !== 0) throw new Error("Keychain credential read failed");
|
|
3217
3220
|
try {
|
|
3218
3221
|
const payload = result.stdout.replace(/\r?\n$/, "");
|
|
3219
|
-
return
|
|
3222
|
+
return this.parse(JSON.parse(payload));
|
|
3220
3223
|
} catch {
|
|
3221
3224
|
throw new Error("Keychain credential payload is invalid");
|
|
3222
3225
|
}
|
|
@@ -3244,6 +3247,11 @@ var MacKeychainTokenStore = class {
|
|
|
3244
3247
|
if (result.code !== 0 && result.code !== 44) throw new Error("Keychain credential deletion failed");
|
|
3245
3248
|
}
|
|
3246
3249
|
};
|
|
3250
|
+
var MacKeychainTokenStore = class extends MacKeychainCredentialStore {
|
|
3251
|
+
constructor(service = DEFAULT_SERVICE, account = DEFAULT_ACCOUNT) {
|
|
3252
|
+
super(service, account, parseStoredCredentials);
|
|
3253
|
+
}
|
|
3254
|
+
};
|
|
3247
3255
|
function runSecurity(args) {
|
|
3248
3256
|
return new Promise((resolve, reject) => {
|
|
3249
3257
|
const child = spawn("security", args, {
|
|
@@ -3388,8 +3396,16 @@ $cipher = [Security.Cryptography.ProtectedData]::Protect($bytes, $null, [Securit
|
|
|
3388
3396
|
$directory = [IO.Path]::GetDirectoryName($path)
|
|
3389
3397
|
[IO.Directory]::CreateDirectory($directory) | Out-Null
|
|
3390
3398
|
$temporary = $path + '.tmp-' + [Guid]::NewGuid().ToString('N')
|
|
3391
|
-
|
|
3392
|
-
|
|
3399
|
+
try {
|
|
3400
|
+
[IO.File]::WriteAllBytes($temporary, $cipher)
|
|
3401
|
+
if ([IO.File]::Exists($path)) {
|
|
3402
|
+
[IO.File]::Replace($temporary, $path, [System.Management.Automation.Language.NullString]::Value)
|
|
3403
|
+
} else {
|
|
3404
|
+
[IO.File]::Move($temporary, $path)
|
|
3405
|
+
}
|
|
3406
|
+
} finally {
|
|
3407
|
+
if ([IO.File]::Exists($temporary)) { [IO.File]::Delete($temporary) }
|
|
3408
|
+
}
|
|
3393
3409
|
`;
|
|
3394
3410
|
const UNPROTECT_SCRIPT = String.raw`
|
|
3395
3411
|
$ErrorActionPreference = 'Stop'
|
|
@@ -3408,14 +3424,16 @@ if ([IO.File]::Exists($path)) { [IO.File]::Delete($path) }
|
|
|
3408
3424
|
function defaultDpapiCredentialPath() {
|
|
3409
3425
|
return join(process.env.DSH_HOME?.trim() || join(homedir(), ".dsh"), "storages", "dsh-chatgpt-subscription", "oauth.dpapi");
|
|
3410
3426
|
}
|
|
3411
|
-
var
|
|
3427
|
+
var WindowsDpapiCredentialStore = class {
|
|
3412
3428
|
path;
|
|
3429
|
+
parse;
|
|
3413
3430
|
storage = {
|
|
3414
3431
|
kind: "windows-dpapi",
|
|
3415
3432
|
encrypted: true
|
|
3416
3433
|
};
|
|
3417
|
-
constructor(path
|
|
3434
|
+
constructor(path, parse) {
|
|
3418
3435
|
this.path = path;
|
|
3436
|
+
this.parse = parse;
|
|
3419
3437
|
if (process.platform !== "win32") throw new Error("Windows DPAPI storage requires Windows");
|
|
3420
3438
|
if (dirname(path) === path) throw new Error("invalid DPAPI credential path");
|
|
3421
3439
|
}
|
|
@@ -3424,7 +3442,7 @@ var WindowsDpapiTokenStore = class {
|
|
|
3424
3442
|
if (result.code === 3) return null;
|
|
3425
3443
|
if (result.code !== 0) throw new Error("DPAPI credential read failed");
|
|
3426
3444
|
try {
|
|
3427
|
-
return
|
|
3445
|
+
return this.parse(JSON.parse(result.stdout));
|
|
3428
3446
|
} catch {
|
|
3429
3447
|
throw new Error("DPAPI credential payload is invalid");
|
|
3430
3448
|
}
|
|
@@ -3436,6 +3454,11 @@ var WindowsDpapiTokenStore = class {
|
|
|
3436
3454
|
if ((await runPowerShell(CLEAR_SCRIPT, this.path, "")).code !== 0) throw new Error("DPAPI credential deletion failed");
|
|
3437
3455
|
}
|
|
3438
3456
|
};
|
|
3457
|
+
var WindowsDpapiTokenStore = class extends WindowsDpapiCredentialStore {
|
|
3458
|
+
constructor(path = defaultDpapiCredentialPath()) {
|
|
3459
|
+
super(path, parseStoredCredentials);
|
|
3460
|
+
}
|
|
3461
|
+
};
|
|
3439
3462
|
function runPowerShell(script, path, stdin) {
|
|
3440
3463
|
return new Promise((resolve, reject) => {
|
|
3441
3464
|
const child = spawn("powershell.exe", [
|
|
@@ -3856,6 +3879,96 @@ const MODELS = [
|
|
|
3856
3879
|
}
|
|
3857
3880
|
];
|
|
3858
3881
|
//#endregion
|
|
3882
|
+
//#region src/host/credential-store-secret-service.ts
|
|
3883
|
+
const UNAVAILABLE = "Linux encrypted credential storage requires secret-tool (libsecret) and an unlocked Secret Service keyring.";
|
|
3884
|
+
/** Secrets travel over stdin/stdout; command arguments contain only lookup attributes. */
|
|
3885
|
+
var SecretServiceCredentialStore = class {
|
|
3886
|
+
service;
|
|
3887
|
+
account;
|
|
3888
|
+
parse;
|
|
3889
|
+
constructor(service, account, parse) {
|
|
3890
|
+
this.service = service;
|
|
3891
|
+
this.account = account;
|
|
3892
|
+
this.parse = parse;
|
|
3893
|
+
}
|
|
3894
|
+
attributes() {
|
|
3895
|
+
return [
|
|
3896
|
+
"service",
|
|
3897
|
+
this.service,
|
|
3898
|
+
"account",
|
|
3899
|
+
this.account
|
|
3900
|
+
];
|
|
3901
|
+
}
|
|
3902
|
+
async load() {
|
|
3903
|
+
const result = await runSecretTool(["lookup", ...this.attributes()]);
|
|
3904
|
+
if (result.code === 1 && !result.hasStderr && result.stdout === "") return null;
|
|
3905
|
+
if (result.code !== 0) throw new Error(UNAVAILABLE);
|
|
3906
|
+
try {
|
|
3907
|
+
return this.parse(JSON.parse(result.stdout));
|
|
3908
|
+
} catch {
|
|
3909
|
+
throw new Error("Secret Service credential payload is invalid");
|
|
3910
|
+
}
|
|
3911
|
+
}
|
|
3912
|
+
async save(value) {
|
|
3913
|
+
const payload = JSON.stringify(value);
|
|
3914
|
+
if (Buffer.byteLength(payload, "utf8") >= 8192) throw new Error("Secret Service credential payload is too large");
|
|
3915
|
+
if ((await runSecretTool([
|
|
3916
|
+
"store",
|
|
3917
|
+
"--label=DSH Antigravity OAuth",
|
|
3918
|
+
...this.attributes()
|
|
3919
|
+
], payload)).code !== 0) throw new Error(UNAVAILABLE);
|
|
3920
|
+
}
|
|
3921
|
+
async clear() {
|
|
3922
|
+
const result = await runSecretTool(["clear", ...this.attributes()]);
|
|
3923
|
+
if (result.code !== 0 && !(result.code === 1 && !result.hasStderr)) throw new Error(UNAVAILABLE);
|
|
3924
|
+
}
|
|
3925
|
+
};
|
|
3926
|
+
function runSecretTool(args, stdin = "") {
|
|
3927
|
+
return new Promise((resolve, reject) => {
|
|
3928
|
+
const child = spawn("secret-tool", args, {
|
|
3929
|
+
stdio: [
|
|
3930
|
+
"pipe",
|
|
3931
|
+
"pipe",
|
|
3932
|
+
"pipe"
|
|
3933
|
+
],
|
|
3934
|
+
windowsHide: true
|
|
3935
|
+
});
|
|
3936
|
+
let stdout = "";
|
|
3937
|
+
let stderrLength = 0;
|
|
3938
|
+
let settled = false;
|
|
3939
|
+
const fail = () => {
|
|
3940
|
+
if (settled) return;
|
|
3941
|
+
settled = true;
|
|
3942
|
+
clearTimeout(timer);
|
|
3943
|
+
child.kill();
|
|
3944
|
+
reject(/* @__PURE__ */ new Error(UNAVAILABLE));
|
|
3945
|
+
};
|
|
3946
|
+
const timer = setTimeout(fail, 1e4);
|
|
3947
|
+
child.stdout.setEncoding("utf8");
|
|
3948
|
+
child.stdout.on("data", (chunk) => {
|
|
3949
|
+
stdout += chunk;
|
|
3950
|
+
if (stdout.length > 1 << 20) fail();
|
|
3951
|
+
});
|
|
3952
|
+
child.stderr.on("data", (chunk) => {
|
|
3953
|
+
stderrLength += chunk.length;
|
|
3954
|
+
if (stderrLength > 1 << 20) fail();
|
|
3955
|
+
});
|
|
3956
|
+
child.once("error", fail);
|
|
3957
|
+
child.stdin.once("error", fail);
|
|
3958
|
+
child.once("close", (code) => {
|
|
3959
|
+
if (settled) return;
|
|
3960
|
+
settled = true;
|
|
3961
|
+
clearTimeout(timer);
|
|
3962
|
+
resolve({
|
|
3963
|
+
code: code ?? 1,
|
|
3964
|
+
stdout,
|
|
3965
|
+
hasStderr: stderrLength > 0
|
|
3966
|
+
});
|
|
3967
|
+
});
|
|
3968
|
+
child.stdin.end(stdin);
|
|
3969
|
+
});
|
|
3970
|
+
}
|
|
3971
|
+
//#endregion
|
|
3859
3972
|
//#region src/host/antigravity/token-store.ts
|
|
3860
3973
|
const ANTIGRAVITY_PREFERENCES_NAMESPACE = "dsh-antigravity";
|
|
3861
3974
|
function registerAntigravityPreferenceStore(settings, fallbackStore = new FileModelSettingsStore()) {
|
|
@@ -3917,36 +4030,110 @@ function credentialPath() {
|
|
|
3917
4030
|
function modelSettingsPath() {
|
|
3918
4031
|
return path.join(dshHomeDir(), "storages", "antigravity-models.json");
|
|
3919
4032
|
}
|
|
4033
|
+
function parseAntigravityCredentials(value) {
|
|
4034
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error("Antigravity credential payload is invalid");
|
|
4035
|
+
const record = value;
|
|
4036
|
+
const credentials = {};
|
|
4037
|
+
for (const key of [
|
|
4038
|
+
"access",
|
|
4039
|
+
"access_token",
|
|
4040
|
+
"refresh",
|
|
4041
|
+
"refresh_token",
|
|
4042
|
+
"email",
|
|
4043
|
+
"projectId"
|
|
4044
|
+
]) {
|
|
4045
|
+
if (record[key] === void 0) continue;
|
|
4046
|
+
if (typeof record[key] !== "string") throw new Error("Antigravity credential payload is invalid");
|
|
4047
|
+
credentials[key] = record[key];
|
|
4048
|
+
}
|
|
4049
|
+
for (const key of ["expires", "expires_at"]) {
|
|
4050
|
+
if (record[key] === void 0) continue;
|
|
4051
|
+
if (typeof record[key] !== "number" || !Number.isFinite(record[key])) throw new Error("Antigravity credential expiry is invalid");
|
|
4052
|
+
credentials[key] = record[key];
|
|
4053
|
+
}
|
|
4054
|
+
if (!(credentials.access || credentials.access_token || credentials.refresh || credentials.refresh_token)) throw new Error("Antigravity credential tokens are missing");
|
|
4055
|
+
return credentials;
|
|
4056
|
+
}
|
|
4057
|
+
function credentialAccount(filePath) {
|
|
4058
|
+
return createHash("sha256").update(path.resolve(filePath)).digest("hex");
|
|
4059
|
+
}
|
|
4060
|
+
function createCredentialBackend(filePath) {
|
|
4061
|
+
if (process.platform === "win32") return new WindowsDpapiCredentialStore(`${filePath}.dpapi`, parseAntigravityCredentials);
|
|
4062
|
+
if (process.platform === "darwin") return new MacKeychainCredentialStore("dsh-antigravity", credentialAccount(filePath), parseAntigravityCredentials);
|
|
4063
|
+
if (process.platform === "linux") return new SecretServiceCredentialStore("dsh-antigravity", credentialAccount(filePath), parseAntigravityCredentials);
|
|
4064
|
+
throw new Error("Antigravity encrypted credential storage requires Windows, macOS, or Linux.");
|
|
4065
|
+
}
|
|
4066
|
+
const credentialOperations = /* @__PURE__ */ new Map();
|
|
4067
|
+
/** Keeps the public API; filePath identifies the legacy JSON that is migrated on first use. */
|
|
3920
4068
|
var FileCredentialStore = class {
|
|
3921
4069
|
filePath;
|
|
3922
|
-
|
|
4070
|
+
backend;
|
|
4071
|
+
constructor(filePath = credentialPath(), backend = createCredentialBackend(filePath)) {
|
|
3923
4072
|
this.filePath = filePath;
|
|
4073
|
+
this.backend = backend;
|
|
3924
4074
|
}
|
|
3925
4075
|
path() {
|
|
3926
|
-
return this.filePath
|
|
4076
|
+
if (process.platform === "win32") return `${this.filePath}.dpapi`;
|
|
4077
|
+
return `${process.platform === "darwin" ? "Keychain" : "Secret Service"}: dsh-antigravity/${credentialAccount(this.filePath)}`;
|
|
4078
|
+
}
|
|
4079
|
+
serialize(operation) {
|
|
4080
|
+
const key = path.resolve(this.filePath);
|
|
4081
|
+
const result = (credentialOperations.get(key) || Promise.resolve()).then(operation);
|
|
4082
|
+
const settled = result.then(() => void 0, () => void 0);
|
|
4083
|
+
credentialOperations.set(key, settled);
|
|
4084
|
+
settled.then(() => {
|
|
4085
|
+
if (credentialOperations.get(key) === settled) credentialOperations.delete(key);
|
|
4086
|
+
});
|
|
4087
|
+
return result;
|
|
3927
4088
|
}
|
|
3928
|
-
async
|
|
4089
|
+
async removeLegacy() {
|
|
3929
4090
|
try {
|
|
3930
|
-
|
|
3931
|
-
|
|
3932
|
-
if (
|
|
3933
|
-
return null;
|
|
3934
|
-
} catch {
|
|
3935
|
-
return null;
|
|
4091
|
+
await fsPromises.unlink(this.filePath);
|
|
4092
|
+
} catch (error) {
|
|
4093
|
+
if (error.code !== "ENOENT") throw new Error("Antigravity legacy credential removal failed");
|
|
3936
4094
|
}
|
|
3937
4095
|
}
|
|
3938
|
-
async
|
|
3939
|
-
await
|
|
3940
|
-
|
|
3941
|
-
await
|
|
3942
|
-
|
|
4096
|
+
async saveVerified(credentials) {
|
|
4097
|
+
await this.backend.save(credentials);
|
|
4098
|
+
if (!isDeepStrictEqual(await this.backend.load(), credentials)) throw new Error("Antigravity encrypted credential verification failed");
|
|
4099
|
+
await this.removeLegacy();
|
|
4100
|
+
}
|
|
4101
|
+
read() {
|
|
4102
|
+
return this.serialize(async () => {
|
|
4103
|
+
const current = await this.backend.load();
|
|
4104
|
+
if (current !== null) {
|
|
4105
|
+
await this.removeLegacy();
|
|
4106
|
+
return current;
|
|
4107
|
+
}
|
|
4108
|
+
let legacy;
|
|
4109
|
+
try {
|
|
4110
|
+
const stats = await fsPromises.lstat(this.filePath);
|
|
4111
|
+
if (!stats.isFile() || stats.isSymbolicLink()) throw new Error("Invalid credential file");
|
|
4112
|
+
if (process.getuid && stats.uid !== process.getuid()) throw new Error("Invalid credential owner");
|
|
4113
|
+
if (process.platform !== "win32") await fsPromises.chmod(this.filePath, 384);
|
|
4114
|
+
legacy = await fsPromises.readFile(this.filePath, "utf8");
|
|
4115
|
+
} catch (error) {
|
|
4116
|
+
if (error.code === "ENOENT") return null;
|
|
4117
|
+
throw new Error("Antigravity legacy credential read failed");
|
|
4118
|
+
}
|
|
4119
|
+
let credentials;
|
|
4120
|
+
try {
|
|
4121
|
+
credentials = parseAntigravityCredentials(JSON.parse(legacy));
|
|
4122
|
+
} catch {
|
|
4123
|
+
throw new Error("Antigravity legacy credential payload is invalid");
|
|
4124
|
+
}
|
|
4125
|
+
await this.saveVerified(credentials);
|
|
4126
|
+
return credentials;
|
|
4127
|
+
});
|
|
3943
4128
|
}
|
|
3944
|
-
|
|
3945
|
-
|
|
3946
|
-
|
|
3947
|
-
|
|
3948
|
-
|
|
3949
|
-
|
|
4129
|
+
write(credentials) {
|
|
4130
|
+
return this.serialize(() => this.saveVerified(parseAntigravityCredentials(credentials)));
|
|
4131
|
+
}
|
|
4132
|
+
delete() {
|
|
4133
|
+
return this.serialize(async () => {
|
|
4134
|
+
await this.removeLegacy();
|
|
4135
|
+
await this.backend.clear();
|
|
4136
|
+
});
|
|
3950
4137
|
}
|
|
3951
4138
|
};
|
|
3952
4139
|
var FileModelSettingsStore = class {
|
|
@@ -4064,9 +4251,9 @@ function extractProjectId(data) {
|
|
|
4064
4251
|
}
|
|
4065
4252
|
}
|
|
4066
4253
|
}
|
|
4067
|
-
async function listCloudAICompanionProjects(token) {
|
|
4254
|
+
async function listCloudAICompanionProjects(token, fetchFn = fetch) {
|
|
4068
4255
|
for (const endpoint of endpointCandidates()) try {
|
|
4069
|
-
const response = await
|
|
4256
|
+
const response = await fetchFn(`${endpoint}/v1internal:listCloudAICompanionProjects`, {
|
|
4070
4257
|
method: "POST",
|
|
4071
4258
|
headers: antigravityHeaders(token),
|
|
4072
4259
|
body: JSON.stringify({}),
|
|
@@ -4076,7 +4263,7 @@ async function listCloudAICompanionProjects(token) {
|
|
|
4076
4263
|
return extractProjectId(await response.json());
|
|
4077
4264
|
} catch {}
|
|
4078
4265
|
}
|
|
4079
|
-
async function loadCodeAssist(token) {
|
|
4266
|
+
async function loadCodeAssist(token, fetchFn = fetch) {
|
|
4080
4267
|
const cached = projectCache.get(token);
|
|
4081
4268
|
if (cached && cached.expiresAt > Date.now()) return cached.projectId;
|
|
4082
4269
|
const body = JSON.stringify({ metadata: {
|
|
@@ -4085,7 +4272,7 @@ async function loadCodeAssist(token) {
|
|
|
4085
4272
|
pluginType: "GEMINI"
|
|
4086
4273
|
} });
|
|
4087
4274
|
for (const endpoint of endpointCandidates()) try {
|
|
4088
|
-
const response = await
|
|
4275
|
+
const response = await fetchFn(`${endpoint}/v1internal:loadCodeAssist`, {
|
|
4089
4276
|
method: "POST",
|
|
4090
4277
|
headers: antigravityHeaders(token),
|
|
4091
4278
|
body,
|
|
@@ -4100,7 +4287,7 @@ async function loadCodeAssist(token) {
|
|
|
4100
4287
|
});
|
|
4101
4288
|
return project;
|
|
4102
4289
|
}
|
|
4103
|
-
const listProj = await listCloudAICompanionProjects(token);
|
|
4290
|
+
const listProj = await listCloudAICompanionProjects(token, fetchFn);
|
|
4104
4291
|
if (listProj) {
|
|
4105
4292
|
projectCache.set(token, {
|
|
4106
4293
|
projectId: listProj,
|
|
@@ -4110,9 +4297,9 @@ async function loadCodeAssist(token) {
|
|
|
4110
4297
|
}
|
|
4111
4298
|
} catch {}
|
|
4112
4299
|
}
|
|
4113
|
-
async function postJson(path, token, body) {
|
|
4300
|
+
async function postJson(path, token, body, fetchFn = fetch) {
|
|
4114
4301
|
for (const endpoint of endpointCandidates()) try {
|
|
4115
|
-
const response = await
|
|
4302
|
+
const response = await fetchFn(`${endpoint}${path}`, {
|
|
4116
4303
|
method: "POST",
|
|
4117
4304
|
headers: jsonHeaders(token),
|
|
4118
4305
|
body: JSON.stringify(body)
|
|
@@ -4175,16 +4362,16 @@ function parseCatalogModels(data) {
|
|
|
4175
4362
|
}
|
|
4176
4363
|
return list;
|
|
4177
4364
|
}
|
|
4178
|
-
async function fetchAccountQuota(store = new FileCredentialStore(), modelSettings) {
|
|
4179
|
-
const { token, projectId: credentialProjectId } = await ensureApiKey(store);
|
|
4365
|
+
async function fetchAccountQuota(store = new FileCredentialStore(), modelSettings, fetchFn = fetch) {
|
|
4366
|
+
const { token, projectId: credentialProjectId } = await ensureApiKey(store, fetchFn);
|
|
4180
4367
|
const [assistResult, summaryResult] = await Promise.all([postJson("/v1internal:loadCodeAssist", token, { metadata: {
|
|
4181
4368
|
ideType: "ANTIGRAVITY",
|
|
4182
4369
|
platform: "PLATFORM_UNSPECIFIED",
|
|
4183
4370
|
pluginType: "GEMINI"
|
|
4184
|
-
} }).catch(() => null), postJson("/v1internal:retrieveUserQuotaSummary", token, {}).catch(() => null)]);
|
|
4371
|
+
} }, fetchFn).catch(() => null), postJson("/v1internal:retrieveUserQuotaSummary", token, {}, fetchFn).catch(() => null)]);
|
|
4185
4372
|
const discoveredProject = assistResult ? extractProjectId(assistResult.data) : void 0;
|
|
4186
4373
|
const projectId = credentialProjectId || discoveredProject || "antigravity-default";
|
|
4187
|
-
const modelsData = (await postJson("/v1internal:fetchAvailableModels", token, { project: projectId }).catch(() => null))?.data;
|
|
4374
|
+
const modelsData = (await postJson("/v1internal:fetchAvailableModels", token, { project: projectId }, fetchFn).catch(() => null))?.data;
|
|
4188
4375
|
const { groups, description } = summaryResult ? parseQuotaSummary(summaryResult.data) : { groups: [] };
|
|
4189
4376
|
const catalogModels = modelsData ? parseCatalogModels(modelsData) : [];
|
|
4190
4377
|
const assistData = assistResult?.data || {};
|
|
@@ -4282,9 +4469,9 @@ function openBrowser(url) {
|
|
|
4282
4469
|
});
|
|
4283
4470
|
} catch {}
|
|
4284
4471
|
}
|
|
4285
|
-
async function getUserEmail(token) {
|
|
4472
|
+
async function getUserEmail(token, fetchFn = fetch) {
|
|
4286
4473
|
try {
|
|
4287
|
-
const response = await
|
|
4474
|
+
const response = await fetchFn("https://www.googleapis.com/oauth2/v1/userinfo?alt=json", { headers: { Authorization: `Bearer ${token}` } });
|
|
4288
4475
|
if (!response.ok) return void 0;
|
|
4289
4476
|
const data = await response.json();
|
|
4290
4477
|
return typeof data.email === "string" ? data.email : void 0;
|
|
@@ -4363,8 +4550,8 @@ function startCallbackServer(expectedState) {
|
|
|
4363
4550
|
});
|
|
4364
4551
|
});
|
|
4365
4552
|
}
|
|
4366
|
-
async function exchangeOAuthCode(code, verifier, callbackUrl) {
|
|
4367
|
-
const tokenResponse = await
|
|
4553
|
+
async function exchangeOAuthCode(code, verifier, callbackUrl, fetchFn = fetch) {
|
|
4554
|
+
const tokenResponse = await fetchFn(TOKEN_URL, {
|
|
4368
4555
|
method: "POST",
|
|
4369
4556
|
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
4370
4557
|
body: new URLSearchParams$1({
|
|
@@ -4382,7 +4569,7 @@ async function exchangeOAuthCode(code, verifier, callbackUrl) {
|
|
|
4382
4569
|
const accessToken = typeof tokenData.access_token === "string" ? tokenData.access_token : "";
|
|
4383
4570
|
const expiresIn = typeof tokenData.expires_in === "number" ? tokenData.expires_in : 3600;
|
|
4384
4571
|
if (!refreshToken) throw new Error("No refresh token received. Re-run login and allow offline access.");
|
|
4385
|
-
const [email, discoveredProject] = await Promise.all([getUserEmail(accessToken), loadCodeAssist(accessToken).catch(() => void 0)]);
|
|
4572
|
+
const [email, discoveredProject] = await Promise.all([getUserEmail(accessToken, fetchFn), loadCodeAssist(accessToken, fetchFn).catch(() => void 0)]);
|
|
4386
4573
|
return {
|
|
4387
4574
|
refresh: refreshToken,
|
|
4388
4575
|
refresh_token: refreshToken,
|
|
@@ -4394,7 +4581,7 @@ async function exchangeOAuthCode(code, verifier, callbackUrl) {
|
|
|
4394
4581
|
email
|
|
4395
4582
|
};
|
|
4396
4583
|
}
|
|
4397
|
-
async function beginWebLogin(store) {
|
|
4584
|
+
async function beginWebLogin(store, fetchFn = fetch) {
|
|
4398
4585
|
if (webLoginFlow.status === "pending") return { ...webLoginFlow };
|
|
4399
4586
|
const { verifier, challenge } = generatePKCE();
|
|
4400
4587
|
const state = base64Url(randomBytes(32));
|
|
@@ -4420,7 +4607,7 @@ async function beginWebLogin(store) {
|
|
|
4420
4607
|
try {
|
|
4421
4608
|
const { code, state: returnedState } = await waitForCode();
|
|
4422
4609
|
if (returnedState !== state) throw new Error("OAuth state mismatch");
|
|
4423
|
-
const credentials = await exchangeOAuthCode(code, verifier, callbackUrl);
|
|
4610
|
+
const credentials = await exchangeOAuthCode(code, verifier, callbackUrl, fetchFn);
|
|
4424
4611
|
await store.write(credentials);
|
|
4425
4612
|
webLoginFlow.status = "complete";
|
|
4426
4613
|
webLoginFlow.email = credentials.email;
|
|
@@ -4438,10 +4625,10 @@ async function beginWebLogin(store) {
|
|
|
4438
4625
|
function getWebLoginStatus() {
|
|
4439
4626
|
return { ...webLoginFlow };
|
|
4440
4627
|
}
|
|
4441
|
-
async function refreshAntigravityToken(credentials) {
|
|
4628
|
+
async function refreshAntigravityToken(credentials, fetchFn = fetch) {
|
|
4442
4629
|
const refreshToken = credentials.refresh || credentials.refresh_token;
|
|
4443
4630
|
if (!refreshToken) throw new Error("Missing Antigravity refresh token.");
|
|
4444
|
-
const response = await
|
|
4631
|
+
const response = await fetchFn(TOKEN_URL, {
|
|
4445
4632
|
method: "POST",
|
|
4446
4633
|
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
4447
4634
|
body: new URLSearchParams$1({
|
|
@@ -4466,12 +4653,12 @@ async function refreshAntigravityToken(credentials) {
|
|
|
4466
4653
|
expires_at: Date.now() + expiresIn * 1e3 - 300 * 1e3
|
|
4467
4654
|
};
|
|
4468
4655
|
}
|
|
4469
|
-
async function ensureApiKey(store) {
|
|
4656
|
+
async function ensureApiKey(store, fetchFn = fetch) {
|
|
4470
4657
|
let credentials = await store.read();
|
|
4471
4658
|
if (!credentials) throw new Error("Not logged into Antigravity. Please log in from Settings > Antigravity.");
|
|
4472
4659
|
const expires = credentials.expires || credentials.expires_at || 0;
|
|
4473
4660
|
if (!(credentials.access || credentials.access_token) || expires <= Date.now() + 6e4) {
|
|
4474
|
-
credentials = await refreshAntigravityToken(credentials);
|
|
4661
|
+
credentials = await refreshAntigravityToken(credentials, fetchFn);
|
|
4475
4662
|
await store.write(credentials);
|
|
4476
4663
|
}
|
|
4477
4664
|
return {
|
|
@@ -4479,7 +4666,7 @@ async function ensureApiKey(store) {
|
|
|
4479
4666
|
projectId: credentials.projectId
|
|
4480
4667
|
};
|
|
4481
4668
|
}
|
|
4482
|
-
async function loginAndSave(store, signal, onUrl) {
|
|
4669
|
+
async function loginAndSave(store, signal, onUrl, fetchFn = fetch) {
|
|
4483
4670
|
const { verifier, challenge } = generatePKCE();
|
|
4484
4671
|
const state = base64Url(randomBytes(32));
|
|
4485
4672
|
const { server, waitForCode } = await startCallbackServer(state);
|
|
@@ -4501,7 +4688,7 @@ async function loginAndSave(store, signal, onUrl) {
|
|
|
4501
4688
|
if (signal?.aborted) throw new Error("OAuth login aborted");
|
|
4502
4689
|
const { code, state: returnedState } = await waitForCode();
|
|
4503
4690
|
if (returnedState !== state) throw new Error("OAuth state mismatch");
|
|
4504
|
-
const credentials = await exchangeOAuthCode(code, verifier, callbackUrl);
|
|
4691
|
+
const credentials = await exchangeOAuthCode(code, verifier, callbackUrl, fetchFn);
|
|
4505
4692
|
await store.write(credentials);
|
|
4506
4693
|
return credentials;
|
|
4507
4694
|
} finally {
|
|
@@ -4578,15 +4765,27 @@ function toolResultText(blocks) {
|
|
|
4578
4765
|
}
|
|
4579
4766
|
function replayBlockFor(message, index) {
|
|
4580
4767
|
const source = message.source;
|
|
4581
|
-
if (!source || source.kind !== "model") return void 0;
|
|
4768
|
+
if (!source || source.kind !== "model" || source.provider !== "antigravity") return void 0;
|
|
4582
4769
|
const state = source.replayState;
|
|
4583
4770
|
if (!isRecord(state)) return void 0;
|
|
4771
|
+
if (Array.isArray(state.blocks)) return state.blocks[index];
|
|
4584
4772
|
const resp = isRecord(state.response) ? state.response : void 0;
|
|
4585
4773
|
if (resp) {
|
|
4586
4774
|
if (Array.isArray(resp.outputItems)) return resp.outputItems[index];
|
|
4587
4775
|
if (Array.isArray(resp.blocks)) return resp.blocks[index];
|
|
4588
4776
|
}
|
|
4589
|
-
|
|
4777
|
+
}
|
|
4778
|
+
function thoughtSignature(part) {
|
|
4779
|
+
return asString(part?.thoughtSignature) || asString(part?.thought_signature) || asString(part?.thinkingSignature) || asString(part?.textSignature);
|
|
4780
|
+
}
|
|
4781
|
+
function replayPart(part) {
|
|
4782
|
+
const copy = { ...part };
|
|
4783
|
+
const signature = thoughtSignature(part);
|
|
4784
|
+
delete copy.thought_signature;
|
|
4785
|
+
delete copy.thinkingSignature;
|
|
4786
|
+
if (signature) copy.thoughtSignature = signature;
|
|
4787
|
+
if (typeof copy.text === "string") copy.text = sanitizeText(copy.text);
|
|
4788
|
+
return copy;
|
|
4590
4789
|
}
|
|
4591
4790
|
function assistantParts(message, model, runtimeModel, toolNames) {
|
|
4592
4791
|
const parts = [];
|
|
@@ -4595,30 +4794,39 @@ function assistantParts(message, model, runtimeModel, toolNames) {
|
|
|
4595
4794
|
const block = message.content[index];
|
|
4596
4795
|
if (!isRecord(block)) continue;
|
|
4597
4796
|
const replay = replayBlockFor(message, index);
|
|
4598
|
-
|
|
4599
|
-
|
|
4600
|
-
|
|
4601
|
-
|
|
4797
|
+
const originalParts = Array.isArray(replay?.parts) ? replay.parts.filter(isRecord) : [];
|
|
4798
|
+
if ((block.type === "text" || block.type === "reasoning") && originalParts.length > 0 && originalParts.every((part) => !part.functionCall) && originalParts.map((part) => asString(part.text) || "").join("") === sanitizeText(String(block.text || ""))) {
|
|
4799
|
+
parts.push(...originalParts.map(replayPart));
|
|
4800
|
+
continue;
|
|
4801
|
+
}
|
|
4802
|
+
if (block.type === "text" && String(block.text || "").trim()) {
|
|
4803
|
+
const sig = thoughtSignature(replay) || thoughtSignature(block);
|
|
4804
|
+
parts.push({
|
|
4805
|
+
text: sanitizeText(String(block.text)),
|
|
4806
|
+
...sig ? { thoughtSignature: sig } : {}
|
|
4807
|
+
});
|
|
4808
|
+
} else if (block.type === "reasoning" && String(block.text || "").trim()) {
|
|
4809
|
+
const sig = thoughtSignature(replay) || thoughtSignature(block);
|
|
4810
|
+
parts.push({
|
|
4602
4811
|
thought: true,
|
|
4603
4812
|
text: sanitizeText(String(block.text)),
|
|
4604
|
-
|
|
4605
|
-
thoughtSignature: sig
|
|
4813
|
+
...sig ? { thoughtSignature: sig } : {}
|
|
4606
4814
|
});
|
|
4607
|
-
else parts.push({ text: sanitizeText(String(block.text)) });
|
|
4608
4815
|
} else if (block.type === "tool-call") {
|
|
4609
4816
|
const toolId = String(block.id || "");
|
|
4610
4817
|
const toolName = String(block.name || "");
|
|
4611
4818
|
toolNames.set(toolId, toolName);
|
|
4612
|
-
const
|
|
4819
|
+
const originalCall = originalParts.find((part) => isRecord(part.functionCall));
|
|
4820
|
+
const effectiveSignature = thoughtSignature(originalCall) || thoughtSignature(replay) || thoughtSignature(block) || (originalCall ? void 0 : "skip_thought_signature_validator");
|
|
4613
4821
|
parts.push({
|
|
4614
4822
|
functionCall: {
|
|
4615
4823
|
name: toolName,
|
|
4616
4824
|
args: parseArguments(block.arguments),
|
|
4617
4825
|
...toolCallIdNeeded(model.id, runtimeModel) ? { id: sanitizeToolCallId(toolId, toolName) } : {}
|
|
4618
4826
|
},
|
|
4619
|
-
|
|
4620
|
-
thoughtSignature: sig
|
|
4827
|
+
...effectiveSignature ? { thoughtSignature: effectiveSignature } : {}
|
|
4621
4828
|
});
|
|
4829
|
+
parts.push(...originalParts.filter((part) => !part.functionCall).map(replayPart));
|
|
4622
4830
|
}
|
|
4623
4831
|
}
|
|
4624
4832
|
return parts;
|
|
@@ -4722,6 +4930,8 @@ function buildRequest(options, model, projectId, runtimeModel, effort) {
|
|
|
4722
4930
|
const isTiered = runtimeModel === "gemini-3.8-flash-tiered" || runtimeModel === "gemini-3.7-flash-tiered";
|
|
4723
4931
|
const isSuffixed = /^gemini-.+(?:-(?:extra-)?low|-medium|-high|-xhigh)$/.test(runtimeModel);
|
|
4724
4932
|
const isGemini25 = runtimeModel.startsWith("gemini-2.5-") || model.id.startsWith("gemini-2.5-");
|
|
4933
|
+
const isGemini3 = /^gemini-3[.-]/.test(runtimeModel) && !runtimeModel.includes("image");
|
|
4934
|
+
const isGeminiAgent = runtimeModel === "gemini-pro-agent" || runtimeModel === "gemini-3-flash-agent";
|
|
4725
4935
|
if (isTiered) {
|
|
4726
4936
|
const selected = (effort || "medium").toLowerCase();
|
|
4727
4937
|
const isOff = selected === "off" || selected === "none";
|
|
@@ -4729,7 +4939,7 @@ function buildRequest(options, model, projectId, runtimeModel, effort) {
|
|
|
4729
4939
|
thinkingLevel: isOff ? "MINIMAL" : selected === "high" || selected === "xhigh" ? "HIGH" : selected === "medium" ? "MEDIUM" : "LOW",
|
|
4730
4940
|
includeThoughts: !isOff
|
|
4731
4941
|
};
|
|
4732
|
-
} else if (isSuffixed) {
|
|
4942
|
+
} else if (isSuffixed || isGemini3 || isGeminiAgent) {
|
|
4733
4943
|
const selected = (effort || "medium").toLowerCase();
|
|
4734
4944
|
generationConfig.thinkingConfig = { includeThoughts: !(selected === "off" || selected === "none") };
|
|
4735
4945
|
} else if (isGemini25) {
|
|
@@ -4765,58 +4975,90 @@ function createStreamState() {
|
|
|
4765
4975
|
replayBlocks: [],
|
|
4766
4976
|
currentBlock: null,
|
|
4767
4977
|
hasContent: false,
|
|
4768
|
-
hasToolCall: false
|
|
4978
|
+
hasToolCall: false,
|
|
4979
|
+
usageMetadata: null,
|
|
4980
|
+
done: false,
|
|
4981
|
+
finished: false
|
|
4982
|
+
};
|
|
4983
|
+
}
|
|
4984
|
+
function closeCurrentBlock(state) {
|
|
4985
|
+
if (!state.currentBlock) return [];
|
|
4986
|
+
const { index, type, text } = state.currentBlock;
|
|
4987
|
+
const block = {
|
|
4988
|
+
type,
|
|
4989
|
+
text
|
|
4990
|
+
};
|
|
4991
|
+
state.blocks[index] = block;
|
|
4992
|
+
state.currentBlock = null;
|
|
4993
|
+
return [{
|
|
4994
|
+
type: "block-end",
|
|
4995
|
+
index,
|
|
4996
|
+
block
|
|
4997
|
+
}];
|
|
4998
|
+
}
|
|
4999
|
+
const USAGE_FIELDS = [
|
|
5000
|
+
"promptTokenCount",
|
|
5001
|
+
"cachedContentTokenCount",
|
|
5002
|
+
"candidatesTokenCount",
|
|
5003
|
+
"thoughtsTokenCount",
|
|
5004
|
+
"totalTokenCount"
|
|
5005
|
+
];
|
|
5006
|
+
function collectUsage(value, state) {
|
|
5007
|
+
if (!isRecord(value)) return;
|
|
5008
|
+
for (const key of USAGE_FIELDS) {
|
|
5009
|
+
const count = value[key];
|
|
5010
|
+
if (typeof count !== "number" || !Number.isSafeInteger(count) || count < 0) continue;
|
|
5011
|
+
state.usageMetadata ??= {};
|
|
5012
|
+
state.usageMetadata[key] = count;
|
|
5013
|
+
}
|
|
5014
|
+
}
|
|
5015
|
+
function tokenUsage(u) {
|
|
5016
|
+
const prompt = u.promptTokenCount ?? 0;
|
|
5017
|
+
const cache = Math.min(prompt, u.cachedContentTokenCount ?? 0);
|
|
5018
|
+
const thoughts = u.thoughtsTokenCount ?? 0;
|
|
5019
|
+
const explicitOutput = (u.candidatesTokenCount ?? 0) + thoughts;
|
|
5020
|
+
const totalOutput = u.totalTokenCount !== void 0 && u.promptTokenCount !== void 0 ? Math.max(0, u.totalTokenCount - prompt) : 0;
|
|
5021
|
+
return {
|
|
5022
|
+
inputTokens: prompt - cache,
|
|
5023
|
+
outputTokens: Math.max(explicitOutput, totalOutput),
|
|
5024
|
+
...cache > 0 ? { cacheReadTokens: cache } : {},
|
|
5025
|
+
...u.thoughtsTokenCount !== void 0 ? { reasoningTokens: thoughts } : {}
|
|
4769
5026
|
};
|
|
4770
5027
|
}
|
|
4771
5028
|
function processStreamLine(line, state) {
|
|
4772
|
-
if (!line.startsWith("data:")) return [];
|
|
5029
|
+
if (state.finished || !line.startsWith("data:")) return [];
|
|
4773
5030
|
const json = line.slice(5).trim();
|
|
4774
|
-
if (
|
|
5031
|
+
if (json === "[DONE]") {
|
|
5032
|
+
state.done = true;
|
|
5033
|
+
return closeStream(state);
|
|
5034
|
+
}
|
|
5035
|
+
if (!json) return [];
|
|
4775
5036
|
const chunk = safeJsonParse(json);
|
|
4776
5037
|
if (!isRecord(chunk)) return [];
|
|
4777
5038
|
const responseData = isRecord(chunk.response) ? chunk.response : chunk;
|
|
4778
|
-
const
|
|
5039
|
+
const candidates = Array.isArray(responseData.candidates) ? responseData.candidates : [];
|
|
5040
|
+
const candidate = isRecord(candidates[0]) ? candidates[0] : void 0;
|
|
4779
5041
|
const content = isRecord(candidate?.content) ? candidate.content : void 0;
|
|
4780
5042
|
const parts = Array.isArray(content?.parts) ? content.parts : [];
|
|
4781
5043
|
const out = [];
|
|
4782
|
-
const closeCurrentBlock = () => {
|
|
4783
|
-
if (!state.currentBlock) return;
|
|
4784
|
-
const index = state.blocks.length - 1;
|
|
4785
|
-
if (state.currentBlock.type === "text") out.push({
|
|
4786
|
-
type: "block-end",
|
|
4787
|
-
index,
|
|
4788
|
-
block: {
|
|
4789
|
-
type: "text",
|
|
4790
|
-
text: state.currentBlock.text
|
|
4791
|
-
}
|
|
4792
|
-
});
|
|
4793
|
-
else out.push({
|
|
4794
|
-
type: "block-end",
|
|
4795
|
-
index,
|
|
4796
|
-
block: {
|
|
4797
|
-
type: "reasoning",
|
|
4798
|
-
text: state.currentBlock.text
|
|
4799
|
-
}
|
|
4800
|
-
});
|
|
4801
|
-
state.currentBlock = null;
|
|
4802
|
-
};
|
|
4803
5044
|
for (const part of parts) {
|
|
4804
5045
|
if (!isRecord(part)) continue;
|
|
4805
|
-
if (part.text
|
|
5046
|
+
if (typeof part.text === "string" && part.text !== "") {
|
|
4806
5047
|
const isThinking = Boolean(part.thought);
|
|
4807
5048
|
const blockType = isThinking ? "reasoning" : "text";
|
|
4808
5049
|
if (!state.currentBlock || state.currentBlock.type !== blockType) {
|
|
4809
|
-
closeCurrentBlock();
|
|
5050
|
+
out.push(...closeCurrentBlock(state));
|
|
5051
|
+
const index = state.blocks.length;
|
|
4810
5052
|
state.currentBlock = {
|
|
5053
|
+
index,
|
|
4811
5054
|
type: blockType,
|
|
4812
5055
|
text: ""
|
|
4813
5056
|
};
|
|
4814
|
-
const index = state.blocks.length;
|
|
4815
5057
|
state.blocks.push({
|
|
4816
5058
|
type: blockType,
|
|
4817
5059
|
text: ""
|
|
4818
5060
|
});
|
|
4819
|
-
state.replayBlocks.push({
|
|
5061
|
+
state.replayBlocks.push({ parts: [] });
|
|
4820
5062
|
out.push({
|
|
4821
5063
|
type: "block-start",
|
|
4822
5064
|
index,
|
|
@@ -4826,45 +5068,55 @@ function processStreamLine(line, state) {
|
|
|
4826
5068
|
const delta = sanitizeText(part.text);
|
|
4827
5069
|
state.currentBlock.text += delta;
|
|
4828
5070
|
state.hasContent = true;
|
|
4829
|
-
|
|
4830
|
-
state.currentBlock.thinkingSignature = part.thoughtSignature;
|
|
4831
|
-
state.replayBlocks[state.blocks.length - 1].thinkingSignature = part.thoughtSignature;
|
|
4832
|
-
} else if (!isThinking && part.thoughtSignature) {
|
|
4833
|
-
state.currentBlock.textSignature = part.thoughtSignature;
|
|
4834
|
-
state.replayBlocks[state.blocks.length - 1].textSignature = part.thoughtSignature;
|
|
4835
|
-
}
|
|
5071
|
+
state.replayBlocks[state.currentBlock.index].parts.push(replayPart(part));
|
|
4836
5072
|
out.push({
|
|
4837
5073
|
type: isThinking ? "reasoning-delta" : "text-delta",
|
|
4838
|
-
index: state.
|
|
5074
|
+
index: state.currentBlock.index,
|
|
4839
5075
|
text: delta
|
|
4840
5076
|
});
|
|
5077
|
+
} else if (!isRecord(part.functionCall) && thoughtSignature(part)) {
|
|
5078
|
+
if (state.replayBlocks.length === 0) {
|
|
5079
|
+
const type = part.thought ? "reasoning" : "text";
|
|
5080
|
+
state.blocks.push({
|
|
5081
|
+
type,
|
|
5082
|
+
text: ""
|
|
5083
|
+
});
|
|
5084
|
+
state.replayBlocks.push({ parts: [] });
|
|
5085
|
+
out.push({
|
|
5086
|
+
type: "block-start",
|
|
5087
|
+
index: 0,
|
|
5088
|
+
blockType: type
|
|
5089
|
+
});
|
|
5090
|
+
out.push({
|
|
5091
|
+
type: "block-end",
|
|
5092
|
+
index: 0,
|
|
5093
|
+
block: {
|
|
5094
|
+
type,
|
|
5095
|
+
text: ""
|
|
5096
|
+
}
|
|
5097
|
+
});
|
|
5098
|
+
}
|
|
5099
|
+
state.replayBlocks[state.replayBlocks.length - 1].parts.push(replayPart(part));
|
|
4841
5100
|
}
|
|
4842
5101
|
if (isRecord(part.functionCall)) {
|
|
4843
|
-
closeCurrentBlock();
|
|
5102
|
+
out.push(...closeCurrentBlock(state));
|
|
4844
5103
|
const fc = part.functionCall;
|
|
4845
5104
|
const toolName = asString(fc.name) || "";
|
|
4846
5105
|
const toolId = sanitizeToolCallId(asString(fc.id) || "", toolName);
|
|
4847
5106
|
const argsText = JSON.stringify(isRecord(fc.args) ? fc.args : {});
|
|
4848
5107
|
const index = state.blocks.length;
|
|
4849
|
-
const sig = asString(part.thought_signature) || asString(part.thoughtSignature) || asString(part.thinkingSignature) || asString(fc.thought_signature) || asString(fc.thoughtSignature) || state.currentBlock?.thinkingSignature;
|
|
4850
5108
|
const block = {
|
|
4851
5109
|
type: "tool-call",
|
|
4852
|
-
id: toolId,
|
|
5110
|
+
id: CallId(toolId),
|
|
4853
5111
|
name: toolName,
|
|
4854
|
-
arguments: argsText
|
|
4855
|
-
...sig ? {
|
|
4856
|
-
thought_signature: sig,
|
|
4857
|
-
thoughtSignature: sig
|
|
4858
|
-
} : {}
|
|
5112
|
+
arguments: argsText
|
|
4859
5113
|
};
|
|
4860
5114
|
state.blocks.push(block);
|
|
4861
|
-
|
|
4862
|
-
|
|
4863
|
-
...
|
|
4864
|
-
|
|
4865
|
-
|
|
4866
|
-
} : {}
|
|
4867
|
-
});
|
|
5115
|
+
const sig = thoughtSignature(part) || thoughtSignature(fc);
|
|
5116
|
+
state.replayBlocks.push({ parts: [{
|
|
5117
|
+
...replayPart(part),
|
|
5118
|
+
...sig ? { thoughtSignature: sig } : {}
|
|
5119
|
+
}] });
|
|
4868
5120
|
state.hasContent = true;
|
|
4869
5121
|
state.hasToolCall = true;
|
|
4870
5122
|
out.push({
|
|
@@ -4875,7 +5127,7 @@ function processStreamLine(line, state) {
|
|
|
4875
5127
|
out.push({
|
|
4876
5128
|
type: "tool-call-delta",
|
|
4877
5129
|
index,
|
|
4878
|
-
id: toolId,
|
|
5130
|
+
id: CallId(toolId),
|
|
4879
5131
|
name: toolName,
|
|
4880
5132
|
argumentsDelta: argsText
|
|
4881
5133
|
});
|
|
@@ -4886,55 +5138,33 @@ function processStreamLine(line, state) {
|
|
|
4886
5138
|
});
|
|
4887
5139
|
}
|
|
4888
5140
|
}
|
|
4889
|
-
|
|
4890
|
-
|
|
4891
|
-
|
|
4892
|
-
inputTokens: Math.max(0, (u.promptTokenCount || 0) - (u.cachedContentTokenCount || 0)),
|
|
4893
|
-
outputTokens: (u.candidatesTokenCount || 0) + (u.thoughtsTokenCount || 0),
|
|
4894
|
-
...u.cachedContentTokenCount ? { cacheReadTokens: u.cachedContentTokenCount } : {}
|
|
4895
|
-
};
|
|
4896
|
-
out.push({
|
|
4897
|
-
type: "usage",
|
|
4898
|
-
usage
|
|
4899
|
-
});
|
|
4900
|
-
}
|
|
4901
|
-
const finishReason = candidate?.finishReason;
|
|
5141
|
+
collectUsage(chunk.usageMetadata, state);
|
|
5142
|
+
if (responseData !== chunk) collectUsage(responseData.usageMetadata, state);
|
|
5143
|
+
const finishReason = asString(candidate?.finishReason) || asString(responseData.finishReason);
|
|
4902
5144
|
if (finishReason) {
|
|
4903
|
-
|
|
4904
|
-
|
|
4905
|
-
out.push({
|
|
4906
|
-
type: "finish",
|
|
4907
|
-
reason,
|
|
4908
|
-
replayState: { response: {
|
|
4909
|
-
outputItems: state.replayBlocks,
|
|
4910
|
-
blocks: state.replayBlocks
|
|
4911
|
-
} }
|
|
4912
|
-
});
|
|
5145
|
+
state.finishReason = finishReason;
|
|
5146
|
+
out.push(...closeCurrentBlock(state));
|
|
4913
5147
|
}
|
|
4914
5148
|
return out;
|
|
4915
5149
|
}
|
|
4916
5150
|
function closeStream(state) {
|
|
4917
|
-
|
|
4918
|
-
if (state.
|
|
4919
|
-
|
|
4920
|
-
|
|
4921
|
-
|
|
4922
|
-
|
|
4923
|
-
|
|
4924
|
-
|
|
4925
|
-
|
|
4926
|
-
|
|
4927
|
-
|
|
4928
|
-
|
|
4929
|
-
|
|
4930
|
-
|
|
4931
|
-
|
|
4932
|
-
|
|
4933
|
-
|
|
4934
|
-
}
|
|
4935
|
-
});
|
|
4936
|
-
state.currentBlock = null;
|
|
4937
|
-
}
|
|
5151
|
+
if (state.finished) return [];
|
|
5152
|
+
if (!state.finishReason && !state.done) throw new LlmError("Antigravity stream ended before its terminal response", "PROVIDER_ERROR");
|
|
5153
|
+
state.finished = true;
|
|
5154
|
+
const out = closeCurrentBlock(state);
|
|
5155
|
+
if (state.usageMetadata) out.push({
|
|
5156
|
+
type: "usage",
|
|
5157
|
+
usage: tokenUsage(state.usageMetadata)
|
|
5158
|
+
});
|
|
5159
|
+
const reason = state.finishReason === "MAX_TOKENS" ? { kind: "max-tokens" } : state.hasToolCall ? { kind: "tool-calls" } : { kind: "stop" };
|
|
5160
|
+
out.push({
|
|
5161
|
+
type: "finish",
|
|
5162
|
+
reason,
|
|
5163
|
+
replayState: {
|
|
5164
|
+
response: { provider: PROVIDER_ID },
|
|
5165
|
+
blocks: state.replayBlocks
|
|
5166
|
+
}
|
|
5167
|
+
});
|
|
4938
5168
|
return out;
|
|
4939
5169
|
}
|
|
4940
5170
|
//#endregion
|
|
@@ -4943,11 +5173,13 @@ var AntigravityAdapter = class extends LlmAdapter {
|
|
|
4943
5173
|
store;
|
|
4944
5174
|
modelSettings;
|
|
4945
5175
|
preferences;
|
|
4946
|
-
|
|
5176
|
+
options;
|
|
5177
|
+
constructor(store = new FileCredentialStore(), modelSettings = new FileModelSettingsStore(), preferences, options = {}) {
|
|
4947
5178
|
super();
|
|
4948
5179
|
this.store = store;
|
|
4949
5180
|
this.modelSettings = modelSettings;
|
|
4950
5181
|
this.preferences = preferences;
|
|
5182
|
+
this.options = options;
|
|
4951
5183
|
}
|
|
4952
5184
|
providerInfo(provider) {
|
|
4953
5185
|
return {
|
|
@@ -5018,7 +5250,7 @@ var AntigravityAdapter = class extends LlmAdapter {
|
|
|
5018
5250
|
contextWindow: 128e3,
|
|
5019
5251
|
maxTokens: 65536
|
|
5020
5252
|
};
|
|
5021
|
-
const settings = await this.modelSettings.read();
|
|
5253
|
+
const settings = this.preferences ? this.preferences.status() : await this.modelSettings.read();
|
|
5022
5254
|
const effectiveEffort = options.reasoningEffort || settings.defaultReasoningEffort || void 0;
|
|
5023
5255
|
const effectiveOptions = effectiveEffort ? {
|
|
5024
5256
|
...options,
|
|
@@ -5027,7 +5259,8 @@ var AntigravityAdapter = class extends LlmAdapter {
|
|
|
5027
5259
|
yield* wrapStreamWithWatchdog((watchdogSignal) => this.requestStream(effectiveOptions, model, watchdogSignal), options.signal, STREAM_IDLE_TIMEOUT_MS, STREAM_IDLE_TIMEOUT_CODE, "Antigravity");
|
|
5028
5260
|
}
|
|
5029
5261
|
async *requestStream(options, model, signal) {
|
|
5030
|
-
const
|
|
5262
|
+
const fetchFn = this.options.fetchFn ?? fetch;
|
|
5263
|
+
const { token, projectId: defaultProj } = await ensureApiKey(this.store, fetchFn);
|
|
5031
5264
|
const projectId = defaultProj || "antigravity-default";
|
|
5032
5265
|
const effort = String(options.reasoningEffort || "medium").toLowerCase();
|
|
5033
5266
|
const routing = ROUTING[model.id];
|
|
@@ -5039,7 +5272,6 @@ var AntigravityAdapter = class extends LlmAdapter {
|
|
|
5039
5272
|
for (const fc of routing.fallbackCandidates) if (!candidates.includes(fc)) candidates.push(fc);
|
|
5040
5273
|
}
|
|
5041
5274
|
let response;
|
|
5042
|
-
candidates[0];
|
|
5043
5275
|
for (const runtimeModel of candidates) {
|
|
5044
5276
|
const body = JSON.stringify(buildRequest(options, model, projectId, runtimeModel, effort));
|
|
5045
5277
|
const headers = {
|
|
@@ -5047,7 +5279,7 @@ var AntigravityAdapter = class extends LlmAdapter {
|
|
|
5047
5279
|
...model.id.startsWith("claude-") ? { "anthropic-beta": "interleaved-thinking-2025-05-14" } : {}
|
|
5048
5280
|
};
|
|
5049
5281
|
for (const endpoint of endpointCandidates()) try {
|
|
5050
|
-
response = await
|
|
5282
|
+
response = await fetchFn(`${endpoint}/v1internal:streamGenerateContent?alt=sse`, {
|
|
5051
5283
|
method: "POST",
|
|
5052
5284
|
headers,
|
|
5053
5285
|
body,
|
|
@@ -5083,8 +5315,10 @@ var AntigravityAdapter = class extends LlmAdapter {
|
|
|
5083
5315
|
if (!trimmed) continue;
|
|
5084
5316
|
const chunks = processStreamLine(trimmed, state);
|
|
5085
5317
|
for (const chunk of chunks) yield chunk;
|
|
5318
|
+
if (state.finished) return;
|
|
5086
5319
|
}
|
|
5087
5320
|
}
|
|
5321
|
+
buffer += decoder.decode();
|
|
5088
5322
|
if (buffer.trim()) {
|
|
5089
5323
|
const chunks = processStreamLine(buffer.trim(), state);
|
|
5090
5324
|
for (const chunk of chunks) yield chunk;
|
|
@@ -5151,7 +5385,7 @@ async function getAntigravityWebStatus(store, modelSettings, preferences) {
|
|
|
5151
5385
|
defaultReasoningEffort: settings.defaultReasoningEffort || null
|
|
5152
5386
|
};
|
|
5153
5387
|
}
|
|
5154
|
-
function registerAntigravityRoutes(ctx, store, modelSettings, preferences) {
|
|
5388
|
+
function registerAntigravityRoutes(ctx, store, modelSettings, preferences, fetchFn = fetch) {
|
|
5155
5389
|
return ctx.webServer.register({
|
|
5156
5390
|
kind: "prefix",
|
|
5157
5391
|
path: "/antigravity/api",
|
|
@@ -5169,7 +5403,7 @@ function registerAntigravityRoutes(ctx, store, modelSettings, preferences) {
|
|
|
5169
5403
|
if (request.method !== "POST") return sendMethodNotAllowed(response);
|
|
5170
5404
|
return sendJson(response, 200, {
|
|
5171
5405
|
ok: true,
|
|
5172
|
-
value: await beginWebLogin(store)
|
|
5406
|
+
value: await beginWebLogin(store, fetchFn)
|
|
5173
5407
|
});
|
|
5174
5408
|
}
|
|
5175
5409
|
if (path === "login/status") {
|
|
@@ -5181,7 +5415,7 @@ function registerAntigravityRoutes(ctx, store, modelSettings, preferences) {
|
|
|
5181
5415
|
}
|
|
5182
5416
|
if (path === "quota") {
|
|
5183
5417
|
if (request.method !== "GET" && request.method !== "POST") return sendMethodNotAllowed(response);
|
|
5184
|
-
const quota = await fetchAccountQuota(store, modelSettings);
|
|
5418
|
+
const quota = await fetchAccountQuota(store, modelSettings, fetchFn);
|
|
5185
5419
|
return sendJson(response, 200, {
|
|
5186
5420
|
ok: true,
|
|
5187
5421
|
value: {
|
|
@@ -5254,21 +5488,15 @@ function apply(ctx) {
|
|
|
5254
5488
|
const antigravityStore = new FileCredentialStore();
|
|
5255
5489
|
const antigravityModelSettings = new FileModelSettingsStore();
|
|
5256
5490
|
const antigravityPreferences = registerAntigravityPreferenceStore(ctx.settings, antigravityModelSettings);
|
|
5257
|
-
const antigravityAdapter = new AntigravityAdapter(antigravityStore, antigravityModelSettings, antigravityPreferences);
|
|
5258
|
-
ctx.effect(() => {
|
|
5259
|
-
const disposeAntigravityAdapter = ctx.llm.registerAdapter([PROVIDER_ID], antigravityAdapter);
|
|
5260
|
-
const disposeAntigravityRoutes = registerAntigravityRoutes(ctx, antigravityStore, antigravityModelSettings, antigravityPreferences);
|
|
5261
|
-
return () => {
|
|
5262
|
-
disposeAntigravityRoutes();
|
|
5263
|
-
disposeAntigravityAdapter();
|
|
5264
|
-
};
|
|
5265
|
-
}, "dsh-antigravity: adapter, routes, and lifecycle");
|
|
5266
5491
|
ctx.effect(() => {
|
|
5267
5492
|
const proxyManager = new ProxyManager({
|
|
5268
5493
|
getPreferences: () => preferences.status(),
|
|
5269
5494
|
logger: ctx.logger
|
|
5270
5495
|
});
|
|
5271
5496
|
const proxyFetch = proxyManager.createFetch();
|
|
5497
|
+
const antigravityAdapter = new AntigravityAdapter(antigravityStore, antigravityModelSettings, antigravityPreferences, { fetchFn: proxyFetch });
|
|
5498
|
+
const disposeAntigravityAdapter = ctx.llm.registerAdapter([PROVIDER_ID], antigravityAdapter);
|
|
5499
|
+
const disposeAntigravityRoutes = registerAntigravityRoutes(ctx, antigravityStore, antigravityModelSettings, antigravityPreferences, proxyFetch);
|
|
5272
5500
|
const oauth = new OAuthService(store, {
|
|
5273
5501
|
fetchFn: proxyFetch,
|
|
5274
5502
|
logger: ctx.logger
|
|
@@ -5312,6 +5540,8 @@ function apply(ctx) {
|
|
|
5312
5540
|
disposeImageTool();
|
|
5313
5541
|
disposeAdapter();
|
|
5314
5542
|
disposeRoutes();
|
|
5543
|
+
disposeAntigravityRoutes();
|
|
5544
|
+
disposeAntigravityAdapter();
|
|
5315
5545
|
oauth.dispose();
|
|
5316
5546
|
proxyManager.dispose();
|
|
5317
5547
|
};
|