@prestyj/ai 5.10.0 → 5.12.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/dist/index.cjs CHANGED
@@ -40,6 +40,7 @@ __export(index_exports, {
40
40
  environmentSecrets: () => environmentSecrets,
41
41
  formatError: () => formatError,
42
42
  formatErrorForDisplay: () => formatErrorForDisplay,
43
+ hasLoneSurrogate: () => hasLoneSurrogate,
43
44
  isHardBillingMessage: () => isHardBillingMessage,
44
45
  isUsageLimitError: () => isUsageLimitError,
45
46
  localWireModelId: () => localWireModelId,
@@ -52,10 +53,14 @@ __export(index_exports, {
52
53
  redactText: () => redactText,
53
54
  redactValue: () => redactValue,
54
55
  registerPalsuProvider: () => registerPalsuProvider,
56
+ sanitizeMessagesForWire: () => sanitizeMessagesForWire,
55
57
  setProviderDiagnostic: () => setProviderDiagnostic,
58
+ sliceHead: () => sliceHead,
59
+ sliceTail: () => sliceTail,
56
60
  stream: () => stream,
57
61
  toAnthropicMessages: () => toAnthropicMessages,
58
- toOpenAIMessages: () => toOpenAIMessages
62
+ toOpenAIMessages: () => toOpenAIMessages,
63
+ toWellFormedText: () => toWellFormedText
59
64
  });
60
65
  module.exports = __toCommonJS(index_exports);
61
66
 
@@ -1221,7 +1226,7 @@ function parseToolArguments(argsJson) {
1221
1226
  var NON_STREAMING_TIMEOUT_MS = 60 * 60 * 1e3;
1222
1227
  var anthropicClientCache = /* @__PURE__ */ new Map();
1223
1228
  function fineGrainedToolStreamingEnabled() {
1224
- const raw = process.env.GG_FINE_GRAINED_TOOL_STREAMING ?? process.env.CLAUDE_CODE_ENABLE_FINE_GRAINED_TOOL_STREAMING;
1229
+ const raw = process.env.EZ_FINE_GRAINED_TOOL_STREAMING ?? process.env.CLAUDE_CODE_ENABLE_FINE_GRAINED_TOOL_STREAMING;
1225
1230
  if (!raw) return false;
1226
1231
  const v = raw.trim().toLowerCase();
1227
1232
  return v === "1" || v === "true" || v === "yes" || v === "on";
@@ -2086,7 +2091,15 @@ async function* runStream2(options) {
2086
2091
  if (chunk.usage) {
2087
2092
  ({ inputTokens, outputTokens, cacheRead, cacheWrite } = extractOpenAIUsage(chunk.usage));
2088
2093
  }
2089
- if (!choice) continue;
2094
+ if (!choice) {
2095
+ const gatewayError = classifyChoicelessFrame(chunk);
2096
+ if (gatewayError) {
2097
+ throw new ProviderError(providerName, gatewayError.message, {
2098
+ statusCode: gatewayError.statusCode
2099
+ });
2100
+ }
2101
+ continue;
2102
+ }
2090
2103
  if (choice.finish_reason) {
2091
2104
  finishReason = choice.finish_reason;
2092
2105
  }
@@ -2270,6 +2283,31 @@ function completionToResponse(completion, endpointKey) {
2270
2283
  }
2271
2284
  };
2272
2285
  }
2286
+ function classifyChoicelessFrame(frame) {
2287
+ if (!frame || typeof frame !== "object" || Array.isArray(frame)) return null;
2288
+ const rec = frame;
2289
+ if (Array.isArray(rec.choices)) return null;
2290
+ const statusOf = (value) => {
2291
+ const n = typeof value === "string" ? Number(value) : value;
2292
+ return typeof n === "number" && Number.isFinite(n) && n >= 400 && n <= 599 ? n : void 0;
2293
+ };
2294
+ const statusCode = statusOf(rec.status) ?? statusOf(rec.statusCode) ?? statusOf(rec.code);
2295
+ const typeIsError = typeof rec.type === "string" && rec.type.toLowerCase() === "error";
2296
+ let detailText;
2297
+ const detail = rec.detail;
2298
+ if (typeof detail === "string" && detail.trim()) {
2299
+ detailText = detail.trim();
2300
+ } else if (Array.isArray(detail)) {
2301
+ const parts = detail.map(
2302
+ (d) => d && typeof d === "object" && typeof d.msg === "string" ? d.msg : typeof d === "string" ? d : ""
2303
+ ).filter(Boolean);
2304
+ if (parts.length) detailText = parts.join("; ");
2305
+ }
2306
+ if (statusCode === void 0 && !typeIsError && !detailText) return null;
2307
+ const rawMessage = (typeof rec.message === "string" && rec.message.trim() ? rec.message.trim() : void 0) ?? detailText ?? (typeof rec.error === "string" && rec.error.trim() ? rec.error.trim() : void 0) ?? (statusCode !== void 0 ? `Gateway returned status ${statusCode}.` : "Gateway error.");
2308
+ const message = rawMessage.slice(0, 500);
2309
+ return { message, statusCode };
2310
+ }
2273
2311
  function classifyOpenAICompatLimit(args) {
2274
2312
  const { status, code, type, message } = args;
2275
2313
  const codeType = `${code ?? ""} ${type ?? ""}`.toLowerCase();
@@ -2281,6 +2319,7 @@ function classifyOpenAICompatLimit(args) {
2281
2319
  return null;
2282
2320
  }
2283
2321
  function toError2(err, provider = "openai") {
2322
+ if (err instanceof ProviderError) return err;
2284
2323
  if (err instanceof import_openai.default.APIError) {
2285
2324
  const body = err.error;
2286
2325
  const bodyMessage = typeof body?.message === "string" && body.message.trim() ? body.message.trim() : void 0;
@@ -3380,14 +3419,16 @@ async function* runStream4(options) {
3380
3419
  let thinkingAccum = "";
3381
3420
  let stopReason = "end_turn";
3382
3421
  let inputTokens = 0;
3383
- let outputTokens = 0;
3422
+ let candidateTokens = 0;
3423
+ let reasoningTokens = 0;
3384
3424
  let cacheRead = 0;
3385
3425
  let toolIndex = 0;
3386
3426
  const handleResponse = function* (chunk) {
3387
3427
  const usage = usageFromResponse(chunk);
3388
3428
  if (usage) {
3389
3429
  inputTokens = usage.promptTokenCount ?? inputTokens;
3390
- outputTokens = usage.candidatesTokenCount ?? outputTokens;
3430
+ candidateTokens = usage.candidatesTokenCount ?? candidateTokens;
3431
+ reasoningTokens = usage.thoughtsTokenCount ?? reasoningTokens;
3391
3432
  cacheRead = usage.cachedContentTokenCount ?? cacheRead;
3392
3433
  }
3393
3434
  const reason = finishReasonFromResponse(chunk);
@@ -3443,6 +3484,7 @@ async function* runStream4(options) {
3443
3484
  }
3444
3485
  if (pendingToolCalls.length > 0) stopReason = "tool_use";
3445
3486
  const adjustedInputTokens = Math.max(0, inputTokens - cacheRead);
3487
+ const outputTokens = candidateTokens + reasoningTokens;
3446
3488
  const streamResponse = {
3447
3489
  message: {
3448
3490
  role: "assistant",
@@ -3452,6 +3494,7 @@ async function* runStream4(options) {
3452
3494
  usage: {
3453
3495
  inputTokens: adjustedInputTokens,
3454
3496
  outputTokens,
3497
+ ...reasoningTokens > 0 ? { reasoningTokens } : {},
3455
3498
  ...cacheRead > 0 ? { cacheRead } : {}
3456
3499
  }
3457
3500
  };
@@ -3500,9 +3543,145 @@ var ProviderRegistryImpl = class {
3500
3543
  };
3501
3544
  var providerRegistry = new ProviderRegistryImpl();
3502
3545
 
3546
+ // src/utils/well-formed.ts
3547
+ var LONE_SURROGATE = /[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/;
3548
+ var LONE_SURROGATE_GLOBAL = new RegExp(LONE_SURROGATE, "g");
3549
+ var REPLACEMENT = "\uFFFD";
3550
+ function hasLoneSurrogate(text) {
3551
+ const isWellFormed = text.isWellFormed;
3552
+ if (typeof isWellFormed === "function") return !isWellFormed.call(text);
3553
+ return LONE_SURROGATE.test(text);
3554
+ }
3555
+ function toWellFormedText(text) {
3556
+ if (!hasLoneSurrogate(text)) return text;
3557
+ const toWellFormed = text.toWellFormed;
3558
+ if (typeof toWellFormed === "function") return toWellFormed.call(text);
3559
+ return text.replace(LONE_SURROGATE_GLOBAL, REPLACEMENT);
3560
+ }
3561
+ function isHighSurrogate(code) {
3562
+ return code !== void 0 && code >= 55296 && code <= 56319;
3563
+ }
3564
+ function isLowSurrogate(code) {
3565
+ return code !== void 0 && code >= 56320 && code <= 57343;
3566
+ }
3567
+ function sliceHead(text, chars) {
3568
+ if (chars <= 0) return "";
3569
+ if (chars >= text.length) return text;
3570
+ const end = isHighSurrogate(text.charCodeAt(chars - 1)) ? chars - 1 : chars;
3571
+ return text.slice(0, end);
3572
+ }
3573
+ function sliceTail(text, chars) {
3574
+ if (chars <= 0) return "";
3575
+ if (chars >= text.length) return text;
3576
+ const start = text.length - chars;
3577
+ return text.slice(isLowSurrogate(text.charCodeAt(start)) ? start + 1 : start);
3578
+ }
3579
+ function sanitizeJsonValue(value) {
3580
+ if (typeof value === "string") return toWellFormedText(value);
3581
+ if (Array.isArray(value)) {
3582
+ let changed = false;
3583
+ const next = value.map((item) => {
3584
+ const sanitized = sanitizeJsonValue(item);
3585
+ if (sanitized !== item) changed = true;
3586
+ return sanitized;
3587
+ });
3588
+ return changed ? next : value;
3589
+ }
3590
+ if (value !== null && typeof value === "object") {
3591
+ let changed = false;
3592
+ const next = {};
3593
+ for (const [key, item] of Object.entries(value)) {
3594
+ const sanitizedKey = toWellFormedText(key);
3595
+ const sanitized = sanitizeJsonValue(item);
3596
+ if (sanitizedKey !== key || sanitized !== item) changed = true;
3597
+ next[sanitizedKey] = sanitized;
3598
+ }
3599
+ return changed ? next : value;
3600
+ }
3601
+ return value;
3602
+ }
3603
+ function sanitizeRecord(value) {
3604
+ return sanitizeJsonValue(value);
3605
+ }
3606
+ function sanitizePart(part) {
3607
+ switch (part.type) {
3608
+ case "text":
3609
+ case "thinking": {
3610
+ const text = toWellFormedText(part.text);
3611
+ return text === part.text ? part : { ...part, text };
3612
+ }
3613
+ case "tool_call": {
3614
+ const args = sanitizeRecord(part.args);
3615
+ return args === part.args ? part : { ...part, args };
3616
+ }
3617
+ case "server_tool_call": {
3618
+ const input = sanitizeJsonValue(part.input);
3619
+ return input === part.input ? part : { ...part, input };
3620
+ }
3621
+ case "server_tool_result": {
3622
+ const data = sanitizeJsonValue(part.data);
3623
+ return data === part.data ? part : { ...part, data };
3624
+ }
3625
+ case "raw": {
3626
+ const data = sanitizeRecord(part.data);
3627
+ return data === part.data ? part : { ...part, data };
3628
+ }
3629
+ default:
3630
+ return part;
3631
+ }
3632
+ }
3633
+ function sanitizeParts(parts) {
3634
+ let changed = false;
3635
+ const next = parts.map((part) => {
3636
+ const sanitized = sanitizePart(part);
3637
+ if (sanitized !== part) changed = true;
3638
+ return sanitized;
3639
+ });
3640
+ return changed ? next : parts;
3641
+ }
3642
+ function sanitizeToolResultContent(content) {
3643
+ if (typeof content === "string") return toWellFormedText(content);
3644
+ return sanitizeParts(content);
3645
+ }
3646
+ function sanitizeToolResults(results) {
3647
+ let changed = false;
3648
+ const next = results.map((result) => {
3649
+ const content = sanitizeToolResultContent(result.content);
3650
+ if (content === result.content) return result;
3651
+ changed = true;
3652
+ return { ...result, content };
3653
+ });
3654
+ return changed ? next : results;
3655
+ }
3656
+ function sanitizeMessage(message) {
3657
+ if (message.role === "tool") {
3658
+ const content2 = sanitizeToolResults(message.content);
3659
+ return content2 === message.content ? message : { ...message, content: content2 };
3660
+ }
3661
+ if (typeof message.content === "string") {
3662
+ const content2 = toWellFormedText(message.content);
3663
+ return content2 === message.content ? message : { ...message, content: content2 };
3664
+ }
3665
+ const content = sanitizeParts(message.content);
3666
+ return content === message.content ? message : { ...message, content };
3667
+ }
3668
+ function sanitizeMessagesForWire(messages) {
3669
+ let sanitized;
3670
+ for (let index = 0; index < messages.length; index++) {
3671
+ const message = messages[index];
3672
+ const next = sanitizeMessage(message);
3673
+ if (next === message) continue;
3674
+ sanitized ??= messages.slice();
3675
+ sanitized[index] = next;
3676
+ }
3677
+ return sanitized ?? messages;
3678
+ }
3679
+
3503
3680
  // src/stream.ts
3504
3681
  var GLM_CODING_BASE_URL = "https://api.z.ai/api/coding/paas/v4";
3505
3682
  var KIMI_CODE_USER_AGENT = `kimi-code-cli/${process.env.KIMI_CODE_VERSION ?? "1.0.11"}`;
3683
+ var GROK_CLI_PROXY_HOST = "cli-chat-proxy.grok.com";
3684
+ var GROK_CLI_VERSION = process.env.GROK_CLI_VERSION ?? "0.2.101";
3506
3685
  providerRegistry.register("anthropic", {
3507
3686
  stream: (options) => streamAnthropic(options)
3508
3687
  });
@@ -3569,13 +3748,25 @@ providerRegistry.register("xai", {
3569
3748
  // xAI's public API (console.x.ai key) is OpenAI-compatible — ride the Chat
3570
3749
  // Completions transport like Moonshot/DeepSeek. Grok reasoning models take
3571
3750
  // top-level `reasoning_effort` (low/medium/high), which the shared thinking
3572
- // path already sends. xAI's OAuth path exists but only via the Grok CLI's
3573
- // private Responses proxy (cli-chat-proxy.grok.com) with reverse-engineered
3574
- // attribution headers and account-tier gating — intentionally not wired.
3575
- stream: (options) => streamOpenAI({
3576
- ...options,
3577
- baseUrl: options.baseUrl ?? "https://api.x.ai/v1"
3578
- })
3751
+ // path already sends.
3752
+ //
3753
+ // Subscription OAuth (SuperGrok / X Premium) routes to the Grok CLI chat proxy
3754
+ // instead, which speaks the same Chat Completions wire but gates on Grok-CLI
3755
+ // client identity. Inject those headers centrally here — exactly as the Kimi
3756
+ // endpoint above — so EVERY stream (agent loop, compaction, title-gen,
3757
+ // sub-agents) is accepted rather than depending on each call site to thread
3758
+ // headers. Caller-provided headers still win on collision.
3759
+ stream: (options) => {
3760
+ const baseUrl = options.baseUrl ?? "https://api.x.ai/v1";
3761
+ const defaultHeaders = baseUrl.includes(GROK_CLI_PROXY_HOST) ? {
3762
+ "X-XAI-Token-Auth": "xai-grok-cli",
3763
+ "x-grok-client-version": GROK_CLI_VERSION,
3764
+ "x-grok-client-identifier": "ezcoder",
3765
+ "x-grok-model-override": options.model,
3766
+ ...options.defaultHeaders
3767
+ } : options.defaultHeaders;
3768
+ return streamOpenAI({ ...options, baseUrl, defaultHeaders });
3769
+ }
3579
3770
  });
3580
3771
  providerRegistry.register("minimax", {
3581
3772
  stream: (options) => streamAnthropic({
@@ -3621,13 +3812,25 @@ function stream(options) {
3621
3812
  if (options.supportsVideo !== true && messagesContainVideo(options.messages)) {
3622
3813
  throw new VideoUnsupportedError();
3623
3814
  }
3815
+ const wireMessages = stripMessageProvenance(options.messages);
3624
3816
  const messages = clampProviderContextImages(
3625
- options.messages,
3817
+ sanitizeMessagesForWire(wireMessages),
3626
3818
  options.provider,
3627
3819
  options.supportsImages
3628
3820
  );
3629
3821
  return entry.stream(messages === options.messages ? options : { ...options, messages });
3630
3822
  }
3823
+ function stripMessageProvenance(messages) {
3824
+ let stripped;
3825
+ for (let index = 0; index < messages.length; index++) {
3826
+ const message = messages[index];
3827
+ if (!message.provenance) continue;
3828
+ stripped ??= messages.slice();
3829
+ const { provenance: _provenance, ...wireMessage } = message;
3830
+ stripped[index] = wireMessage;
3831
+ }
3832
+ return stripped ?? messages;
3833
+ }
3631
3834
  function messagesContainVideo(messages) {
3632
3835
  for (const msg of messages) {
3633
3836
  if (typeof msg.content === "string" || !Array.isArray(msg.content)) continue;
@@ -4013,6 +4216,7 @@ function registerPalsuProvider(config) {
4013
4216
  environmentSecrets,
4014
4217
  formatError,
4015
4218
  formatErrorForDisplay,
4219
+ hasLoneSurrogate,
4016
4220
  isHardBillingMessage,
4017
4221
  isUsageLimitError,
4018
4222
  localWireModelId,
@@ -4025,9 +4229,13 @@ function registerPalsuProvider(config) {
4025
4229
  redactText,
4026
4230
  redactValue,
4027
4231
  registerPalsuProvider,
4232
+ sanitizeMessagesForWire,
4028
4233
  setProviderDiagnostic,
4234
+ sliceHead,
4235
+ sliceTail,
4029
4236
  stream,
4030
4237
  toAnthropicMessages,
4031
- toOpenAIMessages
4238
+ toOpenAIMessages,
4239
+ toWellFormedText
4032
4240
  });
4033
4241
  //# sourceMappingURL=index.cjs.map