dsh-audiogen 0.4.6 → 0.4.7

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.
@@ -34,12 +34,29 @@ export interface PromptEnhanceDeps {
34
34
  llm?: () => unknown
35
35
  }
36
36
 
37
- /** 读取 Agent 默认模型并调用 LLM 增强,返回增强后的文本。 */
38
- export async function enhancePromptText(deps: PromptEnhanceDeps, prompt: string, mode: AudioMode): Promise<string> {
37
+ /** 设置中显式选择的增强模型(provider + model)。 */
38
+ export interface EnhanceModelSelection {
39
+ provider: string
40
+ model: string
41
+ }
42
+
43
+ /** 读取 Agent 默认模型并调用 LLM 增强,返回增强后的文本。
44
+ * @param override - 用户设置的增强模型;缺省/为空时回退到 agent-default-model。 */
45
+ export async function enhancePromptText(
46
+ deps: PromptEnhanceDeps,
47
+ prompt: string,
48
+ mode: AudioMode,
49
+ override?: EnhanceModelSelection,
50
+ ): Promise<string> {
39
51
  const text = prompt.trim()
40
52
  if (text === '') throw new AudioGenError('提示词为空,无法增强', 'enhance-empty-prompt')
41
- const descriptor = (deps.settings.describe({ redactSecrets: true }) ?? []).find(candidate => String(candidate.ns) === 'agent-default-model')
42
- const value = (descriptor?.value ?? {}) as { provider?: unknown; model?: unknown }
53
+ const selected = override !== undefined && override.provider.trim() !== '' && override.model.trim() !== ''
54
+ ? { provider: override.provider.trim(), model: override.model.trim() }
55
+ : undefined
56
+ const descriptor = selected === undefined
57
+ ? (deps.settings.describe({ redactSecrets: true }) ?? []).find(candidate => String(candidate.ns) === 'agent-default-model')
58
+ : undefined
59
+ const value = selected ?? ((descriptor?.value ?? {}) as { provider?: unknown; model?: unknown })
43
60
  const provider = typeof value.provider === 'string' && value.provider.trim() !== '' ? value.provider.trim() : ''
44
61
  const model = typeof value.model === 'string' && value.model.trim() !== '' ? value.model.trim() : ''
45
62
  if (provider === '' || model === '') {
@@ -53,22 +70,32 @@ export async function enhancePromptText(deps: PromptEnhanceDeps, prompt: string,
53
70
  const timer = setTimeout(() => controller.abort(new DOMException('The operation timed out.', 'TimeoutError')), 30_000)
54
71
  timer.unref?.()
55
72
  let output = ''
73
+ let terminalFailure = ''
56
74
  try {
57
75
  for await (const chunk of runtime.stream({
58
76
  provider,
59
77
  model,
60
- messages: [{ role: 'user', content: text }],
78
+ // dsh-llm 的 Message.content 是 ContentBlock[](如 [{ type: 'text', text }]),不是纯字符串;
79
+ // 传字符串会在适配器 contentHasImage() 里抛 "content.some is not a function",
80
+ // 主机把该异常转成终止的 finish/error 分片,导致下面永远收集不到文本。
81
+ messages: [{ role: 'user', content: [{ type: 'text', text }] }],
61
82
  system: instructionsFor(mode),
62
83
  temperature: 0.7,
63
84
  maxTokens: 1200,
64
85
  signal: controller.signal,
65
86
  })) {
66
- const record = chunk as { type?: string; text?: string; block?: { type?: string; text?: string } }
87
+ const record = chunk as { type?: string; text?: string; block?: { type?: string; text?: string }; reason?: { kind?: string; failure?: { message?: string; code?: string } } }
67
88
  if (record.type === 'text-delta' && typeof record.text === 'string') {
68
89
  output += record.text
69
90
  } else if (record.type === 'block-end' && record.block !== undefined
70
91
  && record.block.type === 'text' && typeof record.block.text === 'string') {
71
92
  output += record.block.text
93
+ } else if (record.type === 'finish' && record.reason !== undefined
94
+ && record.reason.kind !== 'stop' && record.reason.kind !== undefined && terminalFailure === '') {
95
+ const failure = record.reason.failure
96
+ terminalFailure = typeof failure?.message === 'string' && failure.message.trim() !== ''
97
+ ? `${failure.message}${typeof failure.code === 'string' ? `(${failure.code})` : ''}`
98
+ : `stream ${record.reason.kind}`
72
99
  }
73
100
  }
74
101
  } finally {
@@ -76,6 +103,9 @@ export async function enhancePromptText(deps: PromptEnhanceDeps, prompt: string,
76
103
  }
77
104
  const result = stripFences(output.trim())
78
105
  if (result === '') {
106
+ if (terminalFailure !== '') {
107
+ throw new AudioGenError(`增强失败:LLM 调用出错(${terminalFailure})。请检查「设置 → 模型」的默认模型是否可用`, 'enhance-llm-error')
108
+ }
79
109
  throw new AudioGenError('模型未返回增强内容:请检查「设置 → 模型」的默认模型是否可用(或稍后重试)', 'enhance-empty-result')
80
110
  }
81
111
  return result
package/src/protocol.ts CHANGED
@@ -8,7 +8,7 @@
8
8
  export const AUDIOGEN_SETTINGS_NAMESPACE = 'dsh-audiogen'
9
9
 
10
10
  /** Published package version shared by the host updater and the client UI. */
11
- export const PLUGIN_VERSION = '0.4.6'
11
+ export const PLUGIN_VERSION = '0.4.7'
12
12
 
13
13
  /** Same-origin route family (loopback-only, mirroring dsh-imagegen). */
14
14
  export const SETTINGS_API = {
@@ -30,6 +30,9 @@ export const ENHANCE_API = '/api/dsh-audiogen/prompt/enhance' as const
30
30
  /** Host-mediated built-in provider catalog (channels the user can instantiate). */
31
31
  export const PRESETS_API = '/api/dsh-audiogen/presets' as const
32
32
 
33
+ /** LLM 模型目录:提示词增强模型的候选(来自「设置 → 模型」各提供方)。 */
34
+ export const LLM_MODELS_API = '/api/dsh-audiogen/llm/models' as const
35
+
33
36
  /** Host-mediated model/voice discovery endpoint. */
34
37
  export const MODEL_API = {
35
38
  discover: '/api/dsh-audiogen/models/discover',
@@ -91,6 +94,18 @@ export interface ModelMapping {
91
94
  category?: AudioModelCategory
92
95
  }
93
96
 
97
+ /** 一个「设置 → 模型」中的 LLM 候选模型(提示词增强模型下拉用)。 */
98
+ export interface LlmModelOption {
99
+ /** 提供方路由(如 deepseek-official / google)。 */
100
+ provider: string
101
+ /** 提供方展示名(如 DeepSeek / google)。 */
102
+ providerName: string
103
+ /** 模型 id(上游型号,如 deepseek-v4-flash-vision-exp)。 */
104
+ id: string
105
+ /** 模型展示名。 */
106
+ name: string
107
+ }
108
+
94
109
  /** A model/voice discovered from a vendor endpoint (not yet persisted). */
95
110
  export interface DiscoveredAudioModel extends ModelMapping {
96
111
  /** Optional human-readable description from the vendor. */
package/src/routes.ts CHANGED
@@ -16,9 +16,9 @@ import { discoverAudioModels } from './audio-models.ts'
16
16
  import { AUDIO_PRESETS } from './audio-presets.ts'
17
17
  import { appendHistory, clearHistory, listHistory, readAudioFile, removeHistory, saveAudioFile, listLibrary, saveToLibrary, updateLibraryEntry, removeLibraryEntries, readLibraryFile } from './audio-store.ts'
18
18
  import {
19
- AUDIO_API, AUDIOGEN_SETTINGS_NAMESPACE, ENHANCE_API, GENERATE_API, HISTORY_API, LIBRARY_API, MODEL_API, PRESETS_API, SETTINGS_API, TASK_API,
19
+ AUDIO_API, AUDIOGEN_SETTINGS_NAMESPACE, ENHANCE_API, GENERATE_API, HISTORY_API, LIBRARY_API, LLM_MODELS_API, MODEL_API, PRESETS_API, SETTINGS_API, TASK_API,
20
20
  LIBRARY_TYPES,
21
- type GenerateAudioRequest, type GeneratedAudio, type HistoryEntryInput, type LibraryAudioInput, type LibraryProvenance, type LibraryType,
21
+ type GenerateAudioRequest, type GeneratedAudio, type HistoryEntryInput, type LibraryAudioInput, type LibraryProvenance, type LibraryType, type LlmModelOption,
22
22
  } from './protocol.ts'
23
23
 
24
24
  const MAX_JSON_BODY_BYTES = 16 * 1024 * 1024
@@ -49,6 +49,8 @@ export interface AudiogenRoutesDeps {
49
49
  budget: GenerationBudget
50
50
  /** 提示词增强:调用 Agent 默认模型,返回增强后的文本。 */
51
51
  enhance: (prompt: string, mode: GenerateAudioRequest['mode']) => Promise<string>
52
+ /** 「设置 → 模型」提供方列表 + 各自可广播模型(增强模型下拉候选)。 */
53
+ llmModelOptions: () => Promise<LlmModelOption[]>
52
54
  }
53
55
 
54
56
  function isLoopbackRequest(request: IncomingMessage): boolean {
@@ -327,6 +329,20 @@ export function makeRoutes(deps: AudiogenRoutesDeps): WebRoute[] {
327
329
  writeJson(res, 200, { ok: true, presets: AUDIO_PRESETS })
328
330
  },
329
331
  },
332
+ // --------------------------------------------- LLM models (enhance)
333
+ {
334
+ kind: 'exact',
335
+ path: LLM_MODELS_API,
336
+ handler: async (req, res) => {
337
+ if (!guard(req, res, 'POST')) return
338
+ try {
339
+ const providers = await deps.llmModelOptions()
340
+ writeJson(res, 200, { ok: true, providers })
341
+ } catch (error) {
342
+ writeJson(res, 200, { ok: false, code: 'llm-models-failed', message: messageOf(error) })
343
+ }
344
+ },
345
+ },
330
346
  // ---------------------------------------------- model/voice discovery
331
347
  {
332
348
  kind: 'exact',