@kenkaiiii/gg-ai 5.16.0 → 5.17.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
@@ -33,8 +33,11 @@ __export(index_exports, {
33
33
  EventStream: () => EventStream,
34
34
  GGAIError: () => GGAIError,
35
35
  ProviderError: () => ProviderError,
36
+ REDACTION_MARKER: () => REDACTED,
36
37
  StreamResult: () => StreamResult,
38
+ clampProviderContextImages: () => clampProviderContextImages,
37
39
  classifyProviderError: () => classifyProviderError,
40
+ environmentSecrets: () => environmentSecrets,
38
41
  formatError: () => formatError,
39
42
  formatErrorForDisplay: () => formatErrorForDisplay,
40
43
  isHardBillingMessage: () => isHardBillingMessage,
@@ -45,6 +48,8 @@ __export(index_exports, {
45
48
  palsuToolCall: () => palsuToolCall,
46
49
  prewarmAnthropicCache: () => prewarmAnthropicCache,
47
50
  providerRegistry: () => providerRegistry,
51
+ redactText: () => redactText,
52
+ redactValue: () => redactValue,
48
53
  registerPalsuProvider: () => registerPalsuProvider,
49
54
  setProviderDiagnostic: () => setProviderDiagnostic,
50
55
  stream: () => stream,
@@ -157,13 +162,20 @@ function isRawJsonErrorEcho(message) {
157
162
  return false;
158
163
  }
159
164
  }
165
+ function isRawHtmlErrorEcho(message) {
166
+ const withoutStatus = message.trimStart().replace(/^\d{3}\s+/, "").trimStart();
167
+ return /^<!doctype\s+html(?:\s|>)/i.test(withoutStatus) || /^<html(?:\s|>)/i.test(withoutStatus);
168
+ }
169
+ function providerHtmlErrorMessage(statusCode) {
170
+ return statusCode ? `The provider returned an HTML error page (HTTP ${statusCode}) instead of an API response.` : "The provider returned an HTML error page instead of an API response.";
171
+ }
160
172
  function emptyProviderErrorMessage(statusCode) {
161
173
  return statusCode ? `The provider returned an empty error response (HTTP ${statusCode}), with no further detail.` : "The provider returned an empty error response, with no further detail.";
162
174
  }
163
175
  function formatError(err) {
164
176
  if (err instanceof ProviderError) {
165
177
  const name = providerDisplayName(err.provider);
166
- const cleanMessage = cleanProviderMessage(err.message);
178
+ const cleanMessage = cleanProviderMessage(err.message, err.statusCode);
167
179
  if (isMythosAccessError(cleanMessage)) {
168
180
  return {
169
181
  headline: "Claude Mythos 5 is invitation-only.",
@@ -258,8 +270,9 @@ function formatErrorForDisplay(err) {
258
270
  lines.push(` \u2192 ${f.guidance}`);
259
271
  return lines.join("\n");
260
272
  }
261
- function cleanProviderMessage(message) {
262
- return message.replace(/^\[[^\]]+\]\s*/, "").trim();
273
+ function cleanProviderMessage(message, statusCode) {
274
+ const clean = message.replace(/^\[[^\]]+\]\s*/, "").trim();
275
+ return isRawHtmlErrorEcho(clean) ? providerHtmlErrorMessage(statusCode) : clean;
263
276
  }
264
277
  function inferSource(err) {
265
278
  const msg = err.message.toLowerCase();
@@ -620,6 +633,66 @@ function toAnthropicAssistantContent(content, preserveThinking, idMap) {
620
633
  return true;
621
634
  }).map((part) => toAnthropicAssistantPart(part, idMap)).filter((b) => b !== null);
622
635
  }
636
+ var PROVIDER_IMAGE_LIMIT_PLACEHOLDER = "[image omitted: provider image limit]";
637
+ var PROVIDER_IMAGE_BUDGETS = {
638
+ anthropic: 90,
639
+ minimax: 90,
640
+ openai: 200,
641
+ gemini: 200,
642
+ openrouter: 90
643
+ };
644
+ function countContextImages(messages) {
645
+ let count = 0;
646
+ for (const message of messages) {
647
+ if (message.role === "user" && Array.isArray(message.content)) {
648
+ count += message.content.filter((part) => part.type === "image").length;
649
+ } else if (message.role === "tool") {
650
+ for (const result of message.content) {
651
+ if (Array.isArray(result.content)) {
652
+ count += result.content.filter((part) => part.type === "image").length;
653
+ }
654
+ }
655
+ }
656
+ }
657
+ return count;
658
+ }
659
+ function clampProviderContextImages(messages, provider, supportsImages) {
660
+ if (supportsImages === false) return messages;
661
+ const budget = PROVIDER_IMAGE_BUDGETS[provider] ?? 5;
662
+ let remainingToRemove = countContextImages(messages) - budget;
663
+ if (remainingToRemove <= 0) return messages;
664
+ return messages.map((message) => {
665
+ if (message.role === "user" && Array.isArray(message.content)) {
666
+ const content = message.content.filter((part) => {
667
+ if (part.type !== "image" || remainingToRemove <= 0) return true;
668
+ remainingToRemove--;
669
+ return false;
670
+ });
671
+ return {
672
+ ...message,
673
+ content: content.length > 0 ? content : [{ type: "text", text: PROVIDER_IMAGE_LIMIT_PLACEHOLDER }]
674
+ };
675
+ }
676
+ if (message.role === "tool") {
677
+ return {
678
+ ...message,
679
+ content: message.content.map((result) => {
680
+ if (!Array.isArray(result.content)) return result;
681
+ const content = result.content.filter((part) => {
682
+ if (part.type !== "image" || remainingToRemove <= 0) return true;
683
+ remainingToRemove--;
684
+ return false;
685
+ });
686
+ return {
687
+ ...result,
688
+ content: content.length > 0 ? content : [{ type: "text", text: PROVIDER_IMAGE_LIMIT_PLACEHOLDER }]
689
+ };
690
+ })
691
+ };
692
+ }
693
+ return message;
694
+ });
695
+ }
623
696
  var NON_VISION_USER_IMAGE_PLACEHOLDER = "(image omitted: model does not support images)";
624
697
  var NON_VISION_TOOL_IMAGE_PLACEHOLDER = "(tool image omitted: model does not support images)";
625
698
  var NON_VIDEO_USER_PLACEHOLDER = "(video omitted: model does not support video)";
@@ -1636,7 +1709,8 @@ function toError(err) {
1636
1709
  const bodyMessage = typeof nestedError?.message === "string" && nestedError.message.trim() ? nestedError.message.trim() : typeof errorBody?.message === "string" && errorBody.message.trim() ? errorBody.message.trim() : void 0;
1637
1710
  const bodyType = typeof nestedError?.type === "string" ? nestedError.type : typeof errorBody?.type === "string" ? errorBody.type : typeof err.type === "string" ? err.type : void 0;
1638
1711
  const fallbackMessage = isRawJsonErrorEcho(err.message) ? emptyProviderErrorMessage(err.status) : err.message;
1639
- const message = bodyType && bodyMessage ? `${bodyType}: ${bodyMessage}` : bodyMessage ?? fallbackMessage;
1712
+ const messageCandidate = bodyMessage ?? err.message;
1713
+ const message = isRawHtmlErrorEcho(messageCandidate) ? providerHtmlErrorMessage(err.status) : bodyType && bodyMessage ? `${bodyType}: ${bodyMessage}` : bodyMessage ?? fallbackMessage;
1640
1714
  if (err.status === 429) {
1641
1715
  const limit = readUnifiedRateLimit(err.headers);
1642
1716
  const farOff = limit.resetsAt != null && limit.resetsAt * 1e3 - Date.now() > 6e4;
@@ -2104,7 +2178,8 @@ function toError2(err, provider = "openai") {
2104
2178
  const body = err.error;
2105
2179
  const bodyMessage = typeof body?.message === "string" && body.message.trim() ? body.message.trim() : void 0;
2106
2180
  const modelName = typeof body?.model === "string" ? body.model : "";
2107
- const cleanMessage = bodyMessage ?? (isRawJsonErrorEcho(err.message) ? emptyProviderErrorMessage(err.status) : err.message);
2181
+ const messageCandidate = bodyMessage ?? err.message;
2182
+ const cleanMessage = isRawHtmlErrorEcho(messageCandidate) ? providerHtmlErrorMessage(err.status) : bodyMessage ? bodyMessage : isRawJsonErrorEcho(err.message) ? emptyProviderErrorMessage(err.status) : err.message;
2108
2183
  let hint;
2109
2184
  if (modelName === "codex-mini-latest" || cleanMessage.includes("codex-mini-latest")) {
2110
2185
  hint = "codex-mini-latest requires an OpenAI Pro or Max subscription. Your account currently has access to GPT-5.4 and GPT-5.4 Mini.";
@@ -2218,6 +2293,21 @@ function outputTextKey(itemId, contentIndex) {
2218
2293
  function isVisibleOutputItem(itemType) {
2219
2294
  return itemType === "message";
2220
2295
  }
2296
+ function toCodexToolChoice(choice, tools) {
2297
+ const resolved = choice ?? "auto";
2298
+ if (typeof resolved === "object") {
2299
+ throw new GGAIError(
2300
+ `OpenAI Codex does not support selecting the named tool \`${resolved.name}\`; use auto, none, or required.`,
2301
+ { source: "capability" }
2302
+ );
2303
+ }
2304
+ if (resolved === "required" && !tools?.length) {
2305
+ throw new GGAIError("OpenAI Codex cannot require a tool call when no tools are configured.", {
2306
+ source: "capability"
2307
+ });
2308
+ }
2309
+ return resolved;
2310
+ }
2221
2311
  function streamOpenAICodex(options) {
2222
2312
  return new StreamResult(runStream3(options), options.signal);
2223
2313
  }
@@ -2234,7 +2324,7 @@ async function* runStream3(options) {
2234
2324
  stream: true,
2235
2325
  instructions: system,
2236
2326
  input,
2237
- tool_choice: "auto",
2327
+ tool_choice: toCodexToolChoice(options.toolChoice, options.tools),
2238
2328
  parallel_tool_calls: !responsesLite,
2239
2329
  include: ["reasoning.encrypted_content"]
2240
2330
  };
@@ -2267,8 +2357,9 @@ async function* runStream3(options) {
2267
2357
  headers["chatgpt-account-id"] = options.accountId;
2268
2358
  }
2269
2359
  if (options.transportSessionId) {
2270
- headers["session_id"] = options.transportSessionId;
2271
- headers["x-client-request-id"] = options.transportSessionId;
2360
+ const transportSessionId = normalizePromptCacheKey(options.transportSessionId);
2361
+ headers["session_id"] = transportSessionId;
2362
+ headers["x-client-request-id"] = transportSessionId;
2272
2363
  }
2273
2364
  const response = await fetch(url, {
2274
2365
  method: "POST",
@@ -2278,7 +2369,7 @@ async function* runStream3(options) {
2278
2369
  });
2279
2370
  if (!response.ok) {
2280
2371
  const text = await response.text().catch(() => "");
2281
- const parsed = parseCodexErrorBody(text);
2372
+ const parsed = parseCodexErrorBody(text, response.status);
2282
2373
  const message = parsed.message ?? `Codex API returned HTTP ${response.status}.`;
2283
2374
  const requestId = parsed.requestId ?? readHeader(response.headers, "x-request-id", "openai-request-id", "x-oai-request-id");
2284
2375
  const usageLimit = codexUsageLimitError(parsed.errorObj, response.status, requestId);
@@ -2672,13 +2763,14 @@ function toCodexTools(tools) {
2672
2763
  strict: null
2673
2764
  }));
2674
2765
  }
2675
- function parseCodexErrorBody(text) {
2766
+ function parseCodexErrorBody(text, statusCode) {
2676
2767
  if (!text) return {};
2677
2768
  try {
2678
2769
  const parsed = JSON.parse(text);
2679
2770
  const error = parsed.error;
2680
2771
  const detail = parsed.detail;
2681
- const message = error?.message ?? parsed.message ?? (typeof detail === "string" ? detail : void 0);
2772
+ const rawMessage = error?.message ?? parsed.message ?? (typeof detail === "string" ? detail : void 0);
2773
+ const message = rawMessage && isRawHtmlErrorEcho(rawMessage) ? providerHtmlErrorMessage(statusCode) : rawMessage;
2682
2774
  const requestId = parsed.request_id ?? error?.request_id ?? (message ? extractRequestIdFromMessage(message) : void 0);
2683
2775
  const errorObj = error ?? parsed;
2684
2776
  return {
@@ -2687,8 +2779,12 @@ function parseCodexErrorBody(text) {
2687
2779
  ...errorObj ? { errorObj } : {}
2688
2780
  };
2689
2781
  } catch {
2690
- const trimmed = text.trim().slice(0, 240);
2691
- return trimmed ? { message: trimmed } : {};
2782
+ const trimmed = text.trim();
2783
+ if (isRawHtmlErrorEcho(trimmed)) {
2784
+ return { message: providerHtmlErrorMessage(statusCode) };
2785
+ }
2786
+ const bounded = trimmed.slice(0, 240);
2787
+ return bounded ? { message: bounded } : {};
2692
2788
  }
2693
2789
  }
2694
2790
  var CODEX_USAGE_LIMIT_CODE = /usage_limit_reached|usage_not_included/i;
@@ -3323,7 +3419,12 @@ function stream(options) {
3323
3419
  if (options.supportsVideo !== true && messagesContainVideo(options.messages)) {
3324
3420
  throw new VideoUnsupportedError();
3325
3421
  }
3326
- return entry.stream(options);
3422
+ const messages = clampProviderContextImages(
3423
+ options.messages,
3424
+ options.provider,
3425
+ options.supportsImages
3426
+ );
3427
+ return entry.stream(messages === options.messages ? options : { ...options, messages });
3327
3428
  }
3328
3429
  function messagesContainVideo(messages) {
3329
3430
  for (const msg of messages) {
@@ -3428,6 +3529,125 @@ Original: ${message}`;
3428
3529
  return message;
3429
3530
  }
3430
3531
 
3532
+ // src/redaction.ts
3533
+ var REDACTED = "[REDACTED]";
3534
+ var TRUNCATED = "[TRUNCATED]";
3535
+ var CIRCULAR = "[CIRCULAR]";
3536
+ var SENSITIVE_NAME = /(?:^|[_-])(?:api[_-]?key|access[_-]?token|refresh[_-]?token|token|key|auth(?:orization)?|bearer|cookie|credential|private[_-]?key|password|passwd|secret)(?:$|[_-])/i;
3537
+ var SENSITIVE_ASSIGNMENT = /\b((?:api[_-]?key|access[_-]?token|refresh[_-]?token|token|key|auth(?:orization)?|bearer|cookie|credential|private[_-]?key|password|passwd|secret))\b(\s*[=:]\s*)(["']?)([^\s,"';}]+)\3/gi;
3538
+ function escaped(value) {
3539
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3540
+ }
3541
+ function normalizedSecrets(secrets) {
3542
+ if (!secrets) return [];
3543
+ return [...new Set([...secrets].filter((value) => value.length >= 8 && value !== REDACTED))].sort(
3544
+ (a, b) => b.length - a.length
3545
+ );
3546
+ }
3547
+ function environmentSecrets(env) {
3548
+ const values = /* @__PURE__ */ new Set();
3549
+ for (const [name, value] of Object.entries(env)) {
3550
+ if (!value || value.length < 8 || value === REDACTED || !SENSITIVE_NAME.test(name)) continue;
3551
+ values.add(value);
3552
+ }
3553
+ return [...values].sort((a, b) => b.length - a.length);
3554
+ }
3555
+ function redactText(text, options = {}) {
3556
+ let result = text;
3557
+ result = result.replace(
3558
+ /-----BEGIN (?:[A-Z0-9 ]+ )?PRIVATE KEY-----[\s\S]*?-----END (?:[A-Z0-9 ]+ )?PRIVATE KEY-----/g,
3559
+ REDACTED
3560
+ );
3561
+ result = result.replace(/\b([a-z][a-z0-9+.-]*:\/\/)[^\s/@:]+:[^\s/@]+@/gi, `$1${REDACTED}@`);
3562
+ result = result.replace(
3563
+ /\b(authorization\s*[:=]\s*)(?:bearer|basic)\s+[^\s,;]+/gi,
3564
+ `$1${REDACTED}`
3565
+ );
3566
+ result = result.replace(/\b(bearer|basic)\s+[A-Za-z0-9+/_.=-]{8,}/gi, `$1 ${REDACTED}`);
3567
+ result = result.replace(/\b(cookie|set-cookie)(\s*[:=]\s*)[^\r\n]+/gi, `$1$2${REDACTED}`);
3568
+ result = result.replace(
3569
+ /\beyJ[A-Za-z0-9_-]{6,}\.[A-Za-z0-9_-]{6,}\.[A-Za-z0-9_-]{6,}\b/g,
3570
+ REDACTED
3571
+ );
3572
+ result = result.replace(
3573
+ /\b(?:sk-(?:ant-|proj-)?|xox[baprs]-|gh[pousr]_|github_pat_|AIza)[A-Za-z0-9_-]{12,}\b/g,
3574
+ REDACTED
3575
+ );
3576
+ result = result.replace(
3577
+ SENSITIVE_ASSIGNMENT,
3578
+ (_match, name, separator) => `${name}${separator}${REDACTED}`
3579
+ );
3580
+ for (const secret of normalizedSecrets(options.secrets)) {
3581
+ result = result.replace(new RegExp(escaped(secret), "g"), REDACTED);
3582
+ }
3583
+ const maxStringLength = options.maxStringLength ?? 1e6;
3584
+ if (result.length > maxStringLength) {
3585
+ result = `${result.slice(0, maxStringLength)}${TRUNCATED}`;
3586
+ }
3587
+ return result;
3588
+ }
3589
+ function isBinary(value) {
3590
+ return value instanceof ArrayBuffer || ArrayBuffer.isView(value) || typeof Blob !== "undefined" && value instanceof Blob;
3591
+ }
3592
+ function isMediaObject(value) {
3593
+ return (value.type === "image" || value.type === "video") && (typeof value.data === "string" || typeof value.url === "string");
3594
+ }
3595
+ function redactValue(value, options = {}) {
3596
+ const maxDepth = options.maxDepth ?? 20;
3597
+ const maxEntries = options.maxEntries ?? 1e4;
3598
+ const seen = /* @__PURE__ */ new WeakSet();
3599
+ let entries = 0;
3600
+ const visit = (current, depth, sensitive = false) => {
3601
+ if (typeof current === "string") {
3602
+ if (sensitive && current.length > 0 && current !== REDACTED) return REDACTED;
3603
+ return redactText(current, options);
3604
+ }
3605
+ if (current === null || current === void 0 || typeof current === "number" || typeof current === "boolean" || typeof current === "bigint") {
3606
+ return current;
3607
+ }
3608
+ if (typeof current !== "object") return current;
3609
+ if (isBinary(current)) return current;
3610
+ if (current instanceof Date) return new Date(current.getTime());
3611
+ if (depth >= maxDepth) return TRUNCATED;
3612
+ if (seen.has(current)) return CIRCULAR;
3613
+ seen.add(current);
3614
+ if (current instanceof Error) {
3615
+ const error = {
3616
+ name: current.name,
3617
+ message: visit(current.message, depth + 1),
3618
+ stack: visit(current.stack, depth + 1)
3619
+ };
3620
+ for (const [key, child] of Object.entries(current)) {
3621
+ error[key] = visit(child, depth + 1, SENSITIVE_NAME.test(key));
3622
+ }
3623
+ return error;
3624
+ }
3625
+ if (Array.isArray(current)) {
3626
+ const clone2 = [];
3627
+ for (const child of current) {
3628
+ if (++entries > maxEntries) {
3629
+ clone2.push(TRUNCATED);
3630
+ break;
3631
+ }
3632
+ clone2.push(visit(child, depth + 1));
3633
+ }
3634
+ return clone2;
3635
+ }
3636
+ const record = current;
3637
+ if (isMediaObject(record)) return { ...record };
3638
+ const clone = {};
3639
+ for (const [key, child] of Object.entries(record)) {
3640
+ if (++entries > maxEntries) {
3641
+ clone[TRUNCATED] = true;
3642
+ break;
3643
+ }
3644
+ clone[key] = visit(child, depth + 1, SENSITIVE_NAME.test(key));
3645
+ }
3646
+ return clone;
3647
+ };
3648
+ return visit(value, 0);
3649
+ }
3650
+
3431
3651
  // src/providers/palsu.ts
3432
3652
  function palsuText(text) {
3433
3653
  return { role: "assistant", content: text ? [{ type: "text", text }] : [] };
@@ -3584,8 +3804,11 @@ function registerPalsuProvider(config) {
3584
3804
  EventStream,
3585
3805
  GGAIError,
3586
3806
  ProviderError,
3807
+ REDACTION_MARKER,
3587
3808
  StreamResult,
3809
+ clampProviderContextImages,
3588
3810
  classifyProviderError,
3811
+ environmentSecrets,
3589
3812
  formatError,
3590
3813
  formatErrorForDisplay,
3591
3814
  isHardBillingMessage,
@@ -3596,6 +3819,8 @@ function registerPalsuProvider(config) {
3596
3819
  palsuToolCall,
3597
3820
  prewarmAnthropicCache,
3598
3821
  providerRegistry,
3822
+ redactText,
3823
+ redactValue,
3599
3824
  registerPalsuProvider,
3600
3825
  setProviderDiagnostic,
3601
3826
  stream,