Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions docs/model-discovery-capabilities-patch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Models 发现能力兼容补丁

## 范围与版本

目标:@deepseek-ai/dsh-llm-pi-ai、@deepseek-ai/dsh-llm、@deepseek-ai/dsh-client-ui-settings-models **0.2.2-alpha.2**,wire 语义核对 @earendil-works/pi-ai **1.3.0**。

补丁修复现有发现 → LlmDiscoveredModel/服务序列化 → UI adopt 链路丢失明确能力元数据的问题,不新增协议或加载入口,不改变 Profile/Safe Mode 的解析基准。失败或未知元数据仍保留原有人工配置入口;不修改用户已有模型行。

权威输入在 patches/ 对应三个包的 patch 中;node_modules 仅供重放及行为验证。移除条件:上游端到端保留经校验的等价能力契约、采用时不覆盖已有用户配置,并通过本次行为/wire 回归;升级时重新核对 pi-ai 映射,不能只机械调整补丁上下文。

## 官方输入与保守边界

- [OpenRouter models](https://openrouter.ai/docs/api/api-reference/models/list-all-models-and-their-properties.md):校验 architecture.input_modalities 数组及 reasoning 对象。supported_efforts 缺失是未知,null 才表示网关接受全部 effort 值。mandatory 为 true 时不提供关闭。none 对应 UI off,但实际 wire 必须为 none;max/xhigh 不降级。校验 default_effort/default_enabled/supports_max_tokens 的已知形状,不把这些默认值强制写成用户偏好,不从 supported_parameters 猜测等级。
- [Anthropic models](https://platform.claude.com/docs/en/api/models/list.md):读取 capabilities.image_input.supported、thinking.supported/types、effort.supported 及各等级的 supported boolean。只有明确支持 adaptive 且披露可用 effort 才自动配置等级,并传递最小 forceAdaptiveThinking 开关,避免 pi-ai budget 路径把扩展等级折成 high。只有 disabled.supported 为 true 才加入 off;between_tools 不代表 disabled。
- OpenAI 标准 models 没有这些能力字段,缺失/畸形不能解释为“不支持”。未声明 reasoning、旧式 budget thinking 等继续人工配置。只投影 runtime 支持的 text/image,忽略合法但 runtime 未支持的 file/audio/video,不声称支持这些输入。
- 内置目录保留 reasoning=false 和显式 thinkingLevelMap 的支持/禁用及 wire 值,不凭 reasoning=true 发明 max/xhigh。

## 验证与限制

行为回归:test/model-discovery-capabilities.test.mjs,覆盖官方形状、缺失/畸形、目录 wire map、服务传递/隔离、UI adopt 与已有行保留。实际导入安装的 pi-ai streamSimple,并在 onPayload 处中止网络,验证 Anthropic off 发送 thinking.disabled、max/xhigh 精确 effort,OpenRouter off 发送 reasoning.effort=none、max/xhigh 精确传递。

补丁在独立临时树从反向重放所得基线重新应用并逐字节比对三包目标文件。patch-package 自动生成时其内部 npm install 退出 255(无诊断输出),因此采用本地已装版本及既有补丁反向基线生成,没有进行破坏性 npm ci;未宣称已验证全新联网安装。未重启正式安装应用、未做真实浏览器/UI 点击验收,行为测试不替代实际加载与保存流程验收。
89 changes: 52 additions & 37 deletions patches/@deepseek-ai+dsh-client-ui-chat+0.2.2-alpha.2.patch

Large diffs are not rendered by default.

61 changes: 42 additions & 19 deletions patches/@deepseek-ai+dsh-client-ui-deliverables+0.2.2-alpha.2.patch
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
diff --git a/node_modules/@deepseek-ai/dsh-client-ui-deliverables/lib/client.js b/node_modules/@deepseek-ai/dsh-client-ui-deliverables/lib/client.js
index 3d7675f21b..8951693184 100644
index 3d7675f..559b892 100644
--- a/node_modules/@deepseek-ai/dsh-client-ui-deliverables/lib/client.js
+++ b/node_modules/@deepseek-ai/dsh-client-ui-deliverables/lib/client.js
@@ -1271,19 +1271,82 @@ window.__ModuleLoader__.load({
@@ -1271,19 +1271,105 @@ window.__ModuleLoader__.load({
* @returns The resolver MarkdownText consumes; the full path rides `title`,
* the same disambiguator the row's chips carry.
*/
+ /**
+ * A known or heuristic path. Folders open in the system file manager;
+ * files stay on the document-preview opener. `kind` selects the icon.
+ */
+ function fileMention(path, kind, openFile, label) {
+ function fileMention(path, kind, openFile, label, line) {
+ return {
+ open: () => {
+ if (kind === "folder") openFile(path, { revealInFileManager: true });
+ else if (line !== void 0) openFile(path, { line });
+ else openFile(path);
+ },
+ label: label(path, kind),
Expand All @@ -22,6 +23,24 @@ index 3d7675f21b..8951693184 100644
+ };
+ }
+ /**
+ * Split a trailing `#L<line>` or `:<line>` suffix from a trimmed candidate
+ * path, returning `{ path, line }` where `line` is a 1-based safe integer
+ * when the suffix names a positive line number.
+ */
+ function splitLineSuffix(candidate) {
+ const hash = /^(.*?)#L(\d+)(?:C\d+)?(?:-L?\d+(?:C\d+)?)?$/.exec(candidate);
+ if (hash !== null) {
+ const line = Number(hash[2]);
+ return line >= 1 && Number.isSafeInteger(line) ? { path: hash[1], line } : { path: hash[1] };
+ }
+ const colon = /^(.*?):(\d+)(?::\d+)?(?:-\d+(?::\d+)?)?$/.exec(candidate);
+ if (colon !== null && colon[1] !== "" && !/^[a-z]:$/i.test(colon[1])) {
+ const line = Number(colon[2]);
+ return line >= 1 && Number.isSafeInteger(line) ? { path: colon[1], line } : { path: colon[1] };
+ }
+ return { path: candidate };
+ }
+ /**
+ * Resolve local path references that were not produced by a tool in the
+ * current turn. Branch names (`codex/foo`, `origin/main`) and bare tokens
+ * (`package.json`, `console.log`) stay inert: a relative slash path is a
Expand All @@ -38,9 +57,9 @@ index 3d7675f21b..8951693184 100644
+ function localPathReference(value) {
+ const candidate = value.trim();
+ if (candidate === "" || /[\r\n]/.test(candidate)) return void 0;
+ if (/^[a-z][a-z\d+.-]*:/i.test(candidate) && !/^[a-z]:[\\/]/i.test(candidate)) return void 0;
+ const path = candidate.replace(/(?:#L\d+(?:C\d+)?|:\d+(?::\d+)?)$/, "");
+ const { path, line } = splitLineSuffix(candidate);
+ if (path === "") return void 0;
+ if (/^[a-z][a-z\d+.-]*:/i.test(path) && !/^[a-z]:[\\/]/i.test(path)) return void 0;
+ if (/^@[^\\/@\s]+\/[^\\/@\s]+(?:@[^\\/\s]+)?$/.test(path)) return void 0;
+ if (/^[^@\s\\/]+@[^@\s\\/]+\.[A-Za-z]{2,}$/.test(path)) return void 0;
+ if (/^v?\d+(?:\.\d+){1,4}(?:-[A-Za-z0-9.-]+)?$/.test(path)) return void 0;
Expand All @@ -52,9 +71,11 @@ index 3d7675f21b..8951693184 100644
+ const separated = /[\\/]/.test(path);
+ const worktree = segments.some((segment) => /[A-Z]/.test(rawSuffix(segment)));
+ if (!explicit && !trailingDir && !(separated && extension !== "") && !worktree) return void 0;
+ const kind = !trailingDir && extension !== "" ? "file" : "folder";
+ return {
+ path,
+ kind: !trailingDir && extension !== "" ? "file" : "folder"
+ kind,
+ ...kind === "file" && line !== void 0 ? { line } : {}
+ };
+ }
+ /** Characters after the segment's last dot, or "" when there is none. */
Expand All @@ -75,8 +96,8 @@ index 3d7675f21b..8951693184 100644
+ }
function producedFileMentions(paths, openFile, label, operations = []) {
return { resolve(value) {
const matches = new Set(paths.filter((path) => path === value || basename(path) === value));
for (const operation of operations) if (operation.argumentPath === value) matches.add(operation.path);
- const matches = new Set(paths.filter((path) => path === value || basename(path) === value));
- for (const operation of operations) if (operation.argumentPath === value) matches.add(operation.path);
- const path = matches.size === 1 ? matches.values().next().value : void 0;
- if (path === void 0) return void 0;
- return {
Expand All @@ -86,31 +107,35 @@ index 3d7675f21b..8951693184 100644
- label: label(path),
- title: path
- };
+ const suffix = splitLineSuffix(value.trim());
+ const target = suffix.path;
+ const matches = new Set(paths.filter((path) => path === value || basename(path) === value || path === target || basename(path) === target));
+ for (const operation of operations) if (operation.argumentPath === value || operation.argumentPath === target) matches.add(operation.path);
+ const known = matches.size === 1 ? matches.values().next().value : void 0;
+ if (known !== void 0) return fileMention(known, "file", openFile, label);
+ if (known !== void 0) return fileMention(known, "file", openFile, label, suffix.line);
+ const local = localPathReference(value);
+ if (local === void 0) return void 0;
+ return fileMention(local.path, local.kind, openFile, label);
+ return fileMention(local.path, local.kind, openFile, label, local.line);
} };
}
//#endregion
@@ -2104,6 +2167,7 @@ window.__ModuleLoader__.load({
@@ -2104,6 +2190,7 @@ window.__ModuleLoader__.load({
"presented.hostError": "无法读取主机桌面信息",
"presented.preview": "在侧边栏预览",
"presented.previewButton": "在侧边栏打开 {name}",
+ "presented.openFolder": "在文件管理器中打开 {name}",
"presented.previewCard": "在侧边栏预览 {name}",
"presented.all": "全部 {count} 个文件",
"presented.expandAria": "展开全部 {count} 个交付文件",
@@ -2164,6 +2228,7 @@ window.__ModuleLoader__.load({
@@ -2164,6 +2251,7 @@ window.__ModuleLoader__.load({
"presented.hostError": "Could not read the Host desktop information",
"presented.preview": "Preview in sidebar",
"presented.previewButton": "Open {name} in sidebar",
+ "presented.openFolder": "Open {name} in file manager",
"presented.previewCard": "Preview {name} in sidebar",
"presented.all": "All {count} files",
"presented.expandAria": "Show all {count} delivered files",
@@ -2672,8 +2737,7 @@ window.__ModuleLoader__.load({
@@ -2672,8 +2760,7 @@ window.__ModuleLoader__.load({
ctx.provide("chatFileMentions", { forClosing(owner) {
const paths = selectProducedFiles(owner);
const presented = presentedForClosing(owner);
Expand All @@ -121,7 +146,7 @@ index 3d7675f21b..8951693184 100644
}
//#endregion
diff --git a/node_modules/@deepseek-ai/dsh-client-ui-deliverables/lib/types/client/locales.d.ts b/node_modules/@deepseek-ai/dsh-client-ui-deliverables/lib/types/client/locales.d.ts
index 59725309e1..6ae6fbba70 100644
index 5972530..6ae6fbb 100644
--- a/node_modules/@deepseek-ai/dsh-client-ui-deliverables/lib/types/client/locales.d.ts
+++ b/node_modules/@deepseek-ai/dsh-client-ui-deliverables/lib/types/client/locales.d.ts
@@ -13,6 +13,7 @@ export declare const zh: {
Expand All @@ -133,15 +158,13 @@ index 59725309e1..6ae6fbba70 100644
'presented.all': string;
'presented.expandAria': string;
diff --git a/node_modules/@deepseek-ai/dsh-client-ui-deliverables/lib/types/client/turn-deliverables.d.ts b/node_modules/@deepseek-ai/dsh-client-ui-deliverables/lib/types/client/turn-deliverables.d.ts
index 14677b6dfc..6fe940766d 100644
index 14677b6..a5cfd4e 100644
--- a/node_modules/@deepseek-ai/dsh-client-ui-deliverables/lib/types/client/turn-deliverables.d.ts
+++ b/node_modules/@deepseek-ai/dsh-client-ui-deliverables/lib/types/client/turn-deliverables.d.ts
@@ -82,5 +82,5 @@ export { basename } from '../presented.ts';
* @returns The resolver MarkdownText consumes; the full path rides `title`,
* the same disambiguator the row's chips carry.
*/
-export declare function producedFileMentions(paths: readonly string[], openFile: (path: string) => void, label: (path: string) => string, operations?: readonly Pick<ProducedPath, 'path' | 'argumentPath'>[]): MarkdownFileMentions;
-//# sourceMappingURL=turn-deliverables.d.ts.map
\ No newline at end of file
+export declare function producedFileMentions(paths: readonly string[], openFile: (path: string, options?: { readonly revealInFileManager?: boolean }) => void, label: (path: string, kind?: 'file' | 'folder') => string, operations?: readonly Pick<ProducedPath, 'path' | 'argumentPath'>[]): MarkdownFileMentions;
+//# sourceMappingURL=turn-deliverables.d.ts.map
+export declare function producedFileMentions(paths: readonly string[], openFile: (path: string, options?: { readonly line?: number; readonly revealInFileManager?: boolean }) => void, label: (path: string, kind?: 'file' | 'folder') => string, operations?: readonly Pick<ProducedPath, 'path' | 'argumentPath'>[]): MarkdownFileMentions;
//# sourceMappingURL=turn-deliverables.d.ts.map
31 changes: 27 additions & 4 deletions patches/@deepseek-ai+dsh-client-ui-primitives+0.2.2-alpha.2.patch
Original file line number Diff line number Diff line change
@@ -1,18 +1,41 @@
diff --git a/node_modules/@deepseek-ai/dsh-client-ui-primitives/lib/index.js b/node_modules/@deepseek-ai/dsh-client-ui-primitives/lib/index.js
index 5b6450c675..4b5673c9d5 100644
index 5b6450c..f0dd986 100644
--- a/node_modules/@deepseek-ai/dsh-client-ui-primitives/lib/index.js
+++ b/node_modules/@deepseek-ai/dsh-client-ui-primitives/lib/index.js
@@ -10428,7 +10428,7 @@ function renderNode(node, key, context) {
@@ -10006,10 +10006,20 @@ function parseFileLink(value) {
} catch (_error) {
return;
}
+ let colonLine;
+ if (hash < 0) {
+ const colon = /^(.*?):([1-9]\d*)(?::[1-9]\d*)?(?:-[1-9]\d*(?::[1-9]\d*)?)?$/.exec(path);
+ if (colon !== null && colon[1] !== "" && !/^[a-z]:$/i.test(colon[1]) && (/[\\/]/.test(colon[1]) || /\.[a-z][a-z\d]{0,9}$/i.test(colon[1]))) {
+ const parsedLine = Number(colon[2]);
+ if (!Number.isSafeInteger(parsedLine)) return void 0;
+ path = colon[1];
+ colonLine = parsedLine;
+ }
+ }
if (path.length === 0 || /[\u0000-\u001f\u007f]/.test(path) || /^[\\/]{2}/.test(path) || /^[a-z][a-z\d+.-]*:/i.test(path) && !/^[a-z]:[\\/]/i.test(path)) return void 0;
- if (hash < 0) return { path };
+ if (hash < 0) return colonLine === void 0 ? { path } : { path, line: colonLine };
const fragment = value.slice(hash + 1);
- const match = /^L([1-9]\d*)(?:-L([1-9]\d*))?$/.exec(fragment);
+ const match = /^L([1-9]\d*)(?:C[1-9]\d*)?(?:-L?([1-9]\d*)(?:C[1-9]\d*)?)?$/.exec(fragment);
if (match === null) return void 0;
const line = Number(match[1]);
const end = match[2] === void 0 ? line : Number(match[2]);
@@ -10428,7 +10438,7 @@ function renderNode(node, key, context) {
"aria-label": mention.label,
onClick: mention.open,
children: [jsx(LinkIconMedium, {
- kind: classifyLinkPath(value),
+ kind: mention.kind === "folder" ? "folder" : classifyLinkPath(value),
+ kind: mention.kind === "folder" ? "folder" : classifyLinkPath(mention.title || value),
className: markdownCss.linkIcon
}), value]
}) }, key);
diff --git a/node_modules/@deepseek-ai/dsh-client-ui-primitives/lib/types/markdown/render.d.ts b/node_modules/@deepseek-ai/dsh-client-ui-primitives/lib/types/markdown/render.d.ts
index bf96376990..cbbbf81e48 100644
index bf96376..cbbbf81 100644
--- a/node_modules/@deepseek-ai/dsh-client-ui-primitives/lib/types/markdown/render.d.ts
+++ b/node_modules/@deepseek-ai/dsh-client-ui-primitives/lib/types/markdown/render.d.ts
@@ -86,6 +86,11 @@ export interface MarkdownFileMentions {
Expand Down
Loading