@baishuyun/coze-provider 0.0.16 → 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.
@@ -10,14 +10,20 @@ import {
10
10
  SharedV2Headers,
11
11
  SharedV2ProviderMetadata,
12
12
  } from '@ai-sdk/provider';
13
- import { CozeChatConfig } from './types';
13
+ import { CozeChatConfig, IGenerateResult } from './types';
14
14
  import { ServerSentEventTransformStream } from 'parse-sse';
15
15
  import { TextDecoderStream } from 'stream/web';
16
16
  import { convertPromptToMessages } from './utils/prompt-to-model-msg';
17
+ import { accumulateSSEResponse } from './utils/accumulate-sse-response';
17
18
  import { createErrorStream } from './utils/create-error-stream';
18
19
  import { LLMChunkInjectorTransformStream } from './stream-trasnformer/llm-chunk-injector-transform-stream';
19
20
  import MCPChunkFilterTransformStream from './stream-trasnformer/mcp-chunk-filter-transform-stream';
20
21
  import LLMPartTrasnformStream from './stream-trasnformer/llm-part-transform-stream';
22
+ import { createCozeNonStreamingChat } from './utils/create-coze-non-streaming-chat';
23
+ import {
24
+ BuildGenerateResultByChatRes,
25
+ BuildWarningGenerateResult,
26
+ } from './utils/build-generate-result-by-chat-res';
21
27
 
22
28
  export class CozeLanguageModel implements LanguageModelV2 {
23
29
  specificationVersion: 'v2' = 'v2';
@@ -34,19 +40,74 @@ export class CozeLanguageModel implements LanguageModelV2 {
34
40
  this.config = config;
35
41
  }
36
42
 
37
- doGenerate(options: LanguageModelV2CallOptions): PromiseLike<{
38
- content: Array<LanguageModelV2Content>;
39
- finishReason: LanguageModelV2FinishReason;
40
- usage: LanguageModelV2Usage;
41
- providerMetadata?: SharedV2ProviderMetadata;
42
- request?: { body?: unknown };
43
- response?: LanguageModelV2ResponseMetadata & {
44
- headers?: SharedV2Headers;
45
- body?: unknown;
43
+ async doGenerate(options: LanguageModelV2CallOptions): Promise<IGenerateResult> {
44
+ const messages = convertPromptToMessages(options.prompt);
45
+ const warnings: LanguageModelV2CallWarning[] = [];
46
+
47
+ const url = this.config.url({ path: '/chat' });
48
+
49
+ const user_id = options.headers ? options.headers['x-user-id'] : '';
50
+ const user_var = options.headers ? options.headers['x-user-var'] : '';
51
+
52
+ let customVar;
53
+ if (user_var) {
54
+ try {
55
+ customVar = JSON.parse(user_var);
56
+ } catch (e) {
57
+ console.warn('x-user-var parse error', e);
58
+ }
59
+ }
60
+
61
+ const body = {
62
+ bot_id: this.config.botId,
63
+ auto_save_history: false,
64
+ user_id: user_id || '7543191937730215936',
65
+ stream: true,
66
+ additional_messages: messages,
67
+ };
68
+
69
+ if (customVar) {
70
+ (body as any).custom_variables = customVar;
71
+ }
72
+
73
+ const headers = this.config.headers();
74
+ const abortSignal = options.abortSignal;
75
+
76
+ const response = await fetch(url, {
77
+ method: 'POST',
78
+ headers: headers,
79
+ credentials: 'include',
80
+ body: JSON.stringify(body),
81
+ signal: abortSignal,
82
+ });
83
+
84
+ if (!response.ok || !response.body) {
85
+ const rawError = await response.text();
86
+ let errorDetail;
87
+ try {
88
+ errorDetail = JSON.parse(rawError);
89
+ } catch {
90
+ errorDetail = rawError;
91
+ }
92
+
93
+ const errorMessage =
94
+ typeof errorDetail === 'object' && errorDetail?.msg
95
+ ? errorDetail.msg
96
+ : `Coze API error: ${response.status} ${response.statusText}`;
97
+
98
+ return BuildWarningGenerateResult(errorMessage);
99
+ }
100
+
101
+ const { content, finishReason, usage } = await accumulateSSEResponse(response.body);
102
+
103
+ return {
104
+ content,
105
+ finishReason,
106
+ usage,
107
+ request: { body },
108
+ response: { body: null },
109
+ warnings,
46
110
  };
47
- warnings: Array<LanguageModelV2CallWarning>;
48
- }> {
49
- throw new Error('Method not implemented.');
50
111
  }
51
112
 
52
113
  isVisibleMsgPart(evtData: any) {
@@ -69,7 +130,7 @@ export class CozeLanguageModel implements LanguageModelV2 {
69
130
  path: '/chat',
70
131
  });
71
132
 
72
- const user_id = (options.headers ? options.headers['x-user-id'] : '') || Date.now().toString();
133
+ const user_id = options.headers ? options.headers['x-user-id'] : '';
73
134
  const user_var = options.headers ? options.headers['x-user-var'] : '';
74
135
  // const user_token = options.headers ? options.headers['x-user-token'] : '';
75
136
 
@@ -85,20 +146,17 @@ export class CozeLanguageModel implements LanguageModelV2 {
85
146
  const body = {
86
147
  bot_id: this.config.botId,
87
148
  auto_save_history: false,
88
- user_id: '7543191937730215936', // user_id,
149
+ user_id: user_id || '7543191937730215936',
89
150
  stream: true,
90
151
  additional_messages: messages,
91
152
  };
92
153
 
93
- console.log('Request body for Coze API:', JSON.stringify(body));
94
-
95
154
  if (customVar) {
96
- (body as any).custom_var = customVar;
155
+ (body as any).custom_variables = customVar;
97
156
  }
98
157
 
99
158
  const headers = this.config.headers();
100
159
 
101
- // Support abort signal for proper cancellation when clients disconnect
102
160
  const abortSignal = options.abortSignal;
103
161
 
104
162
  const response = await fetch(url, {
@@ -1,14 +1,18 @@
1
1
  import { LanguageModelV2StreamPart } from '@ai-sdk/provider';
2
2
  import { JSONParser } from '@streamparser/json';
3
- import { createTextInfoEnqueuer } from '../utils/enqueue-text-info';
3
+ import { createTextInfoEnqueuer, TextInfoEnqueuer } from '../utils/enqueue-text-info';
4
4
  import { rebuildFieldsJsonStructure } from '../utils/fields-json-rebuild';
5
5
 
6
6
  export const createFieldsJsonTransformStream = (enableJsonParser: boolean) => {
7
7
  let parser: JSONParser;
8
+ let enqueueTextDelta: TextInfoEnqueuer;
8
9
  let id: string;
10
+ let isFirstChunk = true;
11
+ // 当 LLM 返回的不是 JSON 而是普通文本时,切换到直通模式
12
+ let passthroughMode = false;
9
13
 
10
14
  const chunkGuard = (chunk: LanguageModelV2StreamPart): boolean => {
11
- if (!enableJsonParser) {
15
+ if (!enableJsonParser || passthroughMode) {
12
16
  return false;
13
17
  }
14
18
 
@@ -32,22 +36,29 @@ export const createFieldsJsonTransformStream = (enableJsonParser: boolean) => {
32
36
  chunk: LanguageModelV2StreamPart,
33
37
  controller: TransformStreamDefaultController<LanguageModelV2StreamPart>
34
38
  ) => {
35
- if (!parser) {
39
+ if (!parser && !passthroughMode) {
36
40
  return;
37
41
  }
38
42
 
39
- // console.log("chunk arrived", chunk);
40
-
41
43
  try {
42
44
  if (chunkGuard(chunk)) {
43
45
  if ('id' in chunk && 'delta' in chunk) {
44
46
  id = chunk.id;
45
- parser.write(chunk.delta);
47
+ let delta = chunk.delta;
48
+ if (isFirstChunk) {
49
+ delta = delta.replace(/^\uFEFF/, '');
50
+ isFirstChunk = false;
51
+ }
52
+ parser.write(delta);
53
+ } else {
54
+ console.warn('Unexpected chunk format for JSON parsing:', chunk);
55
+ controller.enqueue(chunk);
46
56
  }
47
57
  } else {
48
58
  controller.enqueue(chunk);
49
59
  }
50
60
  } catch (e) {
61
+ console.log('write or enqueue error', e);
51
62
  controller.error(e);
52
63
  }
53
64
  },
@@ -56,13 +67,10 @@ export const createFieldsJsonTransformStream = (enableJsonParser: boolean) => {
56
67
  paths: ['$.*'],
57
68
  });
58
69
 
59
- const enqueueTextDelta = enableJsonParser
70
+ enqueueTextDelta = enableJsonParser
60
71
  ? createTextInfoEnqueuer(controller)
61
72
  : (content: string) => {};
62
73
 
63
- // manualChunkForfieldsBeginTips(controller);
64
- // enqueueTextDelta("开始构建字段");
65
-
66
74
  parser.onValue = (parsedInfo: any) => {
67
75
  const value = parsedInfo.value;
68
76
 
@@ -80,21 +88,11 @@ export const createFieldsJsonTransformStream = (enableJsonParser: boolean) => {
80
88
  );
81
89
  };
82
90
 
83
- let errorLogged = false;
84
91
  parser.onError = (err: any) => {
85
- // controller.enqueue({
86
- // type: "error",
87
- // error: "JsonWidgetStream: JSON Parsing Error:" + err.message,
88
- // });
89
- if (!errorLogged) {
90
- console.error('JsonWidgetStream: JSON Parsing Error:', err);
91
- errorLogged = true;
92
- }
92
+ console.error('JsonWidgetStream: JSON Parsing Error:', err);
93
93
 
94
- enqueueTextDelta('error', {
95
- type: 'agent-error',
96
- error: '操作超时,请刷新重试',
97
- });
94
+ // LLM 返回的不是 JSON,切换到直通模式,后续 text-delta 直接透传
95
+ passthroughMode = true;
98
96
  };
99
97
 
100
98
  parser.onEnd = () => {
package/src/types.ts CHANGED
@@ -1,6 +1,16 @@
1
- import { LanguageModelV2, LanguageModelV2StreamPart } from '@ai-sdk/provider';
1
+ import {
2
+ LanguageModelV2,
3
+ LanguageModelV2CallWarning,
4
+ LanguageModelV2Content,
5
+ LanguageModelV2FinishReason,
6
+ LanguageModelV2ResponseMetadata,
7
+ LanguageModelV2StreamPart,
8
+ LanguageModelV2Usage,
9
+ SharedV2Headers,
10
+ SharedV2ProviderMetadata,
11
+ } from '@ai-sdk/provider';
2
12
  import { FetchFunction } from '@ai-sdk/provider-utils';
3
- import { ChatEventType } from '@coze/api';
13
+ import { ChatEventType, CreateChatData } from '@coze/api';
4
14
 
5
15
  export type CozeChatModelId = 'chat';
6
16
 
@@ -24,3 +34,16 @@ export interface McpSSE {
24
34
  type: string;
25
35
  data: string;
26
36
  }
37
+
38
+ export interface IGenerateResult {
39
+ content: Array<LanguageModelV2Content>;
40
+ finishReason: LanguageModelV2FinishReason;
41
+ usage: LanguageModelV2Usage;
42
+ providerMetadata?: SharedV2ProviderMetadata;
43
+ request?: { body?: unknown };
44
+ response?: LanguageModelV2ResponseMetadata & {
45
+ headers?: SharedV2Headers;
46
+ body?: unknown;
47
+ };
48
+ warnings: Array<LanguageModelV2CallWarning>;
49
+ }
@@ -0,0 +1,83 @@
1
+ import {
2
+ LanguageModelV2Content,
3
+ LanguageModelV2FinishReason,
4
+ LanguageModelV2Usage,
5
+ } from '@ai-sdk/provider';
6
+ import { ServerSentEventTransformStream } from 'parse-sse';
7
+ import { TextDecoderStream } from 'stream/web';
8
+
9
+ export interface AccumulatedSSEResponse {
10
+ content: LanguageModelV2Content[];
11
+ finishReason: LanguageModelV2FinishReason;
12
+ usage: LanguageModelV2Usage;
13
+ }
14
+
15
+ /**
16
+ * 从字节流中解码 SSE 事件并累积完整响应内容
17
+ */
18
+ export async function accumulateSSEResponse(
19
+ body: ReadableStream<Uint8Array>
20
+ ): Promise<AccumulatedSSEResponse> {
21
+ const sseStream = body
22
+ .pipeThrough(
23
+ new TextDecoderStream() as ReadableWritablePair<
24
+ string | undefined,
25
+ Uint8Array<ArrayBufferLike>
26
+ >
27
+ )
28
+ .pipeThrough(new ServerSentEventTransformStream());
29
+
30
+ let textContent = '';
31
+ let reasoningContent = '';
32
+ let finishReason: LanguageModelV2FinishReason = 'stop';
33
+ let usage: LanguageModelV2Usage = { inputTokens: 0, outputTokens: 0, totalTokens: 0 };
34
+
35
+ const reader = sseStream.getReader();
36
+ try {
37
+ while (true) {
38
+ const { done, value: evt } = await reader.read();
39
+ if (done) break;
40
+ if (!evt) continue;
41
+
42
+ let evtData;
43
+ try {
44
+ evtData = JSON.parse(evt.data);
45
+ } catch {
46
+ continue;
47
+ }
48
+
49
+ const event = evt.type;
50
+
51
+ if (event === 'conversation.message.delta') {
52
+ if (evtData.reasoning_content) {
53
+ reasoningContent += evtData.reasoning_content;
54
+ } else if (evtData.content) {
55
+ textContent += evtData.content;
56
+ }
57
+ } else if (event === 'conversation.chat.completed' || event === 'done') {
58
+ finishReason = 'stop';
59
+ if (evtData.usage) {
60
+ usage = {
61
+ inputTokens: evtData.usage.input_count || 0,
62
+ outputTokens: evtData.usage.output_count || 0,
63
+ totalTokens: evtData.usage.total_count || 0,
64
+ };
65
+ }
66
+ } else if (event === 'error') {
67
+ finishReason = 'error';
68
+ }
69
+ }
70
+ } finally {
71
+ reader.releaseLock();
72
+ }
73
+
74
+ const content: LanguageModelV2Content[] = [];
75
+ if (reasoningContent) {
76
+ content.push({ type: 'reasoning', text: reasoningContent });
77
+ }
78
+ if (textContent) {
79
+ content.push({ type: 'text', text: textContent });
80
+ }
81
+
82
+ return { content, finishReason, usage };
83
+ }
@@ -0,0 +1,71 @@
1
+ import { CreateChatRes } from '@coze/api';
2
+ import { CozeChatConfig, IGenerateResult } from '../types';
3
+ import { getCozeChatMsgDetail } from './get-coze-chat-msg-detail';
4
+
5
+ export const BuildWarningGenerateResult = (message: string): IGenerateResult => {
6
+ return {
7
+ content: [],
8
+ finishReason: 'error',
9
+ request: {
10
+ body: null,
11
+ },
12
+ usage: {
13
+ inputTokens: 0,
14
+ outputTokens: 0,
15
+ totalTokens: 0,
16
+ },
17
+ response: {
18
+ body: null,
19
+ },
20
+ warnings: [
21
+ {
22
+ type: 'other',
23
+ message,
24
+ },
25
+ ],
26
+ };
27
+ };
28
+
29
+ export const BuildGenerateResultByChatRes = async (
30
+ response: Response,
31
+ chatConfig: CozeChatConfig,
32
+ reqBody?: unknown
33
+ ): Promise<IGenerateResult> => {
34
+ if (!response.ok) {
35
+ return Promise.reject(
36
+ BuildWarningGenerateResult(`Coze API error: ${response.status} ${response.statusText}`)
37
+ );
38
+ }
39
+
40
+ const res = (await response.json()) as CreateChatRes;
41
+ if (res.code !== 0) {
42
+ return Promise.reject(BuildWarningGenerateResult(`Coze API error: ${res.code} ${res.msg}`));
43
+ }
44
+
45
+ const msgRes = await getCozeChatMsgDetail(res.data.conversation_id, res.data.id, chatConfig);
46
+ if (!msgRes.ok) {
47
+ return Promise.reject(
48
+ BuildWarningGenerateResult(
49
+ `Failed to get message detail: ${msgRes.status} ${msgRes.statusText}`
50
+ )
51
+ );
52
+ }
53
+ const msgDetail = await msgRes.json();
54
+
55
+ return {
56
+ content: msgDetail.data.message.content,
57
+ finishReason: msgDetail.data.message.status === 'finished' ? 'stop' : 'error',
58
+ request: {
59
+ body: reqBody,
60
+ },
61
+ usage: {
62
+ inputTokens: 0,
63
+ outputTokens: 0,
64
+ totalTokens: 0,
65
+ },
66
+ response: {
67
+ body: msgDetail,
68
+ },
69
+ warnings: [],
70
+ };
71
+ };
@@ -0,0 +1,54 @@
1
+ import { LanguageModelV2CallOptions, LanguageModelV2CallWarning } from '@ai-sdk/provider';
2
+ import { CozeChatConfig } from '../types';
3
+ import { convertPromptToMessages } from './prompt-to-model-msg';
4
+
5
+ export const createCozeNonStreamingChat = async (
6
+ botId: string,
7
+ options: LanguageModelV2CallOptions,
8
+ chatConfig: CozeChatConfig
9
+ ) => {
10
+ const messages = convertPromptToMessages(options.prompt);
11
+
12
+ const url = chatConfig.url({
13
+ path: '/chat',
14
+ });
15
+
16
+ const user_id = (options.headers ? options.headers['x-user-id'] : '') || Date.now().toString();
17
+ const user_var = options.headers ? options.headers['x-user-var'] : '';
18
+
19
+ let customVar;
20
+ if (user_var) {
21
+ try {
22
+ customVar = JSON.parse(user_var);
23
+ } catch (e) {
24
+ console.warn('x-user-var parse error', e);
25
+ }
26
+ }
27
+
28
+ const body = {
29
+ bot_id: botId,
30
+ auto_save_history: true,
31
+ user_id: user_id || '7543191937730215936',
32
+ additional_messages: messages,
33
+ stream: false,
34
+ };
35
+
36
+ if (customVar) {
37
+ (body as any).custom_var = customVar;
38
+ }
39
+
40
+ const headers = chatConfig.headers();
41
+
42
+ // Support abort signal for proper cancellation when clients disconnect
43
+ const abortSignal = options.abortSignal;
44
+
45
+ const response = await fetch(url, {
46
+ method: 'POST',
47
+ headers: headers,
48
+ credentials: 'include',
49
+ body: JSON.stringify(body),
50
+ signal: abortSignal,
51
+ });
52
+
53
+ return response;
54
+ };
@@ -55,6 +55,9 @@ const RebuilderMap: Record<FieldType, (origin: unknown) => unknown> = {
55
55
  dept: rebuildDept,
56
56
  sn: rebuildSn,
57
57
  deptgroup: rebuildDeptGroup,
58
+ flowState: defaultBuilder,
59
+ flowstate: defaultBuilder,
60
+ formula: defaultBuilder,
58
61
  unknown: defaultBuilder,
59
62
  };
60
63
 
@@ -0,0 +1,19 @@
1
+ import { CozeChatConfig } from '../types';
2
+
3
+ export const getCozeChatMsgDetail = async (
4
+ conversationId: string,
5
+ chatId: string,
6
+ chatConfig: CozeChatConfig
7
+ ) => {
8
+ const url = chatConfig.url({
9
+ path: `/chat/message/list?conversation_id=${conversationId}&chat_id=${chatId}`,
10
+ });
11
+
12
+ const response = await fetch(url, {
13
+ method: 'POST',
14
+ headers: chatConfig.headers(),
15
+ credentials: 'include',
16
+ });
17
+
18
+ return response;
19
+ };