@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.
- package/dist/types/utils.d.ts +1 -0
- package/package.json +5 -5
- package/src/providers/amazon-bedrock.ts +2 -2
- package/src/providers/anthropic-messages-server.ts +2 -1
- package/src/providers/anthropic.ts +2 -1
- package/src/providers/google-shared.ts +3 -3
- package/src/providers/openai-chat-server.ts +3 -2
- package/src/providers/openai-codex/request-transformer.ts +5 -0
- package/src/providers/openai-completions.ts +3 -3
- package/src/providers/openai-responses-server.ts +6 -3
- package/src/providers/openai-responses-shared.ts +3 -3
- package/src/utils.ts +28 -0
package/dist/types/utils.d.ts
CHANGED
|
@@ -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.
|
|
4
|
+
"version": "0.8.1",
|
|
5
5
|
"description": "Unified LLM API with automatic model discovery and provider configuration",
|
|
6
|
-
"homepage": "https://
|
|
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/
|
|
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/
|
|
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.
|
|
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 =
|
|
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 =
|
|
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()) {
|