pi-openai-toolkit 0.17.2 → 0.18.0
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/README.md +2 -2
- package/README.zh.md +2 -2
- package/docs/configuration.md +1 -1
- package/package.json +1 -1
- package/src/codex-astra/extension.ts +0 -15
- package/src/context-management/codex-provider.ts +0 -1
- package/src/image-generation/client.ts +70 -8
- package/src/image-generation/eligibility.ts +3 -3
- package/src/image-generation/extension.ts +18 -4
- package/src/image-generation/protocol.ts +57 -94
- package/src/image-generation/render.ts +3 -3
- package/src/image-generation/service.ts +356 -255
- package/src/image-generation/types.ts +94 -3
- package/src/responses-headers.ts +10 -9
- package/src/types.ts +3 -3
|
@@ -23,8 +23,9 @@ export const IMAGE_REQUEST_TIMEOUT_MS = 5 * 60 * 1000;
|
|
|
23
23
|
export const MAX_IMAGE_PATH_CHARS = 4096;
|
|
24
24
|
export const MAX_IMAGE_DIAGNOSTIC_CHARS = 4096;
|
|
25
25
|
export const MAX_IMAGE_IDENTIFIER_CHARS = 256;
|
|
26
|
-
/** Upper bound for a configured or requested
|
|
26
|
+
/** Upper bound for a configured or requested Images API model id. */
|
|
27
27
|
export const MAX_IMAGE_MODEL_ID_CHARS = 256;
|
|
28
|
+
export const MAX_IMAGE_BATCH_SIZE = 10;
|
|
28
29
|
export const MAX_IMAGE_DIMENSION = 100_000;
|
|
29
30
|
|
|
30
31
|
export type ImageGenerationCapableApi = (typeof IMAGE_GENERATION_CAPABLE_APIS)[number];
|
|
@@ -45,8 +46,10 @@ export type GenerateImageParams = {
|
|
|
45
46
|
outputPath?: string | null;
|
|
46
47
|
size?: ImageGenerationSize;
|
|
47
48
|
quality?: ImageGenerationQuality;
|
|
48
|
-
/** Optional bare image model id that must exist in
|
|
49
|
+
/** Optional bare image model id that must exist in the configured image model allowlist. */
|
|
49
50
|
model?: string | null;
|
|
51
|
+
/** Number of independent image requests to run concurrently. */
|
|
52
|
+
batchSize?: number | null;
|
|
50
53
|
};
|
|
51
54
|
|
|
52
55
|
export type NormalizedGenerateImageParams = {
|
|
@@ -56,6 +59,7 @@ export type NormalizedGenerateImageParams = {
|
|
|
56
59
|
size: ImageGenerationSize;
|
|
57
60
|
quality: ImageGenerationQuality;
|
|
58
61
|
action: ImageGenerationAction;
|
|
62
|
+
batchSize: number;
|
|
59
63
|
/** Trimmed requested model id, or undefined when the caller omitted it. */
|
|
60
64
|
model?: string;
|
|
61
65
|
};
|
|
@@ -92,8 +96,25 @@ export type ImageGenerationDetails = {
|
|
|
92
96
|
warning?: string;
|
|
93
97
|
};
|
|
94
98
|
|
|
99
|
+
export type ImageGenerationBatchFailure = {
|
|
100
|
+
index: number;
|
|
101
|
+
code: ImageGenerationFailureCode;
|
|
102
|
+
message: string;
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
export type ImageGenerationBatchDetails = {
|
|
106
|
+
batch: true;
|
|
107
|
+
requestedCount: number;
|
|
108
|
+
succeededCount: number;
|
|
109
|
+
failedCount: number;
|
|
110
|
+
images: ImageGenerationDetails[];
|
|
111
|
+
failures?: ImageGenerationBatchFailure[];
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
export type ImageGenerationToolDetails = ImageGenerationDetails | ImageGenerationBatchDetails;
|
|
115
|
+
|
|
95
116
|
export type ImageGenerationExecutionResult = {
|
|
96
|
-
details:
|
|
117
|
+
details: ImageGenerationToolDetails;
|
|
97
118
|
text: string;
|
|
98
119
|
};
|
|
99
120
|
|
|
@@ -120,6 +141,35 @@ export type ImageGenerationFailureCode =
|
|
|
120
141
|
| "no-image"
|
|
121
142
|
| "artifact-write-failed";
|
|
122
143
|
|
|
144
|
+
function isImageGenerationFailureCode(value: unknown): value is ImageGenerationFailureCode {
|
|
145
|
+
switch (value) {
|
|
146
|
+
case "aborted":
|
|
147
|
+
case "timeout":
|
|
148
|
+
case "disabled":
|
|
149
|
+
case "unsupported-model":
|
|
150
|
+
case "missing-runtime":
|
|
151
|
+
case "invalid-parameters":
|
|
152
|
+
case "reference-input-invalid":
|
|
153
|
+
case "reference-upload-approval-required":
|
|
154
|
+
case "reference-upload-declined":
|
|
155
|
+
case "output-path-invalid":
|
|
156
|
+
case "output-path-approval-required":
|
|
157
|
+
case "output-path-declined":
|
|
158
|
+
case "authentication":
|
|
159
|
+
case "rate-limit":
|
|
160
|
+
case "request-rejected":
|
|
161
|
+
case "backend-unavailable":
|
|
162
|
+
case "network":
|
|
163
|
+
case "oversized-response":
|
|
164
|
+
case "malformed-response":
|
|
165
|
+
case "no-image":
|
|
166
|
+
case "artifact-write-failed":
|
|
167
|
+
return true;
|
|
168
|
+
default:
|
|
169
|
+
return false;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
123
173
|
export class ImageGenerationError extends Error {
|
|
124
174
|
readonly code: ImageGenerationFailureCode;
|
|
125
175
|
|
|
@@ -170,6 +220,47 @@ export function isImageGenerationDetails(value: unknown): value is ImageGenerati
|
|
|
170
220
|
);
|
|
171
221
|
}
|
|
172
222
|
|
|
223
|
+
function isBoundedIndex(value: unknown, maximum: number): value is number {
|
|
224
|
+
return typeof value === "number" && Number.isInteger(value) && value >= 0 && value < maximum;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
export function isImageGenerationBatchDetails(value: unknown): value is ImageGenerationBatchDetails {
|
|
228
|
+
if (!isRecord(value) || value.batch !== true) return false;
|
|
229
|
+
if (
|
|
230
|
+
typeof value.requestedCount !== "number" ||
|
|
231
|
+
!Number.isInteger(value.requestedCount) ||
|
|
232
|
+
value.requestedCount < 2 ||
|
|
233
|
+
value.requestedCount > MAX_IMAGE_BATCH_SIZE ||
|
|
234
|
+
typeof value.succeededCount !== "number" ||
|
|
235
|
+
!Number.isInteger(value.succeededCount) ||
|
|
236
|
+
value.succeededCount < 1 ||
|
|
237
|
+
value.succeededCount > value.requestedCount ||
|
|
238
|
+
typeof value.failedCount !== "number" ||
|
|
239
|
+
!Number.isInteger(value.failedCount) ||
|
|
240
|
+
value.failedCount !== value.requestedCount - value.succeededCount ||
|
|
241
|
+
!Array.isArray(value.images) ||
|
|
242
|
+
value.images.length !== value.succeededCount ||
|
|
243
|
+
!value.images.every(isImageGenerationDetails)
|
|
244
|
+
) {
|
|
245
|
+
return false;
|
|
246
|
+
}
|
|
247
|
+
if (value.failedCount === 0) return value.failures === undefined;
|
|
248
|
+
if (!Array.isArray(value.failures) || value.failures.length !== value.failedCount) return false;
|
|
249
|
+
const requestedCount = value.requestedCount;
|
|
250
|
+
const indexes = new Set<number>();
|
|
251
|
+
return value.failures.every((failure) => {
|
|
252
|
+
if (!isRecord(failure) || !isBoundedIndex(failure.index, requestedCount)) return false;
|
|
253
|
+
if (!isImageGenerationFailureCode(failure.code) || !isBoundedString(failure.message, MAX_IMAGE_DIAGNOSTIC_CHARS)) return false;
|
|
254
|
+
if (indexes.has(failure.index)) return false;
|
|
255
|
+
indexes.add(failure.index);
|
|
256
|
+
return true;
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
export function isImageGenerationToolDetails(value: unknown): value is ImageGenerationToolDetails {
|
|
261
|
+
return isImageGenerationDetails(value) || isImageGenerationBatchDetails(value);
|
|
262
|
+
}
|
|
263
|
+
|
|
173
264
|
export function sanitizeImageDiagnostic(value: unknown, fallback: string): string {
|
|
174
265
|
if (typeof value !== "string") return fallback;
|
|
175
266
|
const normalized = value
|
package/src/responses-headers.ts
CHANGED
|
@@ -90,11 +90,9 @@ export function buildCodexCliUserAgent(version: string): string {
|
|
|
90
90
|
}
|
|
91
91
|
|
|
92
92
|
/**
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
*
|
|
96
|
-
* requests that name it. Keep this in sync with the newest gate Oh My Pi pins
|
|
97
|
-
* in @oh-my-pi/pi-wire/codex (`CODEX_CLIENT_VERSION`).
|
|
93
|
+
* Codex client identity used by synthetic gateway requests in their User-Agent.
|
|
94
|
+
* Native live and context-management requests do not generate a `version`
|
|
95
|
+
* header; explicitly configured provider values remain caller-owned.
|
|
98
96
|
*/
|
|
99
97
|
export const CODEX_CLIENT_VERSION = "0.153.0";
|
|
100
98
|
|
|
@@ -109,7 +107,8 @@ export function clampCodexSessionId(sessionId: string | undefined): string | und
|
|
|
109
107
|
|
|
110
108
|
export type ResponsesHeaderOptions = {
|
|
111
109
|
accept: string;
|
|
112
|
-
|
|
110
|
+
/** `null` omits content-type so a multipart FormData body can set its boundary. */
|
|
111
|
+
contentType?: string | null;
|
|
113
112
|
/** Conversation identity for Codex cache-affinity headers; clamped to 64 chars here. */
|
|
114
113
|
sessionId?: string;
|
|
115
114
|
};
|
|
@@ -123,7 +122,11 @@ export function buildResponsesRequestHeaders(
|
|
|
123
122
|
runtime.codexAffinity ? filterCodexGatewayHeaders(mergedHeaders) : mergedHeaders,
|
|
124
123
|
);
|
|
125
124
|
headers.set("accept", options.accept);
|
|
126
|
-
|
|
125
|
+
if (options.contentType === null) {
|
|
126
|
+
headers.delete("content-type");
|
|
127
|
+
} else {
|
|
128
|
+
headers.set("content-type", options.contentType ?? "application/json");
|
|
129
|
+
}
|
|
127
130
|
if (!headers.has("authorization")) {
|
|
128
131
|
headers.set("authorization", `Bearer ${runtime.apiKey}`);
|
|
129
132
|
}
|
|
@@ -148,8 +151,6 @@ export function buildResponsesRequestHeaders(
|
|
|
148
151
|
headers.set("originator", "pi");
|
|
149
152
|
headers.set("user-agent", buildCodexUserAgent());
|
|
150
153
|
headers.set("openai-beta", "responses=experimental");
|
|
151
|
-
// Preserve the existing native Codex compatibility pin.
|
|
152
|
-
headers.set("version", CODEX_CLIENT_VERSION);
|
|
153
154
|
}
|
|
154
155
|
// Gateways own their version policy. Explicit versions still pass through
|
|
155
156
|
// the header merge and allowlist, but Toolkit does not generate one.
|
package/src/types.ts
CHANGED
|
@@ -151,9 +151,9 @@ export type WebSearchConfig = {
|
|
|
151
151
|
export type ImageGenerationConfig = {
|
|
152
152
|
enabled: boolean;
|
|
153
153
|
/**
|
|
154
|
-
* Bare
|
|
155
|
-
* The first entry is the default; `openai_generate_image`'s optional `model` argument
|
|
156
|
-
* select any other entry in this order-preserving list.
|
|
154
|
+
* Bare Images API model ids this toolkit may send, without a `provider/` prefix.
|
|
155
|
+
* The first legacy entry is the default; `openai_generate_image`'s optional `model` argument
|
|
156
|
+
* may select any other entry in this order-preserving list.
|
|
157
157
|
*/
|
|
158
158
|
models: string[];
|
|
159
159
|
};
|