mirror of
https://github.com/modelstudioai/cli.git
synced 2026-09-14 19:49:23 +08:00
chore(deps): 更新依赖版本并切换部分包的发布渠道
- 将部分本地链接依赖改为公开版本号依赖 - 升级 vitest 依赖,增加 @types/debug 的版本信息 - 更新 @deepseek-ai 相关包的版本至0.1.0-rc.6和4.0.1 - 调整依赖树,优化部分包的可选 peerDependencies - 增加多项 @deepseek-ai 相关包的校验和信息 - 解绑部分包的本地路径依赖,改为版本号引用,提高模块兼容性
This commit is contained in:
@@ -5,6 +5,7 @@
|
||||
*/
|
||||
|
||||
import type { Context } from '@deepseek-ai/cordis'
|
||||
import type { IncomingMessage, ServerResponse } from 'node:http'
|
||||
import z from '@deepseek-ai/schemastery'
|
||||
import { credentialRef } from '@deepseek-ai/dsh-credentials'
|
||||
import { settingsNamespace, type SettingsRegisterOptions, type SettingsScope } from '@deepseek-ai/dsh-settings'
|
||||
@@ -12,6 +13,20 @@ import { KbClient } from './client.js'
|
||||
import { registerSkill } from './skill.js'
|
||||
import { createKbTools } from './tools.js'
|
||||
|
||||
/** Minimal webServer route shape (declared inline to avoid a host-package dependency). */
|
||||
interface WebRoute {
|
||||
kind: 'exact' | 'prefix'
|
||||
path: string
|
||||
handler: (req: IncomingMessage, res: ServerResponse) => void | Promise<void>
|
||||
}
|
||||
declare module '@deepseek-ai/cordis' {
|
||||
interface Context {
|
||||
webServer: {
|
||||
register(route: WebRoute): () => void
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const name = 'tool-bailian-kb'
|
||||
export const inject = ['tools', 'credentials']
|
||||
|
||||
@@ -95,14 +110,15 @@ export function apply(ctx: Context, config: Config): void {
|
||||
// not carry: the `expose` opt-in (this page edits the section from the
|
||||
// browser) and the scope handle the credential migration writes through.
|
||||
let current: () => Config = () => config
|
||||
let scope: SettingsScope<Config> | undefined
|
||||
ctx.inject(['settings'], (sctx) => {
|
||||
// `expose` is the wire opt-in the harness documents as deferred work; the
|
||||
// assertion keeps this compiling against pristine upstream types, which do
|
||||
// not declare it yet. Until upstream lands it the option is ignored and
|
||||
// the browser page degrades to its credentials-only fallback.
|
||||
const options = { base: config, expose: true } as SettingsRegisterOptions<Config>
|
||||
const scope = sctx.settings.register(SETTINGS_NS, Config, options)
|
||||
current = () => scope.get()
|
||||
scope = sctx.settings.register(SETTINGS_NS, Config, options)
|
||||
current = () => scope!.get()
|
||||
sctx.effect(() => () => { current = () => config }, 'tool-bailian-kb: settings source fallback')
|
||||
void seedFromCredentials(ctx, scope)
|
||||
})
|
||||
@@ -154,4 +170,90 @@ export function apply(ctx: Context, config: Config): void {
|
||||
ctx.tools.register(tool)
|
||||
}
|
||||
registerSkill(ctx)
|
||||
|
||||
// Bridge routes let the browser settings page read and write the resolved
|
||||
// section without riding the settings wire (which requires an apiproxy
|
||||
// allowlist entry the composition does not grant out-of-tree namespaces).
|
||||
ctx.inject(['webServer'], (wctx) => {
|
||||
wctx.effect(() => wctx.webServer.register({
|
||||
kind: 'exact',
|
||||
path: '/bailian-kb/settings',
|
||||
handler: (_req: IncomingMessage, res: ServerResponse) => {
|
||||
sendJson(res, 200, current())
|
||||
},
|
||||
}), 'tool-bailian-kb: settings GET route')
|
||||
|
||||
wctx.effect(() => wctx.webServer.register({
|
||||
kind: 'exact',
|
||||
path: '/bailian-kb/settings',
|
||||
handler: async (req: IncomingMessage, res: ServerResponse) => {
|
||||
if (req.method !== 'POST') {
|
||||
sendJson(res, 405, { error: 'use POST' })
|
||||
return
|
||||
}
|
||||
if (!scope) {
|
||||
sendJson(res, 503, { error: 'settings service unavailable' })
|
||||
return
|
||||
}
|
||||
let body: unknown
|
||||
try { body = await readJsonBody(req) } catch (err) {
|
||||
sendJson(res, 400, { error: err instanceof Error ? err.message : 'bad request' })
|
||||
return
|
||||
}
|
||||
if (typeof body !== 'object' || body === null) {
|
||||
sendJson(res, 400, { error: 'expected JSON object' })
|
||||
return
|
||||
}
|
||||
// Build a settings update patch. null-valued keys are removals (the
|
||||
// field falls back to the entry config and then the credential store).
|
||||
const patch: Record<string, unknown> = {}
|
||||
const removals = new Set<string>()
|
||||
for (const [key, value] of Object.entries(body as Record<string, unknown>)) {
|
||||
if (!(key in current())) continue
|
||||
if (value === null) { removals.add(key); continue }
|
||||
patch[key] = value
|
||||
}
|
||||
try {
|
||||
if (removals.size > 0) {
|
||||
// Remove fields from the user section by replacing it wholesale
|
||||
// with the current resolved config minus the removed keys.
|
||||
// Absent keys re-inherit the entry base and schema defaults.
|
||||
const next = { ...current(), ...patch }
|
||||
for (const key of removals) delete (next as Record<string, unknown>)[key]
|
||||
await scope.replace(next)
|
||||
} else if (Object.keys(patch).length > 0) {
|
||||
await scope.update(patch)
|
||||
}
|
||||
sendJson(res, 200, scope.get())
|
||||
} catch (err) {
|
||||
sendJson(res, 500, { error: err instanceof Error ? err.message : 'settings write failed' })
|
||||
}
|
||||
},
|
||||
}), 'tool-bailian-kb: settings POST route')
|
||||
})
|
||||
}
|
||||
|
||||
/** Write a JSON response. */
|
||||
function sendJson(res: ServerResponse, status: number, data: unknown): void {
|
||||
res.statusCode = status
|
||||
res.setHeader('Content-Type', 'application/json; charset=utf-8')
|
||||
res.end(JSON.stringify(data))
|
||||
}
|
||||
|
||||
/** Read a UTF-8 JSON body up to a size limit. */
|
||||
function readJsonBody(req: IncomingMessage, maxBytes = 16384): Promise<unknown> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const chunks: Buffer[] = []
|
||||
let total = 0
|
||||
req.on('data', (chunk: Buffer) => {
|
||||
total += chunk.length
|
||||
if (total > maxBytes) { req.destroy(); reject(new Error('body too large')); return }
|
||||
chunks.push(chunk)
|
||||
})
|
||||
req.on('end', () => {
|
||||
try { resolve(JSON.parse(Buffer.concat(chunks).toString('utf8'))) }
|
||||
catch (err) { reject(err) }
|
||||
})
|
||||
req.on('error', reject)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -2,13 +2,10 @@
|
||||
* The Bailian page's controller: a hybrid form over two domains.
|
||||
*
|
||||
* The workspace, default-retrieval-service and default-chat-service ids live
|
||||
* in the `bailian-kb` settings section the Host half registers, so while the
|
||||
* settings scope is `ready` they ECHO: the page shows the resolved value and
|
||||
* stages edits over it (clearing a field unsets the user layer, falling back
|
||||
* to the entry config and then the credential store). When the scope is
|
||||
* unavailable — a remote browser (memory mode) or a composition without a
|
||||
* settings service — both fields degrade to the original write-only credential
|
||||
* controls.
|
||||
* in the `bailian-kb` settings section the Host half registers. The Host
|
||||
* exposes them over a bridge route (`/bailian-kb/settings`) so the page can
|
||||
* read and write without riding the settings wire (which requires an apiproxy
|
||||
* allowlist entry the composition does not grant out-of-tree namespaces).
|
||||
*
|
||||
* The API key always rides its credential reference (write-only by design:
|
||||
* the wire is structurally value-free), so that control starts blank and
|
||||
@@ -16,7 +13,7 @@
|
||||
*/
|
||||
|
||||
import type { IApiClient } from '@deepseek-ai/dsh-client-connection/client'
|
||||
import { createSnapshotStore, type SettingsScope, type SnapshotStore } from '@deepseek-ai/dsh-client-runtime/client'
|
||||
import { createSnapshotStore, type SnapshotStore } from '@deepseek-ai/dsh-client-runtime/client'
|
||||
|
||||
/** The credential references this page addresses, keyed by their ref names. */
|
||||
export const BAILIAN_CARD_REFS = [
|
||||
@@ -120,17 +117,15 @@ export function dirtyOf(state: BailianCardState): boolean {
|
||||
return BAILIAN_CARD_REFS.some(key => staged(state, key))
|
||||
}
|
||||
|
||||
/** Bridge the settings scope and the credentials domain onto the page. */
|
||||
/** Bridge the settings bridge route and the credentials domain onto the page. */
|
||||
export class BailianCardController {
|
||||
private readonly store: SnapshotStore<BailianCardState>
|
||||
|
||||
/**
|
||||
* @param api - wire face used for the three credential references.
|
||||
* @param scope - the bound `bailian-kb` settings scope (echo transport).
|
||||
*/
|
||||
constructor(
|
||||
private readonly api: Pick<IApiClient, 'credentials'>,
|
||||
private readonly scope: SettingsScope<BailianKbSection>,
|
||||
) {
|
||||
this.store = createSnapshotStore<BailianCardState>({
|
||||
drafts: {
|
||||
@@ -150,29 +145,35 @@ export class BailianCardController {
|
||||
clearing: false,
|
||||
failed: false,
|
||||
})
|
||||
this.syncSettings()
|
||||
void this.fetchSettings()
|
||||
void this.read()
|
||||
}
|
||||
|
||||
/**
|
||||
* Mirror the scope snapshot into the page state. Called at construction and
|
||||
* from the registration-side subscription (the scope self-refreshes on
|
||||
* pushed document invalidations and connection resets).
|
||||
* Fetch the current settings from the Host bridge route. Called at
|
||||
* construction and after every mutation (save, clear).
|
||||
*/
|
||||
syncSettings(): void {
|
||||
const snapshot = this.scope.getSnapshot()
|
||||
const value = snapshot.value
|
||||
this.store.update(draft => {
|
||||
draft.settings = {
|
||||
status: snapshot.status,
|
||||
writable: snapshot.writable,
|
||||
values: {
|
||||
...(value?.workspaceId !== undefined ? { workspaceId: value.workspaceId } : {}),
|
||||
...(value?.defaultRetrieveAgentId !== undefined ? { defaultRetrieveAgentId: value.defaultRetrieveAgentId } : {}),
|
||||
...(value?.defaultChatAgentId !== undefined ? { defaultChatAgentId: value.defaultChatAgentId } : {}),
|
||||
},
|
||||
}
|
||||
})
|
||||
async fetchSettings(): Promise<void> {
|
||||
try {
|
||||
const resp = await fetch('/bailian-kb/settings')
|
||||
if (!resp.ok) throw new Error(`HTTP ${resp.status}`)
|
||||
const value = await resp.json() as Record<string, unknown>
|
||||
this.store.update(draft => {
|
||||
draft.settings = {
|
||||
status: 'ready',
|
||||
writable: true,
|
||||
values: {
|
||||
...(typeof value.workspaceId === 'string' ? { workspaceId: value.workspaceId } : {}),
|
||||
...(typeof value.defaultRetrieveAgentId === 'string' ? { defaultRetrieveAgentId: value.defaultRetrieveAgentId } : {}),
|
||||
...(typeof value.defaultChatAgentId === 'string' ? { defaultChatAgentId: value.defaultChatAgentId } : {}),
|
||||
},
|
||||
}
|
||||
})
|
||||
} catch (_fetchFailure) {
|
||||
this.store.update(draft => {
|
||||
draft.settings = { status: 'unavailable', writable: false, values: {} }
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -190,28 +191,31 @@ export class BailianCardController {
|
||||
|
||||
/**
|
||||
* Write every staged draft through its domain: echoing fields go to the
|
||||
* settings user layer (blank = unset, falling back to entry config and the
|
||||
* credential store), write-only fields go to `credentials.set`. A refused
|
||||
* credential write keeps its draft; a refused settings write self-heals by
|
||||
* the scope's own recovery read (the control snaps back to the Host value).
|
||||
* settings user layer via the bridge route (blank = removal, falling back
|
||||
* to entry config and the credential store), write-only fields go to
|
||||
* `credentials.set`. A refused credential write keeps its draft; a refused
|
||||
* settings write self-heals by re-fetching the Host value.
|
||||
*/
|
||||
async save(): Promise<void> {
|
||||
const state = this.store.getSnapshot()
|
||||
if (state.saving || !dirtyOf(state)) return
|
||||
this.store.update(draft => { draft.saving = true })
|
||||
let failed = false
|
||||
const writes: Promise<void>[] = []
|
||||
const settingsPatch: Record<string, unknown> = {}
|
||||
let hasSettingsWrite = false
|
||||
const credentialWrites: Promise<void>[] = []
|
||||
const settled: BailianFieldKey[] = []
|
||||
for (const key of BAILIAN_CARD_REFS) {
|
||||
if (!staged(state, key)) continue
|
||||
const text = state.drafts[key] as string
|
||||
const field = SETTINGS_FIELDS[key]
|
||||
if (field !== undefined && state.settings.status === 'ready') {
|
||||
writes.push(text === '' ? this.scope.unset(field) : this.scope.set(field, text))
|
||||
settingsPatch[field] = text === '' ? null : text
|
||||
hasSettingsWrite = true
|
||||
settled.push(key)
|
||||
continue
|
||||
}
|
||||
writes.push((async () => {
|
||||
credentialWrites.push((async () => {
|
||||
try {
|
||||
const response = await this.api.credentials.set({ ref: key, value: text })
|
||||
if (response.result.ok) settled.push(key)
|
||||
@@ -221,13 +225,20 @@ export class BailianCardController {
|
||||
}
|
||||
})())
|
||||
}
|
||||
await Promise.all(writes)
|
||||
if (hasSettingsWrite) {
|
||||
try {
|
||||
await this.saveSettings(settingsPatch)
|
||||
} catch (_settingsWriteFailure) {
|
||||
failed = true
|
||||
}
|
||||
}
|
||||
await Promise.all(credentialWrites)
|
||||
this.store.update(draft => {
|
||||
draft.saving = false
|
||||
draft.failed = failed
|
||||
for (const key of settled) draft.drafts[key] = undefined
|
||||
})
|
||||
this.syncSettings()
|
||||
await this.fetchSettings()
|
||||
await this.read()
|
||||
}
|
||||
|
||||
@@ -252,7 +263,13 @@ export class BailianCardController {
|
||||
if (state.clearing) return
|
||||
this.store.update(draft => { draft.clearing = true })
|
||||
let failed = false
|
||||
if (state.settings.status === 'ready') await this.scope.unset(settingsField)
|
||||
if (state.settings.status === 'ready') {
|
||||
try {
|
||||
await this.saveSettings({ [settingsField]: null })
|
||||
} catch (_settingsWriteFailure) {
|
||||
failed = true
|
||||
}
|
||||
}
|
||||
if (state.credentials[key].configured) {
|
||||
try {
|
||||
const response = await this.api.credentials.unset({ ref: key })
|
||||
@@ -266,7 +283,7 @@ export class BailianCardController {
|
||||
draft.failed = failed
|
||||
draft.drafts[key] = undefined
|
||||
})
|
||||
this.syncSettings()
|
||||
await this.fetchSettings()
|
||||
await this.read()
|
||||
}
|
||||
|
||||
@@ -298,6 +315,24 @@ export class BailianCardController {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a settings patch to the Host bridge route. `null`-valued keys are
|
||||
* removals (the field falls back to the entry config and then the
|
||||
* credential store); other values are merged into the user layer.
|
||||
* @param patch - the partial settings update.
|
||||
*/
|
||||
private async saveSettings(patch: Record<string, unknown>): Promise<void> {
|
||||
const resp = await fetch('/bailian-kb/settings', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(patch),
|
||||
})
|
||||
if (!resp.ok) {
|
||||
const body = await resp.json().catch(() => ({})) as { error?: string }
|
||||
throw new Error(body.error ?? `HTTP ${resp.status}`)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ask the credentials domain about all three references and publish the
|
||||
* answer. A failed read keeps the last known state: the page stays usable
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/**
|
||||
* Bailian knowledge-base plugin, browser half: one section page in the
|
||||
* Settings left nav. The workspace, default-retrieval-service and
|
||||
* default-chat-service ids ride the `bailian-kb` settings namespace the
|
||||
* Host half registers (echoing values through `ctx.settingsScope`,
|
||||
* degrading to write-only credential controls when the scope is
|
||||
* unavailable); the API key stays pure credentials-domain and never
|
||||
* echoes.
|
||||
* default-chat-service ids ride the Host bridge route
|
||||
* (`/bailian-kb/settings`) the Host half registers, bypassing the settings
|
||||
* wire (which requires an apiproxy allowlist entry the composition does not
|
||||
* grant out-of-tree namespaces); the API key stays pure credentials-domain
|
||||
* and never echoes.
|
||||
*/
|
||||
|
||||
import type { ConnectionHandle } from '@deepseek-ai/dsh-client-connection/client'
|
||||
@@ -21,9 +21,8 @@ import type {} from '@deepseek-ai/dsh-client-ui-slots'
|
||||
// through cordis, never a value import).
|
||||
import type {} from '@deepseek-ai/dsh-client-ui-settings/client'
|
||||
import { BailianCard } from './BailianCard.tsx'
|
||||
import { BailianCardController, type BailianKbSection } from './bailian-card-controller.ts'
|
||||
import { BailianCardController } from './bailian-card-controller.ts'
|
||||
import { en, zh, type BailianKbLocaleKey } from './locales.ts'
|
||||
|
||||
declare module '@deepseek-ai/dsh-client-ui-slots' {
|
||||
interface LocaleNamespaceMap {
|
||||
/** The Bailian section page's copy. */
|
||||
@@ -35,7 +34,7 @@ declare module '@deepseek-ai/dsh-client-ui-slots' {
|
||||
const NS = 'tool-bailian-kb'
|
||||
|
||||
/** Required services (cordis fiber inject). */
|
||||
export const inject = ['slots', 'locale', 'connection', 'remote', 'settingsScope']
|
||||
export const inject = ['slots', 'locale', 'connection', 'remote']
|
||||
|
||||
/**
|
||||
* Mount the Bailian section page into the Settings left nav.
|
||||
@@ -49,15 +48,10 @@ export function apply(ctx: ClientContext): void {
|
||||
// render, so copy freshness rides the locale revision without re-registering.
|
||||
const t = ctx.locale.bind(NS)
|
||||
|
||||
// The echo transport: bound on this fiber, self-refreshing on pushed
|
||||
// settings-document invalidations and connection resets. A remote browser
|
||||
// binds in memory mode and the page degrades to write-only controls.
|
||||
const scope = ctx.settingsScope.bind<BailianKbSection>({ namespace: 'bailian-kb' })
|
||||
const card = new BailianCardController(api, scope)
|
||||
ctx.effect(
|
||||
() => scope.subscribe(() => { card.syncSettings() }),
|
||||
'tool-bailian-kb: settings echo',
|
||||
)
|
||||
// The echo transport: the Host bridge route (`/bailian-kb/settings`) lets
|
||||
// the page read and write the resolved section without riding the settings
|
||||
// wire (which requires an apiproxy allowlist entry).
|
||||
const card = new BailianCardController(api)
|
||||
// Values can change elsewhere (Models page, external file edits); the badges
|
||||
// must follow the Host, not the card's last write.
|
||||
ctx.effect(
|
||||
|
||||
Generated
+2058
-26
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user