@dan-ai-studio/dshopencodego 0.1.12 → 0.1.13

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,9 @@ 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
+ - **Capability marks**: badges on each row label **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
+ - **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.
129
+ - 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
130
 
130
131
  **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
132
 
package/README.md CHANGED
@@ -124,8 +124,9 @@ 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 的声明给每个模型加**结构化输出 / 温度控制 / 开源权重**徽标。只标注声明为"支持"的项——**未声明或声明不支持都不加标记**,与推理档位同一口径:不替文档下结论。
128
+ - **输入模态**:单独一行列出 models.dev 声明的输入方式(文本 / 图像 / 音频 / 视频 / PDF)。这是**模型自身的元数据**,不等于 DSH 能直传:DSH 的模型输入只原生支持文本与图像,音视频/PDF 走附件与工具读取路径;文档未声明时该行不显示。
129
+ - 筛选(按名称/ID、仅已启用、显示已弃用——**默认隐藏已弃用**)、排序(新发布优先 / 每月预估次数 / 输入单价 / 上下文 / 名称 / 已启用优先);列表是唯一视图,逐模型纵向排列。
129
130
 
130
131
  **默认开关规则**:没人显式配置过 `modelVisibility` 时,默认启用**每月预估次数最高的 5 个**(跳过已弃用、无法配置、以及"贡献者版"这类以数据换折扣的模型);**一旦有任一显式条目,全部按显式值走**。同一规则同时作用于模型选择器与设置页,两处不会出现不同答案。
131
132
 
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,6 +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"] } : {},
89
+ ...parseModalities(row["inputModalities"]),
77
90
  ...typeof row["structuredOutput"] === "boolean" ? { structuredOutput: row["structuredOutput"] } : {},
78
91
  ...typeof row["temperature"] === "boolean" ? { temperature: row["temperature"] } : {},
79
92
  ...typeof row["openWeights"] === "boolean" ? { openWeights: row["openWeights"] } : {},
@@ -259,17 +272,12 @@ var en = {
259
272
  sortEnabled: "Enabled first",
260
273
  sortQuota: "Monthly requests",
261
274
  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",
275
+ inputLabel: "Input",
276
+ modalityText: "Text",
277
+ modalityImage: "Image",
278
+ modalityAudio: "Audio",
279
+ modalityVideo: "Video",
280
+ modalityPdf: "PDF",
273
281
  capStructured: "Structured",
274
282
  capTemperature: "Temp",
275
283
  capOpenWeights: "Open",
@@ -333,17 +341,12 @@ var zh = {
333
341
  sortEnabled: "\u5DF2\u542F\u7528\u4F18\u5148",
334
342
  sortQuota: "\u6BCF\u6708\u9884\u4F30\u6B21\u6570",
335
343
  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",
344
+ inputLabel: "\u8F93\u5165",
345
+ modalityText: "\u6587\u672C",
346
+ modalityImage: "\u56FE\u50CF",
347
+ modalityAudio: "\u97F3\u9891",
348
+ modalityVideo: "\u89C6\u9891",
349
+ modalityPdf: "PDF",
347
350
  capStructured: "\u7ED3\u6784\u5316",
348
351
  capTemperature: "\u6E29\u5EA6",
349
352
  capOpenWeights: "\u5F00\u6E90",
@@ -396,8 +399,7 @@ var INITIAL_FILTER = {
396
399
  query: "",
397
400
  showDeprecated: false,
398
401
  onlyEnabled: false,
399
- sort: "quota",
400
- view: "list"
402
+ sort: "quota"
401
403
  };
402
404
  function compactCount(value) {
403
405
  if (value >= 1e6) return `${(value / 1e6).toFixed(value >= 1e7 ? 0 : 1)}M`;
@@ -448,6 +450,16 @@ function capabilityLabels(model, t) {
448
450
  if (model.openWeights === true) labels.push(t("capOpenWeights"));
449
451
  return labels;
450
452
  }
453
+ var MODALITY_KEYS = {
454
+ text: "modalityText",
455
+ image: "modalityImage",
456
+ audio: "modalityAudio",
457
+ video: "modalityVideo",
458
+ pdf: "modalityPdf"
459
+ };
460
+ function inputModalityLabels(model, t) {
461
+ return (model.inputModalities ?? []).map((modality) => t(MODALITY_KEYS[modality]));
462
+ }
451
463
 
452
464
  // plugin-css:/home/runner/work/dshopencodego/dshopencodego/src/client/section.module.css
453
465
  var id = "@dan-ai-studio/dshopencodego/section.module.css";
@@ -455,10 +467,10 @@ if (!document.querySelector("style[data-plugin-css=" + JSON.stringify(id) + "]")
455
467
  const style = document.createElement("style");
456
468
  style.dataset.plugin = "@dan-ai-studio/dshopencodego";
457
469
  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)}";
470
+ 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
471
  document.head.appendChild(style);
460
472
  }
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" };
473
+ var section_default = { "title": "d7lz0W_title", "root": "d7lz0W_root", "block": "d7lz0W_block", "name": "d7lz0W_name", "number": "d7lz0W_number", "select": "d7lz0W_select", "badge": "d7lz0W_badge", "itemModalities": "d7lz0W_itemModalities", "hint": "d7lz0W_hint", "mono": "d7lz0W_mono", "warn": "d7lz0W_warn", "row": "d7lz0W_row", "ok": "d7lz0W_ok", "input": "d7lz0W_input", "list": "d7lz0W_list", "button": "d7lz0W_button", "itemMain": "d7lz0W_itemMain", "itemBody": "d7lz0W_itemBody", "itemMeta": "d7lz0W_itemMeta", "item": "d7lz0W_item" };
462
474
 
463
475
  // src/client/Section.tsx
464
476
  var import_jsx_runtime = require("react/jsx-runtime");
@@ -898,80 +910,16 @@ function Section({ controller, t, getLocale }) {
898
910
  }
899
911
  )
900
912
  ] }),
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
913
  hidden > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: section_default.hint, children: [
926
914
  hidden,
927
915
  " ",
928
916
  t("filterHidden")
929
917
  ] })
930
918
  ] }),
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) => {
919
+ reading !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: section_default.list, children: rows.map((model) => {
973
920
  const badge = badgeFor(model, t);
974
921
  const capabilities = capabilityLabels(model, t).join(" \xB7 ");
922
+ const modalities = inputModalityLabels(model, t).join(" \xB7 ");
975
923
  const offered = isOffered(model, visibility);
976
924
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: section_default.item, children: [
977
925
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
@@ -999,10 +947,11 @@ function Section({ controller, t, getLocale }) {
999
947
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: ioLabel(model, t) }),
1000
948
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: priceLabel(model) }),
1001
949
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: quotaLabel(model, t) })
1002
- ] })
950
+ ] }),
951
+ modalities.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: section_default.itemModalities, children: `${t("inputLabel")} ${modalities}` })
1003
952
  ] })
1004
953
  ] }, model.id);
1005
- }) }))
954
+ }) })
1006
955
  ] })
1007
956
  ] });
1008
957
  }
@@ -1019,7 +968,7 @@ if (!document.querySelector("style[data-plugin-css=" + JSON.stringify(id2) + "]"
1019
968
  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
969
  document.head.appendChild(style);
1021
970
  }
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" };
971
+ var pill_default = { "progress": "mMNkkW_progress", "root": "mMNkkW_root", "limited": "mMNkkW_limited", "local": "mMNkkW_local", "panel": "mMNkkW_panel", "row": "mMNkkW_row", "retry": "mMNkkW_retry", "trigger": "mMNkkW_trigger", "warning": "mMNkkW_warning", "hint": "mMNkkW_hint", "window": "mMNkkW_window" };
1023
972
 
1024
973
  // src/client/UsagePill.tsx
1025
974
  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 : {};
@@ -215,6 +216,12 @@ function nonEmptyString(value) {
215
216
  function declaredBoolean(value) {
216
217
  return typeof value === "boolean" ? value : void 0;
217
218
  }
219
+ function readInputModalities(metadata) {
220
+ const declared = record(metadata["modalities"])["input"];
221
+ if (!Array.isArray(declared)) return void 0;
222
+ const kept = INPUT_MODALITIES.filter((modality) => declared.includes(modality));
223
+ return kept.length === 0 ? void 0 : kept;
224
+ }
218
225
  function validReleaseDate(value) {
219
226
  if (typeof value !== "string" || !/^\d{4}-\d{2}-\d{2}$/.test(value)) return void 0;
220
227
  return Number.isFinite(Date.parse(value)) && new Date(value).toISOString().slice(0, 10) === value ? value : void 0;
@@ -344,6 +351,7 @@ function readOnlineMetadata(body, sources) {
344
351
  maxTokens,
345
352
  assumedLimits: onlineContext === void 0 && builtinContext === void 0 || onlineOutput === void 0 && builtinOutput === void 0,
346
353
  input,
354
+ inputModalities: readInputModalities(metadata),
347
355
  reasoning,
348
356
  thinkingLevelMap: reasoning ? thinkingLevels(metadata, exact?.api === api ? exact : sibling) : void 0,
349
357
  structuredOutput: declaredBoolean(metadata["structured_output"]),
@@ -540,6 +548,7 @@ var OpencodeGoCatalog = class {
540
548
  maxTokens,
541
549
  assumedLimits: exact === void 0,
542
550
  input: exact?.input ?? this.options.defaults.input,
551
+ inputModalities: void 0,
543
552
  reasoning: exact?.reasoning ?? false,
544
553
  thinkingLevelMap: exact?.thinkingLevelMap,
545
554
  // No source describes these, so the page says nothing instead of
@@ -1508,6 +1517,11 @@ function parseCost(value) {
1508
1517
  ...typeof row["cacheWrite"] === "number" ? { cacheWrite: rate("cacheWrite") } : {}
1509
1518
  };
1510
1519
  }
1520
+ function parseModalities(value) {
1521
+ if (!Array.isArray(value)) return {};
1522
+ const kept = INPUT_MODALITIES.filter((modality) => value.includes(modality));
1523
+ return kept.length === 0 ? {} : { inputModalities: kept };
1524
+ }
1511
1525
  function parseModel(value) {
1512
1526
  if (value === null || typeof value !== "object") throw new Error("invalid catalog model");
1513
1527
  const row = value;
@@ -1524,6 +1538,7 @@ function parseModel(value) {
1524
1538
  ...row["cost"] === void 0 ? {} : { cost: parseCost(row["cost"]) },
1525
1539
  ...row["protocolSource"] === "builtin" || row["protocolSource"] === "online" || row["protocolSource"] === "inferred" || row["protocolSource"] === "override" ? { protocolSource: row["protocolSource"] } : {},
1526
1540
  ...typeof row["assumedLimits"] === "boolean" ? { assumedLimits: row["assumedLimits"] } : {},
1541
+ ...parseModalities(row["inputModalities"]),
1527
1542
  ...typeof row["structuredOutput"] === "boolean" ? { structuredOutput: row["structuredOutput"] } : {},
1528
1543
  ...typeof row["temperature"] === "boolean" ? { temperature: row["temperature"] } : {},
1529
1544
  ...typeof row["openWeights"] === "boolean" ? { openWeights: row["openWeights"] } : {},
@@ -1718,6 +1733,7 @@ function catalogReading(snapshot, visibility, listingFailure) {
1718
1733
  protocolSource: fact.protocolSource,
1719
1734
  assumedLimits: fact.assumedLimits,
1720
1735
  ...fact.structuredOutput === void 0 ? {} : { structuredOutput: fact.structuredOutput },
1736
+ ...fact.inputModalities === void 0 ? {} : { inputModalities: fact.inputModalities },
1721
1737
  ...fact.temperature === void 0 ? {} : { temperature: fact.temperature },
1722
1738
  ...fact.openWeights === void 0 ? {} : { openWeights: fact.openWeights }
1723
1739
  };
@@ -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,6 +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'] } : {},
75
+ ...parseModalities(row['inputModalities']),
67
76
  ...typeof row['structuredOutput'] === 'boolean' ? { structuredOutput: row['structuredOutput'] } : {},
68
77
  ...typeof row['temperature'] === 'boolean' ? { temperature: row['temperature'] } : {},
69
78
  ...typeof row['openWeights'] === 'boolean' ? { openWeights: row['openWeights'] } : {},
@@ -206,6 +206,7 @@ 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
212
  // No source describes these, so the page says nothing instead of
@@ -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,6 +34,14 @@ 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')[];
37
+ /**
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.
43
+ */
44
+ readonly inputModalities: readonly InputModality[] | undefined;
33
45
  readonly reasoning: boolean;
34
46
  readonly thinkingLevelMap: ThinkingLevelMap | undefined;
35
47
  /**
@@ -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)
@@ -31,6 +33,21 @@ function nonEmptyString(value) {
31
33
  function declaredBoolean(value) {
32
34
  return typeof value === 'boolean' ? value : undefined;
33
35
  }
36
+ /**
37
+ * Input modalities exactly as the document declares them, in display order.
38
+ *
39
+ * A modality this build cannot name is dropped rather than passed through: the
40
+ * settings page labels every value it renders. An absent list, or one naming
41
+ * nothing recognisable, stays `undefined` — the document is silent, which is a
42
+ * different fact from "text only".
43
+ */
44
+ function readInputModalities(metadata) {
45
+ const declared = record(metadata['modalities'])['input'];
46
+ if (!Array.isArray(declared))
47
+ return undefined;
48
+ const kept = INPUT_MODALITIES.filter(modality => declared.includes(modality));
49
+ return kept.length === 0 ? undefined : kept;
50
+ }
34
51
  /** A calendar date models.dev states without a timezone. */
35
52
  function validReleaseDate(value) {
36
53
  if (typeof value !== 'string' || !/^\d{4}-\d{2}-\d{2}$/.test(value))
@@ -225,6 +242,7 @@ export function readOnlineMetadata(body, sources) {
225
242
  assumedLimits: onlineContext === undefined && builtinContext === undefined
226
243
  || onlineOutput === undefined && builtinOutput === undefined,
227
244
  input,
245
+ inputModalities: readInputModalities(metadata),
228
246
  reasoning,
229
247
  thinkingLevelMap: reasoning ? thinkingLevels(metadata, exact?.api === api ? exact : sibling) : undefined,
230
248
  structuredOutput: declaredBoolean(metadata['structured_output']),
@@ -44,6 +44,7 @@ export function catalogReading(snapshot, visibility, listingFailure) {
44
44
  protocolSource: fact.protocolSource,
45
45
  assumedLimits: fact.assumedLimits,
46
46
  ...fact.structuredOutput === undefined ? {} : { structuredOutput: fact.structuredOutput },
47
+ ...fact.inputModalities === undefined ? {} : { inputModalities: fact.inputModalities },
47
48
  ...fact.temperature === undefined ? {} : { temperature: fact.temperature },
48
49
  ...fact.openWeights === undefined ? {} : { openWeights: fact.openWeights },
49
50
  };
@@ -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 {
@@ -36,6 +37,11 @@ export interface ModelSummary {
36
37
  };
37
38
  /** Which ladder level decided this model's protocol. */
38
39
  readonly protocolSource?: ProtocolSource;
40
+ /**
41
+ * Every input modality models.dev declares, in display order. Absent when the
42
+ * document declares none this build can name.
43
+ */
44
+ readonly inputModalities?: readonly InputModality[];
39
45
  /**
40
46
  * Capabilities models.dev declares, in its own words. Each is absent when the
41
47
  * document is silent about it — absence is "unstated", never "unsupported",
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.13",
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",