@baishuyun/coze-provider 2.0.34 → 2.1.0-alpha.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.
@@ -1,246 +1,246 @@
1
- import {
2
- LanguageModelV2,
3
- LanguageModelV2CallOptions,
4
- LanguageModelV2CallWarning,
5
- LanguageModelV2Prompt,
6
- LanguageModelV2StreamPart,
7
- SharedV2Headers,
8
- } from '@ai-sdk/provider';
9
- import { CozeChatConfig, IGenerateResult } from './types';
10
- import { ServerSentEventTransformStream } from 'parse-sse';
11
- import { TextDecoderStream } from 'stream/web';
12
- import { convertPromptToMessages } from './utils/prompt-to-model-msg';
13
- import { accumulateSSEResponse } from './utils/accumulate-sse-response';
14
- import { createErrorStream } from './utils/create-error-stream';
15
- import { LLMChunkInjectorTransformStream } from './stream-trasnformer/llm-chunk-injector-transform-stream';
16
- import MCPChunkFilterTransformStream from './stream-trasnformer/mcp-chunk-filter-transform-stream';
17
- import LLMPartTrasnformStream from './stream-trasnformer/llm-part-transform-stream';
18
- import {
19
- BuildWarningGenerateResult,
20
- } from './utils/build-generate-result-by-chat-res';
21
- import { getChatCtxForHuman, safeParseJson } from '@baishuyun/ai-feat-utils';
22
- import { IChatCtx } from '@baishuyun/types';
23
-
24
- export class CozeLanguageModel implements LanguageModelV2 {
25
- specificationVersion: 'v2' = 'v2';
26
- provider: string;
27
- modelId: string;
28
- supportedUrls: Record<string, RegExp[]> | PromiseLike<Record<string, RegExp[]>>;
29
-
30
- private readonly config: CozeChatConfig;
31
-
32
- constructor(modelId: string, config: CozeChatConfig) {
33
- this.modelId = modelId;
34
- this.provider = config.provider || 'coze';
35
- this.supportedUrls = config.supportedUrls?.() || {};
36
- this.config = config;
37
- }
38
-
39
- async doGenerate(options: LanguageModelV2CallOptions): Promise<IGenerateResult> {
40
- const messages = convertPromptToMessages(options.prompt);
41
- const warnings: LanguageModelV2CallWarning[] = [];
42
-
43
- const url = this.config.url({ path: '/chat' });
44
-
45
- const user_id = options.headers ? options.headers['x-user-id'] : '';
46
- const user_var = options.headers ? options.headers['x-user-var'] : '';
47
-
48
- let customVar;
49
- if (user_var) {
50
- try {
51
- customVar = JSON.parse(user_var);
52
- } catch (e) {
53
- console.warn('x-user-var parse error', e);
54
- }
55
- }
56
-
57
- const body = {
58
- bot_id: this.config.botId,
59
- auto_save_history: false,
60
- user_id: user_id || '7543191937730215936',
61
- stream: true,
62
- additional_messages: messages,
63
- };
64
-
65
- if (customVar) {
66
- (body as any).custom_variables = customVar;
67
- }
68
-
69
- const headers = this.config.headers();
70
- const abortSignal = options.abortSignal;
71
-
72
- const response = await fetch(url, {
73
- method: 'POST',
74
- headers: headers,
75
- credentials: 'include',
76
- body: JSON.stringify(body),
77
- signal: abortSignal,
78
- });
79
-
80
- if (!response.ok || !response.body) {
81
- const rawError = await response.text();
82
- let errorDetail;
83
- try {
84
- errorDetail = JSON.parse(rawError);
85
- } catch {
86
- errorDetail = rawError;
87
- }
88
-
89
- const errorMessage =
90
- typeof errorDetail === 'object' && errorDetail?.msg
91
- ? errorDetail.msg
92
- : `Coze API error: ${response.status} ${response.statusText}`;
93
-
94
- return BuildWarningGenerateResult(errorMessage);
95
- }
96
-
97
- const { content, finishReason, usage } = await accumulateSSEResponse(response.body);
98
-
99
- return {
100
- content,
101
- finishReason,
102
- usage,
103
- request: { body },
104
- response: { body: null },
105
- warnings,
106
- };
107
- }
108
-
109
- isVisibleMsgPart(evtData: any) {
110
- if (evtData.type === 'knowledge' && evtData.role === 'assistant') {
111
- return false;
112
- }
113
-
114
- return true;
115
- }
116
-
117
- async doStream(options: LanguageModelV2CallOptions): Promise<{
118
- stream: ReadableStream<LanguageModelV2StreamPart>;
119
- request?: { body?: unknown };
120
- response?: { headers?: SharedV2Headers };
121
- }> {
122
- const conversation_id = options.headers ? options.headers['x-conversation-id'] : '';
123
-
124
- // 有 conversation_id 时,服务端会话中已持有完整历史(含中断记录)。
125
- const prompt = conversation_id ? keepOnlyLastUserMessage(options.prompt) : options.prompt;
126
-
127
- const messages = convertPromptToMessages(prompt);
128
- const queryStr = conversation_id ? `?conversation_id=${conversation_id}` : '';
129
-
130
- const url = this.config.url({
131
- path: `/chat${queryStr}`,
132
- });
133
-
134
- const user_id = options.headers ? options.headers['x-user-id'] : '';
135
-
136
- const user_var = options.headers ? options.headers['x-user-var'] : '';
137
- const user_param = (options.headers ? options.headers['x-user-param'] : '') ?? '';
138
-
139
- const params = user_param ? safeParseJson(user_param) : {};
140
-
141
- console.log('params 2 workflow', params);
142
- console.log('conversation id 2 workflow', conversation_id);
143
-
144
- const body = {
145
- bot_id: this.config.botId,
146
- auto_save_history: Boolean(conversation_id),
147
- user_id: user_id || '7543191937730215936',
148
- stream: true,
149
- additional_messages: messages,
150
- parameters: params,
151
- custom_variables: user_var ? safeParseJson(user_var) : {},
152
- extra_params: {
153
- 'remark': getChatCtxForHuman(options?.providerOptions?.chatCtx as unknown as IChatCtx)
154
- },
155
- };
156
-
157
- const headers = this.config.headers();
158
-
159
- const abortSignal = options.abortSignal;
160
-
161
- console.log('url', url);
162
- console.log('body', JSON.stringify(body, null, 2));
163
-
164
- const response = await fetch(url, {
165
- method: 'POST',
166
- headers: headers,
167
- credentials: 'include',
168
- body: JSON.stringify(body),
169
- signal: abortSignal,
170
- });
171
-
172
- if (!response.ok || !response.body) {
173
- // if (true) {
174
- const rawError = await response.text();
175
- let errorDetail;
176
- try {
177
- errorDetail = JSON.parse(rawError); // 尝试解析为 JSON
178
- } catch {
179
- errorDetail = rawError; // 非 JSON 直接使用
180
- }
181
-
182
- // 创建一个包含错误信息的流,让 UI 层处理
183
- const errorMessage =
184
- typeof errorDetail === 'object' && errorDetail?.msg
185
- ? errorDetail.msg
186
- : `Coze API error: ${response.status} ${response.statusText}`;
187
-
188
- console.error(errorMessage);
189
-
190
- return {
191
- stream: createErrorStream('会话异常,请重试'),
192
- };
193
- }
194
-
195
- let externalTransformers = (this.config.extraStreamTransformers || []).map((factory) =>
196
- factory()
197
- );
198
-
199
- /// 不同阶段,可能需要不同的流处理逻辑
200
- const stage = options.headers ? options.headers['x-user-stage'] : '';
201
- const isBuildStage = stage === 'build';
202
- const isFillStage = stage === 'fill';
203
-
204
- const result = response.body
205
- // 将字节流解码为文本流
206
- .pipeThrough(
207
- new TextDecoderStream() as ReadableWritablePair<
208
- string | undefined,
209
- Uint8Array<ArrayBufferLike>
210
- >
211
- )
212
- // 解析为 SSE 事件流
213
- .pipeThrough(new ServerSentEventTransformStream())
214
- .pipeThrough(new MCPChunkFilterTransformStream(isBuildStage || isFillStage))
215
- // 补充特殊 llm chunk
216
- .pipeThrough(new LLMChunkInjectorTransformStream())
217
- // 转换为 LanguageModelV2StreamPart 格式
218
- .pipeThrough(new LLMPartTrasnformStream());
219
-
220
- let finalResult = result;
221
-
222
- if (externalTransformers.length > 0) {
223
- finalResult = externalTransformers.reduce(
224
- (stream, transformer) => stream.pipeThrough(transformer),
225
- result // 初始累积值(原始流)
226
- );
227
- }
228
-
229
- return {
230
- stream: finalResult as ReadableStream<LanguageModelV2StreamPart>,
231
- };
232
- }
233
- }
234
-
235
- /**
236
- * 只保留最后一条 user 消息(含其内容),用于有 conversation_id 的场景:
237
- * 服务端会话中已持有完整历史,只需发送当前用户输入。
238
- */
239
- function keepOnlyLastUserMessage(prompt: LanguageModelV2Prompt): LanguageModelV2Prompt {
240
- for (let i = prompt.length - 1; i >= 0; i--) {
241
- if (prompt[i].role === 'user') {
242
- return [prompt[i]];
243
- }
244
- }
245
- return prompt;
246
- }
1
+ import {
2
+ LanguageModelV2,
3
+ LanguageModelV2CallOptions,
4
+ LanguageModelV2CallWarning,
5
+ LanguageModelV2Prompt,
6
+ LanguageModelV2StreamPart,
7
+ SharedV2Headers,
8
+ } from '@ai-sdk/provider';
9
+ import { CozeChatConfig, IGenerateResult } from './types';
10
+ import { ServerSentEventTransformStream } from 'parse-sse';
11
+ import { TextDecoderStream } from 'stream/web';
12
+ import { convertPromptToMessages } from './utils/prompt-to-model-msg';
13
+ import { accumulateSSEResponse } from './utils/accumulate-sse-response';
14
+ import { createErrorStream } from './utils/create-error-stream';
15
+ import { LLMChunkInjectorTransformStream } from './stream-trasnformer/llm-chunk-injector-transform-stream';
16
+ import MCPChunkFilterTransformStream from './stream-trasnformer/mcp-chunk-filter-transform-stream';
17
+ import LLMPartTrasnformStream from './stream-trasnformer/llm-part-transform-stream';
18
+ import {
19
+ BuildWarningGenerateResult,
20
+ } from './utils/build-generate-result-by-chat-res';
21
+ import { getChatCtxForHuman, safeParseJson } from '@baishuyun/ai-feat-utils';
22
+ import { IChatCtx } from '@baishuyun/types';
23
+
24
+ export class CozeLanguageModel implements LanguageModelV2 {
25
+ specificationVersion: 'v2' = 'v2';
26
+ provider: string;
27
+ modelId: string;
28
+ supportedUrls: Record<string, RegExp[]> | PromiseLike<Record<string, RegExp[]>>;
29
+
30
+ private readonly config: CozeChatConfig;
31
+
32
+ constructor(modelId: string, config: CozeChatConfig) {
33
+ this.modelId = modelId;
34
+ this.provider = config.provider || 'coze';
35
+ this.supportedUrls = config.supportedUrls?.() || {};
36
+ this.config = config;
37
+ }
38
+
39
+ async doGenerate(options: LanguageModelV2CallOptions): Promise<IGenerateResult> {
40
+ const messages = convertPromptToMessages(options.prompt);
41
+ const warnings: LanguageModelV2CallWarning[] = [];
42
+
43
+ const url = this.config.url({ path: '/chat' });
44
+
45
+ const user_id = options.headers ? options.headers['x-user-id'] : '';
46
+ const user_var = options.headers ? options.headers['x-user-var'] : '';
47
+
48
+ let customVar;
49
+ if (user_var) {
50
+ try {
51
+ customVar = JSON.parse(user_var);
52
+ } catch (e) {
53
+ console.warn('x-user-var parse error', e);
54
+ }
55
+ }
56
+
57
+ const body = {
58
+ bot_id: this.config.botId,
59
+ auto_save_history: false,
60
+ user_id: user_id || '7543191937730215936',
61
+ stream: true,
62
+ additional_messages: messages,
63
+ };
64
+
65
+ if (customVar) {
66
+ (body as any).custom_variables = customVar;
67
+ }
68
+
69
+ const headers = this.config.headers();
70
+ const abortSignal = options.abortSignal;
71
+
72
+ const response = await fetch(url, {
73
+ method: 'POST',
74
+ headers: headers,
75
+ credentials: 'include',
76
+ body: JSON.stringify(body),
77
+ signal: abortSignal,
78
+ });
79
+
80
+ if (!response.ok || !response.body) {
81
+ const rawError = await response.text();
82
+ let errorDetail;
83
+ try {
84
+ errorDetail = JSON.parse(rawError);
85
+ } catch {
86
+ errorDetail = rawError;
87
+ }
88
+
89
+ const errorMessage =
90
+ typeof errorDetail === 'object' && errorDetail?.msg
91
+ ? errorDetail.msg
92
+ : `Coze API error: ${response.status} ${response.statusText}`;
93
+
94
+ return BuildWarningGenerateResult(errorMessage);
95
+ }
96
+
97
+ const { content, finishReason, usage } = await accumulateSSEResponse(response.body);
98
+
99
+ return {
100
+ content,
101
+ finishReason,
102
+ usage,
103
+ request: { body },
104
+ response: { body: null },
105
+ warnings,
106
+ };
107
+ }
108
+
109
+ isVisibleMsgPart(evtData: any) {
110
+ if (evtData.type === 'knowledge' && evtData.role === 'assistant') {
111
+ return false;
112
+ }
113
+
114
+ return true;
115
+ }
116
+
117
+ async doStream(options: LanguageModelV2CallOptions): Promise<{
118
+ stream: ReadableStream<LanguageModelV2StreamPart>;
119
+ request?: { body?: unknown };
120
+ response?: { headers?: SharedV2Headers };
121
+ }> {
122
+ const conversation_id = options.headers ? options.headers['x-conversation-id'] : '';
123
+
124
+ // 有 conversation_id 时,服务端会话中已持有完整历史(含中断记录)。
125
+ const prompt = conversation_id ? keepOnlyLastUserMessage(options.prompt) : options.prompt;
126
+
127
+ const messages = convertPromptToMessages(prompt);
128
+ const queryStr = conversation_id ? `?conversation_id=${conversation_id}` : '';
129
+
130
+ const url = this.config.url({
131
+ path: `/chat${queryStr}`,
132
+ });
133
+
134
+ const user_id = options.headers ? options.headers['x-user-id'] : '';
135
+
136
+ const user_var = options.headers ? options.headers['x-user-var'] : '';
137
+ const user_param = (options.headers ? options.headers['x-user-param'] : '') ?? '';
138
+
139
+ const params = user_param ? safeParseJson(user_param) : {};
140
+
141
+ console.log('params 2 workflow', params);
142
+ console.log('conversation id 2 workflow', conversation_id);
143
+
144
+ const body = {
145
+ bot_id: this.config.botId,
146
+ auto_save_history: Boolean(conversation_id),
147
+ user_id: user_id || '7543191937730215936',
148
+ stream: true,
149
+ additional_messages: messages,
150
+ parameters: params,
151
+ custom_variables: user_var ? safeParseJson(user_var) : {},
152
+ extra_params: {
153
+ 'remark': getChatCtxForHuman(options?.providerOptions?.chatCtx as unknown as IChatCtx)
154
+ },
155
+ };
156
+
157
+ const headers = this.config.headers();
158
+
159
+ const abortSignal = options.abortSignal;
160
+
161
+ console.log('url', url);
162
+ console.log('body', JSON.stringify(body, null, 2));
163
+
164
+ const response = await fetch(url, {
165
+ method: 'POST',
166
+ headers: headers,
167
+ credentials: 'include',
168
+ body: JSON.stringify(body),
169
+ signal: abortSignal,
170
+ });
171
+
172
+ if (!response.ok || !response.body) {
173
+ // if (true) {
174
+ const rawError = await response.text();
175
+ let errorDetail;
176
+ try {
177
+ errorDetail = JSON.parse(rawError); // 尝试解析为 JSON
178
+ } catch {
179
+ errorDetail = rawError; // 非 JSON 直接使用
180
+ }
181
+
182
+ // 创建一个包含错误信息的流,让 UI 层处理
183
+ const errorMessage =
184
+ typeof errorDetail === 'object' && errorDetail?.msg
185
+ ? errorDetail.msg
186
+ : `Coze API error: ${response.status} ${response.statusText}`;
187
+
188
+ console.error(errorMessage);
189
+
190
+ return {
191
+ stream: createErrorStream('会话异常,请重试'),
192
+ };
193
+ }
194
+
195
+ let externalTransformers = (this.config.extraStreamTransformers || []).map((factory) =>
196
+ factory()
197
+ );
198
+
199
+ /// 不同阶段,可能需要不同的流处理逻辑
200
+ const stage = options.headers ? options.headers['x-user-stage'] : '';
201
+ const isBuildStage = stage === 'build';
202
+ const isFillStage = stage === 'fill';
203
+
204
+ const result = response.body
205
+ // 将字节流解码为文本流
206
+ .pipeThrough(
207
+ new TextDecoderStream() as ReadableWritablePair<
208
+ string | undefined,
209
+ Uint8Array<ArrayBufferLike>
210
+ >
211
+ )
212
+ // 解析为 SSE 事件流
213
+ .pipeThrough(new ServerSentEventTransformStream())
214
+ .pipeThrough(new MCPChunkFilterTransformStream(isBuildStage || isFillStage))
215
+ // 补充特殊 llm chunk
216
+ .pipeThrough(new LLMChunkInjectorTransformStream())
217
+ // 转换为 LanguageModelV2StreamPart 格式
218
+ .pipeThrough(new LLMPartTrasnformStream());
219
+
220
+ let finalResult = result;
221
+
222
+ if (externalTransformers.length > 0) {
223
+ finalResult = externalTransformers.reduce(
224
+ (stream, transformer) => stream.pipeThrough(transformer),
225
+ result // 初始累积值(原始流)
226
+ );
227
+ }
228
+
229
+ return {
230
+ stream: finalResult as ReadableStream<LanguageModelV2StreamPart>,
231
+ };
232
+ }
233
+ }
234
+
235
+ /**
236
+ * 只保留最后一条 user 消息(含其内容),用于有 conversation_id 的场景:
237
+ * 服务端会话中已持有完整历史,只需发送当前用户输入。
238
+ */
239
+ function keepOnlyLastUserMessage(prompt: LanguageModelV2Prompt): LanguageModelV2Prompt {
240
+ for (let i = prompt.length - 1; i >= 0; i--) {
241
+ if (prompt[i].role === 'user') {
242
+ return [prompt[i]];
243
+ }
244
+ }
245
+ return prompt;
246
+ }