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.
Files changed (43) hide show
  1. package/README.md +119 -147
  2. package/README.zh.md +119 -151
  3. package/docs/internals.md +42 -21
  4. package/package.json +127 -127
  5. package/src/auto-mode/classifier.ts +165 -165
  6. package/src/auto-mode/policy.ts +146 -146
  7. package/src/auto-mode/prompt.ts +279 -279
  8. package/src/auto-mode/reviewer.ts +325 -325
  9. package/src/auto-mode/transcript.ts +133 -133
  10. package/src/auto-mode/types.ts +137 -137
  11. package/src/codex-astra/effort-planner.ts +175 -175
  12. package/src/codex-astra/extension.ts +122 -122
  13. package/src/config.ts +621 -592
  14. package/src/context-management/codex-provider.ts +260 -260
  15. package/src/context-management/history-notes.ts +357 -325
  16. package/src/context-management/messages.ts +112 -106
  17. package/src/context-management/namespace-tools.ts +426 -426
  18. package/src/context-management/tool-contract.ts +23 -23
  19. package/src/context-management/tools.ts +306 -311
  20. package/src/context-management/types.ts +168 -161
  21. package/src/context-management/window-budget.ts +83 -83
  22. package/src/context-management/window-manager.ts +60 -51
  23. package/src/context-management/window-request.ts +125 -125
  24. package/src/debug.ts +1 -1
  25. package/src/extension-runtime.ts +34 -9
  26. package/src/image-generation/artifacts.ts +237 -237
  27. package/src/image-generation/client.ts +248 -248
  28. package/src/image-generation/eligibility.ts +18 -18
  29. package/src/image-generation/extension.ts +19 -2
  30. package/src/image-generation/protocol.ts +473 -405
  31. package/src/image-generation/references.ts +194 -194
  32. package/src/image-generation/render.ts +252 -252
  33. package/src/image-generation/service.ts +249 -243
  34. package/src/image-generation/types.ts +189 -184
  35. package/src/model-scope.ts +17 -17
  36. package/src/provider-headers.ts +23 -23
  37. package/src/responses-headers.ts +173 -108
  38. package/src/runtime.ts +513 -477
  39. package/src/types.ts +11 -0
  40. package/src/web-search/extension.ts +80 -80
  41. package/src/web-search/payload.ts +96 -96
  42. package/src/web-search/prompt.ts +20 -20
  43. 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
+ };