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,237 +1,237 @@
|
|
|
1
|
-
import { constants as fsConstants } from "node:fs";
|
|
2
|
-
import {
|
|
3
|
-
link,
|
|
4
|
-
mkdir,
|
|
5
|
-
open,
|
|
6
|
-
realpath,
|
|
7
|
-
stat,
|
|
8
|
-
unlink,
|
|
9
|
-
} from "node:fs/promises";
|
|
10
|
-
import { randomUUID } from "node:crypto";
|
|
11
|
-
import {
|
|
12
|
-
basename,
|
|
13
|
-
dirname,
|
|
14
|
-
extname,
|
|
15
|
-
isAbsolute,
|
|
16
|
-
join,
|
|
17
|
-
relative,
|
|
18
|
-
resolve,
|
|
19
|
-
sep,
|
|
20
|
-
} from "node:path";
|
|
21
|
-
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
22
|
-
import {
|
|
23
|
-
MAX_IMAGE_PATH_CHARS,
|
|
24
|
-
ImageGenerationError,
|
|
25
|
-
sanitizeImageDiagnostic,
|
|
26
|
-
} from "./types";
|
|
27
|
-
|
|
28
|
-
export type ExplicitOutputPlan = {
|
|
29
|
-
path: string;
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
function isPathInsideOrEqual(root: string, candidate: string): boolean {
|
|
33
|
-
const rel = relative(root, candidate);
|
|
34
|
-
return rel === "" || (!rel.startsWith(`..${sep}`) && rel !== ".." && !isAbsolute(rel));
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function sanitizePathPart(value: string, fallback: string): string {
|
|
38
|
-
const sanitized = value
|
|
39
|
-
.trim()
|
|
40
|
-
.replace(/[^A-Za-z0-9._-]+/g, "-")
|
|
41
|
-
.replace(/^[.-]+|[.-]+$/g, "")
|
|
42
|
-
.slice(0, 120);
|
|
43
|
-
return sanitized || fallback;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
async function realDirectory(path: string): Promise<string> {
|
|
47
|
-
return realpath(path).catch(() => resolve(path));
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
async function resolveWriteTarget(candidate: string): Promise<string> {
|
|
51
|
-
const suffix: string[] = [];
|
|
52
|
-
let current = resolve(candidate);
|
|
53
|
-
while (true) {
|
|
54
|
-
try {
|
|
55
|
-
const info = await stat(current);
|
|
56
|
-
if (suffix.length > 0 && !info.isDirectory()) {
|
|
57
|
-
throw new ImageGenerationError(
|
|
58
|
-
"output-path-invalid",
|
|
59
|
-
`Output parent is not a directory: ${current}`,
|
|
60
|
-
);
|
|
61
|
-
}
|
|
62
|
-
const existing = await realpath(current);
|
|
63
|
-
return resolve(existing, ...suffix);
|
|
64
|
-
} catch (error) {
|
|
65
|
-
if (error instanceof ImageGenerationError) throw error;
|
|
66
|
-
if (
|
|
67
|
-
error &&
|
|
68
|
-
typeof error === "object" &&
|
|
69
|
-
"code" in error &&
|
|
70
|
-
error.code !== "ENOENT"
|
|
71
|
-
) {
|
|
72
|
-
throw new ImageGenerationError(
|
|
73
|
-
"output-path-invalid",
|
|
74
|
-
`Unable to inspect output path: ${candidate}`,
|
|
75
|
-
);
|
|
76
|
-
}
|
|
77
|
-
const parent = dirname(current);
|
|
78
|
-
if (parent === current) {
|
|
79
|
-
throw new ImageGenerationError(
|
|
80
|
-
"output-path-invalid",
|
|
81
|
-
`Unable to resolve output path: ${candidate}`,
|
|
82
|
-
);
|
|
83
|
-
}
|
|
84
|
-
suffix.unshift(basename(current));
|
|
85
|
-
current = parent;
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
function throwIfAborted(signal: AbortSignal | undefined): void {
|
|
91
|
-
if (signal?.aborted) {
|
|
92
|
-
throw new ImageGenerationError("aborted", "Image generation was cancelled.");
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
export function getGeneratedImagesRoot(agentDir: string): string {
|
|
97
|
-
return join(resolve(agentDir), "generated-images");
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
export async function prepareExplicitOutputPath(args: {
|
|
101
|
-
rawPath?: string;
|
|
102
|
-
agentDir: string;
|
|
103
|
-
ctx: Pick<ExtensionContext, "cwd" | "hasUI" | "isProjectTrusted" | "ui">;
|
|
104
|
-
signal?: AbortSignal;
|
|
105
|
-
}): Promise<ExplicitOutputPlan | undefined> {
|
|
106
|
-
if (args.rawPath === undefined) return undefined;
|
|
107
|
-
const trimmed = args.rawPath.trim();
|
|
108
|
-
if (!trimmed || trimmed.length > MAX_IMAGE_PATH_CHARS || extname(trimmed).toLowerCase() !== ".png") {
|
|
109
|
-
throw new ImageGenerationError(
|
|
110
|
-
"output-path-invalid",
|
|
111
|
-
"outputPath must be a non-empty .png file path.",
|
|
112
|
-
);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
throwIfAborted(args.signal);
|
|
116
|
-
const lexical = isAbsolute(trimmed) ? resolve(trimmed) : resolve(args.ctx.cwd, trimmed);
|
|
117
|
-
const target = await resolveWriteTarget(lexical);
|
|
118
|
-
const targetInfo = await stat(target).catch(() => undefined);
|
|
119
|
-
if (targetInfo) {
|
|
120
|
-
throw new ImageGenerationError(
|
|
121
|
-
"output-path-invalid",
|
|
122
|
-
`Output path already exists and will not be overwritten: ${target}`,
|
|
123
|
-
);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
const [agentRoot, projectRoot] = await Promise.all([
|
|
127
|
-
realDirectory(args.agentDir),
|
|
128
|
-
realDirectory(args.ctx.cwd),
|
|
129
|
-
]);
|
|
130
|
-
if (isPathInsideOrEqual(agentRoot, target)) return { path: target };
|
|
131
|
-
|
|
132
|
-
if (isPathInsideOrEqual(projectRoot, target)) {
|
|
133
|
-
if (!args.ctx.isProjectTrusted()) {
|
|
134
|
-
throw new ImageGenerationError(
|
|
135
|
-
"output-path-invalid",
|
|
136
|
-
"Writing generated images inside an untrusted project is not allowed.",
|
|
137
|
-
);
|
|
138
|
-
}
|
|
139
|
-
return { path: target };
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
if (!args.ctx.hasUI) {
|
|
143
|
-
throw new ImageGenerationError(
|
|
144
|
-
"output-path-approval-required",
|
|
145
|
-
"Writing outside the Pi agent root or trusted project requires interactive approval.",
|
|
146
|
-
);
|
|
147
|
-
}
|
|
148
|
-
const approved = await args.ctx.ui.confirm(
|
|
149
|
-
"Write generated image outside safe roots?",
|
|
150
|
-
[
|
|
151
|
-
"The generated PNG will be copied to this external path without overwriting an existing file:",
|
|
152
|
-
"",
|
|
153
|
-
target,
|
|
154
|
-
"",
|
|
155
|
-
"Continue?",
|
|
156
|
-
].join("\n"),
|
|
157
|
-
{ signal: args.signal },
|
|
158
|
-
);
|
|
159
|
-
throwIfAborted(args.signal);
|
|
160
|
-
if (!approved) {
|
|
161
|
-
throw new ImageGenerationError("output-path-declined", "External output path was declined.");
|
|
162
|
-
}
|
|
163
|
-
return { path: target };
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
async function publishBytesNoOverwrite(bytes: Uint8Array, target: string): Promise<void> {
|
|
167
|
-
const parent = dirname(target);
|
|
168
|
-
await mkdir(parent, { recursive: true });
|
|
169
|
-
const tempPath = join(parent, `.${basename(target)}.${randomUUID()}.tmp`);
|
|
170
|
-
let handle: Awaited<ReturnType<typeof open>> | undefined;
|
|
171
|
-
try {
|
|
172
|
-
handle = await open(tempPath, fsConstants.O_CREAT | fsConstants.O_EXCL | fsConstants.O_WRONLY, 0o600);
|
|
173
|
-
await handle.writeFile(bytes);
|
|
174
|
-
await handle.sync();
|
|
175
|
-
await handle.close();
|
|
176
|
-
handle = undefined;
|
|
177
|
-
await link(tempPath, target);
|
|
178
|
-
} finally {
|
|
179
|
-
await handle?.close().catch(() => undefined);
|
|
180
|
-
await unlink(tempPath).catch(() => undefined);
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
function isAlreadyExistsError(error: unknown): boolean {
|
|
185
|
-
return !!error && typeof error === "object" && "code" in error && error.code === "EEXIST";
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
export async function saveCanonicalImage(args: {
|
|
189
|
-
bytes: Uint8Array;
|
|
190
|
-
agentDir: string;
|
|
191
|
-
sessionId: string;
|
|
192
|
-
imageCallId: string;
|
|
193
|
-
}): Promise<string> {
|
|
194
|
-
const root = getGeneratedImagesRoot(args.agentDir);
|
|
195
|
-
const sessionPart = sanitizePathPart(args.sessionId, "session");
|
|
196
|
-
const imagePart = sanitizePathPart(args.imageCallId, "image_generation");
|
|
197
|
-
const directory = join(root, sessionPart);
|
|
198
|
-
|
|
199
|
-
for (let index = 1; index <= 1000; index += 1) {
|
|
200
|
-
const suffix = index === 1 ? "" : `-${index}`;
|
|
201
|
-
const target = join(directory, `${imagePart}${suffix}.png`);
|
|
202
|
-
try {
|
|
203
|
-
await publishBytesNoOverwrite(args.bytes, target);
|
|
204
|
-
return target;
|
|
205
|
-
} catch (error) {
|
|
206
|
-
if (isAlreadyExistsError(error)) continue;
|
|
207
|
-
throw new ImageGenerationError(
|
|
208
|
-
"artifact-write-failed",
|
|
209
|
-
sanitizeImageDiagnostic(
|
|
210
|
-
error instanceof Error ? error.message : error,
|
|
211
|
-
"Failed to persist the generated image artifact.",
|
|
212
|
-
),
|
|
213
|
-
);
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
throw new ImageGenerationError(
|
|
217
|
-
"artifact-write-failed",
|
|
218
|
-
"Failed to reserve a unique generated image artifact path.",
|
|
219
|
-
);
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
export async function copyImageToExplicitPath(args: {
|
|
223
|
-
bytes: Uint8Array;
|
|
224
|
-
plan: ExplicitOutputPlan;
|
|
225
|
-
}): Promise<void> {
|
|
226
|
-
const currentTarget = await resolveWriteTarget(args.plan.path);
|
|
227
|
-
if (currentTarget !== args.plan.path) {
|
|
228
|
-
throw new Error("Output path changed after approval.");
|
|
229
|
-
}
|
|
230
|
-
await publishBytesNoOverwrite(args.bytes, args.plan.path);
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
export const _artifactTest = {
|
|
234
|
-
isPathInsideOrEqual,
|
|
235
|
-
publishBytesNoOverwrite,
|
|
236
|
-
resolveWriteTarget,
|
|
237
|
-
};
|
|
1
|
+
import { constants as fsConstants } from "node:fs";
|
|
2
|
+
import {
|
|
3
|
+
link,
|
|
4
|
+
mkdir,
|
|
5
|
+
open,
|
|
6
|
+
realpath,
|
|
7
|
+
stat,
|
|
8
|
+
unlink,
|
|
9
|
+
} from "node:fs/promises";
|
|
10
|
+
import { randomUUID } from "node:crypto";
|
|
11
|
+
import {
|
|
12
|
+
basename,
|
|
13
|
+
dirname,
|
|
14
|
+
extname,
|
|
15
|
+
isAbsolute,
|
|
16
|
+
join,
|
|
17
|
+
relative,
|
|
18
|
+
resolve,
|
|
19
|
+
sep,
|
|
20
|
+
} from "node:path";
|
|
21
|
+
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
22
|
+
import {
|
|
23
|
+
MAX_IMAGE_PATH_CHARS,
|
|
24
|
+
ImageGenerationError,
|
|
25
|
+
sanitizeImageDiagnostic,
|
|
26
|
+
} from "./types";
|
|
27
|
+
|
|
28
|
+
export type ExplicitOutputPlan = {
|
|
29
|
+
path: string;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
function isPathInsideOrEqual(root: string, candidate: string): boolean {
|
|
33
|
+
const rel = relative(root, candidate);
|
|
34
|
+
return rel === "" || (!rel.startsWith(`..${sep}`) && rel !== ".." && !isAbsolute(rel));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function sanitizePathPart(value: string, fallback: string): string {
|
|
38
|
+
const sanitized = value
|
|
39
|
+
.trim()
|
|
40
|
+
.replace(/[^A-Za-z0-9._-]+/g, "-")
|
|
41
|
+
.replace(/^[.-]+|[.-]+$/g, "")
|
|
42
|
+
.slice(0, 120);
|
|
43
|
+
return sanitized || fallback;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async function realDirectory(path: string): Promise<string> {
|
|
47
|
+
return realpath(path).catch(() => resolve(path));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async function resolveWriteTarget(candidate: string): Promise<string> {
|
|
51
|
+
const suffix: string[] = [];
|
|
52
|
+
let current = resolve(candidate);
|
|
53
|
+
while (true) {
|
|
54
|
+
try {
|
|
55
|
+
const info = await stat(current);
|
|
56
|
+
if (suffix.length > 0 && !info.isDirectory()) {
|
|
57
|
+
throw new ImageGenerationError(
|
|
58
|
+
"output-path-invalid",
|
|
59
|
+
`Output parent is not a directory: ${current}`,
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
const existing = await realpath(current);
|
|
63
|
+
return resolve(existing, ...suffix);
|
|
64
|
+
} catch (error) {
|
|
65
|
+
if (error instanceof ImageGenerationError) throw error;
|
|
66
|
+
if (
|
|
67
|
+
error &&
|
|
68
|
+
typeof error === "object" &&
|
|
69
|
+
"code" in error &&
|
|
70
|
+
error.code !== "ENOENT"
|
|
71
|
+
) {
|
|
72
|
+
throw new ImageGenerationError(
|
|
73
|
+
"output-path-invalid",
|
|
74
|
+
`Unable to inspect output path: ${candidate}`,
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
const parent = dirname(current);
|
|
78
|
+
if (parent === current) {
|
|
79
|
+
throw new ImageGenerationError(
|
|
80
|
+
"output-path-invalid",
|
|
81
|
+
`Unable to resolve output path: ${candidate}`,
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
suffix.unshift(basename(current));
|
|
85
|
+
current = parent;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function throwIfAborted(signal: AbortSignal | undefined): void {
|
|
91
|
+
if (signal?.aborted) {
|
|
92
|
+
throw new ImageGenerationError("aborted", "Image generation was cancelled.");
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function getGeneratedImagesRoot(agentDir: string): string {
|
|
97
|
+
return join(resolve(agentDir), "generated-images");
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export async function prepareExplicitOutputPath(args: {
|
|
101
|
+
rawPath?: string;
|
|
102
|
+
agentDir: string;
|
|
103
|
+
ctx: Pick<ExtensionContext, "cwd" | "hasUI" | "isProjectTrusted" | "ui">;
|
|
104
|
+
signal?: AbortSignal;
|
|
105
|
+
}): Promise<ExplicitOutputPlan | undefined> {
|
|
106
|
+
if (args.rawPath === undefined) return undefined;
|
|
107
|
+
const trimmed = args.rawPath.trim();
|
|
108
|
+
if (!trimmed || trimmed.length > MAX_IMAGE_PATH_CHARS || extname(trimmed).toLowerCase() !== ".png") {
|
|
109
|
+
throw new ImageGenerationError(
|
|
110
|
+
"output-path-invalid",
|
|
111
|
+
"outputPath must be a non-empty .png file path.",
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
throwIfAborted(args.signal);
|
|
116
|
+
const lexical = isAbsolute(trimmed) ? resolve(trimmed) : resolve(args.ctx.cwd, trimmed);
|
|
117
|
+
const target = await resolveWriteTarget(lexical);
|
|
118
|
+
const targetInfo = await stat(target).catch(() => undefined);
|
|
119
|
+
if (targetInfo) {
|
|
120
|
+
throw new ImageGenerationError(
|
|
121
|
+
"output-path-invalid",
|
|
122
|
+
`Output path already exists and will not be overwritten: ${target}`,
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const [agentRoot, projectRoot] = await Promise.all([
|
|
127
|
+
realDirectory(args.agentDir),
|
|
128
|
+
realDirectory(args.ctx.cwd),
|
|
129
|
+
]);
|
|
130
|
+
if (isPathInsideOrEqual(agentRoot, target)) return { path: target };
|
|
131
|
+
|
|
132
|
+
if (isPathInsideOrEqual(projectRoot, target)) {
|
|
133
|
+
if (!args.ctx.isProjectTrusted()) {
|
|
134
|
+
throw new ImageGenerationError(
|
|
135
|
+
"output-path-invalid",
|
|
136
|
+
"Writing generated images inside an untrusted project is not allowed.",
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
return { path: target };
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (!args.ctx.hasUI) {
|
|
143
|
+
throw new ImageGenerationError(
|
|
144
|
+
"output-path-approval-required",
|
|
145
|
+
"Writing outside the Pi agent root or trusted project requires interactive approval.",
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
const approved = await args.ctx.ui.confirm(
|
|
149
|
+
"Write generated image outside safe roots?",
|
|
150
|
+
[
|
|
151
|
+
"The generated PNG will be copied to this external path without overwriting an existing file:",
|
|
152
|
+
"",
|
|
153
|
+
target,
|
|
154
|
+
"",
|
|
155
|
+
"Continue?",
|
|
156
|
+
].join("\n"),
|
|
157
|
+
{ signal: args.signal },
|
|
158
|
+
);
|
|
159
|
+
throwIfAborted(args.signal);
|
|
160
|
+
if (!approved) {
|
|
161
|
+
throw new ImageGenerationError("output-path-declined", "External output path was declined.");
|
|
162
|
+
}
|
|
163
|
+
return { path: target };
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
async function publishBytesNoOverwrite(bytes: Uint8Array, target: string): Promise<void> {
|
|
167
|
+
const parent = dirname(target);
|
|
168
|
+
await mkdir(parent, { recursive: true });
|
|
169
|
+
const tempPath = join(parent, `.${basename(target)}.${randomUUID()}.tmp`);
|
|
170
|
+
let handle: Awaited<ReturnType<typeof open>> | undefined;
|
|
171
|
+
try {
|
|
172
|
+
handle = await open(tempPath, fsConstants.O_CREAT | fsConstants.O_EXCL | fsConstants.O_WRONLY, 0o600);
|
|
173
|
+
await handle.writeFile(bytes);
|
|
174
|
+
await handle.sync();
|
|
175
|
+
await handle.close();
|
|
176
|
+
handle = undefined;
|
|
177
|
+
await link(tempPath, target);
|
|
178
|
+
} finally {
|
|
179
|
+
await handle?.close().catch(() => undefined);
|
|
180
|
+
await unlink(tempPath).catch(() => undefined);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function isAlreadyExistsError(error: unknown): boolean {
|
|
185
|
+
return !!error && typeof error === "object" && "code" in error && error.code === "EEXIST";
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export async function saveCanonicalImage(args: {
|
|
189
|
+
bytes: Uint8Array;
|
|
190
|
+
agentDir: string;
|
|
191
|
+
sessionId: string;
|
|
192
|
+
imageCallId: string;
|
|
193
|
+
}): Promise<string> {
|
|
194
|
+
const root = getGeneratedImagesRoot(args.agentDir);
|
|
195
|
+
const sessionPart = sanitizePathPart(args.sessionId, "session");
|
|
196
|
+
const imagePart = sanitizePathPart(args.imageCallId, "image_generation");
|
|
197
|
+
const directory = join(root, sessionPart);
|
|
198
|
+
|
|
199
|
+
for (let index = 1; index <= 1000; index += 1) {
|
|
200
|
+
const suffix = index === 1 ? "" : `-${index}`;
|
|
201
|
+
const target = join(directory, `${imagePart}${suffix}.png`);
|
|
202
|
+
try {
|
|
203
|
+
await publishBytesNoOverwrite(args.bytes, target);
|
|
204
|
+
return target;
|
|
205
|
+
} catch (error) {
|
|
206
|
+
if (isAlreadyExistsError(error)) continue;
|
|
207
|
+
throw new ImageGenerationError(
|
|
208
|
+
"artifact-write-failed",
|
|
209
|
+
sanitizeImageDiagnostic(
|
|
210
|
+
error instanceof Error ? error.message : error,
|
|
211
|
+
"Failed to persist the generated image artifact.",
|
|
212
|
+
),
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
throw new ImageGenerationError(
|
|
217
|
+
"artifact-write-failed",
|
|
218
|
+
"Failed to reserve a unique generated image artifact path.",
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
export async function copyImageToExplicitPath(args: {
|
|
223
|
+
bytes: Uint8Array;
|
|
224
|
+
plan: ExplicitOutputPlan;
|
|
225
|
+
}): Promise<void> {
|
|
226
|
+
const currentTarget = await resolveWriteTarget(args.plan.path);
|
|
227
|
+
if (currentTarget !== args.plan.path) {
|
|
228
|
+
throw new Error("Output path changed after approval.");
|
|
229
|
+
}
|
|
230
|
+
await publishBytesNoOverwrite(args.bytes, args.plan.path);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
export const _artifactTest = {
|
|
234
|
+
isPathInsideOrEqual,
|
|
235
|
+
publishBytesNoOverwrite,
|
|
236
|
+
resolveWriteTarget,
|
|
237
|
+
};
|