pi-openai-toolkit 0.14.4 → 0.14.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.
- package/README.md +119 -147
- package/README.zh.md +119 -151
- package/docs/internals.md +42 -21
- package/package.json +127 -127
- package/src/auto-mode/classifier.ts +165 -165
- package/src/auto-mode/policy.ts +146 -146
- package/src/auto-mode/prompt.ts +279 -279
- package/src/auto-mode/reviewer.ts +325 -325
- package/src/auto-mode/transcript.ts +133 -133
- package/src/auto-mode/types.ts +137 -137
- package/src/codex-astra/effort-planner.ts +175 -175
- package/src/codex-astra/extension.ts +122 -122
- package/src/config.ts +621 -592
- package/src/context-management/codex-provider.ts +260 -260
- package/src/context-management/history-notes.ts +357 -325
- package/src/context-management/messages.ts +112 -106
- package/src/context-management/namespace-tools.ts +426 -426
- package/src/context-management/tool-contract.ts +23 -23
- package/src/context-management/tools.ts +306 -311
- package/src/context-management/types.ts +168 -161
- package/src/context-management/window-budget.ts +83 -83
- package/src/context-management/window-manager.ts +60 -51
- package/src/context-management/window-request.ts +125 -125
- package/src/debug.ts +1 -1
- package/src/extension-runtime.ts +34 -9
- package/src/image-generation/artifacts.ts +237 -237
- package/src/image-generation/client.ts +248 -248
- package/src/image-generation/eligibility.ts +18 -18
- package/src/image-generation/extension.ts +19 -2
- package/src/image-generation/protocol.ts +473 -405
- package/src/image-generation/references.ts +194 -194
- package/src/image-generation/render.ts +252 -252
- package/src/image-generation/service.ts +249 -243
- package/src/image-generation/types.ts +189 -184
- package/src/model-scope.ts +17 -17
- package/src/provider-headers.ts +23 -23
- package/src/responses-headers.ts +173 -108
- package/src/runtime.ts +513 -477
- package/src/types.ts +11 -0
- package/src/web-search/extension.ts +80 -80
- package/src/web-search/payload.ts +96 -96
- package/src/web-search/prompt.ts +20 -20
- package/src/web-search/types.ts +45 -45
|
@@ -1,248 +1,248 @@
|
|
|
1
|
-
import { buildResponsesRequestHeaders } from "../responses-headers";
|
|
2
|
-
import type { ResponsesRuntime } from "../runtime";
|
|
3
|
-
import {
|
|
4
|
-
extractProviderErrorMessage,
|
|
5
|
-
parseImageGenerationResponse,
|
|
6
|
-
type ImageGenerationRequestBody,
|
|
7
|
-
} from "./protocol";
|
|
8
|
-
import {
|
|
9
|
-
IMAGE_REQUEST_TIMEOUT_MS,
|
|
10
|
-
MAX_IMAGE_ERROR_BYTES,
|
|
11
|
-
MAX_IMAGE_RESPONSE_BYTES,
|
|
12
|
-
sanitizeImageDiagnostic,
|
|
13
|
-
type ParsedGeneratedImage,
|
|
14
|
-
} from "./types";
|
|
15
|
-
|
|
16
|
-
export type ImageGenerationClientFailureReason =
|
|
17
|
-
| "aborted"
|
|
18
|
-
| "timeout"
|
|
19
|
-
| "authentication"
|
|
20
|
-
| "rate-limit"
|
|
21
|
-
| "request-rejected"
|
|
22
|
-
| "backend-unavailable"
|
|
23
|
-
| "network"
|
|
24
|
-
| "oversized-response"
|
|
25
|
-
| "malformed-response"
|
|
26
|
-
| "no-image";
|
|
27
|
-
|
|
28
|
-
export type ImageGenerationClientResult =
|
|
29
|
-
| { ok: true; image: ParsedGeneratedImage; status: number }
|
|
30
|
-
| {
|
|
31
|
-
ok: false;
|
|
32
|
-
reason: ImageGenerationClientFailureReason;
|
|
33
|
-
status?: number;
|
|
34
|
-
errorMessage: string;
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
export type ImageGenerationFetch = (
|
|
38
|
-
input: string | URL | Request,
|
|
39
|
-
init?: RequestInit,
|
|
40
|
-
) => Promise<Response>;
|
|
41
|
-
|
|
42
|
-
function buildRequestHeaders(runtime: ResponsesRuntime): Headers {
|
|
43
|
-
return buildResponsesRequestHeaders(runtime, { accept: "application/json" });
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
async function readBoundedBody(
|
|
47
|
-
response: Response,
|
|
48
|
-
maxBytes: number,
|
|
49
|
-
): Promise<{ ok: true; bytes: Uint8Array } | { ok: false }> {
|
|
50
|
-
const contentLength = response.headers.get("content-length");
|
|
51
|
-
if (contentLength !== null) {
|
|
52
|
-
const parsed = Number(contentLength);
|
|
53
|
-
if (Number.isFinite(parsed) && parsed > maxBytes) return { ok: false };
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
if (!response.body) return { ok: true, bytes: new Uint8Array() };
|
|
57
|
-
const reader = response.body.getReader();
|
|
58
|
-
const chunks: Uint8Array[] = [];
|
|
59
|
-
let total = 0;
|
|
60
|
-
try {
|
|
61
|
-
while (true) {
|
|
62
|
-
const next = await reader.read();
|
|
63
|
-
if (next.done) break;
|
|
64
|
-
if (!next.value) continue;
|
|
65
|
-
total += next.value.byteLength;
|
|
66
|
-
if (total > maxBytes) {
|
|
67
|
-
await reader.cancel().catch(() => undefined);
|
|
68
|
-
return { ok: false };
|
|
69
|
-
}
|
|
70
|
-
chunks.push(next.value);
|
|
71
|
-
}
|
|
72
|
-
} finally {
|
|
73
|
-
reader.releaseLock();
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
const bytes = new Uint8Array(total);
|
|
77
|
-
let offset = 0;
|
|
78
|
-
for (const chunk of chunks) {
|
|
79
|
-
bytes.set(chunk, offset);
|
|
80
|
-
offset += chunk.byteLength;
|
|
81
|
-
}
|
|
82
|
-
return { ok: true, bytes };
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
function parseJson(bytes: Uint8Array): unknown {
|
|
86
|
-
const text = new TextDecoder().decode(bytes);
|
|
87
|
-
return JSON.parse(text) as unknown;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
function providerFailureText(value: unknown): string {
|
|
91
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) return "";
|
|
92
|
-
const candidate = value as Record<string, unknown>;
|
|
93
|
-
const error = candidate.error && typeof candidate.error === "object" && !Array.isArray(candidate.error)
|
|
94
|
-
? candidate.error as Record<string, unknown>
|
|
95
|
-
: candidate;
|
|
96
|
-
return [error.code, error.type, error.message]
|
|
97
|
-
.filter((item): item is string => typeof item === "string")
|
|
98
|
-
.join(" ")
|
|
99
|
-
.toLowerCase();
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
function mapHttpFailure(status: number, payload?: unknown): ImageGenerationClientFailureReason {
|
|
103
|
-
if (status === 401 || status === 403) return "authentication";
|
|
104
|
-
const providerFailure = providerFailureText(payload);
|
|
105
|
-
if (
|
|
106
|
-
status === 429 ||
|
|
107
|
-
/(?:rate|usage|billing)[-_ ]?limit|quota|insufficient[-_ ]?(?:quota|credit)/.test(providerFailure)
|
|
108
|
-
) {
|
|
109
|
-
return "rate-limit";
|
|
110
|
-
}
|
|
111
|
-
if (status === 400 || status === 404 || status === 409 || status === 422) {
|
|
112
|
-
return "request-rejected";
|
|
113
|
-
}
|
|
114
|
-
if (status >= 500) return "backend-unavailable";
|
|
115
|
-
return "request-rejected";
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
function defaultHttpMessage(status: number, reason = mapHttpFailure(status)): string {
|
|
119
|
-
switch (reason) {
|
|
120
|
-
case "authentication":
|
|
121
|
-
return "Image generation authentication failed for the current model provider.";
|
|
122
|
-
case "rate-limit":
|
|
123
|
-
return "Image generation was rate-limited or the provider image quota was exhausted.";
|
|
124
|
-
case "backend-unavailable":
|
|
125
|
-
return "Image generation is temporarily unavailable at the configured gateway or upstream provider.";
|
|
126
|
-
default:
|
|
127
|
-
return `Image generation request was rejected (HTTP ${status}).`;
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
export async function requestGeneratedImage(args: {
|
|
132
|
-
runtime: ResponsesRuntime;
|
|
133
|
-
body: ImageGenerationRequestBody;
|
|
134
|
-
signal?: AbortSignal;
|
|
135
|
-
fetchFn?: ImageGenerationFetch;
|
|
136
|
-
timeoutMs?: number;
|
|
137
|
-
}): Promise<ImageGenerationClientResult> {
|
|
138
|
-
const fetchFn = args.fetchFn ?? globalThis.fetch;
|
|
139
|
-
const timeoutSignal = AbortSignal.timeout(args.timeoutMs ?? IMAGE_REQUEST_TIMEOUT_MS);
|
|
140
|
-
const signal = args.signal
|
|
141
|
-
? AbortSignal.any([args.signal, timeoutSignal])
|
|
142
|
-
: timeoutSignal;
|
|
143
|
-
|
|
144
|
-
let response: Response;
|
|
145
|
-
try {
|
|
146
|
-
response = await fetchFn(args.runtime.responsesUrl, {
|
|
147
|
-
method: "POST",
|
|
148
|
-
headers: buildRequestHeaders(args.runtime),
|
|
149
|
-
body: JSON.stringify(args.body),
|
|
150
|
-
signal,
|
|
151
|
-
});
|
|
152
|
-
} catch (error) {
|
|
153
|
-
if (args.signal?.aborted) {
|
|
154
|
-
return { ok: false, reason: "aborted", errorMessage: "Image generation was cancelled." };
|
|
155
|
-
}
|
|
156
|
-
if (timeoutSignal.aborted) {
|
|
157
|
-
return {
|
|
158
|
-
ok: false,
|
|
159
|
-
reason: "timeout",
|
|
160
|
-
errorMessage: "Image generation timed out. The request was not retried automatically.",
|
|
161
|
-
};
|
|
162
|
-
}
|
|
163
|
-
return {
|
|
164
|
-
ok: false,
|
|
165
|
-
reason: "network",
|
|
166
|
-
errorMessage: sanitizeImageDiagnostic(
|
|
167
|
-
error instanceof Error ? error.message : error,
|
|
168
|
-
"Image generation network request failed. The request was not retried automatically.",
|
|
169
|
-
),
|
|
170
|
-
};
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
let bounded: Awaited<ReturnType<typeof readBoundedBody>>;
|
|
174
|
-
try {
|
|
175
|
-
bounded = await readBoundedBody(
|
|
176
|
-
response,
|
|
177
|
-
response.ok ? MAX_IMAGE_RESPONSE_BYTES : MAX_IMAGE_ERROR_BYTES,
|
|
178
|
-
);
|
|
179
|
-
} catch (error) {
|
|
180
|
-
if (args.signal?.aborted) {
|
|
181
|
-
return { ok: false, reason: "aborted", errorMessage: "Image generation was cancelled." };
|
|
182
|
-
}
|
|
183
|
-
if (timeoutSignal.aborted) {
|
|
184
|
-
return {
|
|
185
|
-
ok: false,
|
|
186
|
-
reason: "timeout",
|
|
187
|
-
errorMessage: "Image generation timed out. The request was not retried automatically.",
|
|
188
|
-
};
|
|
189
|
-
}
|
|
190
|
-
return {
|
|
191
|
-
ok: false,
|
|
192
|
-
reason: "network",
|
|
193
|
-
status: response.status,
|
|
194
|
-
errorMessage: sanitizeImageDiagnostic(
|
|
195
|
-
error instanceof Error ? error.message : error,
|
|
196
|
-
"Image generation response could not be read. The request was not retried automatically.",
|
|
197
|
-
),
|
|
198
|
-
};
|
|
199
|
-
}
|
|
200
|
-
if (!bounded.ok) {
|
|
201
|
-
return {
|
|
202
|
-
ok: false,
|
|
203
|
-
reason: "oversized-response",
|
|
204
|
-
status: response.status,
|
|
205
|
-
errorMessage: "Image generation response exceeded the configured size limit.",
|
|
206
|
-
};
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
let payload: unknown;
|
|
210
|
-
try {
|
|
211
|
-
payload = parseJson(bounded.bytes);
|
|
212
|
-
} catch {
|
|
213
|
-
return {
|
|
214
|
-
ok: false,
|
|
215
|
-
reason: "malformed-response",
|
|
216
|
-
status: response.status,
|
|
217
|
-
errorMessage: response.ok
|
|
218
|
-
? "Image generation returned invalid JSON."
|
|
219
|
-
: defaultHttpMessage(response.status),
|
|
220
|
-
};
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
if (!response.ok) {
|
|
224
|
-
const reason = mapHttpFailure(response.status, payload);
|
|
225
|
-
return {
|
|
226
|
-
ok: false,
|
|
227
|
-
reason,
|
|
228
|
-
status: response.status,
|
|
229
|
-
errorMessage: extractProviderErrorMessage(payload, defaultHttpMessage(response.status, reason)),
|
|
230
|
-
};
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
const parsed = parseImageGenerationResponse(payload);
|
|
234
|
-
if (!parsed.ok) {
|
|
235
|
-
return {
|
|
236
|
-
ok: false,
|
|
237
|
-
reason: parsed.reason,
|
|
238
|
-
status: response.status,
|
|
239
|
-
errorMessage: parsed.errorMessage,
|
|
240
|
-
};
|
|
241
|
-
}
|
|
242
|
-
return { ok: true, image: parsed.image, status: response.status };
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
export const _clientTest = {
|
|
246
|
-
buildRequestHeaders,
|
|
247
|
-
readBoundedBody,
|
|
248
|
-
};
|
|
1
|
+
import { buildResponsesRequestHeaders } from "../responses-headers";
|
|
2
|
+
import type { ResponsesRuntime } from "../runtime";
|
|
3
|
+
import {
|
|
4
|
+
extractProviderErrorMessage,
|
|
5
|
+
parseImageGenerationResponse,
|
|
6
|
+
type ImageGenerationRequestBody,
|
|
7
|
+
} from "./protocol";
|
|
8
|
+
import {
|
|
9
|
+
IMAGE_REQUEST_TIMEOUT_MS,
|
|
10
|
+
MAX_IMAGE_ERROR_BYTES,
|
|
11
|
+
MAX_IMAGE_RESPONSE_BYTES,
|
|
12
|
+
sanitizeImageDiagnostic,
|
|
13
|
+
type ParsedGeneratedImage,
|
|
14
|
+
} from "./types";
|
|
15
|
+
|
|
16
|
+
export type ImageGenerationClientFailureReason =
|
|
17
|
+
| "aborted"
|
|
18
|
+
| "timeout"
|
|
19
|
+
| "authentication"
|
|
20
|
+
| "rate-limit"
|
|
21
|
+
| "request-rejected"
|
|
22
|
+
| "backend-unavailable"
|
|
23
|
+
| "network"
|
|
24
|
+
| "oversized-response"
|
|
25
|
+
| "malformed-response"
|
|
26
|
+
| "no-image";
|
|
27
|
+
|
|
28
|
+
export type ImageGenerationClientResult =
|
|
29
|
+
| { ok: true; image: ParsedGeneratedImage; status: number }
|
|
30
|
+
| {
|
|
31
|
+
ok: false;
|
|
32
|
+
reason: ImageGenerationClientFailureReason;
|
|
33
|
+
status?: number;
|
|
34
|
+
errorMessage: string;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export type ImageGenerationFetch = (
|
|
38
|
+
input: string | URL | Request,
|
|
39
|
+
init?: RequestInit,
|
|
40
|
+
) => Promise<Response>;
|
|
41
|
+
|
|
42
|
+
function buildRequestHeaders(runtime: ResponsesRuntime): Headers {
|
|
43
|
+
return buildResponsesRequestHeaders(runtime, { accept: "application/json" });
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async function readBoundedBody(
|
|
47
|
+
response: Response,
|
|
48
|
+
maxBytes: number,
|
|
49
|
+
): Promise<{ ok: true; bytes: Uint8Array } | { ok: false }> {
|
|
50
|
+
const contentLength = response.headers.get("content-length");
|
|
51
|
+
if (contentLength !== null) {
|
|
52
|
+
const parsed = Number(contentLength);
|
|
53
|
+
if (Number.isFinite(parsed) && parsed > maxBytes) return { ok: false };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (!response.body) return { ok: true, bytes: new Uint8Array() };
|
|
57
|
+
const reader = response.body.getReader();
|
|
58
|
+
const chunks: Uint8Array[] = [];
|
|
59
|
+
let total = 0;
|
|
60
|
+
try {
|
|
61
|
+
while (true) {
|
|
62
|
+
const next = await reader.read();
|
|
63
|
+
if (next.done) break;
|
|
64
|
+
if (!next.value) continue;
|
|
65
|
+
total += next.value.byteLength;
|
|
66
|
+
if (total > maxBytes) {
|
|
67
|
+
await reader.cancel().catch(() => undefined);
|
|
68
|
+
return { ok: false };
|
|
69
|
+
}
|
|
70
|
+
chunks.push(next.value);
|
|
71
|
+
}
|
|
72
|
+
} finally {
|
|
73
|
+
reader.releaseLock();
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const bytes = new Uint8Array(total);
|
|
77
|
+
let offset = 0;
|
|
78
|
+
for (const chunk of chunks) {
|
|
79
|
+
bytes.set(chunk, offset);
|
|
80
|
+
offset += chunk.byteLength;
|
|
81
|
+
}
|
|
82
|
+
return { ok: true, bytes };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function parseJson(bytes: Uint8Array): unknown {
|
|
86
|
+
const text = new TextDecoder().decode(bytes);
|
|
87
|
+
return JSON.parse(text) as unknown;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function providerFailureText(value: unknown): string {
|
|
91
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return "";
|
|
92
|
+
const candidate = value as Record<string, unknown>;
|
|
93
|
+
const error = candidate.error && typeof candidate.error === "object" && !Array.isArray(candidate.error)
|
|
94
|
+
? candidate.error as Record<string, unknown>
|
|
95
|
+
: candidate;
|
|
96
|
+
return [error.code, error.type, error.message]
|
|
97
|
+
.filter((item): item is string => typeof item === "string")
|
|
98
|
+
.join(" ")
|
|
99
|
+
.toLowerCase();
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function mapHttpFailure(status: number, payload?: unknown): ImageGenerationClientFailureReason {
|
|
103
|
+
if (status === 401 || status === 403) return "authentication";
|
|
104
|
+
const providerFailure = providerFailureText(payload);
|
|
105
|
+
if (
|
|
106
|
+
status === 429 ||
|
|
107
|
+
/(?:rate|usage|billing)[-_ ]?limit|quota|insufficient[-_ ]?(?:quota|credit)/.test(providerFailure)
|
|
108
|
+
) {
|
|
109
|
+
return "rate-limit";
|
|
110
|
+
}
|
|
111
|
+
if (status === 400 || status === 404 || status === 409 || status === 422) {
|
|
112
|
+
return "request-rejected";
|
|
113
|
+
}
|
|
114
|
+
if (status >= 500) return "backend-unavailable";
|
|
115
|
+
return "request-rejected";
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function defaultHttpMessage(status: number, reason = mapHttpFailure(status)): string {
|
|
119
|
+
switch (reason) {
|
|
120
|
+
case "authentication":
|
|
121
|
+
return "Image generation authentication failed for the current model provider.";
|
|
122
|
+
case "rate-limit":
|
|
123
|
+
return "Image generation was rate-limited or the provider image quota was exhausted.";
|
|
124
|
+
case "backend-unavailable":
|
|
125
|
+
return "Image generation is temporarily unavailable at the configured gateway or upstream provider.";
|
|
126
|
+
default:
|
|
127
|
+
return `Image generation request was rejected (HTTP ${status}).`;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export async function requestGeneratedImage(args: {
|
|
132
|
+
runtime: ResponsesRuntime;
|
|
133
|
+
body: ImageGenerationRequestBody;
|
|
134
|
+
signal?: AbortSignal;
|
|
135
|
+
fetchFn?: ImageGenerationFetch;
|
|
136
|
+
timeoutMs?: number;
|
|
137
|
+
}): Promise<ImageGenerationClientResult> {
|
|
138
|
+
const fetchFn = args.fetchFn ?? globalThis.fetch;
|
|
139
|
+
const timeoutSignal = AbortSignal.timeout(args.timeoutMs ?? IMAGE_REQUEST_TIMEOUT_MS);
|
|
140
|
+
const signal = args.signal
|
|
141
|
+
? AbortSignal.any([args.signal, timeoutSignal])
|
|
142
|
+
: timeoutSignal;
|
|
143
|
+
|
|
144
|
+
let response: Response;
|
|
145
|
+
try {
|
|
146
|
+
response = await fetchFn(args.runtime.responsesUrl, {
|
|
147
|
+
method: "POST",
|
|
148
|
+
headers: buildRequestHeaders(args.runtime),
|
|
149
|
+
body: JSON.stringify(args.body),
|
|
150
|
+
signal,
|
|
151
|
+
});
|
|
152
|
+
} catch (error) {
|
|
153
|
+
if (args.signal?.aborted) {
|
|
154
|
+
return { ok: false, reason: "aborted", errorMessage: "Image generation was cancelled." };
|
|
155
|
+
}
|
|
156
|
+
if (timeoutSignal.aborted) {
|
|
157
|
+
return {
|
|
158
|
+
ok: false,
|
|
159
|
+
reason: "timeout",
|
|
160
|
+
errorMessage: "Image generation timed out. The request was not retried automatically.",
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
return {
|
|
164
|
+
ok: false,
|
|
165
|
+
reason: "network",
|
|
166
|
+
errorMessage: sanitizeImageDiagnostic(
|
|
167
|
+
error instanceof Error ? error.message : error,
|
|
168
|
+
"Image generation network request failed. The request was not retried automatically.",
|
|
169
|
+
),
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
let bounded: Awaited<ReturnType<typeof readBoundedBody>>;
|
|
174
|
+
try {
|
|
175
|
+
bounded = await readBoundedBody(
|
|
176
|
+
response,
|
|
177
|
+
response.ok ? MAX_IMAGE_RESPONSE_BYTES : MAX_IMAGE_ERROR_BYTES,
|
|
178
|
+
);
|
|
179
|
+
} catch (error) {
|
|
180
|
+
if (args.signal?.aborted) {
|
|
181
|
+
return { ok: false, reason: "aborted", errorMessage: "Image generation was cancelled." };
|
|
182
|
+
}
|
|
183
|
+
if (timeoutSignal.aborted) {
|
|
184
|
+
return {
|
|
185
|
+
ok: false,
|
|
186
|
+
reason: "timeout",
|
|
187
|
+
errorMessage: "Image generation timed out. The request was not retried automatically.",
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
return {
|
|
191
|
+
ok: false,
|
|
192
|
+
reason: "network",
|
|
193
|
+
status: response.status,
|
|
194
|
+
errorMessage: sanitizeImageDiagnostic(
|
|
195
|
+
error instanceof Error ? error.message : error,
|
|
196
|
+
"Image generation response could not be read. The request was not retried automatically.",
|
|
197
|
+
),
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
if (!bounded.ok) {
|
|
201
|
+
return {
|
|
202
|
+
ok: false,
|
|
203
|
+
reason: "oversized-response",
|
|
204
|
+
status: response.status,
|
|
205
|
+
errorMessage: "Image generation response exceeded the configured size limit.",
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
let payload: unknown;
|
|
210
|
+
try {
|
|
211
|
+
payload = parseJson(bounded.bytes);
|
|
212
|
+
} catch {
|
|
213
|
+
return {
|
|
214
|
+
ok: false,
|
|
215
|
+
reason: "malformed-response",
|
|
216
|
+
status: response.status,
|
|
217
|
+
errorMessage: response.ok
|
|
218
|
+
? "Image generation returned invalid JSON."
|
|
219
|
+
: defaultHttpMessage(response.status),
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (!response.ok) {
|
|
224
|
+
const reason = mapHttpFailure(response.status, payload);
|
|
225
|
+
return {
|
|
226
|
+
ok: false,
|
|
227
|
+
reason,
|
|
228
|
+
status: response.status,
|
|
229
|
+
errorMessage: extractProviderErrorMessage(payload, defaultHttpMessage(response.status, reason)),
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const parsed = parseImageGenerationResponse(payload);
|
|
234
|
+
if (!parsed.ok) {
|
|
235
|
+
return {
|
|
236
|
+
ok: false,
|
|
237
|
+
reason: parsed.reason,
|
|
238
|
+
status: response.status,
|
|
239
|
+
errorMessage: parsed.errorMessage,
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
return { ok: true, image: parsed.image, status: response.status };
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
export const _clientTest = {
|
|
246
|
+
buildRequestHeaders,
|
|
247
|
+
readBoundedBody,
|
|
248
|
+
};
|
|
@@ -1,18 +1,18 @@
|
|
|
1
|
-
import type { ImageGenerationConfig } from "../types";
|
|
2
|
-
import { IMAGE_GENERATION_CAPABLE_APIS, type ImageGenerationModel } from "./types";
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* The hosted `image_generation` tool is a capability of the Responses API rather than of a
|
|
6
|
-
* specific model, so support is decided by API alone. `enabled` is the single opt-in switch
|
|
7
|
-
* and defaults to off because every successful provider request may incur a charge.
|
|
8
|
-
*/
|
|
9
|
-
export function isImageGenerationEnabledForModel(
|
|
10
|
-
model: ImageGenerationModel | undefined,
|
|
11
|
-
config: ImageGenerationConfig,
|
|
12
|
-
): boolean {
|
|
13
|
-
return (
|
|
14
|
-
config.enabled &&
|
|
15
|
-
typeof model?.api === "string" &&
|
|
16
|
-
(IMAGE_GENERATION_CAPABLE_APIS as readonly string[]).includes(model.api)
|
|
17
|
-
);
|
|
18
|
-
}
|
|
1
|
+
import type { ImageGenerationConfig } from "../types";
|
|
2
|
+
import { IMAGE_GENERATION_CAPABLE_APIS, type ImageGenerationModel } from "./types";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The hosted `image_generation` tool is a capability of the Responses API rather than of a
|
|
6
|
+
* specific model, so support is decided by API alone. `enabled` is the single opt-in switch
|
|
7
|
+
* and defaults to off because every successful provider request may incur a charge.
|
|
8
|
+
*/
|
|
9
|
+
export function isImageGenerationEnabledForModel(
|
|
10
|
+
model: ImageGenerationModel | undefined,
|
|
11
|
+
config: ImageGenerationConfig,
|
|
12
|
+
): boolean {
|
|
13
|
+
return (
|
|
14
|
+
config.enabled &&
|
|
15
|
+
typeof model?.api === "string" &&
|
|
16
|
+
(IMAGE_GENERATION_CAPABLE_APIS as readonly string[]).includes(model.api)
|
|
17
|
+
);
|
|
18
|
+
}
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
IMAGE_GENERATION_QUALITIES,
|
|
9
9
|
IMAGE_GENERATION_SIZES,
|
|
10
10
|
IMAGE_GENERATION_TOOL_NAME,
|
|
11
|
+
MAX_IMAGE_MODEL_ID_CHARS,
|
|
11
12
|
MAX_IMAGE_PATH_CHARS,
|
|
12
13
|
MAX_IMAGE_PROMPT_CHARS,
|
|
13
14
|
MAX_REFERENCE_IMAGE_COUNT,
|
|
@@ -65,6 +66,21 @@ const GenerateImageParameters = Type.Object(
|
|
|
65
66
|
description: "Requested image quality, or auto.",
|
|
66
67
|
}),
|
|
67
68
|
),
|
|
69
|
+
model: Type.Optional(
|
|
70
|
+
Type.Union(
|
|
71
|
+
[
|
|
72
|
+
Type.Null(),
|
|
73
|
+
Type.String({
|
|
74
|
+
minLength: 1,
|
|
75
|
+
maxLength: MAX_IMAGE_MODEL_ID_CHARS,
|
|
76
|
+
}),
|
|
77
|
+
],
|
|
78
|
+
{
|
|
79
|
+
description:
|
|
80
|
+
"Use null to accept the default image model, which is the first entry of imageGeneration.models; otherwise provide another model id exactly as it appears in that configured list.",
|
|
81
|
+
},
|
|
82
|
+
),
|
|
83
|
+
),
|
|
68
84
|
},
|
|
69
85
|
{ additionalProperties: false },
|
|
70
86
|
);
|
|
@@ -98,14 +114,15 @@ export function registerImageGenerationExtension(
|
|
|
98
114
|
name: IMAGE_GENERATION_TOOL_NAME,
|
|
99
115
|
label: "OpenAI Generate Image",
|
|
100
116
|
description:
|
|
101
|
-
"Generate a PNG image, or edit from one to five user-approved local reference images, through the current Responses-capable model and
|
|
102
|
-
promptSnippet: "Generate or edit PNG images through the current Responses-capable model and
|
|
117
|
+
"Generate a PNG image, or edit from one to five user-approved local reference images, through the current Responses-capable model and a configured hosted image_generation model. This is a paid provider operation.",
|
|
118
|
+
promptSnippet: "Generate or edit PNG images through the current Responses-capable model and a configured imageGeneration.models entry.",
|
|
103
119
|
promptGuidelines: [
|
|
104
120
|
"Use openai_generate_image when the user explicitly asks to create, draw, render, or edit a raster image and the active model speaks a Responses API.",
|
|
105
121
|
"Do not call openai_generate_image speculatively: it consumes the user's provider or gateway image quota.",
|
|
106
122
|
"Keep the image prompt faithful to the user's requested subject and constraints; do not invent unrequested style details.",
|
|
107
123
|
"Set referenceImagePaths to null unless the user explicitly identified local files; never invent paths or placeholder strings, and remember upload requires user approval.",
|
|
108
124
|
"Set outputPath to null unless the user explicitly asks for a destination; never invent a destination or placeholder string, and otherwise use the default Pi agent artifact.",
|
|
125
|
+
"Set model to null unless the user named an image model configured in imageGeneration.models; never invent or guess a model id, and remember an unlisted model fails before the paid request.",
|
|
109
126
|
"Do not substitute Python, browser automation, shell scripts, or unrelated image tools for an eligible image request.",
|
|
110
127
|
],
|
|
111
128
|
parameters: GenerateImageParameters,
|