@jsonstudio/llms 0.4.4 → 0.4.6
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.
- package/dist/conversion/codec-registry.js +11 -1
- package/dist/conversion/codecs/anthropic-openai-codec.d.ts +13 -0
- package/dist/conversion/codecs/anthropic-openai-codec.js +18 -473
- package/dist/conversion/codecs/gemini-openai-codec.js +91 -48
- package/dist/conversion/codecs/responses-openai-codec.js +9 -2
- package/dist/conversion/hub/format-adapters/anthropic-format-adapter.js +3 -0
- package/dist/conversion/hub/format-adapters/chat-format-adapter.js +3 -0
- package/dist/conversion/hub/format-adapters/gemini-format-adapter.js +3 -0
- package/dist/conversion/hub/format-adapters/responses-format-adapter.d.ts +19 -0
- package/dist/conversion/hub/format-adapters/responses-format-adapter.js +9 -0
- package/dist/conversion/hub/node-support.js +3 -1
- package/dist/conversion/hub/pipeline/hub-pipeline.js +37 -32
- package/dist/conversion/hub/response/provider-response.js +1 -1
- package/dist/conversion/hub/response/response-mappers.js +1 -1
- package/dist/conversion/hub/response/response-runtime.js +109 -10
- package/dist/conversion/hub/semantic-mappers/anthropic-mapper.js +70 -156
- package/dist/conversion/hub/semantic-mappers/chat-mapper.js +63 -52
- package/dist/conversion/hub/semantic-mappers/gemini-mapper.js +76 -143
- package/dist/conversion/hub/semantic-mappers/responses-mapper.js +40 -160
- package/dist/conversion/hub/standardized-bridge.js +3 -0
- package/dist/conversion/hub/tool-governance/rules.js +2 -2
- package/dist/conversion/index.d.ts +5 -0
- package/dist/conversion/index.js +5 -0
- package/dist/conversion/pipeline/codecs/v2/anthropic-openai-pipeline.d.ts +12 -0
- package/dist/conversion/pipeline/codecs/v2/anthropic-openai-pipeline.js +100 -0
- package/dist/conversion/pipeline/codecs/v2/openai-openai-pipeline.d.ts +15 -0
- package/dist/conversion/pipeline/codecs/v2/openai-openai-pipeline.js +174 -0
- package/dist/conversion/pipeline/codecs/v2/responses-openai-pipeline.d.ts +14 -0
- package/dist/conversion/pipeline/codecs/v2/responses-openai-pipeline.js +166 -0
- package/dist/conversion/pipeline/codecs/v2/shared/openai-chat-helpers.d.ts +13 -0
- package/dist/conversion/pipeline/codecs/v2/shared/openai-chat-helpers.js +66 -0
- package/dist/conversion/pipeline/hooks/adapter-context.d.ts +7 -0
- package/dist/conversion/pipeline/hooks/adapter-context.js +18 -0
- package/dist/conversion/pipeline/hooks/protocol-hooks.d.ts +67 -0
- package/dist/conversion/pipeline/hooks/protocol-hooks.js +1 -0
- package/dist/conversion/pipeline/index.d.ts +35 -0
- package/dist/conversion/pipeline/index.js +103 -0
- package/dist/conversion/pipeline/meta/meta-bag.d.ts +20 -0
- package/dist/conversion/pipeline/meta/meta-bag.js +81 -0
- package/dist/conversion/pipeline/schema/canonical-chat.d.ts +18 -0
- package/dist/conversion/pipeline/schema/canonical-chat.js +1 -0
- package/dist/conversion/pipeline/schema/index.d.ts +1 -0
- package/dist/conversion/pipeline/schema/index.js +1 -0
- package/dist/conversion/responses/responses-openai-bridge.d.ts +48 -0
- package/dist/conversion/responses/responses-openai-bridge.js +157 -1146
- package/dist/conversion/shared/anthropic-message-utils.d.ts +12 -0
- package/dist/conversion/shared/anthropic-message-utils.js +587 -0
- package/dist/conversion/shared/bridge-actions.d.ts +39 -0
- package/dist/conversion/shared/bridge-actions.js +709 -0
- package/dist/conversion/shared/bridge-conversation-store.d.ts +41 -0
- package/dist/conversion/shared/bridge-conversation-store.js +279 -0
- package/dist/conversion/shared/bridge-id-utils.d.ts +7 -0
- package/dist/conversion/shared/bridge-id-utils.js +42 -0
- package/dist/conversion/shared/bridge-instructions.d.ts +1 -0
- package/dist/conversion/shared/bridge-instructions.js +113 -0
- package/dist/conversion/shared/bridge-message-types.d.ts +39 -0
- package/dist/conversion/shared/bridge-message-types.js +1 -0
- package/dist/conversion/shared/bridge-message-utils.d.ts +22 -0
- package/dist/conversion/shared/bridge-message-utils.js +473 -0
- package/dist/conversion/shared/bridge-metadata.d.ts +1 -0
- package/dist/conversion/shared/bridge-metadata.js +1 -0
- package/dist/conversion/shared/bridge-policies.d.ts +18 -0
- package/dist/conversion/shared/bridge-policies.js +276 -0
- package/dist/conversion/shared/bridge-request-adapter.d.ts +28 -0
- package/dist/conversion/shared/bridge-request-adapter.js +430 -0
- package/dist/conversion/shared/chat-output-normalizer.d.ts +4 -0
- package/dist/conversion/shared/chat-output-normalizer.js +56 -0
- package/dist/conversion/shared/chat-request-filters.js +24 -1
- package/dist/conversion/shared/gemini-tool-utils.d.ts +5 -0
- package/dist/conversion/shared/gemini-tool-utils.js +130 -0
- package/dist/conversion/shared/metadata-passthrough.d.ts +11 -0
- package/dist/conversion/shared/metadata-passthrough.js +57 -0
- package/dist/conversion/shared/output-content-normalizer.d.ts +12 -0
- package/dist/conversion/shared/output-content-normalizer.js +119 -0
- package/dist/conversion/shared/reasoning-normalizer.d.ts +21 -0
- package/dist/conversion/shared/reasoning-normalizer.js +368 -0
- package/dist/conversion/shared/reasoning-tool-normalizer.d.ts +12 -0
- package/dist/conversion/shared/reasoning-tool-normalizer.js +132 -0
- package/dist/conversion/shared/reasoning-tool-parser.d.ts +10 -0
- package/dist/conversion/shared/reasoning-tool-parser.js +95 -0
- package/dist/conversion/shared/reasoning-utils.d.ts +2 -0
- package/dist/conversion/shared/reasoning-utils.js +42 -0
- package/dist/conversion/shared/responses-conversation-store.js +5 -11
- package/dist/conversion/shared/responses-message-utils.d.ts +15 -0
- package/dist/conversion/shared/responses-message-utils.js +206 -0
- package/dist/conversion/shared/responses-output-builder.d.ts +15 -0
- package/dist/conversion/shared/responses-output-builder.js +179 -0
- package/dist/conversion/shared/responses-output-utils.d.ts +7 -0
- package/dist/conversion/shared/responses-output-utils.js +108 -0
- package/dist/conversion/shared/responses-request-adapter.d.ts +28 -0
- package/dist/conversion/shared/responses-request-adapter.js +9 -40
- package/dist/conversion/shared/responses-response-utils.d.ts +3 -0
- package/dist/conversion/shared/responses-response-utils.js +209 -0
- package/dist/conversion/shared/responses-tool-utils.d.ts +12 -0
- package/dist/conversion/shared/responses-tool-utils.js +90 -0
- package/dist/conversion/shared/responses-types.d.ts +33 -0
- package/dist/conversion/shared/responses-types.js +1 -0
- package/dist/conversion/shared/tool-call-utils.d.ts +11 -0
- package/dist/conversion/shared/tool-call-utils.js +56 -0
- package/dist/conversion/shared/tool-governor.js +5 -0
- package/dist/conversion/shared/tool-mapping.d.ts +19 -0
- package/dist/conversion/shared/tool-mapping.js +124 -0
- package/dist/conversion/shared/tool-normalizers.d.ts +4 -0
- package/dist/conversion/shared/tool-normalizers.js +84 -0
- package/dist/router/virtual-router/bootstrap.js +18 -3
- package/dist/router/virtual-router/provider-registry.js +4 -2
- package/dist/router/virtual-router/types.d.ts +212 -0
- package/dist/sse/index.d.ts +38 -2
- package/dist/sse/index.js +27 -0
- package/dist/sse/json-to-sse/anthropic-json-to-sse-converter.d.ts +14 -0
- package/dist/sse/json-to-sse/anthropic-json-to-sse-converter.js +106 -73
- package/dist/sse/json-to-sse/chat-json-to-sse-converter.js +6 -2
- package/dist/sse/json-to-sse/gemini-json-to-sse-converter.d.ts +14 -0
- package/dist/sse/json-to-sse/gemini-json-to-sse-converter.js +99 -0
- package/dist/sse/json-to-sse/index.d.ts +7 -0
- package/dist/sse/json-to-sse/index.js +2 -0
- package/dist/sse/json-to-sse/sequencers/anthropic-sequencer.d.ts +13 -0
- package/dist/sse/json-to-sse/sequencers/anthropic-sequencer.js +150 -0
- package/dist/sse/json-to-sse/sequencers/chat-sequencer.d.ts +39 -0
- package/dist/sse/json-to-sse/sequencers/chat-sequencer.js +49 -3
- package/dist/sse/json-to-sse/sequencers/gemini-sequencer.d.ts +10 -0
- package/dist/sse/json-to-sse/sequencers/gemini-sequencer.js +95 -0
- package/dist/sse/json-to-sse/sequencers/responses-sequencer.js +31 -5
- package/dist/sse/registry/sse-codec-registry.d.ts +32 -0
- package/dist/sse/registry/sse-codec-registry.js +30 -1
- package/dist/sse/shared/reasoning-dispatcher.d.ts +10 -0
- package/dist/sse/shared/reasoning-dispatcher.js +25 -0
- package/dist/sse/shared/responses-output-normalizer.d.ts +12 -0
- package/dist/sse/shared/responses-output-normalizer.js +45 -0
- package/dist/sse/shared/serializers/anthropic-event-serializer.d.ts +2 -0
- package/dist/sse/shared/serializers/anthropic-event-serializer.js +9 -0
- package/dist/sse/shared/serializers/gemini-event-serializer.d.ts +2 -0
- package/dist/sse/shared/serializers/gemini-event-serializer.js +5 -0
- package/dist/sse/shared/serializers/index.d.ts +41 -0
- package/dist/sse/shared/serializers/index.js +2 -0
- package/dist/sse/shared/writer.d.ts +127 -0
- package/dist/sse/shared/writer.js +37 -1
- package/dist/sse/sse-to-json/anthropic-sse-to-json-converter.d.ts +11 -0
- package/dist/sse/sse-to-json/anthropic-sse-to-json-converter.js +92 -127
- package/dist/sse/sse-to-json/builders/anthropic-response-builder.d.ts +16 -0
- package/dist/sse/sse-to-json/builders/anthropic-response-builder.js +151 -0
- package/dist/sse/sse-to-json/builders/response-builder.d.ts +165 -0
- package/dist/sse/sse-to-json/builders/response-builder.js +27 -6
- package/dist/sse/sse-to-json/chat-sse-to-json-converter.d.ts +114 -0
- package/dist/sse/sse-to-json/chat-sse-to-json-converter.js +79 -3
- package/dist/sse/sse-to-json/gemini-sse-to-json-converter.d.ts +13 -0
- package/dist/sse/sse-to-json/gemini-sse-to-json-converter.js +160 -0
- package/dist/sse/sse-to-json/index.d.ts +7 -0
- package/dist/sse/sse-to-json/index.js +2 -0
- package/dist/sse/sse-to-json/parsers/sse-parser.js +53 -1
- package/dist/sse/types/anthropic-types.d.ts +170 -0
- package/dist/sse/types/anthropic-types.js +8 -5
- package/dist/sse/types/chat-types.d.ts +10 -0
- package/dist/sse/types/chat-types.js +2 -1
- package/dist/sse/types/core-interfaces.d.ts +1 -1
- package/dist/sse/types/gemini-types.d.ts +116 -0
- package/dist/sse/types/gemini-types.js +5 -0
- package/dist/sse/types/index.d.ts +5 -2
- package/dist/sse/types/index.js +2 -0
- 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,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,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
|
-
|
|
2
|
-
|
|
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
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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 (
|
|
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
|
-
|
|
129
|
-
|
|
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
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
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 {};
|