@gajae-code/ai 0.7.11 → 0.8.1

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.
@@ -2,6 +2,7 @@ import type { ResponseInput } from "openai/resources/responses/responses";
2
2
  import type { CacheRetention, OpenAIResponsesHistoryPayload, ProviderPayload } from "./types";
3
3
  export { isRecord } from "@gajae-code/utils";
4
4
  export declare function normalizeSystemPrompts(systemPrompt: readonly string[] | string | undefined | null): string[];
5
+ export declare function sanitizeJsonStrings(value: unknown): unknown;
5
6
  export declare function toNumber(value: unknown): number | undefined;
6
7
  export declare function toPositiveNumber(value: unknown, fallback: number): number;
7
8
  export declare function toBoolean(value: unknown): boolean | undefined;
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@gajae-code/ai",
4
- "version": "0.7.11",
4
+ "version": "0.8.1",
5
5
  "description": "Unified LLM API with automatic model discovery and provider configuration",
6
- "homepage": "https://gaebal-gajae.dev",
6
+ "homepage": "https://gajae-code.com",
7
7
  "author": "Yeachan-Heo",
8
8
  "contributors": [
9
9
  "Mario Zechner"
@@ -11,11 +11,11 @@
11
11
  "license": "MIT",
12
12
  "repository": {
13
13
  "type": "git",
14
- "url": "git+https://github.com/gajae-ai/gajae-code.git",
14
+ "url": "git+https://github.com/Yeachan-Heo/gajae-code.git",
15
15
  "directory": "packages/ai"
16
16
  },
17
17
  "bugs": {
18
- "url": "https://github.com/gajae-ai/gajae-code/issues"
18
+ "url": "https://github.com/Yeachan-Heo/gajae-code/issues"
19
19
  },
20
20
  "keywords": [
21
21
  "ai",
@@ -43,7 +43,7 @@
43
43
  "dependencies": {
44
44
  "@anthropic-ai/sdk": "^0.94.0",
45
45
  "@bufbuild/protobuf": "^2.12.0",
46
- "@gajae-code/utils": "0.7.11",
46
+ "@gajae-code/utils": "0.8.1",
47
47
  "openai": "^6.36.0",
48
48
  "partial-json": "^0.1.7",
49
49
  "zod": "4.4.3"
@@ -28,7 +28,7 @@ import type {
28
28
  ToolChoice,
29
29
  ToolResultMessage,
30
30
  } from "../types";
31
- import { normalizeToolCallId, resolveCacheRetention } from "../utils";
31
+ import { normalizeToolCallId, resolveCacheRetention, sanitizeJsonStrings } from "../utils";
32
32
  import { AssistantMessageEventStream } from "../utils/event-stream";
33
33
  import { appendRawHttpRequestDumpFor400, type RawHttpRequestDump, withHttpStatus } from "../utils/http-inspector";
34
34
  import { parseStreamingJson } from "../utils/json-parse";
@@ -701,7 +701,7 @@ function convertMessages(
701
701
  toolUse: {
702
702
  toolUseId: normalizeToolCallId(c.id),
703
703
  name: c.name,
704
- input: c.arguments,
704
+ input: sanitizeJsonStrings(c.arguments),
705
705
  },
706
706
  });
707
707
  break;
@@ -13,6 +13,7 @@ import type {
13
13
  ToolResultMessage,
14
14
  UserMessage,
15
15
  } from "../types";
16
+ import { sanitizeJsonStrings } from "../utils";
16
17
  import {
17
18
  type AnthropicAssistantContentBlock,
18
19
  type AnthropicMessage,
@@ -433,7 +434,7 @@ function encodeContentBlocks(message: AssistantMessage): Record<string, unknown>
433
434
  blocks.push({ type: "redacted_thinking", data: c.data });
434
435
  break;
435
436
  case "toolCall":
436
- blocks.push({ type: "tool_use", id: c.id, name: c.name, input: c.arguments ?? {} });
437
+ blocks.push({ type: "tool_use", id: c.id, name: c.name, input: sanitizeJsonStrings(c.arguments ?? {}) });
437
438
  break;
438
439
  }
439
440
  }
@@ -53,6 +53,7 @@ import {
53
53
  normalizeSystemPrompts,
54
54
  normalizeToolCallId,
55
55
  resolveCacheRetention,
56
+ sanitizeJsonStrings,
56
57
  } from "../utils";
57
58
  import { createAbortSourceTracker } from "../utils/abort";
58
59
  import { AssistantMessageEventStream } from "../utils/event-stream";
@@ -2491,7 +2492,7 @@ export function convertAnthropicMessages(
2491
2492
  type: "tool_use",
2492
2493
  id: block.id,
2493
2494
  name: isOAuthToken ? applyClaudeToolPrefix(block.name) : block.name,
2494
- input: block.arguments ?? {},
2495
+ input: sanitizeJsonStrings(block.arguments ?? {}),
2495
2496
  });
2496
2497
  }
2497
2498
  }
@@ -18,7 +18,7 @@ import type {
18
18
  Tool,
19
19
  ToolCall,
20
20
  } from "../types";
21
- import { normalizeSystemPrompts } from "../utils";
21
+ import { normalizeSystemPrompts, sanitizeJsonStrings } from "../utils";
22
22
  import { AssistantMessageEventStream } from "../utils/event-stream";
23
23
  import { finalizeErrorMessage, type RawHttpRequestDump, withHttpStatus } from "../utils/http-inspector";
24
24
  import { normalizeSchemaForCCA, normalizeSchemaForGoogle, toolWireSchema } from "../utils/schema";
@@ -237,7 +237,7 @@ export function convertMessages<T extends GoogleApiType>(model: Model<T>, contex
237
237
  const part: Part = {
238
238
  functionCall: {
239
239
  name: block.name,
240
- args: block.arguments ?? {},
240
+ args: sanitizeJsonStrings(block.arguments ?? {}) as Record<string, unknown>,
241
241
  ...(requiresToolCallId(model.id) ? { id: block.id } : {}),
242
242
  },
243
243
  };
@@ -467,7 +467,7 @@ export function pushToolCallEvents(
467
467
  stream.push({
468
468
  type: "toolcall_delta",
469
469
  contentIndex,
470
- delta: JSON.stringify(toolCall.arguments),
470
+ delta: JSON.stringify(sanitizeJsonStrings(toolCall.arguments)),
471
471
  partial: output,
472
472
  });
473
473
  stream.push({ type: "toolcall_end", contentIndex, toolCall, partial: output });
@@ -19,6 +19,7 @@ import type {
19
19
  ToolResultMessage,
20
20
  TSchema,
21
21
  } from "../types";
22
+ import { sanitizeJsonStrings } from "../utils";
22
23
  import {
23
24
  type OpenAIChatContentPart,
24
25
  type OpenAIChatMessage,
@@ -453,9 +454,9 @@ function isOnlyRaw(args: Record<string, unknown>): boolean {
453
454
 
454
455
  function stringifyArgs(args: Record<string, unknown>): string {
455
456
  // `__raw` is our fallback marker for un-parseable inbound args; preserve it verbatim on the way out.
456
- if (typeof args.__raw === "string" && isOnlyRaw(args)) return args.__raw;
457
+ if (typeof args.__raw === "string" && isOnlyRaw(args)) return args.__raw.toWellFormed();
457
458
  try {
458
- return JSON.stringify(args);
459
+ return JSON.stringify(sanitizeJsonStrings(args));
459
460
  } catch {
460
461
  return "{}";
461
462
  }
@@ -1,6 +1,7 @@
1
1
  import type { Effort } from "../../model-thinking";
2
2
  import { requireSupportedEffort } from "../../model-thinking";
3
3
  import type { Api, Model } from "../../types";
4
+ import { sanitizeJsonStrings } from "../../utils";
4
5
 
5
6
  export interface ReasoningConfig {
6
7
  effort: "none" | "minimal" | "low" | "medium" | "high" | "xhigh" | "max";
@@ -109,6 +110,10 @@ function normalizeInputTextPartFields(input: InputItem[] | undefined): InputItem
109
110
  }
110
111
  if (normalizedItem.type === "message") {
111
112
  normalizedItem.content = normalizeTextPartFields(normalizedItem.content, `input[${itemIndex}].content`);
113
+ } else if (normalizedItem.type === "function_call" && "arguments" in itemRecord) {
114
+ itemRecord.arguments = sanitizeJsonStrings(itemRecord.arguments);
115
+ } else if (normalizedItem.type === "custom_tool_call" && typeof itemRecord.input === "string") {
116
+ itemRecord.input = itemRecord.input.toWellFormed();
112
117
  }
113
118
  return normalizedItem;
114
119
  });
@@ -35,7 +35,7 @@ import {
35
35
  type ToolChoice,
36
36
  type ToolResultMessage,
37
37
  } from "../types";
38
- import { normalizeSystemPrompts } from "../utils";
38
+ import { normalizeSystemPrompts, sanitizeJsonStrings } from "../utils";
39
39
  import { createAbortSourceTracker } from "../utils/abort";
40
40
  import { AssistantMessageEventStream } from "../utils/event-stream";
41
41
  import { toFirepassWireModelId, toFireworksWireModelId } from "../utils/fireworks-model-id";
@@ -166,7 +166,7 @@ function normalizeStreamingContentText(content: unknown): string {
166
166
  function serializeToolArguments(value: unknown): string {
167
167
  if (value && typeof value === "object" && !Array.isArray(value)) {
168
168
  try {
169
- return JSON.stringify(value);
169
+ return JSON.stringify(sanitizeJsonStrings(value));
170
170
  } catch {
171
171
  return "{}";
172
172
  }
@@ -178,7 +178,7 @@ function serializeToolArguments(value: unknown): string {
178
178
  try {
179
179
  const parsed = JSON.parse(trimmed);
180
180
  if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
181
- return JSON.stringify(parsed);
181
+ return JSON.stringify(sanitizeJsonStrings(parsed));
182
182
  }
183
183
  } catch {}
184
184
  return "{}";
@@ -22,6 +22,7 @@ import type {
22
22
  Tool,
23
23
  ToolCall,
24
24
  } from "../types";
25
+ import { sanitizeJsonStrings } from "../utils";
25
26
  import {
26
27
  type OpenAIResponsesFunctionCallItem,
27
28
  type OpenAIResponsesFunctionCallOutputItem,
@@ -612,7 +613,8 @@ function buildOutputItems(message: AssistantMessage): OutputItem[] {
612
613
  } else if (part.type === "toolCall") {
613
614
  flushMessage();
614
615
  if (part.customWireName) {
615
- const rawInput = typeof part.arguments?.input === "string" ? (part.arguments.input as string) : "";
616
+ const rawInput =
617
+ typeof part.arguments?.input === "string" ? (part.arguments.input as string).toWellFormed() : "";
616
618
  out.push({
617
619
  type: "custom_tool_call",
618
620
  id: part.thoughtSignature ?? makeCustomCallId(),
@@ -627,7 +629,7 @@ function buildOutputItems(message: AssistantMessage): OutputItem[] {
627
629
  id: part.thoughtSignature ?? makeFuncCallId(),
628
630
  call_id: part.id,
629
631
  name: part.name,
630
- arguments: JSON.stringify(part.arguments ?? {}),
632
+ arguments: JSON.stringify(sanitizeJsonStrings(part.arguments ?? {})),
631
633
  status: "completed",
632
634
  });
633
635
  }
@@ -1085,7 +1087,8 @@ export function encodeStream(
1085
1087
  } else {
1086
1088
  // Standard JSON tool: arguments object on the gjc side, the
1087
1089
  // wire wants the JSON string the model emitted (= streamed deltas).
1088
- const argsJson = cur.argsText || JSON.stringify(tc.arguments ?? {});
1090
+ const argsJson =
1091
+ cur.argsText.toWellFormed() || JSON.stringify(sanitizeJsonStrings(tc.arguments ?? {}));
1089
1092
  cur.argsText = argsJson;
1090
1093
  emit("response.function_call_arguments.done", {
1091
1094
  item_id: cur.itemId,
@@ -28,7 +28,7 @@ import {
28
28
  type ToolCall,
29
29
  type ToolResultMessage,
30
30
  } from "../types";
31
- import { normalizeResponsesToolCallId } from "../utils";
31
+ import { normalizeResponsesToolCallId, sanitizeJsonStrings } from "../utils";
32
32
  import type { AssistantMessageEventStream } from "../utils/event-stream";
33
33
  import { isCompleteJson, parseStreamingJson } from "../utils/json-parse";
34
34
  import { joinTextWithImagePlaceholder, NON_VISION_IMAGE_PLACEHOLDER, partitionVisionContent } from "./vision-guard";
@@ -275,7 +275,7 @@ export function convertResponsesAssistantMessage<TApi extends Api>(
275
275
  }
276
276
  knownCallIds.add(normalized.callId);
277
277
  if (block.customWireName) {
278
- const rawInput = typeof block.arguments?.input === "string" ? block.arguments.input : "";
278
+ const rawInput = typeof block.arguments?.input === "string" ? block.arguments.input.toWellFormed() : "";
279
279
  customCallIds?.add(normalized.callId);
280
280
  outputItems.push({
281
281
  type: "custom_tool_call",
@@ -291,7 +291,7 @@ export function convertResponsesAssistantMessage<TApi extends Api>(
291
291
  id: itemId,
292
292
  call_id: normalized.callId,
293
293
  name: block.name,
294
- arguments: JSON.stringify(block.arguments),
294
+ arguments: JSON.stringify(sanitizeJsonStrings(block.arguments ?? {})),
295
295
  });
296
296
  }
297
297
 
package/src/utils.ts CHANGED
@@ -11,6 +11,34 @@ export function normalizeSystemPrompts(systemPrompt: readonly string[] | string
11
11
  return prompts.map(prompt => prompt.toWellFormed()).filter(prompt => prompt.length > 0);
12
12
  }
13
13
 
14
+ export function sanitizeJsonStrings(value: unknown): unknown {
15
+ return sanitizeJsonStringsInner(value, new WeakMap<object, unknown>());
16
+ }
17
+
18
+ function sanitizeJsonStringsInner(value: unknown, seen: WeakMap<object, unknown>): unknown {
19
+ if (typeof value === "string") return value.toWellFormed();
20
+ if (!value || typeof value !== "object") return value;
21
+
22
+ const cached = seen.get(value);
23
+ if (cached !== undefined) return cached;
24
+
25
+ if (Array.isArray(value)) {
26
+ const sanitized: unknown[] = [];
27
+ seen.set(value, sanitized);
28
+ for (const item of value) {
29
+ sanitized.push(sanitizeJsonStringsInner(item, seen));
30
+ }
31
+ return sanitized;
32
+ }
33
+
34
+ const sanitized: Record<string, unknown> = {};
35
+ seen.set(value, sanitized);
36
+ for (const [key, nestedValue] of Object.entries(value)) {
37
+ sanitized[key.toWellFormed()] = sanitizeJsonStringsInner(nestedValue, seen);
38
+ }
39
+ return sanitized;
40
+ }
41
+
14
42
  export function toNumber(value: unknown): number | undefined {
15
43
  if (typeof value === "number" && Number.isFinite(value)) return value;
16
44
  if (typeof value === "string" && value.trim()) {