@gajae-code/ai 0.7.5 → 0.7.6

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.
@@ -18,6 +18,7 @@ export interface InputItem {
18
18
  name?: string;
19
19
  output?: unknown;
20
20
  arguments?: unknown;
21
+ encrypted_content?: unknown;
21
22
  }
22
23
  export interface RequestBody {
23
24
  model: string;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@gajae-code/ai",
4
- "version": "0.7.5",
4
+ "version": "0.7.6",
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.5",
46
+ "@gajae-code/utils": "0.7.6",
47
47
  "openai": "^6.36.0",
48
48
  "partial-json": "^0.1.7",
49
49
  "zod": "4.4.3"
@@ -23,6 +23,7 @@ export interface InputItem {
23
23
  name?: string;
24
24
  output?: unknown;
25
25
  arguments?: unknown;
26
+ encrypted_content?: unknown;
26
27
  }
27
28
 
28
29
  export interface RequestBody {
@@ -62,6 +63,57 @@ function getReasoningConfig(model: Model<Api>, options: CodexRequestOptions): Re
62
63
  return config;
63
64
  }
64
65
 
66
+ function describeTextPartValue(value: unknown): string {
67
+ if (value === null) return "null";
68
+ if (Array.isArray(value)) return "array";
69
+ return typeof value;
70
+ }
71
+
72
+ function normalizeTextPartValue(value: unknown, path: string): string {
73
+ if (typeof value === "string") return value.toWellFormed();
74
+ try {
75
+ const encoded = JSON.stringify(value);
76
+ if (typeof encoded === "string") return encoded.toWellFormed();
77
+ } catch {
78
+ // Fall through to the actionable local error below.
79
+ }
80
+ throw new Error(
81
+ `Invalid Codex request text part at ${path}: expected a string or JSON-serializable value, received ${describeTextPartValue(value)}. Normalize compacted continuation content before sending to Codex.`,
82
+ );
83
+ }
84
+
85
+ function normalizeTextPartFields(content: unknown, path: string): unknown {
86
+ if (typeof content === "string") return content.toWellFormed();
87
+ if (!Array.isArray(content)) return content;
88
+ return content.map((part, index) => {
89
+ if (!part || typeof part !== "object") return part;
90
+ const normalizedPart = { ...(part as Record<string, unknown>) };
91
+ if ("text" in normalizedPart) {
92
+ normalizedPart.text = normalizeTextPartValue(normalizedPart.text, `${path}[${index}].text`);
93
+ }
94
+ return normalizedPart;
95
+ });
96
+ }
97
+
98
+ function normalizeInputTextPartFields(input: InputItem[] | undefined): InputItem[] | undefined {
99
+ if (!Array.isArray(input)) return input;
100
+ return input.map((item, itemIndex) => {
101
+ const normalizedItem = { ...item };
102
+ const itemRecord = normalizedItem as Record<string, unknown>;
103
+ if ("encrypted_content" in itemRecord) {
104
+ if (typeof itemRecord.encrypted_content === "string") {
105
+ itemRecord.encrypted_content = itemRecord.encrypted_content.toWellFormed();
106
+ } else {
107
+ delete itemRecord.encrypted_content;
108
+ }
109
+ }
110
+ if (normalizedItem.type === "message") {
111
+ normalizedItem.content = normalizeTextPartFields(normalizedItem.content, `input[${itemIndex}].content`);
112
+ }
113
+ return normalizedItem;
114
+ });
115
+ }
116
+
65
117
  function filterInput(input: InputItem[] | undefined): InputItem[] | undefined {
66
118
  if (!Array.isArray(input)) return input;
67
119
 
@@ -121,6 +173,7 @@ export async function transformRequestBody(
121
173
  return item;
122
174
  });
123
175
  }
176
+ body.input = normalizeInputTextPartFields(body.input);
124
177
  }
125
178
 
126
179
  if (prompt?.developerMessages && prompt.developerMessages.length > 0 && Array.isArray(body.input)) {
package/src/utils.ts CHANGED
@@ -111,6 +111,29 @@ function normalizeResponsesMessageTextForReplay(value: unknown): string {
111
111
  return stringifyResponsesStringParamForReplay(value);
112
112
  }
113
113
 
114
+ type ResponsesImageDetail = "auto" | "low" | "high";
115
+
116
+ interface NormalizedResponsesImageUrl {
117
+ readonly imageUrl: string;
118
+ readonly detail?: ResponsesImageDetail;
119
+ }
120
+
121
+ function isResponsesImageDetail(value: unknown): value is ResponsesImageDetail {
122
+ return value === "auto" || value === "low" || value === "high";
123
+ }
124
+
125
+ function normalizeResponsesImageUrlForReplay(value: unknown): NormalizedResponsesImageUrl {
126
+ if (typeof value === "string") return { imageUrl: value.toWellFormed() };
127
+ if (value && typeof value === "object" && "url" in value && typeof value.url === "string") {
128
+ const detail = "detail" in value && isResponsesImageDetail(value.detail) ? value.detail : undefined;
129
+ return {
130
+ imageUrl: value.url.toWellFormed(),
131
+ ...(detail ? { detail } : {}),
132
+ };
133
+ }
134
+ return { imageUrl: stringifyResponsesStringParamForReplay(value) };
135
+ }
136
+
114
137
  function sanitizeResponsesMessageContentForReplay(content: unknown): unknown {
115
138
  if (typeof content === "string") return content.toWellFormed();
116
139
  if (!Array.isArray(content)) return content;
@@ -120,6 +143,18 @@ function sanitizeResponsesMessageContentForReplay(content: unknown): unknown {
120
143
  if ("text" in sanitizedPart) {
121
144
  sanitizedPart.text = normalizeResponsesMessageTextForReplay(sanitizedPart.text);
122
145
  }
146
+ if ("image_url" in sanitizedPart) {
147
+ const normalizedImageUrl = normalizeResponsesImageUrlForReplay(sanitizedPart.image_url);
148
+ sanitizedPart.image_url = normalizedImageUrl.imageUrl;
149
+ if (sanitizedPart.type === "image_url") {
150
+ sanitizedPart.type = "input_image";
151
+ }
152
+ if (normalizedImageUrl.detail) {
153
+ sanitizedPart.detail = normalizedImageUrl.detail;
154
+ } else if ("detail" in sanitizedPart && !isResponsesImageDetail(sanitizedPart.detail)) {
155
+ delete sanitizedPart.detail;
156
+ }
157
+ }
123
158
  return sanitizedPart;
124
159
  });
125
160
  }