rol-websocket-channel 1.2.5 → 1.3.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/MQTT-API /346/226/260/345/242/236/346/226/207/344/273/266/345/212/237/350/203/275.md" +128 -151
- package/dist/src/admin/methods/agents-extended.js +21 -5
- package/dist/src/admin/methods/models-extended.js +11 -15
- package/dist/src/admin/methods/skills.js +43 -4
- package/package.json +1 -1
- package/src/admin/methods/agents-extended.ts +33 -6
- package/src/admin/methods/models-extended.ts +17 -13
- package/src/admin/methods/skills.ts +69 -4
package/MQTT-API /346/226/260/345/242/236/346/226/207/344/273/266/345/212/237/350/203/275.md"
CHANGED
|
@@ -147,6 +147,11 @@
|
|
|
147
147
|
}
|
|
148
148
|
```
|
|
149
149
|
|
|
150
|
+
**模型字段说明**
|
|
151
|
+
- `modelPrimary` 使用完整的 `providerId/modelName`。
|
|
152
|
+
- `modelProvider` 只做兼容校验;如果传入,必须与 `modelPrimary` 的 provider 部分一致。
|
|
153
|
+
- 创建后只保存 `model.primary`,不会把 `modelProvider` 写成 `model.provider`。
|
|
154
|
+
|
|
150
155
|
---
|
|
151
156
|
|
|
152
157
|
### agentsUpdate - 更新代理
|
|
@@ -159,10 +164,11 @@
|
|
|
159
164
|
- `workspace`
|
|
160
165
|
- `agentDir`
|
|
161
166
|
- `model.primary`
|
|
162
|
-
- `model.provider`
|
|
163
167
|
- `skills`
|
|
164
168
|
- `tools.profile`
|
|
165
169
|
- `behavior`
|
|
170
|
+
- `model.provider` 不能通过 `agentsUpdate` 写入;provider 从 `model.primary` 派生。
|
|
171
|
+
- 切换 agent 模型时只写 `model.primary`,值必须是完整的 `providerId/modelName`。
|
|
166
172
|
|
|
167
173
|
**请求**
|
|
168
174
|
|
|
@@ -305,13 +311,25 @@
|
|
|
305
311
|
|
|
306
312
|
## 模型管理 (Models)
|
|
307
313
|
|
|
314
|
+
### 核心规则
|
|
315
|
+
|
|
316
|
+
调整:模型配置统一收口到 `primary` 和 `models.providers`。
|
|
317
|
+
|
|
318
|
+
- 当前默认模型只看 `agents.defaults.model.primary`。
|
|
319
|
+
- `primary` 必须传完整 ref:`providerId/modelId`,例如 `custom-dashscope-aliyuncs-com/qwen3.6-flash`。
|
|
320
|
+
- 不再写 `agents.defaults.model.provider`;provider 从 `primary` 的 `/` 前半段派生。
|
|
321
|
+
- custom provider 必须维护在 `models.providers.<providerId>`,否则 `primary` 前缀无法解析到真实接口。
|
|
322
|
+
- `agents.defaults.models` 是可选模型池 / allowlist / alias 表,前端可以用它生成模型下拉。
|
|
323
|
+
- 前端提交完整 ref;展示可以用 alias、`modelOptions[].label`、`modelOptions[].model`,或者只展示 `/` 后半段。
|
|
324
|
+
|
|
308
325
|
### modelsGet - 获取模型列表
|
|
309
326
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
-
|
|
327
|
+
调整:只负责读取当前模型和下拉数据,不写配置。
|
|
328
|
+
|
|
329
|
+
**什么时候用**
|
|
330
|
+
|
|
331
|
+
- 页面初始化时获取当前默认模型、模型下拉选项和 provider 配置视图。
|
|
332
|
+
- 前端下拉框优先使用 `data.modelOptions`。
|
|
315
333
|
|
|
316
334
|
**请求**
|
|
317
335
|
```json
|
|
@@ -322,111 +340,21 @@
|
|
|
322
340
|
}
|
|
323
341
|
```
|
|
324
342
|
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
"provider": "custom-dashscope-aliyuncs-com"
|
|
333
|
-
},
|
|
334
|
-
"models": {
|
|
335
|
-
"custom-dashscope-aliyuncs-com/qwen-plus": {
|
|
336
|
-
"alias": "Qwen Plus"
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
},
|
|
340
|
-
"agents": [
|
|
341
|
-
{
|
|
342
|
-
"id": "defaults",
|
|
343
|
-
"name": "defaults",
|
|
344
|
-
"model": {
|
|
345
|
-
"primary": "custom-dashscope-aliyuncs-com/qwen-plus",
|
|
346
|
-
"provider": "custom-dashscope-aliyuncs-com"
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
],
|
|
350
|
-
"modelOptions": [
|
|
351
|
-
{
|
|
352
|
-
"label": "Qwen Plus",
|
|
353
|
-
"value": "custom-dashscope-aliyuncs-com/qwen-plus",
|
|
354
|
-
"provider": "custom-dashscope-aliyuncs-com",
|
|
355
|
-
"providerLabel": "Dashscope Aliyuncs Com",
|
|
356
|
-
"model": "qwen-plus"
|
|
357
|
-
}
|
|
358
|
-
],
|
|
359
|
-
"modelConfigMode": "merge",
|
|
360
|
-
"configuredProviders": {}
|
|
361
|
-
}
|
|
362
|
-
```
|
|
343
|
+
**前端取值**
|
|
344
|
+
- 当前默认模型:`data.defaults.model.primary`
|
|
345
|
+
- 下拉提交值:`data.modelOptions[].value`
|
|
346
|
+
- 下拉展示名:优先 `data.modelOptions[].label`
|
|
347
|
+
- 只展示模型名:`data.modelOptions[].model`,例如 `qwen3.6-flash`
|
|
348
|
+
- provider 展示:`data.modelOptions[].providerLabel` 或 `data.modelOptions[].provider`
|
|
349
|
+
- `data.defaults.model.provider` 即使返回,也只当派生展示值,不写回配置。
|
|
363
350
|
|
|
364
|
-
|
|
365
|
-
- 默认模型:读取 `data.defaults.model.primary`。
|
|
366
|
-
- 默认 provider:读取 `data.defaults.model.provider`;如果为空,可以从选中的 `modelOptions[].provider` 获取。
|
|
367
|
-
- 模型下拉框:使用 `data.modelOptions`。
|
|
368
|
-
- agent 模型列表:使用 `data.agents[]`。
|
|
351
|
+
### modelsSet - 只切换默认模型
|
|
369
352
|
|
|
370
|
-
|
|
353
|
+
调整:只写 `agents.defaults.model.primary即可`,`provider` 只做校验 可以不传。
|
|
371
354
|
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
- 前端模型选择器切换默认模型时使用这个接口。
|
|
376
|
-
- `primary` 是唯一主字段,必须是完整的 `providerId/modelName`。
|
|
377
|
-
- `provider` 是可选字段,只用于校验;如果传入,必须与 `primary` 的 provider 部分一致。
|
|
378
|
-
- 后端写入 `agents.defaults.model.primary`,并从 `primary` 派生写入 `agents.defaults.model.provider`。
|
|
379
|
-
- `primary` 必须存在于 `agents.defaults.models` 白名单中;如果没有配置白名单,则只允许设置当前已使用过的模型。
|
|
380
|
-
|
|
381
|
-
**推荐配置**
|
|
382
|
-
```json
|
|
383
|
-
{
|
|
384
|
-
"models": {
|
|
385
|
-
"mode": "merge",
|
|
386
|
-
"providers": {
|
|
387
|
-
"custom-dashscope-aliyuncs-com": {
|
|
388
|
-
"label": "通义千问 DashScope",
|
|
389
|
-
"apiKey": "YOUR_DASHSCOPE_API_KEY",
|
|
390
|
-
"baseUrl": "https://dashscope.aliyuncs.com/compatible-mode/v1"
|
|
391
|
-
},
|
|
392
|
-
"custom-openai": {
|
|
393
|
-
"label": "OpenAI",
|
|
394
|
-
"apiKey": "YOUR_OPENAI_API_KEY",
|
|
395
|
-
"baseUrl": "https://api.openai.com/v1"
|
|
396
|
-
}
|
|
397
|
-
}
|
|
398
|
-
},
|
|
399
|
-
"agents": {
|
|
400
|
-
"defaults": {
|
|
401
|
-
"model": {
|
|
402
|
-
"primary": "custom-dashscope-aliyuncs-com/qwen-plus",
|
|
403
|
-
"provider": "custom-dashscope-aliyuncs-com"
|
|
404
|
-
},
|
|
405
|
-
"models": {
|
|
406
|
-
"custom-dashscope-aliyuncs-com/qwen-plus": {
|
|
407
|
-
"alias": "Qwen Plus"
|
|
408
|
-
},
|
|
409
|
-
"custom-dashscope-aliyuncs-com/qwen-max": {
|
|
410
|
-
"alias": "Qwen Max"
|
|
411
|
-
},
|
|
412
|
-
"custom-openai/gpt-4.1": {
|
|
413
|
-
"alias": "GPT-4.1"
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
},
|
|
417
|
-
"list": [
|
|
418
|
-
{
|
|
419
|
-
"id": "openai-agent",
|
|
420
|
-
"name": "OpenAI Agent",
|
|
421
|
-
"model": {
|
|
422
|
-
"primary": "custom-openai/gpt-4.1",
|
|
423
|
-
"provider": "custom-openai"
|
|
424
|
-
}
|
|
425
|
-
}
|
|
426
|
-
]
|
|
427
|
-
}
|
|
428
|
-
}
|
|
429
|
-
```
|
|
355
|
+
**什么时候用**
|
|
356
|
+
- 用户只是在已有模型列表里切换默认模型。
|
|
357
|
+
- 这是前端模型选择器最常用的切换接口。
|
|
430
358
|
|
|
431
359
|
**请求**
|
|
432
360
|
```json
|
|
@@ -434,56 +362,47 @@
|
|
|
434
362
|
"type": "modelsSet",
|
|
435
363
|
"trace_id": "models-set-001",
|
|
436
364
|
"data": {
|
|
437
|
-
"primary": "custom-dashscope-aliyuncs-com/
|
|
365
|
+
"primary": "custom-dashscope-aliyuncs-com/qwen3.6-flash"
|
|
438
366
|
}
|
|
439
367
|
}
|
|
440
368
|
```
|
|
441
369
|
|
|
442
|
-
|
|
370
|
+
**可选 provider 校验** 可以忽略不管
|
|
443
371
|
```json
|
|
444
372
|
{
|
|
445
373
|
"type": "modelsSet",
|
|
446
374
|
"trace_id": "models-set-with-provider-001",
|
|
447
375
|
"data": {
|
|
448
|
-
"primary": "custom-dashscope-aliyuncs-com/
|
|
376
|
+
"primary": "custom-dashscope-aliyuncs-com/qwen3.6-flash",
|
|
449
377
|
"provider": "custom-dashscope-aliyuncs-com"
|
|
450
378
|
}
|
|
451
379
|
}
|
|
452
380
|
```
|
|
453
381
|
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
"primary": "custom-dashscope-aliyuncs-com/qwen-plus",
|
|
460
|
-
"provider": "custom-dashscope-aliyuncs-com"
|
|
461
|
-
},
|
|
462
|
-
"defaults": {
|
|
463
|
-
"model": {
|
|
464
|
-
"primary": "custom-dashscope-aliyuncs-com/qwen-plus",
|
|
465
|
-
"provider": "custom-dashscope-aliyuncs-com"
|
|
466
|
-
}
|
|
467
|
-
}
|
|
468
|
-
}
|
|
469
|
-
```
|
|
382
|
+
**行为**
|
|
383
|
+
- 写入:`agents.defaults.model.primary`
|
|
384
|
+
- 不写入:`agents.defaults.model.provider`
|
|
385
|
+
- 如果传了 `provider`,只校验它必须等于 `primary` 的前缀。
|
|
386
|
+
- 如果 `primary` 不在 `agents.defaults.models` allowlist 中,会返回 `MODEL_NOT_ALLOWED`。
|
|
470
387
|
|
|
471
|
-
|
|
472
|
-
- `MODEL_PRIMARY_REQUIRED`:缺少 `primary`。
|
|
473
|
-
- `MODEL_PRIMARY_INVALID`:`primary` 不是 `provider/model` 格式。
|
|
474
|
-
- `MODEL_NOT_ALLOWED`:`primary` 不在允许的模型选项中。
|
|
475
|
-
- `MODEL_PROVIDER_MISMATCH`:传入的 `provider` 与 `primary` 不一致。
|
|
388
|
+
### modelsUpdate - 更新 custom provider,可选同时切默认模型
|
|
476
389
|
|
|
477
|
-
|
|
390
|
+
新增 custom provider
|
|
391
|
+
修改 custom provider 的 baseUrl / apiKey / api
|
|
392
|
+
更新 custom provider 的 models[]
|
|
393
|
+
更新 provider 后顺手用 primaryModel 切到这个模型
|
|
478
394
|
|
|
479
|
-
|
|
395
|
+
暂时不要custom provider 就不需要管这个接口
|
|
480
396
|
|
|
481
|
-
**说明**
|
|
482
|
-
- 用于更新 `models.providers[provider]`。
|
|
483
|
-
- 不建议用它做前端模型下拉切换;默认模型切换请使用 `modelsSet`。
|
|
484
|
-
- `providerConfig` 会合并到已有 provider 配置中。
|
|
485
397
|
|
|
486
|
-
|
|
398
|
+
调整:主要维护 `models.providers`,需要切模型时传 `primaryModel`。
|
|
399
|
+
|
|
400
|
+
**什么时候用**
|
|
401
|
+
- 新增或更新 custom provider。
|
|
402
|
+
- 更新 provider 的 base URL、API key、协议类型或模型列表。
|
|
403
|
+
- 需要在更新 provider 后同时切到某个模型。
|
|
404
|
+
|
|
405
|
+
**只更新 provider**
|
|
487
406
|
```json
|
|
488
407
|
{
|
|
489
408
|
"type": "modelsUpdate",
|
|
@@ -491,35 +410,68 @@
|
|
|
491
410
|
"data": {
|
|
492
411
|
"provider": "custom-dashscope-aliyuncs-com",
|
|
493
412
|
"providerConfig": {
|
|
413
|
+
"baseUrl": "https://dashscope.aliyuncs.com/compatible-mode/v1",
|
|
414
|
+
"api": "openai-completions",
|
|
494
415
|
"apiKey": "YOUR_API_KEY",
|
|
495
|
-
"
|
|
416
|
+
"models": [
|
|
417
|
+
{
|
|
418
|
+
"id": "qwen3.6-flash",
|
|
419
|
+
"name": "qwen3.6-flash (Custom Provider)"
|
|
420
|
+
}
|
|
421
|
+
]
|
|
496
422
|
}
|
|
497
423
|
}
|
|
498
424
|
}
|
|
499
425
|
```
|
|
500
426
|
|
|
501
|
-
|
|
427
|
+
**更新 provider 并切默认模型**
|
|
502
428
|
```json
|
|
503
429
|
{
|
|
504
|
-
"type": "
|
|
505
|
-
"trace_id": "
|
|
430
|
+
"type": "modelsUpdate",
|
|
431
|
+
"trace_id": "models-update-and-switch-001",
|
|
506
432
|
"data": {
|
|
507
|
-
"
|
|
508
|
-
"
|
|
509
|
-
|
|
510
|
-
"
|
|
433
|
+
"primaryModel": "custom-dashscope-aliyuncs-com/qwen3.6-flash",
|
|
434
|
+
"provider": "custom-dashscope-aliyuncs-com",
|
|
435
|
+
"providerConfig": {
|
|
436
|
+
"baseUrl": "https://dashscope.aliyuncs.com/compatible-mode/v1",
|
|
437
|
+
"api": "openai-completions",
|
|
438
|
+
"apiKey": "YOUR_API_KEY",
|
|
439
|
+
"models": [
|
|
440
|
+
{
|
|
441
|
+
"id": "qwen3.6-flash",
|
|
442
|
+
"name": "qwen3.6-flash (Custom Provider)"
|
|
443
|
+
}
|
|
444
|
+
]
|
|
511
445
|
}
|
|
512
446
|
}
|
|
513
447
|
}
|
|
514
448
|
```
|
|
515
449
|
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
450
|
+
**带 modelProvider 的兼容校验**
|
|
451
|
+
```json
|
|
452
|
+
{
|
|
453
|
+
"type": "modelsUpdate",
|
|
454
|
+
"trace_id": "models-update-with-model-provider-001",
|
|
455
|
+
"data": {
|
|
456
|
+
"primaryModel": "custom-dashscope-aliyuncs-com/qwen3.6-flash",
|
|
457
|
+
"modelProvider": "custom-dashscope-aliyuncs-com"
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
**行为**
|
|
463
|
+
- `provider + providerConfig` 写入并合并到 `models.providers[provider]`。
|
|
464
|
+
- `primaryModel` 写入 `agents.defaults.model.primary`。
|
|
465
|
+
- `modelProvider` 不能单独传;它只在同时传 `primaryModel` 时做兼容校验。
|
|
466
|
+
- 不写入 `agents.defaults.model.provider`。
|
|
519
467
|
|
|
520
|
-
|
|
468
|
+
**providerConfig 允许字段**
|
|
521
469
|
- `apiKey`
|
|
470
|
+
- `api`
|
|
522
471
|
- `baseUrl`
|
|
472
|
+
- `models`
|
|
473
|
+
- `name`
|
|
474
|
+
- `label`
|
|
523
475
|
- `model`
|
|
524
476
|
- `temperature`
|
|
525
477
|
- `maxTokens`
|
|
@@ -527,6 +479,31 @@
|
|
|
527
479
|
- `frequencyPenalty`
|
|
528
480
|
- `presencePenalty`
|
|
529
481
|
|
|
482
|
+
### agentsUpdate - 切换指定 agent 模型
|
|
483
|
+
|
|
484
|
+
调整:只允许写 agent 的 `model.primary`,不再写 `model.provider`。
|
|
485
|
+
|
|
486
|
+
**什么时候用**
|
|
487
|
+
- 只切某个命名 agent 的模型,而不是默认模型。
|
|
488
|
+
|
|
489
|
+
**请求**
|
|
490
|
+
```json
|
|
491
|
+
{
|
|
492
|
+
"type": "agentsUpdate",
|
|
493
|
+
"trace_id": "agent-switch-model-001",
|
|
494
|
+
"data": {
|
|
495
|
+
"agentId": "my-agent",
|
|
496
|
+
"updates": {
|
|
497
|
+
"model.primary": "custom-dashscope-aliyuncs-com/qwen3.6-flash"
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
```
|
|
502
|
+
|
|
503
|
+
**行为**
|
|
504
|
+
- 只能写 `model.primary`。
|
|
505
|
+
- 不能写 `model.provider`。
|
|
506
|
+
|
|
530
507
|
---
|
|
531
508
|
|
|
532
509
|
## Mem9 管理 (Mem9)
|
|
@@ -7,7 +7,6 @@ const ALLOWED_AGENT_UPDATES = {
|
|
|
7
7
|
'workspace': true,
|
|
8
8
|
'agentDir': true,
|
|
9
9
|
'model.primary': true,
|
|
10
|
-
'model.provider': true,
|
|
11
10
|
'skills': true,
|
|
12
11
|
'tools.profile': true,
|
|
13
12
|
'behavior': true,
|
|
@@ -50,11 +49,21 @@ export const createAgent = async (params, context) => {
|
|
|
50
49
|
workspace,
|
|
51
50
|
agentDir
|
|
52
51
|
};
|
|
53
|
-
|
|
54
|
-
|
|
52
|
+
const modelPrimary = typeof objectParams.modelPrimary === 'string' && objectParams.modelPrimary.trim().length > 0
|
|
53
|
+
? objectParams.modelPrimary.trim()
|
|
54
|
+
: null;
|
|
55
|
+
const modelProvider = typeof objectParams.modelProvider === 'string' && objectParams.modelProvider.trim().length > 0
|
|
56
|
+
? objectParams.modelProvider.trim()
|
|
57
|
+
: null;
|
|
58
|
+
if (modelPrimary) {
|
|
59
|
+
const inferredProvider = inferProviderFromPrimaryModel(modelPrimary);
|
|
60
|
+
if (modelProvider && modelProvider !== inferredProvider) {
|
|
61
|
+
throw new JsonRpcException(JSON_RPC_ERRORS.invalidParams, 'modelProvider does not match modelPrimary');
|
|
62
|
+
}
|
|
63
|
+
created.model = { ...(created.model ?? {}), primary: modelPrimary };
|
|
55
64
|
}
|
|
56
|
-
if (
|
|
57
|
-
|
|
65
|
+
else if (modelProvider) {
|
|
66
|
+
throw new JsonRpcException(JSON_RPC_ERRORS.invalidParams, 'modelProvider cannot be set without modelPrimary; provider is derived from model.primary');
|
|
58
67
|
}
|
|
59
68
|
if (Array.isArray(objectParams.skills)) {
|
|
60
69
|
created.skills = objectParams.skills.filter((item) => typeof item === 'string');
|
|
@@ -143,6 +152,9 @@ export const updateAgent = async (params, context) => {
|
|
|
143
152
|
const target = resolveAgentTarget(config, agentId);
|
|
144
153
|
const changes = [];
|
|
145
154
|
for (const [fieldPath, value] of Object.entries(updates)) {
|
|
155
|
+
if (fieldPath === 'model.provider') {
|
|
156
|
+
throw new JsonRpcException(JSON_RPC_ERRORS.invalidParams, 'model.provider is derived from model.primary and cannot be updated directly');
|
|
157
|
+
}
|
|
146
158
|
if (!ALLOWED_AGENT_UPDATES[fieldPath]) {
|
|
147
159
|
throw new JsonRpcException(JSON_RPC_ERRORS.invalidParams, `Field not allowed for update: ${fieldPath}. Allowed fields: ${Object.keys(ALLOWED_AGENT_UPDATES).join(', ')}`);
|
|
148
160
|
}
|
|
@@ -170,6 +182,10 @@ function setNestedValue(obj, pathExpression, value) {
|
|
|
170
182
|
}
|
|
171
183
|
current[keys[keys.length - 1]] = value;
|
|
172
184
|
}
|
|
185
|
+
function inferProviderFromPrimaryModel(primary) {
|
|
186
|
+
const separatorIndex = primary.indexOf('/');
|
|
187
|
+
return separatorIndex > 0 ? primary.slice(0, separatorIndex) : null;
|
|
188
|
+
}
|
|
173
189
|
function isObject(value) {
|
|
174
190
|
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
175
191
|
}
|
|
@@ -4,7 +4,11 @@ import { JsonRpcException, JSON_RPC_ERRORS } from '../jsonrpc.ts';
|
|
|
4
4
|
// 允许修改的 provider 配置字段白名单
|
|
5
5
|
const ALLOWED_PROVIDER_FIELDS = {
|
|
6
6
|
'apiKey': true,
|
|
7
|
+
'api': true,
|
|
7
8
|
'baseUrl': true,
|
|
9
|
+
'models': true,
|
|
10
|
+
'name': true,
|
|
11
|
+
'label': true,
|
|
8
12
|
'model': true,
|
|
9
13
|
'temperature': true,
|
|
10
14
|
'maxTokens': true,
|
|
@@ -35,24 +39,15 @@ export const updateModels = async (params, context) => {
|
|
|
35
39
|
if (!config.agents.defaults.model)
|
|
36
40
|
config.agents.defaults.model = {};
|
|
37
41
|
config.agents.defaults.model.primary = primaryModel;
|
|
38
|
-
const inferredProvider =
|
|
39
|
-
if (inferredProvider) {
|
|
40
|
-
|
|
41
|
-
changes.push(`Updated default model provider to: ${inferredProvider}`);
|
|
42
|
+
const inferredProvider = inferProviderFromPrimaryModel(primaryModel);
|
|
43
|
+
if (modelProvider && modelProvider !== inferredProvider) {
|
|
44
|
+
throwModelError('MODEL_PROVIDER_MISMATCH', 'modelProvider does not match primaryModel');
|
|
42
45
|
}
|
|
43
46
|
updated = true;
|
|
44
47
|
changes.push(`Updated primary model to: ${primaryModel}`);
|
|
45
48
|
}
|
|
46
49
|
if (modelProvider && !primaryModel) {
|
|
47
|
-
|
|
48
|
-
config.agents = {};
|
|
49
|
-
if (!config.agents.defaults)
|
|
50
|
-
config.agents.defaults = {};
|
|
51
|
-
if (!config.agents.defaults.model)
|
|
52
|
-
config.agents.defaults.model = {};
|
|
53
|
-
config.agents.defaults.model.provider = modelProvider;
|
|
54
|
-
updated = true;
|
|
55
|
-
changes.push(`Updated default model provider to: ${modelProvider}`);
|
|
50
|
+
throwModelError('MODEL_PROVIDER_DERIVED', 'modelProvider cannot be updated directly; set primaryModel in provider/model format');
|
|
56
51
|
}
|
|
57
52
|
// 更新 provider 配置
|
|
58
53
|
if (provider && providerConfig) {
|
|
@@ -88,7 +83,9 @@ export const updateModels = async (params, context) => {
|
|
|
88
83
|
changes,
|
|
89
84
|
updatedConfig: {
|
|
90
85
|
primaryModel: config.agents?.defaults?.model?.primary ?? null,
|
|
91
|
-
modelProvider: config.agents?.defaults?.model?.
|
|
86
|
+
modelProvider: config.agents?.defaults?.model?.primary
|
|
87
|
+
? inferProviderFromPrimaryModel(config.agents.defaults.model.primary)
|
|
88
|
+
: null,
|
|
92
89
|
defaultModel: config.agents?.defaults?.model ?? {},
|
|
93
90
|
providers: redactSecrets(config.models?.providers ?? {})
|
|
94
91
|
}
|
|
@@ -120,7 +117,6 @@ export const setModel = async (params, context) => {
|
|
|
120
117
|
if (!config.agents.defaults.model)
|
|
121
118
|
config.agents.defaults.model = {};
|
|
122
119
|
config.agents.defaults.model.primary = primary;
|
|
123
|
-
config.agents.defaults.model.provider = inferredProvider;
|
|
124
120
|
await writeJsonFile(configPath, config);
|
|
125
121
|
return {
|
|
126
122
|
ok: true,
|
|
@@ -65,7 +65,7 @@ export const searchClawHubSkills = async (params, context) => {
|
|
|
65
65
|
export const installSkillFromClawHub = async (params, context) => {
|
|
66
66
|
const objectParams = expectObject(params);
|
|
67
67
|
const slug = expectString(objectParams.slug, 'slug');
|
|
68
|
-
const result = await
|
|
68
|
+
const result = await runClawHubSkillCommandWithFallback('install', slug, context.openclawRoot, context.openclawRoot);
|
|
69
69
|
return {
|
|
70
70
|
ok: true,
|
|
71
71
|
slug,
|
|
@@ -75,7 +75,7 @@ export const installSkillFromClawHub = async (params, context) => {
|
|
|
75
75
|
export const updateSkillFromClawHub = async (params, context) => {
|
|
76
76
|
const objectParams = expectObject(params);
|
|
77
77
|
const slug = expectString(objectParams.slug, 'slug');
|
|
78
|
-
const result = await
|
|
78
|
+
const result = await runClawHubSkillCommandWithFallback('update', slug, context.openclawRoot, context.openclawRoot);
|
|
79
79
|
return {
|
|
80
80
|
ok: true,
|
|
81
81
|
slug,
|
|
@@ -124,13 +124,52 @@ async function npmPack(packageSpec, cwd) {
|
|
|
124
124
|
}
|
|
125
125
|
return tarballName;
|
|
126
126
|
}
|
|
127
|
+
export function buildClawHubSkillCommandPlan(action, slug) {
|
|
128
|
+
const args = action === 'install' && slug === 'proactive-agent'
|
|
129
|
+
? [action, slug, '--force']
|
|
130
|
+
: [action, slug];
|
|
131
|
+
return [
|
|
132
|
+
{
|
|
133
|
+
command: process.env.CLAWHUB_BIN || 'clawhub',
|
|
134
|
+
args
|
|
135
|
+
}
|
|
136
|
+
];
|
|
137
|
+
}
|
|
127
138
|
async function runOpenClawSkillCommand(args, cwd, openclawRoot) {
|
|
128
139
|
const command = process.env.OPENCLAW_BIN || 'openclaw';
|
|
140
|
+
return await runSkillCommand(command, args, cwd, openclawRoot);
|
|
141
|
+
}
|
|
142
|
+
async function runClawHubSkillCommandWithFallback(action, slug, cwd, openclawRoot) {
|
|
143
|
+
const attempts = [];
|
|
144
|
+
for (const invocation of buildClawHubSkillCommandPlan(action, slug)) {
|
|
145
|
+
try {
|
|
146
|
+
return await runSkillCommand(invocation.command, invocation.args, cwd, openclawRoot);
|
|
147
|
+
}
|
|
148
|
+
catch (err) {
|
|
149
|
+
const data = err instanceof JsonRpcException && isRecord(err.data) ? err.data : {};
|
|
150
|
+
attempts.push({
|
|
151
|
+
command: invocation.command,
|
|
152
|
+
args: invocation.args,
|
|
153
|
+
stdout: typeof data.stdout === 'string' ? data.stdout : '',
|
|
154
|
+
stderr: typeof data.stderr === 'string' ? data.stderr : '',
|
|
155
|
+
error: err instanceof Error ? err.message : String(err)
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
const lastAttempt = attempts.at(-1);
|
|
160
|
+
throw new JsonRpcException(JSON_RPC_ERRORS.internalError, `ClawHub skill ${action} command failed: ${lastAttempt?.error ?? 'unknown error'}`, {
|
|
161
|
+
action,
|
|
162
|
+
slug,
|
|
163
|
+
attempts
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
async function runSkillCommand(command, args, cwd, openclawRoot) {
|
|
129
167
|
const options = buildOpenClawExecOptions(cwd, openclawRoot);
|
|
130
168
|
const openclawBin = process.env.OPENCLAW_BIN || '';
|
|
169
|
+
const clawhubBin = process.env.CLAWHUB_BIN || '';
|
|
131
170
|
const openclawHome = options.env?.OPENCLAW_HOME || '';
|
|
132
171
|
const home = options.env?.HOME || '';
|
|
133
|
-
console.log(`[skills] exec start: command=${command}, args=${JSON.stringify(args)}, cwd=${cwd}, OPENCLAW_BIN=${openclawBin}, OPENCLAW_HOME=${openclawHome}, HOME=${home}`);
|
|
172
|
+
console.log(`[skills] exec start: command=${command}, args=${JSON.stringify(args)}, cwd=${cwd}, CLAWHUB_BIN=${clawhubBin}, OPENCLAW_BIN=${openclawBin}, OPENCLAW_HOME=${openclawHome}, HOME=${home}`);
|
|
134
173
|
try {
|
|
135
174
|
const { stdout, stderr } = await execFileAsync(command, args, options);
|
|
136
175
|
console.log(`[skills] exec success: command=${command}, args=${JSON.stringify(args)}, stdoutLength=${stdout.length}, stderrLength=${stderr.length}`);
|
|
@@ -143,7 +182,7 @@ async function runOpenClawSkillCommand(args, cwd, openclawRoot) {
|
|
|
143
182
|
catch (err) {
|
|
144
183
|
const stdout = typeof err?.stdout === 'string' ? err.stdout : '';
|
|
145
184
|
const stderr = typeof err?.stderr === 'string' ? err.stderr : '';
|
|
146
|
-
console.error(`[skills] exec failed: command=${command}, args=${JSON.stringify(args)}, cwd=${cwd}, OPENCLAW_BIN=${openclawBin}, OPENCLAW_HOME=${openclawHome}, HOME=${home}, stdout=${JSON.stringify(stdout)}, stderr=${JSON.stringify(stderr)}`);
|
|
185
|
+
console.error(`[skills] exec failed: command=${command}, args=${JSON.stringify(args)}, cwd=${cwd}, CLAWHUB_BIN=${clawhubBin}, OPENCLAW_BIN=${openclawBin}, OPENCLAW_HOME=${openclawHome}, HOME=${home}, stdout=${JSON.stringify(stdout)}, stderr=${JSON.stringify(stderr)}`);
|
|
147
186
|
throw new JsonRpcException(JSON_RPC_ERRORS.internalError, `OpenClaw skill command failed: ${err instanceof Error ? err.message : String(err)}`, {
|
|
148
187
|
command,
|
|
149
188
|
args,
|
package/package.json
CHANGED
|
@@ -30,7 +30,6 @@ const ALLOWED_AGENT_UPDATES: Record<string, boolean> = {
|
|
|
30
30
|
'workspace': true,
|
|
31
31
|
'agentDir': true,
|
|
32
32
|
'model.primary': true,
|
|
33
|
-
'model.provider': true,
|
|
34
33
|
'skills': true,
|
|
35
34
|
'tools.profile': true,
|
|
36
35
|
'behavior': true,
|
|
@@ -83,11 +82,27 @@ export const createAgent: MethodHandler = async (params, context): Promise<JsonV
|
|
|
83
82
|
agentDir
|
|
84
83
|
};
|
|
85
84
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
85
|
+
const modelPrimary = typeof objectParams.modelPrimary === 'string' && objectParams.modelPrimary.trim().length > 0
|
|
86
|
+
? objectParams.modelPrimary.trim()
|
|
87
|
+
: null;
|
|
88
|
+
const modelProvider = typeof objectParams.modelProvider === 'string' && objectParams.modelProvider.trim().length > 0
|
|
89
|
+
? objectParams.modelProvider.trim()
|
|
90
|
+
: null;
|
|
91
|
+
|
|
92
|
+
if (modelPrimary) {
|
|
93
|
+
const inferredProvider = inferProviderFromPrimaryModel(modelPrimary);
|
|
94
|
+
if (modelProvider && modelProvider !== inferredProvider) {
|
|
95
|
+
throw new JsonRpcException(
|
|
96
|
+
JSON_RPC_ERRORS.invalidParams,
|
|
97
|
+
'modelProvider does not match modelPrimary'
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
created.model = { ...(created.model ?? {}), primary: modelPrimary };
|
|
101
|
+
} else if (modelProvider) {
|
|
102
|
+
throw new JsonRpcException(
|
|
103
|
+
JSON_RPC_ERRORS.invalidParams,
|
|
104
|
+
'modelProvider cannot be set without modelPrimary; provider is derived from model.primary'
|
|
105
|
+
);
|
|
91
106
|
}
|
|
92
107
|
if (Array.isArray(objectParams.skills)) {
|
|
93
108
|
created.skills = objectParams.skills.filter((item): item is string => typeof item === 'string');
|
|
@@ -196,6 +211,13 @@ export const updateAgent: MethodHandler = async (params, context): Promise<JsonV
|
|
|
196
211
|
const changes: string[] = [];
|
|
197
212
|
|
|
198
213
|
for (const [fieldPath, value] of Object.entries(updates)) {
|
|
214
|
+
if (fieldPath === 'model.provider') {
|
|
215
|
+
throw new JsonRpcException(
|
|
216
|
+
JSON_RPC_ERRORS.invalidParams,
|
|
217
|
+
'model.provider is derived from model.primary and cannot be updated directly'
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
|
|
199
221
|
if (!ALLOWED_AGENT_UPDATES[fieldPath]) {
|
|
200
222
|
throw new JsonRpcException(
|
|
201
223
|
JSON_RPC_ERRORS.invalidParams,
|
|
@@ -233,6 +255,11 @@ function setNestedValue(obj: any, pathExpression: string, value: any): void {
|
|
|
233
255
|
current[keys[keys.length - 1]] = value;
|
|
234
256
|
}
|
|
235
257
|
|
|
258
|
+
function inferProviderFromPrimaryModel(primary: string): string | null {
|
|
259
|
+
const separatorIndex = primary.indexOf('/');
|
|
260
|
+
return separatorIndex > 0 ? primary.slice(0, separatorIndex) : null;
|
|
261
|
+
}
|
|
262
|
+
|
|
236
263
|
function isObject(value: JsonValue | undefined): value is Record<string, JsonValue> {
|
|
237
264
|
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
238
265
|
}
|
|
@@ -27,7 +27,11 @@ interface OpenClawConfig {
|
|
|
27
27
|
// 允许修改的 provider 配置字段白名单
|
|
28
28
|
const ALLOWED_PROVIDER_FIELDS: Record<string, boolean> = {
|
|
29
29
|
'apiKey': true,
|
|
30
|
+
'api': true,
|
|
30
31
|
'baseUrl': true,
|
|
32
|
+
'models': true,
|
|
33
|
+
'name': true,
|
|
34
|
+
'label': true,
|
|
31
35
|
'model': true,
|
|
32
36
|
'temperature': true,
|
|
33
37
|
'maxTokens': true,
|
|
@@ -63,23 +67,22 @@ export const updateModels: MethodHandler = async (
|
|
|
63
67
|
if (!config.agents.defaults.model) config.agents.defaults.model = {};
|
|
64
68
|
|
|
65
69
|
config.agents.defaults.model.primary = primaryModel;
|
|
66
|
-
const inferredProvider =
|
|
67
|
-
if (inferredProvider) {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
+
const inferredProvider = inferProviderFromPrimaryModel(primaryModel);
|
|
71
|
+
if (modelProvider && modelProvider !== inferredProvider) {
|
|
72
|
+
throwModelError(
|
|
73
|
+
'MODEL_PROVIDER_MISMATCH',
|
|
74
|
+
'modelProvider does not match primaryModel'
|
|
75
|
+
);
|
|
70
76
|
}
|
|
71
77
|
updated = true;
|
|
72
78
|
changes.push(`Updated primary model to: ${primaryModel}`);
|
|
73
79
|
}
|
|
74
80
|
|
|
75
81
|
if (modelProvider && !primaryModel) {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
config.agents.defaults.model.provider = modelProvider;
|
|
81
|
-
updated = true;
|
|
82
|
-
changes.push(`Updated default model provider to: ${modelProvider}`);
|
|
82
|
+
throwModelError(
|
|
83
|
+
'MODEL_PROVIDER_DERIVED',
|
|
84
|
+
'modelProvider cannot be updated directly; set primaryModel in provider/model format'
|
|
85
|
+
);
|
|
83
86
|
}
|
|
84
87
|
|
|
85
88
|
// 更新 provider 配置
|
|
@@ -126,7 +129,9 @@ export const updateModels: MethodHandler = async (
|
|
|
126
129
|
changes,
|
|
127
130
|
updatedConfig: {
|
|
128
131
|
primaryModel: config.agents?.defaults?.model?.primary ?? null,
|
|
129
|
-
modelProvider: config.agents?.defaults?.model?.
|
|
132
|
+
modelProvider: config.agents?.defaults?.model?.primary
|
|
133
|
+
? inferProviderFromPrimaryModel(config.agents.defaults.model.primary)
|
|
134
|
+
: null,
|
|
130
135
|
defaultModel: config.agents?.defaults?.model ?? {},
|
|
131
136
|
providers: redactSecrets(config.models?.providers ?? {})
|
|
132
137
|
}
|
|
@@ -178,7 +183,6 @@ export const setModel: MethodHandler = async (
|
|
|
178
183
|
if (!config.agents.defaults.model) config.agents.defaults.model = {};
|
|
179
184
|
|
|
180
185
|
config.agents.defaults.model.primary = primary;
|
|
181
|
-
config.agents.defaults.model.provider = inferredProvider;
|
|
182
186
|
|
|
183
187
|
await writeJsonFile(configPath, config);
|
|
184
188
|
|
|
@@ -33,6 +33,13 @@ interface ClawHubSkillParams {
|
|
|
33
33
|
slug: string;
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
type ClawHubSkillAction = 'install' | 'update';
|
|
37
|
+
|
|
38
|
+
interface SkillCommandInvocation {
|
|
39
|
+
command: string;
|
|
40
|
+
args: string[];
|
|
41
|
+
}
|
|
42
|
+
|
|
36
43
|
interface ResolvedSkillIdentity {
|
|
37
44
|
slug: string;
|
|
38
45
|
displayName: string;
|
|
@@ -155,7 +162,7 @@ export const searchClawHubSkills: MethodHandler = async (params, context): Promi
|
|
|
155
162
|
export const installSkillFromClawHub: MethodHandler = async (params, context): Promise<JsonValue> => {
|
|
156
163
|
const objectParams = expectObject(params) as unknown as ClawHubSkillParams;
|
|
157
164
|
const slug = expectString(objectParams.slug, 'slug');
|
|
158
|
-
const result = await
|
|
165
|
+
const result = await runClawHubSkillCommandWithFallback('install', slug, context.openclawRoot, context.openclawRoot);
|
|
159
166
|
|
|
160
167
|
return {
|
|
161
168
|
ok: true,
|
|
@@ -167,7 +174,7 @@ export const installSkillFromClawHub: MethodHandler = async (params, context): P
|
|
|
167
174
|
export const updateSkillFromClawHub: MethodHandler = async (params, context): Promise<JsonValue> => {
|
|
168
175
|
const objectParams = expectObject(params) as unknown as ClawHubSkillParams;
|
|
169
176
|
const slug = expectString(objectParams.slug, 'slug');
|
|
170
|
-
const result = await
|
|
177
|
+
const result = await runClawHubSkillCommandWithFallback('update', slug, context.openclawRoot, context.openclawRoot);
|
|
171
178
|
|
|
172
179
|
return {
|
|
173
180
|
ok: true,
|
|
@@ -231,19 +238,77 @@ async function npmPack(packageSpec: string, cwd: string): Promise<string> {
|
|
|
231
238
|
return tarballName;
|
|
232
239
|
}
|
|
233
240
|
|
|
241
|
+
export function buildClawHubSkillCommandPlan(action: ClawHubSkillAction, slug: string): SkillCommandInvocation[] {
|
|
242
|
+
const args = action === 'install' && slug === 'proactive-agent'
|
|
243
|
+
? [action, slug, '--force']
|
|
244
|
+
: [action, slug];
|
|
245
|
+
|
|
246
|
+
return [
|
|
247
|
+
{
|
|
248
|
+
command: process.env.CLAWHUB_BIN || 'clawhub',
|
|
249
|
+
args
|
|
250
|
+
}
|
|
251
|
+
];
|
|
252
|
+
}
|
|
253
|
+
|
|
234
254
|
async function runOpenClawSkillCommand(
|
|
235
255
|
args: string[],
|
|
236
256
|
cwd: string,
|
|
237
257
|
openclawRoot?: string
|
|
238
258
|
): Promise<{ stdout: string; stderr: string; parsed: JsonValue | null }> {
|
|
239
259
|
const command = process.env.OPENCLAW_BIN || 'openclaw';
|
|
260
|
+
return await runSkillCommand(command, args, cwd, openclawRoot);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
async function runClawHubSkillCommandWithFallback(
|
|
264
|
+
action: ClawHubSkillAction,
|
|
265
|
+
slug: string,
|
|
266
|
+
cwd: string,
|
|
267
|
+
openclawRoot?: string
|
|
268
|
+
): Promise<{ stdout: string; stderr: string; parsed: JsonValue | null }> {
|
|
269
|
+
const attempts: Array<SkillCommandInvocation & { stdout: string; stderr: string; error: string }> = [];
|
|
270
|
+
|
|
271
|
+
for (const invocation of buildClawHubSkillCommandPlan(action, slug)) {
|
|
272
|
+
try {
|
|
273
|
+
return await runSkillCommand(invocation.command, invocation.args, cwd, openclawRoot);
|
|
274
|
+
} catch (err) {
|
|
275
|
+
const data = err instanceof JsonRpcException && isRecord(err.data) ? err.data : {};
|
|
276
|
+
attempts.push({
|
|
277
|
+
command: invocation.command,
|
|
278
|
+
args: invocation.args,
|
|
279
|
+
stdout: typeof data.stdout === 'string' ? data.stdout : '',
|
|
280
|
+
stderr: typeof data.stderr === 'string' ? data.stderr : '',
|
|
281
|
+
error: err instanceof Error ? err.message : String(err)
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
const lastAttempt = attempts.at(-1);
|
|
287
|
+
throw new JsonRpcException(
|
|
288
|
+
JSON_RPC_ERRORS.internalError,
|
|
289
|
+
`ClawHub skill ${action} command failed: ${lastAttempt?.error ?? 'unknown error'}`,
|
|
290
|
+
{
|
|
291
|
+
action,
|
|
292
|
+
slug,
|
|
293
|
+
attempts
|
|
294
|
+
}
|
|
295
|
+
);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
async function runSkillCommand(
|
|
299
|
+
command: string,
|
|
300
|
+
args: string[],
|
|
301
|
+
cwd: string,
|
|
302
|
+
openclawRoot?: string
|
|
303
|
+
): Promise<{ stdout: string; stderr: string; parsed: JsonValue | null }> {
|
|
240
304
|
const options = buildOpenClawExecOptions(cwd, openclawRoot);
|
|
241
305
|
const openclawBin = process.env.OPENCLAW_BIN || '';
|
|
306
|
+
const clawhubBin = process.env.CLAWHUB_BIN || '';
|
|
242
307
|
const openclawHome = options.env?.OPENCLAW_HOME || '';
|
|
243
308
|
const home = options.env?.HOME || '';
|
|
244
309
|
|
|
245
310
|
console.log(
|
|
246
|
-
`[skills] exec start: command=${command}, args=${JSON.stringify(args)}, cwd=${cwd}, OPENCLAW_BIN=${openclawBin}, OPENCLAW_HOME=${openclawHome}, HOME=${home}`
|
|
311
|
+
`[skills] exec start: command=${command}, args=${JSON.stringify(args)}, cwd=${cwd}, CLAWHUB_BIN=${clawhubBin}, OPENCLAW_BIN=${openclawBin}, OPENCLAW_HOME=${openclawHome}, HOME=${home}`
|
|
247
312
|
);
|
|
248
313
|
|
|
249
314
|
try {
|
|
@@ -260,7 +325,7 @@ async function runOpenClawSkillCommand(
|
|
|
260
325
|
const stdout = typeof err?.stdout === 'string' ? err.stdout : '';
|
|
261
326
|
const stderr = typeof err?.stderr === 'string' ? err.stderr : '';
|
|
262
327
|
console.error(
|
|
263
|
-
`[skills] exec failed: command=${command}, args=${JSON.stringify(args)}, cwd=${cwd}, OPENCLAW_BIN=${openclawBin}, OPENCLAW_HOME=${openclawHome}, HOME=${home}, stdout=${JSON.stringify(stdout)}, stderr=${JSON.stringify(stderr)}`
|
|
328
|
+
`[skills] exec failed: command=${command}, args=${JSON.stringify(args)}, cwd=${cwd}, CLAWHUB_BIN=${clawhubBin}, OPENCLAW_BIN=${openclawBin}, OPENCLAW_HOME=${openclawHome}, HOME=${home}, stdout=${JSON.stringify(stdout)}, stderr=${JSON.stringify(stderr)}`
|
|
264
329
|
);
|
|
265
330
|
throw new JsonRpcException(
|
|
266
331
|
JSON_RPC_ERRORS.internalError,
|