@gajae-code/ai 0.7.4 → 0.7.5

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/CHANGELOG.md CHANGED
@@ -2,6 +2,13 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [0.7.5] - 2026-06-27
6
+
7
+ ### Fixed
8
+
9
+ - Sanitized Codex history replay text fields so malformed/non-string replay text no longer corrupts the rebuilt request payload (#1199).
10
+ - Preserved object-valued tool replay fields when rebuilding Codex history payloads instead of coercing them to strings (#1200).
11
+
5
12
  ## [0.7.4] - 2026-06-27
6
13
 
7
14
  ### Fixed
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@gajae-code/ai",
4
- "version": "0.7.4",
4
+ "version": "0.7.5",
5
5
  "description": "Unified LLM API with automatic model discovery and provider configuration",
6
6
  "homepage": "https://gaebal-gajae.dev",
7
7
  "author": "Yeachan-Heo",
@@ -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.4",
46
+ "@gajae-code/utils": "0.7.5",
47
47
  "openai": "^6.36.0",
48
48
  "partial-json": "^0.1.7",
49
49
  "zod": "4.4.3"
@@ -44,6 +44,7 @@ import {
44
44
  getOpenAIResponsesHistoryItems,
45
45
  getOpenAIResponsesHistoryPayload,
46
46
  normalizeSystemPrompts,
47
+ sanitizeOpenAIResponsesHistoryItemsForReplay,
47
48
  } from "../utils";
48
49
  import { AssistantMessageEventStream } from "../utils/event-stream";
49
50
  import { finalizeErrorMessage, type RawHttpRequestDump } from "../utils/http-inspector";
@@ -2550,17 +2551,16 @@ function convertMessages(model: Model<"openai-codex-responses">, context: Contex
2550
2551
  for (const msg of transformedMessages) {
2551
2552
  if (msg.role === "user" || msg.role === "developer") {
2552
2553
  const providerPayload = (msg as { providerPayload?: AssistantMessage["providerPayload"] }).providerPayload;
2553
- const historyItems = getOpenAIResponsesHistoryItems(providerPayload, model.provider) as
2554
- | Array<ResponseInput[number]>
2555
- | undefined;
2554
+ const historyItems = getOpenAIResponsesHistoryItems(providerPayload, model.provider);
2556
2555
  if (historyItems) {
2557
- for (const item of historyItems) {
2556
+ const sanitizedHistoryItems = sanitizeOpenAIResponsesHistoryItemsForReplay(historyItems);
2557
+ for (const item of sanitizedHistoryItems) {
2558
2558
  const maybe = item as { type?: string; call_id?: string };
2559
2559
  if (maybe.type === "custom_tool_call" && typeof maybe.call_id === "string") {
2560
2560
  customCallIds.add(maybe.call_id);
2561
2561
  }
2562
2562
  }
2563
- messages.push(...historyItems);
2563
+ messages.push(...sanitizedHistoryItems);
2564
2564
  msgIndex += 1;
2565
2565
  continue;
2566
2566
  }
@@ -2579,18 +2579,19 @@ function convertMessages(model: Model<"openai-codex-responses">, context: Contex
2579
2579
  model.provider,
2580
2580
  assistantMsg.provider,
2581
2581
  );
2582
- const historyItems = providerPayload?.items as Array<ResponseInput[number]> | undefined;
2582
+ const historyItems = providerPayload?.items;
2583
2583
  if (historyItems) {
2584
- for (const item of historyItems) {
2584
+ const sanitizedHistoryItems = sanitizeOpenAIResponsesHistoryItemsForReplay(historyItems);
2585
+ for (const item of sanitizedHistoryItems) {
2585
2586
  const maybe = item as { type?: string; call_id?: string };
2586
2587
  if (maybe.type === "custom_tool_call" && typeof maybe.call_id === "string") {
2587
2588
  customCallIds.add(maybe.call_id);
2588
2589
  }
2589
2590
  }
2590
2591
  if (providerPayload?.dt) {
2591
- messages.push(...historyItems);
2592
+ messages.push(...sanitizedHistoryItems);
2592
2593
  } else {
2593
- messages.splice(0, messages.length, ...historyItems);
2594
+ messages.splice(0, messages.length, ...sanitizedHistoryItems);
2594
2595
  // Keep customCallIds from the pre-splice state since historyItems may re-introduce them.
2595
2596
  }
2596
2597
  msgIndex += 1;
package/src/utils.ts CHANGED
@@ -91,6 +91,57 @@ export function sanitizeOpenAIResponsesHistoryItemsForReplay(items: Array<Record
91
91
  return sanitized ? [sanitized] : [];
92
92
  });
93
93
  }
94
+ function stringifyResponsesStringParamForReplay(value: unknown): string {
95
+ if (typeof value === "string") return value.toWellFormed();
96
+ try {
97
+ const encoded = JSON.stringify(value);
98
+ if (typeof encoded === "string") return encoded.toWellFormed();
99
+ } catch {
100
+ // Fall through to String().
101
+ }
102
+ return String(value ?? "").toWellFormed();
103
+ }
104
+
105
+ function normalizeResponsesMessageTextForReplay(value: unknown): string {
106
+ if (typeof value === "string") return value.toWellFormed();
107
+ if (value && typeof value === "object") {
108
+ const nestedText = (value as { text?: unknown }).text;
109
+ if (typeof nestedText === "string") return nestedText.toWellFormed();
110
+ }
111
+ return stringifyResponsesStringParamForReplay(value);
112
+ }
113
+
114
+ function sanitizeResponsesMessageContentForReplay(content: unknown): unknown {
115
+ if (typeof content === "string") return content.toWellFormed();
116
+ if (!Array.isArray(content)) return content;
117
+ return content.map(part => {
118
+ if (!part || typeof part !== "object") return part;
119
+ const sanitizedPart = { ...(part as Record<string, unknown>) };
120
+ if ("text" in sanitizedPart) {
121
+ sanitizedPart.text = normalizeResponsesMessageTextForReplay(sanitizedPart.text);
122
+ }
123
+ return sanitizedPart;
124
+ });
125
+ }
126
+
127
+ function sanitizeResponsesStringFieldsForReplay(item: Record<string, unknown>): void {
128
+ if (item.type === "message") {
129
+ item.content = sanitizeResponsesMessageContentForReplay(item.content);
130
+ }
131
+ if (item.type === "function_call" && "arguments" in item && typeof item.arguments !== "string") {
132
+ item.arguments = stringifyResponsesStringParamForReplay(item.arguments);
133
+ }
134
+ if (item.type === "custom_tool_call" && "input" in item && typeof item.input !== "string") {
135
+ item.input = stringifyResponsesStringParamForReplay(item.input);
136
+ }
137
+ if (
138
+ (item.type === "function_call_output" || item.type === "custom_tool_call_output") &&
139
+ "output" in item &&
140
+ typeof item.output !== "string"
141
+ ) {
142
+ item.output = stringifyResponsesStringParamForReplay(item.output);
143
+ }
144
+ }
94
145
 
95
146
  function sanitizeOpenAIResponsesHistoryItemForReplay(
96
147
  item: Record<string, unknown>,
@@ -105,6 +156,7 @@ function sanitizeOpenAIResponsesHistoryItemForReplay(
105
156
  if (typeof item.call_id === "string") {
106
157
  sanitizedItem.call_id = normalizeReplayedResponsesHistoryCallId(item.call_id, normalizedCallIds);
107
158
  }
159
+ sanitizeResponsesStringFieldsForReplay(sanitizedItem);
108
160
 
109
161
  return sanitizedItem as unknown as OpenAIResponsesReplayItem;
110
162
  }