pi-openai-toolkit 0.17.3 → 0.19.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 +5 -5
- package/README.zh.md +5 -5
- package/config.schema.json +2 -1
- package/docs/configuration.md +7 -7
- package/package.json +5 -5
- package/src/context-management/codex-provider.ts +18 -8
- package/src/extension-runtime.ts +47 -26
- package/src/image-generation/artifacts.ts +28 -7
- package/src/image-generation/client.ts +72 -8
- package/src/image-generation/eligibility.ts +13 -8
- package/src/image-generation/extension.ts +22 -6
- package/src/image-generation/protocol.ts +228 -128
- package/src/image-generation/render.ts +16 -8
- package/src/image-generation/service.ts +384 -255
- package/src/image-generation/types.ts +117 -6
- package/src/payload-rewrite.ts +16 -1
- package/src/responses-headers.ts +13 -3
- package/src/runtime.ts +295 -28
- package/src/serializer.ts +214 -0
- package/src/supported-environment.ts +5 -0
- package/src/types.ts +9 -5
- package/src/web-search/extension.ts +4 -3
- package/src/web-search/types.ts +25 -3
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
export const IMAGE_GENERATION_TOOL_NAME = "openai_generate_image";
|
|
2
|
+
/** Image formats accepted from independent Images API responses. */
|
|
3
|
+
export const IMAGE_GENERATION_MIME_TYPES = ["image/png", "image/jpeg", "image/webp"] as const;
|
|
4
|
+
/** Backward-compatible default format for callers that construct PNG-only fixtures. */
|
|
2
5
|
export const IMAGE_GENERATION_MIME_TYPE = "image/png" as const;
|
|
3
6
|
export const IMAGE_GENERATION_CAPABLE_APIS = [
|
|
4
7
|
"openai-responses",
|
|
@@ -23,15 +26,28 @@ export const IMAGE_REQUEST_TIMEOUT_MS = 5 * 60 * 1000;
|
|
|
23
26
|
export const MAX_IMAGE_PATH_CHARS = 4096;
|
|
24
27
|
export const MAX_IMAGE_DIAGNOSTIC_CHARS = 4096;
|
|
25
28
|
export const MAX_IMAGE_IDENTIFIER_CHARS = 256;
|
|
26
|
-
/** Upper bound for a configured or requested
|
|
29
|
+
/** Upper bound for a configured or requested Images API model id. */
|
|
27
30
|
export const MAX_IMAGE_MODEL_ID_CHARS = 256;
|
|
31
|
+
export const MAX_IMAGE_BATCH_SIZE = 10;
|
|
28
32
|
export const MAX_IMAGE_DIMENSION = 100_000;
|
|
29
33
|
|
|
30
34
|
export type ImageGenerationCapableApi = (typeof IMAGE_GENERATION_CAPABLE_APIS)[number];
|
|
31
35
|
export type ImageGenerationSize = (typeof IMAGE_GENERATION_SIZES)[number];
|
|
32
36
|
export type ImageGenerationQuality = (typeof IMAGE_GENERATION_QUALITIES)[number];
|
|
33
37
|
export type ImageGenerationAction = "generate" | "edit";
|
|
34
|
-
export type
|
|
38
|
+
export type ImageGenerationMimeType = (typeof IMAGE_GENERATION_MIME_TYPES)[number];
|
|
39
|
+
export type ReferenceImageMimeType = ImageGenerationMimeType;
|
|
40
|
+
|
|
41
|
+
export function imageFileExtension(mimeType: ImageGenerationMimeType): "png" | "jpg" | "webp" {
|
|
42
|
+
switch (mimeType) {
|
|
43
|
+
case "image/png":
|
|
44
|
+
return "png";
|
|
45
|
+
case "image/jpeg":
|
|
46
|
+
return "jpg";
|
|
47
|
+
case "image/webp":
|
|
48
|
+
return "webp";
|
|
49
|
+
}
|
|
50
|
+
}
|
|
35
51
|
|
|
36
52
|
export type ImageGenerationModel = {
|
|
37
53
|
api?: string;
|
|
@@ -45,8 +61,10 @@ export type GenerateImageParams = {
|
|
|
45
61
|
outputPath?: string | null;
|
|
46
62
|
size?: ImageGenerationSize;
|
|
47
63
|
quality?: ImageGenerationQuality;
|
|
48
|
-
/** Optional bare image model id that must exist in
|
|
64
|
+
/** Optional bare image model id that must exist in the configured image model allowlist. */
|
|
49
65
|
model?: string | null;
|
|
66
|
+
/** Number of independent image requests to run concurrently. */
|
|
67
|
+
batchSize?: number | null;
|
|
50
68
|
};
|
|
51
69
|
|
|
52
70
|
export type NormalizedGenerateImageParams = {
|
|
@@ -56,6 +74,7 @@ export type NormalizedGenerateImageParams = {
|
|
|
56
74
|
size: ImageGenerationSize;
|
|
57
75
|
quality: ImageGenerationQuality;
|
|
58
76
|
action: ImageGenerationAction;
|
|
77
|
+
batchSize: number;
|
|
59
78
|
/** Trimmed requested model id, or undefined when the caller omitted it. */
|
|
60
79
|
model?: string;
|
|
61
80
|
};
|
|
@@ -68,6 +87,7 @@ export type PreparedReferenceImage = {
|
|
|
68
87
|
|
|
69
88
|
export type ParsedGeneratedImage = {
|
|
70
89
|
bytes: Buffer;
|
|
90
|
+
mimeType: ImageGenerationMimeType;
|
|
71
91
|
imageCallId: string;
|
|
72
92
|
responseId?: string;
|
|
73
93
|
revisedPrompt?: string;
|
|
@@ -82,7 +102,7 @@ export type ImageGenerationDetails = {
|
|
|
82
102
|
imageModel: string;
|
|
83
103
|
imageCallId: string;
|
|
84
104
|
responseId?: string;
|
|
85
|
-
mimeType:
|
|
105
|
+
mimeType: ImageGenerationMimeType;
|
|
86
106
|
byteCount: number;
|
|
87
107
|
width: number;
|
|
88
108
|
height: number;
|
|
@@ -92,8 +112,25 @@ export type ImageGenerationDetails = {
|
|
|
92
112
|
warning?: string;
|
|
93
113
|
};
|
|
94
114
|
|
|
115
|
+
export type ImageGenerationBatchFailure = {
|
|
116
|
+
index: number;
|
|
117
|
+
code: ImageGenerationFailureCode;
|
|
118
|
+
message: string;
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
export type ImageGenerationBatchDetails = {
|
|
122
|
+
batch: true;
|
|
123
|
+
requestedCount: number;
|
|
124
|
+
succeededCount: number;
|
|
125
|
+
failedCount: number;
|
|
126
|
+
images: ImageGenerationDetails[];
|
|
127
|
+
failures?: ImageGenerationBatchFailure[];
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
export type ImageGenerationToolDetails = ImageGenerationDetails | ImageGenerationBatchDetails;
|
|
131
|
+
|
|
95
132
|
export type ImageGenerationExecutionResult = {
|
|
96
|
-
details:
|
|
133
|
+
details: ImageGenerationToolDetails;
|
|
97
134
|
text: string;
|
|
98
135
|
};
|
|
99
136
|
|
|
@@ -120,6 +157,35 @@ export type ImageGenerationFailureCode =
|
|
|
120
157
|
| "no-image"
|
|
121
158
|
| "artifact-write-failed";
|
|
122
159
|
|
|
160
|
+
function isImageGenerationFailureCode(value: unknown): value is ImageGenerationFailureCode {
|
|
161
|
+
switch (value) {
|
|
162
|
+
case "aborted":
|
|
163
|
+
case "timeout":
|
|
164
|
+
case "disabled":
|
|
165
|
+
case "unsupported-model":
|
|
166
|
+
case "missing-runtime":
|
|
167
|
+
case "invalid-parameters":
|
|
168
|
+
case "reference-input-invalid":
|
|
169
|
+
case "reference-upload-approval-required":
|
|
170
|
+
case "reference-upload-declined":
|
|
171
|
+
case "output-path-invalid":
|
|
172
|
+
case "output-path-approval-required":
|
|
173
|
+
case "output-path-declined":
|
|
174
|
+
case "authentication":
|
|
175
|
+
case "rate-limit":
|
|
176
|
+
case "request-rejected":
|
|
177
|
+
case "backend-unavailable":
|
|
178
|
+
case "network":
|
|
179
|
+
case "oversized-response":
|
|
180
|
+
case "malformed-response":
|
|
181
|
+
case "no-image":
|
|
182
|
+
case "artifact-write-failed":
|
|
183
|
+
return true;
|
|
184
|
+
default:
|
|
185
|
+
return false;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
123
189
|
export class ImageGenerationError extends Error {
|
|
124
190
|
readonly code: ImageGenerationFailureCode;
|
|
125
191
|
|
|
@@ -138,6 +204,10 @@ function isBoundedString(value: unknown, maxChars: number): value is string {
|
|
|
138
204
|
return typeof value === "string" && value.trim().length > 0 && value.length <= maxChars;
|
|
139
205
|
}
|
|
140
206
|
|
|
207
|
+
function isImageGenerationMimeType(value: unknown): value is ImageGenerationMimeType {
|
|
208
|
+
return (IMAGE_GENERATION_MIME_TYPES as readonly string[]).includes(value as string);
|
|
209
|
+
}
|
|
210
|
+
|
|
141
211
|
export function isImageGenerationDetails(value: unknown): value is ImageGenerationDetails {
|
|
142
212
|
if (!isRecord(value)) return false;
|
|
143
213
|
return (
|
|
@@ -147,7 +217,7 @@ export function isImageGenerationDetails(value: unknown): value is ImageGenerati
|
|
|
147
217
|
isBoundedString(value.imageModel, MAX_IMAGE_MODEL_ID_CHARS) &&
|
|
148
218
|
isBoundedString(value.imageCallId, MAX_IMAGE_IDENTIFIER_CHARS) &&
|
|
149
219
|
(value.responseId === undefined || isBoundedString(value.responseId, MAX_IMAGE_IDENTIFIER_CHARS)) &&
|
|
150
|
-
value.mimeType
|
|
220
|
+
isImageGenerationMimeType(value.mimeType) &&
|
|
151
221
|
typeof value.byteCount === "number" &&
|
|
152
222
|
Number.isInteger(value.byteCount) &&
|
|
153
223
|
value.byteCount > 0 &&
|
|
@@ -170,6 +240,47 @@ export function isImageGenerationDetails(value: unknown): value is ImageGenerati
|
|
|
170
240
|
);
|
|
171
241
|
}
|
|
172
242
|
|
|
243
|
+
function isBoundedIndex(value: unknown, maximum: number): value is number {
|
|
244
|
+
return typeof value === "number" && Number.isInteger(value) && value >= 0 && value < maximum;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
export function isImageGenerationBatchDetails(value: unknown): value is ImageGenerationBatchDetails {
|
|
248
|
+
if (!isRecord(value) || value.batch !== true) return false;
|
|
249
|
+
if (
|
|
250
|
+
typeof value.requestedCount !== "number" ||
|
|
251
|
+
!Number.isInteger(value.requestedCount) ||
|
|
252
|
+
value.requestedCount < 2 ||
|
|
253
|
+
value.requestedCount > MAX_IMAGE_BATCH_SIZE ||
|
|
254
|
+
typeof value.succeededCount !== "number" ||
|
|
255
|
+
!Number.isInteger(value.succeededCount) ||
|
|
256
|
+
value.succeededCount < 1 ||
|
|
257
|
+
value.succeededCount > value.requestedCount ||
|
|
258
|
+
typeof value.failedCount !== "number" ||
|
|
259
|
+
!Number.isInteger(value.failedCount) ||
|
|
260
|
+
value.failedCount !== value.requestedCount - value.succeededCount ||
|
|
261
|
+
!Array.isArray(value.images) ||
|
|
262
|
+
value.images.length !== value.succeededCount ||
|
|
263
|
+
!value.images.every(isImageGenerationDetails)
|
|
264
|
+
) {
|
|
265
|
+
return false;
|
|
266
|
+
}
|
|
267
|
+
if (value.failedCount === 0) return value.failures === undefined;
|
|
268
|
+
if (!Array.isArray(value.failures) || value.failures.length !== value.failedCount) return false;
|
|
269
|
+
const requestedCount = value.requestedCount;
|
|
270
|
+
const indexes = new Set<number>();
|
|
271
|
+
return value.failures.every((failure) => {
|
|
272
|
+
if (!isRecord(failure) || !isBoundedIndex(failure.index, requestedCount)) return false;
|
|
273
|
+
if (!isImageGenerationFailureCode(failure.code) || !isBoundedString(failure.message, MAX_IMAGE_DIAGNOSTIC_CHARS)) return false;
|
|
274
|
+
if (indexes.has(failure.index)) return false;
|
|
275
|
+
indexes.add(failure.index);
|
|
276
|
+
return true;
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
export function isImageGenerationToolDetails(value: unknown): value is ImageGenerationToolDetails {
|
|
281
|
+
return isImageGenerationDetails(value) || isImageGenerationBatchDetails(value);
|
|
282
|
+
}
|
|
283
|
+
|
|
173
284
|
export function sanitizeImageDiagnostic(value: unknown, fallback: string): string {
|
|
174
285
|
if (typeof value !== "string") return fallback;
|
|
175
286
|
const normalized = value
|
package/src/payload-rewrite.ts
CHANGED
|
@@ -9,7 +9,13 @@ import type {
|
|
|
9
9
|
NativeCompactionEntry,
|
|
10
10
|
NativeCompactionInputProvenance,
|
|
11
11
|
} from "./types";
|
|
12
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
serializeMessagesToResponsesInput,
|
|
14
|
+
serializeMessagesToResponsesInputWithLimits,
|
|
15
|
+
type ResponsesInputContentItem,
|
|
16
|
+
type ResponsesInputItem,
|
|
17
|
+
type ResponsesInputMessageItem,
|
|
18
|
+
} from "./serializer";
|
|
13
19
|
|
|
14
20
|
export type NativeReplaySegments = {
|
|
15
21
|
boundaryIndex: number;
|
|
@@ -369,6 +375,15 @@ export function serializeLiveTailToResponsesInput<TApi extends Api>(args: {
|
|
|
369
375
|
});
|
|
370
376
|
}
|
|
371
377
|
|
|
378
|
+
export async function serializeLiveTailToResponsesInputWithLimits<TApi extends Api>(args: {
|
|
379
|
+
model: Model<TApi>;
|
|
380
|
+
entries: readonly SessionEntry[];
|
|
381
|
+
}): Promise<ResponsesInputItem[]> {
|
|
382
|
+
return serializeMessagesToResponsesInputWithLimits(args.model, collectReplayMessages(args.entries), {
|
|
383
|
+
firstSystemMessageIsUpdate: true,
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
|
|
372
387
|
function buildNativeReplaySegmentsInternal<TApi extends Api>(args: {
|
|
373
388
|
model: Model<TApi>;
|
|
374
389
|
payload: ResponsesCompatibleRequestPayload;
|
package/src/responses-headers.ts
CHANGED
|
@@ -107,7 +107,8 @@ export function clampCodexSessionId(sessionId: string | undefined): string | und
|
|
|
107
107
|
|
|
108
108
|
export type ResponsesHeaderOptions = {
|
|
109
109
|
accept: string;
|
|
110
|
-
|
|
110
|
+
/** `null` omits content-type so a multipart FormData body can set its boundary. */
|
|
111
|
+
contentType?: string | null;
|
|
111
112
|
/** Conversation identity for Codex cache-affinity headers; clamped to 64 chars here. */
|
|
112
113
|
sessionId?: string;
|
|
113
114
|
};
|
|
@@ -121,8 +122,17 @@ export function buildResponsesRequestHeaders(
|
|
|
121
122
|
runtime.codexAffinity ? filterCodexGatewayHeaders(mergedHeaders) : mergedHeaders,
|
|
122
123
|
);
|
|
123
124
|
headers.set("accept", options.accept);
|
|
124
|
-
|
|
125
|
-
|
|
125
|
+
if (options.contentType === null) {
|
|
126
|
+
headers.delete("content-type");
|
|
127
|
+
} else {
|
|
128
|
+
headers.set("content-type", options.contentType ?? "application/json");
|
|
129
|
+
}
|
|
130
|
+
if (runtime.api === "azure-openai-responses") {
|
|
131
|
+
// Azure OpenAI authenticates Responses requests with `api-key`; do not
|
|
132
|
+
// forward an inherited bearer token from a model/provider header.
|
|
133
|
+
headers.delete("authorization");
|
|
134
|
+
headers.set("api-key", runtime.apiKey);
|
|
135
|
+
} else if (!headers.has("authorization")) {
|
|
126
136
|
headers.set("authorization", `Bearer ${runtime.apiKey}`);
|
|
127
137
|
}
|
|
128
138
|
|