dsh-web-search-firecrawl 0.1.1 → 0.1.4
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 +21 -9
- package/lib/client.js +36 -23
- package/lib/client.js.map +1 -1
- package/lib/dsh-web-search-firecrawl.js +28 -17
- package/lib/types/client/accounts-controller.d.ts +4 -3
- package/lib/types/dsh-web-search-firecrawl.d.ts +19 -7
- package/package.json +29 -21
package/README.md
CHANGED
|
@@ -1,28 +1,40 @@
|
|
|
1
1
|
# dsh-web-search-firecrawl
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
## 安装
|
|
4
|
+
|
|
5
|
+
### DSH Studio 桌面 App(推荐)
|
|
6
|
+
|
|
7
|
+
打开 **设置 → 插件 → 添加插件**,在“包名或地址”中输入:
|
|
8
|
+
|
|
9
|
+
```text
|
|
10
|
+
github:aa2246740/dsh-firecrawl#v0.1.4
|
|
5
11
|
```
|
|
6
12
|
|
|
7
|
-
|
|
13
|
+
桌面端插件管理器负责 Desktop profile 和内置包管理器。本发布已包含编译好的 `lib/`;普通使用不需要 clone 或本地构建。若应用提示刷新或重新打开,请按提示完成。
|
|
14
|
+
|
|
15
|
+
### Web CLI
|
|
16
|
+
|
|
17
|
+
```sh
|
|
18
|
+
dsh plugin --profile web add github:aa2246740/dsh-firecrawl#v0.1.4
|
|
19
|
+
```
|
|
8
20
|
|
|
9
|
-
|
|
21
|
+
这条官方 CLI 命令只写入 `web` profile,不能修改 Desktop App 的 profile。对于已经运行的 Web Host,请重新打开该 Host 一次,再刷新网页。
|
|
10
22
|
|
|
11
23
|
给 DeepSeek Harness 的 `web_search` 换 Firecrawl,并带多账号池。包名仍是 `dsh-web-search-firecrawl`。仓库是 `dsh-firecrawl`。
|
|
12
24
|
|
|
13
25
|
在 `ctx.web` 上注册 `id: firecrawl` 的 `WebSearchProvider`,走 Firecrawl `/v1/search`。调度默认 `round-robin`,也可 `least-loaded`。`429` 或 `5xx` 切到下一个健康账号,默认冷却 60 秒。不可重试的 `4xx` 立刻失败并点名那个账号。
|
|
14
26
|
|
|
15
|
-
这个 bundle 会把官方 `web.searchProvider` 设成 `firecrawl`,并保留 `fetchProvider: http
|
|
27
|
+
这个 bundle 会把官方 `web.searchProvider` 设成 `firecrawl`,并保留 `fetchProvider: http`。账号在侧栏 **插件** 里打开已安装的 `dsh-web-search-firecrawl`,再点该行的 **配置**。
|
|
16
28
|
|
|
17
|
-
面向官方 DeepSeek Harness **0.1.
|
|
29
|
+
面向官方 DeepSeek Harness **0.2.0-rc.2**(`@deepseek-ai/dsh@0.2.0-rc.2`)。`@deepseek-ai/dsh` 与同名 `@deepseek-ai/dsh-*` peer 是 `>=0.2.0-rc.1 <0.2.1`:接受 `0.2.0-rc.2` 和稳定版 `0.2.0`,拒绝 `0.2.0` alpha,也拒绝 `0.1.7-rc.2`。
|
|
18
30
|
|
|
19
|
-
##
|
|
31
|
+
## 其它装法(开发/本地测试)
|
|
20
32
|
|
|
21
33
|
本地目录或 tarball:
|
|
22
34
|
|
|
23
35
|
```sh
|
|
24
36
|
dsh plugin --profile web add ./dsh-firecrawl
|
|
25
|
-
dsh plugin --profile web add ./dsh-web-search-firecrawl-0.1.
|
|
37
|
+
dsh plugin --profile web add ./dsh-web-search-firecrawl-0.1.4.tgz
|
|
26
38
|
```
|
|
27
39
|
|
|
28
40
|
`dsh.bundle` 是开机捕获的。不要再往 profile 的 `cordis.patch.yml` 手写同一条 insert,会重复挂载。
|
|
@@ -33,7 +45,7 @@ dsh plugin --profile web remove dsh-web-search-firecrawl
|
|
|
33
45
|
|
|
34
46
|
## 使用
|
|
35
47
|
|
|
36
|
-
1. 打开 WebUI
|
|
48
|
+
1. 打开 WebUI 侧栏 **插件**,进入 `dsh-web-search-firecrawl`,点 **配置**。
|
|
37
49
|
2. 加至少一个 Firecrawl API key([firecrawl.dev](https://firecrawl.dev) 的 `fc-...`),点 **保存**。密钥不会回显。改名时 key 留空表示保留原 key。
|
|
38
50
|
3. 用会暴露 `web_search` 的新会话。
|
|
39
51
|
|
package/lib/client.js
CHANGED
|
@@ -18,21 +18,24 @@ window.__ModuleLoader__.load({
|
|
|
18
18
|
//#region src/client/accounts-controller.ts
|
|
19
19
|
/** All edits use the namespace's revision fence and never read credential values. */
|
|
20
20
|
var AccountsController = class {
|
|
21
|
-
|
|
22
|
-
constructor(
|
|
23
|
-
this.
|
|
21
|
+
form;
|
|
22
|
+
constructor(form) {
|
|
23
|
+
this.form = form;
|
|
24
24
|
}
|
|
25
25
|
snapshot() {
|
|
26
|
-
const snapshot = this.
|
|
27
|
-
if (snapshot.status !== "ready" || !snapshot.writable) throw new Error("Settings are not writable");
|
|
26
|
+
const snapshot = this.form.getSnapshot();
|
|
27
|
+
if (snapshot.status !== "ready" || !snapshot.writable || snapshot.revision === void 0) throw new Error("Settings are not writable");
|
|
28
28
|
return {
|
|
29
29
|
accounts: metadataOnly(snapshot.value?.accounts ?? []),
|
|
30
30
|
revision: snapshot.revision
|
|
31
31
|
};
|
|
32
32
|
}
|
|
33
|
+
async commit(ops, revision) {
|
|
34
|
+
if (!await this.form.mutate(ops, revision)) throw new Error("Settings write was not accepted");
|
|
35
|
+
}
|
|
33
36
|
add(id) {
|
|
34
37
|
const { accounts, revision } = this.snapshot();
|
|
35
|
-
return this.
|
|
38
|
+
return this.commit([{
|
|
36
39
|
op: "set",
|
|
37
40
|
path: ["accounts"],
|
|
38
41
|
value: [...accounts, {
|
|
@@ -57,11 +60,11 @@ window.__ModuleLoader__.load({
|
|
|
57
60
|
path: ["apiKeys", id],
|
|
58
61
|
value: key.trim()
|
|
59
62
|
});
|
|
60
|
-
return this.
|
|
63
|
+
return this.commit(ops, revision);
|
|
61
64
|
}
|
|
62
65
|
remove(id) {
|
|
63
66
|
const { accounts, revision } = this.snapshot();
|
|
64
|
-
return this.
|
|
67
|
+
return this.commit([{
|
|
65
68
|
op: "set",
|
|
66
69
|
path: ["accounts"],
|
|
67
70
|
value: accounts.filter((account) => account.id !== id)
|
|
@@ -74,24 +77,27 @@ window.__ModuleLoader__.load({
|
|
|
74
77
|
//#endregion
|
|
75
78
|
//#region src/client/index.tsx
|
|
76
79
|
const NAMESPACE = "dsh-web-search-firecrawl";
|
|
80
|
+
/** `plugins.row.config` key: `<package name>#<row id>` from cordis.patch.yml. */
|
|
81
|
+
const ROW_KEY = "dsh-web-search-firecrawl#dsh-web-search-firecrawl";
|
|
77
82
|
const name = "dsh-web-search-firecrawl-client";
|
|
78
83
|
const inject = [
|
|
79
84
|
"slots",
|
|
80
|
-
"
|
|
85
|
+
"configForms",
|
|
81
86
|
"remote",
|
|
82
87
|
"remote.settings"
|
|
83
88
|
];
|
|
84
89
|
function apply(ctx) {
|
|
85
|
-
const
|
|
86
|
-
const accounts = new AccountsController(
|
|
87
|
-
ctx.slots.inject("
|
|
88
|
-
name: "
|
|
89
|
-
key:
|
|
90
|
-
}, () => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FirecrawlCard, {
|
|
91
|
-
|
|
90
|
+
const form = ctx.configForms.get(NAMESPACE);
|
|
91
|
+
const accounts = new AccountsController(form);
|
|
92
|
+
ctx.effect(() => ctx.configForms.whileServed([NAMESPACE], () => ctx.slots.inject("plugins.row.config", () => ctx.slots.register({
|
|
93
|
+
name: "plugins.row.config",
|
|
94
|
+
key: ROW_KEY
|
|
95
|
+
}, (props) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FirecrawlCard, {
|
|
96
|
+
view: props.view,
|
|
97
|
+
form,
|
|
92
98
|
accounts,
|
|
93
99
|
ctx
|
|
94
|
-
})));
|
|
100
|
+
})))));
|
|
95
101
|
}
|
|
96
102
|
const inputStyle = {
|
|
97
103
|
padding: "6px 8px",
|
|
@@ -150,7 +156,7 @@ window.__ModuleLoader__.load({
|
|
|
150
156
|
"aria-label": "Firecrawl API key",
|
|
151
157
|
style: inputStyle,
|
|
152
158
|
type: "password",
|
|
153
|
-
autoComplete: "
|
|
159
|
+
autoComplete: "new-password",
|
|
154
160
|
value: key,
|
|
155
161
|
placeholder: configured ? "留空保留已有 key" : "fc-...",
|
|
156
162
|
onChange: (event) => setKey(event.target.value)
|
|
@@ -173,8 +179,8 @@ window.__ModuleLoader__.load({
|
|
|
173
179
|
})]
|
|
174
180
|
});
|
|
175
181
|
}
|
|
176
|
-
function FirecrawlCard({
|
|
177
|
-
const snapshot = (0, react.useSyncExternalStore)(
|
|
182
|
+
function FirecrawlCard({ view = "page", form, accounts: controller, ctx }) {
|
|
183
|
+
const snapshot = (0, react.useSyncExternalStore)(form.subscribe.bind(form), form.getSnapshot.bind(form));
|
|
178
184
|
const [open, setOpen] = (0, react.useState)(false);
|
|
179
185
|
const [busy, setBusy] = (0, react.useState)(false);
|
|
180
186
|
const [error, setError] = (0, react.useState)("");
|
|
@@ -191,6 +197,7 @@ window.__ModuleLoader__.load({
|
|
|
191
197
|
current = false;
|
|
192
198
|
};
|
|
193
199
|
}, [snapshot.revision, ctx]);
|
|
200
|
+
if (view === "summary") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: "搜索账号与负载均衡" });
|
|
194
201
|
if (snapshot.status !== "ready") return null;
|
|
195
202
|
const section = snapshot.value ?? {};
|
|
196
203
|
async function run(action) {
|
|
@@ -273,7 +280,9 @@ window.__ModuleLoader__.load({
|
|
|
273
280
|
"aria-label": "账号策略",
|
|
274
281
|
style: inputStyle,
|
|
275
282
|
value: section.strategy ?? "round-robin",
|
|
276
|
-
onChange: (event) => act(() =>
|
|
283
|
+
onChange: (event) => act(() => form.set("strategy", event.target.value).then((ok) => {
|
|
284
|
+
if (!ok) throw new Error("refused");
|
|
285
|
+
})),
|
|
277
286
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
278
287
|
value: "round-robin",
|
|
279
288
|
children: "轮询"
|
|
@@ -293,7 +302,9 @@ window.__ModuleLoader__.load({
|
|
|
293
302
|
defaultValue: section.limit ?? 5,
|
|
294
303
|
onBlur: (event) => {
|
|
295
304
|
const value = Number(event.target.value);
|
|
296
|
-
if (Number.isInteger(value) && value > 0) act(() =>
|
|
305
|
+
if (Number.isInteger(value) && value > 0) act(() => form.set("limit", value).then((ok) => {
|
|
306
|
+
if (!ok) throw new Error("refused");
|
|
307
|
+
}));
|
|
297
308
|
}
|
|
298
309
|
})] }),
|
|
299
310
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", { children: ["冷却毫秒 ", /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
@@ -307,7 +318,9 @@ window.__ModuleLoader__.load({
|
|
|
307
318
|
defaultValue: section.cooldownMs ?? 6e4,
|
|
308
319
|
onBlur: (event) => {
|
|
309
320
|
const value = Number(event.target.value);
|
|
310
|
-
if (Number.isInteger(value) && value >= 0) act(() =>
|
|
321
|
+
if (Number.isInteger(value) && value >= 0) act(() => form.set("cooldownMs", value).then((ok) => {
|
|
322
|
+
if (!ok) throw new Error("refused");
|
|
323
|
+
}));
|
|
311
324
|
}
|
|
312
325
|
})] })
|
|
313
326
|
]
|
package/lib/client.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.js","names":["useState","useSyncExternalStore"],"sources":["../src/accounts.ts","../src/client/accounts-controller.ts","../src/client/index.tsx"],"sourcesContent":["import type { FirecrawlAccount } from './provider.js'\n\n/** Legacy secrets are accepted only for migration; new writes keep them separate. */\nexport interface AccountMetadata {\n id: string\n label: string\n apiKey?: string\n}\n\nexport interface AccountSettings {\n accounts?: AccountMetadata[]\n apiKeys?: Record<string, string>\n}\n\n/** Storage boundary: an unfinished account is not a usable credential. */\nexport function resolvedAccounts(config: AccountSettings): FirecrawlAccount[] {\n return (config.accounts ?? []).flatMap(account => {\n const apiKey = config.apiKeys?.[account.id] ?? account.apiKey\n return typeof apiKey === 'string' && apiKey.trim().length > 0\n ? [{ id: account.id, label: account.label, apiKey }]\n : []\n })\n}\n\n/** One atomic owner-side migration, never reconstructed from a redacted view. */\nexport function legacyAccountMigration(config: AccountSettings): AccountSettings | undefined {\n if (!(config.accounts ?? []).some(account => account.apiKey !== undefined)) return undefined\n const apiKeys = { ...config.apiKeys }\n const accounts = (config.accounts ?? []).map(({ id, label, apiKey }) => {\n if (!Object.hasOwn(apiKeys, id) && typeof apiKey === 'string' && apiKey.trim()) apiKeys[id] = apiKey\n return { id, label }\n })\n return { accounts, apiKeys }\n}\n\n/** Wire-safe metadata projection; never repeat any credential in an array write. */\nexport function metadataOnly(accounts: AccountMetadata[]): { id: string; label: string }[] {\n return accounts.map(({ id, label }) => ({ id, label }))\n}\n","import type { SettingsScope } from '@deepseek-ai/dsh-client-ui-settings/client'\nimport type { SettingsPathOpView } from '@deepseek-ai/dsh-settings/types'\nimport { metadataOnly, type AccountSettings } from '../accounts.js'\n\n/** All edits use the namespace's revision fence and never read credential values. */\nexport class AccountsController {\n constructor(private readonly scope: SettingsScope<AccountSettings>) {}\n\n private snapshot() {\n const snapshot = this.scope.getSnapshot()\n if (snapshot.status !== 'ready' || !snapshot.writable) throw new Error('Settings are not writable')\n return { accounts: metadataOnly(snapshot.value?.accounts ?? []), revision: snapshot.revision }\n }\n\n add(id: string): Promise<void> {\n const { accounts, revision } = this.snapshot()\n return this.scope.mutate([{ op: 'set', path: ['accounts'], value: [...accounts, { id, label: '' }] }], revision)\n }\n\n save(id: string, label: string, key: string): Promise<void> {\n const { accounts, revision } = this.snapshot()\n if (!accounts.some(account => account.id === id)) throw new Error('Account was removed')\n const ops: SettingsPathOpView[] = [{ op: 'set', path: ['accounts'], value: accounts.map(account => account.id === id ? { id, label } : account) }]\n if (key.trim()) ops.push({ op: 'set', path: ['apiKeys', id], value: key.trim() })\n return this.scope.mutate(ops, revision)\n }\n\n remove(id: string): Promise<void> {\n const { accounts, revision } = this.snapshot()\n return this.scope.mutate([\n { op: 'set', path: ['accounts'], value: accounts.filter(account => account.id !== id) },\n { op: 'unset', path: ['apiKeys', id] },\n ], revision)\n }\n}\n","import { useEffect, useState, useSyncExternalStore, type ReactNode } from 'react'\nimport type { Context } from '@deepseek-ai/cordis'\nimport type { SettingsScope } from '@deepseek-ai/dsh-client-ui-settings/client'\nimport type {} from '@deepseek-ai/dsh-client-ui-settings-plugins/client'\nimport type {} from '@deepseek-ai/dsh-client-ui-slots'\nimport type {} from '@deepseek-ai/dsh-client-ui-renderer/client'\nimport type { SettingsDescribeValue } from '@deepseek-ai/dsh-settings/types'\nimport type {} from '@deepseek-ai/dsh-api-remotes/client'\nimport type { AccountSettings, AccountMetadata } from '../accounts.js'\nimport { AccountsController } from './accounts-controller.js'\n\nconst NAMESPACE = 'dsh-web-search-firecrawl'\ninterface Section extends AccountSettings {\n limit?: number\n strategy?: 'round-robin' | 'least-loaded'\n cooldownMs?: number\n}\nexport const name = 'dsh-web-search-firecrawl-client'\nexport const inject = ['slots', 'settingsScope', 'remote', 'remote.settings']\n\nexport function apply(ctx: Context): void {\n const scope = ctx.settingsScope.bind<Section>({ namespace: NAMESPACE })\n const accounts = new AccountsController(scope)\n ctx.slots.inject('settings.plugin.item', () => ctx.slots.register({ name: 'settings.plugin.item', key: NAMESPACE },\n () => <FirecrawlCard scope={scope} accounts={accounts} ctx={ctx} />))\n}\n\nconst inputStyle = { padding: '6px 8px', border: '1px solid #8886', borderRadius: 6, background: 'transparent', color: 'inherit' }\nconst buttonStyle = { ...inputStyle, cursor: 'pointer' }\n\nfunction AccountRow({ account, configured, busy, save, remove }: {\n account: AccountMetadata; configured: boolean | undefined; busy: boolean\n save: (label: string, key: string) => Promise<void>; remove: () => void\n}): ReactNode {\n const [label, setLabel] = useState(account.label)\n const [key, setKey] = useState('')\n useEffect(() => { setLabel(account.label) }, [account.label])\n return <fieldset style={{ border: '1px solid #8884', borderRadius: 6, padding: 10, marginTop: 10 }} disabled={busy}>\n <legend>{account.label || '新账号'}</legend>\n <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap', alignItems: 'end' }}>\n <label style={{ display: 'grid', gap: 4 }}>名称\n <input aria-label=\"账号名称\" style={inputStyle} value={label} onChange={event => setLabel(event.target.value)} />\n </label>\n <label style={{ display: 'grid', gap: 4 }}>API key · {configured === undefined ? '状态待确认' : configured ? '已配置' : '未配置'}\n <input aria-label=\"Firecrawl API key\" style={inputStyle} type=\"password\" autoComplete=\"off\" value={key}\n placeholder={configured ? '留空保留已有 key' : 'fc-...'} onChange={event => setKey(event.target.value)} />\n </label>\n <button style={buttonStyle} onClick={() => { void save(label, key).then(() => setKey('')).catch(() => {}) }}>保存</button>\n <button style={buttonStyle} onClick={remove}>移除账号</button>\n </div>\n </fieldset>\n}\n\nfunction FirecrawlCard({ scope, accounts: controller, ctx }: { scope: SettingsScope<Section>; accounts: AccountsController; ctx: Context }): ReactNode {\n const snapshot = useSyncExternalStore(scope.subscribe.bind(scope), scope.getSnapshot.bind(scope))\n const [open, setOpen] = useState(false)\n const [busy, setBusy] = useState(false)\n const [error, setError] = useState('')\n const [configured, setConfigured] = useState<Record<string, boolean> | undefined>()\n // The API reports configured-state metadata, never credential values.\n useEffect(() => {\n let current = true\n setConfigured(undefined)\n void ctx.remote.settings.describe().then((result: { ok: true; value: SettingsDescribeValue } | { ok: false }) => {\n if (!current || !result.ok) return\n const ns = result.value.namespaces.find(item => item.ns === NAMESPACE)\n if (ns) setConfigured(Object.fromEntries(ns.secrets.filter(secret => secret.path[0] === 'apiKeys').map(secret => [secret.path[1], secret.set])))\n }).catch(() => {})\n return () => { current = false }\n }, [snapshot.revision, ctx])\n\n if (snapshot.status !== 'ready') return null\n const section = snapshot.value ?? {}\n async function run(action: () => Promise<void>): Promise<void> {\n setBusy(true)\n setError('')\n try { await action() }\n catch (error) { setError('保存失败,未确认写入。请重新打开设置后重试。'); throw error }\n finally { setBusy(false) }\n }\n function act(action: () => Promise<void>): void { void run(action).catch(() => {}) }\n const disabled = busy || !snapshot.writable\n return <li style={{ listStyle: 'none', border: '1px solid #8884', borderRadius: 8, margin: '8px 0' }}>\n <button style={{ ...buttonStyle, border: 0, width: '100%', textAlign: 'left', padding: 14 }} aria-expanded={open} onClick={() => setOpen(!open)}>\n <strong>Firecrawl search</strong><div style={{ opacity: 0.7, fontSize: 12 }}>搜索账号与负载均衡 · {open ? '收起' : '展开'}</div>\n </button>\n {open && <div style={{ padding: '0 14px 14px' }}>\n {(section.accounts ?? []).map(account => <AccountRow key={account.id} account={account}\n configured={configured === undefined ? undefined : configured[account.id] === true} busy={disabled}\n save={(label, key) => run(() => controller.save(account.id, label, key))}\n remove={() => act(() => controller.remove(account.id))} />)}\n <button style={{ ...buttonStyle, marginTop: 10 }} disabled={disabled} onClick={() => act(() => controller.add(crypto.randomUUID()))}>添加账号</button>\n <p style={{ fontSize: 12, opacity: 0.75 }}>Key 只写入、不回显。填写后点“保存”;只改名称不会清除已有 key。</p>\n <fieldset disabled={disabled} style={{ border: 0, padding: 0, display: 'flex', gap: 12, flexWrap: 'wrap' }}>\n <label>策略 <select aria-label=\"账号策略\" style={inputStyle} value={section.strategy ?? 'round-robin'} onChange={event => act(() => scope.set('strategy', event.target.value))}>\n <option value=\"round-robin\">轮询</option><option value=\"least-loaded\">最少使用</option>\n </select></label>\n <label>结果数 <input aria-label=\"搜索结果数\" style={{ ...inputStyle, width: 65 }} type=\"number\" min={1} defaultValue={section.limit ?? 5}\n onBlur={event => { const value = Number(event.target.value); if (Number.isInteger(value) && value > 0) act(() => scope.set('limit', value)) }} /></label>\n <label>冷却毫秒 <input aria-label=\"冷却毫秒\" style={{ ...inputStyle, width: 90 }} type=\"number\" min={0} defaultValue={section.cooldownMs ?? 60000}\n onBlur={event => { const value = Number(event.target.value); if (Number.isInteger(value) && value >= 0) act(() => scope.set('cooldownMs', value)) }} /></label>\n </fieldset>\n {error && <p role=\"alert\">{error}</p>}\n <p style={{ fontSize: 12, opacity: 0.75 }}>这个 bundle 会把 <code>web.searchProvider</code> 设为 <code>firecrawl</code>。加好账号后,用会暴露 <code>web_search</code> 的新会话。</p>\n </div>}\n </li>\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"client.js","names":["useState","useSyncExternalStore"],"sources":["../src/accounts.ts","../src/client/accounts-controller.ts","../src/client/index.tsx"],"sourcesContent":["import type { FirecrawlAccount } from './provider.js'\n\n/** Legacy secrets are accepted only for migration; new writes keep them separate. */\nexport interface AccountMetadata {\n id: string\n label: string\n apiKey?: string\n}\n\nexport interface AccountSettings {\n accounts?: AccountMetadata[]\n apiKeys?: Record<string, string>\n}\n\n/** Storage boundary: an unfinished account is not a usable credential. */\nexport function resolvedAccounts(config: AccountSettings): FirecrawlAccount[] {\n return (config.accounts ?? []).flatMap(account => {\n const apiKey = config.apiKeys?.[account.id] ?? account.apiKey\n return typeof apiKey === 'string' && apiKey.trim().length > 0\n ? [{ id: account.id, label: account.label, apiKey }]\n : []\n })\n}\n\n/** One atomic owner-side migration, never reconstructed from a redacted view. */\nexport function legacyAccountMigration(config: AccountSettings): AccountSettings | undefined {\n if (!(config.accounts ?? []).some(account => account.apiKey !== undefined)) return undefined\n const apiKeys = { ...config.apiKeys }\n const accounts = (config.accounts ?? []).map(({ id, label, apiKey }) => {\n if (!Object.hasOwn(apiKeys, id) && typeof apiKey === 'string' && apiKey.trim()) apiKeys[id] = apiKey\n return { id, label }\n })\n return { accounts, apiKeys }\n}\n\n/** Wire-safe metadata projection; never repeat any credential in an array write. */\nexport function metadataOnly(accounts: AccountMetadata[]): { id: string; label: string }[] {\n return accounts.map(({ id, label }) => ({ id, label }))\n}\n","import type { ConfigForm } from '@deepseek-ai/dsh-client-ui-settings/client'\nimport type { SettingsPathOpView } from '@deepseek-ai/dsh-settings/types'\nimport { metadataOnly, type AccountSettings } from '../accounts.js'\n\n/** All edits use the namespace's revision fence and never read credential values. */\nexport class AccountsController {\n constructor(private readonly form: ConfigForm<AccountSettings>) {}\n\n private snapshot() {\n const snapshot = this.form.getSnapshot()\n if (snapshot.status !== 'ready' || !snapshot.writable || snapshot.revision === undefined) {\n throw new Error('Settings are not writable')\n }\n return { accounts: metadataOnly(snapshot.value?.accounts ?? []), revision: snapshot.revision }\n }\n\n private async commit(ops: readonly SettingsPathOpView[], revision: number): Promise<void> {\n const accepted = await this.form.mutate(ops, revision)\n if (!accepted) throw new Error('Settings write was not accepted')\n }\n\n add(id: string): Promise<void> {\n const { accounts, revision } = this.snapshot()\n return this.commit([{ op: 'set', path: ['accounts'], value: [...accounts, { id, label: '' }] }], revision)\n }\n\n save(id: string, label: string, key: string): Promise<void> {\n const { accounts, revision } = this.snapshot()\n if (!accounts.some(account => account.id === id)) throw new Error('Account was removed')\n const ops: SettingsPathOpView[] = [{ op: 'set', path: ['accounts'], value: accounts.map(account => account.id === id ? { id, label } : account) }]\n if (key.trim()) ops.push({ op: 'set', path: ['apiKeys', id], value: key.trim() })\n return this.commit(ops, revision)\n }\n\n remove(id: string): Promise<void> {\n const { accounts, revision } = this.snapshot()\n return this.commit([\n { op: 'set', path: ['accounts'], value: accounts.filter(account => account.id !== id) },\n { op: 'unset', path: ['apiKeys', id] },\n ], revision)\n }\n}\n","import { useEffect, useState, useSyncExternalStore, type ReactNode } from 'react'\nimport type { Context } from '@deepseek-ai/cordis'\nimport type { ConfigForm } from '@deepseek-ai/dsh-client-ui-settings/client'\nimport type {} from '@deepseek-ai/dsh-client-ui-settings/client'\nimport type {} from '@deepseek-ai/dsh-client-ui-plugin-manager/client'\nimport type {} from '@deepseek-ai/dsh-client-ui-slots'\nimport type {} from '@deepseek-ai/dsh-client-ui-renderer/client'\nimport type { SettingsDescribeValue } from '@deepseek-ai/dsh-settings/types'\nimport type {} from '@deepseek-ai/dsh-api-remotes/client'\nimport type { AccountSettings, AccountMetadata } from '../accounts.js'\nimport { AccountsController } from './accounts-controller.js'\n\nconst NAMESPACE = 'dsh-web-search-firecrawl'\n/** `plugins.row.config` key: `<package name>#<row id>` from cordis.patch.yml. */\nconst ROW_KEY = 'dsh-web-search-firecrawl#dsh-web-search-firecrawl'\ninterface Section extends AccountSettings {\n limit?: number\n strategy?: 'round-robin' | 'least-loaded'\n cooldownMs?: number\n}\nexport const name = 'dsh-web-search-firecrawl-client'\nexport const inject = ['slots', 'configForms', 'remote', 'remote.settings']\n\nexport function apply(ctx: Context): void {\n const form = ctx.configForms.get<Section>(NAMESPACE)\n const accounts = new AccountsController(form)\n ctx.effect(() => ctx.configForms.whileServed([NAMESPACE], () => ctx.slots.inject('plugins.row.config', () => ctx.slots.register({\n name: 'plugins.row.config',\n key: ROW_KEY,\n }, (props: { view: 'summary' | 'page' }) => <FirecrawlCard view={props.view} form={form} accounts={accounts} ctx={ctx} />))))\n}\n\nconst inputStyle = { padding: '6px 8px', border: '1px solid #8886', borderRadius: 6, background: 'transparent', color: 'inherit' }\nconst buttonStyle = { ...inputStyle, cursor: 'pointer' }\n\nfunction AccountRow({ account, configured, busy, save, remove }: {\n account: AccountMetadata; configured: boolean | undefined; busy: boolean\n save: (label: string, key: string) => Promise<void>; remove: () => void\n}): ReactNode {\n const [label, setLabel] = useState(account.label)\n const [key, setKey] = useState('')\n useEffect(() => { setLabel(account.label) }, [account.label])\n return <fieldset style={{ border: '1px solid #8884', borderRadius: 6, padding: 10, marginTop: 10 }} disabled={busy}>\n <legend>{account.label || '新账号'}</legend>\n <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap', alignItems: 'end' }}>\n <label style={{ display: 'grid', gap: 4 }}>名称\n <input aria-label=\"账号名称\" style={inputStyle} value={label} onChange={event => setLabel(event.target.value)} />\n </label>\n <label style={{ display: 'grid', gap: 4 }}>API key · {configured === undefined ? '状态待确认' : configured ? '已配置' : '未配置'}\n <input aria-label=\"Firecrawl API key\" style={inputStyle} type=\"password\" autoComplete=\"new-password\" value={key}\n placeholder={configured ? '留空保留已有 key' : 'fc-...'} onChange={event => setKey(event.target.value)} />\n </label>\n <button style={buttonStyle} onClick={() => { void save(label, key).then(() => setKey('')).catch(() => {}) }}>保存</button>\n <button style={buttonStyle} onClick={remove}>移除账号</button>\n </div>\n </fieldset>\n}\n\nfunction FirecrawlCard({ view = 'page', form, accounts: controller, ctx }: {\n view?: 'summary' | 'page'\n form: ConfigForm<Section>\n accounts: AccountsController\n ctx: Context\n}): ReactNode {\n const snapshot = useSyncExternalStore(form.subscribe.bind(form), form.getSnapshot.bind(form))\n const [open, setOpen] = useState(false)\n const [busy, setBusy] = useState(false)\n const [error, setError] = useState('')\n const [configured, setConfigured] = useState<Record<string, boolean> | undefined>()\n // The API reports configured-state metadata, never credential values.\n useEffect(() => {\n let current = true\n setConfigured(undefined)\n void ctx.remote.settings.describe().then((result: { ok: true; value: SettingsDescribeValue } | { ok: false }) => {\n if (!current || !result.ok) return\n const ns = result.value.namespaces.find(item => item.ns === NAMESPACE)\n if (ns) setConfigured(Object.fromEntries(ns.secrets.filter(secret => secret.path[0] === 'apiKeys').map(secret => [secret.path[1], secret.set])))\n }).catch(() => {})\n return () => { current = false }\n }, [snapshot.revision, ctx])\n\n if (view === 'summary') return <span>搜索账号与负载均衡</span>\n if (snapshot.status !== 'ready') return null\n const section = snapshot.value ?? {}\n async function run(action: () => Promise<void>): Promise<void> {\n setBusy(true)\n setError('')\n try { await action() }\n catch (error) { setError('保存失败,未确认写入。请重新打开设置后重试。'); throw error }\n finally { setBusy(false) }\n }\n function act(action: () => Promise<void>): void { void run(action).catch(() => {}) }\n const disabled = busy || !snapshot.writable\n return <li style={{ listStyle: 'none', border: '1px solid #8884', borderRadius: 8, margin: '8px 0' }}>\n <button style={{ ...buttonStyle, border: 0, width: '100%', textAlign: 'left', padding: 14 }} aria-expanded={open} onClick={() => setOpen(!open)}>\n <strong>Firecrawl search</strong><div style={{ opacity: 0.7, fontSize: 12 }}>搜索账号与负载均衡 · {open ? '收起' : '展开'}</div>\n </button>\n {open && <div style={{ padding: '0 14px 14px' }}>\n {(section.accounts ?? []).map(account => <AccountRow key={account.id} account={account}\n configured={configured === undefined ? undefined : configured[account.id] === true} busy={disabled}\n save={(label, key) => run(() => controller.save(account.id, label, key))}\n remove={() => act(() => controller.remove(account.id))} />)}\n <button style={{ ...buttonStyle, marginTop: 10 }} disabled={disabled} onClick={() => act(() => controller.add(crypto.randomUUID()))}>添加账号</button>\n <p style={{ fontSize: 12, opacity: 0.75 }}>Key 只写入、不回显。填写后点“保存”;只改名称不会清除已有 key。</p>\n <fieldset disabled={disabled} style={{ border: 0, padding: 0, display: 'flex', gap: 12, flexWrap: 'wrap' }}>\n <label>策略 <select aria-label=\"账号策略\" style={inputStyle} value={section.strategy ?? 'round-robin'} onChange={event => act(() => form.set('strategy', event.target.value).then(ok => { if (!ok) throw new Error('refused') }))}>\n <option value=\"round-robin\">轮询</option><option value=\"least-loaded\">最少使用</option>\n </select></label>\n <label>结果数 <input aria-label=\"搜索结果数\" style={{ ...inputStyle, width: 65 }} type=\"number\" min={1} defaultValue={section.limit ?? 5}\n onBlur={event => { const value = Number(event.target.value); if (Number.isInteger(value) && value > 0) act(() => form.set('limit', value).then(ok => { if (!ok) throw new Error('refused') })) }} /></label>\n <label>冷却毫秒 <input aria-label=\"冷却毫秒\" style={{ ...inputStyle, width: 90 }} type=\"number\" min={0} defaultValue={section.cooldownMs ?? 60000}\n onBlur={event => { const value = Number(event.target.value); if (Number.isInteger(value) && value >= 0) act(() => form.set('cooldownMs', value).then(ok => { if (!ok) throw new Error('refused') })) }} /></label>\n </fieldset>\n {error && <p role=\"alert\">{error}</p>}\n <p style={{ fontSize: 12, opacity: 0.75 }}>这个 bundle 会把 <code>web.searchProvider</code> 设为 <code>firecrawl</code>。加好账号后,用会暴露 <code>web_search</code> 的新会话。</p>\n </div>}\n </li>\n}\n"],"mappings":"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"}
|
|
@@ -125,7 +125,7 @@ function abortable(operation, signal) {
|
|
|
125
125
|
}
|
|
126
126
|
/** Add endpoint/account recovery instructions to a provider failure. */
|
|
127
127
|
function searchEndpointError(message, cause) {
|
|
128
|
-
return new WebError(`${message}\n\nThis came from the Firecrawl search provider. Check the accounts in
|
|
128
|
+
return new WebError(`${message}\n\nThis came from the Firecrawl search provider. Check the accounts in Plugins > dsh-web-search-firecrawl > 配置: each saved account needs a valid Firecrawl API key, and rate-limited accounts are cooled down automatically before they are reused. Only the user should change or choose which account serves a search.`, "WEB_PROVIDER_ERROR", cause === void 0 ? void 0 : { cause });
|
|
129
129
|
}
|
|
130
130
|
/**
|
|
131
131
|
* The Firecrawl-backed search provider. Spreads work across a pool of accounts,
|
|
@@ -156,7 +156,7 @@ var FirecrawlSearchProvider = class {
|
|
|
156
156
|
const options = this.resolveOptions();
|
|
157
157
|
throwIfSearchAborted(signal);
|
|
158
158
|
const slots = this.syncSlots(options);
|
|
159
|
-
if (slots.length === 0) throw new WebError("Firecrawl search has no accounts configured; add at least one Firecrawl API key through
|
|
159
|
+
if (slots.length === 0) throw new WebError("Firecrawl search has no accounts configured; add at least one Firecrawl API key through Plugins > dsh-web-search-firecrawl > 配置, or set the dsh-web-search-firecrawl config", "WEB_PROVIDER_CREDENTIAL_MISSING");
|
|
160
160
|
const endpoint = `${options.baseURL}/v1/search`;
|
|
161
161
|
const body = {
|
|
162
162
|
query: request.query,
|
|
@@ -282,7 +282,7 @@ var FirecrawlSearchProvider = class {
|
|
|
282
282
|
* Two halves, one package:
|
|
283
283
|
* - Host half (this file): registers the {@link FirecrawlSearchProvider} on
|
|
284
284
|
* `ctx.web` and a `settings` section so accounts are manageable.
|
|
285
|
-
* - Browser half (`src/client/`): a `
|
|
285
|
+
* - Browser half (`src/client/`): a `plugins.row.config` page that adds,
|
|
286
286
|
* edits, and removes accounts and tunes pool behavior.
|
|
287
287
|
*
|
|
288
288
|
* @module dsh-web-search-firecrawl
|
|
@@ -300,13 +300,24 @@ const Config = z.object({
|
|
|
300
300
|
id: z.string(),
|
|
301
301
|
label: z.string(),
|
|
302
302
|
apiKey: z.string().role("secret")
|
|
303
|
-
})),
|
|
304
|
-
apiKeys: z.dict(z.string().role("secret")),
|
|
305
|
-
baseURL: z.string(),
|
|
306
|
-
limit: z.number().step(1).min(1),
|
|
307
|
-
strategy: z.union(["round-robin", "least-loaded"]),
|
|
308
|
-
cooldownMs: z.number().step(1).min(0)
|
|
303
|
+
})).volatile(),
|
|
304
|
+
apiKeys: z.dict(z.string().role("secret")).volatile(),
|
|
305
|
+
baseURL: z.string().default(FIRECRAWL_DEFAULT_BASE_URL).volatile(),
|
|
306
|
+
limit: z.number().step(1).min(1).default(5).volatile(),
|
|
307
|
+
strategy: z.union(["round-robin", "least-loaded"]).default("round-robin").volatile(),
|
|
308
|
+
cooldownMs: z.number().step(1).min(0).default(DEFAULT_COOLDOWN_MS).volatile()
|
|
309
309
|
});
|
|
310
|
+
/** Copy one volatile snapshot into the plain section the provider and migration read. */
|
|
311
|
+
function readSection(config) {
|
|
312
|
+
return {
|
|
313
|
+
accounts: config.accounts.get()?.map((account) => ({ ...account })),
|
|
314
|
+
apiKeys: { ...config.apiKeys.get() ?? {} },
|
|
315
|
+
baseURL: config.baseURL.get(),
|
|
316
|
+
limit: config.limit.get(),
|
|
317
|
+
strategy: config.strategy.get(),
|
|
318
|
+
cooldownMs: config.cooldownMs.get()
|
|
319
|
+
};
|
|
320
|
+
}
|
|
310
321
|
/** Project one resolved section into the options the provider serves next. */
|
|
311
322
|
function resolveOptions(config) {
|
|
312
323
|
return {
|
|
@@ -317,17 +328,17 @@ function resolveOptions(config) {
|
|
|
317
328
|
cooldownMs: config.cooldownMs ?? DEFAULT_COOLDOWN_MS
|
|
318
329
|
};
|
|
319
330
|
}
|
|
320
|
-
/** Register the Firecrawl search provider with `ctx.web
|
|
331
|
+
/** Register the Firecrawl search provider with `ctx.web`. */
|
|
321
332
|
function apply(ctx, config) {
|
|
322
333
|
console.log("[my-plugins/dsh-web-search-firecrawl] loaded");
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
334
|
+
ctx.inject(["settings"], (settingsCtx) => {
|
|
335
|
+
settingsCtx.effect(() => settingsCtx.settings.configure({ auto: false }, ctx.fiber));
|
|
336
|
+
return (async () => {
|
|
337
|
+
const migration = legacyAccountMigration(readSection(config));
|
|
338
|
+
if (migration !== void 0) await settingsCtx.settings.update(FIRECRAWL_SETTINGS_NAMESPACE, migration);
|
|
339
|
+
})();
|
|
329
340
|
});
|
|
330
|
-
ctx.web.registerSearchProvider(new FirecrawlSearchProvider(() => resolveOptions(
|
|
341
|
+
ctx.web.registerSearchProvider(new FirecrawlSearchProvider(() => resolveOptions(readSection(config))));
|
|
331
342
|
}
|
|
332
343
|
//#endregion
|
|
333
344
|
export { Config, FIRECRAWL_DEFAULT_BASE_URL, FIRECRAWL_DEFAULT_LIMIT, FIRECRAWL_PROVIDER_ID, FIRECRAWL_SETTINGS_NAMESPACE, FirecrawlSearchProvider, apply, inject, name };
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ConfigForm } from '@deepseek-ai/dsh-client-ui-settings/client';
|
|
2
2
|
import { type AccountSettings } from '../accounts.js';
|
|
3
3
|
/** All edits use the namespace's revision fence and never read credential values. */
|
|
4
4
|
export declare class AccountsController {
|
|
5
|
-
private readonly
|
|
6
|
-
constructor(
|
|
5
|
+
private readonly form;
|
|
6
|
+
constructor(form: ConfigForm<AccountSettings>);
|
|
7
7
|
private snapshot;
|
|
8
|
+
private commit;
|
|
8
9
|
add(id: string): Promise<void>;
|
|
9
10
|
save(id: string, label: string, key: string): Promise<void>;
|
|
10
11
|
remove(id: string): Promise<void>;
|
|
@@ -7,14 +7,14 @@
|
|
|
7
7
|
* Two halves, one package:
|
|
8
8
|
* - Host half (this file): registers the {@link FirecrawlSearchProvider} on
|
|
9
9
|
* `ctx.web` and a `settings` section so accounts are manageable.
|
|
10
|
-
* - Browser half (`src/client/`): a `
|
|
10
|
+
* - Browser half (`src/client/`): a `plugins.row.config` page that adds,
|
|
11
11
|
* edits, and removes accounts and tunes pool behavior.
|
|
12
12
|
*
|
|
13
13
|
* @module dsh-web-search-firecrawl
|
|
14
14
|
*/
|
|
15
|
-
import type { Context } from '@deepseek-ai/cordis';
|
|
15
|
+
import type { Context, Volatile } from '@deepseek-ai/cordis';
|
|
16
16
|
import z from '@deepseek-ai/schemastery';
|
|
17
|
-
import { type AccountSettings } from './accounts.js';
|
|
17
|
+
import { type AccountMetadata, type AccountSettings } from './accounts.js';
|
|
18
18
|
import { type FirecrawlPoolStrategy } from './provider.js';
|
|
19
19
|
export { FIRECRAWL_DEFAULT_BASE_URL, FIRECRAWL_DEFAULT_LIMIT, FIRECRAWL_PROVIDER_ID, FirecrawlSearchProvider, } from './provider.js';
|
|
20
20
|
export type { FirecrawlAccount, FirecrawlPoolStrategy, FirecrawlSearchProviderOptions, } from './provider.ts';
|
|
@@ -26,7 +26,6 @@ export declare const inject: string[];
|
|
|
26
26
|
export declare const FIRECRAWL_SETTINGS_NAMESPACE = "dsh-web-search-firecrawl";
|
|
27
27
|
/** Plugin config (all optional — `apply` fills defaults). */
|
|
28
28
|
export interface Config extends AccountSettings {
|
|
29
|
-
/** Load-balanced pool of Firecrawl accounts (each is one API key). */
|
|
30
29
|
/** Endpoint base; `/v1/search` is appended. Defaults to the public API. */
|
|
31
30
|
baseURL?: string;
|
|
32
31
|
/** Maximum search results requested per search. Defaults to 5. */
|
|
@@ -36,7 +35,20 @@ export interface Config extends AccountSettings {
|
|
|
36
35
|
/** How long a rate-limited account stays cooled down, in ms. */
|
|
37
36
|
cooldownMs?: number;
|
|
38
37
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
38
|
+
/**
|
|
39
|
+
* Live profile config. Harness 0.2.0 reads editable fields from the plugin
|
|
40
|
+
* Config schema; only `.volatile()` fields can change without remounting.
|
|
41
|
+
* Each search calls `.get()` so one operation sees one snapshot.
|
|
42
|
+
*/
|
|
43
|
+
interface LiveConfig {
|
|
44
|
+
accounts: Volatile<AccountMetadata[] | undefined>;
|
|
45
|
+
apiKeys: Volatile<Record<string, string> | undefined>;
|
|
46
|
+
baseURL: Volatile<string | undefined>;
|
|
47
|
+
limit: Volatile<number | undefined>;
|
|
48
|
+
strategy: Volatile<FirecrawlPoolStrategy | undefined>;
|
|
49
|
+
cooldownMs: Volatile<number | undefined>;
|
|
50
|
+
}
|
|
51
|
+
export declare const Config: z;
|
|
52
|
+
/** Register the Firecrawl search provider with `ctx.web`. */
|
|
53
|
+
export declare function apply(ctx: Context, config: LiveConfig): void;
|
|
42
54
|
//# sourceMappingURL=dsh-web-search-firecrawl.d.ts.map
|
package/package.json
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-web-search-firecrawl",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "Firecrawl search provider and multi-account settings for DeepSeek Harness",
|
|
5
|
-
"repository": {
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "https://github.com/aa2246740/dsh-firecrawl.git"
|
|
8
|
+
},
|
|
6
9
|
"homepage": "https://github.com/aa2246740/dsh-firecrawl#readme",
|
|
7
|
-
"bugs": {
|
|
10
|
+
"bugs": {
|
|
11
|
+
"url": "https://github.com/aa2246740/dsh-firecrawl/issues"
|
|
12
|
+
},
|
|
8
13
|
"keywords": [
|
|
9
14
|
"deepseek-harness",
|
|
10
15
|
"dsh",
|
|
@@ -35,7 +40,9 @@
|
|
|
35
40
|
},
|
|
36
41
|
"client": {
|
|
37
42
|
"inject": [
|
|
38
|
-
"@deepseek-ai/dsh-
|
|
43
|
+
"@deepseek-ai/dsh-api-remotes",
|
|
44
|
+
"@deepseek-ai/dsh-client-ui-settings",
|
|
45
|
+
"@deepseek-ai/dsh-client-ui-plugin-manager"
|
|
39
46
|
],
|
|
40
47
|
"platform": "web"
|
|
41
48
|
}
|
|
@@ -53,30 +60,31 @@
|
|
|
53
60
|
},
|
|
54
61
|
"license": "MIT",
|
|
55
62
|
"peerDependencies": {
|
|
56
|
-
"@deepseek-ai/cordis": "^4.0.
|
|
57
|
-
"@deepseek-ai/dsh
|
|
58
|
-
"@deepseek-ai/dsh-
|
|
59
|
-
"@deepseek-ai/dsh-
|
|
60
|
-
"@deepseek-ai/dsh-
|
|
61
|
-
"@deepseek-ai/
|
|
63
|
+
"@deepseek-ai/cordis": "^4.0.4",
|
|
64
|
+
"@deepseek-ai/dsh": ">=0.2.0-rc.1 <0.2.1",
|
|
65
|
+
"@deepseek-ai/dsh-api-remotes": ">=0.2.0-rc.1 <0.2.1",
|
|
66
|
+
"@deepseek-ai/dsh-client-ui-plugin-manager": ">=0.2.0-rc.1 <0.2.1",
|
|
67
|
+
"@deepseek-ai/dsh-client-ui-settings": ">=0.2.0-rc.1 <0.2.1",
|
|
68
|
+
"@deepseek-ai/dsh-settings": ">=0.2.0-rc.1 <0.2.1",
|
|
69
|
+
"@deepseek-ai/dsh-web": ">=0.2.0-rc.1 <0.2.1",
|
|
70
|
+
"@deepseek-ai/schemastery": "^3.18.4",
|
|
62
71
|
"react": "^18.2.0",
|
|
63
72
|
"react-dom": "^18.2.0"
|
|
64
73
|
},
|
|
65
74
|
"devDependencies": {
|
|
66
|
-
"@deepseek-ai/cordis": "^4.0.
|
|
67
|
-
"@deepseek-ai/dsh-api-remotes": "
|
|
68
|
-
"@deepseek-ai/dsh-client-ui-
|
|
69
|
-
"@deepseek-ai/dsh-client-ui-renderer": "
|
|
70
|
-
"@deepseek-ai/dsh-
|
|
71
|
-
"@deepseek-ai/dsh-
|
|
72
|
-
"@deepseek-ai/dsh-
|
|
73
|
-
"@deepseek-ai/dsh-
|
|
74
|
-
"@deepseek-ai/
|
|
75
|
-
"@deepseek-ai/dsh-web": "^0.1.5-rc.3",
|
|
76
|
-
"@deepseek-ai/schemastery": "^3.18.1",
|
|
75
|
+
"@deepseek-ai/cordis": "^4.0.4",
|
|
76
|
+
"@deepseek-ai/dsh-api-remotes": ">=0.2.0-rc.1 <0.2.1",
|
|
77
|
+
"@deepseek-ai/dsh-client-ui-plugin-manager": ">=0.2.0-rc.1 <0.2.1",
|
|
78
|
+
"@deepseek-ai/dsh-client-ui-renderer": ">=0.2.0-rc.1 <0.2.1",
|
|
79
|
+
"@deepseek-ai/dsh-client-ui-settings": ">=0.2.0-rc.1 <0.2.1",
|
|
80
|
+
"@deepseek-ai/dsh-client-ui-slots": ">=0.2.0-rc.1 <0.2.1",
|
|
81
|
+
"@deepseek-ai/dsh-settings": ">=0.2.0-rc.1 <0.2.1",
|
|
82
|
+
"@deepseek-ai/dsh-web": ">=0.2.0-rc.1 <0.2.1",
|
|
83
|
+
"@deepseek-ai/schemastery": "^3.18.4",
|
|
77
84
|
"@types/react": "~18.3.1",
|
|
78
85
|
"react": "^18.2.0",
|
|
79
86
|
"react-dom": "^18.2.0",
|
|
87
|
+
"semver": "^7.8.5",
|
|
80
88
|
"tsdown": "^0.22.2",
|
|
81
89
|
"typescript": "^6.0.3"
|
|
82
90
|
}
|