dsh-plugin-weapp-gateway 0.8.23 → 0.8.34
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/LICENSE +21 -21
- package/PROTOCOL.md +1545 -1486
- package/README.md +503 -464
- package/THIRD-PARTY-NOTICES/dsh-cost-meter.txt +31 -31
- package/bin/setup-ip.mjs +474 -474
- package/cordis.patch.yml +50 -50
- package/docs/agent-team-app-integration.md +945 -0
- package/docs/blog-mobile-gateway.md +542 -542
- package/docs/dsh-0.1.5-rc.2-compatibility-audit.md +255 -255
- package/docs/dsh-0.1.6-alpha.1-compatibility-audit.md +122 -122
- package/docs/dsh-0.1.7-rc.1-compatibility-audit.md +52 -52
- package/docs/dsh-0.2.0-rc.2-plugin-management-mobile-integration.md +109 -109
- package/docs/dsh-rc2-mobile-integration.md +155 -155
- package/docs/file-upload-app-integration.md +250 -0
- package/docs/multi-gateway-app-integration.md +141 -125
- package/docs/multi-gateway-phase1-acceptance.md +179 -179
- package/docs/multi-gateway-todo.md +137 -137
- package/docs/remote-gateway-refactor-plan.md +52 -52
- package/docs/runtime-architecture.architecture.json +264 -264
- package/docs/runtime-architecture.html +15001 -15001
- package/docs/runtime-architecture.visual-check.html +32 -32
- package/docs/runtime-architecture.visual-check.json +548 -548
- package/docs/session-agent-preset-app-integration.md +93 -93
- package/docs/turn-outline-app-integration.md +196 -0
- package/docs/typert-remote-gateway-feature-checklist.md +294 -294
- package/docs/voice-input-implementation.md +219 -219
- package/docs/workspace-changes-mobile-integration.md +32 -32
- package/helper/dsh_mobile_gateway_helper.py +227 -227
- package/lib/account-bridge.mjs +57 -57
- package/lib/client.js +1409 -1361
- package/lib/cloudflare-tunnel.mjs +314 -314
- package/lib/cloudflared-binary.mjs +135 -135
- package/lib/context-bridge.mjs +3338 -1715
- package/lib/cost-meter-bridge.mjs +794 -794
- package/lib/cost-meter-dashboard-bridge.mjs +1655 -1449
- package/lib/devices.js +273 -245
- package/lib/dsh-host-adapter.mjs +508 -426
- package/lib/file-upload.mjs +205 -0
- package/lib/gateway-probe.mjs +6 -6
- package/lib/gateway-state.mjs +57 -57
- package/lib/gateway-tools.mjs +36 -36
- package/lib/history-cache.mjs +66 -0
- package/lib/index.mjs +4390 -3803
- package/lib/plugin-management.mjs +251 -233
- package/lib/public-endpoint-check.mjs +61 -61
- package/lib/relay-connector.mjs +213 -181
- package/lib/relay-state.mjs +83 -76
- package/lib/session-follower.mjs +140 -136
- package/lib/session-resume-bridge.mjs +86 -0
- package/lib/session-target.mjs +29 -0
- package/lib/speech-input.mjs +192 -192
- package/lib/vendor/cost-meter/aliyun-balance.mjs +123 -123
- package/lib/vendor/cost-meter/coding-plans.mjs +1668 -1668
- package/lib/vendor/cost-meter/credentials-shim.mjs +8 -8
- package/lib/vendor/cost-meter/custom-balance.mjs +281 -281
- package/lib/vendor/cost-meter/factory-quota.mjs +69 -69
- package/lib/vendor/cost-meter/gateway-quota-adapters.mjs +1006 -1006
- package/lib/vendor/cost-meter/gateway-quotas.mjs +664 -664
- package/lib/vendor/cost-meter/home-paths-shim.mjs +13 -13
- package/lib/vendor/cost-meter/net.mjs +183 -183
- package/lib/vendor/cost-meter/opendesign.mjs +204 -204
- package/lib/vendor/cost-meter/tokenrhythm-balance.mjs +95 -95
- package/lib/vendor/cost-meter/workbuddy-accounts.mjs +44 -44
- package/lib/vendor/cost-meter/workbuddy-transport.mjs +38 -38
- package/lib/vendor/cost-meter/workbuddy.mjs +356 -356
- package/lib/web-restart.mjs +47 -47
- package/lib/wire-json.mjs +7 -7
- package/lib/workspace-changes-bridge.mjs +32 -32
- package/package.json +54 -54
- package/relay/agent-connector.js +588 -569
- package/relay/vendor/tweetnacl.js +2391 -2391
|
@@ -1,233 +1,251 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
'
|
|
15
|
-
'
|
|
16
|
-
'
|
|
17
|
-
'
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
'plugin-
|
|
22
|
-
'plugin-
|
|
23
|
-
'plugin-
|
|
24
|
-
'plugin-
|
|
25
|
-
'plugin-
|
|
26
|
-
'plugin-
|
|
27
|
-
'plugin-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
'plugin-
|
|
32
|
-
'plugin-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
if (
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
} else if (type === 'plugin-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
} else if (type === 'plugin-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
if (msg.
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
value
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
}
|
|
1
|
+
// 官方配置项图标:与 DSH 客户端注册表一致(图标资源取自 DSH 客户端,MIT)。
|
|
2
|
+
// 同步给移动端,避免客户端各自内置一份导致版本漂移。
|
|
3
|
+
const OFFICIAL_ITEM_ICONS = Object.freeze({
|
|
4
|
+
'subagent': '<svg width="36" height="36" viewBox="0 0 36 36" fill="none" xmlns="http://www.w3.org/2000/svg"><rect x="14.9893" y="15.1877" width="12.2365" height="12.2365" rx="2" fill="url(#dsh_plugin_art_R0a)" fill-opacity="0.8"></rect><rect x="8.87109" y="9.0697" width="12.2365" height="12.2365" rx="2" fill="url(#dsh_plugin_art_R0b)" fill-opacity="0.8"></rect><defs><linearGradient id="dsh_plugin_art_R0a" x1="21.1075" y1="15.1877" x2="21.1075" y2="27.4243" gradientUnits="userSpaceOnUse"><stop stop-color="#45E7A4"></stop><stop offset="1" stop-color="#05909D"></stop></linearGradient><linearGradient id="dsh_plugin_art_R0b" x1="14.9894" y1="9.0697" x2="14.9894" y2="21.3063" gradientUnits="userSpaceOnUse"><stop stop-color="#69B9FF"></stop><stop offset="1" stop-color="#324DE2"></stop></linearGradient></defs></svg>',
|
|
5
|
+
'web-search': '<svg xmlns="http://www.w3.org/2000/svg" width="36" height="36" fill="none" viewBox="0 0 36 36"><path stroke="#658EFF" stroke-width="3" d="m26.536 26.987-4.155-4.155"/><circle cx="15.766" cy="16.216" r="6.206" stroke="url(#a)" stroke-width="4"/><defs><linearGradient id="a" x1="7.561" x2="23.972" y1="8.01" y2="24.422" gradientUnits="userSpaceOnUse"><stop stop-color="#41E1AC"/><stop offset=".65" stop-color="#658EFF"/><stop offset="1" stop-color="#41E1AC"/></linearGradient></defs></svg>',
|
|
6
|
+
'shell': '<svg xmlns="http://www.w3.org/2000/svg" width="36" height="36" fill="none" viewBox="0 0 36 36"><path stroke="#679EFE" stroke-width="3.5" d="m10 11 6.606 6.606a.2.2 0 0 1 0 .283L10 24.495m10.121 0h6.748"/></svg>',
|
|
7
|
+
'agent-loop': '<svg xmlns="http://www.w3.org/2000/svg" width="36" height="36" fill="none" viewBox="0 0 36 36"><path fill="#A797FC" d="M18.049 28.49c-5.963 0-10.797-4.834-10.797-10.797 5.963 0 10.797 4.834 10.797 10.797"/><path fill="url(#a)" d="M18.049 6.897c-5.963 0-10.797 4.833-10.797 10.796 5.963 0 10.797-4.834 10.797-10.796"/><path fill="#4561EE" d="M18.049 28.49c5.962 0 10.796-4.834 10.796-10.797-5.963 0-10.796 4.834-10.796 10.797"/><path fill="#658EFF" d="M18.049 6.897c5.962 0 10.796 4.833 10.796 10.796-5.963 0-10.796-4.834-10.796-10.796"/><defs><linearGradient id="a" x1="16.791" x2="8.982" y1="16.166" y2="8.743" gradientUnits="userSpaceOnUse"><stop stop-color="#A23AE7"/><stop offset="1" stop-color="#E2E2E2"/></linearGradient></defs></svg>',
|
|
8
|
+
})
|
|
9
|
+
|
|
10
|
+
// 插件自身未提供图标时,DSH 客户端使用的默认图标(同样随目录下发)。
|
|
11
|
+
const DEFAULT_PLUGIN_ICON = '<svg xmlns="http://www.w3.org/2000/svg" width="70" height="66" viewBox="0 0 70 66" fill="none">\n <defs>\n <linearGradient id="dshPlugArt" x1="62" y1="4" x2="12" y2="62" gradientUnits="userSpaceOnUse">\n <stop stop-color="#8FE0EA"/><stop offset=".45" stop-color="#6FA8FF"/><stop offset="1" stop-color="#5C8CFF"/>\n </linearGradient>\n </defs>\n <path d="M51.00 2.00 Q61.50 2.00 64.50 5.00 Q67.50 8.00 68.00 12.25 Q68.50 16.50 67.50 19.50 Q66.50 22.50 64.50 24.00 Q62.50 25.50 57.25 26.00 Q52.00 26.50 49.75 28.25 Q47.50 30.00 48.25 33.00 Q49.00 36.00 55.75 37.00 Q62.50 38.00 64.50 39.50 Q66.50 41.00 67.50 44.00 Q68.50 47.00 68.00 52.75 Q67.50 58.50 64.50 61.00 Q61.50 63.50 54.25 63.50 Q47.00 63.50 44.50 60.50 Q42.00 57.50 42.00 52.75 Q42.00 48.00 40.75 46.25 Q39.50 44.50 34.25 45.00 Q29.00 45.50 27.75 47.25 Q26.50 49.00 26.50 52.25 Q26.50 55.50 25.50 58.00 Q24.50 60.50 22.00 62.00 Q19.50 63.50 12.75 63.50 Q6.00 63.50 6.50 62.75 Q7.00 62.00 6.00 61.75 Q5.00 61.50 3.50 58.50 Q2.00 55.50 2.00 50.75 Q2.00 46.00 3.00 44.50 Q4.00 43.00 7.50 41.00 Q11.00 39.00 14.75 39.00 Q18.50 39.00 20.75 37.25 Q23.00 35.50 23.50 28.75 Q24.00 22.00 26.00 21.00 Q28.00 20.00 35.25 19.50 Q42.50 19.00 44.25 12.00 Q46.00 5.00 48.50 3.75 L51.00 2.50 Z" fill="url(#dshPlugArt)"/>\n <circle cx="11" cy="10.5" r="9" fill="#5CD0F1"/>\n</svg>'
|
|
12
|
+
|
|
13
|
+
const OFFICIAL_CONFIGURATION_ITEMS = Object.freeze([
|
|
14
|
+
{ id: 'shell', namespaces: ['bash-sandbox', 'pwsh-sandbox'] },
|
|
15
|
+
{ id: 'agent-loop', namespaces: ['agent-loop'] },
|
|
16
|
+
{ id: 'subagent', namespaces: ['subagent', 'subagent-model-selection-settings'] },
|
|
17
|
+
{ id: 'web-search', namespaces: ['web-search-deepseek'] },
|
|
18
|
+
])
|
|
19
|
+
|
|
20
|
+
export const PLUGIN_REQUEST_TYPES = new Set([
|
|
21
|
+
'plugin-catalog',
|
|
22
|
+
'plugin-market-indexes',
|
|
23
|
+
'plugin-market-set-indexes',
|
|
24
|
+
'plugin-market-search',
|
|
25
|
+
'plugin-registries',
|
|
26
|
+
'plugin-inspect',
|
|
27
|
+
'plugin-install',
|
|
28
|
+
'plugin-install-status',
|
|
29
|
+
'plugin-install-cancel',
|
|
30
|
+
'plugin-bundle-set-enabled',
|
|
31
|
+
'plugin-entry-set-enabled',
|
|
32
|
+
'plugin-remove',
|
|
33
|
+
'plugin-credential-describe',
|
|
34
|
+
'plugin-credential-set',
|
|
35
|
+
'plugin-translation-limits',
|
|
36
|
+
'plugin-settings',
|
|
37
|
+
'plugin-settings-mutate',
|
|
38
|
+
'plugin-version-exemptions',
|
|
39
|
+
'plugin-version-exemption-set',
|
|
40
|
+
])
|
|
41
|
+
|
|
42
|
+
const FLAT_RESPONSE_TYPES = new Set([
|
|
43
|
+
'plugin-catalog', 'plugin-registries', 'plugin-settings',
|
|
44
|
+
'plugin-settings-mutate', 'plugin-version-exemptions',
|
|
45
|
+
])
|
|
46
|
+
|
|
47
|
+
function record(value) {
|
|
48
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function nonempty(value, maxLength = 2048) {
|
|
52
|
+
return typeof value === 'string' && value.trim().length > 0 && value.length <= maxLength
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function badRequest(type, requestId, message) {
|
|
56
|
+
return { kind: 'error', code: 'bad-request', message, requestType: type,
|
|
57
|
+
...(requestId === undefined ? {} : { requestId }) }
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function registryOption(msg, type, requestId) {
|
|
61
|
+
if (msg.registry === undefined) return { value: undefined }
|
|
62
|
+
if (msg.registry === null || (nonempty(msg.registry) && /^https?:\/\//i.test(msg.registry))) {
|
|
63
|
+
return { value: msg.registry }
|
|
64
|
+
}
|
|
65
|
+
return { error: badRequest(type, requestId, 'registry must be null or an HTTP(S) URL') }
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function requireName(msg, field, type, requestId) {
|
|
69
|
+
if (!nonempty(msg[field], 512)) return { error: badRequest(type, requestId, `${field} must be a non-empty string`) }
|
|
70
|
+
return { value: msg[field].trim() }
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async function pluginCatalog(api) {
|
|
74
|
+
const inventory = await api.plugins.inventory()
|
|
75
|
+
if (!record(inventory) || !Array.isArray(inventory.entries)) throw new Error('pluginInventory/list returned an invalid inventory')
|
|
76
|
+
if (inventory.managementAvailable !== true) {
|
|
77
|
+
return { managementAvailable: false, bundles: [], plugins: [], officialItems: [], settingsWritable: false }
|
|
78
|
+
}
|
|
79
|
+
const [bundles, plugins, settings] = await Promise.all([
|
|
80
|
+
api.plugins.listBundles(),
|
|
81
|
+
api.plugins.listPlugins(),
|
|
82
|
+
api.settings.describe(),
|
|
83
|
+
])
|
|
84
|
+
if (!Array.isArray(bundles) || !Array.isArray(plugins) || !Array.isArray(settings?.namespaces)) {
|
|
85
|
+
throw new Error('plugin manager returned an invalid catalog')
|
|
86
|
+
}
|
|
87
|
+
const available = new Set(settings.namespaces.map(item => item.ns))
|
|
88
|
+
const officialItems = OFFICIAL_CONFIGURATION_ITEMS
|
|
89
|
+
.filter(item => item.namespaces.some(ns => available.has(ns)))
|
|
90
|
+
.map(item => ({
|
|
91
|
+
id: item.id,
|
|
92
|
+
namespaces: item.namespaces.filter(ns => available.has(ns)),
|
|
93
|
+
icon: OFFICIAL_ITEM_ICONS[item.id] || null,
|
|
94
|
+
}))
|
|
95
|
+
return {
|
|
96
|
+
managementAvailable: true,
|
|
97
|
+
bundles: bundles.map(bundle => ({
|
|
98
|
+
...bundle,
|
|
99
|
+
experimental: bundle.name.startsWith('@deepseek-ai/dsh-experimental-'),
|
|
100
|
+
})),
|
|
101
|
+
plugins,
|
|
102
|
+
officialItems,
|
|
103
|
+
// 插件自身无图标时客户端用它(与 DSH 客户端默认图标一致)
|
|
104
|
+
defaultPluginIcon: DEFAULT_PLUGIN_ICON,
|
|
105
|
+
settingsWritable: settings.writable === true,
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* The mobile wire owns its request names and correlation ids. The Host adapter
|
|
111
|
+
* owns Typert endpoint names and argument descriptors.
|
|
112
|
+
*/
|
|
113
|
+
export async function handlePluginQuery(api, msg, { onInstallStart, onInstallEnd } = {}) {
|
|
114
|
+
const type = msg?.type
|
|
115
|
+
if (!PLUGIN_REQUEST_TYPES.has(type)) return null
|
|
116
|
+
const requestId = nonempty(msg.requestId, 128) ? msg.requestId.trim() : undefined
|
|
117
|
+
if (!requestId) return badRequest(type, undefined, `${type} requires a requestId`)
|
|
118
|
+
|
|
119
|
+
try {
|
|
120
|
+
let value
|
|
121
|
+
let kind = type
|
|
122
|
+
if (type === 'plugin-market-indexes') {
|
|
123
|
+
value = await api.pluginMarket.listIndexes()
|
|
124
|
+
} else if (type === 'plugin-market-set-indexes') {
|
|
125
|
+
if (!Array.isArray(msg.urls) || msg.urls.length > 100 || msg.urls.some(url => !nonempty(url, 2048))) return badRequest(type, requestId, 'urls must be an array of index URLs')
|
|
126
|
+
// Host validates and normalizes new URLs and preserves existing invalid sources for removal.
|
|
127
|
+
await api.pluginMarket.setIndexes(msg.urls)
|
|
128
|
+
value = { saved: true }
|
|
129
|
+
} else if (type === 'plugin-market-search') {
|
|
130
|
+
if (!nonempty(msg.url, 2048) || !record(msg.query)) return badRequest(type, requestId, 'url and query are required')
|
|
131
|
+
const query = msg.query
|
|
132
|
+
if (!Number.isSafeInteger(query.offset) || query.offset < 0 || !Number.isInteger(query.limit) || query.limit < 1 || query.limit > 100 || (query.text !== undefined && (typeof query.text !== 'string' || query.text.length > 2048))) return badRequest(type, requestId, 'invalid market query')
|
|
133
|
+
if (!record(query.sort) || query.sort.kind !== 'index') return badRequest(type, requestId, 'unsupported sort')
|
|
134
|
+
if (query.fetchedAt !== undefined && (typeof query.fetchedAt !== 'number' || !Number.isFinite(query.fetchedAt))) return badRequest(type, requestId, 'invalid fetchedAt')
|
|
135
|
+
if (query.category !== undefined && !nonempty(query.category, 2048)) return badRequest(type, requestId, 'invalid category')
|
|
136
|
+
value = await api.pluginMarket.search(msg.url, { offset: query.offset, limit: query.limit, sort: { kind: 'index' }, ...(query.category === undefined ? {} : { category: query.category }), ...(query.text ? { text: query.text } : {}), ...(query.fetchedAt === undefined ? {} : { fetchedAt: query.fetchedAt }) })
|
|
137
|
+
} else if (type === 'plugin-catalog') {
|
|
138
|
+
value = await pluginCatalog(api)
|
|
139
|
+
} else if (type === 'plugin-registries') {
|
|
140
|
+
value = await api.plugins.registries()
|
|
141
|
+
} else if (type === 'plugin-inspect' || type === 'plugin-install') {
|
|
142
|
+
const spec = requireName(msg, 'spec', type, requestId)
|
|
143
|
+
if (spec.error) return spec.error
|
|
144
|
+
const registry = registryOption(msg, type, requestId)
|
|
145
|
+
if (registry.error) return registry.error
|
|
146
|
+
if (type === 'plugin-inspect') {
|
|
147
|
+
value = await api.plugins.inspect(spec.value, registry.value === undefined ? undefined : { registry: registry.value })
|
|
148
|
+
kind = 'plugin-inspection'
|
|
149
|
+
} else {
|
|
150
|
+
if (msg.enabled !== undefined && typeof msg.enabled !== 'boolean') {
|
|
151
|
+
return badRequest(type, requestId, 'enabled must be a boolean')
|
|
152
|
+
}
|
|
153
|
+
if (msg.approvedBuilds !== undefined && (!Array.isArray(msg.approvedBuilds)
|
|
154
|
+
|| msg.approvedBuilds.length > 100 || msg.approvedBuilds.some(name => !nonempty(name, 512)))) {
|
|
155
|
+
return badRequest(type, requestId, 'approvedBuilds must be an array of package names')
|
|
156
|
+
}
|
|
157
|
+
const options = {
|
|
158
|
+
requestId,
|
|
159
|
+
...(msg.enabled === undefined ? {} : { enabled: msg.enabled }),
|
|
160
|
+
...(registry.value === undefined ? {} : { registry: registry.value }),
|
|
161
|
+
...(msg.approvedBuilds === undefined ? {} : { approvedBuilds: msg.approvedBuilds }),
|
|
162
|
+
}
|
|
163
|
+
onInstallStart?.(requestId)
|
|
164
|
+
try {
|
|
165
|
+
value = await api.plugins.installBundle(spec.value, options)
|
|
166
|
+
} finally {
|
|
167
|
+
onInstallEnd?.(requestId)
|
|
168
|
+
}
|
|
169
|
+
kind = 'plugin-install-result'
|
|
170
|
+
}
|
|
171
|
+
} else if (type === 'plugin-install-status' || type === 'plugin-install-cancel') {
|
|
172
|
+
const installId = requireName(msg, 'installId', type, requestId)
|
|
173
|
+
if (installId.error) return installId.error
|
|
174
|
+
value = type === 'plugin-install-status'
|
|
175
|
+
? await api.plugins.waitForInstall(installId.value)
|
|
176
|
+
: await api.plugins.cancelInstall(installId.value)
|
|
177
|
+
} else if (type === 'plugin-bundle-set-enabled' || type === 'plugin-entry-set-enabled') {
|
|
178
|
+
const field = type === 'plugin-bundle-set-enabled' ? 'name' : 'entryId'
|
|
179
|
+
const target = requireName(msg, field, type, requestId)
|
|
180
|
+
if (target.error) return target.error
|
|
181
|
+
if (typeof msg.enabled !== 'boolean') return badRequest(type, requestId, 'enabled must be a boolean')
|
|
182
|
+
value = type === 'plugin-bundle-set-enabled'
|
|
183
|
+
? await api.plugins.setBundleEnabled(target.value, msg.enabled)
|
|
184
|
+
: await api.plugins.setPluginEnabled(target.value, msg.enabled)
|
|
185
|
+
} else if (type === 'plugin-remove') {
|
|
186
|
+
const name = requireName(msg, 'name', type, requestId)
|
|
187
|
+
if (name.error) return name.error
|
|
188
|
+
value = await api.plugins.removeBundle(name.value)
|
|
189
|
+
} else if (type === 'plugin-translation-limits') {
|
|
190
|
+
value = await api.pluginConfiguration.translationLimits()
|
|
191
|
+
} else if (type === 'plugin-credential-describe' || type === 'plugin-credential-set') {
|
|
192
|
+
if (typeof msg.ref !== 'string' || !/^[A-Za-z_][A-Za-z0-9_]{0,255}$/.test(msg.ref)) return badRequest(type, requestId, 'invalid credential reference')
|
|
193
|
+
const settings = await api.settings.describe()
|
|
194
|
+
const scopes = (settings.namespaces || []).filter(item => ['web-search-deepseek', 'web-deepseek-api'].includes(item.ns))
|
|
195
|
+
if (!scopes.some(item => (item.value?.apiKeyEnv || 'DEEPSEEK_API_KEY') === msg.ref)) return badRequest(type, requestId, 'reference is not used by web search')
|
|
196
|
+
if (type === 'plugin-credential-set') {
|
|
197
|
+
if (settings.writable !== true) return badRequest(type, requestId, 'settings are read-only')
|
|
198
|
+
if (typeof msg.value !== 'string' || !msg.value.trim() || msg.value.length > 8192) return badRequest(type, requestId, 'invalid credential value')
|
|
199
|
+
try { await api.pluginConfiguration.credentialSet(msg.ref, msg.value) }
|
|
200
|
+
catch { return { kind: 'error', code: 'plugin-credentials/write-failed', message: 'Credential write failed', requestId, requestType: type } }
|
|
201
|
+
value = { saved: true }
|
|
202
|
+
} else {
|
|
203
|
+
const descriptions = await api.pluginConfiguration.credentialDescribe(msg.ref)
|
|
204
|
+
const view = descriptions[msg.ref]
|
|
205
|
+
value = { configured: view?.configured === true, writable: settings.writable === true && view?.writable === true }
|
|
206
|
+
}
|
|
207
|
+
} else if (type === 'plugin-settings') {
|
|
208
|
+
const described = await api.settings.describe()
|
|
209
|
+
if (!record(described) || !Array.isArray(described.namespaces)) throw new Error('settings/describe returned an invalid response')
|
|
210
|
+
if (msg.ns !== undefined) {
|
|
211
|
+
const ns = requireName(msg, 'ns', type, requestId)
|
|
212
|
+
if (ns.error) return ns.error
|
|
213
|
+
const namespace = described.namespaces.find(item => item.ns === ns.value)
|
|
214
|
+
if (!namespace) return { kind: 'error', code: 'plugin-settings/not-found',
|
|
215
|
+
message: `settings namespace is unavailable: ${ns.value}`, requestType: type, requestId }
|
|
216
|
+
value = { writable: described.writable === true, namespace }
|
|
217
|
+
} else {
|
|
218
|
+
value = described
|
|
219
|
+
}
|
|
220
|
+
} else if (type === 'plugin-settings-mutate') {
|
|
221
|
+
const ns = requireName(msg, 'ns', type, requestId)
|
|
222
|
+
if (ns.error) return ns.error
|
|
223
|
+
if (!Number.isSafeInteger(msg.expectedRevision) || msg.expectedRevision < 0) {
|
|
224
|
+
return badRequest(type, requestId, 'expectedRevision must be a non-negative integer')
|
|
225
|
+
}
|
|
226
|
+
if (!Array.isArray(msg.ops) || msg.ops.length < 1 || msg.ops.length > 100
|
|
227
|
+
|| msg.ops.some(op => !record(op) || !['set', 'unset'].includes(op.op)
|
|
228
|
+
|| !Array.isArray(op.path) || op.path.some(part => !nonempty(part, 256))
|
|
229
|
+
|| (op.op === 'set' && !Object.hasOwn(op, 'value')))) {
|
|
230
|
+
return badRequest(type, requestId, 'ops must contain valid set or unset path edits')
|
|
231
|
+
}
|
|
232
|
+
value = await api.settings.mutate({ ns: ns.value, ops: msg.ops, expectedRevision: msg.expectedRevision })
|
|
233
|
+
} else if (type === 'plugin-version-exemptions') {
|
|
234
|
+
value = await api.plugins.listVersionExemptions()
|
|
235
|
+
} else if (type === 'plugin-version-exemption-set') {
|
|
236
|
+
const packageVersion = requireName(msg, 'packageVersion', type, requestId)
|
|
237
|
+
const runtimeVersion = requireName(msg, 'runtimeVersion', type, requestId)
|
|
238
|
+
if (packageVersion.error || runtimeVersion.error) return packageVersion.error || runtimeVersion.error
|
|
239
|
+
if (typeof msg.enabled !== 'boolean' || (msg.acceptRisk !== undefined && typeof msg.acceptRisk !== 'boolean')) {
|
|
240
|
+
return badRequest(type, requestId, 'enabled and acceptRisk must be booleans')
|
|
241
|
+
}
|
|
242
|
+
value = await api.plugins.setVersionExemption(packageVersion.value, runtimeVersion.value,
|
|
243
|
+
msg.enabled, msg.acceptRisk)
|
|
244
|
+
}
|
|
245
|
+
return { kind, requestId, ...(FLAT_RESPONSE_TYPES.has(type) && record(value)
|
|
246
|
+
? value : { result: value }) }
|
|
247
|
+
} catch (error) {
|
|
248
|
+
return { kind: 'error', code: error?.code || 'internal',
|
|
249
|
+
message: error?.message || String(error), requestType: type, requestId }
|
|
250
|
+
}
|
|
251
|
+
}
|