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,194 +1,194 @@
|
|
|
1
|
-
import { open, realpath } from "node:fs/promises";
|
|
2
|
-
import { isAbsolute, relative, resolve, sep } from "node:path";
|
|
3
|
-
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
4
|
-
import { detectReferenceImageMimeType } from "./protocol";
|
|
5
|
-
import {
|
|
6
|
-
MAX_REFERENCE_IMAGE_BYTES,
|
|
7
|
-
MAX_REFERENCE_IMAGE_COUNT,
|
|
8
|
-
MAX_TOTAL_REFERENCE_IMAGE_BYTES,
|
|
9
|
-
ImageGenerationError,
|
|
10
|
-
type PreparedReferenceImage,
|
|
11
|
-
} from "./types";
|
|
12
|
-
|
|
13
|
-
function throwIfAborted(signal: AbortSignal | undefined): void {
|
|
14
|
-
if (signal?.aborted) {
|
|
15
|
-
throw new ImageGenerationError("aborted", "Image generation was cancelled.");
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
function displayPath(filePath: string, cwd: string): string {
|
|
20
|
-
const rel = relative(cwd, filePath);
|
|
21
|
-
const value = rel && !rel.startsWith(`..${sep}`) && rel !== ".." ? rel : filePath;
|
|
22
|
-
return value.length > 240 ? `…${value.slice(-239)}` : value;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function formatBytes(bytes: number): string {
|
|
26
|
-
if (bytes < 1024) return `${bytes} B`;
|
|
27
|
-
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KiB`;
|
|
28
|
-
return `${(bytes / (1024 * 1024)).toFixed(1)} MiB`;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export function clearPreparedReferences(references: readonly PreparedReferenceImage[]): void {
|
|
32
|
-
for (const reference of references) {
|
|
33
|
-
reference.bytes.fill(0);
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export async function prepareReferenceImages(args: {
|
|
38
|
-
paths: readonly string[];
|
|
39
|
-
ctx: Pick<ExtensionContext, "cwd" | "hasUI" | "ui">;
|
|
40
|
-
signal?: AbortSignal;
|
|
41
|
-
}): Promise<PreparedReferenceImage[]> {
|
|
42
|
-
if (args.paths.length === 0) return [];
|
|
43
|
-
if (args.paths.length > MAX_REFERENCE_IMAGE_COUNT) {
|
|
44
|
-
throw new ImageGenerationError(
|
|
45
|
-
"reference-input-invalid",
|
|
46
|
-
`Too many reference images (maximum ${MAX_REFERENCE_IMAGE_COUNT}).`,
|
|
47
|
-
);
|
|
48
|
-
}
|
|
49
|
-
if (!args.ctx.hasUI) {
|
|
50
|
-
throw new ImageGenerationError(
|
|
51
|
-
"reference-upload-approval-required",
|
|
52
|
-
"Reference-image upload requires interactive approval and is unavailable in headless mode.",
|
|
53
|
-
);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const prepared: PreparedReferenceImage[] = [];
|
|
57
|
-
const seen = new Set<string>();
|
|
58
|
-
let totalBytes = 0;
|
|
59
|
-
try {
|
|
60
|
-
for (const rawPath of args.paths) {
|
|
61
|
-
throwIfAborted(args.signal);
|
|
62
|
-
const candidate = isAbsolute(rawPath) ? resolve(rawPath) : resolve(args.ctx.cwd, rawPath);
|
|
63
|
-
let resolvedPath: string;
|
|
64
|
-
try {
|
|
65
|
-
resolvedPath = await realpath(candidate);
|
|
66
|
-
} catch {
|
|
67
|
-
throw new ImageGenerationError(
|
|
68
|
-
"reference-input-invalid",
|
|
69
|
-
`Reference image does not exist or is not accessible: ${displayPath(candidate, args.ctx.cwd)}`,
|
|
70
|
-
);
|
|
71
|
-
}
|
|
72
|
-
if (seen.has(resolvedPath)) continue;
|
|
73
|
-
|
|
74
|
-
const handle = await open(resolvedPath, "r").catch(() => undefined);
|
|
75
|
-
if (!handle) {
|
|
76
|
-
throw new ImageGenerationError(
|
|
77
|
-
"reference-input-invalid",
|
|
78
|
-
`Reference image does not exist or is not accessible: ${displayPath(resolvedPath, args.ctx.cwd)}`,
|
|
79
|
-
);
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
let bytes: Buffer | undefined;
|
|
83
|
-
let readCompleted = false;
|
|
84
|
-
try {
|
|
85
|
-
const info = await handle.stat();
|
|
86
|
-
if (!info.isFile()) {
|
|
87
|
-
throw new ImageGenerationError(
|
|
88
|
-
"reference-input-invalid",
|
|
89
|
-
`Reference image must be a regular file: ${displayPath(resolvedPath, args.ctx.cwd)}`,
|
|
90
|
-
);
|
|
91
|
-
}
|
|
92
|
-
if (info.size <= 0 || info.size > MAX_REFERENCE_IMAGE_BYTES) {
|
|
93
|
-
throw new ImageGenerationError(
|
|
94
|
-
"reference-input-invalid",
|
|
95
|
-
`Reference image must be between 1 byte and 20 MiB: ${displayPath(resolvedPath, args.ctx.cwd)}`,
|
|
96
|
-
);
|
|
97
|
-
}
|
|
98
|
-
totalBytes += info.size;
|
|
99
|
-
if (totalBytes > MAX_TOTAL_REFERENCE_IMAGE_BYTES) {
|
|
100
|
-
throw new ImageGenerationError(
|
|
101
|
-
"reference-input-invalid",
|
|
102
|
-
"Reference images exceed the 50 MiB total limit.",
|
|
103
|
-
);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
bytes = Buffer.alloc(info.size);
|
|
107
|
-
let offset = 0;
|
|
108
|
-
while (offset < bytes.length) {
|
|
109
|
-
if (args.signal?.aborted) {
|
|
110
|
-
bytes.fill(0);
|
|
111
|
-
throw new ImageGenerationError("aborted", "Image generation was cancelled.");
|
|
112
|
-
}
|
|
113
|
-
const { bytesRead } = await handle.read(
|
|
114
|
-
bytes,
|
|
115
|
-
offset,
|
|
116
|
-
bytes.length - offset,
|
|
117
|
-
offset,
|
|
118
|
-
);
|
|
119
|
-
if (bytesRead === 0) break;
|
|
120
|
-
offset += bytesRead;
|
|
121
|
-
}
|
|
122
|
-
const extra = Buffer.alloc(1);
|
|
123
|
-
const { bytesRead: extraBytesRead } = await handle.read(extra, 0, 1, info.size);
|
|
124
|
-
extra.fill(0);
|
|
125
|
-
if (offset !== info.size || extraBytesRead !== 0) {
|
|
126
|
-
bytes.fill(0);
|
|
127
|
-
throw new ImageGenerationError(
|
|
128
|
-
"reference-input-invalid",
|
|
129
|
-
`Reference image changed while being read: ${displayPath(resolvedPath, args.ctx.cwd)}`,
|
|
130
|
-
);
|
|
131
|
-
}
|
|
132
|
-
if (args.signal?.aborted) {
|
|
133
|
-
bytes.fill(0);
|
|
134
|
-
throw new ImageGenerationError("aborted", "Image generation was cancelled.");
|
|
135
|
-
}
|
|
136
|
-
readCompleted = true;
|
|
137
|
-
} finally {
|
|
138
|
-
if (!readCompleted) bytes?.fill(0);
|
|
139
|
-
await handle.close().catch(() => undefined);
|
|
140
|
-
}
|
|
141
|
-
if (!bytes) {
|
|
142
|
-
throw new ImageGenerationError(
|
|
143
|
-
"reference-input-invalid",
|
|
144
|
-
`Reference image could not be read: ${displayPath(resolvedPath, args.ctx.cwd)}`,
|
|
145
|
-
);
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
const mimeType = detectReferenceImageMimeType(bytes);
|
|
149
|
-
if (!mimeType) {
|
|
150
|
-
bytes.fill(0);
|
|
151
|
-
throw new ImageGenerationError(
|
|
152
|
-
"reference-input-invalid",
|
|
153
|
-
`Reference image must be PNG, JPEG, or WebP: ${displayPath(resolvedPath, args.ctx.cwd)}`,
|
|
154
|
-
);
|
|
155
|
-
}
|
|
156
|
-
seen.add(resolvedPath);
|
|
157
|
-
prepared.push({ path: resolvedPath, mimeType, bytes });
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
if (prepared.length === 0) {
|
|
161
|
-
throw new ImageGenerationError(
|
|
162
|
-
"reference-input-invalid",
|
|
163
|
-
"No unique readable reference images were provided.",
|
|
164
|
-
);
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
const lines = prepared.map(
|
|
168
|
-
(reference) =>
|
|
169
|
-
`• ${displayPath(reference.path, args.ctx.cwd)} (${formatBytes(reference.bytes.length)})`,
|
|
170
|
-
);
|
|
171
|
-
const approved = await args.ctx.ui.confirm(
|
|
172
|
-
"Upload reference images?",
|
|
173
|
-
[
|
|
174
|
-
"The following local files will be uploaded to the current model's configured gateway for image editing:",
|
|
175
|
-
"",
|
|
176
|
-
...lines,
|
|
177
|
-
"",
|
|
178
|
-
"Continue with this paid image request?",
|
|
179
|
-
].join("\n"),
|
|
180
|
-
{ signal: args.signal },
|
|
181
|
-
);
|
|
182
|
-
throwIfAborted(args.signal);
|
|
183
|
-
if (!approved) {
|
|
184
|
-
throw new ImageGenerationError(
|
|
185
|
-
"reference-upload-declined",
|
|
186
|
-
"Reference-image upload was declined.",
|
|
187
|
-
);
|
|
188
|
-
}
|
|
189
|
-
return prepared;
|
|
190
|
-
} catch (error) {
|
|
191
|
-
clearPreparedReferences(prepared);
|
|
192
|
-
throw error;
|
|
193
|
-
}
|
|
194
|
-
}
|
|
1
|
+
import { open, realpath } from "node:fs/promises";
|
|
2
|
+
import { isAbsolute, relative, resolve, sep } from "node:path";
|
|
3
|
+
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
4
|
+
import { detectReferenceImageMimeType } from "./protocol";
|
|
5
|
+
import {
|
|
6
|
+
MAX_REFERENCE_IMAGE_BYTES,
|
|
7
|
+
MAX_REFERENCE_IMAGE_COUNT,
|
|
8
|
+
MAX_TOTAL_REFERENCE_IMAGE_BYTES,
|
|
9
|
+
ImageGenerationError,
|
|
10
|
+
type PreparedReferenceImage,
|
|
11
|
+
} from "./types";
|
|
12
|
+
|
|
13
|
+
function throwIfAborted(signal: AbortSignal | undefined): void {
|
|
14
|
+
if (signal?.aborted) {
|
|
15
|
+
throw new ImageGenerationError("aborted", "Image generation was cancelled.");
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function displayPath(filePath: string, cwd: string): string {
|
|
20
|
+
const rel = relative(cwd, filePath);
|
|
21
|
+
const value = rel && !rel.startsWith(`..${sep}`) && rel !== ".." ? rel : filePath;
|
|
22
|
+
return value.length > 240 ? `…${value.slice(-239)}` : value;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function formatBytes(bytes: number): string {
|
|
26
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
27
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KiB`;
|
|
28
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MiB`;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function clearPreparedReferences(references: readonly PreparedReferenceImage[]): void {
|
|
32
|
+
for (const reference of references) {
|
|
33
|
+
reference.bytes.fill(0);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export async function prepareReferenceImages(args: {
|
|
38
|
+
paths: readonly string[];
|
|
39
|
+
ctx: Pick<ExtensionContext, "cwd" | "hasUI" | "ui">;
|
|
40
|
+
signal?: AbortSignal;
|
|
41
|
+
}): Promise<PreparedReferenceImage[]> {
|
|
42
|
+
if (args.paths.length === 0) return [];
|
|
43
|
+
if (args.paths.length > MAX_REFERENCE_IMAGE_COUNT) {
|
|
44
|
+
throw new ImageGenerationError(
|
|
45
|
+
"reference-input-invalid",
|
|
46
|
+
`Too many reference images (maximum ${MAX_REFERENCE_IMAGE_COUNT}).`,
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
if (!args.ctx.hasUI) {
|
|
50
|
+
throw new ImageGenerationError(
|
|
51
|
+
"reference-upload-approval-required",
|
|
52
|
+
"Reference-image upload requires interactive approval and is unavailable in headless mode.",
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const prepared: PreparedReferenceImage[] = [];
|
|
57
|
+
const seen = new Set<string>();
|
|
58
|
+
let totalBytes = 0;
|
|
59
|
+
try {
|
|
60
|
+
for (const rawPath of args.paths) {
|
|
61
|
+
throwIfAborted(args.signal);
|
|
62
|
+
const candidate = isAbsolute(rawPath) ? resolve(rawPath) : resolve(args.ctx.cwd, rawPath);
|
|
63
|
+
let resolvedPath: string;
|
|
64
|
+
try {
|
|
65
|
+
resolvedPath = await realpath(candidate);
|
|
66
|
+
} catch {
|
|
67
|
+
throw new ImageGenerationError(
|
|
68
|
+
"reference-input-invalid",
|
|
69
|
+
`Reference image does not exist or is not accessible: ${displayPath(candidate, args.ctx.cwd)}`,
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
if (seen.has(resolvedPath)) continue;
|
|
73
|
+
|
|
74
|
+
const handle = await open(resolvedPath, "r").catch(() => undefined);
|
|
75
|
+
if (!handle) {
|
|
76
|
+
throw new ImageGenerationError(
|
|
77
|
+
"reference-input-invalid",
|
|
78
|
+
`Reference image does not exist or is not accessible: ${displayPath(resolvedPath, args.ctx.cwd)}`,
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
let bytes: Buffer | undefined;
|
|
83
|
+
let readCompleted = false;
|
|
84
|
+
try {
|
|
85
|
+
const info = await handle.stat();
|
|
86
|
+
if (!info.isFile()) {
|
|
87
|
+
throw new ImageGenerationError(
|
|
88
|
+
"reference-input-invalid",
|
|
89
|
+
`Reference image must be a regular file: ${displayPath(resolvedPath, args.ctx.cwd)}`,
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
if (info.size <= 0 || info.size > MAX_REFERENCE_IMAGE_BYTES) {
|
|
93
|
+
throw new ImageGenerationError(
|
|
94
|
+
"reference-input-invalid",
|
|
95
|
+
`Reference image must be between 1 byte and 20 MiB: ${displayPath(resolvedPath, args.ctx.cwd)}`,
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
totalBytes += info.size;
|
|
99
|
+
if (totalBytes > MAX_TOTAL_REFERENCE_IMAGE_BYTES) {
|
|
100
|
+
throw new ImageGenerationError(
|
|
101
|
+
"reference-input-invalid",
|
|
102
|
+
"Reference images exceed the 50 MiB total limit.",
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
bytes = Buffer.alloc(info.size);
|
|
107
|
+
let offset = 0;
|
|
108
|
+
while (offset < bytes.length) {
|
|
109
|
+
if (args.signal?.aborted) {
|
|
110
|
+
bytes.fill(0);
|
|
111
|
+
throw new ImageGenerationError("aborted", "Image generation was cancelled.");
|
|
112
|
+
}
|
|
113
|
+
const { bytesRead } = await handle.read(
|
|
114
|
+
bytes,
|
|
115
|
+
offset,
|
|
116
|
+
bytes.length - offset,
|
|
117
|
+
offset,
|
|
118
|
+
);
|
|
119
|
+
if (bytesRead === 0) break;
|
|
120
|
+
offset += bytesRead;
|
|
121
|
+
}
|
|
122
|
+
const extra = Buffer.alloc(1);
|
|
123
|
+
const { bytesRead: extraBytesRead } = await handle.read(extra, 0, 1, info.size);
|
|
124
|
+
extra.fill(0);
|
|
125
|
+
if (offset !== info.size || extraBytesRead !== 0) {
|
|
126
|
+
bytes.fill(0);
|
|
127
|
+
throw new ImageGenerationError(
|
|
128
|
+
"reference-input-invalid",
|
|
129
|
+
`Reference image changed while being read: ${displayPath(resolvedPath, args.ctx.cwd)}`,
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
if (args.signal?.aborted) {
|
|
133
|
+
bytes.fill(0);
|
|
134
|
+
throw new ImageGenerationError("aborted", "Image generation was cancelled.");
|
|
135
|
+
}
|
|
136
|
+
readCompleted = true;
|
|
137
|
+
} finally {
|
|
138
|
+
if (!readCompleted) bytes?.fill(0);
|
|
139
|
+
await handle.close().catch(() => undefined);
|
|
140
|
+
}
|
|
141
|
+
if (!bytes) {
|
|
142
|
+
throw new ImageGenerationError(
|
|
143
|
+
"reference-input-invalid",
|
|
144
|
+
`Reference image could not be read: ${displayPath(resolvedPath, args.ctx.cwd)}`,
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const mimeType = detectReferenceImageMimeType(bytes);
|
|
149
|
+
if (!mimeType) {
|
|
150
|
+
bytes.fill(0);
|
|
151
|
+
throw new ImageGenerationError(
|
|
152
|
+
"reference-input-invalid",
|
|
153
|
+
`Reference image must be PNG, JPEG, or WebP: ${displayPath(resolvedPath, args.ctx.cwd)}`,
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
seen.add(resolvedPath);
|
|
157
|
+
prepared.push({ path: resolvedPath, mimeType, bytes });
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (prepared.length === 0) {
|
|
161
|
+
throw new ImageGenerationError(
|
|
162
|
+
"reference-input-invalid",
|
|
163
|
+
"No unique readable reference images were provided.",
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const lines = prepared.map(
|
|
168
|
+
(reference) =>
|
|
169
|
+
`• ${displayPath(reference.path, args.ctx.cwd)} (${formatBytes(reference.bytes.length)})`,
|
|
170
|
+
);
|
|
171
|
+
const approved = await args.ctx.ui.confirm(
|
|
172
|
+
"Upload reference images?",
|
|
173
|
+
[
|
|
174
|
+
"The following local files will be uploaded to the current model's configured gateway for image editing:",
|
|
175
|
+
"",
|
|
176
|
+
...lines,
|
|
177
|
+
"",
|
|
178
|
+
"Continue with this paid image request?",
|
|
179
|
+
].join("\n"),
|
|
180
|
+
{ signal: args.signal },
|
|
181
|
+
);
|
|
182
|
+
throwIfAborted(args.signal);
|
|
183
|
+
if (!approved) {
|
|
184
|
+
throw new ImageGenerationError(
|
|
185
|
+
"reference-upload-declined",
|
|
186
|
+
"Reference-image upload was declined.",
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
return prepared;
|
|
190
|
+
} catch (error) {
|
|
191
|
+
clearPreparedReferences(prepared);
|
|
192
|
+
throw error;
|
|
193
|
+
}
|
|
194
|
+
}
|