@gajae-code/ai 0.7.5 → 0.7.7
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/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@gajae-code/ai",
|
|
4
|
-
"version": "0.7.
|
|
4
|
+
"version": "0.7.7",
|
|
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.
|
|
46
|
+
"@gajae-code/utils": "0.7.7",
|
|
47
47
|
"openai": "^6.36.0",
|
|
48
48
|
"partial-json": "^0.1.7",
|
|
49
49
|
"zod": "4.4.3"
|
package/src/model-thinking.ts
CHANGED
|
@@ -454,7 +454,13 @@ function inferGeneratedApplyPatchToolType(
|
|
|
454
454
|
|
|
455
455
|
function applyGpt55ContextWindow(model: ApiModel<Api>, parsedModel: OpenAIModel): boolean {
|
|
456
456
|
if (parsedModel.variant === "base" && semverEqual(parsedModel.version, "5.5")) {
|
|
457
|
-
|
|
457
|
+
// The first-party OpenAI GPT-5.5 model advertises a 1M total window, but
|
|
458
|
+
// the OpenAI code backend request path still enforces the smaller prompt
|
|
459
|
+
// budget. GJC's `contextWindow` is the usable prompt/input cap, not the
|
|
460
|
+
// marketing total window; using 1M here delays compaction and makes the UI
|
|
461
|
+
// promise space that `/responses/compact`/agent turns cannot actually use.
|
|
462
|
+
model.contextWindow =
|
|
463
|
+
model.provider === "openai-codex" || model.api === "openai-codex-responses" ? 272_000 : 1_000_000;
|
|
458
464
|
return true;
|
|
459
465
|
}
|
|
460
466
|
return false;
|
package/src/models.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { enrichModelThinking } from "./model-thinking";
|
|
1
|
+
import { applyGeneratedModelPolicies, enrichModelThinking } from "./model-thinking";
|
|
2
2
|
import MODELS from "./models.json" with { type: "json" };
|
|
3
3
|
import type { Api, KnownProvider, Model, Usage } from "./types";
|
|
4
4
|
import { isClaudeForcedToolChoiceIncapableModelId } from "./utils/tool-choice-capability";
|
|
@@ -47,7 +47,9 @@ function applyBundledCompatDefaults(model: Model<Api>): Model<Api> {
|
|
|
47
47
|
compat: { ...(normalized.compat ?? {}), toolChoiceSupport: "auto" } as Model<Api>["compat"],
|
|
48
48
|
};
|
|
49
49
|
}
|
|
50
|
-
|
|
50
|
+
const policyModels = [normalized];
|
|
51
|
+
applyGeneratedModelPolicies(policyModels);
|
|
52
|
+
return policyModels[0] ?? normalized;
|
|
51
53
|
}
|
|
52
54
|
|
|
53
55
|
export type GeneratedProvider = keyof typeof MODELS;
|
|
@@ -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
|
}
|