lark-remote 0.5.3 → 0.5.5
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/CHANGELOG.md +38 -0
- package/README.en.md +1 -1
- package/README.md +1 -1
- package/dist/bridge/approval-coordinator.d.ts +18 -1
- package/dist/bridge/approval-coordinator.js +57 -10
- package/dist/bridge/index.js +90 -17
- package/dist/card/approval-render.js +18 -0
- package/dist/card/run-state.js +10 -5
- package/dist/config/index.d.ts +16 -0
- package/dist/config/index.js +46 -6
- package/dist/config/opencode-config.d.ts +3 -3
- package/dist/config/opencode-config.js +38 -22
- package/dist/inbound/authorize.d.ts +34 -0
- package/dist/inbound/authorize.js +31 -0
- package/dist/index.js +116 -63
- package/dist/instance-lock.d.ts +9 -0
- package/dist/instance-lock.js +113 -42
- package/dist/router/config/dsh.d.ts +51 -21
- package/dist/router/config/dsh.js +255 -66
- package/dist/router/config/opencode.js +16 -6
- package/dist/router/config/types.d.ts +6 -3
- package/dist/router/index.d.ts +1 -0
- package/dist/router/index.js +13 -5
- package/dist/runner/codex/app-server/translator.js +21 -7
- package/dist/runner/common/acp/base-acp-runner.d.ts +12 -0
- package/dist/runner/common/acp/base-acp-runner.js +83 -8
- package/dist/runner/common/acp/protocol-types.d.ts +10 -2
- package/dist/runner/common/acp/protocol-types.js +3 -1
- package/dist/runner/common/connection-based-runner.d.ts +15 -0
- package/dist/runner/common/connection-based-runner.js +40 -5
- package/dist/runner/common/jsonrpc/connection-manager.d.ts +15 -0
- package/dist/runner/common/jsonrpc/connection-manager.js +83 -50
- package/dist/runner/dsh/approval.d.ts +53 -0
- package/dist/runner/dsh/approval.js +161 -0
- package/dist/runner/dsh/auth.d.ts +36 -0
- package/dist/runner/dsh/auth.js +118 -0
- package/dist/runner/dsh/client.d.ts +161 -42
- package/dist/runner/dsh/client.js +551 -244
- package/dist/runner/dsh/daemon.d.ts +85 -0
- package/dist/runner/dsh/daemon.js +272 -0
- package/dist/runner/dsh/merge.d.ts +9 -0
- package/dist/runner/dsh/merge.js +51 -0
- package/dist/runner/dsh/runner.d.ts +94 -11
- package/dist/runner/dsh/runner.js +553 -67
- package/dist/runner/dsh/translator.d.ts +43 -3
- package/dist/runner/dsh/translator.js +115 -38
- package/dist/runner/dsh/types.d.ts +189 -27
- package/dist/runner/dsh/types.js +11 -5
- package/dist/runner/kimi/acp/runner.js +15 -3
- package/dist/runner/opencode/acp/error-monitor.d.ts +2 -2
- package/dist/runner/opencode/acp/error-monitor.js +2 -2
- package/dist/runner/opencode/acp/runner.d.ts +9 -0
- package/dist/runner/opencode/acp/runner.js +40 -1
- package/dist/runner/opencode/acp/translator.d.ts +4 -2
- package/dist/runner/opencode/acp/translator.js +12 -3
- package/dist/runner/pi/rpc/translator.d.ts +2 -0
- package/dist/runner/pi/rpc/translator.js +27 -1
- package/dist/runner/types.d.ts +34 -0
- package/dist/runner/types.js +24 -0
- package/dist/session/codex/rollout-reader.js +44 -7
- package/dist/session/dsh/sessions.d.ts +45 -6
- package/dist/session/dsh/sessions.js +171 -27
- package/dist/session/opencode/sessions.d.ts +18 -7
- package/dist/session/opencode/sessions.js +294 -15
- package/dist/session/pi/sessions.js +6 -2
- package/package.json +3 -1
|
@@ -1,82 +1,176 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* DshConfigBuilder — /config card fields for the DSH agent.
|
|
3
3
|
*
|
|
4
|
-
* DSH (DeepSeek Harness) is an HTTP-only agent.
|
|
5
|
-
*
|
|
4
|
+
* DSH (DeepSeek Harness) is an HTTP-only agent. The card exposes the Web Host
|
|
5
|
+
* base URL plus preset/provider/model/reasoning-effort knobs. The launch token
|
|
6
|
+
* is a YAML-only fallback and is never rendered or saved through the card.
|
|
6
7
|
*
|
|
7
|
-
*
|
|
8
|
-
* -
|
|
9
|
-
* 预取(Router 调 `prefetch()
|
|
8
|
+
* 清单来源:
|
|
9
|
+
* - 动态清单:打开 /config 前通过 `session/modelCatalog` / `agentPresets/list`
|
|
10
|
+
* 预取(Router 调 `prefetch()`),失败/离线回退固定兜底清单,卡不依赖
|
|
10
11
|
* DSH 服务在线。
|
|
11
|
-
* -
|
|
12
|
-
* 在 handleFieldChange 中被转成 undefined patch(router
|
|
13
|
-
* 不写该键 = 服务端默认。
|
|
12
|
+
* - provider / model / reasoningEffort 用空字符串哨兵表示「跟随默认」:select
|
|
13
|
+
* 的 value `''` 在 handleFieldChange 中被转成 undefined patch(router 删除
|
|
14
|
+
* 键),config.yaml 不写该键 = 服务端默认。
|
|
15
|
+
* - provider 没有哨兵:旧配置没有 provider 时按 `deepseek-official` 生效,
|
|
16
|
+
* 与 runner 的兼容规则一致。provider 清单不硬编码,来自服务端目录。
|
|
14
17
|
*/
|
|
15
18
|
import { DEFAULT_DSH_HOST } from '../../config/index.js';
|
|
16
19
|
import { DshClient } from '../../runner/dsh/client.js';
|
|
17
20
|
import { getLogger } from '../../logger/index.js';
|
|
18
|
-
/**
|
|
21
|
+
/** 旧配置没有 provider 时的生效值(与 DshRunner 的兼容规则一致)。 */
|
|
22
|
+
const LEGACY_PROVIDER = 'deepseek-official';
|
|
23
|
+
/** 固定兜底 preset 清单(DSH 0.2.x 实测;动态拉取失败时使用)。 */
|
|
19
24
|
const FALLBACK_PRESET_OPTIONS = [
|
|
20
25
|
{ text: 'standard(标准模式)', value: 'standard' },
|
|
21
26
|
{ text: 'minimal(极简模式)', value: 'minimal' },
|
|
22
|
-
{ text: '
|
|
27
|
+
{ text: 'ptc(PTC 模式)', value: 'ptc' },
|
|
23
28
|
{ text: 'cordis(创造模式)', value: 'cordis' },
|
|
24
29
|
];
|
|
25
|
-
/** 固定兜底模型清单(DSH
|
|
26
|
-
const FALLBACK_MODEL_OPTIONS = ['deepseek-
|
|
27
|
-
/** 固定兜底推理档位(
|
|
30
|
+
/** 固定兜底模型清单(DSH 0.2.x 实测;动态拉取失败时使用)。 */
|
|
31
|
+
const FALLBACK_MODEL_OPTIONS = ['deepseek-flash', 'deepseek-v4-pro'];
|
|
32
|
+
/** 固定兜底推理档位(session/modelCatalog 实际档位;动态拉取失败时使用)。 */
|
|
28
33
|
const FALLBACK_EFFORT_OPTIONS = ['off', 'low', 'high', 'max'];
|
|
29
|
-
/**
|
|
34
|
+
/** 固定兜底权限预设(DSH 0.2.x 实测;动态拉取失败时使用)。 */
|
|
35
|
+
const FALLBACK_PERMISSION_OPTIONS = [
|
|
36
|
+
{ text: 'read-only(只读)', value: 'read-only' },
|
|
37
|
+
{ text: 'workspace-write(工作区可写)', value: 'workspace-write' },
|
|
38
|
+
{ text: 'danger-full-access(完全访问,关闭审批)', value: 'danger-full-access' },
|
|
39
|
+
];
|
|
40
|
+
/** 「跟随默认」哨兵值:select value 用空串,保存时转 undefined 删除键。 */
|
|
30
41
|
const DEFAULT_SENTINEL = '';
|
|
31
42
|
export class DshConfigBuilder {
|
|
32
|
-
/** 最近一次预取用的 host;host
|
|
43
|
+
/** 最近一次预取用的 host;host 变更时缓存失效。 */
|
|
33
44
|
prefetchHost;
|
|
34
45
|
modelCatalog;
|
|
35
46
|
presets;
|
|
47
|
+
permissionCatalog;
|
|
36
48
|
/** 可注入 client 工厂(测试用);默认生产实现。 */
|
|
37
49
|
clientFactory;
|
|
38
50
|
constructor(clientFactory) {
|
|
39
|
-
this.clientFactory =
|
|
51
|
+
this.clientFactory =
|
|
52
|
+
clientFactory ??
|
|
53
|
+
((base, token, credentialsFile) => new DshClient(base, {
|
|
54
|
+
token,
|
|
55
|
+
credentialsFile,
|
|
56
|
+
// 卡片预取是交互路径:DSH 卡死时最多等 5 秒后回退固定清单。
|
|
57
|
+
requestTimeoutMs: 5_000,
|
|
58
|
+
}));
|
|
40
59
|
}
|
|
41
|
-
/**
|
|
42
|
-
*
|
|
43
|
-
|
|
60
|
+
/**
|
|
61
|
+
* 异步预取模型/预设目录。失败/离线静默回退固定兜底清单,卡不依赖 DSH
|
|
62
|
+
* 服务在线。`force` 用于打开 /config 时强制刷新(在 DSH Web UI 新增
|
|
63
|
+
* provider 后不需要重启 lark-remote)。三个请求并行,最坏等待一个超时。
|
|
64
|
+
*/
|
|
65
|
+
async prefetch(host, token, credentialsFile, options = {}) {
|
|
44
66
|
const base = (host ?? DEFAULT_DSH_HOST).replace(/\/$/, '');
|
|
45
|
-
|
|
67
|
+
const key = `${base}\u0000${token ?? ''}\u0000${credentialsFile ?? ''}`;
|
|
68
|
+
if (!base || (!options.force && key === this.prefetchHost))
|
|
46
69
|
return;
|
|
47
|
-
const client = this.clientFactory(base);
|
|
48
|
-
|
|
49
|
-
|
|
70
|
+
const client = this.clientFactory(base, token, credentialsFile);
|
|
71
|
+
const [models, presets, permissions] = await Promise.allSettled([
|
|
72
|
+
client.listModels(),
|
|
73
|
+
client.listPresets(),
|
|
74
|
+
client.listPermissionPresets(),
|
|
75
|
+
]);
|
|
76
|
+
if (models.status === 'fulfilled') {
|
|
77
|
+
this.modelCatalog = models.value;
|
|
50
78
|
}
|
|
51
|
-
|
|
52
|
-
getLogger().warn(`[dsh-config]
|
|
79
|
+
else {
|
|
80
|
+
getLogger().warn(`[dsh-config] session/modelCatalog prefetch failed (falling back to fixed catalog): ${models.reason.message}`);
|
|
53
81
|
this.modelCatalog = undefined;
|
|
54
82
|
}
|
|
55
|
-
|
|
56
|
-
this.presets =
|
|
83
|
+
if (presets.status === 'fulfilled') {
|
|
84
|
+
this.presets = presets.value;
|
|
57
85
|
}
|
|
58
|
-
|
|
59
|
-
getLogger().warn(`[dsh-config]
|
|
86
|
+
else {
|
|
87
|
+
getLogger().warn(`[dsh-config] agentPresets/list prefetch failed (falling back to fixed presets): ${presets.reason.message}`);
|
|
60
88
|
this.presets = undefined;
|
|
61
89
|
}
|
|
62
|
-
|
|
90
|
+
if (permissions.status === 'fulfilled') {
|
|
91
|
+
this.permissionCatalog = permissions.value;
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
getLogger().warn(`[dsh-config] permissionPresets/catalog prefetch failed (falling back to fixed presets): ${permissions.reason.message}`);
|
|
95
|
+
this.permissionCatalog = undefined;
|
|
96
|
+
}
|
|
97
|
+
this.prefetchHost = key;
|
|
63
98
|
}
|
|
64
99
|
/** 同步注入 catalog/presets(测试用,替代异步 prefetch;标记 prefetchHost 使
|
|
65
100
|
* 后续 prefetch 短路,避免测试误触发网络请求)。 */
|
|
66
|
-
setCatalog(catalog, presets) {
|
|
101
|
+
setCatalog(catalog, presets, permissionCatalog) {
|
|
67
102
|
this.modelCatalog = catalog;
|
|
68
103
|
if (presets)
|
|
69
104
|
this.presets = presets;
|
|
105
|
+
if (permissionCatalog)
|
|
106
|
+
this.permissionCatalog = permissionCatalog;
|
|
70
107
|
this.prefetchHost = 'injected';
|
|
71
108
|
}
|
|
72
|
-
/**
|
|
73
|
-
|
|
109
|
+
/** Provider 下拉:目录 groups + 当前值兜底;离线时只保留当前值。 */
|
|
110
|
+
providerOptions(currentProvider, configured) {
|
|
111
|
+
const options = [];
|
|
74
112
|
if (this.modelCatalog) {
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
|
|
113
|
+
for (const group of this.modelCatalog.groups) {
|
|
114
|
+
options.push({
|
|
115
|
+
text: group.name && group.name !== group.id ? `${group.name}(${group.id})` : group.id,
|
|
116
|
+
value: group.id,
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
if (!options.some((option) => option.value === currentProvider)) {
|
|
121
|
+
const source = configured ? '当前配置' : '默认';
|
|
122
|
+
options.push({
|
|
123
|
+
text: this.modelCatalog
|
|
124
|
+
? `${currentProvider}(${source},服务端未列出)`
|
|
125
|
+
: `${currentProvider}(${source},离线未校验)`,
|
|
126
|
+
value: currentProvider,
|
|
127
|
+
});
|
|
78
128
|
}
|
|
79
|
-
return
|
|
129
|
+
return options;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* 当前 provider 的模型清单。目录在线但该 provider 不在目录中时返回空列表
|
|
133
|
+
* (不把 DeepSeek 模型冒充成其他 provider 的模型);目录离线时用固定兜底。
|
|
134
|
+
*/
|
|
135
|
+
modelOptionsFor(provider) {
|
|
136
|
+
if (!this.modelCatalog)
|
|
137
|
+
return FALLBACK_MODEL_OPTIONS;
|
|
138
|
+
const group = this.modelCatalog.groups.find((entry) => entry.id === provider);
|
|
139
|
+
if (!group)
|
|
140
|
+
return [];
|
|
141
|
+
return group.models.map((model) => ({
|
|
142
|
+
text: model.name && model.name !== model.id ? `${model.name}(${model.id})` : model.id,
|
|
143
|
+
value: model.id,
|
|
144
|
+
}));
|
|
145
|
+
}
|
|
146
|
+
modelValues(provider) {
|
|
147
|
+
return this.modelOptionsFor(provider).map((option) => typeof option === 'string' ? option : option.value);
|
|
148
|
+
}
|
|
149
|
+
/** Provider 的默认模型:目录默认值属于该 provider 时优先,否则首个模型。 */
|
|
150
|
+
defaultModelFor(provider) {
|
|
151
|
+
const group = this.modelCatalog?.groups.find((entry) => entry.id === provider);
|
|
152
|
+
if (!group || group.models.length === 0)
|
|
153
|
+
return undefined;
|
|
154
|
+
const defaultModel = this.modelCatalog?.default?.provider === provider
|
|
155
|
+
? this.modelCatalog.default.model
|
|
156
|
+
: undefined;
|
|
157
|
+
return defaultModel && group.models.some((model) => model.id === defaultModel)
|
|
158
|
+
? defaultModel
|
|
159
|
+
: group.models[0].id;
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* 模型可用的推理档位。目录在线时严格按模型声明:模型没有 reasoning 元数据
|
|
163
|
+
* 就返回空(只留「跟随模型默认」),避免把 A 模型的档位塞给 B 模型。
|
|
164
|
+
*/
|
|
165
|
+
effortOptionsFor(provider, model) {
|
|
166
|
+
if (!this.modelCatalog)
|
|
167
|
+
return FALLBACK_EFFORT_OPTIONS;
|
|
168
|
+
const group = this.modelCatalog.groups.find((entry) => entry.id === provider);
|
|
169
|
+
if (!group)
|
|
170
|
+
return [];
|
|
171
|
+
const target = model ?? this.defaultModelFor(provider);
|
|
172
|
+
const entry = target ? group.models.find((candidate) => candidate.id === target) : undefined;
|
|
173
|
+
return entry?.reasoning?.efforts.map((effort) => effort.id) ?? [];
|
|
80
174
|
}
|
|
81
175
|
/** preset 清单;无 presets 时回退固定清单。显示名优先用 preset.yml 的 name。 */
|
|
82
176
|
presetOptions() {
|
|
@@ -87,28 +181,60 @@ export class DshConfigBuilder {
|
|
|
87
181
|
}
|
|
88
182
|
return FALLBACK_PRESET_OPTIONS;
|
|
89
183
|
}
|
|
90
|
-
/**
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
184
|
+
/** 权限预设清单;配置默认只允许 defaultOptions(auto 只适用于当前会话)。 */
|
|
185
|
+
permissionOptions() {
|
|
186
|
+
const entries = this.permissionCatalog?.defaultOptions;
|
|
187
|
+
if (entries && entries.length > 0) {
|
|
188
|
+
return entries.map((p) => ({
|
|
189
|
+
text: p.value === 'read-only'
|
|
190
|
+
? 'read-only(只读)'
|
|
191
|
+
: p.value === 'workspace-write'
|
|
192
|
+
? 'workspace-write(工作区可写)'
|
|
193
|
+
: p.value === 'danger-full-access'
|
|
194
|
+
? 'danger-full-access(完全访问,关闭审批)'
|
|
195
|
+
: p.name && p.name !== p.value
|
|
196
|
+
? `${p.value}(${p.name})`
|
|
197
|
+
: p.value,
|
|
198
|
+
value: p.value,
|
|
199
|
+
}));
|
|
99
200
|
}
|
|
100
|
-
return
|
|
201
|
+
return FALLBACK_PERMISSION_OPTIONS;
|
|
202
|
+
}
|
|
203
|
+
/** “跟随服务端默认”文案带上实际默认值,避免用户不知道默认是什么。 */
|
|
204
|
+
permissionDefaultText() {
|
|
205
|
+
const current = this.permissionCatalog?.defaultPreset;
|
|
206
|
+
return current ? `跟随服务端默认(当前 ${current})` : '跟随服务端默认';
|
|
101
207
|
}
|
|
102
208
|
buildFields(displayConfig) {
|
|
103
209
|
const dsh = displayConfig.agents?.dsh;
|
|
104
210
|
const currentHost = dsh?.host ?? DEFAULT_DSH_HOST;
|
|
105
211
|
const currentPreset = dsh?.agentPreset ?? DEFAULT_SENTINEL;
|
|
212
|
+
const currentPermission = dsh?.permissionPreset ?? DEFAULT_SENTINEL;
|
|
213
|
+
const configuredProvider = dsh?.provider;
|
|
214
|
+
const currentProvider = configuredProvider ?? LEGACY_PROVIDER;
|
|
106
215
|
const currentModel = dsh?.model ?? DEFAULT_SENTINEL;
|
|
107
216
|
const currentEffort = dsh?.reasoningEffort ?? DEFAULT_SENTINEL;
|
|
108
|
-
const
|
|
109
|
-
const
|
|
110
|
-
const
|
|
111
|
-
|
|
217
|
+
const providerOptions = this.providerOptions(currentProvider, configuredProvider !== undefined);
|
|
218
|
+
const modelOptions = this.modelOptionsFor(currentProvider);
|
|
219
|
+
const isCustomModel = currentModel !== DEFAULT_SENTINEL &&
|
|
220
|
+
!this.modelValues(currentProvider).includes(currentModel);
|
|
221
|
+
const permissionOptions = [...this.permissionOptions()];
|
|
222
|
+
if (currentPermission !== DEFAULT_SENTINEL &&
|
|
223
|
+
!permissionOptions.some((option) => option.value === currentPermission)) {
|
|
224
|
+
permissionOptions.push({
|
|
225
|
+
text: `${currentPermission}(当前配置,服务端未列出)`,
|
|
226
|
+
value: currentPermission,
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
const effortOptions = this.effortOptionsFor(currentProvider, currentModel || undefined).map((value) => ({ text: value, value }));
|
|
230
|
+
if (currentEffort !== DEFAULT_SENTINEL &&
|
|
231
|
+
!effortOptions.some((option) => option.value === currentEffort)) {
|
|
232
|
+
effortOptions.push({
|
|
233
|
+
text: `${currentEffort}(当前配置,模型未列出)`,
|
|
234
|
+
value: currentEffort,
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
const fields = [
|
|
112
238
|
{
|
|
113
239
|
key: 'agents.dsh.host',
|
|
114
240
|
label: 'DSH Web Host',
|
|
@@ -117,17 +243,35 @@ export class DshConfigBuilder {
|
|
|
117
243
|
},
|
|
118
244
|
{
|
|
119
245
|
key: 'agents.dsh.agentPreset',
|
|
120
|
-
label: '
|
|
246
|
+
label: '预设模式(工具集)',
|
|
121
247
|
type: 'select',
|
|
122
248
|
options: [{ text: '跟随服务端默认', value: DEFAULT_SENTINEL }, ...this.presetOptions()],
|
|
123
249
|
currentValue: currentPreset,
|
|
124
250
|
},
|
|
251
|
+
{
|
|
252
|
+
key: 'agents.dsh.permissionPreset',
|
|
253
|
+
label: '权限预设(沙箱 + 审批)',
|
|
254
|
+
type: 'select',
|
|
255
|
+
options: [
|
|
256
|
+
{ text: this.permissionDefaultText(), value: DEFAULT_SENTINEL },
|
|
257
|
+
...permissionOptions,
|
|
258
|
+
],
|
|
259
|
+
currentValue: currentPermission,
|
|
260
|
+
},
|
|
261
|
+
{
|
|
262
|
+
key: 'agents.dsh.provider',
|
|
263
|
+
label: 'Provider(模型服务商)',
|
|
264
|
+
type: 'select',
|
|
265
|
+
options: providerOptions,
|
|
266
|
+
currentValue: currentProvider,
|
|
267
|
+
},
|
|
125
268
|
{
|
|
126
269
|
key: 'agents.dsh.model',
|
|
127
270
|
label: '使用模型',
|
|
128
271
|
type: 'select',
|
|
129
|
-
options: [{ text: '
|
|
130
|
-
|
|
272
|
+
options: [{ text: '跟随该 Provider 默认', value: DEFAULT_SENTINEL }, ...modelOptions],
|
|
273
|
+
// 自定义模型不在下拉里:让下拉停在哨兵,模型名由下方输入框承载。
|
|
274
|
+
currentValue: isCustomModel ? DEFAULT_SENTINEL : currentModel,
|
|
131
275
|
},
|
|
132
276
|
// 自定义模型名输入框(与 codex/claude 一致的行为)
|
|
133
277
|
{
|
|
@@ -140,40 +284,85 @@ export class DshConfigBuilder {
|
|
|
140
284
|
key: 'agents.dsh.reasoningEffort',
|
|
141
285
|
label: '推理强度',
|
|
142
286
|
type: 'select',
|
|
143
|
-
options: [{ text: '
|
|
287
|
+
options: [{ text: '跟随模型默认', value: DEFAULT_SENTINEL }, ...effortOptions],
|
|
144
288
|
currentValue: currentEffort,
|
|
145
289
|
},
|
|
146
290
|
];
|
|
291
|
+
const failures = this.modelCatalog?.failures ?? [];
|
|
292
|
+
if (failures.length > 0) {
|
|
293
|
+
fields.push({
|
|
294
|
+
key: 'agents.dsh.providerFailures',
|
|
295
|
+
label: `⚠️ Provider 目录异常:${failures
|
|
296
|
+
.map((failure) => `${failure.id}(${failure.message})`)
|
|
297
|
+
.join(';')}`,
|
|
298
|
+
type: 'note',
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
return fields;
|
|
147
302
|
}
|
|
148
303
|
handleFieldChange(key, value, config) {
|
|
304
|
+
// provider 没有哨兵:切换时联动 model/effort,避免 provider 与 model 来自
|
|
305
|
+
// 不同目录(selectModel 会以 session/model-unavailable 失败)。
|
|
306
|
+
if (key === 'agents.dsh.provider' && typeof value === 'string') {
|
|
307
|
+
return this.providerChangePatches(value, config);
|
|
308
|
+
}
|
|
149
309
|
const patches = [];
|
|
150
|
-
//
|
|
310
|
+
// 空串哨兵「跟随默认」→ undefined patch(router 删除键)
|
|
151
311
|
const effective = value === DEFAULT_SENTINEL ? undefined : value;
|
|
152
312
|
patches.push({ key, value: effective });
|
|
153
|
-
//
|
|
313
|
+
// 模型变更时,把 provider 固定到当前 provider(旧配置只有 model 时,
|
|
314
|
+
// 第一次选模型即完成迁移),并按新模型重置不被支持的档位。
|
|
154
315
|
if (key === 'agents.dsh.model' && typeof value === 'string') {
|
|
155
|
-
const
|
|
316
|
+
const newModel = effective;
|
|
317
|
+
const provider = config.agents?.dsh?.provider ?? LEGACY_PROVIDER;
|
|
318
|
+
if (newModel) {
|
|
319
|
+
patches.push({ key: 'agents.dsh.provider', value: provider });
|
|
320
|
+
}
|
|
321
|
+
const effortPatch = this.effortPatchForModel(config, provider, newModel);
|
|
156
322
|
if (effortPatch)
|
|
157
323
|
patches.push(effortPatch);
|
|
158
324
|
}
|
|
159
325
|
return patches;
|
|
160
326
|
}
|
|
161
|
-
/**
|
|
162
|
-
|
|
163
|
-
|
|
327
|
+
/** 切 provider 后的联动:provider → 新 provider 的默认/首个模型 → 合法档位。 */
|
|
328
|
+
providerChangePatches(provider, config) {
|
|
329
|
+
const patches = [
|
|
330
|
+
{ key: 'agents.dsh.provider', value: provider },
|
|
331
|
+
];
|
|
332
|
+
if (!this.modelCatalog)
|
|
333
|
+
return patches;
|
|
334
|
+
const group = this.modelCatalog.groups.find((entry) => entry.id === provider);
|
|
335
|
+
if (!group)
|
|
336
|
+
return patches;
|
|
337
|
+
const currentModel = config.agents?.dsh?.model;
|
|
338
|
+
const keepModel = currentModel !== undefined && group.models.some((model) => model.id === currentModel);
|
|
339
|
+
const nextModel = keepModel ? currentModel : this.defaultModelFor(provider);
|
|
340
|
+
if (nextModel !== currentModel) {
|
|
341
|
+
patches.push({ key: 'agents.dsh.model', value: nextModel });
|
|
342
|
+
}
|
|
343
|
+
const effortPatch = this.effortPatchForModel(config, provider, nextModel);
|
|
344
|
+
if (effortPatch)
|
|
345
|
+
patches.push(effortPatch);
|
|
346
|
+
return patches;
|
|
347
|
+
}
|
|
348
|
+
/**
|
|
349
|
+
* 切到 (provider, newModel) 后的档位补丁:当前档位仍被新模型支持 → 不产出;
|
|
350
|
+
* 否则用新模型目录中位;目录离线 → 不产出(保留现状,由服务端校验);
|
|
351
|
+
* 模型无 reasoning 元数据 → 清空档位(跟随模型默认)。
|
|
352
|
+
*/
|
|
353
|
+
effortPatchForModel(config, provider, newModel) {
|
|
164
354
|
const currentEffort = config.agents?.dsh?.reasoningEffort;
|
|
165
355
|
if (!newModel) {
|
|
166
|
-
// 模型被清空(跟随默认)→ 档位一并清空跟随默认
|
|
167
356
|
return currentEffort ? { key: 'agents.dsh.reasoningEffort', value: undefined } : null;
|
|
168
357
|
}
|
|
169
|
-
|
|
358
|
+
if (!this.modelCatalog)
|
|
359
|
+
return null;
|
|
360
|
+
const supported = this.effortOptionsFor(provider, newModel);
|
|
170
361
|
if (currentEffort && supported.includes(currentEffort))
|
|
171
362
|
return null;
|
|
172
363
|
// 新模型目录中位档位;偶数长度取偏高位(off/low → low,符合 DSH 默认 high 语义)
|
|
173
364
|
const middle = supported[Math.floor(supported.length / 2)];
|
|
174
|
-
return middle
|
|
175
|
-
? { key: 'agents.dsh.reasoningEffort', value: middle }
|
|
176
|
-
: { key: 'agents.dsh.reasoningEffort', value: undefined };
|
|
365
|
+
return { key: 'agents.dsh.reasoningEffort', value: middle };
|
|
177
366
|
}
|
|
178
367
|
}
|
|
179
368
|
//# sourceMappingURL=dsh.js.map
|
|
@@ -5,18 +5,27 @@ import { loadOpencodeConfig } from '../../config/opencode-config.js';
|
|
|
5
5
|
import { DEFAULT_TURN_IDLE_TIMEOUT_MINUTES } from '../../config/index.js';
|
|
6
6
|
import { resetModelPatch } from './common/model-patch.js';
|
|
7
7
|
const MODE_OPTIONS = [
|
|
8
|
-
{ text: 'build
|
|
8
|
+
{ text: 'build(默认,按 opencode 权限配置)', value: 'build' },
|
|
9
9
|
{ text: 'plan(规划模式)', value: 'plan' },
|
|
10
10
|
];
|
|
11
|
+
/** Keep the currently configured value selectable even when the CLI failed and
|
|
12
|
+
* we are showing the static fallback catalog. */
|
|
13
|
+
function withCurrent(options, current) {
|
|
14
|
+
if (current === undefined || options.includes(current))
|
|
15
|
+
return options;
|
|
16
|
+
return [current, ...options];
|
|
17
|
+
}
|
|
11
18
|
export class OpencodeConfigBuilder {
|
|
12
19
|
buildFields(displayConfig) {
|
|
13
20
|
const fields = [];
|
|
14
21
|
// 从 opencode models 命令动态读取 provider 和 model
|
|
15
|
-
const
|
|
16
|
-
const
|
|
17
|
-
// 根据 displayConfig 中已选的 provider 过滤 model 选项
|
|
22
|
+
const binary = displayConfig.agents?.opencode?.acp?.binary;
|
|
23
|
+
const opencodeCfg = loadOpencodeConfig(binary);
|
|
18
24
|
const currentProvider = displayConfig.agents?.opencode?.providerID;
|
|
19
|
-
const
|
|
25
|
+
const currentModel = displayConfig.agents?.opencode?.modelID;
|
|
26
|
+
const providerNames = withCurrent(opencodeCfg.providerNames, currentProvider);
|
|
27
|
+
// 根据 displayConfig 中已选的 provider 过滤 model 选项
|
|
28
|
+
const modelOptions = withCurrent(opencodeCfg.modelOptions(currentProvider), currentModel);
|
|
20
29
|
fields.push({
|
|
21
30
|
key: 'agents.opencode.providerID',
|
|
22
31
|
label: 'Provider',
|
|
@@ -56,7 +65,8 @@ export class OpencodeConfigBuilder {
|
|
|
56
65
|
patches.push({ key, value });
|
|
57
66
|
// provider 变更时,重置 model 为新 provider 的首个模型
|
|
58
67
|
if (key === 'agents.opencode.providerID' && typeof value === 'string') {
|
|
59
|
-
const
|
|
68
|
+
const binary = config.agents?.opencode?.acp?.binary;
|
|
69
|
+
const opencodeCfg = loadOpencodeConfig(binary);
|
|
60
70
|
const currentModel = config.agents?.opencode?.modelID;
|
|
61
71
|
const modelPatch = resetModelPatch('agents.opencode.modelID', currentModel, opencodeCfg.modelOptions(value));
|
|
62
72
|
if (modelPatch)
|
|
@@ -51,8 +51,11 @@ export interface AgentConfigCardBuilder {
|
|
|
51
51
|
}>;
|
|
52
52
|
/**
|
|
53
53
|
* 可选的异步预取钩子:卡片渲染前拉取动态选项(如 DSH 的模型/预设目录)。
|
|
54
|
-
* host 为该 agent
|
|
55
|
-
*
|
|
54
|
+
* host 为该 agent 的当前连接地址;实现内部默认按 host 缓存,`force` 用于
|
|
55
|
+
* 打开 /config 时强制刷新(外部新增 provider 后无需重启)。失败静默回退
|
|
56
|
+
* 固定兜底,不阻断卡片渲染。非异步预取的 agent 可不实现。
|
|
56
57
|
*/
|
|
57
|
-
prefetch?(host?: string
|
|
58
|
+
prefetch?(host?: string, token?: string, credentialsFile?: string, options?: {
|
|
59
|
+
force?: boolean;
|
|
60
|
+
}): Promise<void>;
|
|
58
61
|
}
|
package/dist/router/index.d.ts
CHANGED
|
@@ -664,6 +664,7 @@ export declare class CommandRouter {
|
|
|
664
664
|
* DSH /config 卡片构建前异步预取模型/预设目录(§4.2 动态清单)。
|
|
665
665
|
* 只在 defaultAgent 为 dsh 时生效;其他 agent 无 prefetch 钩子直接跳过。
|
|
666
666
|
* 预取失败不阻断——DshConfigBuilder 内部已回退固定兜底清单。
|
|
667
|
+
* `force` 在打开 /config 时使用:DSH Web UI 刚新增的 provider 立即出现。
|
|
667
668
|
*/
|
|
668
669
|
private prefetchConfigCardCatalogs;
|
|
669
670
|
/** Build an interactive config card with CardKit 2.0 tabs + batch save */
|
package/dist/router/index.js
CHANGED
|
@@ -4,6 +4,7 @@ import path from 'node:path';
|
|
|
4
4
|
import YAML from 'yaml';
|
|
5
5
|
import { getConfigDir, getConfigValue, setConfigValue, setConfigValues, mapAgentKey, getAgentConfig, } from '../config/index.js';
|
|
6
6
|
import { syncAgentChoices } from '../runner/index.js';
|
|
7
|
+
import { resolveDshCredentialsFile } from '../runner/dsh/auth.js';
|
|
7
8
|
import { WorkspaceStore } from '../workspace/index.js';
|
|
8
9
|
import { OrderStore } from '../order/index.js';
|
|
9
10
|
import { resolveAlias } from '../order/alias-resolve.js';
|
|
@@ -2144,7 +2145,8 @@ export class CommandRouter {
|
|
|
2144
2145
|
k === 'agents.opencode' ||
|
|
2145
2146
|
k.startsWith('agents.opencode.') ||
|
|
2146
2147
|
k === 'opencode' ||
|
|
2147
|
-
k.startsWith('opencode.')
|
|
2148
|
+
k.startsWith('opencode.') ||
|
|
2149
|
+
k === 'agents.dsh.permissionPreset');
|
|
2148
2150
|
if (hasApprovalConfigChange) {
|
|
2149
2151
|
this.bridge.syncActiveApprovalModes();
|
|
2150
2152
|
}
|
|
@@ -3848,8 +3850,9 @@ ${sessionCwdLine}${agentLines.map((l) => `- ${l}`).join('\n')}
|
|
|
3848
3850
|
async cmdConfig(args, ctx) {
|
|
3849
3851
|
// Ensure probe cache is populated before building the card.
|
|
3850
3852
|
await probeAllAgents();
|
|
3851
|
-
// 动态清单预取(DSH
|
|
3852
|
-
|
|
3853
|
+
// 动态清单预取(DSH 模型/预设目录):卡渲染前强制刷新,DSH Web UI 刚新增
|
|
3854
|
+
// 的 provider 立即可见;失败回退兜底清单。
|
|
3855
|
+
await this.prefetchConfigCardCatalogs(true);
|
|
3853
3856
|
// Check if this is a direct set command: /config <key> <value>
|
|
3854
3857
|
// Command-style set: immediate write to disk, clear pendingConfig
|
|
3855
3858
|
if (args.length >= 2) {
|
|
@@ -3900,13 +3903,18 @@ ${sessionCwdLine}${agentLines.map((l) => `- ${l}`).join('\n')}
|
|
|
3900
3903
|
* DSH /config 卡片构建前异步预取模型/预设目录(§4.2 动态清单)。
|
|
3901
3904
|
* 只在 defaultAgent 为 dsh 时生效;其他 agent 无 prefetch 钩子直接跳过。
|
|
3902
3905
|
* 预取失败不阻断——DshConfigBuilder 内部已回退固定兜底清单。
|
|
3906
|
+
* `force` 在打开 /config 时使用:DSH Web UI 刚新增的 provider 立即出现。
|
|
3903
3907
|
*/
|
|
3904
|
-
async prefetchConfigCardCatalogs() {
|
|
3908
|
+
async prefetchConfigCardCatalogs(force = false) {
|
|
3905
3909
|
if (this.config.defaultAgent !== 'dsh')
|
|
3906
3910
|
return;
|
|
3907
3911
|
const builder = getConfigBuilder('dsh');
|
|
3908
3912
|
const dshConf = getAgentConfig(this.config, 'dsh');
|
|
3909
|
-
|
|
3913
|
+
const credentialsFile = dshConf?.credentialsFile?.trim() ||
|
|
3914
|
+
(dshConf?.dshHome?.trim()
|
|
3915
|
+
? resolveDshCredentialsFile(process.env, dshConf.dshHome)
|
|
3916
|
+
: undefined);
|
|
3917
|
+
await builder.prefetch?.(dshConf?.host, dshConf?.token, credentialsFile, { force });
|
|
3910
3918
|
}
|
|
3911
3919
|
/** Build an interactive config card with CardKit 2.0 tabs + batch save */
|
|
3912
3920
|
buildConfigCard() {
|
|
@@ -222,6 +222,11 @@ export class CodexAppServerTranslator {
|
|
|
222
222
|
this.threadId = params.threadId;
|
|
223
223
|
this.turnId = params.turn.id;
|
|
224
224
|
this.turnStatus = params.turn.status ?? 'inProgress';
|
|
225
|
+
// 单 turn 用量从零开始累计:turn 内每次采样都会发一条
|
|
226
|
+
// `thread/tokenUsage/updated`,本 run 的用量是这些增量之和(不是最后一条)。
|
|
227
|
+
// resume 后服务端重放的上一 turn 用量若在此之后到达,由 handleTokenUsageUpdated
|
|
228
|
+
// 的 turnId 闸门挡掉。
|
|
229
|
+
this.usage = { inputTokens: 0, outputTokens: 0 };
|
|
225
230
|
this.textByItem.clear();
|
|
226
231
|
this.reasoningByItem.clear();
|
|
227
232
|
this.toolOutputByItem.clear();
|
|
@@ -455,22 +460,31 @@ export class CodexAppServerTranslator {
|
|
|
455
460
|
];
|
|
456
461
|
}
|
|
457
462
|
handleTokenUsageUpdated(params) {
|
|
463
|
+
// 归属闸门:resume 之后服务端可能重放上一 turn 的 tokenUsage(turnId 不同),
|
|
464
|
+
// 计入会把上一 turn 的消耗算成本 run。本轮 turnId 由 turn/started 确立。
|
|
465
|
+
if (params.turnId && this.turnId && params.turnId !== this.turnId) {
|
|
466
|
+
return [];
|
|
467
|
+
}
|
|
458
468
|
const last = params.tokenUsage.last;
|
|
459
469
|
// 协议原生 inputTokens 含 cached(total = input + output),而 lark-remote 的
|
|
460
470
|
// 卡片契约是 ccusage 口径「input_tokens 表示未命中缓存的输入」(design §9.21)。
|
|
461
471
|
// 在入口归一,否则 done 卡 Input 会等于 Context、cache% 分母重复计数、
|
|
462
472
|
// Total 被 max(total, input+output+cache) 抬高一倍(实测 364K → 727K)。
|
|
463
473
|
// jsonl 链路有同一处减法(session/codex/rollout-reader.ts buildUsageFromScan)。
|
|
474
|
+
// 单 turn 口径:一个 turn 内每次采样各发一条通知(实测 7 次工具往返 = 7 条),
|
|
475
|
+
// 本 run 是这些增量之和——只取最后一条会把多步 run 低报(实测 3,648 output 显示成 679)。
|
|
464
476
|
const cachedInputTokens = last.cachedInputTokens === undefined
|
|
465
477
|
? undefined
|
|
466
478
|
: Math.min(last.cachedInputTokens, last.inputTokens);
|
|
467
|
-
this.usage
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
cachedInputTokens
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
479
|
+
this.usage.inputTokens += last.inputTokens - (cachedInputTokens ?? 0);
|
|
480
|
+
this.usage.outputTokens += last.outputTokens;
|
|
481
|
+
if (cachedInputTokens !== undefined) {
|
|
482
|
+
this.usage.cachedInputTokens = (this.usage.cachedInputTokens ?? 0) + cachedInputTokens;
|
|
483
|
+
}
|
|
484
|
+
if (last.totalTokens !== undefined) {
|
|
485
|
+
this.usage.totalTokens = (this.usage.totalTokens ?? 0) + last.totalTokens;
|
|
486
|
+
}
|
|
487
|
+
this.usage.contextLimit = params.tokenUsage.modelContextWindow ?? this.usage.contextLimit;
|
|
474
488
|
return [];
|
|
475
489
|
}
|
|
476
490
|
/**
|
|
@@ -53,12 +53,24 @@ export declare abstract class BaseAcpRunner<TTranslator extends BaseAcpTranslato
|
|
|
53
53
|
* matches. Failures are non-fatal (warn only).
|
|
54
54
|
*/
|
|
55
55
|
protected abstract applyTurnSettings(client: JsonRpcClient, sessionId: string): Promise<void>;
|
|
56
|
+
/**
|
|
57
|
+
* session/new|resume retry policy. Default: no retry. Agents whose ACP
|
|
58
|
+
* server sits behind a separately-starting service (opencode v2) override
|
|
59
|
+
* this to absorb a transient `ServiceUnavailableError` without failing the
|
|
60
|
+
* whole turn.
|
|
61
|
+
*/
|
|
62
|
+
protected sessionSetupRetryDelaysMs(): number[];
|
|
63
|
+
/** Whether a failed session/new|resume is safe to retry. */
|
|
64
|
+
protected isRetryableSessionSetupError(_err: unknown): boolean;
|
|
65
|
+
/** Final error surfaced to the user after retries are exhausted. */
|
|
66
|
+
protected enrichSessionSetupError(err: unknown): Error;
|
|
56
67
|
/**
|
|
57
68
|
* Dispatch the compact prompt result once fired. kimi: background
|
|
58
69
|
* compaction — poll wire.jsonl for a NEW terminal record before translating
|
|
59
70
|
* (§5.2). opencode: synchronous — standard prompt-settle translation.
|
|
60
71
|
*/
|
|
61
72
|
protected abstract onCompactPromptFired(promptPromise: Promise<SessionPromptResult>, translator: TTranslator, sessionId: string, opts: SpawnOptions, baselineTotalCount: number): void;
|
|
73
|
+
private withSessionSetupRetry;
|
|
62
74
|
protected currentSessionId(): string | null;
|
|
63
75
|
protected cancelCurrentTurn(): Promise<void>;
|
|
64
76
|
protected clearTurnState(): void;
|