@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 CHANGED
@@ -40,10 +40,10 @@ Tool parameters are Zod schemas. Converted to JSON Schema at the provider bounda
40
40
 
41
41
  | Provider | Models | Notes |
42
42
  |---|---|---|
43
- | `anthropic` | Claude Opus 4.6, Sonnet 4.6, Haiku 4.5 | Extended thinking, prompt caching, server-side compaction |
43
+ | `anthropic` | Claude Opus 4.7, Sonnet 4.6, Haiku 4.5 | Extended thinking, prompt caching, server-side compaction |
44
44
  | `openai` | GPT-4.1, o3, o4-mini | Supports OAuth (codex endpoint) and API keys |
45
45
  | `glm` | GLM-5.1, GLM-4.7 | Z.AI platform, OpenAI-compatible |
46
- | `moonshot` | Kimi K2.5 | Moonshot platform, OpenAI-compatible |
46
+ | `moonshot` | Kimi K2.6 | Moonshot platform, OpenAI-compatible |
47
47
 
48
48
  ---
49
49
 
package/dist/index.cjs CHANGED
@@ -174,12 +174,70 @@ function zodToJsonSchema(schema) {
174
174
  }
175
175
 
176
176
  // src/providers/transform.ts
177
+ var NON_VISION_USER_IMAGE_PLACEHOLDER = "(image omitted: model does not support images)";
178
+ var NON_VISION_TOOL_IMAGE_PLACEHOLDER = "(tool image omitted: model does not support images)";
179
+ function stripImages(content, placeholder) {
180
+ const out = [];
181
+ let lastWasPlaceholder = false;
182
+ for (const block of content) {
183
+ if (block.type === "image") {
184
+ if (!lastWasPlaceholder) out.push({ type: "text", text: placeholder });
185
+ lastWasPlaceholder = true;
186
+ continue;
187
+ }
188
+ out.push(block);
189
+ lastWasPlaceholder = block.text === placeholder;
190
+ }
191
+ return out;
192
+ }
193
+ function downgradeUnsupportedImages(messages, supportsImages) {
194
+ if (supportsImages !== false) return messages;
195
+ return messages.map((msg) => {
196
+ if (msg.role === "user" && Array.isArray(msg.content)) {
197
+ return { ...msg, content: stripImages(msg.content, NON_VISION_USER_IMAGE_PLACEHOLDER) };
198
+ }
199
+ if (msg.role === "tool") {
200
+ return {
201
+ ...msg,
202
+ content: msg.content.map(
203
+ (tr) => Array.isArray(tr.content) ? {
204
+ ...tr,
205
+ content: stripImages(tr.content, NON_VISION_TOOL_IMAGE_PLACEHOLDER)
206
+ } : tr
207
+ )
208
+ };
209
+ }
210
+ return msg;
211
+ });
212
+ }
213
+ function toolResultText(content) {
214
+ if (typeof content === "string") return content;
215
+ return content.filter((b) => b.type === "text").map((b) => b.text).join("\n");
216
+ }
217
+ function toolResultImages(content) {
218
+ if (typeof content === "string") return [];
219
+ return content.filter((b) => b.type === "image");
220
+ }
177
221
  function toAnthropicCacheControl(retention, baseUrl) {
178
222
  const resolved = retention ?? "short";
179
223
  if (resolved === "none") return void 0;
180
224
  const ttl = resolved === "long" && (!baseUrl || baseUrl.includes("api.anthropic.com")) ? "1h" : void 0;
181
225
  return { type: "ephemeral", ...ttl && { ttl } };
182
226
  }
227
+ function toAnthropicToolResultContent(content) {
228
+ if (typeof content === "string") return content;
229
+ return content.map((block) => {
230
+ if (block.type === "text") return { type: "text", text: block.text };
231
+ return {
232
+ type: "image",
233
+ source: {
234
+ type: "base64",
235
+ media_type: block.mediaType,
236
+ data: block.data
237
+ }
238
+ };
239
+ });
240
+ }
183
241
  function toAnthropicMessages(messages, cacheControl) {
184
242
  let systemText;
185
243
  const out = [];
@@ -243,7 +301,7 @@ function toAnthropicMessages(messages, cacheControl) {
243
301
  content: msg.content.map((result) => ({
244
302
  type: "tool_result",
245
303
  tool_use_id: result.toolCallId,
246
- content: result.content,
304
+ content: toAnthropicToolResultContent(result.content),
247
305
  is_error: result.isError
248
306
  }))
249
307
  });
@@ -312,7 +370,7 @@ function toAnthropicToolChoice(choice) {
312
370
  return { type: "tool", name: choice.name };
313
371
  }
314
372
  function supportsAdaptiveThinking(model) {
315
- return /opus-4-6|sonnet-4-6/.test(model);
373
+ return /opus-4-7|opus-4-6|sonnet-4-6/.test(model);
316
374
  }
317
375
  function toAnthropicThinking(level, maxTokens, model) {
318
376
  if (supportsAdaptiveThinking(model)) {
@@ -339,10 +397,10 @@ function toAnthropicThinking(level, maxTokens, model) {
339
397
  };
340
398
  }
341
399
  function remapToolCallId(id, idMap) {
342
- if (id.startsWith("call_")) return id;
400
+ if (!id.startsWith("toolu_")) return id;
343
401
  const existing = idMap.get(id);
344
402
  if (existing) return existing;
345
- const mapped = `call_${id.replace(/^toolu_/, "")}`;
403
+ const mapped = `call_${id.slice(5)}`;
346
404
  idMap.set(id, mapped);
347
405
  return mapped;
348
406
  }
@@ -407,16 +465,36 @@ function toOpenAIMessages(messages, options) {
407
465
  };
408
466
  if (thinkingParts) {
409
467
  assistantMsg.reasoning_content = thinkingParts;
468
+ } else if (options?.thinking && hasToolCalls && options.provider !== "glm") {
469
+ assistantMsg.reasoning_content = " ";
410
470
  }
411
471
  out.push(assistantMsg);
412
472
  continue;
413
473
  }
414
474
  if (msg.role === "tool") {
475
+ const imageBlocks = [];
415
476
  for (const result of msg.content) {
477
+ const text = toolResultText(result.content);
478
+ const images = toolResultImages(result.content);
479
+ const hasText = text.length > 0;
416
480
  out.push({
417
481
  role: "tool",
418
482
  tool_call_id: remapToolCallId(result.toolCallId, idMap),
419
- content: result.content
483
+ content: hasText ? text : "(see attached image)"
484
+ });
485
+ if (images.length > 0 && options?.supportsImages !== false) {
486
+ for (const img of images) {
487
+ imageBlocks.push({
488
+ type: "image_url",
489
+ image_url: { url: `data:${img.mediaType};base64,${img.data}` }
490
+ });
491
+ }
492
+ }
493
+ }
494
+ if (imageBlocks.length > 0) {
495
+ out.push({
496
+ role: "user",
497
+ content: [{ type: "text", text: "Attached image(s) from tool result:" }, ...imageBlocks]
420
498
  });
421
499
  }
422
500
  }
@@ -496,8 +574,10 @@ function streamAnthropic(options) {
496
574
  async function* runStream(options) {
497
575
  const client = createClient(options);
498
576
  const isOAuth = options.apiKey?.startsWith("sk-ant-oat");
577
+ const useStreaming = options.streaming !== false;
499
578
  const cacheControl = toAnthropicCacheControl(options.cacheRetention, options.baseUrl);
500
- const { system: rawSystem, messages } = toAnthropicMessages(options.messages, cacheControl);
579
+ const downgradedMessages = downgradeUnsupportedImages(options.messages, options.supportsImages);
580
+ const { system: rawSystem, messages } = toAnthropicMessages(downgradedMessages, cacheControl);
501
581
  const system = isOAuth ? [
502
582
  {
503
583
  type: "text",
@@ -541,9 +621,9 @@ async function* runStream(options) {
541
621
  ];
542
622
  return contextEdits.length ? { context_management: { edits: contextEdits } } : {};
543
623
  })(),
544
- stream: true
624
+ stream: useStreaming
545
625
  };
546
- 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");
626
+ 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");
547
627
  const betaHeaders = [
548
628
  ...isOAuth ? ["claude-code-20250219", "oauth-2025-04-20"] : [],
549
629
  ...options.compaction ? ["compact-2026-01-12"] : [],
@@ -551,10 +631,23 @@ async function* runStream(options) {
551
631
  "fine-grained-tool-streaming-2025-05-14",
552
632
  ...!hasAdaptiveThinking ? ["interleaved-thinking-2025-05-14"] : []
553
633
  ];
554
- const stream2 = client.messages.stream(params, {
634
+ const requestOptions = {
555
635
  signal: options.signal ?? void 0,
556
636
  ...betaHeaders.length ? { headers: { "anthropic-beta": betaHeaders.join(",") } } : {}
557
- });
637
+ };
638
+ if (!useStreaming) {
639
+ try {
640
+ const message = await client.messages.create(
641
+ { ...params, stream: false },
642
+ requestOptions
643
+ );
644
+ yield* synthesizeEventsFromMessage(message);
645
+ return messageToResponse(message);
646
+ } catch (err) {
647
+ throw toError(err);
648
+ }
649
+ }
650
+ const stream2 = client.messages.stream(params, requestOptions);
558
651
  const contentParts = [];
559
652
  const blocks = /* @__PURE__ */ new Map();
560
653
  let inputTokens = 0;
@@ -747,6 +840,105 @@ async function* runStream(options) {
747
840
  yield { type: "done", stopReason: normalizedStop };
748
841
  return response;
749
842
  }
843
+ function* synthesizeEventsFromMessage(message) {
844
+ for (const block of message.content) {
845
+ const blk = block;
846
+ const type = blk.type;
847
+ if (type === "text") {
848
+ const text = blk.text;
849
+ if (text) yield { type: "text_delta", text };
850
+ } else if (type === "thinking") {
851
+ const text = blk.thinking;
852
+ if (text) yield { type: "thinking_delta", text };
853
+ } else if (type === "tool_use") {
854
+ const argsJson = JSON.stringify(blk.input ?? {});
855
+ yield {
856
+ type: "toolcall_delta",
857
+ id: blk.id,
858
+ name: blk.name,
859
+ argsJson
860
+ };
861
+ yield {
862
+ type: "toolcall_done",
863
+ id: blk.id,
864
+ name: blk.name,
865
+ args: blk.input ?? {}
866
+ };
867
+ } else if (type === "server_tool_use") {
868
+ yield {
869
+ type: "server_toolcall",
870
+ id: blk.id,
871
+ name: blk.name,
872
+ input: blk.input
873
+ };
874
+ } else if (type === "web_search_tool_result") {
875
+ yield {
876
+ type: "server_toolresult",
877
+ toolUseId: blk.tool_use_id,
878
+ resultType: type,
879
+ data: blk
880
+ };
881
+ }
882
+ }
883
+ yield { type: "done", stopReason: normalizeAnthropicStopReason(message.stop_reason) };
884
+ }
885
+ function messageToResponse(message) {
886
+ const contentParts = [];
887
+ for (const block of message.content) {
888
+ const blk = block;
889
+ const type = blk.type;
890
+ if (type === "text") {
891
+ contentParts.push({ type: "text", text: blk.text });
892
+ } else if (type === "thinking") {
893
+ contentParts.push({
894
+ type: "thinking",
895
+ text: blk.thinking,
896
+ signature: blk.signature ?? ""
897
+ });
898
+ } else if (type === "tool_use") {
899
+ contentParts.push({
900
+ type: "tool_call",
901
+ id: blk.id,
902
+ name: blk.name,
903
+ args: blk.input ?? {}
904
+ });
905
+ } else if (type === "server_tool_use") {
906
+ contentParts.push({
907
+ type: "server_tool_call",
908
+ id: blk.id,
909
+ name: blk.name,
910
+ input: blk.input
911
+ });
912
+ } else if (type === "web_search_tool_result") {
913
+ contentParts.push({
914
+ type: "server_tool_result",
915
+ toolUseId: blk.tool_use_id,
916
+ resultType: type,
917
+ data: blk
918
+ });
919
+ } else {
920
+ contentParts.push({ type: "raw", data: blk });
921
+ }
922
+ }
923
+ const usage = message.usage;
924
+ const inputTokens = usage.input_tokens ?? 0;
925
+ const outputTokens = usage.output_tokens ?? 0;
926
+ const cacheRead = usage.cache_read_input_tokens;
927
+ const cacheWrite = usage.cache_creation_input_tokens;
928
+ return {
929
+ message: {
930
+ role: "assistant",
931
+ content: contentParts.length > 0 ? contentParts : ""
932
+ },
933
+ stopReason: normalizeAnthropicStopReason(message.stop_reason),
934
+ usage: {
935
+ inputTokens,
936
+ outputTokens,
937
+ ...cacheRead != null && { cacheRead },
938
+ ...cacheWrite != null && { cacheWrite }
939
+ }
940
+ };
941
+ }
750
942
  function toError(err) {
751
943
  if (err instanceof import_sdk.default.APIError) {
752
944
  return new ProviderError("anthropic", err.message, {
@@ -774,15 +966,21 @@ function streamOpenAI(options) {
774
966
  }
775
967
  async function* runStream2(options) {
776
968
  const providerName = options.provider ?? "openai";
969
+ const useStreaming = options.streaming !== false;
777
970
  const client = createClient2(options);
778
971
  const usesThinkingParam = options.provider === "glm" || options.provider === "moonshot" || options.provider === "xiaomi";
779
- const messages = toOpenAIMessages(options.messages, { provider: options.provider });
972
+ const downgradedMessages = downgradeUnsupportedImages(options.messages, options.supportsImages);
973
+ const messages = toOpenAIMessages(downgradedMessages, {
974
+ provider: options.provider,
975
+ thinking: !!options.thinking,
976
+ supportsImages: options.supportsImages
977
+ });
780
978
  const defaultTemp = options.provider === "glm" ? 0.6 : void 0;
781
979
  const effectiveTemp = options.temperature ?? defaultTemp;
782
980
  const params = {
783
981
  model: options.model,
784
982
  messages,
785
- stream: true,
983
+ stream: useStreaming,
786
984
  ...options.maxTokens ? { max_completion_tokens: options.maxTokens } : {},
787
985
  ...effectiveTemp != null && !options.thinking ? { temperature: effectiveTemp } : {},
788
986
  ...options.topP != null ? { top_p: options.topP } : {},
@@ -790,14 +988,14 @@ async function* runStream2(options) {
790
988
  ...options.thinking && !usesThinkingParam ? { reasoning_effort: toOpenAIReasoningEffort(options.thinking) } : {},
791
989
  ...options.tools?.length ? { tools: toOpenAITools(options.tools) } : {},
792
990
  ...options.toolChoice && options.tools?.length ? { tool_choice: toOpenAIToolChoice(options.toolChoice) } : {},
793
- stream_options: { include_usage: true }
991
+ ...useStreaming ? { stream_options: { include_usage: true } } : {}
794
992
  };
795
- if (options.webSearch) {
796
- if (options.provider === "moonshot") {
797
- const raw = params;
798
- const tools = (raw.tools ?? []).slice();
799
- tools.push({ type: "builtin_function", function: { name: "$web_search" } });
800
- raw.tools = tools;
993
+ if (options.provider === "openai" || options.provider === "moonshot") {
994
+ const paramsAny = params;
995
+ paramsAny.prompt_cache_key = "ezcoder";
996
+ const retention = options.cacheRetention ?? "short";
997
+ if (retention === "long") {
998
+ paramsAny.prompt_cache_retention = "24h";
801
999
  }
802
1000
  }
803
1001
  if (usesThinkingParam) {
@@ -818,6 +1016,17 @@ async function* runStream2(options) {
818
1016
  `
819
1017
  );
820
1018
  }
1019
+ if (!useStreaming) {
1020
+ try {
1021
+ const completion = await client.chat.completions.create(params, {
1022
+ signal: options.signal ?? void 0
1023
+ });
1024
+ yield* synthesizeEventsFromCompletion(completion, !!options.thinking);
1025
+ return completionToResponse(completion);
1026
+ } catch (err) {
1027
+ throw toError2(err, providerName);
1028
+ }
1029
+ }
821
1030
  let stream2;
822
1031
  try {
823
1032
  stream2 = await client.chat.completions.create(params, {
@@ -842,6 +1051,10 @@ async function* runStream2(options) {
842
1051
  if (details?.cached_tokens) {
843
1052
  cacheRead = details.cached_tokens;
844
1053
  }
1054
+ const usageAny = chunk.usage;
1055
+ if (!cacheRead && typeof usageAny.cached_tokens === "number" && usageAny.cached_tokens > 0) {
1056
+ cacheRead = usageAny.cached_tokens;
1057
+ }
845
1058
  inputTokens = chunk.usage.prompt_tokens - cacheRead;
846
1059
  }
847
1060
  if (!choice) continue;
@@ -923,6 +1136,102 @@ async function* runStream2(options) {
923
1136
  yield { type: "done", stopReason };
924
1137
  return response;
925
1138
  }
1139
+ function* synthesizeEventsFromCompletion(completion, thinkingEnabled) {
1140
+ const choice = completion.choices?.[0];
1141
+ if (!choice) {
1142
+ yield { type: "done", stopReason: normalizeOpenAIStopReason(null) };
1143
+ return;
1144
+ }
1145
+ const msg = choice.message;
1146
+ const reasoning = msg.reasoning_content;
1147
+ if (typeof reasoning === "string" && reasoning && thinkingEnabled) {
1148
+ yield { type: "thinking_delta", text: reasoning };
1149
+ }
1150
+ if (typeof msg.content === "string" && msg.content) {
1151
+ yield { type: "text_delta", text: msg.content };
1152
+ }
1153
+ const toolCalls = msg.tool_calls;
1154
+ if (toolCalls) {
1155
+ for (const tc of toolCalls) {
1156
+ const argsJson = tc.function?.arguments ?? "";
1157
+ if (argsJson) {
1158
+ yield {
1159
+ type: "toolcall_delta",
1160
+ id: tc.id,
1161
+ name: tc.function?.name ?? "",
1162
+ argsJson
1163
+ };
1164
+ }
1165
+ let args = {};
1166
+ try {
1167
+ args = JSON.parse(argsJson);
1168
+ } catch {
1169
+ }
1170
+ yield {
1171
+ type: "toolcall_done",
1172
+ id: tc.id,
1173
+ name: tc.function?.name ?? "",
1174
+ args
1175
+ };
1176
+ }
1177
+ }
1178
+ yield { type: "done", stopReason: normalizeOpenAIStopReason(choice.finish_reason ?? null) };
1179
+ }
1180
+ function completionToResponse(completion) {
1181
+ const choice = completion.choices?.[0];
1182
+ const contentParts = [];
1183
+ let textAccum = "";
1184
+ if (choice) {
1185
+ const msg = choice.message;
1186
+ const reasoning = msg.reasoning_content;
1187
+ if (typeof reasoning === "string" && reasoning) {
1188
+ contentParts.push({ type: "thinking", text: reasoning });
1189
+ }
1190
+ if (typeof msg.content === "string" && msg.content) {
1191
+ textAccum = msg.content;
1192
+ contentParts.push({ type: "text", text: msg.content });
1193
+ }
1194
+ const toolCalls = msg.tool_calls;
1195
+ if (toolCalls) {
1196
+ for (const tc of toolCalls) {
1197
+ let args = {};
1198
+ try {
1199
+ args = JSON.parse(tc.function?.arguments ?? "{}");
1200
+ } catch {
1201
+ }
1202
+ const toolCall = {
1203
+ type: "tool_call",
1204
+ id: tc.id,
1205
+ name: tc.function?.name ?? "",
1206
+ args
1207
+ };
1208
+ contentParts.push(toolCall);
1209
+ }
1210
+ }
1211
+ }
1212
+ let inputTokens = 0;
1213
+ let outputTokens = 0;
1214
+ let cacheRead = 0;
1215
+ if (completion.usage) {
1216
+ outputTokens = completion.usage.completion_tokens;
1217
+ const details = completion.usage.prompt_tokens_details;
1218
+ if (details?.cached_tokens) cacheRead = details.cached_tokens;
1219
+ const usageAny = completion.usage;
1220
+ if (!cacheRead && typeof usageAny.cached_tokens === "number" && usageAny.cached_tokens > 0) {
1221
+ cacheRead = usageAny.cached_tokens;
1222
+ }
1223
+ inputTokens = completion.usage.prompt_tokens - cacheRead;
1224
+ }
1225
+ const stopReason = normalizeOpenAIStopReason(choice?.finish_reason ?? null);
1226
+ return {
1227
+ message: {
1228
+ role: "assistant",
1229
+ content: contentParts.length > 0 ? contentParts : textAccum
1230
+ },
1231
+ stopReason,
1232
+ usage: { inputTokens, outputTokens, ...cacheRead > 0 && { cacheRead } }
1233
+ };
1234
+ }
926
1235
  function toError2(err, provider = "openai") {
927
1236
  if (err instanceof import_openai.default.APIError) {
928
1237
  let msg = err.message;
@@ -956,7 +1265,8 @@ function streamOpenAICodex(options) {
956
1265
  async function* runStream3(options) {
957
1266
  const baseUrl = (options.baseUrl || DEFAULT_BASE_URL).replace(/\/+$/, "");
958
1267
  const url = `${baseUrl}/codex/responses`;
959
- const { system, input } = toCodexInput(options.messages);
1268
+ const downgraded = downgradeUnsupportedImages(options.messages, options.supportsImages);
1269
+ const { system, input } = toCodexInput(downgraded, { supportsImages: options.supportsImages });
960
1270
  const body = {
961
1271
  model: options.model,
962
1272
  store: false,
@@ -1176,7 +1486,11 @@ function remapCodexId(id, idMap) {
1176
1486
  idMap.set(id, mapped);
1177
1487
  return mapped;
1178
1488
  }
1179
- function toCodexInput(messages) {
1489
+ function codexToolResultText(content) {
1490
+ if (typeof content === "string") return content;
1491
+ return content.filter((b) => b.type === "text").map((b) => b.text).join("\n");
1492
+ }
1493
+ function toCodexInput(messages, options) {
1180
1494
  let system;
1181
1495
  const input = [];
1182
1496
  const idMap = /* @__PURE__ */ new Map();
@@ -1229,12 +1543,33 @@ function toCodexInput(messages) {
1229
1543
  continue;
1230
1544
  }
1231
1545
  if (msg.role === "tool") {
1546
+ const toolImages = [];
1232
1547
  for (const result of msg.content) {
1233
1548
  const [callId] = result.toolCallId.includes("|") ? result.toolCallId.split("|", 2) : [result.toolCallId];
1549
+ const text = codexToolResultText(result.content);
1234
1550
  input.push({
1235
1551
  type: "function_call_output",
1236
1552
  call_id: remapCodexId(callId, idMap),
1237
- output: result.content
1553
+ output: text.length > 0 ? text : "(see attached image)"
1554
+ });
1555
+ if (options?.supportsImages !== false && Array.isArray(result.content)) {
1556
+ for (const block of result.content) {
1557
+ if (block.type === "image") toolImages.push(block);
1558
+ }
1559
+ }
1560
+ }
1561
+ if (toolImages.length > 0) {
1562
+ input.push({
1563
+ type: "message",
1564
+ role: "user",
1565
+ content: [
1566
+ { type: "input_text", text: "Attached image(s) from tool result:" },
1567
+ ...toolImages.map((img) => ({
1568
+ type: "input_image",
1569
+ detail: "auto",
1570
+ image_url: `data:${img.mediaType};base64,${img.data}`
1571
+ }))
1572
+ ]
1238
1573
  });
1239
1574
  }
1240
1575
  }
@@ -1319,6 +1654,12 @@ providerRegistry.register("moonshot", {
1319
1654
  baseUrl: options.baseUrl ?? "https://api.moonshot.ai/v1"
1320
1655
  })
1321
1656
  });
1657
+ providerRegistry.register("openrouter", {
1658
+ stream: (options) => streamOpenAI({
1659
+ ...options,
1660
+ baseUrl: options.baseUrl ?? "https://openrouter.ai/api/v1"
1661
+ })
1662
+ });
1322
1663
  providerRegistry.register("minimax", {
1323
1664
  stream: (options) => streamAnthropic({
1324
1665
  ...options,