@prestyj/ai 4.2.16 → 4.2.44

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2026 Nolan Grout
3
+ Copyright (c) 2025 KenKai
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -11,7 +11,7 @@
11
11
 
12
12
  One function. Flat options. Switch providers by changing a string. No adapters, no plugins, no wrapper classes.
13
13
 
14
- Part of the [EZCoder](../../README.md) monorepo.
14
+ Part of the [EZCoder Framework](../../README.md) monorepo.
15
15
 
16
16
  ---
17
17
 
package/dist/index.cjs CHANGED
@@ -34,6 +34,7 @@ __export(index_exports, {
34
34
  GGAIError: () => GGAIError,
35
35
  ProviderError: () => ProviderError,
36
36
  StreamResult: () => StreamResult,
37
+ providerRegistry: () => providerRegistry,
37
38
  stream: () => stream
38
39
  });
39
40
  module.exports = __toCommonJS(index_exports);
@@ -56,9 +57,6 @@ var ProviderError = class extends GGAIError {
56
57
  }
57
58
  };
58
59
 
59
- // src/providers/anthropic.ts
60
- var import_sdk = __toESM(require("@anthropic-ai/sdk"), 1);
61
-
62
60
  // src/utils/event-stream.ts
63
61
  var EventStream = class {
64
62
  queue = [];
@@ -66,8 +64,8 @@ var EventStream = class {
66
64
  done = false;
67
65
  error = null;
68
66
  push(event) {
69
- if (this.queue.length > 5e4) {
70
- this.queue.splice(0, this.queue.length - 25e3);
67
+ if (this.queue.length > 1e4) {
68
+ this.queue.splice(0, this.queue.length - 5e3);
71
69
  }
72
70
  this.queue.push(event);
73
71
  this.resolve?.();
@@ -141,6 +139,9 @@ var StreamResult = class {
141
139
  }
142
140
  };
143
141
 
142
+ // src/providers/anthropic.ts
143
+ var import_sdk = __toESM(require("@anthropic-ai/sdk"), 1);
144
+
144
145
  // src/utils/zod-to-json-schema.ts
145
146
  var import_zod = require("zod");
146
147
  function zodToJsonSchema(schema) {
@@ -320,15 +321,24 @@ function remapToolCallId(id, idMap) {
320
321
  idMap.set(id, mapped);
321
322
  return mapped;
322
323
  }
323
- function toOpenAIMessages(messages) {
324
+ function toOpenAIMessages(messages, options) {
324
325
  const out = [];
325
326
  const idMap = /* @__PURE__ */ new Map();
327
+ const mergeToolResultText = options?.provider === "glm";
326
328
  for (const msg of messages) {
327
329
  if (msg.role === "system") {
328
330
  out.push({ role: "system", content: msg.content });
329
331
  continue;
330
332
  }
331
333
  if (msg.role === "user") {
334
+ if (mergeToolResultText && out.length > 0 && out[out.length - 1].role === "tool") {
335
+ const userText = typeof msg.content === "string" ? msg.content : msg.content.filter((p) => p.type === "text").map((p) => p.text).join("");
336
+ if (userText) {
337
+ const lastTool = out[out.length - 1];
338
+ lastTool.content = (lastTool.content ?? "") + "\n\n" + userText;
339
+ continue;
340
+ }
341
+ }
332
342
  if (typeof msg.content === "string") {
333
343
  out.push({ role: "user", content: msg.content });
334
344
  } else {
@@ -443,10 +453,24 @@ async function runStream(options, result) {
443
453
  const isOAuth = options.apiKey?.startsWith("sk-ant-oat");
444
454
  const client = new import_sdk.default({
445
455
  ...isOAuth ? { apiKey: null, authToken: options.apiKey } : { apiKey: options.apiKey },
446
- ...options.baseUrl ? { baseURL: options.baseUrl } : {}
456
+ ...options.baseUrl ? { baseURL: options.baseUrl } : {},
457
+ ...options.fetch ? { fetch: options.fetch } : {},
458
+ ...isOAuth ? {
459
+ defaultHeaders: {
460
+ "user-agent": "claude-cli/2.1.75",
461
+ "x-app": "cli"
462
+ }
463
+ } : {}
447
464
  });
448
465
  const cacheControl = toAnthropicCacheControl(options.cacheRetention, options.baseUrl);
449
- const { system, messages } = toAnthropicMessages(options.messages, cacheControl);
466
+ const { system: rawSystem, messages } = toAnthropicMessages(options.messages, cacheControl);
467
+ const system = isOAuth ? [
468
+ {
469
+ type: "text",
470
+ text: "You are Claude Code, Anthropic's official CLI for Claude."
471
+ },
472
+ ...rawSystem ?? []
473
+ ] : rawSystem;
450
474
  let maxTokens = options.maxTokens ?? 4096;
451
475
  let thinking;
452
476
  let outputConfig;
@@ -480,7 +504,7 @@ async function runStream(options, result) {
480
504
  stream: true
481
505
  };
482
506
  const betaHeaders = [
483
- ...isOAuth ? ["oauth-2025-04-20"] : [],
507
+ ...isOAuth ? ["claude-code-20250219", "oauth-2025-04-20"] : [],
484
508
  ...options.compaction ? ["compact-2026-01-12"] : []
485
509
  ];
486
510
  const stream2 = client.messages.stream(params, {
@@ -623,16 +647,19 @@ function streamOpenAI(options) {
623
647
  async function runStream2(options, result) {
624
648
  const client = new import_openai.default({
625
649
  apiKey: options.apiKey,
626
- ...options.baseUrl ? { baseURL: options.baseUrl } : {}
650
+ ...options.baseUrl ? { baseURL: options.baseUrl } : {},
651
+ ...options.fetch ? { fetch: options.fetch } : {}
627
652
  });
628
653
  const usesThinkingParam = options.provider === "glm" || options.provider === "moonshot";
629
- const messages = toOpenAIMessages(options.messages);
654
+ const messages = toOpenAIMessages(options.messages, { provider: options.provider });
655
+ const defaultTemp = options.provider === "glm" ? 0.6 : void 0;
656
+ const effectiveTemp = options.temperature ?? defaultTemp;
630
657
  const params = {
631
658
  model: options.model,
632
659
  messages,
633
660
  stream: true,
634
661
  ...options.maxTokens ? { max_tokens: options.maxTokens } : {},
635
- ...options.temperature != null && !options.thinking ? { temperature: options.temperature } : {},
662
+ ...effectiveTemp != null && !options.thinking ? { temperature: effectiveTemp } : {},
636
663
  ...options.topP != null ? { top_p: options.topP } : {},
637
664
  ...options.stop ? { stop: options.stop } : {},
638
665
  ...options.thinking && !usesThinkingParam ? { reasoning_effort: toOpenAIReasoningEffort(options.thinking) } : {},
@@ -749,7 +776,7 @@ async function runStream2(options, result) {
749
776
  result.push({ type: "done", stopReason });
750
777
  result.complete(response);
751
778
  }
752
- function toError2(err, provider) {
779
+ function toError2(err, provider = "openai") {
753
780
  if (err instanceof import_openai.default.APIError) {
754
781
  let msg = err.message;
755
782
  const body = err.error;
@@ -1075,31 +1102,122 @@ function toError3(err) {
1075
1102
  return new ProviderError("openai", String(err));
1076
1103
  }
1077
1104
 
1105
+ // src/provider-registry.ts
1106
+ var ProviderRegistryImpl = class {
1107
+ providers = /* @__PURE__ */ new Map();
1108
+ /**
1109
+ * Register a provider. Overwrites any existing provider with the same name.
1110
+ *
1111
+ * ```ts
1112
+ * import { providerRegistry } from "@prestyj/ai";
1113
+ *
1114
+ * providerRegistry.register("deepseek", {
1115
+ * stream: (options) => streamOpenAI({ ...options, baseUrl: "https://api.deepseek.com/v1" }),
1116
+ * });
1117
+ * ```
1118
+ */
1119
+ register(name, entry) {
1120
+ this.providers.set(name, entry);
1121
+ }
1122
+ /** Remove a registered provider. */
1123
+ unregister(name) {
1124
+ return this.providers.delete(name);
1125
+ }
1126
+ /** Get a provider entry by name. */
1127
+ get(name) {
1128
+ return this.providers.get(name);
1129
+ }
1130
+ /** Check if a provider is registered. */
1131
+ has(name) {
1132
+ return this.providers.has(name);
1133
+ }
1134
+ /** List all registered provider names. */
1135
+ list() {
1136
+ return [...this.providers.keys()];
1137
+ }
1138
+ };
1139
+ var providerRegistry = new ProviderRegistryImpl();
1140
+
1078
1141
  // src/stream.ts
1142
+ var GLM_CODING_BASE_URL = "https://api.z.ai/api/coding/paas/v4";
1143
+ var GLM_REGULAR_BASE_URL = "https://api.z.ai/api/paas/v4";
1144
+ providerRegistry.register("anthropic", {
1145
+ stream: (options) => streamAnthropic(options)
1146
+ });
1147
+ providerRegistry.register("openai", {
1148
+ stream: (options) => {
1149
+ if (options.accountId) {
1150
+ return streamOpenAICodex(options);
1151
+ }
1152
+ return streamOpenAI(options);
1153
+ }
1154
+ });
1155
+ providerRegistry.register("glm", {
1156
+ stream: (options) => {
1157
+ if (options.baseUrl) {
1158
+ return streamOpenAI(options);
1159
+ }
1160
+ return streamGLMWithFallback(options);
1161
+ }
1162
+ });
1163
+ providerRegistry.register("moonshot", {
1164
+ stream: (options) => streamOpenAI({
1165
+ ...options,
1166
+ baseUrl: options.baseUrl ?? "https://api.moonshot.ai/v1"
1167
+ })
1168
+ });
1079
1169
  function stream(options) {
1080
- switch (options.provider) {
1081
- case "anthropic":
1082
- return streamAnthropic(options);
1083
- case "openai":
1084
- if (options.accountId) {
1085
- return streamOpenAICodex(options);
1170
+ const entry = providerRegistry.get(options.provider);
1171
+ if (!entry) {
1172
+ throw new GGAIError(
1173
+ `Unknown provider: "${options.provider}". Registered: ${providerRegistry.list().join(", ")}`
1174
+ );
1175
+ }
1176
+ return entry.stream(options);
1177
+ }
1178
+ function streamGLMWithFallback(options) {
1179
+ const result = new StreamResult();
1180
+ runGLMWithFallback(options, result).catch((err) => {
1181
+ result.abort(err instanceof Error ? err : new Error(String(err)));
1182
+ });
1183
+ return result;
1184
+ }
1185
+ async function runGLMWithFallback(options, result) {
1186
+ const codingResult = streamOpenAI({ ...options, baseUrl: GLM_CODING_BASE_URL });
1187
+ try {
1188
+ for await (const event of codingResult) {
1189
+ result.push(event);
1190
+ }
1191
+ const response = await codingResult.response;
1192
+ result.complete(response);
1193
+ } catch (err) {
1194
+ if (isEndpointMismatchError(err)) {
1195
+ const regularResult = streamOpenAI({ ...options, baseUrl: GLM_REGULAR_BASE_URL });
1196
+ try {
1197
+ for await (const event of regularResult) {
1198
+ result.push(event);
1199
+ }
1200
+ const response = await regularResult.response;
1201
+ result.complete(response);
1202
+ } catch (fallbackErr) {
1203
+ result.abort(fallbackErr instanceof Error ? fallbackErr : new Error(String(fallbackErr)));
1086
1204
  }
1087
- return streamOpenAI(options);
1088
- case "glm":
1089
- return streamOpenAI({
1090
- ...options,
1091
- baseUrl: options.baseUrl ?? "https://api.z.ai/api/coding/paas/v4"
1092
- });
1093
- case "moonshot":
1094
- return streamOpenAI({
1095
- ...options,
1096
- baseUrl: options.baseUrl ?? "https://api.moonshot.ai/v1"
1097
- });
1098
- default:
1099
- throw new GGAIError(
1100
- `Unknown provider: ${options.provider}. Supported: "anthropic", "openai", "glm", "moonshot"`
1101
- );
1205
+ } else {
1206
+ result.abort(err instanceof Error ? err : new Error(String(err)));
1207
+ }
1208
+ }
1209
+ }
1210
+ function isEndpointMismatchError(err) {
1211
+ if (err instanceof ProviderError) {
1212
+ if (err.statusCode === 404 || err.statusCode === 403) return true;
1213
+ }
1214
+ if (err instanceof Error) {
1215
+ const msg = err.message.toLowerCase();
1216
+ if (msg.includes("model_not_found") || msg.includes("does not exist") || /model.*not.*exist/.test(msg) || /not have access/.test(msg) || msg.includes("resource not found")) {
1217
+ return true;
1218
+ }
1102
1219
  }
1220
+ return false;
1103
1221
  }
1104
1222
  // Annotate the CommonJS export names for ESM import in node:
1105
1223
  0 && (module.exports = {
@@ -1107,6 +1225,7 @@ function stream(options) {
1107
1225
  GGAIError,
1108
1226
  ProviderError,
1109
1227
  StreamResult,
1228
+ providerRegistry,
1110
1229
  stream
1111
1230
  });
1112
1231
  //# sourceMappingURL=index.cjs.map