@prestyj/ai 4.3.15 → 4.3.54
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/README.md +2 -2
- package/dist/index.cjs +363 -22
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +14 -3
- package/dist/index.d.ts +14 -3
- package/dist/index.js +363 -22
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/index.d.cts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
|
|
3
|
-
type Provider = "anthropic" | "xiaomi" | "openai" | "glm" | "moonshot" | "minimax" | "palsu";
|
|
3
|
+
type Provider = "anthropic" | "xiaomi" | "openai" | "glm" | "moonshot" | "minimax" | "openrouter" | "palsu";
|
|
4
4
|
type ThinkingLevel = "low" | "medium" | "high" | "max";
|
|
5
5
|
type CacheRetention = "none" | "short" | "long";
|
|
6
6
|
interface TextContent {
|
|
@@ -23,10 +23,11 @@ interface ToolCall {
|
|
|
23
23
|
name: string;
|
|
24
24
|
args: Record<string, unknown>;
|
|
25
25
|
}
|
|
26
|
+
type ToolResultContent = string | (TextContent | ImageContent)[];
|
|
26
27
|
interface ToolResult {
|
|
27
28
|
type: "tool_result";
|
|
28
29
|
toolCallId: string;
|
|
29
|
-
content:
|
|
30
|
+
content: ToolResultContent;
|
|
30
31
|
isError?: boolean;
|
|
31
32
|
}
|
|
32
33
|
interface ServerToolCall {
|
|
@@ -174,6 +175,16 @@ interface StreamOptions {
|
|
|
174
175
|
* where the default `globalThis.fetch` doesn't support streaming properly.
|
|
175
176
|
* Passed directly to the underlying provider SDK. */
|
|
176
177
|
fetch?: typeof globalThis.fetch;
|
|
178
|
+
/** Whether the target model supports image input. When false, image content
|
|
179
|
+
* in user messages and tool_result messages is downgraded to a text placeholder
|
|
180
|
+
* before being sent to the provider. Default: true. */
|
|
181
|
+
supportsImages?: boolean;
|
|
182
|
+
/** Use streaming transport (default: true). When false, providers issue a
|
|
183
|
+
* single non-streaming request and synthesize events from the full response.
|
|
184
|
+
* The agent loop flips this to `false` as a fallback after repeated stream
|
|
185
|
+
* stalls — broken SSE connections (transient CDN / proxy issues) often
|
|
186
|
+
* recover when the same request is issued over a plain HTTP request/response. */
|
|
187
|
+
streaming?: boolean;
|
|
177
188
|
}
|
|
178
189
|
|
|
179
190
|
/**
|
|
@@ -362,4 +373,4 @@ interface PalsuProviderConfig {
|
|
|
362
373
|
*/
|
|
363
374
|
declare function registerPalsuProvider(config?: PalsuProviderConfig): PalsuProviderHandle;
|
|
364
375
|
|
|
365
|
-
export { type AssistantMessage, type CacheRetention, type ContentPart, type DoneEvent, EZCoderAIError, type ErrorEvent, EventStream, type ImageContent, type Message, type PalsuModelConfig, type PalsuModelHandle, type PalsuProviderConfig, type PalsuProviderHandle, type PalsuProviderState, type PalsuResponse, type PalsuResponseFactory, type Provider, type ProviderEntry, ProviderError, type ProviderStreamFn, type RawContent, type ServerToolCall, type ServerToolCallEvent, type ServerToolDefinition, type ServerToolResult, type ServerToolResultEvent, type StopReason, type StreamEvent, type StreamOptions, type StreamResponse, StreamResult, type SystemMessage, type TextContent, type TextDeltaEvent, type ThinkingContent, type ThinkingDeltaEvent, type ThinkingLevel, type Tool, type ToolCall, type ToolCallDeltaEvent, type ToolCallDoneEvent, type ToolChoice, type ToolResult, type ToolResultMessage, type Usage, type UserMessage, palsuAssistantMessage, palsuText, palsuThinking, palsuToolCall, providerRegistry, registerPalsuProvider, stream };
|
|
376
|
+
export { type AssistantMessage, type CacheRetention, type ContentPart, type DoneEvent, EZCoderAIError, type ErrorEvent, EventStream, type ImageContent, type Message, type PalsuModelConfig, type PalsuModelHandle, type PalsuProviderConfig, type PalsuProviderHandle, type PalsuProviderState, type PalsuResponse, type PalsuResponseFactory, type Provider, type ProviderEntry, ProviderError, type ProviderStreamFn, type RawContent, type ServerToolCall, type ServerToolCallEvent, type ServerToolDefinition, type ServerToolResult, type ServerToolResultEvent, type StopReason, type StreamEvent, type StreamOptions, type StreamResponse, StreamResult, type SystemMessage, type TextContent, type TextDeltaEvent, type ThinkingContent, type ThinkingDeltaEvent, type ThinkingLevel, type Tool, type ToolCall, type ToolCallDeltaEvent, type ToolCallDoneEvent, type ToolChoice, type ToolResult, type ToolResultContent, type ToolResultMessage, type Usage, type UserMessage, palsuAssistantMessage, palsuText, palsuThinking, palsuToolCall, providerRegistry, registerPalsuProvider, stream };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
|
|
3
|
-
type Provider = "anthropic" | "xiaomi" | "openai" | "glm" | "moonshot" | "minimax" | "palsu";
|
|
3
|
+
type Provider = "anthropic" | "xiaomi" | "openai" | "glm" | "moonshot" | "minimax" | "openrouter" | "palsu";
|
|
4
4
|
type ThinkingLevel = "low" | "medium" | "high" | "max";
|
|
5
5
|
type CacheRetention = "none" | "short" | "long";
|
|
6
6
|
interface TextContent {
|
|
@@ -23,10 +23,11 @@ interface ToolCall {
|
|
|
23
23
|
name: string;
|
|
24
24
|
args: Record<string, unknown>;
|
|
25
25
|
}
|
|
26
|
+
type ToolResultContent = string | (TextContent | ImageContent)[];
|
|
26
27
|
interface ToolResult {
|
|
27
28
|
type: "tool_result";
|
|
28
29
|
toolCallId: string;
|
|
29
|
-
content:
|
|
30
|
+
content: ToolResultContent;
|
|
30
31
|
isError?: boolean;
|
|
31
32
|
}
|
|
32
33
|
interface ServerToolCall {
|
|
@@ -174,6 +175,16 @@ interface StreamOptions {
|
|
|
174
175
|
* where the default `globalThis.fetch` doesn't support streaming properly.
|
|
175
176
|
* Passed directly to the underlying provider SDK. */
|
|
176
177
|
fetch?: typeof globalThis.fetch;
|
|
178
|
+
/** Whether the target model supports image input. When false, image content
|
|
179
|
+
* in user messages and tool_result messages is downgraded to a text placeholder
|
|
180
|
+
* before being sent to the provider. Default: true. */
|
|
181
|
+
supportsImages?: boolean;
|
|
182
|
+
/** Use streaming transport (default: true). When false, providers issue a
|
|
183
|
+
* single non-streaming request and synthesize events from the full response.
|
|
184
|
+
* The agent loop flips this to `false` as a fallback after repeated stream
|
|
185
|
+
* stalls — broken SSE connections (transient CDN / proxy issues) often
|
|
186
|
+
* recover when the same request is issued over a plain HTTP request/response. */
|
|
187
|
+
streaming?: boolean;
|
|
177
188
|
}
|
|
178
189
|
|
|
179
190
|
/**
|
|
@@ -362,4 +373,4 @@ interface PalsuProviderConfig {
|
|
|
362
373
|
*/
|
|
363
374
|
declare function registerPalsuProvider(config?: PalsuProviderConfig): PalsuProviderHandle;
|
|
364
375
|
|
|
365
|
-
export { type AssistantMessage, type CacheRetention, type ContentPart, type DoneEvent, EZCoderAIError, type ErrorEvent, EventStream, type ImageContent, type Message, type PalsuModelConfig, type PalsuModelHandle, type PalsuProviderConfig, type PalsuProviderHandle, type PalsuProviderState, type PalsuResponse, type PalsuResponseFactory, type Provider, type ProviderEntry, ProviderError, type ProviderStreamFn, type RawContent, type ServerToolCall, type ServerToolCallEvent, type ServerToolDefinition, type ServerToolResult, type ServerToolResultEvent, type StopReason, type StreamEvent, type StreamOptions, type StreamResponse, StreamResult, type SystemMessage, type TextContent, type TextDeltaEvent, type ThinkingContent, type ThinkingDeltaEvent, type ThinkingLevel, type Tool, type ToolCall, type ToolCallDeltaEvent, type ToolCallDoneEvent, type ToolChoice, type ToolResult, type ToolResultMessage, type Usage, type UserMessage, palsuAssistantMessage, palsuText, palsuThinking, palsuToolCall, providerRegistry, registerPalsuProvider, stream };
|
|
376
|
+
export { type AssistantMessage, type CacheRetention, type ContentPart, type DoneEvent, EZCoderAIError, type ErrorEvent, EventStream, type ImageContent, type Message, type PalsuModelConfig, type PalsuModelHandle, type PalsuProviderConfig, type PalsuProviderHandle, type PalsuProviderState, type PalsuResponse, type PalsuResponseFactory, type Provider, type ProviderEntry, ProviderError, type ProviderStreamFn, type RawContent, type ServerToolCall, type ServerToolCallEvent, type ServerToolDefinition, type ServerToolResult, type ServerToolResultEvent, type StopReason, type StreamEvent, type StreamOptions, type StreamResponse, StreamResult, type SystemMessage, type TextContent, type TextDeltaEvent, type ThinkingContent, type ThinkingDeltaEvent, type ThinkingLevel, type Tool, type ToolCall, type ToolCallDeltaEvent, type ToolCallDoneEvent, type ToolChoice, type ToolResult, type ToolResultContent, type ToolResultMessage, type Usage, type UserMessage, palsuAssistantMessage, palsuText, palsuThinking, palsuToolCall, providerRegistry, registerPalsuProvider, stream };
|
package/dist/index.js
CHANGED
|
@@ -128,12 +128,70 @@ function zodToJsonSchema(schema) {
|
|
|
128
128
|
}
|
|
129
129
|
|
|
130
130
|
// src/providers/transform.ts
|
|
131
|
+
var NON_VISION_USER_IMAGE_PLACEHOLDER = "(image omitted: model does not support images)";
|
|
132
|
+
var NON_VISION_TOOL_IMAGE_PLACEHOLDER = "(tool image omitted: model does not support images)";
|
|
133
|
+
function stripImages(content, placeholder) {
|
|
134
|
+
const out = [];
|
|
135
|
+
let lastWasPlaceholder = false;
|
|
136
|
+
for (const block of content) {
|
|
137
|
+
if (block.type === "image") {
|
|
138
|
+
if (!lastWasPlaceholder) out.push({ type: "text", text: placeholder });
|
|
139
|
+
lastWasPlaceholder = true;
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
out.push(block);
|
|
143
|
+
lastWasPlaceholder = block.text === placeholder;
|
|
144
|
+
}
|
|
145
|
+
return out;
|
|
146
|
+
}
|
|
147
|
+
function downgradeUnsupportedImages(messages, supportsImages) {
|
|
148
|
+
if (supportsImages !== false) return messages;
|
|
149
|
+
return messages.map((msg) => {
|
|
150
|
+
if (msg.role === "user" && Array.isArray(msg.content)) {
|
|
151
|
+
return { ...msg, content: stripImages(msg.content, NON_VISION_USER_IMAGE_PLACEHOLDER) };
|
|
152
|
+
}
|
|
153
|
+
if (msg.role === "tool") {
|
|
154
|
+
return {
|
|
155
|
+
...msg,
|
|
156
|
+
content: msg.content.map(
|
|
157
|
+
(tr) => Array.isArray(tr.content) ? {
|
|
158
|
+
...tr,
|
|
159
|
+
content: stripImages(tr.content, NON_VISION_TOOL_IMAGE_PLACEHOLDER)
|
|
160
|
+
} : tr
|
|
161
|
+
)
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
return msg;
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
function toolResultText(content) {
|
|
168
|
+
if (typeof content === "string") return content;
|
|
169
|
+
return content.filter((b) => b.type === "text").map((b) => b.text).join("\n");
|
|
170
|
+
}
|
|
171
|
+
function toolResultImages(content) {
|
|
172
|
+
if (typeof content === "string") return [];
|
|
173
|
+
return content.filter((b) => b.type === "image");
|
|
174
|
+
}
|
|
131
175
|
function toAnthropicCacheControl(retention, baseUrl) {
|
|
132
176
|
const resolved = retention ?? "short";
|
|
133
177
|
if (resolved === "none") return void 0;
|
|
134
178
|
const ttl = resolved === "long" && (!baseUrl || baseUrl.includes("api.anthropic.com")) ? "1h" : void 0;
|
|
135
179
|
return { type: "ephemeral", ...ttl && { ttl } };
|
|
136
180
|
}
|
|
181
|
+
function toAnthropicToolResultContent(content) {
|
|
182
|
+
if (typeof content === "string") return content;
|
|
183
|
+
return content.map((block) => {
|
|
184
|
+
if (block.type === "text") return { type: "text", text: block.text };
|
|
185
|
+
return {
|
|
186
|
+
type: "image",
|
|
187
|
+
source: {
|
|
188
|
+
type: "base64",
|
|
189
|
+
media_type: block.mediaType,
|
|
190
|
+
data: block.data
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
});
|
|
194
|
+
}
|
|
137
195
|
function toAnthropicMessages(messages, cacheControl) {
|
|
138
196
|
let systemText;
|
|
139
197
|
const out = [];
|
|
@@ -197,7 +255,7 @@ function toAnthropicMessages(messages, cacheControl) {
|
|
|
197
255
|
content: msg.content.map((result) => ({
|
|
198
256
|
type: "tool_result",
|
|
199
257
|
tool_use_id: result.toolCallId,
|
|
200
|
-
content: result.content,
|
|
258
|
+
content: toAnthropicToolResultContent(result.content),
|
|
201
259
|
is_error: result.isError
|
|
202
260
|
}))
|
|
203
261
|
});
|
|
@@ -266,7 +324,7 @@ function toAnthropicToolChoice(choice) {
|
|
|
266
324
|
return { type: "tool", name: choice.name };
|
|
267
325
|
}
|
|
268
326
|
function supportsAdaptiveThinking(model) {
|
|
269
|
-
return /opus-4-6|sonnet-4-6/.test(model);
|
|
327
|
+
return /opus-4-7|opus-4-6|sonnet-4-6/.test(model);
|
|
270
328
|
}
|
|
271
329
|
function toAnthropicThinking(level, maxTokens, model) {
|
|
272
330
|
if (supportsAdaptiveThinking(model)) {
|
|
@@ -293,10 +351,10 @@ function toAnthropicThinking(level, maxTokens, model) {
|
|
|
293
351
|
};
|
|
294
352
|
}
|
|
295
353
|
function remapToolCallId(id, idMap) {
|
|
296
|
-
if (id.startsWith("
|
|
354
|
+
if (!id.startsWith("toolu_")) return id;
|
|
297
355
|
const existing = idMap.get(id);
|
|
298
356
|
if (existing) return existing;
|
|
299
|
-
const mapped = `call_${id.
|
|
357
|
+
const mapped = `call_${id.slice(5)}`;
|
|
300
358
|
idMap.set(id, mapped);
|
|
301
359
|
return mapped;
|
|
302
360
|
}
|
|
@@ -361,16 +419,36 @@ function toOpenAIMessages(messages, options) {
|
|
|
361
419
|
};
|
|
362
420
|
if (thinkingParts) {
|
|
363
421
|
assistantMsg.reasoning_content = thinkingParts;
|
|
422
|
+
} else if (options?.thinking && hasToolCalls && options.provider !== "glm") {
|
|
423
|
+
assistantMsg.reasoning_content = " ";
|
|
364
424
|
}
|
|
365
425
|
out.push(assistantMsg);
|
|
366
426
|
continue;
|
|
367
427
|
}
|
|
368
428
|
if (msg.role === "tool") {
|
|
429
|
+
const imageBlocks = [];
|
|
369
430
|
for (const result of msg.content) {
|
|
431
|
+
const text = toolResultText(result.content);
|
|
432
|
+
const images = toolResultImages(result.content);
|
|
433
|
+
const hasText = text.length > 0;
|
|
370
434
|
out.push({
|
|
371
435
|
role: "tool",
|
|
372
436
|
tool_call_id: remapToolCallId(result.toolCallId, idMap),
|
|
373
|
-
content:
|
|
437
|
+
content: hasText ? text : "(see attached image)"
|
|
438
|
+
});
|
|
439
|
+
if (images.length > 0 && options?.supportsImages !== false) {
|
|
440
|
+
for (const img of images) {
|
|
441
|
+
imageBlocks.push({
|
|
442
|
+
type: "image_url",
|
|
443
|
+
image_url: { url: `data:${img.mediaType};base64,${img.data}` }
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
if (imageBlocks.length > 0) {
|
|
449
|
+
out.push({
|
|
450
|
+
role: "user",
|
|
451
|
+
content: [{ type: "text", text: "Attached image(s) from tool result:" }, ...imageBlocks]
|
|
374
452
|
});
|
|
375
453
|
}
|
|
376
454
|
}
|
|
@@ -450,8 +528,10 @@ function streamAnthropic(options) {
|
|
|
450
528
|
async function* runStream(options) {
|
|
451
529
|
const client = createClient(options);
|
|
452
530
|
const isOAuth = options.apiKey?.startsWith("sk-ant-oat");
|
|
531
|
+
const useStreaming = options.streaming !== false;
|
|
453
532
|
const cacheControl = toAnthropicCacheControl(options.cacheRetention, options.baseUrl);
|
|
454
|
-
const
|
|
533
|
+
const downgradedMessages = downgradeUnsupportedImages(options.messages, options.supportsImages);
|
|
534
|
+
const { system: rawSystem, messages } = toAnthropicMessages(downgradedMessages, cacheControl);
|
|
455
535
|
const system = isOAuth ? [
|
|
456
536
|
{
|
|
457
537
|
type: "text",
|
|
@@ -495,9 +575,9 @@ async function* runStream(options) {
|
|
|
495
575
|
];
|
|
496
576
|
return contextEdits.length ? { context_management: { edits: contextEdits } } : {};
|
|
497
577
|
})(),
|
|
498
|
-
stream:
|
|
578
|
+
stream: useStreaming
|
|
499
579
|
};
|
|
500
|
-
const hasAdaptiveThinking = options.model.includes("opus-4-6") || options.model.includes("opus-4.6") || options.model.includes("sonnet-4-6") || options.model.includes("sonnet-4.6");
|
|
580
|
+
const hasAdaptiveThinking = options.model.includes("opus-4-7") || options.model.includes("opus-4.7") || options.model.includes("opus-4-6") || options.model.includes("opus-4.6") || options.model.includes("sonnet-4-6") || options.model.includes("sonnet-4.6");
|
|
501
581
|
const betaHeaders = [
|
|
502
582
|
...isOAuth ? ["claude-code-20250219", "oauth-2025-04-20"] : [],
|
|
503
583
|
...options.compaction ? ["compact-2026-01-12"] : [],
|
|
@@ -505,10 +585,23 @@ async function* runStream(options) {
|
|
|
505
585
|
"fine-grained-tool-streaming-2025-05-14",
|
|
506
586
|
...!hasAdaptiveThinking ? ["interleaved-thinking-2025-05-14"] : []
|
|
507
587
|
];
|
|
508
|
-
const
|
|
588
|
+
const requestOptions = {
|
|
509
589
|
signal: options.signal ?? void 0,
|
|
510
590
|
...betaHeaders.length ? { headers: { "anthropic-beta": betaHeaders.join(",") } } : {}
|
|
511
|
-
}
|
|
591
|
+
};
|
|
592
|
+
if (!useStreaming) {
|
|
593
|
+
try {
|
|
594
|
+
const message = await client.messages.create(
|
|
595
|
+
{ ...params, stream: false },
|
|
596
|
+
requestOptions
|
|
597
|
+
);
|
|
598
|
+
yield* synthesizeEventsFromMessage(message);
|
|
599
|
+
return messageToResponse(message);
|
|
600
|
+
} catch (err) {
|
|
601
|
+
throw toError(err);
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
const stream2 = client.messages.stream(params, requestOptions);
|
|
512
605
|
const contentParts = [];
|
|
513
606
|
const blocks = /* @__PURE__ */ new Map();
|
|
514
607
|
let inputTokens = 0;
|
|
@@ -701,6 +794,105 @@ async function* runStream(options) {
|
|
|
701
794
|
yield { type: "done", stopReason: normalizedStop };
|
|
702
795
|
return response;
|
|
703
796
|
}
|
|
797
|
+
function* synthesizeEventsFromMessage(message) {
|
|
798
|
+
for (const block of message.content) {
|
|
799
|
+
const blk = block;
|
|
800
|
+
const type = blk.type;
|
|
801
|
+
if (type === "text") {
|
|
802
|
+
const text = blk.text;
|
|
803
|
+
if (text) yield { type: "text_delta", text };
|
|
804
|
+
} else if (type === "thinking") {
|
|
805
|
+
const text = blk.thinking;
|
|
806
|
+
if (text) yield { type: "thinking_delta", text };
|
|
807
|
+
} else if (type === "tool_use") {
|
|
808
|
+
const argsJson = JSON.stringify(blk.input ?? {});
|
|
809
|
+
yield {
|
|
810
|
+
type: "toolcall_delta",
|
|
811
|
+
id: blk.id,
|
|
812
|
+
name: blk.name,
|
|
813
|
+
argsJson
|
|
814
|
+
};
|
|
815
|
+
yield {
|
|
816
|
+
type: "toolcall_done",
|
|
817
|
+
id: blk.id,
|
|
818
|
+
name: blk.name,
|
|
819
|
+
args: blk.input ?? {}
|
|
820
|
+
};
|
|
821
|
+
} else if (type === "server_tool_use") {
|
|
822
|
+
yield {
|
|
823
|
+
type: "server_toolcall",
|
|
824
|
+
id: blk.id,
|
|
825
|
+
name: blk.name,
|
|
826
|
+
input: blk.input
|
|
827
|
+
};
|
|
828
|
+
} else if (type === "web_search_tool_result") {
|
|
829
|
+
yield {
|
|
830
|
+
type: "server_toolresult",
|
|
831
|
+
toolUseId: blk.tool_use_id,
|
|
832
|
+
resultType: type,
|
|
833
|
+
data: blk
|
|
834
|
+
};
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
yield { type: "done", stopReason: normalizeAnthropicStopReason(message.stop_reason) };
|
|
838
|
+
}
|
|
839
|
+
function messageToResponse(message) {
|
|
840
|
+
const contentParts = [];
|
|
841
|
+
for (const block of message.content) {
|
|
842
|
+
const blk = block;
|
|
843
|
+
const type = blk.type;
|
|
844
|
+
if (type === "text") {
|
|
845
|
+
contentParts.push({ type: "text", text: blk.text });
|
|
846
|
+
} else if (type === "thinking") {
|
|
847
|
+
contentParts.push({
|
|
848
|
+
type: "thinking",
|
|
849
|
+
text: blk.thinking,
|
|
850
|
+
signature: blk.signature ?? ""
|
|
851
|
+
});
|
|
852
|
+
} else if (type === "tool_use") {
|
|
853
|
+
contentParts.push({
|
|
854
|
+
type: "tool_call",
|
|
855
|
+
id: blk.id,
|
|
856
|
+
name: blk.name,
|
|
857
|
+
args: blk.input ?? {}
|
|
858
|
+
});
|
|
859
|
+
} else if (type === "server_tool_use") {
|
|
860
|
+
contentParts.push({
|
|
861
|
+
type: "server_tool_call",
|
|
862
|
+
id: blk.id,
|
|
863
|
+
name: blk.name,
|
|
864
|
+
input: blk.input
|
|
865
|
+
});
|
|
866
|
+
} else if (type === "web_search_tool_result") {
|
|
867
|
+
contentParts.push({
|
|
868
|
+
type: "server_tool_result",
|
|
869
|
+
toolUseId: blk.tool_use_id,
|
|
870
|
+
resultType: type,
|
|
871
|
+
data: blk
|
|
872
|
+
});
|
|
873
|
+
} else {
|
|
874
|
+
contentParts.push({ type: "raw", data: blk });
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
const usage = message.usage;
|
|
878
|
+
const inputTokens = usage.input_tokens ?? 0;
|
|
879
|
+
const outputTokens = usage.output_tokens ?? 0;
|
|
880
|
+
const cacheRead = usage.cache_read_input_tokens;
|
|
881
|
+
const cacheWrite = usage.cache_creation_input_tokens;
|
|
882
|
+
return {
|
|
883
|
+
message: {
|
|
884
|
+
role: "assistant",
|
|
885
|
+
content: contentParts.length > 0 ? contentParts : ""
|
|
886
|
+
},
|
|
887
|
+
stopReason: normalizeAnthropicStopReason(message.stop_reason),
|
|
888
|
+
usage: {
|
|
889
|
+
inputTokens,
|
|
890
|
+
outputTokens,
|
|
891
|
+
...cacheRead != null && { cacheRead },
|
|
892
|
+
...cacheWrite != null && { cacheWrite }
|
|
893
|
+
}
|
|
894
|
+
};
|
|
895
|
+
}
|
|
704
896
|
function toError(err) {
|
|
705
897
|
if (err instanceof Anthropic.APIError) {
|
|
706
898
|
return new ProviderError("anthropic", err.message, {
|
|
@@ -728,15 +920,21 @@ function streamOpenAI(options) {
|
|
|
728
920
|
}
|
|
729
921
|
async function* runStream2(options) {
|
|
730
922
|
const providerName = options.provider ?? "openai";
|
|
923
|
+
const useStreaming = options.streaming !== false;
|
|
731
924
|
const client = createClient2(options);
|
|
732
925
|
const usesThinkingParam = options.provider === "glm" || options.provider === "moonshot" || options.provider === "xiaomi";
|
|
733
|
-
const
|
|
926
|
+
const downgradedMessages = downgradeUnsupportedImages(options.messages, options.supportsImages);
|
|
927
|
+
const messages = toOpenAIMessages(downgradedMessages, {
|
|
928
|
+
provider: options.provider,
|
|
929
|
+
thinking: !!options.thinking,
|
|
930
|
+
supportsImages: options.supportsImages
|
|
931
|
+
});
|
|
734
932
|
const defaultTemp = options.provider === "glm" ? 0.6 : void 0;
|
|
735
933
|
const effectiveTemp = options.temperature ?? defaultTemp;
|
|
736
934
|
const params = {
|
|
737
935
|
model: options.model,
|
|
738
936
|
messages,
|
|
739
|
-
stream:
|
|
937
|
+
stream: useStreaming,
|
|
740
938
|
...options.maxTokens ? { max_completion_tokens: options.maxTokens } : {},
|
|
741
939
|
...effectiveTemp != null && !options.thinking ? { temperature: effectiveTemp } : {},
|
|
742
940
|
...options.topP != null ? { top_p: options.topP } : {},
|
|
@@ -744,14 +942,14 @@ async function* runStream2(options) {
|
|
|
744
942
|
...options.thinking && !usesThinkingParam ? { reasoning_effort: toOpenAIReasoningEffort(options.thinking) } : {},
|
|
745
943
|
...options.tools?.length ? { tools: toOpenAITools(options.tools) } : {},
|
|
746
944
|
...options.toolChoice && options.tools?.length ? { tool_choice: toOpenAIToolChoice(options.toolChoice) } : {},
|
|
747
|
-
stream_options: { include_usage: true }
|
|
945
|
+
...useStreaming ? { stream_options: { include_usage: true } } : {}
|
|
748
946
|
};
|
|
749
|
-
if (options.
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
947
|
+
if (options.provider === "openai" || options.provider === "moonshot") {
|
|
948
|
+
const paramsAny = params;
|
|
949
|
+
paramsAny.prompt_cache_key = "ezcoder";
|
|
950
|
+
const retention = options.cacheRetention ?? "short";
|
|
951
|
+
if (retention === "long") {
|
|
952
|
+
paramsAny.prompt_cache_retention = "24h";
|
|
755
953
|
}
|
|
756
954
|
}
|
|
757
955
|
if (usesThinkingParam) {
|
|
@@ -772,6 +970,17 @@ async function* runStream2(options) {
|
|
|
772
970
|
`
|
|
773
971
|
);
|
|
774
972
|
}
|
|
973
|
+
if (!useStreaming) {
|
|
974
|
+
try {
|
|
975
|
+
const completion = await client.chat.completions.create(params, {
|
|
976
|
+
signal: options.signal ?? void 0
|
|
977
|
+
});
|
|
978
|
+
yield* synthesizeEventsFromCompletion(completion, !!options.thinking);
|
|
979
|
+
return completionToResponse(completion);
|
|
980
|
+
} catch (err) {
|
|
981
|
+
throw toError2(err, providerName);
|
|
982
|
+
}
|
|
983
|
+
}
|
|
775
984
|
let stream2;
|
|
776
985
|
try {
|
|
777
986
|
stream2 = await client.chat.completions.create(params, {
|
|
@@ -796,6 +1005,10 @@ async function* runStream2(options) {
|
|
|
796
1005
|
if (details?.cached_tokens) {
|
|
797
1006
|
cacheRead = details.cached_tokens;
|
|
798
1007
|
}
|
|
1008
|
+
const usageAny = chunk.usage;
|
|
1009
|
+
if (!cacheRead && typeof usageAny.cached_tokens === "number" && usageAny.cached_tokens > 0) {
|
|
1010
|
+
cacheRead = usageAny.cached_tokens;
|
|
1011
|
+
}
|
|
799
1012
|
inputTokens = chunk.usage.prompt_tokens - cacheRead;
|
|
800
1013
|
}
|
|
801
1014
|
if (!choice) continue;
|
|
@@ -877,6 +1090,102 @@ async function* runStream2(options) {
|
|
|
877
1090
|
yield { type: "done", stopReason };
|
|
878
1091
|
return response;
|
|
879
1092
|
}
|
|
1093
|
+
function* synthesizeEventsFromCompletion(completion, thinkingEnabled) {
|
|
1094
|
+
const choice = completion.choices?.[0];
|
|
1095
|
+
if (!choice) {
|
|
1096
|
+
yield { type: "done", stopReason: normalizeOpenAIStopReason(null) };
|
|
1097
|
+
return;
|
|
1098
|
+
}
|
|
1099
|
+
const msg = choice.message;
|
|
1100
|
+
const reasoning = msg.reasoning_content;
|
|
1101
|
+
if (typeof reasoning === "string" && reasoning && thinkingEnabled) {
|
|
1102
|
+
yield { type: "thinking_delta", text: reasoning };
|
|
1103
|
+
}
|
|
1104
|
+
if (typeof msg.content === "string" && msg.content) {
|
|
1105
|
+
yield { type: "text_delta", text: msg.content };
|
|
1106
|
+
}
|
|
1107
|
+
const toolCalls = msg.tool_calls;
|
|
1108
|
+
if (toolCalls) {
|
|
1109
|
+
for (const tc of toolCalls) {
|
|
1110
|
+
const argsJson = tc.function?.arguments ?? "";
|
|
1111
|
+
if (argsJson) {
|
|
1112
|
+
yield {
|
|
1113
|
+
type: "toolcall_delta",
|
|
1114
|
+
id: tc.id,
|
|
1115
|
+
name: tc.function?.name ?? "",
|
|
1116
|
+
argsJson
|
|
1117
|
+
};
|
|
1118
|
+
}
|
|
1119
|
+
let args = {};
|
|
1120
|
+
try {
|
|
1121
|
+
args = JSON.parse(argsJson);
|
|
1122
|
+
} catch {
|
|
1123
|
+
}
|
|
1124
|
+
yield {
|
|
1125
|
+
type: "toolcall_done",
|
|
1126
|
+
id: tc.id,
|
|
1127
|
+
name: tc.function?.name ?? "",
|
|
1128
|
+
args
|
|
1129
|
+
};
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
yield { type: "done", stopReason: normalizeOpenAIStopReason(choice.finish_reason ?? null) };
|
|
1133
|
+
}
|
|
1134
|
+
function completionToResponse(completion) {
|
|
1135
|
+
const choice = completion.choices?.[0];
|
|
1136
|
+
const contentParts = [];
|
|
1137
|
+
let textAccum = "";
|
|
1138
|
+
if (choice) {
|
|
1139
|
+
const msg = choice.message;
|
|
1140
|
+
const reasoning = msg.reasoning_content;
|
|
1141
|
+
if (typeof reasoning === "string" && reasoning) {
|
|
1142
|
+
contentParts.push({ type: "thinking", text: reasoning });
|
|
1143
|
+
}
|
|
1144
|
+
if (typeof msg.content === "string" && msg.content) {
|
|
1145
|
+
textAccum = msg.content;
|
|
1146
|
+
contentParts.push({ type: "text", text: msg.content });
|
|
1147
|
+
}
|
|
1148
|
+
const toolCalls = msg.tool_calls;
|
|
1149
|
+
if (toolCalls) {
|
|
1150
|
+
for (const tc of toolCalls) {
|
|
1151
|
+
let args = {};
|
|
1152
|
+
try {
|
|
1153
|
+
args = JSON.parse(tc.function?.arguments ?? "{}");
|
|
1154
|
+
} catch {
|
|
1155
|
+
}
|
|
1156
|
+
const toolCall = {
|
|
1157
|
+
type: "tool_call",
|
|
1158
|
+
id: tc.id,
|
|
1159
|
+
name: tc.function?.name ?? "",
|
|
1160
|
+
args
|
|
1161
|
+
};
|
|
1162
|
+
contentParts.push(toolCall);
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
let inputTokens = 0;
|
|
1167
|
+
let outputTokens = 0;
|
|
1168
|
+
let cacheRead = 0;
|
|
1169
|
+
if (completion.usage) {
|
|
1170
|
+
outputTokens = completion.usage.completion_tokens;
|
|
1171
|
+
const details = completion.usage.prompt_tokens_details;
|
|
1172
|
+
if (details?.cached_tokens) cacheRead = details.cached_tokens;
|
|
1173
|
+
const usageAny = completion.usage;
|
|
1174
|
+
if (!cacheRead && typeof usageAny.cached_tokens === "number" && usageAny.cached_tokens > 0) {
|
|
1175
|
+
cacheRead = usageAny.cached_tokens;
|
|
1176
|
+
}
|
|
1177
|
+
inputTokens = completion.usage.prompt_tokens - cacheRead;
|
|
1178
|
+
}
|
|
1179
|
+
const stopReason = normalizeOpenAIStopReason(choice?.finish_reason ?? null);
|
|
1180
|
+
return {
|
|
1181
|
+
message: {
|
|
1182
|
+
role: "assistant",
|
|
1183
|
+
content: contentParts.length > 0 ? contentParts : textAccum
|
|
1184
|
+
},
|
|
1185
|
+
stopReason,
|
|
1186
|
+
usage: { inputTokens, outputTokens, ...cacheRead > 0 && { cacheRead } }
|
|
1187
|
+
};
|
|
1188
|
+
}
|
|
880
1189
|
function toError2(err, provider = "openai") {
|
|
881
1190
|
if (err instanceof OpenAI.APIError) {
|
|
882
1191
|
let msg = err.message;
|
|
@@ -910,7 +1219,8 @@ function streamOpenAICodex(options) {
|
|
|
910
1219
|
async function* runStream3(options) {
|
|
911
1220
|
const baseUrl = (options.baseUrl || DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
912
1221
|
const url = `${baseUrl}/codex/responses`;
|
|
913
|
-
const
|
|
1222
|
+
const downgraded = downgradeUnsupportedImages(options.messages, options.supportsImages);
|
|
1223
|
+
const { system, input } = toCodexInput(downgraded, { supportsImages: options.supportsImages });
|
|
914
1224
|
const body = {
|
|
915
1225
|
model: options.model,
|
|
916
1226
|
store: false,
|
|
@@ -1130,7 +1440,11 @@ function remapCodexId(id, idMap) {
|
|
|
1130
1440
|
idMap.set(id, mapped);
|
|
1131
1441
|
return mapped;
|
|
1132
1442
|
}
|
|
1133
|
-
function
|
|
1443
|
+
function codexToolResultText(content) {
|
|
1444
|
+
if (typeof content === "string") return content;
|
|
1445
|
+
return content.filter((b) => b.type === "text").map((b) => b.text).join("\n");
|
|
1446
|
+
}
|
|
1447
|
+
function toCodexInput(messages, options) {
|
|
1134
1448
|
let system;
|
|
1135
1449
|
const input = [];
|
|
1136
1450
|
const idMap = /* @__PURE__ */ new Map();
|
|
@@ -1183,12 +1497,33 @@ function toCodexInput(messages) {
|
|
|
1183
1497
|
continue;
|
|
1184
1498
|
}
|
|
1185
1499
|
if (msg.role === "tool") {
|
|
1500
|
+
const toolImages = [];
|
|
1186
1501
|
for (const result of msg.content) {
|
|
1187
1502
|
const [callId] = result.toolCallId.includes("|") ? result.toolCallId.split("|", 2) : [result.toolCallId];
|
|
1503
|
+
const text = codexToolResultText(result.content);
|
|
1188
1504
|
input.push({
|
|
1189
1505
|
type: "function_call_output",
|
|
1190
1506
|
call_id: remapCodexId(callId, idMap),
|
|
1191
|
-
output:
|
|
1507
|
+
output: text.length > 0 ? text : "(see attached image)"
|
|
1508
|
+
});
|
|
1509
|
+
if (options?.supportsImages !== false && Array.isArray(result.content)) {
|
|
1510
|
+
for (const block of result.content) {
|
|
1511
|
+
if (block.type === "image") toolImages.push(block);
|
|
1512
|
+
}
|
|
1513
|
+
}
|
|
1514
|
+
}
|
|
1515
|
+
if (toolImages.length > 0) {
|
|
1516
|
+
input.push({
|
|
1517
|
+
type: "message",
|
|
1518
|
+
role: "user",
|
|
1519
|
+
content: [
|
|
1520
|
+
{ type: "input_text", text: "Attached image(s) from tool result:" },
|
|
1521
|
+
...toolImages.map((img) => ({
|
|
1522
|
+
type: "input_image",
|
|
1523
|
+
detail: "auto",
|
|
1524
|
+
image_url: `data:${img.mediaType};base64,${img.data}`
|
|
1525
|
+
}))
|
|
1526
|
+
]
|
|
1192
1527
|
});
|
|
1193
1528
|
}
|
|
1194
1529
|
}
|
|
@@ -1273,6 +1608,12 @@ providerRegistry.register("moonshot", {
|
|
|
1273
1608
|
baseUrl: options.baseUrl ?? "https://api.moonshot.ai/v1"
|
|
1274
1609
|
})
|
|
1275
1610
|
});
|
|
1611
|
+
providerRegistry.register("openrouter", {
|
|
1612
|
+
stream: (options) => streamOpenAI({
|
|
1613
|
+
...options,
|
|
1614
|
+
baseUrl: options.baseUrl ?? "https://openrouter.ai/api/v1"
|
|
1615
|
+
})
|
|
1616
|
+
});
|
|
1276
1617
|
providerRegistry.register("minimax", {
|
|
1277
1618
|
stream: (options) => streamAnthropic({
|
|
1278
1619
|
...options,
|