@dan-ai-studio/dshopencodego 0.1.12 → 0.1.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.en.md CHANGED
@@ -124,8 +124,8 @@ The "OpenCode Go" section shows the live gateway catalog (42+ models) with, per
124
124
 
125
125
  - **context / max input / max output**, **release date**, **price per 1M tokens** (from models.dev), and **Go allowance**;
126
126
  - **Go allowance** comes from OpenCode's own documentation (the "usage limits / estimated requests" tables) — **no API exposes it**. It is a transcribed table (source URL and date in the `src/go-limits.ts` header); pricing or promotion changes require updating it and shipping a release;
127
- - **Capability marks**: the table's new "Capabilities" column labels **structured output / temperature control / open weights** exactly as models.dev declares them. Only a declared "yes" is marked — an unstated or declared "no" stays unmarked, the same rule the reasoning levels follow.
128
- - filtering (by name/id, enabled-only, show-deprecated — **deprecated hidden by default**), sorting (newest first, monthly requests, input price, context, name, enabled first), and a list/table view switch.
127
+ - **Input modalities**: a separate line lists the input modes models.dev declares (text / image / audio / video / PDF). These are the **model's own metadata**, not what the Harness can forward — the Harness sends text and images natively; audio/video/PDF travel as attachments and tool reads. The line is absent when the document declares none.
128
+ - filtering (by name/id, enabled-only, show-deprecated — **deprecated hidden by default**), sorting (newest first, monthly requests, input price, context, name, enabled first); the list is the only view, one model per row.
129
129
 
130
130
  **Default switches**: with no explicit `modelVisibility` entries at all, the **top five models by published monthly request estimate** are enabled (deprecated, unconfigurable, and training-"contributor" models never qualify). The first explicit entry switches the whole list to explicit values. The Host picker and the settings page compute this from one rule, so they never disagree.
131
131
 
package/README.md CHANGED
@@ -124,8 +124,8 @@ API Key 通过 Harness 凭证库提供(引用名 `OPENCODE_GO_API_KEY`),
124
124
 
125
125
  - **上下文 / 输入 / 输出**(最大输入只有部分模型有官方数据)、**发布时间**、**单价 /1M**(来自 models.dev)、**Go 额度**;
126
126
  - **Go 额度**来自 OpenCode 官方文档的「使用限制 / 预估请求数」表,**没有接口提供**,是本插件转写的数据表(见 `src/go-limits.ts` 的注释:来源 URL 与转写日期);文档调整价格或促销时需要更新该表并随发版发布。
127
- - **能力标记**:表格新增「能力」列,按 models.dev 的声明标注**结构化输出 / 温度控制 / 开源权重**。只标注声明为"支持"的项——**未声明或声明不支持都不加标记**,与推理档位同一口径:不替文档下结论。
128
- - 筛选(按名称/ID、仅已启用、显示已弃用——**默认隐藏已弃用**)、排序(新发布优先 / 每月预估次数 / 输入单价 / 上下文 / 名称 / 已启用优先)、列表与表格两种视图。
127
+ - **输入模态**:单独一行列出 models.dev 声明的输入方式(文本 / 图像 / 音频 / 视频 / PDF)。这是**模型自身的元数据**,不等于 DSH 能直传:DSH 的模型输入只原生支持文本与图像,音视频/PDF 走附件与工具读取路径;文档未声明时该行不显示。
128
+ - 筛选(按名称/ID、仅已启用、显示已弃用——**默认隐藏已弃用**)、排序(新发布优先 / 每月预估次数 / 输入单价 / 上下文 / 名称 / 已启用优先);列表是唯一视图,逐模型纵向排列。
129
129
 
130
130
  **默认开关规则**:没人显式配置过 `modelVisibility` 时,默认启用**每月预估次数最高的 5 个**(跳过已弃用、无法配置、以及"贡献者版"这类以数据换折扣的模型);**一旦有任一显式条目,全部按显式值走**。同一规则同时作用于模型选择器与设置页,两处不会出现不同答案。
131
131
 
package/lib/client.js CHANGED
@@ -26,6 +26,13 @@ __export(index_exports, {
26
26
  });
27
27
  module.exports = __toCommonJS(index_exports);
28
28
 
29
+ // src/catalog/constants.ts
30
+ var MODEL_LISTING_MAX_BYTES = 1024 * 1024;
31
+ var MODEL_METADATA_MAX_BYTES = 16 * 1024 * 1024;
32
+
33
+ // src/catalog/metadata.ts
34
+ var INPUT_MODALITIES = ["text", "image", "audio", "video", "pdf"];
35
+
29
36
  // src/catalog/contract.ts
30
37
  function parseGoQuota(value) {
31
38
  if (value === null || typeof value !== "object") throw new Error("invalid go quota");
@@ -58,6 +65,11 @@ function parseCost(value) {
58
65
  ...typeof row["cacheWrite"] === "number" ? { cacheWrite: rate("cacheWrite") } : {}
59
66
  };
60
67
  }
68
+ function parseModalities(value) {
69
+ if (!Array.isArray(value)) return {};
70
+ const kept = INPUT_MODALITIES.filter((modality) => value.includes(modality));
71
+ return kept.length === 0 ? {} : { inputModalities: kept };
72
+ }
61
73
  function parseModel(value) {
62
74
  if (value === null || typeof value !== "object") throw new Error("invalid catalog model");
63
75
  const row = value;
@@ -74,9 +86,7 @@ function parseModel(value) {
74
86
  ...row["cost"] === void 0 ? {} : { cost: parseCost(row["cost"]) },
75
87
  ...row["protocolSource"] === "builtin" || row["protocolSource"] === "online" || row["protocolSource"] === "inferred" || row["protocolSource"] === "override" ? { protocolSource: row["protocolSource"] } : {},
76
88
  ...typeof row["assumedLimits"] === "boolean" ? { assumedLimits: row["assumedLimits"] } : {},
77
- ...typeof row["structuredOutput"] === "boolean" ? { structuredOutput: row["structuredOutput"] } : {},
78
- ...typeof row["temperature"] === "boolean" ? { temperature: row["temperature"] } : {},
79
- ...typeof row["openWeights"] === "boolean" ? { openWeights: row["openWeights"] } : {},
89
+ ...parseModalities(row["inputModalities"]),
80
90
  ...typeof row["configurationMissing"] === "string" ? { configurationMissing: row["configurationMissing"] } : {}
81
91
  };
82
92
  }
@@ -259,20 +269,12 @@ var en = {
259
269
  sortEnabled: "Enabled first",
260
270
  sortQuota: "Monthly requests",
261
271
  sortPrice: "Input price",
262
- viewList: "List",
263
- viewTable: "Table",
264
- headerModel: "Model",
265
- headerId: "ID",
266
- headerReleased: "Released",
267
- headerCapacity: "Context",
268
- headerIo: "In / Out",
269
- headerPrice: "Price /1M",
270
- headerQuota: "Go quota",
271
- headerCapabilities: "Capabilities",
272
- headerNotes: "Notes",
273
- capStructured: "Structured",
274
- capTemperature: "Temp",
275
- capOpenWeights: "Open",
272
+ inputLabel: "Input",
273
+ modalityText: "Text",
274
+ modalityImage: "Image",
275
+ modalityAudio: "Audio",
276
+ modalityVideo: "Video",
277
+ modalityPdf: "PDF",
276
278
  iosInput: "in",
277
279
  iosOutput: "out",
278
280
  iosContext: "ctx",
@@ -333,20 +335,12 @@ var zh = {
333
335
  sortEnabled: "\u5DF2\u542F\u7528\u4F18\u5148",
334
336
  sortQuota: "\u6BCF\u6708\u9884\u4F30\u6B21\u6570",
335
337
  sortPrice: "\u8F93\u5165\u5355\u4EF7",
336
- viewList: "\u5217\u8868",
337
- viewTable: "\u8868\u683C",
338
- headerModel: "\u6A21\u578B",
339
- headerId: "ID",
340
- headerReleased: "\u53D1\u5E03",
341
- headerCapacity: "\u4E0A\u4E0B\u6587",
342
- headerIo: "\u8F93\u5165 / \u8F93\u51FA",
343
- headerPrice: "\u5355\u4EF7 /1M",
344
- headerQuota: "Go \u989D\u5EA6",
345
- headerCapabilities: "\u80FD\u529B",
346
- headerNotes: "\u5907\u6CE8",
347
- capStructured: "\u7ED3\u6784\u5316",
348
- capTemperature: "\u6E29\u5EA6",
349
- capOpenWeights: "\u5F00\u6E90",
338
+ inputLabel: "\u8F93\u5165",
339
+ modalityText: "\u6587\u672C",
340
+ modalityImage: "\u56FE\u50CF",
341
+ modalityAudio: "\u97F3\u9891",
342
+ modalityVideo: "\u89C6\u9891",
343
+ modalityPdf: "PDF",
350
344
  iosInput: "\u5165",
351
345
  iosOutput: "\u51FA",
352
346
  iosContext: "\u4E0A\u4E0B\u6587",
@@ -396,8 +390,7 @@ var INITIAL_FILTER = {
396
390
  query: "",
397
391
  showDeprecated: false,
398
392
  onlyEnabled: false,
399
- sort: "quota",
400
- view: "list"
393
+ sort: "quota"
401
394
  };
402
395
  function compactCount(value) {
403
396
  if (value >= 1e6) return `${(value / 1e6).toFixed(value >= 1e7 ? 0 : 1)}M`;
@@ -441,12 +434,15 @@ function visibleModels(models, filter, visibility, locale) {
441
434
  });
442
435
  return sortRows(rows, filter.sort, visibility, locale);
443
436
  }
444
- function capabilityLabels(model, t) {
445
- const labels = [];
446
- if (model.structuredOutput === true) labels.push(t("capStructured"));
447
- if (model.temperature === true) labels.push(t("capTemperature"));
448
- if (model.openWeights === true) labels.push(t("capOpenWeights"));
449
- return labels;
437
+ var MODALITY_KEYS = {
438
+ text: "modalityText",
439
+ image: "modalityImage",
440
+ audio: "modalityAudio",
441
+ video: "modalityVideo",
442
+ pdf: "modalityPdf"
443
+ };
444
+ function inputModalityLabels(model, t) {
445
+ return (model.inputModalities ?? []).map((modality) => t(MODALITY_KEYS[modality]));
450
446
  }
451
447
 
452
448
  // plugin-css:/home/runner/work/dshopencodego/dshopencodego/src/client/section.module.css
@@ -455,10 +451,10 @@ if (!document.querySelector("style[data-plugin-css=" + JSON.stringify(id) + "]")
455
451
  const style = document.createElement("style");
456
452
  style.dataset.plugin = "@dan-ai-studio/dshopencodego";
457
453
  style.dataset.pluginCss = id;
458
- style.textContent = ".d7lz0W_root{color:var(--dsw-alias-label-primary);flex-direction:column;gap:1.25rem;display:flex}.d7lz0W_title{margin:0;font-size:1.05rem}.d7lz0W_hint{color:var(--dsw-alias-label-secondary);margin:0;font-size:.8rem}.d7lz0W_warn{color:var(--dsw-alias-state-warn-primary);margin:0;font-size:.8rem}.d7lz0W_ok{color:var(--dsw-alias-state-success-primary);font-size:.8rem}.d7lz0W_block{border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-1);border-radius:.6rem;flex-direction:column;gap:.5rem;padding:.85rem;display:flex}.d7lz0W_row{flex-wrap:wrap;align-items:center;gap:.5rem;display:flex}.d7lz0W_row>strong{margin-right:auto}.d7lz0W_input{border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-base);min-width:10rem;color:var(--dsw-alias-label-primary);font:inherit;border-radius:.35rem;flex:14rem;padding:.35rem .5rem;font-size:.85rem}.d7lz0W_button{border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary);font:inherit;cursor:pointer;border-radius:.35rem;padding:.35rem .7rem;font-size:.8rem}.d7lz0W_button:disabled{color:var(--dsw-alias-state-idle-primary);cursor:default}.d7lz0W_number{border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-base);width:5rem;color:var(--dsw-alias-label-primary);font:inherit;border-radius:.35rem;padding:.25rem .4rem;font-size:.8rem}.d7lz0W_select{border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-base);width:auto;max-width:100%;color:var(--dsw-alias-label-primary);font:inherit;border-radius:.35rem;padding:.25rem .4rem;font-size:.8rem}.d7lz0W_segmented{border:1px solid var(--dsw-alias-border-l1);border-radius:.35rem;display:inline-flex;overflow:hidden}.d7lz0W_segment,.d7lz0W_segmentActive{background:var(--dsw-alias-bg-base);color:var(--dsw-alias-label-secondary);font:inherit;cursor:pointer;border:0;padding:.25rem .6rem;font-size:.8rem}.d7lz0W_segmentActive{background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary)}.d7lz0W_tableWrap{overflow-x:auto}.d7lz0W_table{border-collapse:collapse;width:100%;min-width:62rem;font-size:.78rem}.d7lz0W_table th{color:var(--dsw-alias-label-secondary);text-align:left;white-space:nowrap;padding:.25rem .45rem;font-weight:500}.d7lz0W_table td{border-top:1px solid var(--dsw-alias-border-l1);vertical-align:middle;white-space:nowrap;padding:.28rem .45rem}.d7lz0W_toggle{width:2rem}.d7lz0W_toggle input{accent-color:var(--dsw-alias-brand-primary)}.d7lz0W_list{flex-direction:column;display:flex}.d7lz0W_item{border-top:1px solid var(--dsw-alias-border-l1);align-items:flex-start;gap:.5rem;padding:.45rem .15rem;display:flex}.d7lz0W_item input[type=checkbox]{accent-color:var(--dsw-alias-brand-primary);margin-top:.15rem}.d7lz0W_itemBody{flex-direction:column;gap:.15rem;min-width:0;display:flex}.d7lz0W_itemMain{flex-wrap:wrap;align-items:baseline;gap:.6rem;display:flex}.d7lz0W_itemMeta{color:var(--dsw-alias-label-secondary);flex-wrap:wrap;gap:.75rem;font-size:.75rem;display:flex}.d7lz0W_name{color:var(--dsw-alias-label-primary)}.d7lz0W_mono{color:var(--dsw-alias-label-secondary);font-family:ui-monospace,SFMono-Regular,Menlo,monospace}.d7lz0W_badge{color:var(--dsw-alias-brand-primary)}";
454
+ style.textContent = ".d7lz0W_root{color:var(--dsw-alias-label-primary);flex-direction:column;gap:1.25rem;display:flex}.d7lz0W_title{margin:0;font-size:1.05rem}.d7lz0W_hint{color:var(--dsw-alias-label-secondary);margin:0;font-size:.8rem}.d7lz0W_warn{color:var(--dsw-alias-state-warn-primary);margin:0;font-size:.8rem}.d7lz0W_ok{color:var(--dsw-alias-state-success-primary);font-size:.8rem}.d7lz0W_block{border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-1);border-radius:.6rem;flex-direction:column;gap:.5rem;padding:.85rem;display:flex}.d7lz0W_row{flex-wrap:wrap;align-items:center;gap:.5rem;display:flex}.d7lz0W_row>strong{margin-right:auto}.d7lz0W_input{border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-base);min-width:10rem;color:var(--dsw-alias-label-primary);font:inherit;border-radius:.35rem;flex:14rem;padding:.35rem .5rem;font-size:.85rem}.d7lz0W_button{border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary);font:inherit;cursor:pointer;border-radius:.35rem;padding:.35rem .7rem;font-size:.8rem}.d7lz0W_button:disabled{color:var(--dsw-alias-state-idle-primary);cursor:default}.d7lz0W_number{border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-base);width:5rem;color:var(--dsw-alias-label-primary);font:inherit;border-radius:.35rem;padding:.25rem .4rem;font-size:.8rem}.d7lz0W_select{border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-base);width:auto;max-width:100%;color:var(--dsw-alias-label-primary);font:inherit;border-radius:.35rem;padding:.25rem .4rem;font-size:.8rem}.d7lz0W_list{flex-direction:column;display:flex}.d7lz0W_item{border-top:1px solid var(--dsw-alias-border-l1);align-items:flex-start;gap:.5rem;padding:.45rem .15rem;display:flex}.d7lz0W_item input[type=checkbox]{accent-color:var(--dsw-alias-brand-primary);margin-top:.15rem}.d7lz0W_itemBody{flex-direction:column;gap:.15rem;min-width:0;display:flex}.d7lz0W_itemMain{flex-wrap:wrap;align-items:baseline;gap:.6rem;display:flex}.d7lz0W_itemMeta{color:var(--dsw-alias-label-secondary);flex-wrap:wrap;gap:.75rem;font-size:.75rem;display:flex}.d7lz0W_itemModalities{color:var(--dsw-alias-label-secondary);font-size:.75rem}.d7lz0W_name{color:var(--dsw-alias-label-primary)}.d7lz0W_mono{color:var(--dsw-alias-label-secondary);font-family:ui-monospace,SFMono-Regular,Menlo,monospace}.d7lz0W_badge{color:var(--dsw-alias-brand-primary)}";
459
455
  document.head.appendChild(style);
460
456
  }
461
- var section_default = { "title": "d7lz0W_title", "itemBody": "d7lz0W_itemBody", "segment": "d7lz0W_segment", "ok": "d7lz0W_ok", "select": "d7lz0W_select", "warn": "d7lz0W_warn", "table": "d7lz0W_table", "list": "d7lz0W_list", "itemMeta": "d7lz0W_itemMeta", "mono": "d7lz0W_mono", "name": "d7lz0W_name", "number": "d7lz0W_number", "toggle": "d7lz0W_toggle", "root": "d7lz0W_root", "tableWrap": "d7lz0W_tableWrap", "row": "d7lz0W_row", "hint": "d7lz0W_hint", "button": "d7lz0W_button", "segmentActive": "d7lz0W_segmentActive", "item": "d7lz0W_item", "itemMain": "d7lz0W_itemMain", "badge": "d7lz0W_badge", "block": "d7lz0W_block", "input": "d7lz0W_input", "segmented": "d7lz0W_segmented" };
457
+ var section_default = { "itemBody": "d7lz0W_itemBody", "root": "d7lz0W_root", "input": "d7lz0W_input", "name": "d7lz0W_name", "title": "d7lz0W_title", "list": "d7lz0W_list", "badge": "d7lz0W_badge", "warn": "d7lz0W_warn", "itemModalities": "d7lz0W_itemModalities", "block": "d7lz0W_block", "mono": "d7lz0W_mono", "row": "d7lz0W_row", "itemMeta": "d7lz0W_itemMeta", "button": "d7lz0W_button", "select": "d7lz0W_select", "number": "d7lz0W_number", "item": "d7lz0W_item", "ok": "d7lz0W_ok", "hint": "d7lz0W_hint", "itemMain": "d7lz0W_itemMain" };
462
458
 
463
459
  // src/client/Section.tsx
464
460
  var import_jsx_runtime = require("react/jsx-runtime");
@@ -898,80 +894,15 @@ function Section({ controller, t, getLocale }) {
898
894
  }
899
895
  )
900
896
  ] }),
901
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: section_default.segmented, children: [
902
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
903
- "button",
904
- {
905
- type: "button",
906
- className: state.filter.view === "list" ? section_default.segmentActive : section_default.segment,
907
- onClick: () => {
908
- controller.setFilter({ view: "list" });
909
- },
910
- children: t("viewList")
911
- }
912
- ),
913
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
914
- "button",
915
- {
916
- type: "button",
917
- className: state.filter.view === "table" ? section_default.segmentActive : section_default.segment,
918
- onClick: () => {
919
- controller.setFilter({ view: "table" });
920
- },
921
- children: t("viewTable")
922
- }
923
- )
924
- ] }),
925
897
  hidden > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: section_default.hint, children: [
926
898
  hidden,
927
899
  " ",
928
900
  t("filterHidden")
929
901
  ] })
930
902
  ] }),
931
- reading !== void 0 && (state.filter.view === "table" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: section_default.tableWrap, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("table", { className: section_default.table, children: [
932
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("tr", { children: [
933
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", {}),
934
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { className: section_default.hint, children: t("headerModel") }),
935
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { className: section_default.hint, children: t("headerId") }),
936
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { className: section_default.hint, children: t("headerReleased") }),
937
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { className: section_default.hint, children: t("headerCapacity") }),
938
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { className: section_default.hint, children: t("headerIo") }),
939
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { className: section_default.hint, children: t("headerPrice") }),
940
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { className: section_default.hint, children: t("headerQuota") }),
941
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { className: section_default.hint, children: t("headerCapabilities") }),
942
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { className: section_default.hint, children: t("headerNotes") })
943
- ] }) }),
944
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("tbody", { children: rows.map((model) => {
945
- const badge = badgeFor(model, t);
946
- const capabilities = capabilityLabels(model, t).join(" \xB7 ");
947
- const offered = isOffered(model, visibility);
948
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("tr", { children: [
949
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { className: section_default.toggle, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
950
- "input",
951
- {
952
- type: "checkbox",
953
- "aria-label": `${t("catalogTitle")}: ${model.name}`,
954
- checked: offered,
955
- disabled: !editable || model.configurationMissing !== void 0,
956
- onChange: (event) => {
957
- controller.setVisibility(model.id, event.target.checked);
958
- }
959
- }
960
- ) }),
961
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { className: section_default.name, children: model.name }),
962
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { className: section_default.mono, children: model.id }),
963
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { className: section_default.hint, children: model.releaseDate ?? "\u2014" }),
964
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { className: section_default.hint, children: model.contextWindow === void 0 ? "\u2014" : model.contextWindow.toLocaleString(getLocale?.()) }),
965
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { className: section_default.hint, children: ioLabel(model, t) }),
966
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { className: section_default.hint, children: priceLabel(model) }),
967
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { className: section_default.hint, children: quotaLabel(model, t) }),
968
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { className: section_default.hint, children: capabilities.length === 0 ? "\u2014" : capabilities }),
969
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { className: badge.length === 0 ? section_default.hint : section_default.badge, children: badge })
970
- ] }, model.id);
971
- }) })
972
- ] }) }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: section_default.list, children: rows.map((model) => {
903
+ reading !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: section_default.list, children: rows.map((model) => {
973
904
  const badge = badgeFor(model, t);
974
- const capabilities = capabilityLabels(model, t).join(" \xB7 ");
905
+ const modalities = inputModalityLabels(model, t).join(" \xB7 ");
975
906
  const offered = isOffered(model, visibility);
976
907
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: section_default.item, children: [
977
908
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
@@ -991,7 +922,6 @@ function Section({ controller, t, getLocale }) {
991
922
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: section_default.name, children: model.name }),
992
923
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: section_default.mono, children: model.id }),
993
924
  model.releaseDate !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: section_default.hint, children: model.releaseDate }),
994
- capabilities.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: section_default.badge, children: capabilities }),
995
925
  badge.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: section_default.badge, children: badge })
996
926
  ] }),
997
927
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: section_default.itemMeta, children: [
@@ -999,10 +929,11 @@ function Section({ controller, t, getLocale }) {
999
929
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: ioLabel(model, t) }),
1000
930
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: priceLabel(model) }),
1001
931
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: quotaLabel(model, t) })
1002
- ] })
932
+ ] }),
933
+ modalities.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: section_default.itemModalities, children: `${t("inputLabel")} ${modalities}` })
1003
934
  ] })
1004
935
  ] }, model.id);
1005
- }) }))
936
+ }) })
1006
937
  ] })
1007
938
  ] });
1008
939
  }
@@ -1019,7 +950,7 @@ if (!document.querySelector("style[data-plugin-css=" + JSON.stringify(id2) + "]"
1019
950
  style.textContent = ".mMNkkW_root{align-items:center;display:inline-flex;position:relative}.mMNkkW_trigger{border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-secondary);font:inherit;cursor:pointer;white-space:nowrap;border-radius:999px;padding:.35rem .6rem;font-size:.8rem;line-height:1}.mMNkkW_trigger:hover{color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-border-l2)}.mMNkkW_panel{z-index:20;border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-overlay);width:18rem;color:var(--dsw-alias-label-primary);border-radius:.6rem;flex-direction:column;gap:.6rem;padding:.75rem .85rem;display:flex;position:absolute;bottom:calc(100% + .5rem);right:0;box-shadow:0 8px 24px #0000002e}.mMNkkW_window{flex-direction:column;gap:.25rem;display:flex}.mMNkkW_row{justify-content:space-between;gap:.5rem;font-size:.85rem;display:flex}.mMNkkW_hint{color:var(--dsw-alias-label-secondary);margin:0;font-size:.75rem}.mMNkkW_progress{width:100%;height:.35rem;accent-color:var(--dsw-alias-brand-primary)}.mMNkkW_limited{color:var(--dsw-alias-state-warn-primary);font-size:.75rem}.mMNkkW_warning{border:1px solid var(--dsw-alias-state-warn-primary);color:var(--dsw-alias-label-primary);border-radius:.4rem;padding:.4rem .5rem;font-size:.75rem}.mMNkkW_retry{border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);font:inherit;cursor:pointer;border-radius:.35rem;align-self:flex-start;padding:.25rem .5rem;font-size:.75rem}.mMNkkW_local{border-top:1px solid var(--dsw-alias-border-l1);flex-direction:column;gap:.2rem;padding-top:.5rem;display:flex}";
1020
951
  document.head.appendChild(style);
1021
952
  }
1022
- var pill_default = { "panel": "mMNkkW_panel", "warning": "mMNkkW_warning", "hint": "mMNkkW_hint", "retry": "mMNkkW_retry", "window": "mMNkkW_window", "row": "mMNkkW_row", "root": "mMNkkW_root", "trigger": "mMNkkW_trigger", "progress": "mMNkkW_progress", "limited": "mMNkkW_limited", "local": "mMNkkW_local" };
953
+ var pill_default = { "trigger": "mMNkkW_trigger", "limited": "mMNkkW_limited", "panel": "mMNkkW_panel", "retry": "mMNkkW_retry", "local": "mMNkkW_local", "hint": "mMNkkW_hint", "progress": "mMNkkW_progress", "warning": "mMNkkW_warning", "row": "mMNkkW_row", "root": "mMNkkW_root", "window": "mMNkkW_window" };
1023
954
 
1024
955
  // src/client/UsagePill.tsx
1025
956
  var import_jsx_runtime2 = require("react/jsx-runtime");
package/lib/index.js CHANGED
@@ -202,6 +202,7 @@ function decideProtocol(id, evidence) {
202
202
  }
203
203
 
204
204
  // src/catalog/metadata.ts
205
+ var INPUT_MODALITIES = ["text", "image", "audio", "video", "pdf"];
205
206
  var LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh", "max"];
206
207
  function record(value) {
207
208
  return value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
@@ -212,8 +213,11 @@ function positiveInteger(value) {
212
213
  function nonEmptyString(value) {
213
214
  return typeof value === "string" && value.length > 0 ? value : void 0;
214
215
  }
215
- function declaredBoolean(value) {
216
- return typeof value === "boolean" ? value : void 0;
216
+ function readInputModalities(metadata) {
217
+ const declared = record(metadata["modalities"])["input"];
218
+ if (!Array.isArray(declared)) return void 0;
219
+ const kept = INPUT_MODALITIES.filter((modality) => declared.includes(modality));
220
+ return kept.length === 0 ? void 0 : kept;
217
221
  }
218
222
  function validReleaseDate(value) {
219
223
  if (typeof value !== "string" || !/^\d{4}-\d{2}-\d{2}$/.test(value)) return void 0;
@@ -344,11 +348,9 @@ function readOnlineMetadata(body, sources) {
344
348
  maxTokens,
345
349
  assumedLimits: onlineContext === void 0 && builtinContext === void 0 || onlineOutput === void 0 && builtinOutput === void 0,
346
350
  input,
351
+ inputModalities: readInputModalities(metadata),
347
352
  reasoning,
348
353
  thinkingLevelMap: reasoning ? thinkingLevels(metadata, exact?.api === api ? exact : sibling) : void 0,
349
- structuredOutput: declaredBoolean(metadata["structured_output"]),
350
- temperature: declaredBoolean(metadata["temperature"]),
351
- openWeights: declaredBoolean(metadata["open_weights"]),
352
354
  compat: compatFor(api, metadata, exact, sibling),
353
355
  cost,
354
356
  deprecated: metadata["status"] === "deprecated",
@@ -540,13 +542,9 @@ var OpencodeGoCatalog = class {
540
542
  maxTokens,
541
543
  assumedLimits: exact === void 0,
542
544
  input: exact?.input ?? this.options.defaults.input,
545
+ inputModalities: void 0,
543
546
  reasoning: exact?.reasoning ?? false,
544
547
  thinkingLevelMap: exact?.thinkingLevelMap,
545
- // No source describes these, so the page says nothing instead of
546
- // guessing: the installed catalog knows wire shapes, not capabilities.
547
- structuredOutput: void 0,
548
- temperature: void 0,
549
- openWeights: void 0,
550
548
  compat: exact?.compat ?? {},
551
549
  cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
552
550
  deprecated: false,
@@ -1508,6 +1506,11 @@ function parseCost(value) {
1508
1506
  ...typeof row["cacheWrite"] === "number" ? { cacheWrite: rate("cacheWrite") } : {}
1509
1507
  };
1510
1508
  }
1509
+ function parseModalities(value) {
1510
+ if (!Array.isArray(value)) return {};
1511
+ const kept = INPUT_MODALITIES.filter((modality) => value.includes(modality));
1512
+ return kept.length === 0 ? {} : { inputModalities: kept };
1513
+ }
1511
1514
  function parseModel(value) {
1512
1515
  if (value === null || typeof value !== "object") throw new Error("invalid catalog model");
1513
1516
  const row = value;
@@ -1524,9 +1527,7 @@ function parseModel(value) {
1524
1527
  ...row["cost"] === void 0 ? {} : { cost: parseCost(row["cost"]) },
1525
1528
  ...row["protocolSource"] === "builtin" || row["protocolSource"] === "online" || row["protocolSource"] === "inferred" || row["protocolSource"] === "override" ? { protocolSource: row["protocolSource"] } : {},
1526
1529
  ...typeof row["assumedLimits"] === "boolean" ? { assumedLimits: row["assumedLimits"] } : {},
1527
- ...typeof row["structuredOutput"] === "boolean" ? { structuredOutput: row["structuredOutput"] } : {},
1528
- ...typeof row["temperature"] === "boolean" ? { temperature: row["temperature"] } : {},
1529
- ...typeof row["openWeights"] === "boolean" ? { openWeights: row["openWeights"] } : {},
1530
+ ...parseModalities(row["inputModalities"]),
1530
1531
  ...typeof row["configurationMissing"] === "string" ? { configurationMissing: row["configurationMissing"] } : {}
1531
1532
  };
1532
1533
  }
@@ -1717,9 +1718,7 @@ function catalogReading(snapshot, visibility, listingFailure) {
1717
1718
  },
1718
1719
  protocolSource: fact.protocolSource,
1719
1720
  assumedLimits: fact.assumedLimits,
1720
- ...fact.structuredOutput === void 0 ? {} : { structuredOutput: fact.structuredOutput },
1721
- ...fact.temperature === void 0 ? {} : { temperature: fact.temperature },
1722
- ...fact.openWeights === void 0 ? {} : { openWeights: fact.openWeights }
1721
+ ...fact.inputModalities === void 0 ? {} : { inputModalities: fact.inputModalities }
1723
1722
  };
1724
1723
  }),
1725
1724
  ...[...snapshot.unavailable].map(([id, reason]) => ({ id, name: id, configurationMissing: reason }))
@@ -9,6 +9,7 @@
9
9
  *
10
10
  * @module @dan-ai-studio/dshopencodego/catalog/contract
11
11
  */
12
+ import { INPUT_MODALITIES } from "./metadata.js";
12
13
  function parseGoQuota(value) {
13
14
  if (value === null || typeof value !== 'object')
14
15
  throw new Error('invalid go quota');
@@ -44,6 +45,13 @@ function parseCost(value) {
44
45
  ...typeof row['cacheWrite'] === 'number' ? { cacheWrite: rate('cacheWrite') } : {},
45
46
  };
46
47
  }
48
+ /** Recognised input modalities out of one wire value; absent when none survive. */
49
+ function parseModalities(value) {
50
+ if (!Array.isArray(value))
51
+ return {};
52
+ const kept = INPUT_MODALITIES.filter(modality => value.includes(modality));
53
+ return kept.length === 0 ? {} : { inputModalities: kept };
54
+ }
47
55
  function parseModel(value) {
48
56
  if (value === null || typeof value !== 'object')
49
57
  throw new Error('invalid catalog model');
@@ -64,9 +72,7 @@ function parseModel(value) {
64
72
  || row['protocolSource'] === 'inferred' || row['protocolSource'] === 'override'
65
73
  ? { protocolSource: row['protocolSource'] } : {},
66
74
  ...typeof row['assumedLimits'] === 'boolean' ? { assumedLimits: row['assumedLimits'] } : {},
67
- ...typeof row['structuredOutput'] === 'boolean' ? { structuredOutput: row['structuredOutput'] } : {},
68
- ...typeof row['temperature'] === 'boolean' ? { temperature: row['temperature'] } : {},
69
- ...typeof row['openWeights'] === 'boolean' ? { openWeights: row['openWeights'] } : {},
75
+ ...parseModalities(row['inputModalities']),
70
76
  ...typeof row['configurationMissing'] === 'string' ? { configurationMissing: row['configurationMissing'] } : {},
71
77
  };
72
78
  }
@@ -206,13 +206,9 @@ export class OpencodeGoCatalog {
206
206
  maxTokens,
207
207
  assumedLimits: exact === undefined,
208
208
  input: exact?.input ?? this.options.defaults.input,
209
+ inputModalities: undefined,
209
210
  reasoning: exact?.reasoning ?? false,
210
211
  thinkingLevelMap: exact?.thinkingLevelMap,
211
- // No source describes these, so the page says nothing instead of
212
- // guessing: the installed catalog knows wire shapes, not capabilities.
213
- structuredOutput: undefined,
214
- temperature: undefined,
215
- openWeights: undefined,
216
212
  compat: exact?.compat ?? {},
217
213
  cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
218
214
  deprecated: false,
@@ -16,6 +16,10 @@
16
16
  */
17
17
  import type { Api, Model, ModelCost, ThinkingLevelMap } from '@earendil-works/pi-ai';
18
18
  import type { ProtocolSource, WireProtocol } from './protocol.ts';
19
+ /** Input modalities this build can name, in display order. */
20
+ export declare const INPUT_MODALITIES: readonly ["text", "image", "audio", "video", "pdf"];
21
+ /** One input modality a model document may declare. */
22
+ export type InputModality = (typeof INPUT_MODALITIES)[number];
19
23
  /** Everything this plugin knows about one advertised model. */
20
24
  export interface ModelFacts {
21
25
  readonly id: string;
@@ -30,16 +34,16 @@ export interface ModelFacts {
30
34
  /** True when a capacity came from the route default rather than a source. */
31
35
  readonly assumedLimits: boolean;
32
36
  readonly input: readonly ('text' | 'image')[];
33
- readonly reasoning: boolean;
34
- readonly thinkingLevelMap: ThinkingLevelMap | undefined;
35
37
  /**
36
- * Declared capability facts, exactly as models.dev states them. `undefined`
37
- * means the document says nothing about the capability, which is not the
38
- * same answer as `false` and must never be rendered as "unsupported".
38
+ * Every input modality the document declares, in display order. `undefined`
39
+ * means it declares none this build can name — silence, not "text only".
40
+ * Kept beside {@link input} because the two answer different questions:
41
+ * `input` gates what this route may send, this one reports what the model
42
+ * itself accepts.
39
43
  */
40
- readonly structuredOutput: boolean | undefined;
41
- readonly temperature: boolean | undefined;
42
- readonly openWeights: boolean | undefined;
44
+ readonly inputModalities: readonly InputModality[] | undefined;
45
+ readonly reasoning: boolean;
46
+ readonly thinkingLevelMap: ThinkingLevelMap | undefined;
43
47
  readonly compat: Model<Api>['compat'];
44
48
  readonly cost: ModelCost;
45
49
  readonly deprecated: boolean;
@@ -16,6 +16,8 @@
16
16
  */
17
17
  import { asWireProtocol, decideProtocol, protocolOfNpm } from "./protocol.js";
18
18
  import { MODEL_METADATA_PROVIDER } from "./constants.js";
19
+ /** Input modalities this build can name, in display order. */
20
+ export const INPUT_MODALITIES = ['text', 'image', 'audio', 'video', 'pdf'];
19
21
  const LEVELS = ['off', 'minimal', 'low', 'medium', 'high', 'xhigh', 'max'];
20
22
  function record(value) {
21
23
  return value !== null && typeof value === 'object' && !Array.isArray(value)
@@ -27,9 +29,20 @@ function positiveInteger(value) {
27
29
  function nonEmptyString(value) {
28
30
  return typeof value === 'string' && value.length > 0 ? value : undefined;
29
31
  }
30
- /** A capability the document states as yes or no; silence stays silence. */
31
- function declaredBoolean(value) {
32
- return typeof value === 'boolean' ? value : undefined;
32
+ /**
33
+ * Input modalities exactly as the document declares them, in display order.
34
+ *
35
+ * A modality this build cannot name is dropped rather than passed through: the
36
+ * settings page labels every value it renders. An absent list, or one naming
37
+ * nothing recognisable, stays `undefined` — the document is silent, which is a
38
+ * different fact from "text only".
39
+ */
40
+ function readInputModalities(metadata) {
41
+ const declared = record(metadata['modalities'])['input'];
42
+ if (!Array.isArray(declared))
43
+ return undefined;
44
+ const kept = INPUT_MODALITIES.filter(modality => declared.includes(modality));
45
+ return kept.length === 0 ? undefined : kept;
33
46
  }
34
47
  /** A calendar date models.dev states without a timezone. */
35
48
  function validReleaseDate(value) {
@@ -225,11 +238,9 @@ export function readOnlineMetadata(body, sources) {
225
238
  assumedLimits: onlineContext === undefined && builtinContext === undefined
226
239
  || onlineOutput === undefined && builtinOutput === undefined,
227
240
  input,
241
+ inputModalities: readInputModalities(metadata),
228
242
  reasoning,
229
243
  thinkingLevelMap: reasoning ? thinkingLevels(metadata, exact?.api === api ? exact : sibling) : undefined,
230
- structuredOutput: declaredBoolean(metadata['structured_output']),
231
- temperature: declaredBoolean(metadata['temperature']),
232
- openWeights: declaredBoolean(metadata['open_weights']),
233
244
  compat: compatFor(api, metadata, exact, sibling),
234
245
  cost,
235
246
  deprecated: metadata['status'] === 'deprecated',
@@ -43,9 +43,7 @@ export function catalogReading(snapshot, visibility, listingFailure) {
43
43
  },
44
44
  protocolSource: fact.protocolSource,
45
45
  assumedLimits: fact.assumedLimits,
46
- ...fact.structuredOutput === undefined ? {} : { structuredOutput: fact.structuredOutput },
47
- ...fact.temperature === undefined ? {} : { temperature: fact.temperature },
48
- ...fact.openWeights === undefined ? {} : { openWeights: fact.openWeights },
46
+ ...fact.inputModalities === undefined ? {} : { inputModalities: fact.inputModalities },
49
47
  };
50
48
  }),
51
49
  ...[...snapshot.unavailable].map(([id, reason]) => ({ id, name: id, configurationMissing: reason })),
@@ -9,6 +9,7 @@
9
9
  * @module @dan-ai-studio/dshopencodego/models
10
10
  */
11
11
  import type { ProtocolSource } from './catalog/protocol.ts';
12
+ import type { InputModality } from './catalog/metadata.ts';
12
13
  import type { GoQuota } from './go-limits.ts';
13
14
  /** One model as the settings page and the picker describe it. */
14
15
  export interface ModelSummary {
@@ -37,13 +38,10 @@ export interface ModelSummary {
37
38
  /** Which ladder level decided this model's protocol. */
38
39
  readonly protocolSource?: ProtocolSource;
39
40
  /**
40
- * Capabilities models.dev declares, in its own words. Each is absent when the
41
- * document is silent about it — absence is "unstated", never "unsupported",
42
- * so the page leaves the cell empty instead of showing a negative.
41
+ * Every input modality models.dev declares, in display order. Absent when the
42
+ * document declares none this build can name.
43
43
  */
44
- readonly structuredOutput?: boolean;
45
- readonly temperature?: boolean;
46
- readonly openWeights?: boolean;
44
+ readonly inputModalities?: readonly InputModality[];
47
45
  /** True when a capacity came from the route default rather than a source. */
48
46
  readonly assumedLimits?: boolean;
49
47
  /** Advertised by the gateway but not configurable, with the reason. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dan-ai-studio/dshopencodego",
3
- "version": "0.1.12",
3
+ "version": "0.1.14",
4
4
  "description": "OpenCode Go provider for DeepSeek Harness: live gateway catalog, per-conversation session header, usage",
5
5
  "type": "module",
6
6
  "license": "MIT",