dsh-agy 0.4.5 → 0.4.6

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.
Files changed (33) hide show
  1. package/README.md +8 -3
  2. package/lib/{accounts-BOuz6_Sh.mjs → accounts-DtIsWCeF.mjs} +15 -11
  3. package/lib/{accounts-BOuz6_Sh.mjs.map → accounts-DtIsWCeF.mjs.map} +1 -1
  4. package/lib/cli/index.mjs +6 -6
  5. package/lib/client.js +181 -23
  6. package/lib/{constants-CmA9wXNe.mjs → constants-AdsD4sKh.mjs} +3 -3
  7. package/lib/{constants-CmA9wXNe.mjs.map → constants-AdsD4sKh.mjs.map} +1 -1
  8. package/lib/{exchange-C-tndv7j.mjs → exchange-B_rHhU8K.mjs} +3 -3
  9. package/lib/{exchange-C-tndv7j.mjs.map → exchange-B_rHhU8K.mjs.map} +1 -1
  10. package/lib/{import-SxPDjH3u.mjs → import-BqyA9tzt.mjs} +4 -4
  11. package/lib/{import-SxPDjH3u.mjs.map → import-BqyA9tzt.mjs.map} +1 -1
  12. package/lib/index.mjs +2 -2
  13. package/lib/{models-CjB0rXtI.mjs → models-D_54Ro5z.mjs} +3 -3
  14. package/lib/{models-CjB0rXtI.mjs.map → models-D_54Ro5z.mjs.map} +1 -1
  15. package/lib/{parse-D3InNyUx.mjs → parse-CeJc7cHo.mjs} +108 -8
  16. package/lib/parse-CeJc7cHo.mjs.map +1 -0
  17. package/lib/{plugin-common-Dwa1K13O.mjs → plugin-common-BlGbNHxX.mjs} +267 -10
  18. package/lib/plugin-common-BlGbNHxX.mjs.map +1 -0
  19. package/lib/{proxy-BbuQX5U9.mjs → proxy-B7VnbAfO.mjs} +5 -2
  20. package/lib/proxy-B7VnbAfO.mjs.map +1 -0
  21. package/lib/{quota-summary-DEA3P3Zh.mjs → quota-summary-DarIZkmo.mjs} +3 -3
  22. package/lib/{quota-summary-DEA3P3Zh.mjs.map → quota-summary-DarIZkmo.mjs.map} +1 -1
  23. package/lib/{translate-Cq6PGO4b.mjs → translate-vh3M5H_9.mjs} +7 -6
  24. package/lib/translate-vh3M5H_9.mjs.map +1 -0
  25. package/lib/web/plugin.d.mts +23 -1
  26. package/lib/web/plugin.d.mts.map +1 -1
  27. package/lib/web/plugin.mjs +86 -19
  28. package/lib/web/plugin.mjs.map +1 -1
  29. package/package.json +1 -1
  30. package/lib/parse-D3InNyUx.mjs.map +0 -1
  31. package/lib/plugin-common-Dwa1K13O.mjs.map +0 -1
  32. package/lib/proxy-BbuQX5U9.mjs.map +0 -1
  33. package/lib/translate-Cq6PGO4b.mjs.map +0 -1
@@ -1,10 +1,10 @@
1
- import { m as fetchAgyFirstOk, v as AgyAuthError, y as AgyPoolBlockedError } from "./constants-CmA9wXNe.mjs";
2
- import { a as probeFetch, o as proxiedFetch, t as accountFetch } from "./proxy-BbuQX5U9.mjs";
3
- import { C as isSessionAccumulationOverflow, S as describeFetchError, _ as persistMasterKey, b as classifyFetchError, f as migrateToAgyDir, g as loadMasterKey, h as deriveKey, m as createAesGcmCodec, p as MASTER_KEY_REF, r as AgySessionManager, s as resolveAntigravityVersion, t as JsonAccountStore, u as pickProbeProxyUrl, v as resolveDshHome, x as classifyHttpError, y as resolveMasterKeyCodec } from "./accounts-BOuz6_Sh.mjs";
1
+ import { m as fetchAgyFirstOk, v as AgyAuthError, y as AgyPoolBlockedError } from "./constants-AdsD4sKh.mjs";
2
+ import { a as probeFetch, o as proxiedFetch, t as accountFetch } from "./proxy-B7VnbAfO.mjs";
3
+ import { C as isSessionAccumulationOverflow, S as describeFetchError, _ as persistMasterKey, b as classifyFetchError, f as migrateToAgyDir, g as loadMasterKey, h as deriveKey, m as createAesGcmCodec, p as MASTER_KEY_REF, r as AgySessionManager, s as resolveAntigravityVersion, t as JsonAccountStore, u as pickProbeProxyUrl, v as resolveDshHome, x as classifyHttpError, y as resolveMasterKeyCodec } from "./accounts-DtIsWCeF.mjs";
4
4
  import { i as generateAntigravityRequestId, n as currentSessionGeneration, r as deriveAntigravitySessionId, t as bumpSessionGeneration } from "./identity-Ci8EGc1F.mjs";
5
- import { i as setThoughtSignature, r as resolveMultimodalFiles, t as toAgyRequestBody } from "./translate-Cq6PGO4b.mjs";
6
- import { n as parseAgySse, t as UnmappedFinishReasonError } from "./parse-D3InNyUx.mjs";
7
- import { i as resolveAgyModel, n as listAgyModels, t as catalogModelList } from "./models-CjB0rXtI.mjs";
5
+ import { i as setThoughtSignature, r as resolveMultimodalFiles, t as toAgyRequestBody } from "./translate-vh3M5H_9.mjs";
6
+ import { n as UnmappedFinishReasonError, r as parseAgySse, t as AgyStreamIdleTimeoutError } from "./parse-CeJc7cHo.mjs";
7
+ import { i as resolveAgyModel, n as listAgyModels, t as catalogModelList } from "./models-D_54Ro5z.mjs";
8
8
  import { randomBytes } from "node:crypto";
9
9
  import { LlmAdapter, LlmError, ProviderRequestId, QUOTA_EXCEEDED_CODE } from "@deepseek-ai/dsh-llm";
10
10
  import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
@@ -231,7 +231,7 @@ var AgyAdapter = class extends LlmAdapter {
231
231
  throw error;
232
232
  }
233
233
  if (!session) throw new LlmError("No agy account configured — run `dsh-agy login` to authenticate.", "NO_CREDENTIAL");
234
- const multimodalFiles = await resolveMultimodalFiles(options);
234
+ const multimodalFiles = await resolveMultimodalFiles(options, { maxBytes: this.options.maxInlineBytes?.() });
235
235
  holder.account = session.account;
236
236
  this.options.noteRequestStarted?.(session.account);
237
237
  const conversationAccount = ledgerAccountKey(session);
@@ -417,7 +417,25 @@ var AgyAdapter = class extends LlmAdapter {
417
417
  }, attemptStartedAt);
418
418
  return;
419
419
  } catch (error) {
420
- if (error instanceof DOMException && error.name === "AbortError") throw new LlmError("agy stream aborted", "ABORTED", { cause: error });
420
+ if (error instanceof DOMException && error.name === "AbortError") {
421
+ this.recordUsage(session, options.model, {
422
+ ok: false,
423
+ reason: "aborted",
424
+ usage
425
+ }, attemptStartedAt);
426
+ await response.body?.cancel().catch(() => {});
427
+ throw new LlmError("agy stream aborted", "ABORTED", { cause: error });
428
+ }
429
+ if (error instanceof AgyStreamIdleTimeoutError && !yieldedDirect) {
430
+ await this.options.reportFailure("network-error", session);
431
+ this.recordUsage(session, options.model, {
432
+ ok: false,
433
+ reason: "network-error",
434
+ usage
435
+ }, attemptStartedAt);
436
+ await response.body?.cancel().catch(() => {});
437
+ throw new LlmError(`${error.message} — nothing was delivered, retrying`, "TIMEOUT", { cause: error });
438
+ }
421
439
  if (error instanceof UnmappedFinishReasonError && error.reason === "MALFORMED_FUNCTION_CALL" && !yieldedDirect && malformedAttempt < MAX_MALFORMED_RETRIES) {
422
440
  this.recordUsage(session, options.model, {
423
441
  ok: false,
@@ -653,6 +671,10 @@ var ModelVisibility = class ModelVisibility {
653
671
  this.reload();
654
672
  }
655
673
  };
674
+ /** Whether `value` may be stored as a per-file inline cap: an integer MB in range. */
675
+ function isValidMultimodalMb(value) {
676
+ return typeof value === "number" && Number.isInteger(value) && value >= 1 && value <= 100;
677
+ }
656
678
  //#endregion
657
679
  //#region src/thinking-types.ts
658
680
  /**
@@ -919,6 +941,238 @@ var ThinkingBudgetStore = class ThinkingBudgetStore {
919
941
  return this.snapshot();
920
942
  }
921
943
  };
944
+ /** Environment override for the cap, in MB. */
945
+ const MULTIMODAL_ENV_VAR = "DSH_AGY_MULTIMODAL_MAX_INLINE_MB";
946
+ /**
947
+ * Rebuild the document, keeping only fields that are set.
948
+ *
949
+ * ONE place builds a document, so `parse`/`snapshot`/`write` cannot disagree
950
+ * about which fields exist: `thinking-budget.ts` spells its document out in each
951
+ * of the three, and a field added to only two of them is silently dropped by the
952
+ * third (that is why its `write` and `snapshot` both enumerate `claudeBudget`
953
+ * and `tieredBudget` by hand). `undefined` is dropped rather than serialized as
954
+ * `null`, so "unset" has exactly one on-disk form.
955
+ */
956
+ function toDocument(doc) {
957
+ return {
958
+ version: 1,
959
+ ...doc.maxInlineMb === void 0 ? {} : { maxInlineMb: doc.maxInlineMb }
960
+ };
961
+ }
962
+ /**
963
+ * Keep the stored value only when it is usable (tolerates hand edits).
964
+ *
965
+ * Dropped rather than clamped: a clamped cap would silently inline files the
966
+ * user asked to keep out, and the fallback — no stored value — is a valid state
967
+ * that simply means the built-in default.
968
+ */
969
+ function sanitizeMultimodalDocument(raw) {
970
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return toDocument({ version: 1 });
971
+ const value = raw.maxInlineMb;
972
+ return toDocument({
973
+ version: 1,
974
+ ...isValidMultimodalMb(value) ? { maxInlineMb: value } : {}
975
+ });
976
+ }
977
+ function parseMultimodalDocument(text) {
978
+ let raw;
979
+ try {
980
+ raw = JSON.parse(text);
981
+ } catch {
982
+ return toDocument({ version: 1 });
983
+ }
984
+ return sanitizeMultimodalDocument(raw);
985
+ }
986
+ /**
987
+ * The env override, or undefined when unset or unusable.
988
+ *
989
+ * `DSH_AGY_*` rather than `AGY_*`: that prefix is this plugin's own runtime
990
+ * knobs (`risk.ts`'s kill switch and fingerprint mode, `plugin-common.ts`'s
991
+ * health-probe interval), while `AGY_CLIENT_ID`/`AGY_CLIENT_SECRET` are the
992
+ * OAuth client-credential namespace — a knob that changes request behavior
993
+ * belongs to the former.
994
+ *
995
+ * This is the codebase's first NUMERIC env knob, and it inherits `envFlag`'s
996
+ * posture: anything not recognized yields the fallback (here "no override")
997
+ * rather than throwing or warning, because the fallback chain (store, then
998
+ * default) is always a valid state. Recognized means a plain decimal integer —
999
+ * `30MB`, `1.5` and `1e2` are ignored rather than guessed at, so the value that
1000
+ * takes effect is the one written.
1001
+ *
1002
+ * Read per call, not cached at module load, so a test (or a process that sets it
1003
+ * late) sees the change without a restart.
1004
+ */
1005
+ function envMaxInlineMb() {
1006
+ const raw = process.env[MULTIMODAL_ENV_VAR];
1007
+ if (raw === void 0) return void 0;
1008
+ const trimmed = raw.trim();
1009
+ if (!/^\d+$/.test(trimmed)) return void 0;
1010
+ const value = Number(trimmed);
1011
+ return isValidMultimodalMb(value) ? value : void 0;
1012
+ }
1013
+ /**
1014
+ * In-memory authoritative copy of the cap, backed by a JSON file.
1015
+ *
1016
+ * `maxInlineBytes` sits on the GENERATION HOT PATH (the adapter resolves the cap
1017
+ * for every request), so the cross-instance revalidation it needs is
1018
+ * rate-limited — the same window, shared with `thinking-budget.ts` rather than
1019
+ * re-declared: two settings read on one hot path should not drift into two
1020
+ * different staleness bounds. Writes go to the file immediately and bypass the
1021
+ * limit so a concurrent edit is never merged into a stale copy.
1022
+ *
1023
+ * No file lock: a settings write is atomic (tmp+rename) and the accepted loss is
1024
+ * one concurrent toggle, which is the same trade `thinking-budget.ts` makes.
1025
+ */
1026
+ var MultimodalConfigStore = class MultimodalConfigStore {
1027
+ file;
1028
+ revalidateIntervalMs;
1029
+ doc;
1030
+ /** Raw text this instance last read or wrote; see `reloadNow`. */
1031
+ raw;
1032
+ /** When `reloadNow` last ran, for the hot-path throttle. */
1033
+ checkedAt;
1034
+ constructor(options = {}) {
1035
+ this.file = options.file ?? migrateToAgyDir("agy-multimodal.json").file;
1036
+ this.revalidateIntervalMs = options.revalidateIntervalMs ?? 1e3;
1037
+ const initial = this.readWithRaw();
1038
+ this.doc = initial.doc;
1039
+ this.raw = initial.raw;
1040
+ this.checkedAt = Date.now();
1041
+ }
1042
+ /** Path of the backing file. */
1043
+ get path() {
1044
+ return this.file;
1045
+ }
1046
+ static serialize(doc) {
1047
+ return JSON.stringify(doc, null, 2) + "\n";
1048
+ }
1049
+ /**
1050
+ * Read the document together with the raw text it came from.
1051
+ *
1052
+ * Content, not mtime: two writers can land in the same filesystem timestamp
1053
+ * tick (Windows resolves it coarsely enough to have failed CI in this repo),
1054
+ * and a missed change is exactly the bug this check exists to prevent. A
1055
+ * missing or unreadable file means "nothing configured" — and must NOT create
1056
+ * it, because a settings read runs in processes that never write.
1057
+ */
1058
+ readWithRaw() {
1059
+ try {
1060
+ const raw = readFileSync(this.file, "utf8");
1061
+ return {
1062
+ doc: parseMultimodalDocument(raw),
1063
+ raw
1064
+ };
1065
+ } catch {
1066
+ return {
1067
+ doc: toDocument({ version: 1 }),
1068
+ raw: ""
1069
+ };
1070
+ }
1071
+ }
1072
+ /**
1073
+ * Re-read the file if another writer changed it.
1074
+ *
1075
+ * Unconditional — callers on the hot path use `reloadIfChanged` instead.
1076
+ */
1077
+ reloadNow() {
1078
+ let current;
1079
+ try {
1080
+ current = readFileSync(this.file, "utf8");
1081
+ } catch {
1082
+ current = "";
1083
+ }
1084
+ this.checkedAt = Date.now();
1085
+ if (current === this.raw) return;
1086
+ const fresh = this.readWithRaw();
1087
+ this.doc = fresh.doc;
1088
+ this.raw = fresh.raw;
1089
+ }
1090
+ /**
1091
+ * Hot-path revalidation: `reloadNow`, rate-limited.
1092
+ *
1093
+ * Without the limit every generation pays a blocking `readFileSync` plus a
1094
+ * UTF-8 decode for a value that changes only when a user saves the settings
1095
+ * card, so the worst case here is that a change takes up to
1096
+ * `revalidateIntervalMs` to reach an already-running adapter — the right trade
1097
+ * for a setting, and not right for the write path, which is why `write` calls
1098
+ * `reloadNow` directly.
1099
+ */
1100
+ reloadIfChanged() {
1101
+ if (Date.now() - this.checkedAt < this.revalidateIntervalMs) return;
1102
+ this.reloadNow();
1103
+ }
1104
+ /**
1105
+ * Write the whole document atomically.
1106
+ *
1107
+ * Always re-reads first, so a concurrent edit by the OTHER instance in this
1108
+ * process (main plugin + web entry) is merged rather than overwritten: the
1109
+ * caller passes a mutation, not a full replacement built from a stale copy.
1110
+ */
1111
+ write(mutate) {
1112
+ this.reloadNow();
1113
+ const next = toDocument(this.doc);
1114
+ mutate(next);
1115
+ const text = MultimodalConfigStore.serialize(next);
1116
+ mkdirSync(dirname(this.file), { recursive: true });
1117
+ const tmp = `${this.file}.tmp`;
1118
+ writeFileSync(tmp, text, { mode: 384 });
1119
+ renameSync(tmp, this.file);
1120
+ this.doc = next;
1121
+ this.raw = text;
1122
+ }
1123
+ /** The STORED cap in MB, or undefined when nothing is stored. */
1124
+ maxInlineMb() {
1125
+ this.reloadIfChanged();
1126
+ return this.doc.maxInlineMb;
1127
+ }
1128
+ /**
1129
+ * The cap actually in force: env overrides the stored value, which overrides
1130
+ * the built-in default.
1131
+ *
1132
+ * The env var is read HERE rather than at construction so the override is
1133
+ * live for a long-running process and flippable in a test.
1134
+ */
1135
+ effectiveMaxInlineMb() {
1136
+ const fromEnv = envMaxInlineMb();
1137
+ if (fromEnv !== void 0) return {
1138
+ value: fromEnv,
1139
+ source: "env"
1140
+ };
1141
+ const stored = this.maxInlineMb();
1142
+ if (stored !== void 0) return {
1143
+ value: stored,
1144
+ source: "stored"
1145
+ };
1146
+ return {
1147
+ value: 20,
1148
+ source: "default"
1149
+ };
1150
+ }
1151
+ /** The effective cap in bytes, as the multimodal resolver wants it. */
1152
+ maxInlineBytes() {
1153
+ return this.effectiveMaxInlineMb().value * 1024 * 1024;
1154
+ }
1155
+ /**
1156
+ * The settings state: the stored value (null when unset) plus which source is
1157
+ * effective. See `MultimodalView` for why the two are separate fields.
1158
+ */
1159
+ snapshot() {
1160
+ return {
1161
+ value: this.maxInlineMb() ?? null,
1162
+ source: this.effectiveMaxInlineMb().source,
1163
+ max: 100
1164
+ };
1165
+ }
1166
+ /** Replace the cap, or clear it when `value` is undefined. */
1167
+ setMaxInlineMb(value) {
1168
+ if (value !== void 0 && !isValidMultimodalMb(value)) throw new Error(`multimodal inline cap must be an integer in [1, 100] MB`);
1169
+ this.write((doc) => {
1170
+ if (value === void 0) delete doc.maxInlineMb;
1171
+ else doc.maxInlineMb = value;
1172
+ });
1173
+ return this.snapshot();
1174
+ }
1175
+ };
922
1176
  //#endregion
923
1177
  //#region src/usage-types.ts
924
1178
  /** Zeroed counters, for accumulation. */
@@ -1686,6 +1940,7 @@ async function buildAgyRuntime(ctx) {
1686
1940
  });
1687
1941
  const modelVisibility = new ModelVisibility();
1688
1942
  const thinkingBudget = new ThinkingBudgetStore();
1943
+ const multimodalConfig = new MultimodalConfigStore();
1689
1944
  let invalidateModelCache = () => {};
1690
1945
  const sessions = new AgySessionManager({
1691
1946
  store,
@@ -1709,6 +1964,7 @@ async function buildAgyRuntime(ctx) {
1709
1964
  thinkingBudgetFor: (level) => thinkingBudget.budgetFor(level),
1710
1965
  claudeBudgetFor: () => thinkingBudget.claudeBudget(),
1711
1966
  tieredBudgetFor: () => thinkingBudget.tieredBudget(),
1967
+ maxInlineBytes: () => multimodalConfig.maxInlineBytes(),
1712
1968
  recordUsage: (record) => {
1713
1969
  stats.record({
1714
1970
  ...record,
@@ -1731,10 +1987,11 @@ async function buildAgyRuntime(ctx) {
1731
1987
  stats,
1732
1988
  recentStore,
1733
1989
  modelVisibility,
1734
- thinkingBudget
1990
+ thinkingBudget,
1991
+ multimodalConfig
1735
1992
  };
1736
1993
  }
1737
1994
  //#endregion
1738
1995
  export { CLAUDE_BUDGET_MIN as a, CLAUDE_BUDGET_MAX as i, dayKey as n, THINKING_BUDGET_MAX as o, foldWindowBreakdown as r, createAgyRuntime as t };
1739
1996
 
1740
- //# sourceMappingURL=plugin-common-Dwa1K13O.mjs.map
1997
+ //# sourceMappingURL=plugin-common-BlGbNHxX.mjs.map