Skip to content

Commit 328739b

Browse files
committed
chore(deps): 更新依赖版本并切换部分包的发布渠道
- 将部分本地链接依赖改为公开版本号依赖 - 升级 vitest 依赖,增加 @types/debug 的版本信息 - 更新 @deepseek-ai 相关包的版本至0.1.0-rc.6和4.0.1 - 调整依赖树,优化部分包的可选 peerDependencies - 增加多项 @deepseek-ai 相关包的校验和信息 - 解绑部分包的本地路径依赖,改为版本号引用,提高模块兼容性
1 parent 4b3ea28 commit 328739b

4 files changed

Lines changed: 2267 additions & 104 deletions

File tree

‎packages/tool-bailian-kb/src/index.ts‎

Lines changed: 104 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,28 @@
55
*/
66

77
import type { Context } from '@deepseek-ai/cordis'
8+
import type { IncomingMessage, ServerResponse } from 'node:http'
89
import z from '@deepseek-ai/schemastery'
910
import { credentialRef } from '@deepseek-ai/dsh-credentials'
1011
import { settingsNamespace, type SettingsRegisterOptions, type SettingsScope } from '@deepseek-ai/dsh-settings'
1112
import { KbClient } from './client.js'
1213
import { registerSkill } from './skill.js'
1314
import { createKbTools } from './tools.js'
1415

16+
/** Minimal webServer route shape (declared inline to avoid a host-package dependency). */
17+
interface WebRoute {
18+
kind: 'exact' | 'prefix'
19+
path: string
20+
handler: (req: IncomingMessage, res: ServerResponse) => void | Promise<void>
21+
}
22+
declare module '@deepseek-ai/cordis' {
23+
interface Context {
24+
webServer: {
25+
register(route: WebRoute): () => void
26+
}
27+
}
28+
}
29+
1530
export const name = 'tool-bailian-kb'
1631
export const inject = ['tools', 'credentials']
1732

@@ -95,14 +110,15 @@ export function apply(ctx: Context, config: Config): void {
95110
// not carry: the `expose` opt-in (this page edits the section from the
96111
// browser) and the scope handle the credential migration writes through.
97112
let current: () => Config = () => config
113+
let scope: SettingsScope<Config> | undefined
98114
ctx.inject(['settings'], (sctx) => {
99115
// `expose` is the wire opt-in the harness documents as deferred work; the
100116
// assertion keeps this compiling against pristine upstream types, which do
101117
// not declare it yet. Until upstream lands it the option is ignored and
102118
// the browser page degrades to its credentials-only fallback.
103119
const options = { base: config, expose: true } as SettingsRegisterOptions<Config>
104-
const scope = sctx.settings.register(SETTINGS_NS, Config, options)
105-
current = () => scope.get()
120+
scope = sctx.settings.register(SETTINGS_NS, Config, options)
121+
current = () => scope!.get()
106122
sctx.effect(() => () => { current = () => config }, 'tool-bailian-kb: settings source fallback')
107123
void seedFromCredentials(ctx, scope)
108124
})
@@ -154,4 +170,90 @@ export function apply(ctx: Context, config: Config): void {
154170
ctx.tools.register(tool)
155171
}
156172
registerSkill(ctx)
173+
174+
// Bridge routes let the browser settings page read and write the resolved
175+
// section without riding the settings wire (which requires an apiproxy
176+
// allowlist entry the composition does not grant out-of-tree namespaces).
177+
ctx.inject(['webServer'], (wctx) => {
178+
wctx.effect(() => wctx.webServer.register({
179+
kind: 'exact',
180+
path: '/bailian-kb/settings',
181+
handler: (_req: IncomingMessage, res: ServerResponse) => {
182+
sendJson(res, 200, current())
183+
},
184+
}), 'tool-bailian-kb: settings GET route')
185+
186+
wctx.effect(() => wctx.webServer.register({
187+
kind: 'exact',
188+
path: '/bailian-kb/settings',
189+
handler: async (req: IncomingMessage, res: ServerResponse) => {
190+
if (req.method !== 'POST') {
191+
sendJson(res, 405, { error: 'use POST' })
192+
return
193+
}
194+
if (!scope) {
195+
sendJson(res, 503, { error: 'settings service unavailable' })
196+
return
197+
}
198+
let body: unknown
199+
try { body = await readJsonBody(req) } catch (err) {
200+
sendJson(res, 400, { error: err instanceof Error ? err.message : 'bad request' })
201+
return
202+
}
203+
if (typeof body !== 'object' || body === null) {
204+
sendJson(res, 400, { error: 'expected JSON object' })
205+
return
206+
}
207+
// Build a settings update patch. null-valued keys are removals (the
208+
// field falls back to the entry config and then the credential store).
209+
const patch: Record<string, unknown> = {}
210+
const removals = new Set<string>()
211+
for (const [key, value] of Object.entries(body as Record<string, unknown>)) {
212+
if (!(key in current())) continue
213+
if (value === null) { removals.add(key); continue }
214+
patch[key] = value
215+
}
216+
try {
217+
if (removals.size > 0) {
218+
// Remove fields from the user section by replacing it wholesale
219+
// with the current resolved config minus the removed keys.
220+
// Absent keys re-inherit the entry base and schema defaults.
221+
const next = { ...current(), ...patch }
222+
for (const key of removals) delete (next as Record<string, unknown>)[key]
223+
await scope.replace(next)
224+
} else if (Object.keys(patch).length > 0) {
225+
await scope.update(patch)
226+
}
227+
sendJson(res, 200, scope.get())
228+
} catch (err) {
229+
sendJson(res, 500, { error: err instanceof Error ? err.message : 'settings write failed' })
230+
}
231+
},
232+
}), 'tool-bailian-kb: settings POST route')
233+
})
234+
}
235+
236+
/** Write a JSON response. */
237+
function sendJson(res: ServerResponse, status: number, data: unknown): void {
238+
res.statusCode = status
239+
res.setHeader('Content-Type', 'application/json; charset=utf-8')
240+
res.end(JSON.stringify(data))
241+
}
242+
243+
/** Read a UTF-8 JSON body up to a size limit. */
244+
function readJsonBody(req: IncomingMessage, maxBytes = 16384): Promise<unknown> {
245+
return new Promise((resolve, reject) => {
246+
const chunks: Buffer[] = []
247+
let total = 0
248+
req.on('data', (chunk: Buffer) => {
249+
total += chunk.length
250+
if (total > maxBytes) { req.destroy(); reject(new Error('body too large')); return }
251+
chunks.push(chunk)
252+
})
253+
req.on('end', () => {
254+
try { resolve(JSON.parse(Buffer.concat(chunks).toString('utf8'))) }
255+
catch (err) { reject(err) }
256+
})
257+
req.on('error', reject)
258+
})
157259
}

‎packages/tool-bailian-kb/src/web/bailian-card-controller.ts‎

Lines changed: 75 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,18 @@
22
* The Bailian page's controller: a hybrid form over two domains.
33
*
44
* The workspace, default-retrieval-service and default-chat-service ids live
5-
* in the `bailian-kb` settings section the Host half registers, so while the
6-
* settings scope is `ready` they ECHO: the page shows the resolved value and
7-
* stages edits over it (clearing a field unsets the user layer, falling back
8-
* to the entry config and then the credential store). When the scope is
9-
* unavailable — a remote browser (memory mode) or a composition without a
10-
* settings service — both fields degrade to the original write-only credential
11-
* controls.
5+
* in the `bailian-kb` settings section the Host half registers. The Host
6+
* exposes them over a bridge route (`/bailian-kb/settings`) so the page can
7+
* read and write without riding the settings wire (which requires an apiproxy
8+
* allowlist entry the composition does not grant out-of-tree namespaces).
129
*
1310
* The API key always rides its credential reference (write-only by design:
1411
* the wire is structurally value-free), so that control starts blank and
1512
* reports only configured/unconfigured.
1613
*/
1714

1815
import type { IApiClient } from '@deepseek-ai/dsh-client-connection/client'
19-
import { createSnapshotStore, type SettingsScope, type SnapshotStore } from '@deepseek-ai/dsh-client-runtime/client'
16+
import { createSnapshotStore, type SnapshotStore } from '@deepseek-ai/dsh-client-runtime/client'
2017

2118
/** The credential references this page addresses, keyed by their ref names. */
2219
export const BAILIAN_CARD_REFS = [
@@ -120,17 +117,15 @@ export function dirtyOf(state: BailianCardState): boolean {
120117
return BAILIAN_CARD_REFS.some(key => staged(state, key))
121118
}
122119

123-
/** Bridge the settings scope and the credentials domain onto the page. */
120+
/** Bridge the settings bridge route and the credentials domain onto the page. */
124121
export class BailianCardController {
125122
private readonly store: SnapshotStore<BailianCardState>
126123

127124
/**
128125
* @param api - wire face used for the three credential references.
129-
* @param scope - the bound `bailian-kb` settings scope (echo transport).
130126
*/
131127
constructor(
132128
private readonly api: Pick<IApiClient, 'credentials'>,
133-
private readonly scope: SettingsScope<BailianKbSection>,
134129
) {
135130
this.store = createSnapshotStore<BailianCardState>({
136131
drafts: {
@@ -150,29 +145,35 @@ export class BailianCardController {
150145
clearing: false,
151146
failed: false,
152147
})
153-
this.syncSettings()
148+
void this.fetchSettings()
154149
void this.read()
155150
}
156151

157152
/**
158-
* Mirror the scope snapshot into the page state. Called at construction and
159-
* from the registration-side subscription (the scope self-refreshes on
160-
* pushed document invalidations and connection resets).
153+
* Fetch the current settings from the Host bridge route. Called at
154+
* construction and after every mutation (save, clear).
161155
*/
162-
syncSettings(): void {
163-
const snapshot = this.scope.getSnapshot()
164-
const value = snapshot.value
165-
this.store.update(draft => {
166-
draft.settings = {
167-
status: snapshot.status,
168-
writable: snapshot.writable,
169-
values: {
170-
...(value?.workspaceId !== undefined ? { workspaceId: value.workspaceId } : {}),
171-
...(value?.defaultRetrieveAgentId !== undefined ? { defaultRetrieveAgentId: value.defaultRetrieveAgentId } : {}),
172-
...(value?.defaultChatAgentId !== undefined ? { defaultChatAgentId: value.defaultChatAgentId } : {}),
173-
},
174-
}
175-
})
156+
async fetchSettings(): Promise<void> {
157+
try {
158+
const resp = await fetch('/bailian-kb/settings')
159+
if (!resp.ok) throw new Error(`HTTP ${resp.status}`)
160+
const value = await resp.json() as Record<string, unknown>
161+
this.store.update(draft => {
162+
draft.settings = {
163+
status: 'ready',
164+
writable: true,
165+
values: {
166+
...(typeof value.workspaceId === 'string' ? { workspaceId: value.workspaceId } : {}),
167+
...(typeof value.defaultRetrieveAgentId === 'string' ? { defaultRetrieveAgentId: value.defaultRetrieveAgentId } : {}),
168+
...(typeof value.defaultChatAgentId === 'string' ? { defaultChatAgentId: value.defaultChatAgentId } : {}),
169+
},
170+
}
171+
})
172+
} catch (_fetchFailure) {
173+
this.store.update(draft => {
174+
draft.settings = { status: 'unavailable', writable: false, values: {} }
175+
})
176+
}
176177
}
177178

178179
/**
@@ -190,28 +191,31 @@ export class BailianCardController {
190191

191192
/**
192193
* Write every staged draft through its domain: echoing fields go to the
193-
* settings user layer (blank = unset, falling back to entry config and the
194-
* credential store), write-only fields go to `credentials.set`. A refused
195-
* credential write keeps its draft; a refused settings write self-heals by
196-
* the scope's own recovery read (the control snaps back to the Host value).
194+
* settings user layer via the bridge route (blank = removal, falling back
195+
* to entry config and the credential store), write-only fields go to
196+
* `credentials.set`. A refused credential write keeps its draft; a refused
197+
* settings write self-heals by re-fetching the Host value.
197198
*/
198199
async save(): Promise<void> {
199200
const state = this.store.getSnapshot()
200201
if (state.saving || !dirtyOf(state)) return
201202
this.store.update(draft => { draft.saving = true })
202203
let failed = false
203-
const writes: Promise<void>[] = []
204+
const settingsPatch: Record<string, unknown> = {}
205+
let hasSettingsWrite = false
206+
const credentialWrites: Promise<void>[] = []
204207
const settled: BailianFieldKey[] = []
205208
for (const key of BAILIAN_CARD_REFS) {
206209
if (!staged(state, key)) continue
207210
const text = state.drafts[key] as string
208211
const field = SETTINGS_FIELDS[key]
209212
if (field !== undefined && state.settings.status === 'ready') {
210-
writes.push(text === '' ? this.scope.unset(field) : this.scope.set(field, text))
213+
settingsPatch[field] = text === '' ? null : text
214+
hasSettingsWrite = true
211215
settled.push(key)
212216
continue
213217
}
214-
writes.push((async () => {
218+
credentialWrites.push((async () => {
215219
try {
216220
const response = await this.api.credentials.set({ ref: key, value: text })
217221
if (response.result.ok) settled.push(key)
@@ -221,13 +225,20 @@ export class BailianCardController {
221225
}
222226
})())
223227
}
224-
await Promise.all(writes)
228+
if (hasSettingsWrite) {
229+
try {
230+
await this.saveSettings(settingsPatch)
231+
} catch (_settingsWriteFailure) {
232+
failed = true
233+
}
234+
}
235+
await Promise.all(credentialWrites)
225236
this.store.update(draft => {
226237
draft.saving = false
227238
draft.failed = failed
228239
for (const key of settled) draft.drafts[key] = undefined
229240
})
230-
this.syncSettings()
241+
await this.fetchSettings()
231242
await this.read()
232243
}
233244

@@ -252,7 +263,13 @@ export class BailianCardController {
252263
if (state.clearing) return
253264
this.store.update(draft => { draft.clearing = true })
254265
let failed = false
255-
if (state.settings.status === 'ready') await this.scope.unset(settingsField)
266+
if (state.settings.status === 'ready') {
267+
try {
268+
await this.saveSettings({ [settingsField]: null })
269+
} catch (_settingsWriteFailure) {
270+
failed = true
271+
}
272+
}
256273
if (state.credentials[key].configured) {
257274
try {
258275
const response = await this.api.credentials.unset({ ref: key })
@@ -266,7 +283,7 @@ export class BailianCardController {
266283
draft.failed = failed
267284
draft.drafts[key] = undefined
268285
})
269-
this.syncSettings()
286+
await this.fetchSettings()
270287
await this.read()
271288
}
272289

@@ -298,6 +315,24 @@ export class BailianCardController {
298315
}
299316
}
300317

318+
/**
319+
* Send a settings patch to the Host bridge route. `null`-valued keys are
320+
* removals (the field falls back to the entry config and then the
321+
* credential store); other values are merged into the user layer.
322+
* @param patch - the partial settings update.
323+
*/
324+
private async saveSettings(patch: Record<string, unknown>): Promise<void> {
325+
const resp = await fetch('/bailian-kb/settings', {
326+
method: 'POST',
327+
headers: { 'Content-Type': 'application/json' },
328+
body: JSON.stringify(patch),
329+
})
330+
if (!resp.ok) {
331+
const body = await resp.json().catch(() => ({})) as { error?: string }
332+
throw new Error(body.error ?? `HTTP ${resp.status}`)
333+
}
334+
}
335+
301336
/**
302337
* Ask the credentials domain about all three references and publish the
303338
* answer. A failed read keeps the last known state: the page stays usable

0 commit comments

Comments
 (0)