@eddyskywalker/dsh-chatgpt-subscription 0.2.1 → 0.2.3

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/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
  /**
@@ -49,6 +50,7 @@ const OAUTH_TOKEN_URL = `${CHATGPT_OAUTH_ISSUER}/oauth/token`;
49
50
  //#endregion
50
51
  //#region src/shared/model-catalog.ts
51
52
  const GPT_56_MAX_CONTEXT_WINDOW = 1e6;
53
+ const GPT_6_ASTRA_MAX_CONTEXT_WINDOW = 872e3;
52
54
  const CODEX_MODEL_CATALOG = [
53
55
  {
54
56
  id: "gpt-5.6-sol",
@@ -60,6 +62,15 @@ const CODEX_MODEL_CATALOG = [
60
62
  supportsReasoningSummary: true,
61
63
  fallbackModelId: "gpt-5.6-terra"
62
64
  },
65
+ {
66
+ id: "gpt-6-astra",
67
+ name: "6 Astra",
68
+ contextWindow: 272e3,
69
+ inputModalities: ["text", "image"],
70
+ defaultReasoningEffort: "medium",
71
+ reasoningProfile: "gpt-6-astra",
72
+ supportsReasoningSummary: true
73
+ },
63
74
  {
64
75
  id: "gpt-5.6-terra",
65
76
  name: "5.6 Terra",
@@ -120,11 +131,13 @@ const CODEX_MODEL_CATALOG = [
120
131
  ];
121
132
  const DEFAULT_VISIBLE_CODEX_MODEL_IDS = [
122
133
  "gpt-5.6-sol",
134
+ "gpt-6-astra",
123
135
  "gpt-5.6-terra",
124
136
  "gpt-5.6-luna"
125
137
  ];
126
138
  const DEFAULT_CODEX_MODEL = CODEX_MODEL_CATALOG[0];
127
139
  const CONFIGURABLE_CONTEXT_MODEL_IDS = [
140
+ "gpt-6-astra",
128
141
  "gpt-5.6-sol",
129
142
  "gpt-5.6-terra",
130
143
  "gpt-5.6-luna"
@@ -137,8 +150,17 @@ const STANDARD_REASONING_EFFORTS = [
137
150
  "xhigh"
138
151
  ];
139
152
  const GPT_56_REASONING_EFFORTS = [...STANDARD_REASONING_EFFORTS, "max"];
153
+ const GPT_6_ASTRA_REASONING_EFFORTS = [
154
+ "low",
155
+ "medium",
156
+ "high",
157
+ "xhigh",
158
+ "max"
159
+ ];
140
160
  function reasoningEffortsForModel(model) {
141
- return resolveCodexCatalogEntry(model).reasoningProfile === "gpt-5.6" ? GPT_56_REASONING_EFFORTS : STANDARD_REASONING_EFFORTS;
161
+ const profile = resolveCodexCatalogEntry(model).reasoningProfile;
162
+ if (profile === "gpt-6-astra") return GPT_6_ASTRA_REASONING_EFFORTS;
163
+ return profile === "gpt-5.6" ? GPT_56_REASONING_EFFORTS : STANDARD_REASONING_EFFORTS;
142
164
  }
143
165
  function isCodexModelId(model) {
144
166
  return typeof model === "string" && CODEX_MODEL_CATALOG.some((entry) => entry.id === model);
@@ -146,6 +168,9 @@ function isCodexModelId(model) {
146
168
  function isConfigurableContextModelId(model) {
147
169
  return typeof model === "string" && CONFIGURABLE_CONTEXT_MODEL_IDS.some((id) => id === model);
148
170
  }
171
+ function contextWindowLimitForModel(model) {
172
+ return model === "gpt-6-astra" ? GPT_6_ASTRA_MAX_CONTEXT_WINDOW : GPT_56_MAX_CONTEXT_WINDOW;
173
+ }
149
174
  function resolveCodexCatalogEntry(model) {
150
175
  return CODEX_MODEL_CATALOG.find((entry) => entry.id === model) ?? DEFAULT_CODEX_MODEL;
151
176
  }
@@ -1321,6 +1346,7 @@ const DEFAULT_PREFERENCES = {
1321
1346
  visibleModelIds: [...DEFAULT_VISIBLE_CODEX_MODEL_IDS],
1322
1347
  searchProvider: "dsh",
1323
1348
  contextWindowOverrides: {
1349
+ "gpt-6-astra": 272e3,
1324
1350
  "gpt-5.6-sol": 272e3,
1325
1351
  "gpt-5.6-terra": 272e3,
1326
1352
  "gpt-5.6-luna": 272e3
@@ -1366,6 +1392,7 @@ function registerPreferenceStore(settings) {
1366
1392
  visibleModelIds: z.array(z.string()).default(DEFAULT_PREFERENCES.visibleModelIds),
1367
1393
  searchProvider: z.union([z.const("dsh"), z.const(SEARCH_PROVIDER_CODEX)]).default(DEFAULT_PREFERENCES.searchProvider),
1368
1394
  contextWindowOverrides: z.object({
1395
+ "gpt-6-astra": z.number().step(1).min(1).max(GPT_6_ASTRA_MAX_CONTEXT_WINDOW).default(DEFAULT_PREFERENCES.contextWindowOverrides["gpt-6-astra"]),
1369
1396
  "gpt-5.6-sol": z.number().step(1).min(1).max(GPT_56_MAX_CONTEXT_WINDOW).default(DEFAULT_PREFERENCES.contextWindowOverrides["gpt-5.6-sol"]),
1370
1397
  "gpt-5.6-terra": z.number().step(1).min(1).max(GPT_56_MAX_CONTEXT_WINDOW).default(DEFAULT_PREFERENCES.contextWindowOverrides["gpt-5.6-terra"]),
1371
1398
  "gpt-5.6-luna": z.number().step(1).min(1).max(GPT_56_MAX_CONTEXT_WINDOW).default(DEFAULT_PREFERENCES.contextWindowOverrides["gpt-5.6-luna"])
@@ -1691,10 +1718,13 @@ async function buildResponsesPayload(options, attachments, localRawImages = {},
1691
1718
  }
1692
1719
  if (outputVerbosity !== null) payload.text = { verbosity: outputVerbosity };
1693
1720
  if (fastMode) payload.service_tier = "priority";
1694
- if (options.reasoningEffort !== void 0) payload.reasoning = codexModelSupportsReasoningSummary(options.model) ? {
1695
- effort: options.reasoningEffort,
1696
- summary: reasoningSummary ?? "auto"
1697
- } : { effort: options.reasoningEffort };
1721
+ if (options.reasoningEffort !== void 0) {
1722
+ const effort = options.model === "gpt-6-astra" && ["none", "minimal"].includes(options.reasoningEffort) ? "low" : options.reasoningEffort;
1723
+ payload.reasoning = codexModelSupportsReasoningSummary(options.model) ? {
1724
+ effort,
1725
+ summary: reasoningSummary ?? "auto"
1726
+ } : { effort };
1727
+ }
1698
1728
  return payload;
1699
1729
  }
1700
1730
  function progressExplanationInstruction(tools) {
@@ -3083,8 +3113,8 @@ function readPreferencesUpdate(value, current) {
3083
3113
  if (!isRecord$1(value.contextWindowOverrides)) throw new PreferenceError("contextWindowOverrides must be an object.");
3084
3114
  const overrides = {};
3085
3115
  for (const [model, contextWindow] of Object.entries(value.contextWindowOverrides)) {
3086
- if (!isConfigurableContextModelId(model)) throw new PreferenceError("Only GPT-5.6 context windows can be changed.");
3087
- if (!Number.isSafeInteger(contextWindow) || contextWindow < 1 || contextWindow > 1e6) throw new PreferenceError(`contextWindowOverrides.${model} must be a positive integer no greater than the provider limit.`);
3116
+ if (!isConfigurableContextModelId(model)) throw new PreferenceError("This model does not support a configurable context window.");
3117
+ if (!Number.isSafeInteger(contextWindow) || contextWindow < 1 || contextWindow > contextWindowLimitForModel(model)) throw new PreferenceError(`contextWindowOverrides.${model} must be a positive integer no greater than the provider limit.`);
3088
3118
  overrides[model] = contextWindow;
3089
3119
  }
3090
3120
  patch.contextWindowOverrides = overrides;
@@ -3162,16 +3192,18 @@ const DEFAULT_ACCOUNT = "oauth";
3162
3192
  * `security` command-line tool. The payload is encrypted at rest by the
3163
3193
  * Keychain, so this store reports itself as encrypted like Windows DPAPI.
3164
3194
  */
3165
- var MacKeychainTokenStore = class {
3195
+ var MacKeychainCredentialStore = class {
3166
3196
  service;
3167
3197
  account;
3198
+ parse;
3168
3199
  storage = {
3169
3200
  kind: "macos-keychain",
3170
3201
  encrypted: true
3171
3202
  };
3172
- constructor(service = DEFAULT_SERVICE, account = DEFAULT_ACCOUNT) {
3203
+ constructor(service, account, parse) {
3173
3204
  this.service = service;
3174
3205
  this.account = account;
3206
+ this.parse = parse;
3175
3207
  if (process.platform !== "darwin") throw new Error("macOS Keychain storage requires macOS");
3176
3208
  }
3177
3209
  async load() {
@@ -3187,7 +3219,7 @@ var MacKeychainTokenStore = class {
3187
3219
  if (result.code !== 0) throw new Error("Keychain credential read failed");
3188
3220
  try {
3189
3221
  const payload = result.stdout.replace(/\r?\n$/, "");
3190
- return parseStoredCredentials(JSON.parse(payload));
3222
+ return this.parse(JSON.parse(payload));
3191
3223
  } catch {
3192
3224
  throw new Error("Keychain credential payload is invalid");
3193
3225
  }
@@ -3215,6 +3247,11 @@ var MacKeychainTokenStore = class {
3215
3247
  if (result.code !== 0 && result.code !== 44) throw new Error("Keychain credential deletion failed");
3216
3248
  }
3217
3249
  };
3250
+ var MacKeychainTokenStore = class extends MacKeychainCredentialStore {
3251
+ constructor(service = DEFAULT_SERVICE, account = DEFAULT_ACCOUNT) {
3252
+ super(service, account, parseStoredCredentials);
3253
+ }
3254
+ };
3218
3255
  function runSecurity(args) {
3219
3256
  return new Promise((resolve, reject) => {
3220
3257
  const child = spawn("security", args, {
@@ -3359,8 +3396,16 @@ $cipher = [Security.Cryptography.ProtectedData]::Protect($bytes, $null, [Securit
3359
3396
  $directory = [IO.Path]::GetDirectoryName($path)
3360
3397
  [IO.Directory]::CreateDirectory($directory) | Out-Null
3361
3398
  $temporary = $path + '.tmp-' + [Guid]::NewGuid().ToString('N')
3362
- [IO.File]::WriteAllBytes($temporary, $cipher)
3363
- if ([IO.File]::Exists($path)) { [IO.File]::Replace($temporary, $path, $null) } else { [IO.File]::Move($temporary, $path) }
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
+ }
3364
3409
  `;
3365
3410
  const UNPROTECT_SCRIPT = String.raw`
3366
3411
  $ErrorActionPreference = 'Stop'
@@ -3379,14 +3424,16 @@ if ([IO.File]::Exists($path)) { [IO.File]::Delete($path) }
3379
3424
  function defaultDpapiCredentialPath() {
3380
3425
  return join(process.env.DSH_HOME?.trim() || join(homedir(), ".dsh"), "storages", "dsh-chatgpt-subscription", "oauth.dpapi");
3381
3426
  }
3382
- var WindowsDpapiTokenStore = class {
3427
+ var WindowsDpapiCredentialStore = class {
3383
3428
  path;
3429
+ parse;
3384
3430
  storage = {
3385
3431
  kind: "windows-dpapi",
3386
3432
  encrypted: true
3387
3433
  };
3388
- constructor(path = defaultDpapiCredentialPath()) {
3434
+ constructor(path, parse) {
3389
3435
  this.path = path;
3436
+ this.parse = parse;
3390
3437
  if (process.platform !== "win32") throw new Error("Windows DPAPI storage requires Windows");
3391
3438
  if (dirname(path) === path) throw new Error("invalid DPAPI credential path");
3392
3439
  }
@@ -3395,7 +3442,7 @@ var WindowsDpapiTokenStore = class {
3395
3442
  if (result.code === 3) return null;
3396
3443
  if (result.code !== 0) throw new Error("DPAPI credential read failed");
3397
3444
  try {
3398
- return parseStoredCredentials(JSON.parse(result.stdout));
3445
+ return this.parse(JSON.parse(result.stdout));
3399
3446
  } catch {
3400
3447
  throw new Error("DPAPI credential payload is invalid");
3401
3448
  }
@@ -3407,6 +3454,11 @@ var WindowsDpapiTokenStore = class {
3407
3454
  if ((await runPowerShell(CLEAR_SCRIPT, this.path, "")).code !== 0) throw new Error("DPAPI credential deletion failed");
3408
3455
  }
3409
3456
  };
3457
+ var WindowsDpapiTokenStore = class extends WindowsDpapiCredentialStore {
3458
+ constructor(path = defaultDpapiCredentialPath()) {
3459
+ super(path, parseStoredCredentials);
3460
+ }
3461
+ };
3410
3462
  function runPowerShell(script, path, stdin) {
3411
3463
  return new Promise((resolve, reject) => {
3412
3464
  const child = spawn("powershell.exe", [
@@ -3827,6 +3879,96 @@ const MODELS = [
3827
3879
  }
3828
3880
  ];
3829
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
3830
3972
  //#region src/host/antigravity/token-store.ts
3831
3973
  const ANTIGRAVITY_PREFERENCES_NAMESPACE = "dsh-antigravity";
3832
3974
  function registerAntigravityPreferenceStore(settings, fallbackStore = new FileModelSettingsStore()) {
@@ -3888,36 +4030,110 @@ function credentialPath() {
3888
4030
  function modelSettingsPath() {
3889
4031
  return path.join(dshHomeDir(), "storages", "antigravity-models.json");
3890
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. */
3891
4068
  var FileCredentialStore = class {
3892
4069
  filePath;
3893
- constructor(filePath = credentialPath()) {
4070
+ backend;
4071
+ constructor(filePath = credentialPath(), backend = createCredentialBackend(filePath)) {
3894
4072
  this.filePath = filePath;
4073
+ this.backend = backend;
3895
4074
  }
3896
4075
  path() {
3897
- 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;
3898
4088
  }
3899
- async read() {
4089
+ async removeLegacy() {
3900
4090
  try {
3901
- const content = await fsPromises.readFile(this.filePath, "utf8");
3902
- const parsed = JSON.parse(content);
3903
- if (typeof parsed === "object" && parsed !== null && ("access_token" in parsed || "access" in parsed)) return parsed;
3904
- return null;
3905
- } catch {
3906
- return null;
4091
+ await fsPromises.unlink(this.filePath);
4092
+ } catch (error) {
4093
+ if (error.code !== "ENOENT") throw new Error("Antigravity legacy credential removal failed");
3907
4094
  }
3908
4095
  }
3909
- async write(credentials) {
3910
- await fsPromises.mkdir(path.dirname(this.filePath), { recursive: true });
3911
- const tmp = `${this.filePath}.tmp.${Date.now()}`;
3912
- await fsPromises.writeFile(tmp, JSON.stringify(credentials, null, 2), "utf8");
3913
- await fsPromises.rename(tmp, this.filePath);
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
+ });
3914
4128
  }
3915
- async delete() {
3916
- try {
3917
- await fsPromises.unlink(this.filePath);
3918
- } catch (err) {
3919
- if (err.code !== "ENOENT") throw err;
3920
- }
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
+ });
3921
4137
  }
3922
4138
  };
3923
4139
  var FileModelSettingsStore = class {
@@ -4549,15 +4765,27 @@ function toolResultText(blocks) {
4549
4765
  }
4550
4766
  function replayBlockFor(message, index) {
4551
4767
  const source = message.source;
4552
- if (!source || source.kind !== "model") return void 0;
4768
+ if (!source || source.kind !== "model" || source.provider !== "antigravity") return void 0;
4553
4769
  const state = source.replayState;
4554
4770
  if (!isRecord(state)) return void 0;
4771
+ if (Array.isArray(state.blocks)) return state.blocks[index];
4555
4772
  const resp = isRecord(state.response) ? state.response : void 0;
4556
4773
  if (resp) {
4557
4774
  if (Array.isArray(resp.outputItems)) return resp.outputItems[index];
4558
4775
  if (Array.isArray(resp.blocks)) return resp.blocks[index];
4559
4776
  }
4560
- if (Array.isArray(state.blocks)) return state.blocks[index];
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;
4561
4789
  }
4562
4790
  function assistantParts(message, model, runtimeModel, toolNames) {
4563
4791
  const parts = [];
@@ -4566,30 +4794,39 @@ function assistantParts(message, model, runtimeModel, toolNames) {
4566
4794
  const block = message.content[index];
4567
4795
  if (!isRecord(block)) continue;
4568
4796
  const replay = replayBlockFor(message, index);
4569
- if (block.type === "text" && String(block.text || "").trim()) parts.push({ text: sanitizeText(String(block.text)) });
4570
- else if (block.type === "reasoning" && String(block.text || "").trim()) {
4571
- const sig = asString(replay?.thinkingSignature) || asString(replay?.thought_signature) || asString(block.thought_signature);
4572
- if (sig) parts.push({
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({
4573
4811
  thought: true,
4574
4812
  text: sanitizeText(String(block.text)),
4575
- thought_signature: sig,
4576
- thoughtSignature: sig
4813
+ ...sig ? { thoughtSignature: sig } : {}
4577
4814
  });
4578
- else parts.push({ text: sanitizeText(String(block.text)) });
4579
4815
  } else if (block.type === "tool-call") {
4580
4816
  const toolId = String(block.id || "");
4581
4817
  const toolName = String(block.name || "");
4582
4818
  toolNames.set(toolId, toolName);
4583
- const sig = asString(replay?.thought_signature) || asString(replay?.thoughtSignature) || asString(block.thought_signature) || asString(block.thoughtSignature) || "skip_thought_signature_validator";
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");
4584
4821
  parts.push({
4585
4822
  functionCall: {
4586
4823
  name: toolName,
4587
4824
  args: parseArguments(block.arguments),
4588
4825
  ...toolCallIdNeeded(model.id, runtimeModel) ? { id: sanitizeToolCallId(toolId, toolName) } : {}
4589
4826
  },
4590
- thought_signature: sig,
4591
- thoughtSignature: sig
4827
+ ...effectiveSignature ? { thoughtSignature: effectiveSignature } : {}
4592
4828
  });
4829
+ parts.push(...originalParts.filter((part) => !part.functionCall).map(replayPart));
4593
4830
  }
4594
4831
  }
4595
4832
  return parts;
@@ -4693,6 +4930,8 @@ function buildRequest(options, model, projectId, runtimeModel, effort) {
4693
4930
  const isTiered = runtimeModel === "gemini-3.8-flash-tiered" || runtimeModel === "gemini-3.7-flash-tiered";
4694
4931
  const isSuffixed = /^gemini-.+(?:-(?:extra-)?low|-medium|-high|-xhigh)$/.test(runtimeModel);
4695
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";
4696
4935
  if (isTiered) {
4697
4936
  const selected = (effort || "medium").toLowerCase();
4698
4937
  const isOff = selected === "off" || selected === "none";
@@ -4700,7 +4939,7 @@ function buildRequest(options, model, projectId, runtimeModel, effort) {
4700
4939
  thinkingLevel: isOff ? "MINIMAL" : selected === "high" || selected === "xhigh" ? "HIGH" : selected === "medium" ? "MEDIUM" : "LOW",
4701
4940
  includeThoughts: !isOff
4702
4941
  };
4703
- } else if (isSuffixed) {
4942
+ } else if (isSuffixed || isGemini3 || isGeminiAgent) {
4704
4943
  const selected = (effort || "medium").toLowerCase();
4705
4944
  generationConfig.thinkingConfig = { includeThoughts: !(selected === "off" || selected === "none") };
4706
4945
  } else if (isGemini25) {
@@ -4736,58 +4975,90 @@ function createStreamState() {
4736
4975
  replayBlocks: [],
4737
4976
  currentBlock: null,
4738
4977
  hasContent: false,
4739
- 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 } : {}
4740
5026
  };
4741
5027
  }
4742
5028
  function processStreamLine(line, state) {
4743
- if (!line.startsWith("data:")) return [];
5029
+ if (state.finished || !line.startsWith("data:")) return [];
4744
5030
  const json = line.slice(5).trim();
4745
- if (!json || json === "[DONE]") return [];
5031
+ if (json === "[DONE]") {
5032
+ state.done = true;
5033
+ return closeStream(state);
5034
+ }
5035
+ if (!json) return [];
4746
5036
  const chunk = safeJsonParse(json);
4747
5037
  if (!isRecord(chunk)) return [];
4748
5038
  const responseData = isRecord(chunk.response) ? chunk.response : chunk;
4749
- const candidate = (Array.isArray(responseData.candidates) ? responseData.candidates : [])[0];
5039
+ const candidates = Array.isArray(responseData.candidates) ? responseData.candidates : [];
5040
+ const candidate = isRecord(candidates[0]) ? candidates[0] : void 0;
4750
5041
  const content = isRecord(candidate?.content) ? candidate.content : void 0;
4751
5042
  const parts = Array.isArray(content?.parts) ? content.parts : [];
4752
5043
  const out = [];
4753
- const closeCurrentBlock = () => {
4754
- if (!state.currentBlock) return;
4755
- const index = state.blocks.length - 1;
4756
- if (state.currentBlock.type === "text") out.push({
4757
- type: "block-end",
4758
- index,
4759
- block: {
4760
- type: "text",
4761
- text: state.currentBlock.text
4762
- }
4763
- });
4764
- else out.push({
4765
- type: "block-end",
4766
- index,
4767
- block: {
4768
- type: "reasoning",
4769
- text: state.currentBlock.text
4770
- }
4771
- });
4772
- state.currentBlock = null;
4773
- };
4774
5044
  for (const part of parts) {
4775
5045
  if (!isRecord(part)) continue;
4776
- if (part.text !== void 0 && typeof part.text === "string") {
5046
+ if (typeof part.text === "string" && part.text !== "") {
4777
5047
  const isThinking = Boolean(part.thought);
4778
5048
  const blockType = isThinking ? "reasoning" : "text";
4779
5049
  if (!state.currentBlock || state.currentBlock.type !== blockType) {
4780
- closeCurrentBlock();
5050
+ out.push(...closeCurrentBlock(state));
5051
+ const index = state.blocks.length;
4781
5052
  state.currentBlock = {
5053
+ index,
4782
5054
  type: blockType,
4783
5055
  text: ""
4784
5056
  };
4785
- const index = state.blocks.length;
4786
5057
  state.blocks.push({
4787
5058
  type: blockType,
4788
5059
  text: ""
4789
5060
  });
4790
- state.replayBlocks.push({ type: blockType });
5061
+ state.replayBlocks.push({ parts: [] });
4791
5062
  out.push({
4792
5063
  type: "block-start",
4793
5064
  index,
@@ -4797,45 +5068,55 @@ function processStreamLine(line, state) {
4797
5068
  const delta = sanitizeText(part.text);
4798
5069
  state.currentBlock.text += delta;
4799
5070
  state.hasContent = true;
4800
- if (isThinking && part.thoughtSignature) {
4801
- state.currentBlock.thinkingSignature = part.thoughtSignature;
4802
- state.replayBlocks[state.blocks.length - 1].thinkingSignature = part.thoughtSignature;
4803
- } else if (!isThinking && part.thoughtSignature) {
4804
- state.currentBlock.textSignature = part.thoughtSignature;
4805
- state.replayBlocks[state.blocks.length - 1].textSignature = part.thoughtSignature;
4806
- }
5071
+ state.replayBlocks[state.currentBlock.index].parts.push(replayPart(part));
4807
5072
  out.push({
4808
5073
  type: isThinking ? "reasoning-delta" : "text-delta",
4809
- index: state.blocks.length - 1,
5074
+ index: state.currentBlock.index,
4810
5075
  text: delta
4811
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));
4812
5100
  }
4813
5101
  if (isRecord(part.functionCall)) {
4814
- closeCurrentBlock();
5102
+ out.push(...closeCurrentBlock(state));
4815
5103
  const fc = part.functionCall;
4816
5104
  const toolName = asString(fc.name) || "";
4817
5105
  const toolId = sanitizeToolCallId(asString(fc.id) || "", toolName);
4818
5106
  const argsText = JSON.stringify(isRecord(fc.args) ? fc.args : {});
4819
5107
  const index = state.blocks.length;
4820
- const sig = asString(part.thought_signature) || asString(part.thoughtSignature) || asString(part.thinkingSignature) || asString(fc.thought_signature) || asString(fc.thoughtSignature) || state.currentBlock?.thinkingSignature;
4821
5108
  const block = {
4822
5109
  type: "tool-call",
4823
- id: toolId,
5110
+ id: CallId(toolId),
4824
5111
  name: toolName,
4825
- arguments: argsText,
4826
- ...sig ? {
4827
- thought_signature: sig,
4828
- thoughtSignature: sig
4829
- } : {}
5112
+ arguments: argsText
4830
5113
  };
4831
5114
  state.blocks.push(block);
4832
- state.replayBlocks.push({
4833
- type: "tool-call",
4834
- ...sig ? {
4835
- thought_signature: sig,
4836
- thoughtSignature: sig
4837
- } : {}
4838
- });
5115
+ const sig = thoughtSignature(part) || thoughtSignature(fc);
5116
+ state.replayBlocks.push({ parts: [{
5117
+ ...replayPart(part),
5118
+ ...sig ? { thoughtSignature: sig } : {}
5119
+ }] });
4839
5120
  state.hasContent = true;
4840
5121
  state.hasToolCall = true;
4841
5122
  out.push({
@@ -4846,7 +5127,7 @@ function processStreamLine(line, state) {
4846
5127
  out.push({
4847
5128
  type: "tool-call-delta",
4848
5129
  index,
4849
- id: toolId,
5130
+ id: CallId(toolId),
4850
5131
  name: toolName,
4851
5132
  argumentsDelta: argsText
4852
5133
  });
@@ -4857,55 +5138,33 @@ function processStreamLine(line, state) {
4857
5138
  });
4858
5139
  }
4859
5140
  }
4860
- if (responseData.usageMetadata && isRecord(responseData.usageMetadata)) {
4861
- const u = responseData.usageMetadata;
4862
- const usage = {
4863
- inputTokens: Math.max(0, (u.promptTokenCount || 0) - (u.cachedContentTokenCount || 0)),
4864
- outputTokens: (u.candidatesTokenCount || 0) + (u.thoughtsTokenCount || 0),
4865
- ...u.cachedContentTokenCount ? { cacheReadTokens: u.cachedContentTokenCount } : {}
4866
- };
4867
- out.push({
4868
- type: "usage",
4869
- usage
4870
- });
4871
- }
4872
- 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);
4873
5144
  if (finishReason) {
4874
- closeCurrentBlock();
4875
- const reason = state.hasToolCall ? { kind: "tool-calls" } : finishReason === "MAX_TOKENS" ? { kind: "max-tokens" } : { kind: "stop" };
4876
- out.push({
4877
- type: "finish",
4878
- reason,
4879
- replayState: { response: {
4880
- outputItems: state.replayBlocks,
4881
- blocks: state.replayBlocks
4882
- } }
4883
- });
5145
+ state.finishReason = finishReason;
5146
+ out.push(...closeCurrentBlock(state));
4884
5147
  }
4885
5148
  return out;
4886
5149
  }
4887
5150
  function closeStream(state) {
4888
- const out = [];
4889
- if (state.currentBlock) {
4890
- const index = state.blocks.length - 1;
4891
- if (state.currentBlock.type === "text") out.push({
4892
- type: "block-end",
4893
- index,
4894
- block: {
4895
- type: "text",
4896
- text: state.currentBlock.text
4897
- }
4898
- });
4899
- else out.push({
4900
- type: "block-end",
4901
- index,
4902
- block: {
4903
- type: "reasoning",
4904
- text: state.currentBlock.text
4905
- }
4906
- });
4907
- state.currentBlock = null;
4908
- }
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
+ });
4909
5168
  return out;
4910
5169
  }
4911
5170
  //#endregion
@@ -4989,7 +5248,7 @@ var AntigravityAdapter = class extends LlmAdapter {
4989
5248
  contextWindow: 128e3,
4990
5249
  maxTokens: 65536
4991
5250
  };
4992
- const settings = await this.modelSettings.read();
5251
+ const settings = this.preferences ? this.preferences.status() : await this.modelSettings.read();
4993
5252
  const effectiveEffort = options.reasoningEffort || settings.defaultReasoningEffort || void 0;
4994
5253
  const effectiveOptions = effectiveEffort ? {
4995
5254
  ...options,
@@ -5010,7 +5269,6 @@ var AntigravityAdapter = class extends LlmAdapter {
5010
5269
  for (const fc of routing.fallbackCandidates) if (!candidates.includes(fc)) candidates.push(fc);
5011
5270
  }
5012
5271
  let response;
5013
- candidates[0];
5014
5272
  for (const runtimeModel of candidates) {
5015
5273
  const body = JSON.stringify(buildRequest(options, model, projectId, runtimeModel, effort));
5016
5274
  const headers = {
@@ -5054,8 +5312,10 @@ var AntigravityAdapter = class extends LlmAdapter {
5054
5312
  if (!trimmed) continue;
5055
5313
  const chunks = processStreamLine(trimmed, state);
5056
5314
  for (const chunk of chunks) yield chunk;
5315
+ if (state.finished) return;
5057
5316
  }
5058
5317
  }
5318
+ buffer += decoder.decode();
5059
5319
  if (buffer.trim()) {
5060
5320
  const chunks = processStreamLine(buffer.trim(), state);
5061
5321
  for (const chunk of chunks) yield chunk;