@jsonstudio/llms 0.4.4 → 0.4.5

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 (159) hide show
  1. package/dist/conversion/codec-registry.js +11 -1
  2. package/dist/conversion/codecs/anthropic-openai-codec.d.ts +13 -0
  3. package/dist/conversion/codecs/anthropic-openai-codec.js +18 -473
  4. package/dist/conversion/codecs/gemini-openai-codec.js +91 -48
  5. package/dist/conversion/codecs/responses-openai-codec.js +9 -2
  6. package/dist/conversion/hub/format-adapters/anthropic-format-adapter.js +3 -0
  7. package/dist/conversion/hub/format-adapters/chat-format-adapter.js +3 -0
  8. package/dist/conversion/hub/format-adapters/gemini-format-adapter.js +3 -0
  9. package/dist/conversion/hub/format-adapters/responses-format-adapter.d.ts +19 -0
  10. package/dist/conversion/hub/format-adapters/responses-format-adapter.js +9 -0
  11. package/dist/conversion/hub/node-support.js +3 -1
  12. package/dist/conversion/hub/pipeline/hub-pipeline.js +37 -32
  13. package/dist/conversion/hub/response/provider-response.js +1 -1
  14. package/dist/conversion/hub/response/response-mappers.js +1 -1
  15. package/dist/conversion/hub/response/response-runtime.js +109 -10
  16. package/dist/conversion/hub/semantic-mappers/anthropic-mapper.js +70 -156
  17. package/dist/conversion/hub/semantic-mappers/chat-mapper.js +63 -52
  18. package/dist/conversion/hub/semantic-mappers/gemini-mapper.js +76 -143
  19. package/dist/conversion/hub/semantic-mappers/responses-mapper.js +40 -160
  20. package/dist/conversion/hub/standardized-bridge.js +3 -0
  21. package/dist/conversion/hub/tool-governance/rules.js +2 -2
  22. package/dist/conversion/index.d.ts +5 -0
  23. package/dist/conversion/index.js +5 -0
  24. package/dist/conversion/pipeline/codecs/v2/anthropic-openai-pipeline.d.ts +12 -0
  25. package/dist/conversion/pipeline/codecs/v2/anthropic-openai-pipeline.js +100 -0
  26. package/dist/conversion/pipeline/codecs/v2/openai-openai-pipeline.d.ts +15 -0
  27. package/dist/conversion/pipeline/codecs/v2/openai-openai-pipeline.js +174 -0
  28. package/dist/conversion/pipeline/codecs/v2/responses-openai-pipeline.d.ts +14 -0
  29. package/dist/conversion/pipeline/codecs/v2/responses-openai-pipeline.js +166 -0
  30. package/dist/conversion/pipeline/codecs/v2/shared/openai-chat-helpers.d.ts +13 -0
  31. package/dist/conversion/pipeline/codecs/v2/shared/openai-chat-helpers.js +66 -0
  32. package/dist/conversion/pipeline/hooks/adapter-context.d.ts +7 -0
  33. package/dist/conversion/pipeline/hooks/adapter-context.js +18 -0
  34. package/dist/conversion/pipeline/hooks/protocol-hooks.d.ts +67 -0
  35. package/dist/conversion/pipeline/hooks/protocol-hooks.js +1 -0
  36. package/dist/conversion/pipeline/index.d.ts +35 -0
  37. package/dist/conversion/pipeline/index.js +103 -0
  38. package/dist/conversion/pipeline/meta/meta-bag.d.ts +20 -0
  39. package/dist/conversion/pipeline/meta/meta-bag.js +81 -0
  40. package/dist/conversion/pipeline/schema/canonical-chat.d.ts +18 -0
  41. package/dist/conversion/pipeline/schema/canonical-chat.js +1 -0
  42. package/dist/conversion/pipeline/schema/index.d.ts +1 -0
  43. package/dist/conversion/pipeline/schema/index.js +1 -0
  44. package/dist/conversion/responses/responses-openai-bridge.d.ts +48 -0
  45. package/dist/conversion/responses/responses-openai-bridge.js +157 -1146
  46. package/dist/conversion/shared/anthropic-message-utils.d.ts +12 -0
  47. package/dist/conversion/shared/anthropic-message-utils.js +587 -0
  48. package/dist/conversion/shared/bridge-actions.d.ts +39 -0
  49. package/dist/conversion/shared/bridge-actions.js +709 -0
  50. package/dist/conversion/shared/bridge-conversation-store.d.ts +41 -0
  51. package/dist/conversion/shared/bridge-conversation-store.js +279 -0
  52. package/dist/conversion/shared/bridge-id-utils.d.ts +7 -0
  53. package/dist/conversion/shared/bridge-id-utils.js +42 -0
  54. package/dist/conversion/shared/bridge-instructions.d.ts +1 -0
  55. package/dist/conversion/shared/bridge-instructions.js +113 -0
  56. package/dist/conversion/shared/bridge-message-types.d.ts +39 -0
  57. package/dist/conversion/shared/bridge-message-types.js +1 -0
  58. package/dist/conversion/shared/bridge-message-utils.d.ts +22 -0
  59. package/dist/conversion/shared/bridge-message-utils.js +473 -0
  60. package/dist/conversion/shared/bridge-metadata.d.ts +1 -0
  61. package/dist/conversion/shared/bridge-metadata.js +1 -0
  62. package/dist/conversion/shared/bridge-policies.d.ts +18 -0
  63. package/dist/conversion/shared/bridge-policies.js +276 -0
  64. package/dist/conversion/shared/bridge-request-adapter.d.ts +28 -0
  65. package/dist/conversion/shared/bridge-request-adapter.js +430 -0
  66. package/dist/conversion/shared/chat-output-normalizer.d.ts +4 -0
  67. package/dist/conversion/shared/chat-output-normalizer.js +56 -0
  68. package/dist/conversion/shared/chat-request-filters.js +24 -1
  69. package/dist/conversion/shared/gemini-tool-utils.d.ts +5 -0
  70. package/dist/conversion/shared/gemini-tool-utils.js +130 -0
  71. package/dist/conversion/shared/metadata-passthrough.d.ts +11 -0
  72. package/dist/conversion/shared/metadata-passthrough.js +57 -0
  73. package/dist/conversion/shared/output-content-normalizer.d.ts +12 -0
  74. package/dist/conversion/shared/output-content-normalizer.js +119 -0
  75. package/dist/conversion/shared/reasoning-normalizer.d.ts +21 -0
  76. package/dist/conversion/shared/reasoning-normalizer.js +368 -0
  77. package/dist/conversion/shared/reasoning-tool-normalizer.d.ts +12 -0
  78. package/dist/conversion/shared/reasoning-tool-normalizer.js +132 -0
  79. package/dist/conversion/shared/reasoning-tool-parser.d.ts +10 -0
  80. package/dist/conversion/shared/reasoning-tool-parser.js +95 -0
  81. package/dist/conversion/shared/reasoning-utils.d.ts +2 -0
  82. package/dist/conversion/shared/reasoning-utils.js +42 -0
  83. package/dist/conversion/shared/responses-conversation-store.js +5 -11
  84. package/dist/conversion/shared/responses-message-utils.d.ts +15 -0
  85. package/dist/conversion/shared/responses-message-utils.js +206 -0
  86. package/dist/conversion/shared/responses-output-builder.d.ts +15 -0
  87. package/dist/conversion/shared/responses-output-builder.js +179 -0
  88. package/dist/conversion/shared/responses-output-utils.d.ts +7 -0
  89. package/dist/conversion/shared/responses-output-utils.js +108 -0
  90. package/dist/conversion/shared/responses-request-adapter.d.ts +28 -0
  91. package/dist/conversion/shared/responses-request-adapter.js +9 -40
  92. package/dist/conversion/shared/responses-response-utils.d.ts +3 -0
  93. package/dist/conversion/shared/responses-response-utils.js +209 -0
  94. package/dist/conversion/shared/responses-tool-utils.d.ts +12 -0
  95. package/dist/conversion/shared/responses-tool-utils.js +90 -0
  96. package/dist/conversion/shared/responses-types.d.ts +33 -0
  97. package/dist/conversion/shared/responses-types.js +1 -0
  98. package/dist/conversion/shared/tool-call-utils.d.ts +11 -0
  99. package/dist/conversion/shared/tool-call-utils.js +56 -0
  100. package/dist/conversion/shared/tool-mapping.d.ts +19 -0
  101. package/dist/conversion/shared/tool-mapping.js +124 -0
  102. package/dist/conversion/shared/tool-normalizers.d.ts +4 -0
  103. package/dist/conversion/shared/tool-normalizers.js +84 -0
  104. package/dist/router/virtual-router/bootstrap.js +18 -3
  105. package/dist/router/virtual-router/provider-registry.js +4 -2
  106. package/dist/router/virtual-router/types.d.ts +212 -0
  107. package/dist/sse/index.d.ts +38 -2
  108. package/dist/sse/index.js +27 -0
  109. package/dist/sse/json-to-sse/anthropic-json-to-sse-converter.d.ts +14 -0
  110. package/dist/sse/json-to-sse/anthropic-json-to-sse-converter.js +106 -73
  111. package/dist/sse/json-to-sse/chat-json-to-sse-converter.js +6 -2
  112. package/dist/sse/json-to-sse/gemini-json-to-sse-converter.d.ts +14 -0
  113. package/dist/sse/json-to-sse/gemini-json-to-sse-converter.js +99 -0
  114. package/dist/sse/json-to-sse/index.d.ts +7 -0
  115. package/dist/sse/json-to-sse/index.js +2 -0
  116. package/dist/sse/json-to-sse/sequencers/anthropic-sequencer.d.ts +13 -0
  117. package/dist/sse/json-to-sse/sequencers/anthropic-sequencer.js +150 -0
  118. package/dist/sse/json-to-sse/sequencers/chat-sequencer.d.ts +39 -0
  119. package/dist/sse/json-to-sse/sequencers/chat-sequencer.js +49 -3
  120. package/dist/sse/json-to-sse/sequencers/gemini-sequencer.d.ts +10 -0
  121. package/dist/sse/json-to-sse/sequencers/gemini-sequencer.js +95 -0
  122. package/dist/sse/json-to-sse/sequencers/responses-sequencer.js +31 -5
  123. package/dist/sse/registry/sse-codec-registry.d.ts +32 -0
  124. package/dist/sse/registry/sse-codec-registry.js +30 -1
  125. package/dist/sse/shared/reasoning-dispatcher.d.ts +10 -0
  126. package/dist/sse/shared/reasoning-dispatcher.js +25 -0
  127. package/dist/sse/shared/responses-output-normalizer.d.ts +12 -0
  128. package/dist/sse/shared/responses-output-normalizer.js +45 -0
  129. package/dist/sse/shared/serializers/anthropic-event-serializer.d.ts +2 -0
  130. package/dist/sse/shared/serializers/anthropic-event-serializer.js +9 -0
  131. package/dist/sse/shared/serializers/gemini-event-serializer.d.ts +2 -0
  132. package/dist/sse/shared/serializers/gemini-event-serializer.js +5 -0
  133. package/dist/sse/shared/serializers/index.d.ts +41 -0
  134. package/dist/sse/shared/serializers/index.js +2 -0
  135. package/dist/sse/shared/writer.d.ts +127 -0
  136. package/dist/sse/shared/writer.js +37 -1
  137. package/dist/sse/sse-to-json/anthropic-sse-to-json-converter.d.ts +11 -0
  138. package/dist/sse/sse-to-json/anthropic-sse-to-json-converter.js +92 -127
  139. package/dist/sse/sse-to-json/builders/anthropic-response-builder.d.ts +16 -0
  140. package/dist/sse/sse-to-json/builders/anthropic-response-builder.js +151 -0
  141. package/dist/sse/sse-to-json/builders/response-builder.d.ts +165 -0
  142. package/dist/sse/sse-to-json/builders/response-builder.js +27 -6
  143. package/dist/sse/sse-to-json/chat-sse-to-json-converter.d.ts +114 -0
  144. package/dist/sse/sse-to-json/chat-sse-to-json-converter.js +79 -3
  145. package/dist/sse/sse-to-json/gemini-sse-to-json-converter.d.ts +13 -0
  146. package/dist/sse/sse-to-json/gemini-sse-to-json-converter.js +160 -0
  147. package/dist/sse/sse-to-json/index.d.ts +7 -0
  148. package/dist/sse/sse-to-json/index.js +2 -0
  149. package/dist/sse/sse-to-json/parsers/sse-parser.js +53 -1
  150. package/dist/sse/types/anthropic-types.d.ts +170 -0
  151. package/dist/sse/types/anthropic-types.js +8 -5
  152. package/dist/sse/types/chat-types.d.ts +10 -0
  153. package/dist/sse/types/chat-types.js +2 -1
  154. package/dist/sse/types/core-interfaces.d.ts +1 -1
  155. package/dist/sse/types/gemini-types.d.ts +116 -0
  156. package/dist/sse/types/gemini-types.js +5 -0
  157. package/dist/sse/types/index.d.ts +5 -2
  158. package/dist/sse/types/index.js +2 -0
  159. package/package.json +1 -1
@@ -0,0 +1,10 @@
1
+ import type { ChatReasoningMode } from '../types/chat-types.js';
2
+ export interface ReasoningDispatchResult {
3
+ channel?: string;
4
+ appendToContent?: string;
5
+ }
6
+ export interface ReasoningDispatchOptions {
7
+ mode?: ChatReasoningMode;
8
+ prefix?: string;
9
+ }
10
+ export declare function dispatchReasoning(input: string | undefined, options?: ReasoningDispatchOptions): ReasoningDispatchResult;
@@ -0,0 +1,25 @@
1
+ function formatText(text, prefix) {
2
+ const trimmed = text.trim();
3
+ if (!trimmed)
4
+ return '';
5
+ if (!prefix)
6
+ return trimmed;
7
+ const needsSpace = !prefix.endsWith(' ') && !prefix.endsWith('\n');
8
+ return `${prefix}${needsSpace ? ' ' : ''}${trimmed}`;
9
+ }
10
+ export function dispatchReasoning(input, options) {
11
+ const trimmed = typeof input === 'string' ? input.trim() : '';
12
+ if (!trimmed.length) {
13
+ return {};
14
+ }
15
+ const mode = options?.mode ?? 'channel';
16
+ if (mode === 'drop') {
17
+ return {};
18
+ }
19
+ if (mode === 'text') {
20
+ return {
21
+ appendToContent: formatText(trimmed, options?.prefix)
22
+ };
23
+ }
24
+ return { channel: trimmed };
25
+ }
@@ -0,0 +1,12 @@
1
+ import type { ResponsesMessageItem, ResponsesOutputItem, ResponsesReasoningItem } from '../types/index.js';
2
+ export interface ResponsesMessageNormalizationOptions {
3
+ requestId?: string;
4
+ outputIndex?: number;
5
+ extraReasoning?: string | string[];
6
+ }
7
+ export interface ResponsesMessageNormalizationResult {
8
+ message: ResponsesMessageItem;
9
+ reasoning?: ResponsesReasoningItem;
10
+ }
11
+ export declare function normalizeResponsesMessageItem(item: ResponsesMessageItem, options: ResponsesMessageNormalizationOptions): ResponsesMessageNormalizationResult;
12
+ export declare function expandResponsesMessageItem(item: ResponsesMessageItem, options: ResponsesMessageNormalizationOptions): ResponsesOutputItem[];
@@ -0,0 +1,45 @@
1
+ import { normalizeMessageContentParts } from '../../conversion/shared/output-content-normalizer.js';
2
+ export function normalizeResponsesMessageItem(item, options) {
3
+ const fallbackRequestId = typeof options.requestId === 'string' && options.requestId.trim().length
4
+ ? options.requestId.trim()
5
+ : 'message';
6
+ const fallbackIndex = typeof options.outputIndex === 'number' ? options.outputIndex : 0;
7
+ const baseId = typeof item.id === 'string' && item.id.trim().length
8
+ ? item.id.trim()
9
+ : `${fallbackRequestId}-message-${fallbackIndex}`;
10
+ const { normalizedParts, reasoningChunks } = normalizeMessageContentParts(item.content);
11
+ const additionalReasoning = options.extraReasoning;
12
+ if (additionalReasoning) {
13
+ const extras = Array.isArray(additionalReasoning) ? additionalReasoning : [additionalReasoning];
14
+ for (const entry of extras) {
15
+ if (typeof entry === 'string') {
16
+ const trimmed = entry.trim();
17
+ if (trimmed.length) {
18
+ reasoningChunks.push(trimmed);
19
+ }
20
+ }
21
+ }
22
+ }
23
+ const normalizedContent = normalizedParts.length
24
+ ? normalizedParts
25
+ : [{ type: 'output_text', text: '' }];
26
+ const message = {
27
+ ...item,
28
+ id: baseId,
29
+ content: normalizedContent
30
+ };
31
+ let reasoning;
32
+ if (reasoningChunks.length) {
33
+ reasoning = {
34
+ id: `${baseId}_reasoning`,
35
+ type: 'reasoning',
36
+ summary: [],
37
+ content: reasoningChunks.map((text) => ({ type: 'reasoning_text', text }))
38
+ };
39
+ }
40
+ return { message, reasoning };
41
+ }
42
+ export function expandResponsesMessageItem(item, options) {
43
+ const { message, reasoning } = normalizeResponsesMessageItem(item, options);
44
+ return reasoning ? [message, reasoning] : [message];
45
+ }
@@ -0,0 +1,2 @@
1
+ import { AnthropicSseEvent } from '../../types/index.js';
2
+ export declare function serializeAnthropicEventToSSE(event: AnthropicSseEvent): string;
@@ -0,0 +1,9 @@
1
+ export function serializeAnthropicEventToSSE(event) {
2
+ const payload = event.data ?? {};
3
+ const type = event.event ||
4
+ event.type ||
5
+ (typeof payload?.type === 'string'
6
+ ? payload.type
7
+ : 'message');
8
+ return [`event: ${type}`, `data: ${JSON.stringify(payload)}`].join('\n') + '\n\n';
9
+ }
@@ -0,0 +1,2 @@
1
+ import { GeminiSseEvent } from '../../types/index.js';
2
+ export declare function serializeGeminiEventToSSE(event: GeminiSseEvent): string;
@@ -0,0 +1,5 @@
1
+ export function serializeGeminiEventToSSE(event) {
2
+ const payload = event.data ?? {};
3
+ const eventType = event.event ?? event.type ?? 'gemini.data';
4
+ return [`event: ${eventType}`, `data: ${JSON.stringify(payload)}`].join('\n') + '\n\n';
5
+ }
@@ -0,0 +1,41 @@
1
+ /**
2
+ * 事件序列化适配器模块导出
3
+ * 提供Chat和Responses协议的事件序列化功能
4
+ */
5
+ import { BatchSerializer, EventSerializer, SerializationOptions } from './base-serializer.js';
6
+ import { ChatEventSerializer } from './chat-event-serializer.js';
7
+ import { ResponsesEventSerializer } from './responses-event-serializer.js';
8
+ export type { SerializationOptions, ChatSerializationOptions, ResponsesSerializationOptions, EventSerializer, ChatEventSerializer, ResponsesEventSerializer } from './types.js';
9
+ export type { BaseEventSerializer, SerializationError, SerializationResult, BatchSerializer } from './base-serializer.js';
10
+ export { defaultChatEventSerializer } from './chat-event-serializer.js';
11
+ export { defaultResponsesEventSerializer } from './responses-event-serializer.js';
12
+ export { serializeAnthropicEventToSSE } from './anthropic-event-serializer.js';
13
+ export { serializeGeminiEventToSSE } from './gemini-event-serializer.js';
14
+ /**
15
+ * 创建序列化器工厂
16
+ */
17
+ export declare class SerializerFactory {
18
+ /**
19
+ * 创建Chat协议序列化器
20
+ */
21
+ static createChatSerializer(options?: SerializationOptions): ChatEventSerializer;
22
+ /**
23
+ * 创建Responses协议序列化器
24
+ */
25
+ static createResponsesSerializer(options?: SerializationOptions): ResponsesEventSerializer;
26
+ /**
27
+ * 根据协议类型创建序列化器
28
+ */
29
+ static createSerializer(protocol: 'chat' | 'responses', options?: SerializationOptions): EventSerializer<any>;
30
+ /**
31
+ * 创建批量序列化器
32
+ */
33
+ static createBatchSerializer<TEvent>(serializer: EventSerializer<TEvent>): BatchSerializer<TEvent>;
34
+ }
35
+ /**
36
+ * 默认序列化器实例
37
+ */
38
+ export declare const defaultSerializers: {
39
+ chat: ChatEventSerializer;
40
+ responses: ResponsesEventSerializer;
41
+ };
@@ -9,6 +9,8 @@ import { ResponsesEventSerializer, defaultResponsesEventSerializer } from './res
9
9
  export { defaultChatEventSerializer } from './chat-event-serializer.js';
10
10
  // Responses协议序列化器
11
11
  export { defaultResponsesEventSerializer } from './responses-event-serializer.js';
12
+ export { serializeAnthropicEventToSSE } from './anthropic-event-serializer.js';
13
+ export { serializeGeminiEventToSSE } from './gemini-event-serializer.js';
12
14
  /**
13
15
  * 创建序列化器工厂
14
16
  */
@@ -0,0 +1,127 @@
1
+ /**
2
+ * 统一的SSE事件流写入器
3
+ * 处理backpressure、超时、心跳、错误处理等通用流管理逻辑
4
+ */
5
+ import { PassThrough } from 'stream';
6
+ import { BaseSseEvent } from '../types/core-interfaces.js';
7
+ import { ChatSseEvent, ResponsesSseEvent, AnthropicSseEvent, GeminiSseEvent } from '../types/index.js';
8
+ export interface StreamWriterConfig {
9
+ timeoutMs?: number;
10
+ enableHeartbeat?: boolean;
11
+ heartbeatIntervalMs?: number;
12
+ maxBufferSize?: number;
13
+ enableBackpressure?: boolean;
14
+ onEvent?: (event: BaseSseEvent) => void;
15
+ onError?: (error: Error) => void;
16
+ onComplete?: () => void;
17
+ }
18
+ export interface StreamWriterStats {
19
+ totalEvents: number;
20
+ bytesWritten: number;
21
+ startTime: number;
22
+ lastWriteTime: number;
23
+ errors: number;
24
+ }
25
+ /**
26
+ * 统一的SSE流写入器
27
+ */
28
+ export declare class StreamWriter {
29
+ private stream;
30
+ private config;
31
+ private stats;
32
+ private heartbeatInterval?;
33
+ private isActive;
34
+ private writeQueue;
35
+ private isWriting;
36
+ constructor(stream: PassThrough, config?: StreamWriterConfig);
37
+ /**
38
+ * 设置心跳
39
+ */
40
+ private setupHeartbeat;
41
+ /**
42
+ * 设置错误处理
43
+ */
44
+ private setupErrorHandling;
45
+ /**
46
+ * 发送心跳事件
47
+ */
48
+ private sendHeartbeat;
49
+ /**
50
+ * 写入单个事件
51
+ */
52
+ private writeEvent;
53
+ /**
54
+ * 处理背压
55
+ */
56
+ private handleBackpressure;
57
+ /**
58
+ * 序列化Responses事件(临时实现,需要 ResponsesSerializer)
59
+ * TODO: 等Responses协议修复后,实现完整的ResponsesSerializer
60
+ * 当前为临时实现,仅用于避免编译错误
61
+ */
62
+ private serializeResponsesEvent;
63
+ /**
64
+ * 异步写入事件流
65
+ */
66
+ writeEventStream(events: AsyncIterable<BaseSseEvent>): Promise<void>;
67
+ /**
68
+ * 同步写入事件数组
69
+ */
70
+ writeEvents(events: BaseSseEvent[]): Promise<void>;
71
+ /**
72
+ * 写入Chat事件流
73
+ */
74
+ writeChatEvents(events: AsyncIterable<ChatSseEvent> | ChatSseEvent[]): Promise<void>;
75
+ /**
76
+ * 写入Responses事件流
77
+ */
78
+ writeResponsesEvents(events: AsyncIterable<ResponsesSseEvent> | ResponsesSseEvent[]): Promise<void>;
79
+ /**
80
+ * 写入Anthropic事件流
81
+ */
82
+ writeAnthropicEvents(events: AsyncIterable<AnthropicSseEvent> | AnthropicSseEvent[]): Promise<void>;
83
+ /**
84
+ * 写入Gemini事件流
85
+ */
86
+ writeGeminiEvents(events: AsyncIterable<GeminiSseEvent> | GeminiSseEvent[]): Promise<void>;
87
+ /**
88
+ * 完成流写入
89
+ */
90
+ complete(): void;
91
+ /**
92
+ * 中止流写入
93
+ */
94
+ abort(error?: Error): void;
95
+ /**
96
+ * 获取写入统计
97
+ */
98
+ getStats(): StreamWriterStats;
99
+ /**
100
+ * 检查流是否活跃
101
+ */
102
+ isStreamActive(): boolean;
103
+ /**
104
+ * 清理资源
105
+ */
106
+ private cleanup;
107
+ /**
108
+ * 获取底层流
109
+ */
110
+ getUnderlyingStream(): PassThrough;
111
+ }
112
+ /**
113
+ * 创建Chat流写入器工厂函数
114
+ */
115
+ export declare function createChatStreamWriter(stream: PassThrough, config?: StreamWriterConfig): StreamWriter;
116
+ /**
117
+ * 创建Responses流写入器工厂函数
118
+ */
119
+ export declare function createResponsesStreamWriter(stream: PassThrough, config?: StreamWriterConfig): StreamWriter;
120
+ /**
121
+ * 创建Anthropic流写入器工厂函数
122
+ */
123
+ export declare function createAnthropicStreamWriter(stream: PassThrough, config?: StreamWriterConfig): StreamWriter;
124
+ /**
125
+ * 创建Gemini流写入器工厂函数
126
+ */
127
+ export declare function createGeminiStreamWriter(stream: PassThrough, config?: StreamWriterConfig): StreamWriter;
@@ -2,7 +2,7 @@
2
2
  * 统一的SSE事件流写入器
3
3
  * 处理backpressure、超时、心跳、错误处理等通用流管理逻辑
4
4
  */
5
- import { defaultResponsesEventSerializer } from './serializers/index.js';
5
+ import { defaultResponsesEventSerializer, serializeAnthropicEventToSSE, serializeGeminiEventToSSE } from './serializers/index.js';
6
6
  import { TimeUtils } from './utils.js';
7
7
  import { serializeChatEventToSSE } from './chat-serializer.js';
8
8
  /**
@@ -92,12 +92,24 @@ export class StreamWriter {
92
92
  else if (event && event.protocol === 'responses') {
93
93
  serialized = this.serializeResponsesEvent(event);
94
94
  }
95
+ else if (event && event.protocol === 'anthropic-messages') {
96
+ serialized = serializeAnthropicEventToSSE(event);
97
+ }
98
+ else if (event && event.protocol === 'gemini-chat') {
99
+ serialized = serializeGeminiEventToSSE(event);
100
+ }
95
101
  else {
96
102
  // 兜底处理:尝试通过事件字段识别
97
103
  const eventField = event.event;
98
104
  if (eventField === 'chat_chunk' || eventField === 'chat.done' || eventField === 'error' || eventField === 'ping') {
99
105
  serialized = serializeChatEventToSSE(event);
100
106
  }
107
+ else if (eventField === 'message_start' || eventField === 'content_block_start') {
108
+ serialized = serializeAnthropicEventToSSE(event);
109
+ }
110
+ else if (eventField === 'gemini.data' || eventField === 'gemini.done') {
111
+ serialized = serializeGeminiEventToSSE(event);
112
+ }
101
113
  else {
102
114
  serialized = this.serializeResponsesEvent(event);
103
115
  }
@@ -195,6 +207,18 @@ export class StreamWriter {
195
207
  async writeResponsesEvents(events) {
196
208
  await this.writeEventStream(events);
197
209
  }
210
+ /**
211
+ * 写入Anthropic事件流
212
+ */
213
+ async writeAnthropicEvents(events) {
214
+ await this.writeEventStream(events);
215
+ }
216
+ /**
217
+ * 写入Gemini事件流
218
+ */
219
+ async writeGeminiEvents(events) {
220
+ await this.writeEventStream(events);
221
+ }
198
222
  /**
199
223
  * 完成流写入
200
224
  */
@@ -266,3 +290,15 @@ export function createChatStreamWriter(stream, config = {}) {
266
290
  export function createResponsesStreamWriter(stream, config = {}) {
267
291
  return new StreamWriter(stream, config);
268
292
  }
293
+ /**
294
+ * 创建Anthropic流写入器工厂函数
295
+ */
296
+ export function createAnthropicStreamWriter(stream, config = {}) {
297
+ return new StreamWriter(stream, config);
298
+ }
299
+ /**
300
+ * 创建Gemini流写入器工厂函数
301
+ */
302
+ export function createGeminiStreamWriter(stream, config = {}) {
303
+ return new StreamWriter(stream, config);
304
+ }
@@ -0,0 +1,11 @@
1
+ import { AnthropicMessageResponse, SseToAnthropicJsonOptions } from '../types/index.js';
2
+ export declare class AnthropicSseToJsonConverter {
3
+ private config;
4
+ private contexts;
5
+ constructor(config?: Partial<typeof this.config>);
6
+ convertSseToJson(sseStream: AsyncIterable<string | Buffer>, options: SseToAnthropicJsonOptions): Promise<AnthropicMessageResponse>;
7
+ private createContext;
8
+ private chunkStrings;
9
+ private updateStats;
10
+ private wrapError;
11
+ }
@@ -1,139 +1,104 @@
1
- function isStringOrBuffer(v) {
2
- return typeof v === 'string' || Buffer.isBuffer(v);
3
- }
4
- /**
5
- * Minimal Anthropic SSE → JSON converter (v3)
6
- *
7
- * Aggregates a subset of Anthropic events into a single message object:
8
- * - message_start → capture id/model
9
- * - content_block_start/delta/stop → build text/tool_use blocks
10
- * - message_delta → stop_reason hint
11
- * - message_stop → finalize
12
- */
1
+ import { DEFAULT_ANTHROPIC_CONVERSION_CONFIG } from '../types/index.js';
2
+ import { ErrorUtils } from '../shared/utils.js';
3
+ import { createSseParser } from './parsers/sse-parser.js';
4
+ import { createAnthropicResponseBuilder } from './builders/anthropic-response-builder.js';
13
5
  export class AnthropicSseToJsonConverter {
14
- async convertSseToJson(sseStream, _options) {
15
- const state = { content: [], role: 'assistant', current: null };
16
- let buffer = '';
17
- const flushEvent = (event) => {
18
- const t = String(event?.event || '').trim();
19
- const data = event?.data;
20
- if (!t)
21
- return;
22
- if (t === 'message_start') {
23
- state.id = data?.message?.id || state.id;
24
- state.model = data?.message?.model || state.model;
25
- state.role = data?.message?.role || 'assistant';
26
- return;
27
- }
28
- if (t === 'content_block_start') {
29
- const cb = data?.content_block;
30
- if (cb?.type === 'text') {
31
- state.current = { index: Number(data?.index || 0), kind: 'text' };
32
- }
33
- else if (cb?.type === 'tool_use') {
34
- state.current = {
35
- index: Number(data?.index || 0),
36
- kind: 'tool_use',
37
- id: String(cb?.id || `call_${Math.random().toString(36).slice(2, 10)}`),
38
- name: String(cb?.name || 'unknown_tool'),
39
- json: ''
40
- };
41
- }
42
- return;
43
- }
44
- if (t === 'content_block_delta') {
45
- if (!state.current)
46
- return;
47
- const d = data?.delta;
48
- if (state.current.kind === 'text' && d?.type === 'text_delta' && typeof d?.text === 'string') {
49
- const last = state.content[state.content.length - 1];
50
- if (last && last.type === 'text') {
51
- last.text += d.text;
52
- }
53
- else {
54
- state.content.push({ type: 'text', text: d.text });
55
- }
56
- }
57
- else if (state.current.kind === 'tool_use' && d?.type === 'input_json_delta') {
58
- state.current.json = (state.current.json || '') + String(d?.partial_json || '');
59
- }
60
- return;
61
- }
62
- if (t === 'content_block_stop') {
63
- if (!state.current)
64
- return;
65
- if (state.current.kind === 'tool_use') {
66
- let input = {};
67
- try {
68
- input = state.current.json ? JSON.parse(state.current.json) : {};
69
- }
70
- catch {
71
- input = { _raw: state.current.json };
72
- }
73
- state.content.push({ type: 'tool_use', id: state.current.id, name: state.current.name, input });
74
- }
75
- state.current = null;
76
- return;
77
- }
78
- if (t === 'message_delta') {
79
- const r = data?.delta?.stop_reason;
80
- if (r)
81
- state.stop_reason = r;
82
- return;
83
- }
84
- if (t === 'message_stop') {
85
- // finalize
86
- return;
87
- }
88
- };
89
- // very small SSE line parser (event/data/id); compatible with our Generators
90
- let current = {};
91
- const pushCurrent = () => {
92
- if (Object.keys(current).length) {
93
- flushEvent(current);
94
- current = {};
95
- }
96
- };
97
- for await (const chunk of sseStream) {
98
- const text = isStringOrBuffer(chunk) ? chunk.toString() : String(chunk);
99
- buffer += text;
100
- const lines = buffer.split('\n');
101
- buffer = lines.pop() || '';
102
- for (const raw of lines) {
103
- const line = raw.trim();
104
- if (!line) {
105
- pushCurrent();
106
- continue;
107
- }
108
- if (line.startsWith('event:')) {
109
- current.event = line.slice(6).trim();
110
- continue;
111
- }
112
- if (line.startsWith('data:')) {
113
- const data = line.slice(5).trim();
114
- try {
115
- current.data = JSON.parse(data);
116
- }
117
- catch {
118
- current.data = { raw: data };
6
+ config = {
7
+ enableEventValidation: true,
8
+ strictMode: false,
9
+ ...DEFAULT_ANTHROPIC_CONVERSION_CONFIG
10
+ };
11
+ contexts = new Map();
12
+ constructor(config) {
13
+ if (config) {
14
+ this.config = { ...this.config, ...config };
15
+ }
16
+ }
17
+ async convertSseToJson(sseStream, options) {
18
+ const context = this.createContext(options);
19
+ this.contexts.set(options.requestId, context);
20
+ const parser = createSseParser({
21
+ enableStrictValidation: this.config.enableEventValidation,
22
+ enableEventRecovery: !this.config.strictMode,
23
+ allowedEventTypes: new Set([
24
+ 'message_start',
25
+ 'content_block_start',
26
+ 'content_block_delta',
27
+ 'content_block_stop',
28
+ 'message_delta',
29
+ 'message_stop'
30
+ ])
31
+ });
32
+ const builder = createAnthropicResponseBuilder({
33
+ reasoningMode: options.reasoningMode ?? this.config.reasoningMode,
34
+ reasoningTextPrefix: options.reasoningTextPrefix ?? this.config.reasoningTextPrefix
35
+ });
36
+ try {
37
+ for await (const result of parser.parseStreamAsync(this.chunkStrings(sseStream))) {
38
+ if (!result.success || !result.event) {
39
+ if (this.config.strictMode) {
40
+ throw new Error(result.error || 'Failed to parse Anthropic SSE event');
119
41
  }
120
42
  continue;
121
43
  }
122
- if (line.startsWith('id:')) {
123
- current.id = line.slice(3).trim();
44
+ if (result.event.protocol !== 'anthropic-messages') {
124
45
  continue;
125
46
  }
47
+ builder.processEvent(result.event);
48
+ this.updateStats(context, result.event);
49
+ }
50
+ const outcome = builder.getResult();
51
+ if (!outcome.success || !outcome.response) {
52
+ throw outcome.error || new Error('Anthropic SSE conversion incomplete');
126
53
  }
54
+ context.isCompleted = true;
55
+ context.eventStats.endTime = Date.now();
56
+ return outcome.response;
127
57
  }
128
- // flush tail
129
- pushCurrent();
58
+ catch (error) {
59
+ context.eventStats.errors = (context.eventStats.errors ?? 0) + 1;
60
+ throw this.wrapError('ANTHROPIC_SSE_TO_JSON_FAILED', error, options.requestId);
61
+ }
62
+ finally {
63
+ this.contexts.delete(options.requestId);
64
+ }
65
+ }
66
+ createContext(options) {
130
67
  return {
131
- id: state.id || `msg_${Date.now()}`,
132
- type: 'message',
133
- role: state.role || 'assistant',
134
- model: state.model || 'unknown',
135
- content: state.content,
136
- stop_reason: state.stop_reason || 'end_turn'
68
+ requestId: options.requestId,
69
+ model: options.model,
70
+ startTime: Date.now(),
71
+ eventStats: {
72
+ totalEvents: 0,
73
+ contentBlocks: 0,
74
+ toolUseBlocks: 0,
75
+ thinkingBlocks: 0,
76
+ textBlocks: 0,
77
+ errors: 0,
78
+ startTime: Date.now()
79
+ },
80
+ isCompleted: false
137
81
  };
138
82
  }
83
+ async *chunkStrings(stream) {
84
+ for await (const chunk of stream) {
85
+ yield typeof chunk === 'string' ? chunk : chunk.toString();
86
+ }
87
+ }
88
+ updateStats(context, event) {
89
+ context.eventStats.totalEvents += 1;
90
+ if (event.type === 'content_block_start') {
91
+ context.eventStats.contentBlocks += 1;
92
+ const blockType = event.data?.content_block?.type;
93
+ if (blockType === 'tool_use')
94
+ context.eventStats.toolUseBlocks += 1;
95
+ if (blockType === 'thinking')
96
+ context.eventStats.thinkingBlocks += 1;
97
+ if (blockType === 'text')
98
+ context.eventStats.textBlocks += 1;
99
+ }
100
+ }
101
+ wrapError(code, error, requestId) {
102
+ return ErrorUtils.createError(error.message, code, { requestId });
103
+ }
139
104
  }
@@ -0,0 +1,16 @@
1
+ import { AnthropicMessageResponse, AnthropicSseEvent } from '../../types/index.js';
2
+ import type { ChatReasoningMode } from '../../types/chat-types.js';
3
+ interface BuilderOptions {
4
+ reasoningMode?: ChatReasoningMode;
5
+ reasoningTextPrefix?: string;
6
+ }
7
+ export interface AnthropicBuilderResult {
8
+ success: boolean;
9
+ response?: AnthropicMessageResponse;
10
+ error?: Error;
11
+ }
12
+ export declare function createAnthropicResponseBuilder(options?: BuilderOptions): {
13
+ processEvent(event: AnthropicSseEvent): boolean;
14
+ getResult(): AnthropicBuilderResult;
15
+ };
16
+ export {};