dsh-packycode-api 1.4.7 → 1.4.9

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
@@ -1078,8 +1078,8 @@ const DEFAULT_REASONING_LEVEL = "high";
1078
1078
  * ends early. So every model pins the protocol its vendor serves here.
1079
1079
  *
1080
1080
  * Effort levels come from each vendor's documentation, because an effort the
1081
- * model does not support is refused before the request is sent: Claude Opus
1082
- * 5.5 (platform.claude.com effort docs), GPT-6.1 Sol, GPT-6.1 Terra, GPT-6
1081
+ * model does not support is refused before the request is sent: Claude Fable
1082
+ * 5.2 and Opus 5.5 (platform.claude.com effort docs), GPT-6.1 Sol, GPT-6.1 Terra, GPT-6
1083
1083
  * Astra and GPT-5.6 Sol `reasoning.effort` (developers.openai.com model
1084
1084
  * pages), DeepSeek thinking mode (`low|high|max` effort, or off, for Flash
1085
1085
  * and V4 Pro alike), Grok 4.7
@@ -1125,7 +1125,7 @@ const DEEPSEEK_LEVELS = Object.freeze({
1125
1125
  max: "max"
1126
1126
  });
1127
1127
  /**
1128
- * Claude 5.5 models, GPT-6.1 Sol, and GPT-6 Astra cannot disable reasoning, so `off` is
1128
+ * Claude Fable 5.2, the Claude 5.5 models, GPT-6.1 Sol, and GPT-6 Astra cannot disable reasoning, so `off` is
1129
1129
  * pinned unsupported; GPT-5.6 Sol is kept on the same levels. Claude effort is
1130
1130
  * sent through the native Messages API.
1131
1131
  */
@@ -1315,6 +1315,23 @@ const PACKYCODE_MODELS = Object.freeze([
1315
1315
  maxTokens: 65536,
1316
1316
  thinkingLevelMap: GEMINI_LEVELS
1317
1317
  }),
1318
+ Object.freeze({
1319
+ id: "claude-fable-5-2",
1320
+ name: "Claude Fable 5.2",
1321
+ api: "anthropic-messages",
1322
+ provider: PROVIDER,
1323
+ baseUrl: MESSAGES_BASE,
1324
+ reasoning: true,
1325
+ input: Object.freeze(["text", "image"]),
1326
+ cost: NO_COST,
1327
+ contextWindow: 1e6,
1328
+ maxTokens: 128e3,
1329
+ thinkingLevelMap: ALWAYS_THINKING_LEVELS,
1330
+ compat: Object.freeze({
1331
+ forceAdaptiveThinking: true,
1332
+ supportsTemperature: false
1333
+ })
1334
+ }),
1318
1335
  Object.freeze({
1319
1336
  id: "claude-opus-5-5",
1320
1337
  name: "Claude Opus 5.5",
@@ -1375,11 +1392,13 @@ const PACKYCODE_MODEL_IDS = Object.freeze(PACKYCODE_MODELS.map((model) => model.
1375
1392
  * JSON state over the System One protocol, which PackyCode lists as the
1376
1393
  * `systemone` endpoint (`POST /v1/systemone`) in the `typesafe-officially`
1377
1394
  * group. Auto review asks it whether a tool call may run. The descriptor
1378
- * follows pi-ai's own TypeSafe catalog entry (64K context, text only).
1395
+ * follows pi-ai's own TypeSafe catalog entry (64K context, text only), and
1396
+ * the id is the version PackyCode publishes, since it no longer lists a
1397
+ * `jev-latest` alias.
1379
1398
  */
1380
1399
  const PACKYCODE_CLASSIFIER_MODELS = Object.freeze([Object.freeze({
1381
1400
  type: "classifier",
1382
- id: "jev-latest",
1401
+ id: "jev-1.14.0",
1383
1402
  name: "Jev",
1384
1403
  api: "typesafe-system-one",
1385
1404
  provider: PROVIDER,
@@ -1510,13 +1529,15 @@ function parseSelectedGroups(message) {
1510
1529
  * Normalize `/api/pricing`: the groups each covered model is enabled in, and
1511
1530
  * each group's public description.
1512
1531
  * @param payload - the parsed pricing body.
1532
+ * @param modelIds - the models to keep: the conversation catalog's, or the
1533
+ * image engines' for the image key ring.
1513
1534
  * @returns `{ modelGroups, descriptions }`, or undefined when unrecognized.
1514
1535
  */
1515
- function parsePricing(payload) {
1536
+ function parsePricing(payload, modelIds = PACKYCODE_COVERED_MODEL_IDS) {
1516
1537
  if (!record$5(payload) || !Array.isArray(payload.data)) return void 0;
1517
1538
  const modelGroups = {};
1518
1539
  for (const item of payload.data) {
1519
- if (!record$5(item) || !PACKYCODE_COVERED_MODEL_IDS.includes(item.model_name) || !Array.isArray(item.enable_groups)) continue;
1540
+ if (!record$5(item) || !modelIds.includes(item.model_name) || !Array.isArray(item.enable_groups)) continue;
1520
1541
  modelGroups[item.model_name] = [...new Set(item.enable_groups.filter((name) => typeof name === "string" && GROUP_NAME.test(name)))].sort();
1521
1542
  }
1522
1543
  const descriptions = {};
@@ -1534,12 +1555,13 @@ function parsePricing(payload) {
1534
1555
  * Which covered models a key can reach. Either source decides: a model the
1535
1556
  * key's groups are enabled for, or one its model list shows. A model is only
1536
1557
  * marked unreachable on evidence; with neither source it is left unknown.
1537
- * @param options - the key's groups, its model ids, and pricing's model groups.
1558
+ * @param options - the key's groups, its model ids, pricing's model groups,
1559
+ * and the models to decide (the conversation catalog's by default).
1538
1560
  * @returns covered model id → reachable, for the models with evidence.
1539
1561
  */
1540
- function catalogCoverage({ groups, models, modelGroups }) {
1562
+ function catalogCoverage({ groups, models, modelGroups, modelIds = PACKYCODE_COVERED_MODEL_IDS }) {
1541
1563
  const coverage = {};
1542
- for (const id of PACKYCODE_COVERED_MODEL_IDS) {
1564
+ for (const id of modelIds) {
1543
1565
  const enabled = modelGroups?.[id];
1544
1566
  const byGroups = groups === void 0 || enabled === void 0 ? void 0 : enabled.some((name) => groups.includes(name));
1545
1567
  const byModels = models === void 0 ? void 0 : models.has(id);
@@ -1565,10 +1587,12 @@ function errorMessage(text) {
1565
1587
  }
1566
1588
  /**
1567
1589
  * Cached, single-flight group reader.
1568
- * @param options - key listing, host resolution, and transport knobs.
1590
+ * @param options - key listing, host resolution, the models whose coverage
1591
+ * is read (`modelIds`: the conversation catalog's by default, the image
1592
+ * engines' for the image key ring), and transport knobs.
1569
1593
  * @returns `{ read, snapshot, covers, requiredGroups, invalidate }`.
1570
1594
  */
1571
- function createGroupReader({ listKeys, getHosts, fetchImpl = ambientFetch, now = Date.now, ttlMs = DEFAULT_GROUP_TTL_MS, retryMs = FAILED_GROUP_RETRY_MS, timeoutMs = DEFAULT_REQUEST_TIMEOUT_MS } = {}) {
1595
+ function createGroupReader({ listKeys, getHosts, modelIds = PACKYCODE_COVERED_MODEL_IDS, fetchImpl = ambientFetch, now = Date.now, ttlMs = DEFAULT_GROUP_TTL_MS, retryMs = FAILED_GROUP_RETRY_MS, timeoutMs = DEFAULT_REQUEST_TIMEOUT_MS } = {}) {
1572
1596
  if (typeof listKeys !== "function") throw new Error("PackyCode group reader requires listKeys");
1573
1597
  if (typeof getHosts !== "function") throw new Error("PackyCode group reader requires getHosts");
1574
1598
  /** Last good reading per key id, kept when a later read of that key fails. */
@@ -1584,7 +1608,7 @@ function createGroupReader({ listKeys, getHosts, fetchImpl = ambientFetch, now =
1584
1608
  for (const host of statusHostOrder(hosts)) try {
1585
1609
  const response = await request(`${host}/api/pricing`, { headers: { Accept: "application/json" } }, signal);
1586
1610
  if (!response.ok) throw new Error(`HTTP ${response.status}`);
1587
- const pricing = parsePricing(await response.json());
1611
+ const pricing = parsePricing(await response.json(), modelIds);
1588
1612
  if (pricing === void 0) throw new Error("unrecognized pricing payload");
1589
1613
  return pricing;
1590
1614
  } catch (error) {
@@ -1669,7 +1693,7 @@ function createGroupReader({ listKeys, getHosts, fetchImpl = ambientFetch, now =
1669
1693
  return {
1670
1694
  id: entry.id,
1671
1695
  rejected: true,
1672
- coverage: Object.fromEntries(PACKYCODE_COVERED_MODEL_IDS.map((id) => [id, false])),
1696
+ coverage: Object.fromEntries(modelIds.map((id) => [id, false])),
1673
1697
  error: new RejectedKeyError().message
1674
1698
  };
1675
1699
  }
@@ -1680,7 +1704,8 @@ function createGroupReader({ listKeys, getHosts, fetchImpl = ambientFetch, now =
1680
1704
  coverage: catalogCoverage({
1681
1705
  groups: value.groups,
1682
1706
  models: value.models,
1683
- modelGroups: pricing?.modelGroups
1707
+ modelGroups: pricing?.modelGroups,
1708
+ modelIds
1684
1709
  })
1685
1710
  };
1686
1711
  lastGood.set(entry.id, {
@@ -1961,15 +1986,19 @@ function createKeyChooser({ ring, groups, onRotated, logger, waitMs = DEFAULT_GR
1961
1986
  * with an optional `reason` on a denial.
1962
1987
  *
1963
1988
  * - `deepseek-flash` gets the same request, addressed to the PackyCode route.
1964
- * - `jev-latest`, TypeSafe's classifier, cannot write that object. The
1989
+ * - `jev-1.14.0`, TypeSafe's classifier, cannot write that object. The
1965
1990
  * sections become a System One state with the official policy in it, Jev
1966
1991
  * answers two typed questions, and the decision is written back as the
1967
1992
  * official JSON object.
1968
1993
  *
1969
1994
  * @module dsh-packycode-api/auto-review
1970
1995
  */
1971
- /** TypeSafe's decision model, reached through the `typesafe-officially` group. */
1972
- const AUTO_REVIEW_JEV = "jev-latest";
1996
+ /**
1997
+ * TypeSafe's decision model, reached through the `typesafe-officially` group.
1998
+ * PackyCode publishes it under its exact version (the `jev-latest` alias was
1999
+ * retired), so this id moves with TypeSafe's releases.
2000
+ */
2001
+ const AUTO_REVIEW_JEV = "jev-1.14.0";
1973
2002
  /** DeepSeek Flash on the PackyCode route. */
1974
2003
  const AUTO_REVIEW_FLASH = "deepseek-flash";
1975
2004
  /** Leave the official reviewer on the session's own model. */
@@ -1981,9 +2010,16 @@ const AUTO_REVIEW_MODELS = Object.freeze([
1981
2010
  AUTO_REVIEW_SESSION
1982
2011
  ]);
1983
2012
  const DEFAULT_AUTO_REVIEW_MODEL = AUTO_REVIEW_JEV;
2013
+ /**
2014
+ * Reviewer values the settings once offered, mapped to what replaced them. A
2015
+ * saved preference still names them, so the Config keeps them as hidden
2016
+ * values (dropping them would fail the plugin's Config at load) and reading
2017
+ * the preference moves them to the successor.
2018
+ */
2019
+ const RETIRED_AUTO_REVIEW_MODELS = Object.freeze({ "jev-latest": AUTO_REVIEW_JEV });
1984
2020
  /** Names for logs and the settings card; the card localizes the session choice. */
1985
2021
  const AUTO_REVIEW_MODEL_NAMES = Object.freeze({
1986
- [AUTO_REVIEW_JEV]: "jev-latest",
2022
+ [AUTO_REVIEW_JEV]: AUTO_REVIEW_JEV,
1987
2023
  [AUTO_REVIEW_FLASH]: "DeepSeek Flash",
1988
2024
  [AUTO_REVIEW_SESSION]: "session model"
1989
2025
  });
@@ -2217,8 +2253,8 @@ function decideFromJev(body) {
2217
2253
  const low = probability(shares?.low);
2218
2254
  const medium = probability(shares?.medium);
2219
2255
  const high = probability(shares?.high);
2220
- if (risk?.type !== "choice" || low === void 0 || medium === void 0 || high === void 0) throw new Error("PackyCode auto review: jev-latest returned no risk answer");
2221
- if (authorized === void 0) throw new Error("PackyCode auto review: jev-latest returned no authorization answer");
2256
+ if (risk?.type !== "choice" || low === void 0 || medium === void 0 || high === void 0) throw new Error(`PackyCode auto review: ${AUTO_REVIEW_JEV} returned no risk answer`);
2257
+ if (authorized === void 0) throw new Error(`PackyCode auto review: ${AUTO_REVIEW_JEV} returned no authorization answer`);
2222
2258
  const allowProbability = low + medium * authorized;
2223
2259
  const allowed = allowProbability >= .6 && high < .2;
2224
2260
  const usage = record$4(body?.usage) && Number.isFinite(body.usage.input_tokens) ? {
@@ -2239,7 +2275,7 @@ function decideFromJev(body) {
2239
2275
  return {
2240
2276
  decision: "deny",
2241
2277
  risk: blocking,
2242
- reason: blocking === "high" ? "jev-latest 判定为高风险操作(如外发凭据或敏感数据),需要你亲自决定" : "jev-latest 判定为中风险操作,且没有找到你对此操作的明确指示",
2278
+ reason: blocking === "high" ? `${AUTO_REVIEW_JEV} 判定为高风险操作(如外发凭据或敏感数据),需要你亲自决定` : `${AUTO_REVIEW_JEV} 判定为中风险操作,且没有找到你对此操作的明确指示`,
2243
2279
  ...common
2244
2280
  };
2245
2281
  }
@@ -2369,7 +2405,7 @@ function createJevReviewer({ ring, hosts, covers, onKeyFailure, fetchImpl = ambi
2369
2405
  parsed = await response.json();
2370
2406
  } catch (error) {
2371
2407
  if (signal?.aborted === true) throw error;
2372
- throw new Error("PackyCode auto review got an unreadable answer from jev-latest", { cause: error });
2408
+ throw new Error(`PackyCode auto review got an unreadable answer from ${AUTO_REVIEW_JEV}`, { cause: error });
2373
2409
  }
2374
2410
  if (record$4(parsed?.error)) return {
2375
2411
  failure: mask$1(text$2(parsed.error.message) || JSON.stringify(parsed.error).slice(0, MAX_ERROR_DETAIL_CHARS$1), key),
@@ -2412,7 +2448,7 @@ function createJevReviewer({ ring, hosts, covers, onKeyFailure, fetchImpl = ambi
2412
2448
  }
2413
2449
  break;
2414
2450
  }
2415
- if (uncovered) throw new Error(`PackyCode auto review: no stored API key's group covers jev-latest. Gateway said: ${lastFailure}`);
2451
+ if (uncovered) throw new Error(`PackyCode auto review: no stored API key's group covers ${AUTO_REVIEW_JEV}. Gateway said: ${lastFailure}`);
2416
2452
  throw new Error(`PackyCode auto review failed: ${lastFailure ?? "no detail"}`);
2417
2453
  } });
2418
2454
  }
@@ -2522,7 +2558,7 @@ function connectSettings(settingsCtx, { ctx, Config, namespace, source, setSourc
2522
2558
  * request per tool call passes through DSH's `llm/stream` waterfall like
2523
2559
  * every model call; the listener recognizes it and answers it with the
2524
2560
  * reviewer chosen under Settings → PackyCode:
2525
- * - `jev-latest`: over System One, written back as the official answer;
2561
+ * - `jev-1.14.0`: over System One, written back as the official answer;
2526
2562
  * - `deepseek-flash`: the same request, addressed to the PackyCode route;
2527
2563
  * - `session`: untouched, as without this plugin.
2528
2564
  * Every other request, conversation turns included, goes on unchanged. A
@@ -2535,9 +2571,10 @@ function connectSettings(settingsCtx, { ctx, Config, namespace, source, setSourc
2535
2571
  const DSH_AUTO_REVIEW_ROW = "auto-review";
2536
2572
  /** DSH's id of the 自动审查 permission preset. */
2537
2573
  const AUTO_PRESET = "auto";
2538
- /** Normalize a stored preference; anything unknown reads as the default. */
2574
+ /** Normalize a stored preference; a retired reviewer reads as its successor, anything else unknown as the default. */
2539
2575
  function readAutoReviewChoice(value) {
2540
- const model = fieldValue(value?.autoReviewModel);
2576
+ const saved = fieldValue(value?.autoReviewModel);
2577
+ const model = Object.hasOwn(RETIRED_AUTO_REVIEW_MODELS, saved) ? RETIRED_AUTO_REVIEW_MODELS[saved] : saved;
2541
2578
  return { model: AUTO_REVIEW_MODELS.includes(model) ? model : DEFAULT_AUTO_REVIEW_MODEL };
2542
2579
  }
2543
2580
  const errorText$1 = (error) => (error instanceof Error ? error.message : String(error)).slice(0, 300);
@@ -2595,7 +2632,7 @@ function createAutoReviewController({ jev, stream, readPreference, writePreferen
2595
2632
  tool = sections.pendingAction.name;
2596
2633
  const decision = await jev.review(options.system, sections, signal);
2597
2634
  settle(decision.decision === "allow" ? "allowed" : "denied", {
2598
- model: "jev-latest",
2635
+ model: AUTO_REVIEW_JEV,
2599
2636
  tool,
2600
2637
  ms: now() - started,
2601
2638
  risk: decision.risk,
@@ -2609,7 +2646,7 @@ function createAutoReviewController({ jev, stream, readPreference, writePreferen
2609
2646
  }
2610
2647
  const message = errorText$1(error);
2611
2648
  settle("failed", {
2612
- model: "jev-latest",
2649
+ model: AUTO_REVIEW_JEV,
2613
2650
  tool,
2614
2651
  ms: now() - started,
2615
2652
  error: message
@@ -3320,7 +3357,8 @@ const UPDATES_BETA = "thinking-display-updates-2026-08-18";
3320
3357
  const THINKING_DISPLAY = Object.freeze({
3321
3358
  "claude-haiku-5-5": "omitted",
3322
3359
  "claude-sonnet-5-5": "updates",
3323
- "claude-opus-5-5": "updates"
3360
+ "claude-opus-5-5": "updates",
3361
+ "claude-fable-5-2": "updates"
3324
3362
  });
3325
3363
  const IDENTITY = "You are Claude Code, Anthropic's official CLI for Claude, running within the Claude Agent SDK.";
3326
3364
  const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/iu;
@@ -6550,10 +6588,19 @@ const IMAGE_MODELS = Object.freeze({
6550
6588
  "high",
6551
6589
  "xhigh",
6552
6590
  "max"
6553
- ])
6591
+ ]),
6592
+ "MiniMax-H3-Image": Object.freeze(["auto"]),
6593
+ "MiniMax-H3-Image-Highspeed": Object.freeze(["auto"])
6554
6594
  });
6555
6595
  const IMAGE_MODEL_IDS = Object.freeze(Object.keys(IMAGE_MODELS));
6556
6596
  /**
6597
+ * MiniMax's H3 Image models take the Images API paths with their own fields
6598
+ * (platform.minimax.io/docs/api-reference/image-generation-h3-generations and
6599
+ * -h3-edits): no `quality`, `background` or `mask`, a JPEG link unless PNG and
6600
+ * `b64_json` are asked for, and 4K output unless a size is given.
6601
+ */
6602
+ const isMiniMaxImageModel = (model) => typeof model === "string" && model.startsWith("MiniMax-H3-Image");
6603
+ /**
6557
6604
  * Engines the settings once offered, mapped to the ones that replaced them.
6558
6605
  * A saved setting still names them, so the Config schema keeps them as hidden
6559
6606
  * values (dropping them would fail the plugin's Config at load) and reading
@@ -6579,6 +6626,7 @@ function validateImageOptions({ model = DEFAULT_IMAGE_MODEL, quality = "auto", s
6579
6626
  "transparent",
6580
6627
  "opaque"
6581
6628
  ].includes(background)) throw new Error("PackyCode image background is not supported");
6629
+ if (background === "transparent" && isMiniMaxImageModel(model)) throw new Error("PackyCode image transparent background is not supported by the selected model");
6582
6630
  if (size !== "auto") {
6583
6631
  const match = typeof size === "string" ? /^(\d{1,4})x(\d{1,4})$/u.exec(size) : null;
6584
6632
  const width = Number(match?.[1]);
@@ -6607,7 +6655,7 @@ function readImagePreference(value = {}) {
6607
6655
  };
6608
6656
  }
6609
6657
  /** No request is sent by a settings or credential mutation. */
6610
- function createImageController({ ring, balance, readPreference, writePreference, isWritable }) {
6658
+ function createImageController({ ring, balance, groups, readPreference, writePreference, isWritable }) {
6611
6659
  let mounted;
6612
6660
  let disposeTool;
6613
6661
  let generation = 0;
@@ -6639,6 +6687,7 @@ function createImageController({ ring, balance, readPreference, writePreference,
6639
6687
  preference,
6640
6688
  status,
6641
6689
  invalidateBalance: () => balance?.invalidate(),
6690
+ invalidateGroups: () => groups?.invalidate(),
6642
6691
  async readBalance({ force = false, signal } = {}) {
6643
6692
  if (balance === void 0) throw new Error("PackyCode image balance is unavailable in this runtime");
6644
6693
  try {
@@ -6654,6 +6703,25 @@ function createImageController({ ring, balance, readPreference, writePreference,
6654
6703
  throw new Error("PackyCode image balance could not be read");
6655
6704
  }
6656
6705
  },
6706
+ /**
6707
+ * Each image key's groups and the image engines they serve. Group names,
6708
+ * their public descriptions and per-engine booleans only; no key material.
6709
+ */
6710
+ async readGroups({ force = false, signal } = {}) {
6711
+ if (groups === void 0) throw new Error("PackyCode image key groups are unavailable in this runtime");
6712
+ try {
6713
+ const [value, state] = await Promise.all([groups.read({
6714
+ force,
6715
+ signal
6716
+ }), ring.list()]);
6717
+ return {
6718
+ ...value,
6719
+ activeId: state.activeId
6720
+ };
6721
+ } catch {
6722
+ throw new Error("PackyCode image key groups could not be read");
6723
+ }
6724
+ },
6657
6725
  reconcile,
6658
6726
  mount(register) {
6659
6727
  mounted = register;
@@ -6704,6 +6772,7 @@ function createImageController({ ring, balance, readPreference, writePreference,
6704
6772
  if (!Object.hasOwn(operations, operation)) throw new Error("Unknown PackyCode image key operation");
6705
6773
  await operations[operation]();
6706
6774
  balance?.invalidate();
6775
+ if (operation === "add" || operation === "remove") groups?.invalidate();
6707
6776
  await reconcile();
6708
6777
  return status();
6709
6778
  }
@@ -6862,8 +6931,33 @@ function decodeImagePng(encoded, maximum = MAX_IMAGE_BYTES) {
6862
6931
  pngDimensions(data);
6863
6932
  return data;
6864
6933
  }
6934
+ /**
6935
+ * The fields each engine's Images API takes. GPT Image gets the validated
6936
+ * options as they are. MiniMax H3 Image has no `quality` or `background`,
6937
+ * answers with a JPEG link unless PNG and `b64_json` are asked for, and
6938
+ * renders 4K unless told otherwise, so `auto` carries the 2K `area` tier.
6939
+ */
6940
+ function requestFields(options, prompt) {
6941
+ if (!isMiniMaxImageModel(options.model)) return {
6942
+ ...options,
6943
+ prompt,
6944
+ n: 1,
6945
+ output_format: "png"
6946
+ };
6947
+ return {
6948
+ model: options.model,
6949
+ prompt,
6950
+ n: 1,
6951
+ size: options.size,
6952
+ ...options.size === "auto" ? { area: "2K" } : {},
6953
+ output_format: "png",
6954
+ response_format: "b64_json"
6955
+ };
6956
+ }
6865
6957
  function imageFailure(status, value) {
6866
- if (value?.error?.code === "moderation_blocked") return "PackyCode image request was blocked by the image safety check; revise the prompt or references";
6958
+ const code = value?.error?.code;
6959
+ if (code === "model_not_found") return "PackyCode image key is in a group that does not serve the selected image model. Do not retry with another model: tell the user to pick a model their key serves under Settings → PackyCode → Images, or to add an image key in a group that lists this one";
6960
+ if (code === "moderation_blocked") return "PackyCode image request was blocked by the image safety check; revise the prompt or references";
6867
6961
  if (status === 401 || status === 403) return "PackyCode image key was rejected; check the separate image key and its model permissions";
6868
6962
  if (status === 429) return "PackyCode image quota or rate limit is unavailable; check the separate image key";
6869
6963
  return `PackyCode image request failed (HTTP ${status}); check model, quality, and image key permissions`;
@@ -6876,6 +6970,7 @@ function createPackyCodeImageClient({ resolveKey, getHost, fetchImpl = ambientFe
6876
6970
  if (prompt === void 0 || prompt.length > 32e3) throw new ImageRequestError("PackyCode image prompt must contain 1-32000 characters");
6877
6971
  if (images !== void 0 && (!Array.isArray(images) || images.length === 0 || images.length > MAX_REFERENCE_IMAGES)) throw new ImageRequestError("PackyCode image edits require 1-16 reference images");
6878
6972
  if (mask !== void 0 && images === void 0) throw new ImageRequestError("PackyCode image mask requires a reference image");
6973
+ if (mask !== void 0 && isMiniMaxImageModel(options.model)) throw new ImageRequestError("PackyCode image masks are not supported by the selected model; omit maskImage or choose a GPT Image model");
6879
6974
  const timeout = AbortSignal.timeout(timeoutMs);
6880
6975
  const requestSignal = signal === void 0 ? timeout : AbortSignal.any([signal, timeout]);
6881
6976
  let selected;
@@ -6887,12 +6982,7 @@ function createPackyCodeImageClient({ resolveKey, getHost, fetchImpl = ambientFe
6887
6982
  throw new ImageRequestError("PackyCode image credentials could not be read");
6888
6983
  }
6889
6984
  if (typeof selected?.key !== "string" || selected.key === "") throw new ImageRequestError("PackyCode images need a separate image key; add one under Settings → PackyCode → Images");
6890
- const fields = {
6891
- ...options,
6892
- prompt,
6893
- n: 1,
6894
- output_format: "png"
6895
- };
6985
+ const fields = requestFields(options, prompt);
6896
6986
  let body;
6897
6987
  const headers = {
6898
6988
  authorization: `Bearer ${selected.key}`,
@@ -7106,11 +7196,11 @@ function createPackyCodeImageTool({ client, attachments, getPreference, getSessi
7106
7196
  quality: {
7107
7197
  type: "string",
7108
7198
  enum: ["settings", ...IMAGE_QUALITIES],
7109
- description: "Use settings (or omit) to follow the user image setting. auto is an explicit API quality, not a request to follow settings. xhigh and max require a 2.6 model; never downgrade automatically."
7199
+ description: "Use settings (or omit) to follow the user image setting. auto is an explicit API quality, not a request to follow settings. xhigh and max require a GPT Image 2.6 model. MiniMax H3 Image takes no quality: leave it to settings or auto. Never downgrade automatically."
7110
7200
  },
7111
7201
  size: {
7112
7202
  type: "string",
7113
- description: "auto or widthxheight, e.g. 1024x1024; both edges multiples of 16, each <=3840, ratio <=3:1, pixels 655360..8294400."
7203
+ description: "auto or widthxheight, e.g. 1024x1024; both edges multiples of 16, each <=3840, ratio <=3:1, pixels 655360..8294400. On MiniMax H3 Image auto lets the model pick the shape at about 4 million pixels (2K); give a size to fix it, e.g. 2048x2048, 2720x1536 or 1536x2720."
7114
7204
  },
7115
7205
  background: {
7116
7206
  type: "string",
@@ -7118,7 +7208,8 @@ function createPackyCodeImageTool({ client, attachments, getPreference, getSessi
7118
7208
  "auto",
7119
7209
  "opaque",
7120
7210
  "transparent"
7121
- ]
7211
+ ],
7212
+ description: "transparent needs a GPT Image model; MiniMax H3 Image does not support it."
7122
7213
  },
7123
7214
  referenceImages: {
7124
7215
  type: "array",
@@ -7127,7 +7218,7 @@ function createPackyCodeImageTool({ client, attachments, getPreference, getSessi
7127
7218
  },
7128
7219
  maskImage: {
7129
7220
  oneOf: [{ type: "null" }, referenceSchema],
7130
- description: "Omit or use null for new images and ordinary reference-image edits, including refining a sketch. Only supply an actual PNG mask provided for masking, matching the first reference and under 4 MB; alpha=0 marks the area to edit. Do not copy an ordinary reference into this field or invent a mask."
7221
+ description: "Omit or use null for new images and ordinary reference-image edits, including refining a sketch. Only supply an actual PNG mask provided for masking, matching the first reference and under 4 MB; alpha=0 marks the area to edit. Do not copy an ordinary reference into this field or invent a mask. MiniMax H3 Image does not support masks."
7131
7222
  }
7132
7223
  },
7133
7224
  output: {
@@ -7164,10 +7255,12 @@ function createPackyCodeImageTool({ client, attachments, getPreference, getSessi
7164
7255
  const repeatedNonPng = repeatedIndex >= 0 && images[repeatedIndex].mediaType !== "image/png";
7165
7256
  const masks = maskImage === void 0 || repeatedNonPng ? void 0 : await readReferences([maskImage], attachments, messages, signal);
7166
7257
  if (!getPreference().imageEnabled) throw new ImageRequestError("PackyCode images are disabled in settings");
7258
+ const model = args.model === "settings" ? preference.imageModel : args.model ?? preference.imageModel;
7259
+ const savedQuality = IMAGE_MODELS[model]?.length === 1 ? IMAGE_MODELS[model][0] : preference.imageQuality;
7167
7260
  const result = await client.request({
7168
7261
  ...args,
7169
- model: args.model === "settings" ? preference.imageModel : args.model ?? preference.imageModel,
7170
- quality: args.quality === "settings" ? preference.imageQuality : args.quality ?? preference.imageQuality
7262
+ model,
7263
+ quality: args.quality === "settings" ? savedQuality : args.quality ?? savedQuality
7171
7264
  }, {
7172
7265
  signal,
7173
7266
  images,
@@ -8418,6 +8511,7 @@ const TITLE_MODELS = Object.freeze({
8418
8511
  "deepseek-v4-pro": "deepseek-flash",
8419
8512
  "gpt-6-astra": "gpt-6.1-sol",
8420
8513
  "gpt-5.6-sol": "gpt-6.1-sol",
8514
+ "claude-fable-5-2": "claude-haiku-5-5",
8421
8515
  "claude-opus-5-5": "claude-haiku-5-5",
8422
8516
  "claude-sonnet-5-5": "claude-haiku-5-5"
8423
8517
  });
@@ -8745,6 +8839,7 @@ function createPackyCodeRpc({ ring, balance, groups, hosts, preference, claude,
8745
8839
  balance.invalidate();
8746
8840
  groups?.invalidate();
8747
8841
  images?.invalidateBalance?.();
8842
+ images?.invalidateGroups?.();
8748
8843
  return status();
8749
8844
  },
8750
8845
  "claude-cli/status": (payload) => cli().status({ force: payload?.force === true }),
@@ -8775,6 +8870,10 @@ function createPackyCodeRpc({ ring, balance, groups, hosts, preference, claude,
8775
8870
  force: payload?.force === true,
8776
8871
  signal
8777
8872
  }),
8873
+ "images/groups": (payload, signal) => imageSupport().readGroups({
8874
+ force: payload?.force === true,
8875
+ signal
8876
+ }),
8778
8877
  "images/original": (payload, signal) => originals().info(payload, signal),
8779
8878
  "images/original/chunk": (payload, signal) => originals().chunk(payload, signal),
8780
8879
  "images/update": (payload) => imageSupport().update(payload),
@@ -9851,10 +9950,12 @@ const Config = z.object({
9851
9950
  /** Path to the Claude Code CLI; empty means "the usual install places, then PATH". */
9852
9951
  claudeCliPath: live(z.string().default("")),
9853
9952
  /**
9854
- * Which model DSH's official 自动审查 reviews with: `jev-latest` or
9855
- * `deepseek-flash` through PackyCode, or `session` for its own default.
9953
+ * Which model DSH's official 自动审查 reviews with: `jev-1.14.0` or
9954
+ * `deepseek-flash` through PackyCode, or `session` for its own default. A
9955
+ * retired reviewer value still parses as a hidden choice and reads as its
9956
+ * successor, like a retired image engine.
9856
9957
  */
9857
- autoReviewModel: live(z.union([...AUTO_REVIEW_MODELS]).default(DEFAULT_AUTO_REVIEW_MODEL))
9958
+ autoReviewModel: live(z.union([...AUTO_REVIEW_MODELS, ...Object.keys(RETIRED_AUTO_REVIEW_MODELS).map((id) => z.const(id).hidden())]).default(DEFAULT_AUTO_REVIEW_MODEL))
9858
9959
  });
9859
9960
  function apply(ctx, config) {
9860
9961
  const ring = new PackyCodeKeyRing(ctx.credentials);
@@ -10121,9 +10222,16 @@ function apply(ctx, config) {
10121
10222
  set: void 0
10122
10223
  };
10123
10224
  const dshHome = typeof process.env.DSH_HOME === "string" && process.env.DSH_HOME !== "" ? process.env.DSH_HOME : join(homedir(), ".dsh");
10225
+ /** The image keys' groups, read when the settings card asks: an engine may be listed in `image` and not in `image-sale`. */
10226
+ const imageGroups = createGroupReader({
10227
+ listKeys: () => imageRing.secrets(),
10228
+ getHosts: hosts,
10229
+ modelIds: IMAGE_MODEL_IDS
10230
+ });
10124
10231
  const images = createImageController({
10125
10232
  ring: imageRing,
10126
10233
  balance: imageBalance,
10234
+ groups: imageGroups,
10127
10235
  readPreference: () => source() ?? {},
10128
10236
  writePreference: (patch) => imagePreference.set(patch),
10129
10237
  isWritable: () => imagePreference.writable === true && typeof imagePreference.set === "function"
@@ -10193,6 +10301,7 @@ function apply(ctx, config) {
10193
10301
  balance.invalidate();
10194
10302
  imageBalance.invalidate();
10195
10303
  groups.invalidate();
10304
+ imageGroups.invalidate();
10196
10305
  modelSelection.reconcile();
10197
10306
  images.reconcile().catch(() => void 0);
10198
10307
  search.reconcile();
@@ -10298,4 +10407,4 @@ function apply(ctx, config) {
10298
10407
  });
10299
10408
  }
10300
10409
  //#endregion
10301
- export { AUTO_PRESET, AUTO_REVIEW_FLASH, AUTO_REVIEW_JEV, AUTO_REVIEW_MODELS, AUTO_REVIEW_MODEL_NAMES, AUTO_REVIEW_SESSION, AUTO_REVIEW_TIMEOUT_MS, CHANNEL, CLAUDE_INSTALL_COMMAND, CLAUDE_MIN_VERSION, CLAUDE_MISSING_MESSAGE, CLAUDE_NPM_LATEST_URL, CLAUDE_NPM_PACKAGE, CLAUDE_NPM_UPDATE_COMMAND, CLAUDE_PERMISSION_MODES, CLAUDE_SELF_UPDATE_COMMAND, CLI_ONLY_GROUPS, CLI_PROJECT_DIR_NAME, CLI_SESSION_IDLE_MS, Config, DEFAULT_API_HOST, DEFAULT_AUTO_REVIEW_MODEL, DEFAULT_CLAUDE_CONFIG_DIR, DEFAULT_IMAGE_MODEL, DEFAULT_SEARCH_BACKEND, DEFAULT_STATUS_HOST, DIAGNOSTICS_FORMAT, DIAGNOSTICS_LIMITS, DISPLAY_NAME, DSH_AUTO_REVIEW_ROW, HTTP_CLAUDE_GROUPS, IMAGE_MODELS, IMAGE_MODEL_IDS, IMAGE_QUALITIES, IMAGE_TIMEOUT_MS, IMAGE_TOOL_NAME, JEV_ALLOW_THRESHOLD, JEV_HIGH_RISK_LIMIT, JEV_LIMITS, JEV_QUESTIONS, LEGACY_STATUS_HOSTS, MAX_IMAGE_BYTES, MAX_LIVE_CLI_SESSIONS, MIRROR_TOOL, PACKYCODE_AUTO_SEARCH_PROVIDER_ID, PACKYCODE_CLASSIFIER_MODELS, PACKYCODE_COVERED_MODEL_IDS, PACKYCODE_MODELS, PACKYCODE_MODEL_IDS, PACKYCODE_SEARCH_PROVIDER_ID, PLAN_TOOL, PLUGIN_ID, PROVIDER, PackyCodeKeyRing, RETIRED_IMAGE_MODELS, SEARCH_BACKENDS, SEARCH_BACKEND_MODELS, SEARCH_MODES, SETTINGS_NAMESPACE, TEST_MESSAGE_POLICY_CODE, answerChunks, answersFor, apply, approvalReason, assertApiKey, baseUrlOf, buildCliArgs, buildCliEnvironment, buildSearchRequest, buildTranscript, catalogCoverage, classifyCliOutdated, classifyCliUnavailable, classifyClientPolicy, classifyGatewayFailure, classifyTestMessagePolicy, classifyUpstreamServerError, claudeBinaryCandidates, claudePermissionMode, claudeTransportFor, cliErrorMessage, compactionSummary, createAutoReviewController, createBalanceReader, createClaudeAgentBridge, createClaudeCliManager, createClaudeCliTransport, createClaudeLatestReader, createClaudeToolNames, createDiagnostics, createGroupReader, createImageController, createJevReviewer, createKeyChooser, createModelSelectionController, createPackyCodeAutoSearchProvider, createPackyCodeImageClient, createPackyCodeImageTool, createPackyCodeProfile, createPackyCodeProvider, createPackyCodeRpc, createPackyCodeSearchProvider, createProfileSearchComposition, createRouteAdapter, createSearchComposition, createSearchController, decideFromJev, decodeImagePng, describeRequest, dshSystemText, explainCliClientPolicyFailure, explainCliOutdated, explainCliUnavailable, explainClientPolicyFailure, explainNpmFailure, explainTestMessagePolicyFailure, failureChunk, guardKeyRotation, inject, isClaudeCliOutdated, isNpmClaudeInstall, isOfficialReviewRequest, jevState, locateClaudeBinary, locateNpm, messageFingerprint, name, normalizeFailureMessage, normalizeHost, officialAnswer, orderKeysFor, parseClaudeVersion, parseMessagesSearchResponse, parsePricing, parseResponsesSearchResponse, parseSelectedGroups, parseTokenUsage, pickKeyForModel, planAnswer, planContinuation, pngDimensions, presentClaudeCall, presentClaudeResult, questionPrompt, questionsFor, readAutoReviewChoice, readHiddenModels, readImagePreference, readReviewSections, readSearchChoice, refusalMessage, resolveSpawnCommand, responseFingerprint, sessionUuid, splitAtLastAnswer, startClaudeCliSession, statusHostOrder, summarizeSessionEvents, textResponse, titlePrompt, toCliHistory, toolResultText, validateImageOptions };
10410
+ export { AUTO_PRESET, AUTO_REVIEW_FLASH, AUTO_REVIEW_JEV, AUTO_REVIEW_MODELS, AUTO_REVIEW_MODEL_NAMES, AUTO_REVIEW_SESSION, AUTO_REVIEW_TIMEOUT_MS, CHANNEL, CLAUDE_INSTALL_COMMAND, CLAUDE_MIN_VERSION, CLAUDE_MISSING_MESSAGE, CLAUDE_NPM_LATEST_URL, CLAUDE_NPM_PACKAGE, CLAUDE_NPM_UPDATE_COMMAND, CLAUDE_PERMISSION_MODES, CLAUDE_SELF_UPDATE_COMMAND, CLI_ONLY_GROUPS, CLI_PROJECT_DIR_NAME, CLI_SESSION_IDLE_MS, Config, DEFAULT_API_HOST, DEFAULT_AUTO_REVIEW_MODEL, DEFAULT_CLAUDE_CONFIG_DIR, DEFAULT_IMAGE_MODEL, DEFAULT_SEARCH_BACKEND, DEFAULT_STATUS_HOST, DIAGNOSTICS_FORMAT, DIAGNOSTICS_LIMITS, DISPLAY_NAME, DSH_AUTO_REVIEW_ROW, HTTP_CLAUDE_GROUPS, IMAGE_MODELS, IMAGE_MODEL_IDS, IMAGE_QUALITIES, IMAGE_TIMEOUT_MS, IMAGE_TOOL_NAME, JEV_ALLOW_THRESHOLD, JEV_HIGH_RISK_LIMIT, JEV_LIMITS, JEV_QUESTIONS, LEGACY_STATUS_HOSTS, MAX_IMAGE_BYTES, MAX_LIVE_CLI_SESSIONS, MIRROR_TOOL, PACKYCODE_AUTO_SEARCH_PROVIDER_ID, PACKYCODE_CLASSIFIER_MODELS, PACKYCODE_COVERED_MODEL_IDS, PACKYCODE_MODELS, PACKYCODE_MODEL_IDS, PACKYCODE_SEARCH_PROVIDER_ID, PLAN_TOOL, PLUGIN_ID, PROVIDER, PackyCodeKeyRing, RETIRED_AUTO_REVIEW_MODELS, RETIRED_IMAGE_MODELS, SEARCH_BACKENDS, SEARCH_BACKEND_MODELS, SEARCH_MODES, SETTINGS_NAMESPACE, TEST_MESSAGE_POLICY_CODE, answerChunks, answersFor, apply, approvalReason, assertApiKey, baseUrlOf, buildCliArgs, buildCliEnvironment, buildSearchRequest, buildTranscript, catalogCoverage, classifyCliOutdated, classifyCliUnavailable, classifyClientPolicy, classifyGatewayFailure, classifyTestMessagePolicy, classifyUpstreamServerError, claudeBinaryCandidates, claudePermissionMode, claudeTransportFor, cliErrorMessage, compactionSummary, createAutoReviewController, createBalanceReader, createClaudeAgentBridge, createClaudeCliManager, createClaudeCliTransport, createClaudeLatestReader, createClaudeToolNames, createDiagnostics, createGroupReader, createImageController, createJevReviewer, createKeyChooser, createModelSelectionController, createPackyCodeAutoSearchProvider, createPackyCodeImageClient, createPackyCodeImageTool, createPackyCodeProfile, createPackyCodeProvider, createPackyCodeRpc, createPackyCodeSearchProvider, createProfileSearchComposition, createRouteAdapter, createSearchComposition, createSearchController, decideFromJev, decodeImagePng, describeRequest, dshSystemText, explainCliClientPolicyFailure, explainCliOutdated, explainCliUnavailable, explainClientPolicyFailure, explainNpmFailure, explainTestMessagePolicyFailure, failureChunk, guardKeyRotation, inject, isClaudeCliOutdated, isNpmClaudeInstall, isOfficialReviewRequest, jevState, locateClaudeBinary, locateNpm, messageFingerprint, name, normalizeFailureMessage, normalizeHost, officialAnswer, orderKeysFor, parseClaudeVersion, parseMessagesSearchResponse, parsePricing, parseResponsesSearchResponse, parseSelectedGroups, parseTokenUsage, pickKeyForModel, planAnswer, planContinuation, pngDimensions, presentClaudeCall, presentClaudeResult, questionPrompt, questionsFor, readAutoReviewChoice, readHiddenModels, readImagePreference, readReviewSections, readSearchChoice, refusalMessage, resolveSpawnCommand, responseFingerprint, sessionUuid, splitAtLastAnswer, startClaudeCliSession, statusHostOrder, summarizeSessionEvents, textResponse, titlePrompt, toCliHistory, toolResultText, validateImageOptions };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-packycode-api",
3
- "version": "1.4.7",
3
+ "version": "1.4.9",
4
4
  "description": "Use PackyCode API keys in DeepSeek Harness with multi-protocol models, image generation and editing, separate key rings, and balance display",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",