@ai-workbench/remote-agent 0.8.5 → 0.8.11
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.md +49 -8
- package/dist/acp.d.ts +22 -2
- package/dist/acp.js +143 -25
- package/dist/approval-label.d.ts +1 -1
- package/dist/approval-label.js +15 -3
- package/dist/broker-runtime.js +40 -8
- package/dist/broker.js +5 -1
- package/dist/claude-local.d.ts +37 -0
- package/dist/claude-local.js +207 -0
- package/dist/client.js +71 -2
- package/dist/conversation-details.d.ts +0 -1
- package/dist/conversation-details.js +37 -7
- package/dist/employee-endpoint.d.ts +91 -0
- package/dist/employee-endpoint.js +141 -0
- package/dist/employee-exec.d.ts +53 -0
- package/dist/employee-exec.js +423 -0
- package/dist/employee-location.d.ts +66 -0
- package/dist/employee-location.js +51 -0
- package/dist/employee-mcp.d.ts +42 -0
- package/dist/employee-mcp.js +181 -0
- package/dist/employee-native.d.ts +18 -0
- package/dist/employee-native.js +38 -0
- package/dist/employee-provider.d.ts +42 -0
- package/dist/employee-provider.js +326 -0
- package/dist/employee-relay.d.ts +18 -0
- package/dist/employee-relay.js +49 -0
- package/dist/employee-shell.d.ts +26 -0
- package/dist/employee-shell.js +89 -0
- package/dist/employee-ssh.d.ts +22 -0
- package/dist/employee-ssh.js +123 -0
- package/dist/employee-tools.d.ts +117 -0
- package/dist/employee-tools.js +292 -0
- package/dist/employee-workspace.d.ts +26 -0
- package/dist/employee-workspace.js +61 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +235 -41
- package/dist/input.d.ts +11 -0
- package/dist/input.js +61 -23
- package/dist/local-user-turn.d.ts +24 -0
- package/dist/local-user-turn.js +103 -0
- package/dist/model-catalog.d.ts +18 -2
- package/dist/model-catalog.js +101 -36
- package/dist/model-defaults.d.ts +29 -0
- package/dist/model-defaults.js +62 -0
- package/dist/model-input-retention.d.ts +16 -0
- package/dist/model-input-retention.js +129 -0
- package/dist/presentation.d.ts +1 -1
- package/dist/presentation.js +52 -2
- package/dist/provider.d.ts +39 -2
- package/dist/provider.js +69 -13
- package/dist/readiness.js +1 -1
- package/dist/recovery.d.ts +14 -1
- package/dist/recovery.js +11 -2
- package/dist/remote-details.d.ts +7 -0
- package/dist/remote-details.js +11 -1
- package/dist/workspace-source-confirmation.d.ts +4 -0
- package/dist/workspace-source-confirmation.js +12 -0
- package/dist/workspace-source.d.ts +25 -0
- package/dist/workspace-source.js +136 -0
- package/helpers/ripgrep/LICENSE-vscode +13 -0
- package/helpers/ripgrep/PROVENANCE.md +9 -0
- package/helpers/ripgrep/darwin-arm64/rg +0 -0
- package/helpers/ripgrep/darwin-x64/rg +0 -0
- package/helpers/ripgrep/manifest.json +56 -0
- package/helpers/ripgrep/notices/COPYING-ripgrep +3 -0
- package/helpers/ripgrep/notices/DEPENDENCIES.json +946 -0
- package/helpers/ripgrep/notices/LICENCE-PCRE2.md +103 -0
- package/helpers/ripgrep/notices/LICENSE-MIT-ripgrep +21 -0
- package/helpers/ripgrep/notices/UNLICENSE-ripgrep +24 -0
- package/helpers/ripgrep/notices/aho-corasick-1.1.3/COPYING +3 -0
- package/helpers/ripgrep/notices/aho-corasick-1.1.3/LICENSE-MIT +21 -0
- package/helpers/ripgrep/notices/aho-corasick-1.1.3/UNLICENSE +24 -0
- package/helpers/ripgrep/notices/anyhow-1.0.100/LICENSE-APACHE +176 -0
- package/helpers/ripgrep/notices/anyhow-1.0.100/LICENSE-MIT +23 -0
- package/helpers/ripgrep/notices/arbitrary-1.4.2/LICENSE-APACHE +201 -0
- package/helpers/ripgrep/notices/arbitrary-1.4.2/LICENSE-MIT +27 -0
- package/helpers/ripgrep/notices/bstr-1.12.0/COPYING +8 -0
- package/helpers/ripgrep/notices/bstr-1.12.0/LICENSE-APACHE +201 -0
- package/helpers/ripgrep/notices/bstr-1.12.0/LICENSE-MIT +21 -0
- package/helpers/ripgrep/notices/cc-1.2.41/LICENSE-APACHE +201 -0
- package/helpers/ripgrep/notices/cc-1.2.41/LICENSE-MIT +25 -0
- package/helpers/ripgrep/notices/cfg-if-1.0.4/LICENSE-APACHE +201 -0
- package/helpers/ripgrep/notices/cfg-if-1.0.4/LICENSE-MIT +25 -0
- package/helpers/ripgrep/notices/crossbeam-channel-0.5.15/LICENSE-APACHE +201 -0
- package/helpers/ripgrep/notices/crossbeam-channel-0.5.15/LICENSE-MIT +27 -0
- package/helpers/ripgrep/notices/crossbeam-channel-0.5.15/LICENSE-THIRD-PARTY +593 -0
- package/helpers/ripgrep/notices/crossbeam-deque-0.8.6/LICENSE-APACHE +201 -0
- package/helpers/ripgrep/notices/crossbeam-deque-0.8.6/LICENSE-MIT +27 -0
- package/helpers/ripgrep/notices/crossbeam-epoch-0.9.18/LICENSE-APACHE +201 -0
- package/helpers/ripgrep/notices/crossbeam-epoch-0.9.18/LICENSE-MIT +27 -0
- package/helpers/ripgrep/notices/crossbeam-utils-0.8.21/LICENSE-APACHE +201 -0
- package/helpers/ripgrep/notices/crossbeam-utils-0.8.21/LICENSE-MIT +27 -0
- package/helpers/ripgrep/notices/derive_arbitrary-1.4.2/LICENSE-APACHE +201 -0
- package/helpers/ripgrep/notices/derive_arbitrary-1.4.2/LICENSE-MIT +27 -0
- package/helpers/ripgrep/notices/encoding_rs-0.8.35/COPYRIGHT +17 -0
- package/helpers/ripgrep/notices/encoding_rs-0.8.35/LICENSE-APACHE +202 -0
- package/helpers/ripgrep/notices/encoding_rs-0.8.35/LICENSE-MIT +25 -0
- package/helpers/ripgrep/notices/encoding_rs-0.8.35/LICENSE-WHATWG +26 -0
- package/helpers/ripgrep/notices/encoding_rs_io-0.1.7/COPYING +8 -0
- package/helpers/ripgrep/notices/encoding_rs_io-0.1.7/LICENSE-APACHE +201 -0
- package/helpers/ripgrep/notices/encoding_rs_io-0.1.7/LICENSE-MIT +21 -0
- package/helpers/ripgrep/notices/find-msvc-tools-0.1.4/LICENSE-APACHE +201 -0
- package/helpers/ripgrep/notices/find-msvc-tools-0.1.4/LICENSE-MIT +25 -0
- package/helpers/ripgrep/notices/getrandom-0.3.4/LICENSE-APACHE +201 -0
- package/helpers/ripgrep/notices/getrandom-0.3.4/LICENSE-MIT +26 -0
- package/helpers/ripgrep/notices/glob-0.3.3/LICENSE-APACHE +201 -0
- package/helpers/ripgrep/notices/glob-0.3.3/LICENSE-MIT +25 -0
- package/helpers/ripgrep/notices/itoa-1.0.15/LICENSE-APACHE +176 -0
- package/helpers/ripgrep/notices/itoa-1.0.15/LICENSE-MIT +23 -0
- package/helpers/ripgrep/notices/jobserver-0.1.34/LICENSE-APACHE +201 -0
- package/helpers/ripgrep/notices/jobserver-0.1.34/LICENSE-MIT +25 -0
- package/helpers/ripgrep/notices/lexopt-0.3.1/LICENSE +19 -0
- package/helpers/ripgrep/notices/libc-0.2.177/LICENSE-APACHE +176 -0
- package/helpers/ripgrep/notices/libc-0.2.177/LICENSE-MIT +25 -0
- package/helpers/ripgrep/notices/log-0.4.28/LICENSE-APACHE +201 -0
- package/helpers/ripgrep/notices/log-0.4.28/LICENSE-MIT +25 -0
- package/helpers/ripgrep/notices/memchr-2.7.6/COPYING +3 -0
- package/helpers/ripgrep/notices/memchr-2.7.6/LICENSE-MIT +21 -0
- package/helpers/ripgrep/notices/memchr-2.7.6/UNLICENSE +24 -0
- package/helpers/ripgrep/notices/memmap2-0.9.8/LICENSE-APACHE +201 -0
- package/helpers/ripgrep/notices/memmap2-0.9.8/LICENSE-MIT +26 -0
- package/helpers/ripgrep/notices/msvc_spectre_libs-0.1.3/LICENSE +21 -0
- package/helpers/ripgrep/notices/pcre2-0.2.11/COPYING +3 -0
- package/helpers/ripgrep/notices/pcre2-0.2.11/LICENSE-MIT +21 -0
- package/helpers/ripgrep/notices/pcre2-0.2.11/UNLICENSE +24 -0
- package/helpers/ripgrep/notices/pcre2-sys-0.2.10/COPYING +3 -0
- package/helpers/ripgrep/notices/pcre2-sys-0.2.10/LICENSE-MIT +21 -0
- package/helpers/ripgrep/notices/pcre2-sys-0.2.10/UNLICENSE +24 -0
- package/helpers/ripgrep/notices/pkg-config-0.3.32/LICENSE-APACHE +201 -0
- package/helpers/ripgrep/notices/pkg-config-0.3.32/LICENSE-MIT +25 -0
- package/helpers/ripgrep/notices/proc-macro2-1.0.101/LICENSE-APACHE +176 -0
- package/helpers/ripgrep/notices/proc-macro2-1.0.101/LICENSE-MIT +23 -0
- package/helpers/ripgrep/notices/quote-1.0.41/LICENSE-APACHE +176 -0
- package/helpers/ripgrep/notices/quote-1.0.41/LICENSE-MIT +23 -0
- package/helpers/ripgrep/notices/r-efi-5.3.0/AUTHORS +74 -0
- package/helpers/ripgrep/notices/regex-1.12.2/LICENSE-APACHE +201 -0
- package/helpers/ripgrep/notices/regex-1.12.2/LICENSE-MIT +25 -0
- package/helpers/ripgrep/notices/regex-automata-0.4.13/LICENSE-APACHE +201 -0
- package/helpers/ripgrep/notices/regex-automata-0.4.13/LICENSE-MIT +25 -0
- package/helpers/ripgrep/notices/regex-syntax-0.8.8/LICENSE-APACHE +201 -0
- package/helpers/ripgrep/notices/regex-syntax-0.8.8/LICENSE-MIT +25 -0
- package/helpers/ripgrep/notices/regex-syntax-0.8.8/src/unicode_tables/LICENSE-UNICODE +57 -0
- package/helpers/ripgrep/notices/ryu-1.0.20/LICENSE-APACHE +176 -0
- package/helpers/ripgrep/notices/ryu-1.0.20/LICENSE-BOOST +23 -0
- package/helpers/ripgrep/notices/same-file-1.0.6/COPYING +3 -0
- package/helpers/ripgrep/notices/same-file-1.0.6/LICENSE-MIT +21 -0
- package/helpers/ripgrep/notices/same-file-1.0.6/UNLICENSE +24 -0
- package/helpers/ripgrep/notices/serde-1.0.228/LICENSE-APACHE +176 -0
- package/helpers/ripgrep/notices/serde-1.0.228/LICENSE-MIT +23 -0
- package/helpers/ripgrep/notices/serde_core-1.0.228/LICENSE-APACHE +176 -0
- package/helpers/ripgrep/notices/serde_core-1.0.228/LICENSE-MIT +23 -0
- package/helpers/ripgrep/notices/serde_derive-1.0.228/LICENSE-APACHE +176 -0
- package/helpers/ripgrep/notices/serde_derive-1.0.228/LICENSE-MIT +23 -0
- package/helpers/ripgrep/notices/serde_json-1.0.145/LICENSE-APACHE +176 -0
- package/helpers/ripgrep/notices/serde_json-1.0.145/LICENSE-MIT +23 -0
- package/helpers/ripgrep/notices/shlex-1.3.0/LICENSE-APACHE +13 -0
- package/helpers/ripgrep/notices/shlex-1.3.0/LICENSE-MIT +21 -0
- package/helpers/ripgrep/notices/syn-2.0.106/LICENSE-APACHE +176 -0
- package/helpers/ripgrep/notices/syn-2.0.106/LICENSE-MIT +23 -0
- package/helpers/ripgrep/notices/termcolor-1.4.1/COPYING +3 -0
- package/helpers/ripgrep/notices/termcolor-1.4.1/LICENSE-MIT +21 -0
- package/helpers/ripgrep/notices/termcolor-1.4.1/UNLICENSE +24 -0
- package/helpers/ripgrep/notices/textwrap-0.16.2/LICENSE +21 -0
- package/helpers/ripgrep/notices/tikv-jemalloc-sys-0.6.0+5.3.0-1-ge13ca993e8ccb9ba9847cc330696e02839f328f7/LICENSE-APACHE +201 -0
- package/helpers/ripgrep/notices/tikv-jemalloc-sys-0.6.0+5.3.0-1-ge13ca993e8ccb9ba9847cc330696e02839f328f7/LICENSE-MIT +25 -0
- package/helpers/ripgrep/notices/tikv-jemalloc-sys-0.6.0+5.3.0-1-ge13ca993e8ccb9ba9847cc330696e02839f328f7/jemalloc/COPYING +27 -0
- package/helpers/ripgrep/notices/tikv-jemallocator-0.6.0/LICENSE-APACHE +201 -0
- package/helpers/ripgrep/notices/tikv-jemallocator-0.6.0/LICENSE-MIT +25 -0
- package/helpers/ripgrep/notices/unicode-ident-1.0.19/LICENSE-APACHE +176 -0
- package/helpers/ripgrep/notices/unicode-ident-1.0.19/LICENSE-MIT +23 -0
- package/helpers/ripgrep/notices/unicode-ident-1.0.19/LICENSE-UNICODE +39 -0
- package/helpers/ripgrep/notices/walkdir-2.5.0/COPYING +3 -0
- package/helpers/ripgrep/notices/walkdir-2.5.0/LICENSE-MIT +21 -0
- package/helpers/ripgrep/notices/walkdir-2.5.0/UNLICENSE +24 -0
- package/helpers/ripgrep/notices/wasip2-1.0.1+wasi-0.2.4/LICENSE-APACHE +201 -0
- package/helpers/ripgrep/notices/wasip2-1.0.1+wasi-0.2.4/LICENSE-Apache-2.0_WITH_LLVM-exception +220 -0
- package/helpers/ripgrep/notices/wasip2-1.0.1+wasi-0.2.4/LICENSE-MIT +23 -0
- package/helpers/ripgrep/notices/winapi-util-0.1.11/COPYING +3 -0
- package/helpers/ripgrep/notices/winapi-util-0.1.11/LICENSE-MIT +21 -0
- package/helpers/ripgrep/notices/winapi-util-0.1.11/UNLICENSE +24 -0
- package/helpers/ripgrep/notices/windows-link-0.2.1/license-apache-2.0 +201 -0
- package/helpers/ripgrep/notices/windows-link-0.2.1/license-mit +21 -0
- package/helpers/ripgrep/notices/windows-sys-0.61.2/license-apache-2.0 +201 -0
- package/helpers/ripgrep/notices/windows-sys-0.61.2/license-mit +21 -0
- package/helpers/ripgrep/notices/wit-bindgen-0.46.0/LICENSE-APACHE +201 -0
- package/helpers/ripgrep/notices/wit-bindgen-0.46.0/LICENSE-Apache-2.0_WITH_LLVM-exception +220 -0
- package/helpers/ripgrep/notices/wit-bindgen-0.46.0/LICENSE-MIT +23 -0
- package/locale/en.json +1 -1
- package/locale/zh.json +2 -2
- package/package.json +29 -11
package/dist/input.js
CHANGED
|
@@ -1,5 +1,59 @@
|
|
|
1
1
|
import { NeedError } from "./remote.js";
|
|
2
2
|
import { isWorkspaceNoticeMessage, withoutWorkspaceNotice } from "./workspace-notice.js";
|
|
3
|
+
async function publicBlock(block, options, readImage, history = false) {
|
|
4
|
+
options.signal?.throwIfAborted();
|
|
5
|
+
if (block.type === 'text')
|
|
6
|
+
return { type: 'text', text: block.text };
|
|
7
|
+
if (block.type === 'image') {
|
|
8
|
+
if (block.offloaded) {
|
|
9
|
+
return { type: 'text', text: `图片 ${block.attachment.name ?? block.attachment.attachmentId} 已按官方会话策略移出当前上下文。` };
|
|
10
|
+
}
|
|
11
|
+
if (!readImage)
|
|
12
|
+
throw new NeedError('ATTACHMENT_REQUIRED', '我需要官方附件组件读取这张图片', '官方attachments.readImage');
|
|
13
|
+
const image = await readImage(block.attachment, options.signal);
|
|
14
|
+
options.signal?.throwIfAborted();
|
|
15
|
+
if (!image || !(image.data instanceof Uint8Array) || image.ref?.attachmentId !== block.attachment.attachmentId || !['image/png', 'image/jpeg', 'image/webp', 'image/gif'].includes(image.ref.mediaType) || image.ref.bytes !== image.data.byteLength)
|
|
16
|
+
throw new NeedError('ATTACHMENT_INVALID', '图片附件没有返回匹配的已核验内容', '官方核验的图片字节与媒体类型');
|
|
17
|
+
return { type: 'image', mimeType: image.ref.mediaType, data: Buffer.from(image.data).toString('base64') };
|
|
18
|
+
}
|
|
19
|
+
throw new NeedError(history ? 'HISTORY_CONTENT_UNSUPPORTED' : 'INPUT_TYPE_REQUIRED', `我需要此${history ? '历史' : '输入'}类型的远程转接能力:${block.type};本轮未新建或发送`, '对应类型的公开ACP输入映射;内部推理及工具记录不可降为用户要求');
|
|
20
|
+
}
|
|
21
|
+
/** Role-labelled public history only; never serialize source/replay/tool internals. */
|
|
22
|
+
export async function prepareContinuation(options, input, readImage) {
|
|
23
|
+
const messages = options.messages.filter(message => !isWorkspaceNoticeMessage(message)).map(withoutWorkspaceNotice);
|
|
24
|
+
for (const message of messages)
|
|
25
|
+
if (!['user', 'assistant', 'system', 'developer'].includes(message.role))
|
|
26
|
+
throw new NeedError('HISTORY_CONTENT_UNSUPPORTED', `此前含有无法可靠承接的历史角色:${message.role};本轮未新建或发送`, '完整公开的用户及助手历史;工具记录的公开内容映射');
|
|
27
|
+
const after = messages.findLastIndex(message => message.role === 'assistant');
|
|
28
|
+
const prompt = [];
|
|
29
|
+
const historyTransfer = { scope: 'visible-user-assistant-text-and-images', textBlocks: 0, imageBlocks: 0, offloadedImagePlaceholders: 0, omittedReasoningBlocks: 0, reasoningOmissionReason: 'dsh-internal-reasoning' };
|
|
30
|
+
for (const message of messages.slice(0, after + 1)) {
|
|
31
|
+
if (message.role === 'system' || message.role === 'developer')
|
|
32
|
+
continue;
|
|
33
|
+
if (message.role !== 'user' && message.role !== 'assistant')
|
|
34
|
+
throw new NeedError('HISTORY_CONTENT_UNSUPPORTED', `此前含有无法可靠承接的历史角色:${message.role};本轮未新建或发送`, '完整公开的用户及助手历史;工具记录的公开内容映射');
|
|
35
|
+
prompt.push({ type: 'text', text: `此前可见对话(历史角色:${message.role};仅为历史记录):\n` });
|
|
36
|
+
for (const block of message.content) {
|
|
37
|
+
// DSH explicitly distinguishes internal thinking from visible user text.
|
|
38
|
+
if (block.type === 'reasoning') {
|
|
39
|
+
historyTransfer.omittedReasoningBlocks++;
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
prompt.push(await publicBlock(block, options, readImage, true));
|
|
43
|
+
if (block.type === 'text')
|
|
44
|
+
historyTransfer.textBlocks++;
|
|
45
|
+
else if (block.type === 'image') {
|
|
46
|
+
if (block.offloaded)
|
|
47
|
+
historyTransfer.offloadedImagePlaceholders++;
|
|
48
|
+
else
|
|
49
|
+
historyTransfer.imageBlocks++;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
prompt.push({ type: 'text', text: `\n(历史角色:${message.role} 的记录结束)\n` });
|
|
53
|
+
}
|
|
54
|
+
prompt.push({ type: 'text', text: '此前可见对话结束。以下为当前用户输入:\n' }, ...input.prompt);
|
|
55
|
+
return { ...input, prompt, historyTransfer };
|
|
56
|
+
}
|
|
3
57
|
/** The attachment service supplies verified bytes; client paths never cross SSH. */
|
|
4
58
|
export async function prepareInput(options, readImage) {
|
|
5
59
|
options.signal?.throwIfAborted();
|
|
@@ -10,7 +64,11 @@ export async function prepareInput(options, readImage) {
|
|
|
10
64
|
for (const message of messages) {
|
|
11
65
|
if (message.role !== 'system' && message.role !== 'developer')
|
|
12
66
|
continue;
|
|
13
|
-
const parts = message.content.map(block =>
|
|
67
|
+
const parts = message.content.map(block => {
|
|
68
|
+
if (block.type !== 'text')
|
|
69
|
+
throw new NeedError('SYSTEM_CONTEXT_TYPE_UNSUPPORTED', '系统或开发者要求包含暂不支持的非文本内容,本轮未新建或发送', '系统及开发者仅支持公开text上下文;非文本内容需要已定义的公开ACP投影');
|
|
70
|
+
return block.text;
|
|
71
|
+
});
|
|
14
72
|
if (parts.length)
|
|
15
73
|
context.push((message.role === 'system' ? '系统上下文:' : '开发者上下文:') + '\n' + parts.join('\n'));
|
|
16
74
|
}
|
|
@@ -22,28 +80,8 @@ export async function prepareInput(options, readImage) {
|
|
|
22
80
|
const preparedUsers = [];
|
|
23
81
|
for (const message of users) {
|
|
24
82
|
const before = prompt.length;
|
|
25
|
-
for (const block of message.content)
|
|
26
|
-
|
|
27
|
-
if (block.type === 'text') {
|
|
28
|
-
prompt.push({ type: 'text', text: block.text });
|
|
29
|
-
continue;
|
|
30
|
-
}
|
|
31
|
-
if (block.type === 'image') {
|
|
32
|
-
if (block.offloaded) {
|
|
33
|
-
prompt.push({ type: 'text', text: `图片 ${block.attachment.name ?? block.attachment.attachmentId} 已按官方会话策略移出当前上下文。` });
|
|
34
|
-
continue;
|
|
35
|
-
}
|
|
36
|
-
if (!readImage)
|
|
37
|
-
throw new NeedError('ATTACHMENT_REQUIRED', '我需要官方附件组件读取这张图片', '官方attachments.readImage');
|
|
38
|
-
const image = await readImage(block.attachment, options.signal);
|
|
39
|
-
options.signal?.throwIfAborted();
|
|
40
|
-
if (!image || !(image.data instanceof Uint8Array) || image.ref?.attachmentId !== block.attachment.attachmentId || !['image/png', 'image/jpeg', 'image/webp', 'image/gif'].includes(image.ref.mediaType) || image.ref.bytes !== image.data.byteLength)
|
|
41
|
-
throw new NeedError('ATTACHMENT_INVALID', '图片附件没有返回匹配的已核验内容', '官方核验的图片字节与媒体类型');
|
|
42
|
-
prompt.push({ type: 'image', mimeType: image.ref.mediaType, data: Buffer.from(image.data).toString('base64') });
|
|
43
|
-
continue;
|
|
44
|
-
}
|
|
45
|
-
throw new NeedError('INPUT_TYPE_REQUIRED', `我需要此输入类型的远程转接能力:${block.type}`, '对应类型的公开ACP输入映射');
|
|
46
|
-
}
|
|
83
|
+
for (const block of message.content)
|
|
84
|
+
prompt.push(await publicBlock(block, options, readImage));
|
|
47
85
|
preparedUsers.push({ id: message.id, prompt: prompt.slice(before) });
|
|
48
86
|
}
|
|
49
87
|
if (!prompt.length)
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { RuntimeRequest } from './provider.ts';
|
|
2
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
3
|
+
export interface LocalTurnProof {
|
|
4
|
+
expectedUserTurn: {
|
|
5
|
+
turn: number;
|
|
6
|
+
userMessageId: string;
|
|
7
|
+
};
|
|
8
|
+
startSequence: number;
|
|
9
|
+
userMessageSequence: number;
|
|
10
|
+
assertCurrent(): void;
|
|
11
|
+
}
|
|
12
|
+
/** CAS expectation only. L independently establishes live human-turn/brand evidence. */
|
|
13
|
+
export declare function readLocalUserTurn(ctx: Context, request: {
|
|
14
|
+
sessionId: string;
|
|
15
|
+
route: string;
|
|
16
|
+
userMessageIds: readonly string[];
|
|
17
|
+
}): Promise<LocalTurnProof>;
|
|
18
|
+
/** Only metadata observed live since this plugin mounted; never seed or persisted tails. */
|
|
19
|
+
export declare function createLocalUserTurnObserver(ctx: Pick<Context, 'get' | 'on'>): {
|
|
20
|
+
current(request: Pick<RuntimeRequest, "sessionId" | "userInputIds">): {
|
|
21
|
+
turn: number;
|
|
22
|
+
userMessageId: string;
|
|
23
|
+
};
|
|
24
|
+
};
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { SessionId } from '@deepseek-ai/dsh-session';
|
|
2
|
+
import { isDeepStrictEqual } from 'node:util';
|
|
3
|
+
import { NeedError } from "./remote.js";
|
|
4
|
+
const failure = (code = 'REMOTE_DETAIL_SCOPE_INVALID') => new NeedError(code, '本轮输入无法核对,已停止本次处理。', '官方当前员工输入与所选远程品牌');
|
|
5
|
+
/** CAS expectation only. L independently establishes live human-turn/brand evidence. */
|
|
6
|
+
export async function readLocalUserTurn(ctx, request) {
|
|
7
|
+
request = structuredClone(request);
|
|
8
|
+
const id = SessionId(request.sessionId), sessions = ctx.get('sessions'), session = sessions?.get(id);
|
|
9
|
+
const persistence = ctx.get('sessionPersistence');
|
|
10
|
+
if (!session || typeof sessions?.flush !== 'function' || !persistence?.identity || typeof persistence.open !== 'function' || !request.userMessageIds.length)
|
|
11
|
+
throw failure();
|
|
12
|
+
const watermark = session.seq, header = structuredClone(session.header), persistenceIdentity = persistence.identity;
|
|
13
|
+
const assertCurrent = () => { if (ctx.get('sessions')?.get(id) !== session || ctx.get('sessionPersistence')?.identity !== persistenceIdentity || session.seq !== watermark || !isDeepStrictEqual(session.header, header))
|
|
14
|
+
throw failure('LOCAL_USER_TURN_CHANGED'); };
|
|
15
|
+
assertCurrent();
|
|
16
|
+
if (!await sessions.flush(session))
|
|
17
|
+
throw failure();
|
|
18
|
+
assertCurrent();
|
|
19
|
+
const handle = await persistence.open(id, 'read');
|
|
20
|
+
let proof, firstFailure;
|
|
21
|
+
try {
|
|
22
|
+
assertCurrent();
|
|
23
|
+
if (handle.id !== id || handle.access !== 'read' || !isDeepStrictEqual(handle.header, header) || handle.inheritedEventCount !== session.inheritedEventCount)
|
|
24
|
+
throw failure();
|
|
25
|
+
const { events } = await handle.read();
|
|
26
|
+
assertCurrent();
|
|
27
|
+
if (!Array.isArray(events) || events.length !== watermark)
|
|
28
|
+
throw failure();
|
|
29
|
+
let turn, step, input, route, routeContext;
|
|
30
|
+
for (let i = 0; i < events.length; i++) {
|
|
31
|
+
const e = events[i];
|
|
32
|
+
if (e.seq !== i)
|
|
33
|
+
throw failure();
|
|
34
|
+
if (e.type === 'turn/start') {
|
|
35
|
+
turn = e;
|
|
36
|
+
step = undefined;
|
|
37
|
+
input = undefined;
|
|
38
|
+
}
|
|
39
|
+
else if (e.type === 'turn/end') {
|
|
40
|
+
turn = undefined;
|
|
41
|
+
step = undefined;
|
|
42
|
+
input = undefined;
|
|
43
|
+
}
|
|
44
|
+
else if (e.type === 'step/start') {
|
|
45
|
+
step = e;
|
|
46
|
+
input = undefined;
|
|
47
|
+
}
|
|
48
|
+
else if (e.type === 'step/end') {
|
|
49
|
+
step = undefined;
|
|
50
|
+
input = undefined;
|
|
51
|
+
}
|
|
52
|
+
else if (e.type === 'user/message' && step && e.data.source.kind === 'user')
|
|
53
|
+
input = e;
|
|
54
|
+
else if (e.type === 'request/header')
|
|
55
|
+
route = e.data.header.config.provider;
|
|
56
|
+
else if (e.type === 'request/context')
|
|
57
|
+
routeContext = e.data.provider;
|
|
58
|
+
}
|
|
59
|
+
if (!turn || !step || step.data.turn !== turn.data.turn || !input || route !== request.route || routeContext !== undefined && routeContext !== request.route || !session.isOwnSeq(turn.seq) || !session.isOwnSeq(step.seq) || !session.isOwnSeq(input.seq) || !request.userMessageIds.includes(input.data.id))
|
|
60
|
+
throw failure();
|
|
61
|
+
proof = { expectedUserTurn: { turn: turn.data.turn, userMessageId: input.data.id }, startSequence: turn.seq, userMessageSequence: input.seq };
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
firstFailure = error;
|
|
65
|
+
}
|
|
66
|
+
try {
|
|
67
|
+
await handle.close();
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
if (firstFailure instanceof NeedError)
|
|
71
|
+
throw Object.assign(new NeedError(firstFailure.code, firstFailure.message, firstFailure.requires + ';持久句柄关闭失败'), { cause: new AggregateError([firstFailure, error], 'LOCAL_TURN_READ_AND_CLOSE_FAILED') });
|
|
72
|
+
if (firstFailure !== undefined)
|
|
73
|
+
throw new AggregateError([firstFailure, error], 'LOCAL_TURN_READ_AND_CLOSE_FAILED');
|
|
74
|
+
throw error;
|
|
75
|
+
}
|
|
76
|
+
if (firstFailure !== undefined)
|
|
77
|
+
throw firstFailure;
|
|
78
|
+
assertCurrent();
|
|
79
|
+
return { ...proof, assertCurrent };
|
|
80
|
+
}
|
|
81
|
+
/** Only metadata observed live since this plugin mounted; never seed or persisted tails. */
|
|
82
|
+
export function createLocalUserTurnObserver(ctx) {
|
|
83
|
+
const current = new WeakMap();
|
|
84
|
+
ctx.on('session/event', (session, event) => {
|
|
85
|
+
if (event.type === 'turn/start')
|
|
86
|
+
current.set(session, { turn: event.data.turn });
|
|
87
|
+
else if (event.type === 'turn/end')
|
|
88
|
+
current.delete(session);
|
|
89
|
+
else if (event.type === 'user/message' && event.data.source.kind === 'user') {
|
|
90
|
+
const turn = current.get(session);
|
|
91
|
+
if (turn)
|
|
92
|
+
turn.userMessageId = event.data.id;
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
ctx.on('session/disposed', session => current.delete(session));
|
|
96
|
+
return { current(request) {
|
|
97
|
+
const session = request.sessionId ? ctx.get('sessions')?.get(SessionId(request.sessionId)) : undefined;
|
|
98
|
+
const metadata = session ? current.get(session) : undefined;
|
|
99
|
+
if (!metadata || !Number.isSafeInteger(metadata.turn) || metadata.turn < 0 || !metadata.userMessageId || !request.userInputIds?.includes(metadata.userMessageId))
|
|
100
|
+
throw new NeedError('LOCAL_USER_TURN_UNCONFIRMED', '无法核对本次员工输入,本轮已停止。', '本插件启用后观察到的官方真实员工输入');
|
|
101
|
+
return { turn: metadata.turn, userMessageId: metadata.userMessageId };
|
|
102
|
+
} };
|
|
103
|
+
}
|
package/dist/model-catalog.d.ts
CHANGED
|
@@ -28,7 +28,19 @@ export interface ModelDirectory {
|
|
|
28
28
|
read(agent: AgentDefinition, signal?: AbortSignal): Promise<CatalogLease>;
|
|
29
29
|
}
|
|
30
30
|
export type CatalogRunner = (target: ConnectTarget, command: string, signal: AbortSignal, maxBytes: number) => Promise<string>;
|
|
31
|
-
export
|
|
31
|
+
export interface CatalogIssue {
|
|
32
|
+
readonly modelName: string;
|
|
33
|
+
readonly reason: string;
|
|
34
|
+
readonly code: string;
|
|
35
|
+
}
|
|
36
|
+
export interface ParsedCatalog {
|
|
37
|
+
readonly models: readonly HostModel[];
|
|
38
|
+
readonly issues: readonly CatalogIssue[];
|
|
39
|
+
}
|
|
40
|
+
export declare function catalogDiagnostic(issues: readonly CatalogIssue[]): string | undefined;
|
|
41
|
+
/** Defaults are provider declarations, never catalog order or display names. */
|
|
42
|
+
export declare function declaredDefault(agent: AgentDefinition, models: readonly HostModel[]): HostModel | undefined;
|
|
43
|
+
export declare function parseHostCatalog(input: string, provider: string, username: string): ParsedCatalog;
|
|
32
44
|
/** Only the public L sshArgs function chooses SSH trust and transport arguments. */
|
|
33
45
|
export declare const runCatalog: CatalogRunner;
|
|
34
46
|
/** No cache: every query binds to L's current object, host-user and generation. */
|
|
@@ -37,6 +49,8 @@ export declare class ModelCatalog implements ModelDirectory {
|
|
|
37
49
|
private maxBytes;
|
|
38
50
|
private run;
|
|
39
51
|
private changed;
|
|
52
|
+
private diagnostic;
|
|
53
|
+
private loaded;
|
|
40
54
|
private api?;
|
|
41
55
|
private unsubscribe?;
|
|
42
56
|
private signature?;
|
|
@@ -44,7 +58,9 @@ export declare class ModelCatalog implements ModelDirectory {
|
|
|
44
58
|
private disposed;
|
|
45
59
|
private initialized;
|
|
46
60
|
private active;
|
|
47
|
-
|
|
61
|
+
private serial;
|
|
62
|
+
private latest;
|
|
63
|
+
constructor(get: () => ConnectApi | undefined, maxBytes: () => number | undefined, run?: CatalogRunner, changed?: () => void, diagnostic?: (provider: 'claude' | 'codex', message: string | undefined) => void, loaded?: (agent: AgentDefinition, models: readonly HostModel[], assertCurrent: () => void) => void | Promise<void>);
|
|
48
64
|
refresh(): void;
|
|
49
65
|
private trusted;
|
|
50
66
|
read(agent: AgentDefinition, signal?: AbortSignal): Promise<CatalogLease>;
|
package/dist/model-catalog.js
CHANGED
|
@@ -13,12 +13,25 @@ import { ModelConfigurationError } from "./model-configuration.js";
|
|
|
13
13
|
import { sanitizeObservation } from "./conversation-details.js";
|
|
14
14
|
const object = (value) => !!value && typeof value === 'object' && !Array.isArray(value);
|
|
15
15
|
const text = (value) => typeof value === 'string' && value.trim().length > 0;
|
|
16
|
-
|
|
16
|
+
class CatalogError extends LlmError {
|
|
17
|
+
diagnostic;
|
|
18
|
+
constructor(reason, diagnostic) { super('无法获取模型列表:' + reason, 'MODEL_CATALOG_UNAVAILABLE'); this.diagnostic = diagnostic + '(诊断代码:MODEL_CATALOG_UNAVAILABLE)'; }
|
|
19
|
+
}
|
|
20
|
+
const safe = (value) => sanitizeObservation({ kind: 'session', severity: 'warning', code: 'MODEL_CATALOG_DIAGNOSTIC', summary: value, payload: {} }).summary;
|
|
21
|
+
function fail(reason, diagnostic = reason) { throw new CatalogError(reason, diagnostic); }
|
|
17
22
|
function freeze(value) { if (value && typeof value === 'object') {
|
|
18
23
|
for (const item of Object.values(value))
|
|
19
24
|
freeze(item);
|
|
20
25
|
Object.freeze(value);
|
|
21
26
|
} return value; }
|
|
27
|
+
export function catalogDiagnostic(issues) {
|
|
28
|
+
return issues.length ? '以下模型未加入可选列表:\n' + issues.map(issue => `模型「${issue.modelName}」:${issue.reason}(诊断代码:${issue.code})`).join('\n') : undefined;
|
|
29
|
+
}
|
|
30
|
+
/** Defaults are provider declarations, never catalog order or display names. */
|
|
31
|
+
export function declaredDefault(agent, models) {
|
|
32
|
+
const selected = models.filter(model => agent.loginProvider === 'claude' ? model.value === 'default' : object(model.source) && model.source.isDefault === true);
|
|
33
|
+
return selected.length === 1 ? selected[0] : undefined;
|
|
34
|
+
}
|
|
22
35
|
export function parseHostCatalog(input, provider, username) {
|
|
23
36
|
let wire;
|
|
24
37
|
try {
|
|
@@ -32,36 +45,60 @@ export function parseHostCatalog(input, provider, username) {
|
|
|
32
45
|
if (wire.ok === false) {
|
|
33
46
|
const error = object(wire.error) ? wire.error : undefined;
|
|
34
47
|
const code = typeof error?.code === 'string' && /^[A-Z][A-Z0-9_]*$/.test(error.code) ? error.code : 'PROTOCOL_INVALID';
|
|
35
|
-
const reason = text(error?.message) ?
|
|
36
|
-
fail('
|
|
48
|
+
const reason = text(error?.message) ? safe(error.message) : '主机未提供失败说明';
|
|
49
|
+
fail('主机目录查询失败,请到设置 → 模型查看诊断。', `主机目录查询失败(诊断代码:${code}):${reason}`);
|
|
37
50
|
}
|
|
38
51
|
if (wire.ok !== true || wire.permissionProbe !== false || !object(wire.subject) || wire.subject.username !== username || !Number.isSafeInteger(wire.subject.uid) || !text(wire.observedAt) || !object(wire.source) || !object(wire.settings) || !Array.isArray(wire.models))
|
|
39
52
|
fail('主机目录缺少完整元数据');
|
|
40
|
-
const ids = new
|
|
41
|
-
for (const model of wire.models)
|
|
42
|
-
if (
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
53
|
+
const ids = new Map(), values = new Map();
|
|
54
|
+
for (const model of wire.models)
|
|
55
|
+
if (object(model)) {
|
|
56
|
+
if (text(model.id))
|
|
57
|
+
ids.set(model.id, (ids.get(model.id) ?? 0) + 1);
|
|
58
|
+
if (text(model.value))
|
|
59
|
+
values.set(model.value, (values.get(model.value) ?? 0) + 1);
|
|
60
|
+
}
|
|
61
|
+
const models = [], issues = [];
|
|
62
|
+
for (const [index, model] of wire.models.entries()) {
|
|
63
|
+
const modelName = object(model) && text(model.name) ? safe(model.name) : object(model) && text(model.id) ? safe(model.id) : `第 ${index + 1} 项(缺少模型名称)`;
|
|
64
|
+
const reject = (reason, code = 'MODEL_CATALOG_ITEM_INVALID') => issues.push({ modelName, reason, code });
|
|
65
|
+
if (!object(model) || !text(model.id) || !text(model.value) || !text(model.name) || (model.resolvedModel !== null && !text(model.resolvedModel)) || !object(model.source) || !object(model.reasoning)) {
|
|
66
|
+
reject('模型标识、名称或能力资料不完整');
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
if ((ids.get(model.id) ?? 0) > 1 || (values.get(model.value) ?? 0) > 1) {
|
|
70
|
+
reject('模型标识或发送值重复,无法确定对应关系', 'MODEL_CATALOG_DUPLICATE');
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
if (provider === 'codex' && model.resolvedModel !== model.value) {
|
|
74
|
+
reject('具体型号与发送模型值不一致');
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
50
77
|
const r = model.reasoning;
|
|
51
|
-
if (!Array.isArray(r.options) || typeof r.defaultKnown !== 'boolean' || (r.defaultValue !== null && !text(r.defaultValue)) || (!r.defaultKnown && r.defaultValue !== null))
|
|
52
|
-
|
|
78
|
+
if (!Array.isArray(r.options) || typeof r.defaultKnown !== 'boolean' || (r.defaultValue !== null && !text(r.defaultValue)) || (!r.defaultKnown && r.defaultValue !== null)) {
|
|
79
|
+
reject('推理档位或默认档位资料无效', 'MODEL_REASONING_INVALID');
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
53
82
|
const efforts = new Set();
|
|
83
|
+
let invalid = false;
|
|
54
84
|
for (const effort of r.options) {
|
|
55
|
-
if (!object(effort) || !text(effort.value) || !text(effort.name) || efforts.has(effort.value))
|
|
56
|
-
|
|
85
|
+
if (!object(effort) || !text(effort.value) || !text(effort.name) || efforts.has(effort.value)) {
|
|
86
|
+
invalid = true;
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
57
89
|
efforts.add(effort.value);
|
|
58
90
|
}
|
|
59
|
-
if (
|
|
60
|
-
|
|
91
|
+
if (invalid) {
|
|
92
|
+
reject('推理档位缺少名称、值无效或重复', 'MODEL_REASONING_INVALID');
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
if (r.defaultKnown && (r.defaultValue === null || !efforts.has(r.defaultValue))) {
|
|
96
|
+
reject('默认推理档位不在该模型提供的选项中', 'MODEL_REASONING_DEFAULT_INVALID');
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
models.push(model);
|
|
61
100
|
}
|
|
62
|
-
|
|
63
|
-
fail('主机返回的完整模型目录为空');
|
|
64
|
-
return freeze(structuredClone(wire.models));
|
|
101
|
+
return freeze(structuredClone({ models, issues }));
|
|
65
102
|
}
|
|
66
103
|
/** Only the public L sshArgs function chooses SSH trust and transport arguments. */
|
|
67
104
|
export const runCatalog = async (target, command, signal, maxBytes) => {
|
|
@@ -141,6 +178,8 @@ export class ModelCatalog {
|
|
|
141
178
|
maxBytes;
|
|
142
179
|
run;
|
|
143
180
|
changed;
|
|
181
|
+
diagnostic;
|
|
182
|
+
loaded;
|
|
144
183
|
api;
|
|
145
184
|
unsubscribe;
|
|
146
185
|
signature;
|
|
@@ -148,11 +187,15 @@ export class ModelCatalog {
|
|
|
148
187
|
disposed = false;
|
|
149
188
|
initialized = false;
|
|
150
189
|
active = new Set();
|
|
151
|
-
|
|
190
|
+
serial = 0;
|
|
191
|
+
latest = new Map();
|
|
192
|
+
constructor(get, maxBytes, run = runCatalog, changed = () => { }, diagnostic = () => { }, loaded = () => { }) {
|
|
152
193
|
this.get = get;
|
|
153
194
|
this.maxBytes = maxBytes;
|
|
154
195
|
this.run = run;
|
|
155
196
|
this.changed = changed;
|
|
197
|
+
this.diagnostic = diagnostic;
|
|
198
|
+
this.loaded = loaded;
|
|
156
199
|
this.refresh();
|
|
157
200
|
}
|
|
158
201
|
refresh() {
|
|
@@ -183,9 +226,12 @@ export class ModelCatalog {
|
|
|
183
226
|
this.signature = signature;
|
|
184
227
|
if (changed) {
|
|
185
228
|
this.revision++;
|
|
229
|
+
this.latest.clear();
|
|
186
230
|
for (const controller of this.active)
|
|
187
231
|
controller.abort();
|
|
188
232
|
this.active.clear();
|
|
233
|
+
this.diagnostic('claude', undefined);
|
|
234
|
+
this.diagnostic('codex', undefined);
|
|
189
235
|
this.changed();
|
|
190
236
|
}
|
|
191
237
|
}
|
|
@@ -198,7 +244,7 @@ export class ModelCatalog {
|
|
|
198
244
|
fail('当前没有已验证的远程主机连接');
|
|
199
245
|
const result = api.getModelCatalogTarget(agent.loginProvider);
|
|
200
246
|
if (!result.ok)
|
|
201
|
-
fail(result.error.message);
|
|
247
|
+
fail('远程主机连接暂未提供可信的模型目录目标,请到设置 → 模型查看诊断。', `模型目录目标不可用(诊断代码:${safe(result.error.code)}):${safe(result.error.message)}`);
|
|
202
248
|
const target = result.value;
|
|
203
249
|
if (target.version !== 1 || target.purpose !== 'model-catalog' || target.scope !== 'host-user' || target.provider !== agent.loginProvider || target.instanceId !== state.instanceId || target.epoch !== state.epoch)
|
|
204
250
|
fail('模型目录目标与当前连接或品牌不匹配');
|
|
@@ -212,43 +258,62 @@ export class ModelCatalog {
|
|
|
212
258
|
}
|
|
213
259
|
async read(agent, signal) {
|
|
214
260
|
let controller;
|
|
261
|
+
let api;
|
|
262
|
+
let revision = this.revision;
|
|
263
|
+
const serial = ++this.serial;
|
|
264
|
+
this.latest.set(agent.loginProvider, serial);
|
|
265
|
+
const current = () => !this.disposed && revision === this.revision && api === this.get() && this.latest.get(agent.loginProvider) === serial;
|
|
266
|
+
let reported = false;
|
|
267
|
+
const report = (message) => { if (current()) {
|
|
268
|
+
reported = true;
|
|
269
|
+
this.diagnostic(agent.loginProvider, message);
|
|
270
|
+
} };
|
|
215
271
|
try {
|
|
216
272
|
if (this.disposed)
|
|
217
273
|
fail('远程模型插件已关闭');
|
|
218
274
|
this.refresh();
|
|
219
|
-
|
|
275
|
+
api = this.get();
|
|
276
|
+
revision = this.revision;
|
|
220
277
|
if (!api)
|
|
221
278
|
fail('远程主机连接组件没有装好');
|
|
222
|
-
|
|
279
|
+
// refresh may clear reads from the previous connection generation.
|
|
280
|
+
this.latest.set(agent.loginProvider, serial);
|
|
281
|
+
const trustedApi = api, target = freeze(this.trusted(agent, trustedApi));
|
|
223
282
|
const max = this.maxBytes();
|
|
224
283
|
if (!Number.isSafeInteger(max) || max < 1)
|
|
225
284
|
fail('acpMaxMessageBytes 设置无效');
|
|
226
285
|
controller = new AbortController();
|
|
227
286
|
this.active.add(controller);
|
|
228
287
|
const combined = signal ? AbortSignal.any([signal, controller.signal]) : controller.signal;
|
|
229
|
-
const assertCurrent = () => { if (combined.aborted || this.disposed || this.get() !==
|
|
230
|
-
fail('连接已改变,原模型目录已失效'); const current = this.trusted(agent,
|
|
288
|
+
const assertCurrent = () => { if (combined.aborted || this.disposed || this.get() !== trustedApi || revision !== this.revision)
|
|
289
|
+
fail('连接已改变,原模型目录已失效'); const current = this.trusted(agent, trustedApi); if (current.instanceId !== target.instanceId || current.epoch !== target.epoch || current.provider !== target.provider || !sameTarget(current.target, target.target))
|
|
231
290
|
fail('可信目标或连接代次已改变,原模型目录已失效'); };
|
|
232
291
|
assertCurrent();
|
|
233
292
|
const output = await this.run(target.target, '/usr/local/aw-host/bin/aw-model-catalog --provider ' + agent.loginProvider, combined, max);
|
|
234
293
|
assertCurrent();
|
|
235
|
-
const
|
|
294
|
+
const parsed = parseHostCatalog(output, agent.loginProvider, target.target.username), diagnostic = catalogDiagnostic(parsed.issues);
|
|
295
|
+
if (!parsed.models.length)
|
|
296
|
+
fail('当前没有可选模型,请到设置 → 模型查看诊断。', diagnostic ?? '主机返回的完整模型目录为空(诊断代码:MODEL_CATALOG_EMPTY)');
|
|
297
|
+
report(diagnostic);
|
|
298
|
+
if (current())
|
|
299
|
+
await this.loaded(agent, parsed.models, assertCurrent);
|
|
300
|
+
assertCurrent();
|
|
236
301
|
const own = controller;
|
|
237
|
-
return { target, models, signal: combined, assertCurrent, release: () => this.active.delete(own) };
|
|
302
|
+
return { target, models: parsed.models, signal: combined, assertCurrent, release: () => this.active.delete(own) };
|
|
238
303
|
}
|
|
239
304
|
catch (error) {
|
|
240
305
|
if (controller)
|
|
241
306
|
this.active.delete(controller);
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
fail('目录查询失败,根因未查明');
|
|
307
|
+
const failure = error instanceof CatalogError ? error : new CatalogError(error instanceof ModelConfigurationError ? error.message.replace(/^无法获取模型列表:/, '') : '目录查询失败,请到设置 → 模型查看诊断。', error instanceof Error ? `${error instanceof LlmError ? error.code : 'MODEL_CATALOG_QUERY_FAILED'}:${safe(error.message)}` : 'MODEL_CATALOG_QUERY_FAILED:目录查询失败,根因未查明');
|
|
308
|
+
if (!reported)
|
|
309
|
+
report(failure.diagnostic);
|
|
310
|
+
throw failure;
|
|
247
311
|
}
|
|
248
312
|
}
|
|
249
313
|
dispose() { this.disposed = true; this.unsubscribe?.(); for (const controller of this.active)
|
|
250
314
|
controller.abort(); this.active.clear(); }
|
|
251
315
|
}
|
|
252
316
|
export function modelInfo(provider, model) {
|
|
253
|
-
|
|
317
|
+
// Official rc.2 represents no reasoning levels by absence, and rejects an empty efforts array.
|
|
318
|
+
return { provider, id: model.id, name: model.name, inputModalities: ['text', 'image'], ...(model.reasoning.options.length ? { reasoning: { efforts: model.reasoning.options.map(option => ({ id: ReasoningEffortId(option.value), name: option.name })), ...(model.reasoning.defaultKnown && model.reasoning.defaultValue !== null ? { defaultEffort: ReasoningEffortId(model.reasoning.defaultValue) } : {}) } } : {}), catalog: model };
|
|
254
319
|
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { AgentDefinition } from './agents.ts';
|
|
2
|
+
import { type HostModel } from './model-catalog.ts';
|
|
3
|
+
export interface DefaultSelection {
|
|
4
|
+
provider: string;
|
|
5
|
+
model: string;
|
|
6
|
+
reasoningEffort?: string;
|
|
7
|
+
}
|
|
8
|
+
/** Public agentDefaultModel service; writes below use the atomic config editor. */
|
|
9
|
+
export interface DefaultModelService {
|
|
10
|
+
currentSelection(): DefaultSelection;
|
|
11
|
+
}
|
|
12
|
+
interface DefaultEntry {
|
|
13
|
+
options: {
|
|
14
|
+
name?: string;
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
/** Public rc.2 configEditor.entries/edit subset; no ownerContext/private config access. */
|
|
18
|
+
export interface DefaultConfigEditor {
|
|
19
|
+
entries(): readonly DefaultEntry[];
|
|
20
|
+
edit(entry: DefaultEntry, change: (current: Record<string, unknown>, inherited: Record<string, unknown>) => Record<string, unknown>): Promise<void>;
|
|
21
|
+
}
|
|
22
|
+
export type DefaultSelectionWriter = (expected: DefaultSelection, next: DefaultSelection, assertCurrent: () => void) => Promise<void>;
|
|
23
|
+
/** The editor invokes the guard synchronously under its profile lock after reconciliation. */
|
|
24
|
+
export declare function createDefaultSelectionWriter(service: DefaultModelService, getEditor: () => DefaultConfigEditor | undefined): DefaultSelectionWriter;
|
|
25
|
+
export declare function createModelDefaults(service: DefaultModelService, report: (provider: string, message: string | undefined) => void, write?: DefaultSelectionWriter): {
|
|
26
|
+
observe(agent: AgentDefinition, models: readonly HostModel[], assertCurrent?: () => void): Promise<void>;
|
|
27
|
+
dispose(): void;
|
|
28
|
+
};
|
|
29
|
+
export {};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { declaredDefault } from "./model-catalog.js";
|
|
2
|
+
const same = (a, b) => a.provider === b.provider && a.model === b.model && a.reasoningEffort === b.reasoningEffort;
|
|
3
|
+
/** The editor invokes the guard synchronously under its profile lock after reconciliation. */
|
|
4
|
+
export function createDefaultSelectionWriter(service, getEditor) {
|
|
5
|
+
return async (expected, next, assertCurrent) => {
|
|
6
|
+
const editor = getEditor();
|
|
7
|
+
const entries = editor?.entries().filter(entry => entry.options.name === '@deepseek-ai/dsh-agent-default-model');
|
|
8
|
+
if (!editor || entries?.length !== 1)
|
|
9
|
+
throw Error('缺少唯一可原子编辑的官方默认模型设置');
|
|
10
|
+
await editor.edit(entries[0], current => {
|
|
11
|
+
assertCurrent();
|
|
12
|
+
if (!same(expected, service.currentSelection()))
|
|
13
|
+
throw Error('员工已改变模型选择,本次默认更新已撤回');
|
|
14
|
+
const updated = { ...current, provider: next.provider, model: next.model };
|
|
15
|
+
Reflect.deleteProperty(updated, 'reasoningEffort');
|
|
16
|
+
if (next.reasoningEffort !== undefined)
|
|
17
|
+
Object.assign(updated, { reasoningEffort: next.reasoningEffort });
|
|
18
|
+
return updated;
|
|
19
|
+
});
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
export function createModelDefaults(service, report, write) {
|
|
23
|
+
let disposed = false, queue = Promise.resolve();
|
|
24
|
+
return {
|
|
25
|
+
observe(agent, models, assertCurrent = () => { }) {
|
|
26
|
+
const snapshot = { ...service.currentSelection() };
|
|
27
|
+
const job = queue.then(async () => {
|
|
28
|
+
if (disposed || snapshot.provider !== agent.id || !same(snapshot, service.currentSelection()))
|
|
29
|
+
return;
|
|
30
|
+
if (snapshot.model !== 'remote' && models.some(model => model.id === snapshot.model)) {
|
|
31
|
+
report(agent.id, undefined);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
const model = declaredDefault(agent, models);
|
|
35
|
+
const notice = model ? undefined : '提供方没有声明唯一的默认模型,请先选择模型。(诊断代码:MODEL_DEFAULT_UNAVAILABLE)';
|
|
36
|
+
const next = { provider: agent.id, model: model?.id ?? '', ...(model?.reasoning.defaultKnown && model.reasoning.defaultValue !== null ? { reasoningEffort: model.reasoning.defaultValue } : {}) };
|
|
37
|
+
try {
|
|
38
|
+
const guard = () => { assertCurrent(); if (disposed)
|
|
39
|
+
throw Error('默认设置消费已撤回'); };
|
|
40
|
+
guard();
|
|
41
|
+
if (!same(snapshot, next)) {
|
|
42
|
+
if (!write)
|
|
43
|
+
throw Error('缺少原子配置能力');
|
|
44
|
+
await write(snapshot, next, guard);
|
|
45
|
+
}
|
|
46
|
+
if (disposed)
|
|
47
|
+
return;
|
|
48
|
+
if (!same(next, service.currentSelection()))
|
|
49
|
+
throw Error('未确认官方默认模型设置已保存');
|
|
50
|
+
report(agent.id, notice);
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
if (!disposed)
|
|
54
|
+
report(agent.id, (notice ? notice + '\n' : '') + '未能确认默认模型设置已更新,员工新选择不会被迟到的自动更新覆盖;请在模型菜单选择当前目录中的模型。(诊断代码:MODEL_DEFAULT_SAVE_FAILED)');
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
queue = job;
|
|
58
|
+
return job;
|
|
59
|
+
},
|
|
60
|
+
dispose() { disposed = true; },
|
|
61
|
+
};
|
|
62
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
2
|
+
export interface ModelInputRetentionOptions {
|
|
3
|
+
isOurProvider: (provider: string) => boolean;
|
|
4
|
+
diagnostic: (provider: string, message: string | undefined) => void;
|
|
5
|
+
}
|
|
6
|
+
/** Mark only at the actual X adapter's preparation catch, then rethrow the same
|
|
7
|
+
* error. One error object can prove only its first preparation, for that signal.
|
|
8
|
+
* Public request config is an intermediate proposal and is not route evidence. */
|
|
9
|
+
export declare function markModelPreparationFailure(provider: string, error: unknown, signal?: AbortSignal): void;
|
|
10
|
+
/**
|
|
11
|
+
* Retain a claimed human batch only after a known, undispatched metadata prepare
|
|
12
|
+
* failure. Restoration uses the public durable inbox without sending or waking
|
|
13
|
+
* the agent. This cannot cover errors before pre-step/request, or unknown core
|
|
14
|
+
* admission failures; it never manufactures transcript messages or model routes.
|
|
15
|
+
*/
|
|
16
|
+
export declare function installModelInputRetention(ctx: Context, options: ModelInputRetentionOptions): void;
|