@modesto_ma/dsh-channels 0.0.0-stage → 0.1.0
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 +302 -2
- package/cordis.patch.yml +3 -0
- package/docs/access-control-design.md +550 -0
- package/docs/assets/web-settings-panel.png +0 -0
- package/docs/assets/web-settings-restart-pending.png +0 -0
- package/docs/feasibility-and-design.md +213 -0
- package/docs/feishu-onboarding.md +268 -0
- package/docs/persistence.md +199 -0
- package/docs/refactor-plan.md +56 -0
- package/docs/review-dsh-0.2.0.md +389 -0
- package/docs/slash-commands-implementation-plan.md +410 -0
- package/docs/web-display-only-surface-roadmap.md +363 -0
- package/lib/client.js +2226 -0
- package/lib/client.js.map +1 -0
- package/lib/index.js +10217 -0
- package/lib/index.js.map +1 -0
- package/package.json +98 -4
- package/src/access/cards.ts +188 -0
- package/src/access/controller.ts +201 -0
- package/src/access/index.ts +13 -0
- package/src/access/policy.ts +103 -0
- package/src/access/requests.ts +512 -0
- package/src/access/store.ts +220 -0
- package/src/approvals.ts +8 -0
- package/src/cards/approval.ts +114 -0
- package/src/cards/index.ts +11 -0
- package/src/cards/primitives.ts +82 -0
- package/src/cards/question.ts +141 -0
- package/src/cards/streaming.ts +563 -0
- package/src/cards.ts +8 -0
- package/src/cleanup.ts +398 -0
- package/src/client/api.ts +262 -0
- package/src/client/catalog.ts +92 -0
- package/src/client/index.ts +61 -0
- package/src/client/panel.tsx +1475 -0
- package/src/client/styles.ts +98 -0
- package/src/commands/registry.ts +155 -0
- package/src/commands.ts +335 -0
- package/src/config.ts +206 -0
- package/src/domain.ts +428 -0
- package/src/dsh-types.ts +11 -0
- package/src/feishu/agent-context.ts +62 -0
- package/src/feishu/delivery.ts +188 -0
- package/src/feishu/inbound-attachments.ts +436 -0
- package/src/feishu-channel.ts +339 -0
- package/src/health.ts +233 -0
- package/src/host/agent.ts +181 -0
- package/src/host/commands.ts +63 -0
- package/src/host/core.ts +345 -0
- package/src/host/index.ts +12 -0
- package/src/host/interaction.ts +101 -0
- package/src/host/services.ts +320 -0
- package/src/index.ts +122 -0
- package/src/interactions/approvals.ts +154 -0
- package/src/interactions/pending.ts +162 -0
- package/src/interactions/questions.ts +274 -0
- package/src/questions.ts +8 -0
- package/src/routing/inbound.ts +524 -0
- package/src/service.ts +2138 -0
- package/src/session/manager.ts +352 -0
- package/src/session/repair.ts +157 -0
- package/src/session/request-policy.ts +192 -0
- package/src/session/routes.ts +165 -0
- package/src/session-commands.ts +310 -0
- package/src/session-export.ts +112 -0
- package/src/settings/catalog.ts +309 -0
- package/src/settings/contract.ts +508 -0
- package/src/settings/overlay.ts +164 -0
- package/src/settings/routes.ts +409 -0
- package/src/settings/surface.ts +773 -0
- package/src/stats.ts +485 -0
- package/src/streamer.ts +1108 -0
- package/src/workspaces.ts +199 -0
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Logical Session export format and in-memory gzip packaging.
|
|
3
|
+
*
|
|
4
|
+
* This is deliberately a projection of the live logical Session surface. It
|
|
5
|
+
* does not walk descendant Sessions, fetch attachments, or copy persistence
|
|
6
|
+
* backend artifacts.
|
|
7
|
+
*
|
|
8
|
+
* @module dsh-channels/session-export
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { readFileSync } from 'node:fs'
|
|
12
|
+
import { promisify } from 'node:util'
|
|
13
|
+
import { gzip } from 'node:zlib'
|
|
14
|
+
import type { Session } from './dsh-types.ts'
|
|
15
|
+
|
|
16
|
+
interface PackageMetadata {
|
|
17
|
+
version?: unknown
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function packageVersion(): string {
|
|
21
|
+
const metadata = JSON.parse(
|
|
22
|
+
readFileSync(new URL('../package.json', import.meta.url), 'utf8'),
|
|
23
|
+
) as PackageMetadata
|
|
24
|
+
if (typeof metadata.version !== 'string' || metadata.version === '') {
|
|
25
|
+
throw new Error('dsh-channels: package version is unavailable')
|
|
26
|
+
}
|
|
27
|
+
return metadata.version
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export const LOGICAL_EXPORT_FORMAT = 'dsh-logical-session-jsonl'
|
|
31
|
+
export const LOGICAL_EXPORT_FORMAT_VERSION = 1
|
|
32
|
+
export const DSH_CHANNELS_VERSION = packageVersion()
|
|
33
|
+
|
|
34
|
+
export interface LogicalExportManifest {
|
|
35
|
+
format: typeof LOGICAL_EXPORT_FORMAT
|
|
36
|
+
formatVersion: typeof LOGICAL_EXPORT_FORMAT_VERSION
|
|
37
|
+
logical: true
|
|
38
|
+
exportedAt: string
|
|
39
|
+
plugin: {
|
|
40
|
+
name: 'dsh-channels'
|
|
41
|
+
version: string
|
|
42
|
+
}
|
|
43
|
+
includes: {
|
|
44
|
+
descendants: false
|
|
45
|
+
attachments: false
|
|
46
|
+
rawArtifacts: false
|
|
47
|
+
}
|
|
48
|
+
session: {
|
|
49
|
+
id: string
|
|
50
|
+
header: Session['header']
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface LogicalSessionExportFile {
|
|
55
|
+
fileName: string
|
|
56
|
+
source: Buffer
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export type SessionExportBuilder = (session: Session) => Promise<LogicalSessionExportFile>
|
|
60
|
+
|
|
61
|
+
export interface LogicalSessionExportOptions {
|
|
62
|
+
/** Deterministic clock seam for format tests. */
|
|
63
|
+
exportedAt?: Date
|
|
64
|
+
/** Explicit package-version seam for format tests. */
|
|
65
|
+
pluginVersion?: string
|
|
66
|
+
/** Compression seam used to verify gzip failures without filesystem I/O. */
|
|
67
|
+
compress?: (source: Buffer) => Promise<Buffer>
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const gzipBuffer = promisify(gzip)
|
|
71
|
+
|
|
72
|
+
/** Build one immutable UTF-8 JSONL snapshot before any asynchronous work. */
|
|
73
|
+
export function serializeLogicalSession(
|
|
74
|
+
session: Session,
|
|
75
|
+
options: Pick<LogicalSessionExportOptions, 'exportedAt' | 'pluginVersion'> = {},
|
|
76
|
+
): Buffer {
|
|
77
|
+
const manifest: LogicalExportManifest = {
|
|
78
|
+
format: LOGICAL_EXPORT_FORMAT,
|
|
79
|
+
formatVersion: LOGICAL_EXPORT_FORMAT_VERSION,
|
|
80
|
+
logical: true,
|
|
81
|
+
exportedAt: (options.exportedAt ?? new Date()).toISOString(),
|
|
82
|
+
plugin: {
|
|
83
|
+
name: 'dsh-channels',
|
|
84
|
+
version: options.pluginVersion ?? DSH_CHANNELS_VERSION,
|
|
85
|
+
},
|
|
86
|
+
includes: {
|
|
87
|
+
descendants: false,
|
|
88
|
+
attachments: false,
|
|
89
|
+
rawArtifacts: false,
|
|
90
|
+
},
|
|
91
|
+
session: {
|
|
92
|
+
id: session.id,
|
|
93
|
+
header: session.header,
|
|
94
|
+
},
|
|
95
|
+
}
|
|
96
|
+
const lines = [JSON.stringify(manifest)]
|
|
97
|
+
for (const event of session.snapshotEvents()) lines.push(JSON.stringify(event))
|
|
98
|
+
return Buffer.from(`${lines.join('\n')}\n`, 'utf8')
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** Serialize the logical snapshot, then gzip it entirely in memory. */
|
|
102
|
+
export async function createLogicalSessionExport(
|
|
103
|
+
session: Session,
|
|
104
|
+
options: LogicalSessionExportOptions = {},
|
|
105
|
+
): Promise<LogicalSessionExportFile> {
|
|
106
|
+
const snapshot = serializeLogicalSession(session, options)
|
|
107
|
+
const source = await (options.compress ?? gzipBuffer)(snapshot)
|
|
108
|
+
return {
|
|
109
|
+
fileName: `dsh-session-${session.id}.logical.jsonl.gz`,
|
|
110
|
+
source,
|
|
111
|
+
}
|
|
112
|
+
}
|
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Web 设置面板的 Host 侧选择器目录:把宿主 LLM 服务注册的 provider / model
|
|
3
|
+
* / 推理档位,以及 agent-preset roster 折叠成面板可渲染的选项列表。
|
|
4
|
+
*
|
|
5
|
+
* 目录只用于「选择」:任何查询失败(服务未挂载、方法缺失、超时、抛错)都
|
|
6
|
+
* 降级为 `selectable: false`,客户端据此退回原有自由文本输入框,绝不让
|
|
7
|
+
* 面板加载依赖宿主的网络状态。成功结果按短 TTL 缓存,避免用户来回切换
|
|
8
|
+
* provider/model 时反复查询适配器。
|
|
9
|
+
*
|
|
10
|
+
* @module dsh-channels/settings/catalog
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import type {
|
|
14
|
+
AgentDefaultModel,
|
|
15
|
+
AgentPreset,
|
|
16
|
+
AgentPresets,
|
|
17
|
+
HostContext,
|
|
18
|
+
LlmModelInfo,
|
|
19
|
+
LlmProviderInfo,
|
|
20
|
+
} from '../dsh-types.ts'
|
|
21
|
+
import type { CatalogWire, FieldOptionWire, SelectCatalogWire } from './contract.ts'
|
|
22
|
+
|
|
23
|
+
/** 目录查询的硬上限:面板加载不能被宿主的模型发现/连接拖住。 */
|
|
24
|
+
const LOOKUP_TIMEOUT_MS = 4_000
|
|
25
|
+
|
|
26
|
+
/** 成功查询的缓存时长(毫秒)。 */
|
|
27
|
+
const CACHE_TTL_MS = 30_000
|
|
28
|
+
|
|
29
|
+
export type CatalogWarn = (message: string, ...args: unknown[]) => void
|
|
30
|
+
|
|
31
|
+
/** Host capability used by the settings panel to populate its selectors. */
|
|
32
|
+
export interface SettingsCatalog {
|
|
33
|
+
/**
|
|
34
|
+
* Resolve the selector catalog for one configured route.
|
|
35
|
+
* @param provider - configured provider route (absent = inherit deployment default).
|
|
36
|
+
* @param model - configured model id (absent = inherit deployment default).
|
|
37
|
+
* @returns selector options; groups the host cannot enumerate are `selectable: false`.
|
|
38
|
+
*/
|
|
39
|
+
catalog(provider?: string, model?: string): Promise<CatalogWire>
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function messageOf(error: unknown): string {
|
|
43
|
+
return error instanceof Error ? error.message : String(error)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Resolve `work` or `undefined` after `ms`, so a hanging adapter cannot stall the panel. */
|
|
47
|
+
async function within<T>(work: Promise<T>, ms: number): Promise<T | undefined> {
|
|
48
|
+
let timer: ReturnType<typeof setTimeout> | undefined
|
|
49
|
+
const timeout = new Promise<undefined>((resolve) => {
|
|
50
|
+
timer = setTimeout(() => { resolve(undefined) }, ms)
|
|
51
|
+
timer.unref?.()
|
|
52
|
+
})
|
|
53
|
+
try {
|
|
54
|
+
return await Promise.race([work, timeout])
|
|
55
|
+
} finally {
|
|
56
|
+
if (timer !== undefined) clearTimeout(timer)
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Leading "inherit / clear" choice every selector gets. */
|
|
61
|
+
function inheritOption(label: string): FieldOptionWire {
|
|
62
|
+
return { value: '', label }
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** `Display name(id)` when the host publishes both, else the id alone. */
|
|
66
|
+
function labelledOption(id: string, name: string | undefined): FieldOptionWire {
|
|
67
|
+
const display = name === undefined ? '' : name.trim()
|
|
68
|
+
return { value: id, label: display === '' || display === id ? id : `${display}(${id})` }
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Non-empty display text, otherwise undefined. */
|
|
72
|
+
function textOf(value: unknown): string | undefined {
|
|
73
|
+
if (typeof value !== 'string') return undefined
|
|
74
|
+
const trimmed = value.trim()
|
|
75
|
+
return trimmed === '' ? undefined : trimmed
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Build the catalog over one live host context.
|
|
80
|
+
* @param ctx - host context carrying `llm` and the optional preset/default-model services.
|
|
81
|
+
* @param warn - optional diagnostic sink for failed lookups.
|
|
82
|
+
* @returns the catalog facade.
|
|
83
|
+
*/
|
|
84
|
+
export function createHostCatalog(ctx: HostContext, warn?: CatalogWarn): SettingsCatalog {
|
|
85
|
+
const llm = ctx.llm
|
|
86
|
+
const presets = ctx.get?.('agentPresets') as AgentPresets | undefined
|
|
87
|
+
const defaultModel = ctx.get?.('agentDefaultModel') as AgentDefaultModel | undefined
|
|
88
|
+
const cache = new Map<string, { at: number; value: unknown }>()
|
|
89
|
+
|
|
90
|
+
/** Cache a successful lookup; failures are re-tried on the next panel refresh. */
|
|
91
|
+
async function cached<T>(key: string, load: () => Promise<T> | T): Promise<T | undefined> {
|
|
92
|
+
const hit = cache.get(key)
|
|
93
|
+
if (hit !== undefined && Date.now() - hit.at < CACHE_TTL_MS) return hit.value as T
|
|
94
|
+
try {
|
|
95
|
+
const value = await within(Promise.resolve(load()), LOOKUP_TIMEOUT_MS)
|
|
96
|
+
if (value === undefined) return undefined
|
|
97
|
+
cache.set(key, { at: Date.now(), value })
|
|
98
|
+
return value
|
|
99
|
+
} catch (error) {
|
|
100
|
+
warn?.('dsh-channels: 读取 %s 选择目录失败:%s', key, messageOf(error))
|
|
101
|
+
return undefined
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** Registered provider routes, `undefined` when the deployment exposes none. */
|
|
106
|
+
async function providerOptions(): Promise<FieldOptionWire[] | undefined> {
|
|
107
|
+
if (typeof llm.listProviders !== 'function') return undefined
|
|
108
|
+
const rows = await cached<readonly LlmProviderInfo[]>('providers', () => llm.listProviders!())
|
|
109
|
+
if (rows === undefined) return undefined
|
|
110
|
+
const options: FieldOptionWire[] = []
|
|
111
|
+
const seen = new Set<string>()
|
|
112
|
+
for (const row of rows) {
|
|
113
|
+
const id = textOf((row as { id?: unknown } | undefined)?.id)
|
|
114
|
+
if (id === undefined || seen.has(id)) continue
|
|
115
|
+
seen.add(id)
|
|
116
|
+
options.push(labelledOption(id, textOf((row as { name?: unknown }).name)))
|
|
117
|
+
}
|
|
118
|
+
return options.length === 0 ? undefined : options
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** Models advertised for one provider route, `undefined` when it has no catalog. */
|
|
122
|
+
async function modelOptions(provider: string): Promise<FieldOptionWire[] | undefined> {
|
|
123
|
+
if (provider === '' || typeof llm.listModels !== 'function') return undefined
|
|
124
|
+
const rows = await cached<readonly LlmModelInfo[]>(`models:${provider}`, () => llm.listModels!(provider))
|
|
125
|
+
if (rows === undefined) return undefined
|
|
126
|
+
const options: FieldOptionWire[] = []
|
|
127
|
+
const seen = new Set<string>()
|
|
128
|
+
for (const row of rows) {
|
|
129
|
+
const id = textOf((row as { id?: unknown } | undefined)?.id)
|
|
130
|
+
if (id === undefined || seen.has(id)) continue
|
|
131
|
+
seen.add(id)
|
|
132
|
+
options.push(labelledOption(id, textOf((row as { name?: unknown }).name)))
|
|
133
|
+
}
|
|
134
|
+
return options.length === 0 ? undefined : options
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** Adapter metadata for one exact route; `undefined` when it cannot be resolved. */
|
|
138
|
+
async function modelInfo(provider: string, model: string): Promise<LlmModelInfo | undefined> {
|
|
139
|
+
if (provider === '' || model === '') return undefined
|
|
140
|
+
return await cached<LlmModelInfo>(`model-info:${provider}/${model}`, () => llm.resolveModelInfo(provider, model))
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** Declared preset rows, `undefined` when the roster has no `list()` or no rows. */
|
|
144
|
+
async function presetRows(): Promise<readonly AgentPreset[] | undefined> {
|
|
145
|
+
if (typeof presets?.list !== 'function') return undefined
|
|
146
|
+
const rows = await cached<readonly AgentPreset[]>('presets', () => presets!.list!())
|
|
147
|
+
if (rows === undefined || rows.length === 0) return undefined
|
|
148
|
+
const filtered = rows.filter(row => textOf((row as { id?: unknown } | undefined)?.id) !== undefined)
|
|
149
|
+
return filtered.length === 0 ? undefined : filtered
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/** Roster default preset id, when the implementation publishes it. */
|
|
153
|
+
function defaultPresetId(): string | undefined {
|
|
154
|
+
try {
|
|
155
|
+
return textOf(presets?.defaultId)
|
|
156
|
+
} catch {
|
|
157
|
+
return undefined
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/** Deployment default model selection, when the host exposes `agentDefaultModel`. */
|
|
162
|
+
function deploymentDefault(): CatalogWire['deploymentDefault'] | undefined {
|
|
163
|
+
try {
|
|
164
|
+
const selection = defaultModel?.currentSelection?.()
|
|
165
|
+
const provider = textOf(selection?.provider)
|
|
166
|
+
const model = textOf(selection?.model)
|
|
167
|
+
if (provider === undefined || model === undefined) return undefined
|
|
168
|
+
const reasoningEffort = textOf(selection?.reasoningEffort)
|
|
169
|
+
return { provider, model, ...(reasoningEffort === undefined ? {} : { reasoningEffort }) }
|
|
170
|
+
} catch {
|
|
171
|
+
return undefined
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/** Selector for the configured provider route. */
|
|
176
|
+
function providerWire(
|
|
177
|
+
providers: readonly FieldOptionWire[] | undefined,
|
|
178
|
+
deployment: CatalogWire['deploymentDefault'],
|
|
179
|
+
): SelectCatalogWire {
|
|
180
|
+
if (providers === undefined) return { selectable: false, options: [] }
|
|
181
|
+
const label = deployment?.provider === undefined
|
|
182
|
+
? '留空 = 部署默认'
|
|
183
|
+
: `留空 = 部署默认(${deployment.provider})`
|
|
184
|
+
return { selectable: true, options: [inheritOption(label), ...providers] }
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/** Selector for the configured model id. */
|
|
188
|
+
function modelWire(
|
|
189
|
+
models: readonly FieldOptionWire[] | undefined,
|
|
190
|
+
provider: string,
|
|
191
|
+
deployment: CatalogWire['deploymentDefault'],
|
|
192
|
+
providers: readonly FieldOptionWire[] | undefined,
|
|
193
|
+
): SelectCatalogWire {
|
|
194
|
+
const label = deployment?.model === undefined
|
|
195
|
+
? '留空 = 部署默认'
|
|
196
|
+
: `留空 = 部署默认(${deployment.model})`
|
|
197
|
+
if (models === undefined) {
|
|
198
|
+
// Provider selectable but none configured yet: keep the model selector
|
|
199
|
+
// visible and disabled so the dependency is discoverable. A provider the
|
|
200
|
+
// adapter cannot enumerate instead falls back to free text.
|
|
201
|
+
if (providers !== undefined && provider === '') {
|
|
202
|
+
return {
|
|
203
|
+
selectable: true,
|
|
204
|
+
options: [inheritOption(label)],
|
|
205
|
+
disabled: true,
|
|
206
|
+
help: '先选择模型服务商,再选择模型 id。留空表示沿用部署默认。',
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
return { selectable: false, options: [] }
|
|
210
|
+
}
|
|
211
|
+
const select: SelectCatalogWire = {
|
|
212
|
+
selectable: true,
|
|
213
|
+
options: [inheritOption(label), ...models],
|
|
214
|
+
}
|
|
215
|
+
if (provider === '') {
|
|
216
|
+
select.disabled = true
|
|
217
|
+
select.help = '先选择模型服务商,再选择模型 id。留空表示沿用部署默认。'
|
|
218
|
+
}
|
|
219
|
+
return select
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/** Selector for the configured reasoning effort. */
|
|
223
|
+
function effortWire(
|
|
224
|
+
info: LlmModelInfo | undefined,
|
|
225
|
+
provider: string,
|
|
226
|
+
model: string,
|
|
227
|
+
providers: readonly FieldOptionWire[] | undefined,
|
|
228
|
+
): SelectCatalogWire {
|
|
229
|
+
// 无法枚举 provider 时,整条选择链都退回自由文本输入。
|
|
230
|
+
if (providers === undefined) return { selectable: false, options: [] }
|
|
231
|
+
if (provider === '' || model === '') {
|
|
232
|
+
return {
|
|
233
|
+
selectable: true,
|
|
234
|
+
options: [inheritOption('留空 = 适配器默认')],
|
|
235
|
+
disabled: true,
|
|
236
|
+
help: '先选择模型服务商与模型 id,再选择推理档位。',
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
// 路由无法解析(适配器报错/超时):退回自由文本,保留手填能力。
|
|
240
|
+
if (info === undefined) return { selectable: false, options: [] }
|
|
241
|
+
const efforts = (info.reasoning?.efforts ?? [])
|
|
242
|
+
.filter(effort => textOf(effort.id) !== undefined)
|
|
243
|
+
.map(effort => labelledOption(effort.id, textOf(effort.name)))
|
|
244
|
+
if (efforts.length === 0) {
|
|
245
|
+
return {
|
|
246
|
+
selectable: true,
|
|
247
|
+
options: [inheritOption('留空 = 适配器默认')],
|
|
248
|
+
disabled: true,
|
|
249
|
+
help: '当前模型没有声明可选推理档位,只能使用适配器默认。',
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
const defaultEffort = textOf(info.reasoning?.defaultEffort)
|
|
253
|
+
return {
|
|
254
|
+
selectable: true,
|
|
255
|
+
options: [
|
|
256
|
+
inheritOption(defaultEffort === undefined
|
|
257
|
+
? '留空 = 适配器默认'
|
|
258
|
+
: `留空 = 适配器默认(${defaultEffort})`),
|
|
259
|
+
...efforts,
|
|
260
|
+
],
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/** Selector for the agent preset id. */
|
|
265
|
+
function presetWire(
|
|
266
|
+
rows: readonly AgentPreset[] | undefined,
|
|
267
|
+
defaultId: string | undefined,
|
|
268
|
+
): SelectCatalogWire {
|
|
269
|
+
if (rows === undefined) return { selectable: false, options: [] }
|
|
270
|
+
const options = rows.map((row) => {
|
|
271
|
+
const id = textOf(row.id)!
|
|
272
|
+
const option = labelledOption(id, textOf(row.name))
|
|
273
|
+
if (row.broken === undefined) return option
|
|
274
|
+
return { ...option, label: `${option.label}(不可用)` }
|
|
275
|
+
})
|
|
276
|
+
return {
|
|
277
|
+
selectable: true,
|
|
278
|
+
options: [
|
|
279
|
+
inheritOption(defaultId === undefined ? '留空 = roster 默认' : `留空 = roster 默认(${defaultId})`),
|
|
280
|
+
...options,
|
|
281
|
+
],
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
return {
|
|
286
|
+
async catalog(provider?: string, model?: string): Promise<CatalogWire> {
|
|
287
|
+
const wantedProvider = textOf(provider) ?? ''
|
|
288
|
+
const wantedModel = textOf(model) ?? ''
|
|
289
|
+
const deployment = deploymentDefault()
|
|
290
|
+
const defaultPreset = defaultPresetId()
|
|
291
|
+
|
|
292
|
+
const [providers, models, info, rows] = await Promise.all([
|
|
293
|
+
providerOptions(),
|
|
294
|
+
modelOptions(wantedProvider),
|
|
295
|
+
modelInfo(wantedProvider, wantedModel),
|
|
296
|
+
presetRows(),
|
|
297
|
+
])
|
|
298
|
+
|
|
299
|
+
return {
|
|
300
|
+
provider: providerWire(providers, deployment),
|
|
301
|
+
model: modelWire(models, wantedProvider, deployment, providers),
|
|
302
|
+
reasoningEffort: effortWire(info, wantedProvider, wantedModel, providers),
|
|
303
|
+
agentPreset: presetWire(rows, defaultPreset),
|
|
304
|
+
...(deployment === undefined ? {} : { deploymentDefault: deployment }),
|
|
305
|
+
...(defaultPreset === undefined ? {} : { defaultPresetId: defaultPreset }),
|
|
306
|
+
}
|
|
307
|
+
},
|
|
308
|
+
}
|
|
309
|
+
}
|