@tachu/extensions 1.0.0-alpha.1

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.
Files changed (210) hide show
  1. package/CHANGELOG.md +119 -0
  2. package/LICENSE +201 -0
  3. package/README.md +1104 -0
  4. package/README_ZH.md +1082 -0
  5. package/dist/backends/file.d.ts +18 -0
  6. package/dist/backends/file.d.ts.map +1 -0
  7. package/dist/backends/file.js +85 -0
  8. package/dist/backends/file.js.map +1 -0
  9. package/dist/backends/index.d.ts +4 -0
  10. package/dist/backends/index.d.ts.map +1 -0
  11. package/dist/backends/index.js +4 -0
  12. package/dist/backends/index.js.map +1 -0
  13. package/dist/backends/terminal.d.ts +18 -0
  14. package/dist/backends/terminal.d.ts.map +1 -0
  15. package/dist/backends/terminal.js +81 -0
  16. package/dist/backends/terminal.js.map +1 -0
  17. package/dist/backends/web.d.ts +18 -0
  18. package/dist/backends/web.d.ts.map +1 -0
  19. package/dist/backends/web.js +55 -0
  20. package/dist/backends/web.js.map +1 -0
  21. package/dist/common/net.d.ts +39 -0
  22. package/dist/common/net.d.ts.map +1 -0
  23. package/dist/common/net.js +177 -0
  24. package/dist/common/net.js.map +1 -0
  25. package/dist/common/path.d.ts +51 -0
  26. package/dist/common/path.d.ts.map +1 -0
  27. package/dist/common/path.js +76 -0
  28. package/dist/common/path.js.map +1 -0
  29. package/dist/common/process.d.ts +19 -0
  30. package/dist/common/process.d.ts.map +1 -0
  31. package/dist/common/process.js +67 -0
  32. package/dist/common/process.js.map +1 -0
  33. package/dist/index.d.ts +13 -0
  34. package/dist/index.d.ts.map +1 -0
  35. package/dist/index.js +13 -0
  36. package/dist/index.js.map +1 -0
  37. package/dist/mcp/index.d.ts +3 -0
  38. package/dist/mcp/index.d.ts.map +1 -0
  39. package/dist/mcp/index.js +3 -0
  40. package/dist/mcp/index.js.map +1 -0
  41. package/dist/mcp/sse-adapter.d.ts +82 -0
  42. package/dist/mcp/sse-adapter.d.ts.map +1 -0
  43. package/dist/mcp/sse-adapter.js +201 -0
  44. package/dist/mcp/sse-adapter.js.map +1 -0
  45. package/dist/mcp/stdio-adapter.d.ts +85 -0
  46. package/dist/mcp/stdio-adapter.d.ts.map +1 -0
  47. package/dist/mcp/stdio-adapter.js +203 -0
  48. package/dist/mcp/stdio-adapter.js.map +1 -0
  49. package/dist/memory/fs-memory-system.d.ts +147 -0
  50. package/dist/memory/fs-memory-system.d.ts.map +1 -0
  51. package/dist/memory/fs-memory-system.js +266 -0
  52. package/dist/memory/fs-memory-system.js.map +1 -0
  53. package/dist/memory/index.d.ts +2 -0
  54. package/dist/memory/index.d.ts.map +1 -0
  55. package/dist/memory/index.js +2 -0
  56. package/dist/memory/index.js.map +1 -0
  57. package/dist/observability/index.d.ts +3 -0
  58. package/dist/observability/index.d.ts.map +1 -0
  59. package/dist/observability/index.js +3 -0
  60. package/dist/observability/index.js.map +1 -0
  61. package/dist/observability/jsonl-emitter.d.ts +58 -0
  62. package/dist/observability/jsonl-emitter.d.ts.map +1 -0
  63. package/dist/observability/jsonl-emitter.js +96 -0
  64. package/dist/observability/jsonl-emitter.js.map +1 -0
  65. package/dist/observability/otel-emitter.d.ts +52 -0
  66. package/dist/observability/otel-emitter.d.ts.map +1 -0
  67. package/dist/observability/otel-emitter.js +143 -0
  68. package/dist/observability/otel-emitter.js.map +1 -0
  69. package/dist/providers/anthropic.d.ts +73 -0
  70. package/dist/providers/anthropic.d.ts.map +1 -0
  71. package/dist/providers/anthropic.js +521 -0
  72. package/dist/providers/anthropic.js.map +1 -0
  73. package/dist/providers/index.d.ts +5 -0
  74. package/dist/providers/index.d.ts.map +1 -0
  75. package/dist/providers/index.js +5 -0
  76. package/dist/providers/index.js.map +1 -0
  77. package/dist/providers/mock.d.ts +81 -0
  78. package/dist/providers/mock.d.ts.map +1 -0
  79. package/dist/providers/mock.js +160 -0
  80. package/dist/providers/mock.js.map +1 -0
  81. package/dist/providers/openai.d.ts +95 -0
  82. package/dist/providers/openai.d.ts.map +1 -0
  83. package/dist/providers/openai.js +529 -0
  84. package/dist/providers/openai.js.map +1 -0
  85. package/dist/providers/qwen.d.ts +145 -0
  86. package/dist/providers/qwen.d.ts.map +1 -0
  87. package/dist/providers/qwen.js +669 -0
  88. package/dist/providers/qwen.js.map +1 -0
  89. package/dist/rules/index.d.ts +9 -0
  90. package/dist/rules/index.d.ts.map +1 -0
  91. package/dist/rules/index.js +15 -0
  92. package/dist/rules/index.js.map +1 -0
  93. package/dist/rules/no-hallucination.md +11 -0
  94. package/dist/rules/no-sensitive-output.md +11 -0
  95. package/dist/rules/prefer-concise-response.md +11 -0
  96. package/dist/rules/require-tool-verification.md +11 -0
  97. package/dist/safety/default-gate.d.ts +112 -0
  98. package/dist/safety/default-gate.d.ts.map +1 -0
  99. package/dist/safety/default-gate.js +188 -0
  100. package/dist/safety/default-gate.js.map +1 -0
  101. package/dist/safety/index.d.ts +2 -0
  102. package/dist/safety/index.d.ts.map +1 -0
  103. package/dist/safety/index.js +2 -0
  104. package/dist/safety/index.js.map +1 -0
  105. package/dist/tools/_shared/web-client.d.ts +18 -0
  106. package/dist/tools/_shared/web-client.d.ts.map +1 -0
  107. package/dist/tools/_shared/web-client.js +46 -0
  108. package/dist/tools/_shared/web-client.js.map +1 -0
  109. package/dist/tools/apply-patch/descriptor.md +27 -0
  110. package/dist/tools/apply-patch/executor.d.ts +19 -0
  111. package/dist/tools/apply-patch/executor.d.ts.map +1 -0
  112. package/dist/tools/apply-patch/executor.js +193 -0
  113. package/dist/tools/apply-patch/executor.js.map +1 -0
  114. package/dist/tools/fetch-url/descriptor.md +44 -0
  115. package/dist/tools/fetch-url/executor.d.ts +28 -0
  116. package/dist/tools/fetch-url/executor.d.ts.map +1 -0
  117. package/dist/tools/fetch-url/executor.js +115 -0
  118. package/dist/tools/fetch-url/executor.js.map +1 -0
  119. package/dist/tools/index.d.ts +12 -0
  120. package/dist/tools/index.d.ts.map +1 -0
  121. package/dist/tools/index.js +286 -0
  122. package/dist/tools/index.js.map +1 -0
  123. package/dist/tools/list-dir/descriptor.md +29 -0
  124. package/dist/tools/list-dir/executor.d.ts +22 -0
  125. package/dist/tools/list-dir/executor.d.ts.map +1 -0
  126. package/dist/tools/list-dir/executor.js +48 -0
  127. package/dist/tools/list-dir/executor.js.map +1 -0
  128. package/dist/tools/read-file/descriptor.md +28 -0
  129. package/dist/tools/read-file/executor.d.ts +15 -0
  130. package/dist/tools/read-file/executor.d.ts.map +1 -0
  131. package/dist/tools/read-file/executor.js +22 -0
  132. package/dist/tools/read-file/executor.js.map +1 -0
  133. package/dist/tools/run-shell/descriptor.md +39 -0
  134. package/dist/tools/run-shell/executor.d.ts +20 -0
  135. package/dist/tools/run-shell/executor.d.ts.map +1 -0
  136. package/dist/tools/run-shell/executor.js +76 -0
  137. package/dist/tools/run-shell/executor.js.map +1 -0
  138. package/dist/tools/search-code/descriptor.md +31 -0
  139. package/dist/tools/search-code/executor.d.ts +23 -0
  140. package/dist/tools/search-code/executor.d.ts.map +1 -0
  141. package/dist/tools/search-code/executor.js +122 -0
  142. package/dist/tools/search-code/executor.js.map +1 -0
  143. package/dist/tools/shared.d.ts +47 -0
  144. package/dist/tools/shared.d.ts.map +1 -0
  145. package/dist/tools/shared.js +27 -0
  146. package/dist/tools/shared.js.map +1 -0
  147. package/dist/tools/web-fetch/descriptor.md +198 -0
  148. package/dist/tools/web-fetch/errors.d.ts +32 -0
  149. package/dist/tools/web-fetch/errors.d.ts.map +1 -0
  150. package/dist/tools/web-fetch/errors.js +91 -0
  151. package/dist/tools/web-fetch/errors.js.map +1 -0
  152. package/dist/tools/web-fetch/executor.d.ts +10 -0
  153. package/dist/tools/web-fetch/executor.d.ts.map +1 -0
  154. package/dist/tools/web-fetch/executor.js +191 -0
  155. package/dist/tools/web-fetch/executor.js.map +1 -0
  156. package/dist/tools/web-fetch/index.d.ts +4 -0
  157. package/dist/tools/web-fetch/index.d.ts.map +1 -0
  158. package/dist/tools/web-fetch/index.js +3 -0
  159. package/dist/tools/web-fetch/index.js.map +1 -0
  160. package/dist/tools/web-fetch/types.d.ts +157 -0
  161. package/dist/tools/web-fetch/types.d.ts.map +1 -0
  162. package/dist/tools/web-fetch/types.js +7 -0
  163. package/dist/tools/web-fetch/types.js.map +1 -0
  164. package/dist/tools/web-search/descriptor.md +89 -0
  165. package/dist/tools/web-search/errors.d.ts +33 -0
  166. package/dist/tools/web-search/errors.d.ts.map +1 -0
  167. package/dist/tools/web-search/errors.js +45 -0
  168. package/dist/tools/web-search/errors.js.map +1 -0
  169. package/dist/tools/web-search/executor.d.ts +10 -0
  170. package/dist/tools/web-search/executor.d.ts.map +1 -0
  171. package/dist/tools/web-search/executor.js +185 -0
  172. package/dist/tools/web-search/executor.js.map +1 -0
  173. package/dist/tools/web-search/index.d.ts +4 -0
  174. package/dist/tools/web-search/index.d.ts.map +1 -0
  175. package/dist/tools/web-search/index.js +3 -0
  176. package/dist/tools/web-search/index.js.map +1 -0
  177. package/dist/tools/web-search/types.d.ts +86 -0
  178. package/dist/tools/web-search/types.d.ts.map +1 -0
  179. package/dist/tools/web-search/types.js +7 -0
  180. package/dist/tools/web-search/types.js.map +1 -0
  181. package/dist/tools/write-file/descriptor.md +30 -0
  182. package/dist/tools/write-file/executor.d.ts +16 -0
  183. package/dist/tools/write-file/executor.d.ts.map +1 -0
  184. package/dist/tools/write-file/executor.js +18 -0
  185. package/dist/tools/write-file/executor.js.map +1 -0
  186. package/dist/transformers/document-to-text.d.ts +23 -0
  187. package/dist/transformers/document-to-text.d.ts.map +1 -0
  188. package/dist/transformers/document-to-text.js +69 -0
  189. package/dist/transformers/document-to-text.js.map +1 -0
  190. package/dist/transformers/image-to-text.d.ts +38 -0
  191. package/dist/transformers/image-to-text.d.ts.map +1 -0
  192. package/dist/transformers/image-to-text.js +82 -0
  193. package/dist/transformers/image-to-text.js.map +1 -0
  194. package/dist/transformers/index.d.ts +3 -0
  195. package/dist/transformers/index.d.ts.map +1 -0
  196. package/dist/transformers/index.js +3 -0
  197. package/dist/transformers/index.js.map +1 -0
  198. package/dist/vector/index.d.ts +3 -0
  199. package/dist/vector/index.d.ts.map +1 -0
  200. package/dist/vector/index.js +3 -0
  201. package/dist/vector/index.js.map +1 -0
  202. package/dist/vector/local-fs.d.ts +76 -0
  203. package/dist/vector/local-fs.d.ts.map +1 -0
  204. package/dist/vector/local-fs.js +153 -0
  205. package/dist/vector/local-fs.js.map +1 -0
  206. package/dist/vector/qdrant.d.ts +85 -0
  207. package/dist/vector/qdrant.d.ts.map +1 -0
  208. package/dist/vector/qdrant.js +208 -0
  209. package/dist/vector/qdrant.js.map +1 -0
  210. package/package.json +74 -0
@@ -0,0 +1,669 @@
1
+ import { ProviderError, TimeoutError, } from "@tachu/core";
2
+ import { withAbortTimeout } from "../common/net";
3
+ import { OpenAIProviderAdapter } from "./openai";
4
+ /**
5
+ * 从构造器级 `defaultHeaders` 去掉 `X-DashScope-SSE`,避免全局 `enable` 与非流式 `stream=false` 冲突;
6
+ * 具体取值由 {@link OpenAIProviderAdapter} 按请求类型(chat / chatStream)单独设置。
7
+ */
8
+ const stripDashScopeSseFromHeaders = (h) => {
9
+ if (!h)
10
+ return h;
11
+ const out = { ...h };
12
+ for (const k of Object.keys(out)) {
13
+ if (k.toLowerCase() === "x-dashscope-sse") {
14
+ delete out[k];
15
+ }
16
+ }
17
+ return out;
18
+ };
19
+ const mergeOpenAiExtra = (extra, workspaceId) => {
20
+ const prevRaw = extra && typeof extra.defaultHeaders === "object" && extra.defaultHeaders !== null
21
+ ? extra.defaultHeaders
22
+ : {};
23
+ const prevHeaders = stripDashScopeSseFromHeaders(prevRaw);
24
+ if (!workspaceId) {
25
+ if (prevRaw === prevHeaders) {
26
+ return extra;
27
+ }
28
+ return { ...(extra ?? {}), defaultHeaders: prevHeaders };
29
+ }
30
+ return {
31
+ ...(extra ?? {}),
32
+ defaultHeaders: {
33
+ ...prevHeaders,
34
+ "X-DashScope-WorkSpace": workspaceId,
35
+ },
36
+ };
37
+ };
38
+ const DEFAULT_TIMEOUT_MS = 120_000;
39
+ const DEFAULT_POLL_INTERVAL_MS = 2_000;
40
+ const DEFAULT_DASHSCOPE_ORIGIN = "https://dashscope.aliyuncs.com";
41
+ const isWanxImageModel = (model) => /wanx/i.test(model);
42
+ /**
43
+ * 判定走同步 `multimodal-generation/generation` 端点的生图模型。
44
+ *
45
+ * 目前覆盖:
46
+ * - `wan2.x-image` 系列(wan2.1-image、wan2.5-image、wan2.7-image、wan2.7-image-pro ...)
47
+ *
48
+ * 与 {@link isWanxImageModel} 互斥。`qwen-image-*` 系列目前仍走 OpenAI 兼容 chat,
49
+ * 行为由 {@link isDashScopeImageChatModelRequiringListContent} 继续覆盖,以避免对
50
+ * 已有用户的行为变更。
51
+ */
52
+ const isDashScopeMultimodalImageGenerationModel = (model) => {
53
+ if (isWanxImageModel(model)) {
54
+ return false;
55
+ }
56
+ return /^wan2\.\d+-image/i.test(model);
57
+ };
58
+ /**
59
+ * 万相 `wanx-*` 走原生异步 `image-synthesis`;其余在兼容 chat 里走的生图模型(如
60
+ * `qwen-image-*`、`wan2.x-image` 等)百炼常要求 `content` 为 list,不能为纯 string。
61
+ * 用模型名含 `image` 且非 `wanx` 作启发式(与控制台 id 对齐)。
62
+ *
63
+ * 注意:`wan2.x-image*` 在当前实现下会被路由到专属 multimodal-generation 端点,
64
+ * 不再经过 OpenAI 兼容 chat;因此此处判定结果虽然为 true 也不会触发,保留语义兼容。
65
+ */
66
+ const isDashScopeImageChatModelRequiringListContent = (model) => {
67
+ if (isWanxImageModel(model)) {
68
+ return false;
69
+ }
70
+ return /image/i.test(model);
71
+ };
72
+ /**
73
+ * 将 string 形式的 `content` 转为 `[{ type: "text", text }]`,满足 DashScope 对生图类 chat 的校验。
74
+ */
75
+ export const coerceStringContentToTextPartsForDashScopeQwenImage = (model, messages) => {
76
+ if (!isDashScopeImageChatModelRequiringListContent(model)) {
77
+ return messages;
78
+ }
79
+ return messages.map((m) => {
80
+ if (typeof m.content === "string") {
81
+ return { ...m, content: [{ type: "text", text: m.content }] };
82
+ }
83
+ return m;
84
+ });
85
+ };
86
+ const normalizeMessagesForDashScopeOpenAi = (model, messages) => {
87
+ const merged = mergeSystemIntoLastUserForDashScope(messages);
88
+ return coerceStringContentToTextPartsForDashScopeQwenImage(model, merged);
89
+ };
90
+ /**
91
+ * 百炼 OpenAI 兼容接口对部分模型不接受独立 `system` 行,且要求 `messages[0].role` 为 `user`、
92
+ * 多模态时 `content` 为合法 list,典型 400:`Input should be 'user': input.messages.0.role`。
93
+ *
94
+ * 将 system 前缀合并进**最后一条 user**(引擎里即本轮当前输入;含多模态块时并入其文本前缀),
95
+ * 与 Intent / direct-answer / tool-use 组装顺序一致:`[system, ...history, lastUser]`。
96
+ *
97
+ * @see https://help.aliyun.com/zh/dashscope/developer-reference/compatibility-of-openai-with-dashscope
98
+ */
99
+ export const mergeSystemIntoLastUserForDashScope = (messages) => {
100
+ const systemTexts = [];
101
+ const rest = [];
102
+ for (const m of messages) {
103
+ if (m.role === "system") {
104
+ if (typeof m.content === "string") {
105
+ systemTexts.push(m.content);
106
+ }
107
+ else {
108
+ systemTexts.push(m.content.map((p) => (p.type === "text" ? p.text : "")).join("\n"));
109
+ }
110
+ }
111
+ else {
112
+ rest.push(m);
113
+ }
114
+ }
115
+ if (systemTexts.length === 0) {
116
+ return messages;
117
+ }
118
+ const prefix = systemTexts.join("\n\n").trim();
119
+ if (prefix.length === 0) {
120
+ return rest;
121
+ }
122
+ const prefixBlock = `${prefix}\n\n`;
123
+ let idx = -1;
124
+ for (let i = rest.length - 1; i >= 0; i--) {
125
+ if (rest[i].role === "user") {
126
+ idx = i;
127
+ break;
128
+ }
129
+ }
130
+ if (idx === -1) {
131
+ return [{ role: "user", content: prefixBlock.trim() }, ...rest];
132
+ }
133
+ const target = rest[idx];
134
+ const mergedContent = prependTextToUserContent(target.content, prefixBlock);
135
+ const out = [...rest];
136
+ out[idx] = { ...target, content: mergedContent };
137
+ return out;
138
+ };
139
+ const prependTextToUserContent = (content, prefix) => {
140
+ if (typeof content === "string") {
141
+ return prefix + content;
142
+ }
143
+ const parts = [...content];
144
+ const tIdx = parts.findIndex((p) => p.type === "text");
145
+ if (tIdx >= 0) {
146
+ const t = parts[tIdx];
147
+ const next = [...parts];
148
+ next[tIdx] = { type: "text", text: prefix + t.text };
149
+ return next;
150
+ }
151
+ return [{ type: "text", text: prefix.trimEnd() }, ...parts];
152
+ };
153
+ const inferTextModelCapabilities = (modelName) => {
154
+ const lowered = modelName.toLowerCase();
155
+ const vision = lowered.includes("vl") || lowered.includes("vision");
156
+ const longContext = lowered.includes("max") || lowered.includes("72b") || lowered.includes("32k") || lowered.includes("128k");
157
+ return {
158
+ supportedModalities: vision ? ["text", "image"] : ["text"],
159
+ maxContextTokens: longContext ? 128_000 : 32_000,
160
+ supportsStreaming: true,
161
+ supportsFunctionCalling: true,
162
+ };
163
+ };
164
+ const wanxCapabilities = () => ({
165
+ supportedModalities: ["text", "image"],
166
+ maxContextTokens: 2_000,
167
+ supportsStreaming: false,
168
+ supportsFunctionCalling: false,
169
+ });
170
+ const FALLBACK_MODELS = [
171
+ { modelName: "qwen-turbo", capabilities: inferTextModelCapabilities("qwen-turbo") },
172
+ { modelName: "qwen-plus", capabilities: inferTextModelCapabilities("qwen-plus") },
173
+ { modelName: "qwen-max", capabilities: inferTextModelCapabilities("qwen-max") },
174
+ { modelName: "qwen-vl-plus", capabilities: inferTextModelCapabilities("qwen-vl-plus") },
175
+ { modelName: "qwen-vl-max", capabilities: inferTextModelCapabilities("qwen-vl-max") },
176
+ { modelName: "wanx-v1", capabilities: wanxCapabilities() },
177
+ ];
178
+ const extractUserPrompt = (messages) => {
179
+ const lastUser = [...messages].reverse().find((m) => m.role === "user");
180
+ if (!lastUser) {
181
+ return { prompt: "" };
182
+ }
183
+ if (typeof lastUser.content === "string") {
184
+ return { prompt: lastUser.content.trim() };
185
+ }
186
+ const text = lastUser.content
187
+ .filter((p) => p.type === "text")
188
+ .map((p) => p.text)
189
+ .join("\n")
190
+ .trim();
191
+ const imagePart = lastUser.content.find((p) => p.type === "image_url");
192
+ if (imagePart?.type === "image_url") {
193
+ return { prompt: text, refImageFromMessage: imagePart.image_url.url };
194
+ }
195
+ return { prompt: text };
196
+ };
197
+ const mapFetchError = (status, body) => {
198
+ if (status === 401) {
199
+ return new ProviderError("PROVIDER_AUTH_FAILED", body || "DashScope 鉴权失败", {
200
+ retryable: false,
201
+ });
202
+ }
203
+ if (status === 429) {
204
+ return new ProviderError("PROVIDER_RATE_LIMITED", body || "DashScope 限流", {
205
+ retryable: true,
206
+ });
207
+ }
208
+ if (status >= 500) {
209
+ return new ProviderError("PROVIDER_UPSTREAM_ERROR", body || "DashScope 服务端错误", {
210
+ retryable: true,
211
+ });
212
+ }
213
+ return new ProviderError("PROVIDER_CALL_FAILED", body || `DashScope HTTP ${status}`, {
214
+ retryable: true,
215
+ });
216
+ };
217
+ const parseDashScopeEnvelope = (raw) => {
218
+ try {
219
+ return JSON.parse(raw);
220
+ }
221
+ catch {
222
+ return {};
223
+ }
224
+ };
225
+ const parseDashScopeMultimodalEnvelope = (raw) => {
226
+ try {
227
+ return JSON.parse(raw);
228
+ }
229
+ catch {
230
+ return {};
231
+ }
232
+ };
233
+ const inferImageMimeTypeFromUrl = (url) => {
234
+ if (url.startsWith("data:")) {
235
+ const semi = url.indexOf(";");
236
+ if (semi > "data:".length) {
237
+ return url.slice("data:".length, semi);
238
+ }
239
+ return undefined;
240
+ }
241
+ const clean = url.split(/[?#]/)[0] ?? "";
242
+ const dot = clean.lastIndexOf(".");
243
+ if (dot < 0)
244
+ return undefined;
245
+ const ext = clean.slice(dot + 1).toLowerCase();
246
+ switch (ext) {
247
+ case "png":
248
+ return "image/png";
249
+ case "jpg":
250
+ case "jpeg":
251
+ return "image/jpeg";
252
+ case "webp":
253
+ return "image/webp";
254
+ case "gif":
255
+ return "image/gif";
256
+ case "bmp":
257
+ return "image/bmp";
258
+ default:
259
+ return undefined;
260
+ }
261
+ };
262
+ const collectRefImageUrls = (img, refFromMessage) => {
263
+ const seen = new Set();
264
+ const out = [];
265
+ const push = (u) => {
266
+ if (!u)
267
+ return;
268
+ if (seen.has(u))
269
+ return;
270
+ seen.add(u);
271
+ out.push(u);
272
+ };
273
+ if (Array.isArray(img.refImages)) {
274
+ for (const u of img.refImages) {
275
+ push(u);
276
+ }
277
+ }
278
+ push(img.refImage);
279
+ push(refFromMessage);
280
+ return out;
281
+ };
282
+ const buildMultimodalImageParameters = (img) => {
283
+ const params = {};
284
+ if (img.size)
285
+ params.size = img.size;
286
+ if (typeof img.n === "number")
287
+ params.n = img.n;
288
+ if (typeof img.seed === "number")
289
+ params.seed = img.seed;
290
+ if (typeof img.watermark === "boolean")
291
+ params.watermark = img.watermark;
292
+ if (typeof img.thinkingMode === "boolean")
293
+ params.thinking_mode = img.thinkingMode;
294
+ if (typeof img.promptExtend === "boolean")
295
+ params.prompt_extend = img.promptExtend;
296
+ if (typeof img.enableSequential === "boolean") {
297
+ params.enable_sequential = img.enableSequential;
298
+ }
299
+ if (img.negativePrompt)
300
+ params.negative_prompt = img.negativePrompt;
301
+ if (Array.isArray(img.bboxList) && img.bboxList.length > 0) {
302
+ params.bbox_list = img.bboxList;
303
+ }
304
+ if (Array.isArray(img.colorPalette) && img.colorPalette.length > 0) {
305
+ params.color_palette = img.colorPalette;
306
+ }
307
+ if (img.outputFormat)
308
+ params.output_format = img.outputFormat;
309
+ if (img.style)
310
+ params.style = img.style;
311
+ return params;
312
+ };
313
+ /**
314
+ * 阿里云百炼 Qwen / 万相(DashScope)Provider。
315
+ *
316
+ * - 文本与多模态对话:OpenAI 兼容接口 `compatible-mode/v1`(与官方示例一致)。
317
+ * - 文生图:`wanx-*` 模型走异步 `text2image/image-synthesis` + `tasks` 轮询(HTTP 仅支持异步)。
318
+ *
319
+ * @see https://help.aliyun.com/zh/dashscope/developer-reference/api-details-9
320
+ */
321
+ export class QwenProviderAdapter {
322
+ id = "qwen";
323
+ name = "Qwen (DashScope)";
324
+ inner;
325
+ apiKey;
326
+ dashScopeOrigin;
327
+ workspaceId;
328
+ timeoutMs;
329
+ imageTaskPollIntervalMs;
330
+ constructor(options = {}) {
331
+ const apiKey = options.apiKey ?? process.env.DASHSCOPE_API_KEY;
332
+ if (!apiKey) {
333
+ throw new ProviderError("PROVIDER_MISSING_CREDENTIALS", "缺少 DASHSCOPE_API_KEY 或 options.apiKey");
334
+ }
335
+ this.apiKey = apiKey;
336
+ this.dashScopeOrigin = (options.dashScopeOrigin ?? DEFAULT_DASHSCOPE_ORIGIN).replace(/\/$/, "");
337
+ this.workspaceId = options.workspaceId;
338
+ this.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
339
+ this.imageTaskPollIntervalMs = options.imageTaskPollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
340
+ const compatibleBaseUrl = options.compatibleBaseUrl ?? `${this.dashScopeOrigin}/compatible-mode/v1`;
341
+ const mergedExtra = mergeOpenAiExtra(options.extra, this.workspaceId);
342
+ this.inner = new OpenAIProviderAdapter({
343
+ apiKey,
344
+ baseURL: compatibleBaseUrl,
345
+ timeoutMs: this.timeoutMs,
346
+ ...(options.modelListCacheTtlMs !== undefined
347
+ ? { modelListCacheTtlMs: options.modelListCacheTtlMs }
348
+ : {}),
349
+ ...(mergedExtra ? { extra: mergedExtra } : {}),
350
+ });
351
+ }
352
+ async listAvailableModels() {
353
+ try {
354
+ const raw = await this.inner.listAvailableModels();
355
+ return raw.map((m) => isWanxImageModel(m.modelName)
356
+ ? { modelName: m.modelName, capabilities: wanxCapabilities() }
357
+ : { modelName: m.modelName, capabilities: inferTextModelCapabilities(m.modelName) });
358
+ }
359
+ catch {
360
+ return FALLBACK_MODELS;
361
+ }
362
+ }
363
+ async chat(request, signal) {
364
+ if (isWanxImageModel(request.model)) {
365
+ return this.chatImageSynthesis(request, signal);
366
+ }
367
+ if (isDashScopeMultimodalImageGenerationModel(request.model)) {
368
+ return this.chatMultimodalGeneration(request, signal);
369
+ }
370
+ const normalized = {
371
+ ...request,
372
+ messages: normalizeMessagesForDashScopeOpenAi(request.model, request.messages),
373
+ };
374
+ return this.inner.chat(normalized, signal);
375
+ }
376
+ async *chatStream(request, signal) {
377
+ if (isWanxImageModel(request.model) ||
378
+ isDashScopeMultimodalImageGenerationModel(request.model)) {
379
+ const res = isWanxImageModel(request.model)
380
+ ? await this.chatImageSynthesis(request, signal)
381
+ : await this.chatMultimodalGeneration(request, signal);
382
+ for (const char of res.content) {
383
+ if (signal?.aborted) {
384
+ throw new ProviderError("PROVIDER_CALL_FAILED", "请求已取消", { cause: signal.reason });
385
+ }
386
+ yield { type: "text-delta", delta: char };
387
+ }
388
+ yield {
389
+ type: "finish",
390
+ finishReason: "stop",
391
+ usage: res.usage,
392
+ };
393
+ return;
394
+ }
395
+ const normalized = {
396
+ ...request,
397
+ messages: normalizeMessagesForDashScopeOpenAi(request.model, request.messages),
398
+ };
399
+ yield* this.inner.chatStream(normalized, signal);
400
+ }
401
+ async countTokens(messages, model) {
402
+ if (isWanxImageModel(model) || isDashScopeMultimodalImageGenerationModel(model)) {
403
+ const { prompt } = extractUserPrompt(messages);
404
+ return Math.ceil(prompt.length / 4);
405
+ }
406
+ return this.inner.countTokens(normalizeMessagesForDashScopeOpenAi(model, messages), model);
407
+ }
408
+ async dispose() {
409
+ await this.inner.dispose?.();
410
+ }
411
+ async chatImageSynthesis(request, signal) {
412
+ const { prompt, refImageFromMessage } = extractUserPrompt(request.messages);
413
+ if (!prompt) {
414
+ throw new ProviderError("PROVIDER_INVALID_INPUT", "万相文生图需要最后一条 user 消息包含非空文本提示词");
415
+ }
416
+ const img = request.qwenImage ?? {};
417
+ const ref_img = img.refImage ?? refImageFromMessage;
418
+ const timeout = withAbortTimeout(signal, this.timeoutMs);
419
+ try {
420
+ const taskId = await this.createImageTask(request.model, prompt, img, ref_img, timeout.signal);
421
+ const out = await this.pollTaskUntilDone(taskId, timeout.signal);
422
+ const lines = [];
423
+ const images = [];
424
+ const results = out.output?.results ?? [];
425
+ let idx = 0;
426
+ for (const r of results) {
427
+ if (typeof r.url === "string" && r.url.length > 0) {
428
+ lines.push(`![generated-${idx + 1}](${r.url})\n\n${r.url}`);
429
+ images.push({
430
+ url: r.url,
431
+ index: idx,
432
+ mimeType: inferImageMimeTypeFromUrl(r.url),
433
+ ...(img.size ? { size: img.size } : {}),
434
+ providerMetadata: {
435
+ provider: this.id,
436
+ model: request.model,
437
+ taskId: out.output?.task_id ?? taskId,
438
+ ...(out.request_id ? { requestId: out.request_id } : {}),
439
+ },
440
+ });
441
+ idx += 1;
442
+ }
443
+ else if (r.message || r.code) {
444
+ lines.push(`[图片 ${idx + 1} 失败] ${r.code ?? ""} ${r.message ?? ""}`.trim());
445
+ idx += 1;
446
+ }
447
+ }
448
+ const content = lines.length > 0
449
+ ? lines.join("\n\n")
450
+ : `任务已完成,但未解析到图片 URL(task_id=${out.output?.task_id ?? taskId})`;
451
+ const imageCount = out.usage?.image_count ?? images.length;
452
+ return {
453
+ content,
454
+ finishReason: "stop",
455
+ usage: {
456
+ promptTokens: Math.ceil(prompt.length / 4),
457
+ completionTokens: imageCount,
458
+ totalTokens: Math.ceil(prompt.length / 4) + imageCount,
459
+ },
460
+ ...(images.length > 0 ? { images } : {}),
461
+ };
462
+ }
463
+ finally {
464
+ timeout.cleanup();
465
+ }
466
+ }
467
+ /**
468
+ * 同步调用 DashScope `POST /api/v1/services/aigc/multimodal-generation/generation`
469
+ * 为 `wan2.x-image*` 系列提供文生图能力。
470
+ *
471
+ * 请求结构:
472
+ * ```json
473
+ * {
474
+ * "model": "wan2.7-image",
475
+ * "input": {
476
+ * "messages": [
477
+ * {"role": "user", "content": [{"text": "..."}, {"image": "https://..."}]}
478
+ * ]
479
+ * },
480
+ * "parameters": {"size": "2048*2048", "n": 1, "watermark": false}
481
+ * }
482
+ * ```
483
+ *
484
+ * 响应结构:
485
+ * ```json
486
+ * {"output": {"choices": [{"message": {"content": [{"image": "https://..."}]}}]}}
487
+ * ```
488
+ *
489
+ * @see https://help.aliyun.com/zh/model-studio/text-to-image-api-reference
490
+ */
491
+ async chatMultimodalGeneration(request, signal) {
492
+ const { prompt, refImageFromMessage } = extractUserPrompt(request.messages);
493
+ if (!prompt) {
494
+ throw new ProviderError("PROVIDER_INVALID_INPUT", `${request.model} 需要最后一条 user 消息包含非空文本提示词`);
495
+ }
496
+ const img = request.qwenImage ?? {};
497
+ const refImageUrls = collectRefImageUrls(img, refImageFromMessage);
498
+ const contentParts = [{ text: prompt }];
499
+ for (const url of refImageUrls) {
500
+ contentParts.push({ image: url });
501
+ }
502
+ const body = {
503
+ model: request.model,
504
+ input: {
505
+ messages: [{ role: "user", content: contentParts }],
506
+ },
507
+ parameters: buildMultimodalImageParameters(img),
508
+ };
509
+ const url = `${this.dashScopeOrigin}/api/v1/services/aigc/multimodal-generation/generation`;
510
+ const timeout = withAbortTimeout(signal, this.timeoutMs);
511
+ try {
512
+ const res = await fetch(url, {
513
+ method: "POST",
514
+ headers: this.buildHeaders(false),
515
+ body: JSON.stringify(body),
516
+ signal: timeout.signal,
517
+ });
518
+ const text = await res.text();
519
+ if (!res.ok) {
520
+ throw mapFetchError(res.status, text);
521
+ }
522
+ const env = parseDashScopeMultimodalEnvelope(text);
523
+ if (env.code != null && String(env.code).length > 0) {
524
+ throw new ProviderError("PROVIDER_UPSTREAM_ERROR", env.message ?? String(env.code), { retryable: false });
525
+ }
526
+ const choices = env.output?.choices ?? [];
527
+ const lines = [];
528
+ const images = [];
529
+ let idx = 0;
530
+ for (const choice of choices) {
531
+ const parts = choice.message?.content ?? [];
532
+ for (const p of parts) {
533
+ if (typeof p.image === "string" && p.image.length > 0) {
534
+ lines.push(`![generated-${idx + 1}](${p.image})\n\n${p.image}`);
535
+ images.push({
536
+ url: p.image,
537
+ index: idx,
538
+ mimeType: inferImageMimeTypeFromUrl(p.image),
539
+ ...(img.size ? { size: img.size } : {}),
540
+ providerMetadata: {
541
+ provider: this.id,
542
+ model: request.model,
543
+ endpoint: "multimodal-generation",
544
+ ...(env.request_id ? { requestId: env.request_id } : {}),
545
+ ...(choice.finish_reason ? { finishReason: choice.finish_reason } : {}),
546
+ },
547
+ });
548
+ idx += 1;
549
+ }
550
+ else if (typeof p.text === "string" && p.text.length > 0) {
551
+ lines.push(p.text);
552
+ }
553
+ }
554
+ }
555
+ const content = lines.length > 0
556
+ ? lines.join("\n\n")
557
+ : `${request.model} 已返回,但未解析到图片结果(request_id=${env.request_id ?? "n/a"})`;
558
+ const promptTokens = env.usage?.input_tokens ?? Math.ceil(prompt.length / 4);
559
+ const completionTokens = env.usage?.output_tokens ?? env.usage?.image_count ?? images.length;
560
+ return {
561
+ content,
562
+ finishReason: "stop",
563
+ usage: {
564
+ promptTokens,
565
+ completionTokens,
566
+ totalTokens: env.usage?.total_tokens ?? promptTokens + completionTokens,
567
+ },
568
+ ...(images.length > 0 ? { images } : {}),
569
+ };
570
+ }
571
+ finally {
572
+ timeout.cleanup();
573
+ }
574
+ }
575
+ buildHeaders(asyncEnable) {
576
+ const h = {
577
+ Authorization: `Bearer ${this.apiKey}`,
578
+ "Content-Type": "application/json",
579
+ };
580
+ if (asyncEnable) {
581
+ h["X-DashScope-Async"] = "enable";
582
+ }
583
+ if (this.workspaceId) {
584
+ h["X-DashScope-WorkSpace"] = this.workspaceId;
585
+ }
586
+ return h;
587
+ }
588
+ async createImageTask(model, prompt, img, ref_img, signal) {
589
+ const input = { prompt };
590
+ if (img.negativePrompt) {
591
+ input.negative_prompt = img.negativePrompt;
592
+ }
593
+ if (ref_img) {
594
+ input.ref_img = ref_img;
595
+ }
596
+ const parameters = {
597
+ style: img.style ?? "<auto>",
598
+ size: img.size ?? "1024*1024",
599
+ n: img.n ?? 1,
600
+ };
601
+ if (img.seed !== undefined) {
602
+ parameters.seed = img.seed;
603
+ }
604
+ if (ref_img) {
605
+ if (img.refStrength !== undefined) {
606
+ parameters.ref_strength = img.refStrength;
607
+ }
608
+ if (img.refMode) {
609
+ parameters.ref_mode = img.refMode;
610
+ }
611
+ }
612
+ const url = `${this.dashScopeOrigin}/api/v1/services/aigc/text2image/image-synthesis`;
613
+ const res = await fetch(url, {
614
+ method: "POST",
615
+ headers: this.buildHeaders(true),
616
+ body: JSON.stringify({ model, input, parameters }),
617
+ signal,
618
+ });
619
+ const text = await res.text();
620
+ if (!res.ok) {
621
+ throw mapFetchError(res.status, text);
622
+ }
623
+ const env = parseDashScopeEnvelope(text);
624
+ if (env.code != null && String(env.code).length > 0) {
625
+ throw new ProviderError("PROVIDER_UPSTREAM_ERROR", env.message ?? String(env.code), {
626
+ retryable: false,
627
+ });
628
+ }
629
+ const taskId = env.output?.task_id;
630
+ if (!taskId) {
631
+ throw new ProviderError("PROVIDER_CALL_FAILED", "文生图创建任务未返回 task_id", { retryable: true });
632
+ }
633
+ return taskId;
634
+ }
635
+ async pollTaskUntilDone(taskId, signal) {
636
+ const url = `${this.dashScopeOrigin}/api/v1/tasks/${encodeURIComponent(taskId)}`;
637
+ const start = Date.now();
638
+ while (true) {
639
+ if (signal.aborted) {
640
+ throw new ProviderError("PROVIDER_CALL_FAILED", "请求已取消", { cause: signal.reason });
641
+ }
642
+ const res = await fetch(url, {
643
+ method: "GET",
644
+ headers: this.buildHeaders(false),
645
+ signal,
646
+ });
647
+ const text = await res.text();
648
+ if (!res.ok) {
649
+ throw mapFetchError(res.status, text);
650
+ }
651
+ const env = parseDashScopeEnvelope(text);
652
+ const status = env.output?.task_status;
653
+ if (status === "SUCCEEDED") {
654
+ return env;
655
+ }
656
+ if (status === "FAILED" || status === "CANCELED" || status === "UNKNOWN") {
657
+ const msg = env.output?.message ?? env.message ?? env.output?.code ?? status;
658
+ throw new ProviderError("PROVIDER_UPSTREAM_ERROR", `文生图任务失败: ${msg}`, {
659
+ retryable: false,
660
+ });
661
+ }
662
+ if (Date.now() - start > this.timeoutMs) {
663
+ throw new TimeoutError("TIMEOUT_PROVIDER_REQUEST", "文生图任务等待超时", { retryable: true });
664
+ }
665
+ await new Promise((r) => setTimeout(r, this.imageTaskPollIntervalMs));
666
+ }
667
+ }
668
+ }
669
+ //# sourceMappingURL=qwen.js.map