@acosmi/sdk-ts 1.0.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.
- package/LICENSE +21 -0
- package/README.md +247 -0
- package/dist/browser/index.js +3950 -0
- package/dist/browser/index.js.map +1 -0
- package/dist/index.js +3950 -0
- package/dist/index.js.map +1 -0
- package/dist/node/adapters/anthropic.cjs +257 -0
- package/dist/node/adapters/anthropic.cjs.map +1 -0
- package/dist/node/adapters/anthropic.d.cts +39 -0
- package/dist/node/adapters/anthropic.d.ts +39 -0
- package/dist/node/adapters/anthropic.js +254 -0
- package/dist/node/adapters/anthropic.js.map +1 -0
- package/dist/node/adapters/openai.cjs +472 -0
- package/dist/node/adapters/openai.cjs.map +1 -0
- package/dist/node/adapters/openai.d.cts +64 -0
- package/dist/node/adapters/openai.d.ts +64 -0
- package/dist/node/adapters/openai.js +465 -0
- package/dist/node/adapters/openai.js.map +1 -0
- package/dist/node/index-C3Z_84Bv.d.cts +992 -0
- package/dist/node/index-C3Z_84Bv.d.ts +992 -0
- package/dist/node/index-nvLKgCm9.d.cts +147 -0
- package/dist/node/index-nvLKgCm9.d.ts +147 -0
- package/dist/node/index.cjs +4024 -0
- package/dist/node/index.cjs.map +1 -0
- package/dist/node/index.d.cts +791 -0
- package/dist/node/index.d.ts +791 -0
- package/dist/node/index.js +3950 -0
- package/dist/node/index.js.map +1 -0
- package/dist/node/sanitize/index.cjs +248 -0
- package/dist/node/sanitize/index.cjs.map +1 -0
- package/dist/node/sanitize/index.d.cts +1 -0
- package/dist/node/sanitize/index.d.ts +1 -0
- package/dist/node/sanitize/index.js +217 -0
- package/dist/node/sanitize/index.js.map +1 -0
- package/package.json +84 -0
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { P as ProviderAdapter, aj as ProviderFormat, M as ModelCapabilities, b as ChatRequest, e as ChatResponse, f as StreamEvent, A as AnthropicResponse } from '../index-C3Z_84Bv.cjs';
|
|
2
|
+
|
|
3
|
+
/** 实现 ProviderAdapter, 用于所有非 Anthropic 厂商 */
|
|
4
|
+
declare class OpenAIAdapter implements ProviderAdapter {
|
|
5
|
+
format(): ProviderFormat;
|
|
6
|
+
endpointSuffix(): string;
|
|
7
|
+
/**
|
|
8
|
+
* 构建 OpenAI 兼容格式请求体
|
|
9
|
+
* 不注入 Anthropic betas, 扩展字段 (thinking/effort/speed) 以通用 JSON 传递
|
|
10
|
+
*/
|
|
11
|
+
buildRequestBody(_caps: ModelCapabilities, req: ChatRequest): Record<string, unknown>;
|
|
12
|
+
/**
|
|
13
|
+
* 解析 OpenAI 格式同步响应为 ChatResponse
|
|
14
|
+
* 兼容 APIResponse 包装 {"code":0,"data":{...}} 和裸 OpenAI JSON 两种格式
|
|
15
|
+
*/
|
|
16
|
+
parseResponse(bodyInput: Uint8Array | string): ChatResponse;
|
|
17
|
+
/**
|
|
18
|
+
* 解析 OpenAI SSE 行
|
|
19
|
+
* [DONE] 标记流结束
|
|
20
|
+
*/
|
|
21
|
+
parseStreamLine(eventType: string, data: string): {
|
|
22
|
+
event: StreamEvent;
|
|
23
|
+
done: boolean;
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* 把 Anthropic 心智模型的 thinking/effort 翻译成 OpenAI `reasoning_effort` 字段值
|
|
28
|
+
* 返回空串表示不设置
|
|
29
|
+
*/
|
|
30
|
+
declare function resolveOpenAIReasoningEffort(req: ChatRequest): string;
|
|
31
|
+
/**
|
|
32
|
+
* 把 outputConfig 翻译成 OpenAI response_format
|
|
33
|
+
* 返回 null 表示不设置
|
|
34
|
+
*/
|
|
35
|
+
declare function resolveOpenAIResponseFormat(req: ChatRequest): Record<string, unknown> | null;
|
|
36
|
+
/**
|
|
37
|
+
* 解析 OpenAI 格式响应并转换为 AnthropicResponse
|
|
38
|
+
* 用于 chatMessagesOpenAI 方法, 使 Hub 层无需感知 provider 差异
|
|
39
|
+
*/
|
|
40
|
+
declare function parseOpenAIResponseToAnthropic(raw: string | Uint8Array): AnthropicResponse;
|
|
41
|
+
/**
|
|
42
|
+
* 将 OpenAI SSE chunks 转换为 Anthropic 兼容的 StreamEvent
|
|
43
|
+
* 有状态: 跨 chunk 追踪 block 索引
|
|
44
|
+
*/
|
|
45
|
+
declare class OpenAIStreamConverter {
|
|
46
|
+
private messageStarted;
|
|
47
|
+
private thinkingStarted;
|
|
48
|
+
private thinkingStopped;
|
|
49
|
+
private textStarted;
|
|
50
|
+
/** OpenAI tool_call index → Anthropic block index */
|
|
51
|
+
private toolBlockIndex;
|
|
52
|
+
private blockIndex;
|
|
53
|
+
/**
|
|
54
|
+
* 将一行 OpenAI SSE data 转换为零或多个 Anthropic 格式 StreamEvent
|
|
55
|
+
* 返回 { events, done }
|
|
56
|
+
*/
|
|
57
|
+
convert(data: string): {
|
|
58
|
+
events: StreamEvent[];
|
|
59
|
+
done: boolean;
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
declare function newOpenAIStreamConverter(): OpenAIStreamConverter;
|
|
63
|
+
|
|
64
|
+
export { OpenAIAdapter, OpenAIStreamConverter, newOpenAIStreamConverter, parseOpenAIResponseToAnthropic, resolveOpenAIReasoningEffort, resolveOpenAIResponseFormat };
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { P as ProviderAdapter, aj as ProviderFormat, M as ModelCapabilities, b as ChatRequest, e as ChatResponse, f as StreamEvent, A as AnthropicResponse } from '../index-C3Z_84Bv.js';
|
|
2
|
+
|
|
3
|
+
/** 实现 ProviderAdapter, 用于所有非 Anthropic 厂商 */
|
|
4
|
+
declare class OpenAIAdapter implements ProviderAdapter {
|
|
5
|
+
format(): ProviderFormat;
|
|
6
|
+
endpointSuffix(): string;
|
|
7
|
+
/**
|
|
8
|
+
* 构建 OpenAI 兼容格式请求体
|
|
9
|
+
* 不注入 Anthropic betas, 扩展字段 (thinking/effort/speed) 以通用 JSON 传递
|
|
10
|
+
*/
|
|
11
|
+
buildRequestBody(_caps: ModelCapabilities, req: ChatRequest): Record<string, unknown>;
|
|
12
|
+
/**
|
|
13
|
+
* 解析 OpenAI 格式同步响应为 ChatResponse
|
|
14
|
+
* 兼容 APIResponse 包装 {"code":0,"data":{...}} 和裸 OpenAI JSON 两种格式
|
|
15
|
+
*/
|
|
16
|
+
parseResponse(bodyInput: Uint8Array | string): ChatResponse;
|
|
17
|
+
/**
|
|
18
|
+
* 解析 OpenAI SSE 行
|
|
19
|
+
* [DONE] 标记流结束
|
|
20
|
+
*/
|
|
21
|
+
parseStreamLine(eventType: string, data: string): {
|
|
22
|
+
event: StreamEvent;
|
|
23
|
+
done: boolean;
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* 把 Anthropic 心智模型的 thinking/effort 翻译成 OpenAI `reasoning_effort` 字段值
|
|
28
|
+
* 返回空串表示不设置
|
|
29
|
+
*/
|
|
30
|
+
declare function resolveOpenAIReasoningEffort(req: ChatRequest): string;
|
|
31
|
+
/**
|
|
32
|
+
* 把 outputConfig 翻译成 OpenAI response_format
|
|
33
|
+
* 返回 null 表示不设置
|
|
34
|
+
*/
|
|
35
|
+
declare function resolveOpenAIResponseFormat(req: ChatRequest): Record<string, unknown> | null;
|
|
36
|
+
/**
|
|
37
|
+
* 解析 OpenAI 格式响应并转换为 AnthropicResponse
|
|
38
|
+
* 用于 chatMessagesOpenAI 方法, 使 Hub 层无需感知 provider 差异
|
|
39
|
+
*/
|
|
40
|
+
declare function parseOpenAIResponseToAnthropic(raw: string | Uint8Array): AnthropicResponse;
|
|
41
|
+
/**
|
|
42
|
+
* 将 OpenAI SSE chunks 转换为 Anthropic 兼容的 StreamEvent
|
|
43
|
+
* 有状态: 跨 chunk 追踪 block 索引
|
|
44
|
+
*/
|
|
45
|
+
declare class OpenAIStreamConverter {
|
|
46
|
+
private messageStarted;
|
|
47
|
+
private thinkingStarted;
|
|
48
|
+
private thinkingStopped;
|
|
49
|
+
private textStarted;
|
|
50
|
+
/** OpenAI tool_call index → Anthropic block index */
|
|
51
|
+
private toolBlockIndex;
|
|
52
|
+
private blockIndex;
|
|
53
|
+
/**
|
|
54
|
+
* 将一行 OpenAI SSE data 转换为零或多个 Anthropic 格式 StreamEvent
|
|
55
|
+
* 返回 { events, done }
|
|
56
|
+
*/
|
|
57
|
+
convert(data: string): {
|
|
58
|
+
events: StreamEvent[];
|
|
59
|
+
done: boolean;
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
declare function newOpenAIStreamConverter(): OpenAIStreamConverter;
|
|
63
|
+
|
|
64
|
+
export { OpenAIAdapter, OpenAIStreamConverter, newOpenAIStreamConverter, parseOpenAIResponseToAnthropic, resolveOpenAIReasoningEffort, resolveOpenAIResponseFormat };
|
|
@@ -0,0 +1,465 @@
|
|
|
1
|
+
// src/types.ts
|
|
2
|
+
var ThinkingOff = "off";
|
|
3
|
+
var ThinkingHigh = "high";
|
|
4
|
+
var ThinkingMax = "max";
|
|
5
|
+
var BusinessError = class extends Error {
|
|
6
|
+
code;
|
|
7
|
+
constructor(code, message) {
|
|
8
|
+
super(`API error (code=${code}): ${message}`);
|
|
9
|
+
this.name = "BusinessError";
|
|
10
|
+
this.code = code;
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
new OpenAIAdapter();
|
|
14
|
+
|
|
15
|
+
// src/adapters/openai.ts
|
|
16
|
+
var OpenAIAdapter = class {
|
|
17
|
+
format() {
|
|
18
|
+
return 1 /* OpenAI */;
|
|
19
|
+
}
|
|
20
|
+
endpointSuffix() {
|
|
21
|
+
return "/chat";
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* 构建 OpenAI 兼容格式请求体
|
|
25
|
+
* 不注入 Anthropic betas, 扩展字段 (thinking/effort/speed) 以通用 JSON 传递
|
|
26
|
+
*/
|
|
27
|
+
buildRequestBody(_caps, req) {
|
|
28
|
+
const body = {};
|
|
29
|
+
if (req.rawMessages != null) {
|
|
30
|
+
body["messages"] = req.rawMessages;
|
|
31
|
+
} else if ((req.messages?.length ?? 0) > 0) {
|
|
32
|
+
body["messages"] = req.messages;
|
|
33
|
+
}
|
|
34
|
+
body["stream"] = req.stream === true;
|
|
35
|
+
if (req.max_tokens && req.max_tokens > 0) {
|
|
36
|
+
body["max_tokens"] = req.max_tokens;
|
|
37
|
+
}
|
|
38
|
+
if (req.system != null) {
|
|
39
|
+
body["system"] = req.system;
|
|
40
|
+
}
|
|
41
|
+
if (req.temperature != null) {
|
|
42
|
+
body["temperature"] = req.temperature;
|
|
43
|
+
}
|
|
44
|
+
if (req.tools != null) {
|
|
45
|
+
body["tools"] = req.tools;
|
|
46
|
+
}
|
|
47
|
+
const eff = resolveOpenAIReasoningEffort(req);
|
|
48
|
+
if (eff !== "") {
|
|
49
|
+
body["reasoning_effort"] = eff;
|
|
50
|
+
}
|
|
51
|
+
if (req.speed && req.speed !== "") {
|
|
52
|
+
body["speed"] = req.speed;
|
|
53
|
+
}
|
|
54
|
+
const rf = resolveOpenAIResponseFormat(req);
|
|
55
|
+
if (rf) {
|
|
56
|
+
body["response_format"] = rf;
|
|
57
|
+
}
|
|
58
|
+
if (req.metadata) {
|
|
59
|
+
body["metadata"] = req.metadata;
|
|
60
|
+
}
|
|
61
|
+
if (req.parallelToolCalls != null) {
|
|
62
|
+
body["parallel_tool_calls"] = req.parallelToolCalls;
|
|
63
|
+
}
|
|
64
|
+
if (req.extraBody) {
|
|
65
|
+
for (const [k, v] of Object.entries(req.extraBody)) {
|
|
66
|
+
body[k] = v;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
if (req.stream === true) {
|
|
70
|
+
body["stream_options"] = { include_usage: true };
|
|
71
|
+
}
|
|
72
|
+
return body;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* 解析 OpenAI 格式同步响应为 ChatResponse
|
|
76
|
+
* 兼容 APIResponse 包装 {"code":0,"data":{...}} 和裸 OpenAI JSON 两种格式
|
|
77
|
+
*/
|
|
78
|
+
parseResponse(bodyInput) {
|
|
79
|
+
const bodyStr = typeof bodyInput === "string" ? bodyInput : new TextDecoder().decode(bodyInput);
|
|
80
|
+
let raw = bodyStr;
|
|
81
|
+
try {
|
|
82
|
+
const wrapper = JSON.parse(bodyStr);
|
|
83
|
+
if (wrapper.data != null && wrapper.data !== null) {
|
|
84
|
+
if ((wrapper.code ?? 0) !== 0) {
|
|
85
|
+
throw new BusinessError(wrapper.code ?? 0, wrapper.message ?? "");
|
|
86
|
+
}
|
|
87
|
+
raw = JSON.stringify(wrapper.data);
|
|
88
|
+
}
|
|
89
|
+
} catch (e) {
|
|
90
|
+
if (e instanceof BusinessError) throw e;
|
|
91
|
+
}
|
|
92
|
+
let oaiResp;
|
|
93
|
+
try {
|
|
94
|
+
oaiResp = JSON.parse(raw);
|
|
95
|
+
} catch (e) {
|
|
96
|
+
throw new Error(`decode openai response: ${e instanceof Error ? e.message : String(e)}`);
|
|
97
|
+
}
|
|
98
|
+
return convertOpenAIToChatResponse(oaiResp);
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* 解析 OpenAI SSE 行
|
|
102
|
+
* [DONE] 标记流结束
|
|
103
|
+
*/
|
|
104
|
+
parseStreamLine(eventType, data) {
|
|
105
|
+
if (data === "[DONE]") {
|
|
106
|
+
return { event: { event: "", data: "" }, done: true };
|
|
107
|
+
}
|
|
108
|
+
try {
|
|
109
|
+
JSON.parse(data);
|
|
110
|
+
} catch (e) {
|
|
111
|
+
throw new Error(`parse openai stream chunk: ${e instanceof Error ? e.message : String(e)}`);
|
|
112
|
+
}
|
|
113
|
+
return { event: { event: eventType, data }, done: false };
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
function resolveOpenAIReasoningEffort(req) {
|
|
117
|
+
if (req.effort && req.effort.level !== "") {
|
|
118
|
+
switch (req.effort.level) {
|
|
119
|
+
case "low":
|
|
120
|
+
case "medium":
|
|
121
|
+
case "high":
|
|
122
|
+
return req.effort.level;
|
|
123
|
+
case "max":
|
|
124
|
+
return "high";
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
if (req.thinking) {
|
|
128
|
+
switch (req.thinking.level) {
|
|
129
|
+
case ThinkingHigh:
|
|
130
|
+
return "high";
|
|
131
|
+
case ThinkingMax:
|
|
132
|
+
return "high";
|
|
133
|
+
case ThinkingOff:
|
|
134
|
+
return "";
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return "";
|
|
138
|
+
}
|
|
139
|
+
function resolveOpenAIResponseFormat(req) {
|
|
140
|
+
if (!req.outputConfig) return null;
|
|
141
|
+
switch (req.outputConfig.format) {
|
|
142
|
+
case "json_schema": {
|
|
143
|
+
const js = {};
|
|
144
|
+
if (req.outputConfig.schema != null) {
|
|
145
|
+
js["schema"] = req.outputConfig.schema;
|
|
146
|
+
}
|
|
147
|
+
js["strict"] = true;
|
|
148
|
+
return {
|
|
149
|
+
type: "json_schema",
|
|
150
|
+
json_schema: js
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
case "json_object":
|
|
154
|
+
return { type: "json_object" };
|
|
155
|
+
case "":
|
|
156
|
+
case void 0:
|
|
157
|
+
return null;
|
|
158
|
+
default:
|
|
159
|
+
return { type: req.outputConfig.format };
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
function convertOpenAIToChatResponse(oai) {
|
|
163
|
+
const resp = {
|
|
164
|
+
id: oai.id,
|
|
165
|
+
type: "message",
|
|
166
|
+
model: oai.model,
|
|
167
|
+
role: "assistant",
|
|
168
|
+
content: [],
|
|
169
|
+
stop_reason: "",
|
|
170
|
+
usage: {
|
|
171
|
+
input_tokens: oai.usage.prompt_tokens,
|
|
172
|
+
output_tokens: oai.usage.completion_tokens
|
|
173
|
+
},
|
|
174
|
+
tokenRemaining: -1,
|
|
175
|
+
callRemaining: -1,
|
|
176
|
+
modelTokenRemaining: -1,
|
|
177
|
+
modelTokenRemainingETU: -1
|
|
178
|
+
};
|
|
179
|
+
if (oai.choices.length > 0) {
|
|
180
|
+
const choice = oai.choices[0];
|
|
181
|
+
switch (choice.finish_reason) {
|
|
182
|
+
case "stop":
|
|
183
|
+
resp.stop_reason = "end_turn";
|
|
184
|
+
break;
|
|
185
|
+
case "tool_calls":
|
|
186
|
+
resp.stop_reason = "tool_use";
|
|
187
|
+
break;
|
|
188
|
+
case "length":
|
|
189
|
+
resp.stop_reason = "max_tokens";
|
|
190
|
+
break;
|
|
191
|
+
default:
|
|
192
|
+
resp.stop_reason = choice.finish_reason;
|
|
193
|
+
}
|
|
194
|
+
if (choice.message.reasoning_content && choice.message.reasoning_content !== "") {
|
|
195
|
+
resp.content.push({
|
|
196
|
+
type: "thinking",
|
|
197
|
+
thinking: choice.message.reasoning_content
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
if (choice.message.content && choice.message.content !== "") {
|
|
201
|
+
resp.content.push({
|
|
202
|
+
type: "text",
|
|
203
|
+
text: choice.message.content
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
for (const tc of choice.message.tool_calls ?? []) {
|
|
207
|
+
resp.content.push({
|
|
208
|
+
type: "tool_use",
|
|
209
|
+
id: tc.id,
|
|
210
|
+
name: tc.function.name,
|
|
211
|
+
// Anthropic 协议 input 是 raw JSON value; OpenAI 给的 arguments 是 string,
|
|
212
|
+
// Go 侧用 json.RawMessage(arguments) 直透(原始字节). TS 我们尝试解析:
|
|
213
|
+
input: tryParseJSON(tc.function.arguments)
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
return resp;
|
|
218
|
+
}
|
|
219
|
+
function tryParseJSON(s) {
|
|
220
|
+
try {
|
|
221
|
+
return JSON.parse(s);
|
|
222
|
+
} catch {
|
|
223
|
+
return s;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
function parseOpenAIResponseToAnthropic(raw) {
|
|
227
|
+
const rawStr = typeof raw === "string" ? raw : new TextDecoder().decode(raw);
|
|
228
|
+
let data = rawStr;
|
|
229
|
+
try {
|
|
230
|
+
const wrapper = JSON.parse(rawStr);
|
|
231
|
+
if (wrapper.data != null && wrapper.data !== null) {
|
|
232
|
+
if ((wrapper.code ?? 0) !== 0) {
|
|
233
|
+
throw new BusinessError(wrapper.code ?? 0, wrapper.message ?? "");
|
|
234
|
+
}
|
|
235
|
+
data = JSON.stringify(wrapper.data);
|
|
236
|
+
}
|
|
237
|
+
} catch (e) {
|
|
238
|
+
if (e instanceof BusinessError) throw e;
|
|
239
|
+
}
|
|
240
|
+
let oaiResp;
|
|
241
|
+
try {
|
|
242
|
+
oaiResp = JSON.parse(data);
|
|
243
|
+
} catch (e) {
|
|
244
|
+
throw new Error(`decode openai response: ${e instanceof Error ? e.message : String(e)}`);
|
|
245
|
+
}
|
|
246
|
+
const resp = {
|
|
247
|
+
id: oaiResp.id,
|
|
248
|
+
type: "message",
|
|
249
|
+
role: "assistant",
|
|
250
|
+
content: [],
|
|
251
|
+
model: oaiResp.model,
|
|
252
|
+
stop_reason: "",
|
|
253
|
+
usage: {
|
|
254
|
+
input_tokens: oaiResp.usage.prompt_tokens,
|
|
255
|
+
output_tokens: oaiResp.usage.completion_tokens
|
|
256
|
+
}
|
|
257
|
+
};
|
|
258
|
+
if (oaiResp.choices.length > 0) {
|
|
259
|
+
const choice = oaiResp.choices[0];
|
|
260
|
+
switch (choice.finish_reason) {
|
|
261
|
+
case "stop":
|
|
262
|
+
resp.stop_reason = "end_turn";
|
|
263
|
+
break;
|
|
264
|
+
case "tool_calls":
|
|
265
|
+
resp.stop_reason = "tool_use";
|
|
266
|
+
break;
|
|
267
|
+
case "length":
|
|
268
|
+
resp.stop_reason = "max_tokens";
|
|
269
|
+
break;
|
|
270
|
+
default:
|
|
271
|
+
resp.stop_reason = choice.finish_reason;
|
|
272
|
+
}
|
|
273
|
+
if (choice.message.reasoning_content && choice.message.reasoning_content !== "") {
|
|
274
|
+
resp.content.push({
|
|
275
|
+
type: "thinking",
|
|
276
|
+
thinking: choice.message.reasoning_content
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
if (choice.message.content && choice.message.content !== "") {
|
|
280
|
+
resp.content.push({
|
|
281
|
+
type: "text",
|
|
282
|
+
text: choice.message.content
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
for (const tc of choice.message.tool_calls ?? []) {
|
|
286
|
+
resp.content.push({
|
|
287
|
+
type: "tool_use",
|
|
288
|
+
id: tc.id,
|
|
289
|
+
name: tc.function.name,
|
|
290
|
+
input: tryParseJSON(tc.function.arguments)
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
return resp;
|
|
295
|
+
}
|
|
296
|
+
var OpenAIStreamConverter = class {
|
|
297
|
+
messageStarted = false;
|
|
298
|
+
thinkingStarted = false;
|
|
299
|
+
thinkingStopped = false;
|
|
300
|
+
textStarted = false;
|
|
301
|
+
/** OpenAI tool_call index → Anthropic block index */
|
|
302
|
+
toolBlockIndex = /* @__PURE__ */ new Map();
|
|
303
|
+
blockIndex = 0;
|
|
304
|
+
/**
|
|
305
|
+
* 将一行 OpenAI SSE data 转换为零或多个 Anthropic 格式 StreamEvent
|
|
306
|
+
* 返回 { events, done }
|
|
307
|
+
*/
|
|
308
|
+
convert(data) {
|
|
309
|
+
if (data === "[DONE]") {
|
|
310
|
+
return { events: [], done: true };
|
|
311
|
+
}
|
|
312
|
+
let chunk;
|
|
313
|
+
try {
|
|
314
|
+
chunk = JSON.parse(data);
|
|
315
|
+
} catch (e) {
|
|
316
|
+
throw new Error(`parse openai stream chunk: ${e instanceof Error ? e.message : String(e)}`);
|
|
317
|
+
}
|
|
318
|
+
const events = [];
|
|
319
|
+
if (chunk.choices.length === 0) {
|
|
320
|
+
return { events, done: false };
|
|
321
|
+
}
|
|
322
|
+
const choice = chunk.choices[0];
|
|
323
|
+
if (!this.messageStarted) {
|
|
324
|
+
this.messageStarted = true;
|
|
325
|
+
const msgJSON = JSON.stringify({
|
|
326
|
+
type: "message_start",
|
|
327
|
+
message: {
|
|
328
|
+
id: chunk.id,
|
|
329
|
+
type: "message",
|
|
330
|
+
role: "assistant",
|
|
331
|
+
content: [],
|
|
332
|
+
model: ""
|
|
333
|
+
}
|
|
334
|
+
});
|
|
335
|
+
events.push({ event: "message_start", data: msgJSON });
|
|
336
|
+
}
|
|
337
|
+
if (choice.delta.reasoning_content && choice.delta.reasoning_content !== "") {
|
|
338
|
+
if (!this.thinkingStarted) {
|
|
339
|
+
this.thinkingStarted = true;
|
|
340
|
+
const blockJSON = JSON.stringify({
|
|
341
|
+
type: "content_block_start",
|
|
342
|
+
index: this.blockIndex,
|
|
343
|
+
content_block: { type: "thinking", thinking: "" }
|
|
344
|
+
});
|
|
345
|
+
events.push({ event: "content_block_start", data: blockJSON });
|
|
346
|
+
}
|
|
347
|
+
const deltaJSON = JSON.stringify({
|
|
348
|
+
type: "content_block_delta",
|
|
349
|
+
index: this.blockIndex,
|
|
350
|
+
delta: { type: "thinking_delta", thinking: choice.delta.reasoning_content }
|
|
351
|
+
});
|
|
352
|
+
events.push({ event: "content_block_delta", data: deltaJSON });
|
|
353
|
+
}
|
|
354
|
+
if (choice.delta.content && choice.delta.content !== "") {
|
|
355
|
+
if (this.thinkingStarted && !this.thinkingStopped) {
|
|
356
|
+
this.thinkingStopped = true;
|
|
357
|
+
const stopJSON = JSON.stringify({
|
|
358
|
+
type: "content_block_stop",
|
|
359
|
+
index: this.blockIndex
|
|
360
|
+
});
|
|
361
|
+
events.push({ event: "content_block_stop", data: stopJSON });
|
|
362
|
+
this.blockIndex++;
|
|
363
|
+
}
|
|
364
|
+
if (!this.textStarted) {
|
|
365
|
+
this.textStarted = true;
|
|
366
|
+
const blockJSON = JSON.stringify({
|
|
367
|
+
type: "content_block_start",
|
|
368
|
+
index: this.blockIndex,
|
|
369
|
+
content_block: { type: "text", text: "" }
|
|
370
|
+
});
|
|
371
|
+
events.push({ event: "content_block_start", data: blockJSON });
|
|
372
|
+
}
|
|
373
|
+
const deltaJSON = JSON.stringify({
|
|
374
|
+
type: "content_block_delta",
|
|
375
|
+
index: this.blockIndex,
|
|
376
|
+
delta: { type: "text_delta", text: choice.delta.content }
|
|
377
|
+
});
|
|
378
|
+
events.push({ event: "content_block_delta", data: deltaJSON });
|
|
379
|
+
}
|
|
380
|
+
for (const tc of choice.delta.tool_calls ?? []) {
|
|
381
|
+
if (!this.toolBlockIndex.has(tc.index)) {
|
|
382
|
+
if (this.textStarted) {
|
|
383
|
+
const stopJSON = JSON.stringify({
|
|
384
|
+
type: "content_block_stop",
|
|
385
|
+
index: this.blockIndex
|
|
386
|
+
});
|
|
387
|
+
events.push({ event: "content_block_stop", data: stopJSON });
|
|
388
|
+
this.blockIndex++;
|
|
389
|
+
this.textStarted = false;
|
|
390
|
+
}
|
|
391
|
+
this.toolBlockIndex.set(tc.index, this.blockIndex);
|
|
392
|
+
const blockJSON = JSON.stringify({
|
|
393
|
+
type: "content_block_start",
|
|
394
|
+
index: this.blockIndex,
|
|
395
|
+
content_block: {
|
|
396
|
+
type: "tool_use",
|
|
397
|
+
id: tc.id,
|
|
398
|
+
name: tc.function.name,
|
|
399
|
+
input: {}
|
|
400
|
+
}
|
|
401
|
+
});
|
|
402
|
+
events.push({ event: "content_block_start", data: blockJSON });
|
|
403
|
+
this.blockIndex++;
|
|
404
|
+
}
|
|
405
|
+
if (tc.function.arguments && tc.function.arguments !== "") {
|
|
406
|
+
const idx = this.toolBlockIndex.get(tc.index);
|
|
407
|
+
const deltaJSON = JSON.stringify({
|
|
408
|
+
type: "content_block_delta",
|
|
409
|
+
index: idx,
|
|
410
|
+
delta: {
|
|
411
|
+
type: "input_json_delta",
|
|
412
|
+
partial_json: tc.function.arguments
|
|
413
|
+
}
|
|
414
|
+
});
|
|
415
|
+
events.push({ event: "content_block_delta", data: deltaJSON });
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
if (choice.finish_reason != null && choice.finish_reason !== "") {
|
|
419
|
+
if (this.textStarted) {
|
|
420
|
+
const stopJSON2 = JSON.stringify({
|
|
421
|
+
type: "content_block_stop",
|
|
422
|
+
index: this.blockIndex
|
|
423
|
+
});
|
|
424
|
+
events.push({ event: "content_block_stop", data: stopJSON2 });
|
|
425
|
+
} else if (this.thinkingStarted && !this.thinkingStopped) {
|
|
426
|
+
const stopJSON2 = JSON.stringify({
|
|
427
|
+
type: "content_block_stop",
|
|
428
|
+
index: this.blockIndex
|
|
429
|
+
});
|
|
430
|
+
events.push({ event: "content_block_stop", data: stopJSON2 });
|
|
431
|
+
}
|
|
432
|
+
for (const idx of this.toolBlockIndex.values()) {
|
|
433
|
+
const stopJSON2 = JSON.stringify({
|
|
434
|
+
type: "content_block_stop",
|
|
435
|
+
index: idx
|
|
436
|
+
});
|
|
437
|
+
events.push({ event: "content_block_stop", data: stopJSON2 });
|
|
438
|
+
}
|
|
439
|
+
let stopReason = "end_turn";
|
|
440
|
+
switch (choice.finish_reason) {
|
|
441
|
+
case "tool_calls":
|
|
442
|
+
stopReason = "tool_use";
|
|
443
|
+
break;
|
|
444
|
+
case "length":
|
|
445
|
+
stopReason = "max_tokens";
|
|
446
|
+
break;
|
|
447
|
+
}
|
|
448
|
+
const deltaJSON = JSON.stringify({
|
|
449
|
+
type: "message_delta",
|
|
450
|
+
delta: { stop_reason: stopReason }
|
|
451
|
+
});
|
|
452
|
+
events.push({ event: "message_delta", data: deltaJSON });
|
|
453
|
+
const stopJSON = JSON.stringify({ type: "message_stop" });
|
|
454
|
+
events.push({ event: "message_stop", data: stopJSON });
|
|
455
|
+
}
|
|
456
|
+
return { events, done: false };
|
|
457
|
+
}
|
|
458
|
+
};
|
|
459
|
+
function newOpenAIStreamConverter() {
|
|
460
|
+
return new OpenAIStreamConverter();
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
export { OpenAIAdapter, OpenAIStreamConverter, newOpenAIStreamConverter, parseOpenAIResponseToAnthropic, resolveOpenAIReasoningEffort, resolveOpenAIResponseFormat };
|
|
464
|
+
//# sourceMappingURL=openai.js.map
|
|
465
|
+
//# sourceMappingURL=openai.js.map
|