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,252 +1,252 @@
|
|
|
1
|
-
import { readFileSync, realpathSync, statSync } from "node:fs";
|
|
2
|
-
import { isAbsolute, relative, resolve, sep } from "node:path";
|
|
3
|
-
import type { AgentToolResult } from "@earendil-works/pi-agent-core";
|
|
4
|
-
import {
|
|
5
|
-
getAgentDir,
|
|
6
|
-
type Theme,
|
|
7
|
-
type ToolRenderResultOptions,
|
|
8
|
-
} from "@earendil-works/pi-coding-agent";
|
|
9
|
-
import {
|
|
10
|
-
Container,
|
|
11
|
-
getCapabilities,
|
|
12
|
-
Image,
|
|
13
|
-
Spacer,
|
|
14
|
-
Text,
|
|
15
|
-
type Component,
|
|
16
|
-
} from "@earendil-works/pi-tui";
|
|
17
|
-
import { getGeneratedImagesRoot } from "./artifacts";
|
|
18
|
-
import { isValidPng, readPngDimensions } from "./protocol";
|
|
19
|
-
import {
|
|
20
|
-
IMAGE_GENERATION_MIME_TYPE,
|
|
21
|
-
MAX_GENERATED_IMAGE_BYTES,
|
|
22
|
-
isImageGenerationDetails,
|
|
23
|
-
type GenerateImageParams,
|
|
24
|
-
type ImageGenerationDetails,
|
|
25
|
-
} from "./types";
|
|
26
|
-
|
|
27
|
-
export type ImageGenerationRenderState = Record<string, never>;
|
|
28
|
-
|
|
29
|
-
export type ImageGenerationRenderContext = {
|
|
30
|
-
lastComponent: Component | undefined;
|
|
31
|
-
showImages: boolean;
|
|
32
|
-
isError: boolean;
|
|
33
|
-
isPartial: boolean;
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
export type ImageRendererDependencies = {
|
|
37
|
-
getAgentDir: () => string;
|
|
38
|
-
getCapabilities: typeof getCapabilities;
|
|
39
|
-
readFileSync: typeof readFileSync;
|
|
40
|
-
realpathSync: typeof realpathSync;
|
|
41
|
-
statSync: typeof statSync;
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
const DEFAULT_RENDER_DEPS: ImageRendererDependencies = {
|
|
45
|
-
getAgentDir,
|
|
46
|
-
getCapabilities,
|
|
47
|
-
readFileSync,
|
|
48
|
-
realpathSync,
|
|
49
|
-
statSync,
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
function isPathInsideOrEqual(root: string, candidate: string): boolean {
|
|
53
|
-
const rel = relative(root, candidate);
|
|
54
|
-
return rel === "" || (!rel.startsWith(`..${sep}`) && rel !== ".." && !isAbsolute(rel));
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function resultText(result: AgentToolResult<ImageGenerationDetails>): string {
|
|
58
|
-
return result.content
|
|
59
|
-
.filter((block): block is { type: "text"; text: string } => block.type === "text")
|
|
60
|
-
.map((block) => block.text)
|
|
61
|
-
.join("\n")
|
|
62
|
-
.trim();
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
class DiskBackedImageResult extends Container {
|
|
66
|
-
readonly artifactPath?: string;
|
|
67
|
-
readonly fileSize?: number;
|
|
68
|
-
readonly mtimeMs?: number;
|
|
69
|
-
readonly showImages: boolean;
|
|
70
|
-
readonly textValue: string;
|
|
71
|
-
|
|
72
|
-
constructor(args: {
|
|
73
|
-
text: string;
|
|
74
|
-
artifactPath?: string;
|
|
75
|
-
fileSize?: number;
|
|
76
|
-
mtimeMs?: number;
|
|
77
|
-
showImages: boolean;
|
|
78
|
-
base64?: string;
|
|
79
|
-
theme: Theme;
|
|
80
|
-
previewWarning?: string;
|
|
81
|
-
}) {
|
|
82
|
-
super();
|
|
83
|
-
this.artifactPath = args.artifactPath;
|
|
84
|
-
this.fileSize = args.fileSize;
|
|
85
|
-
this.mtimeMs = args.mtimeMs;
|
|
86
|
-
this.showImages = args.showImages;
|
|
87
|
-
this.textValue = `${args.text}\n${args.previewWarning ?? ""}`;
|
|
88
|
-
this.addChild(new Text(args.theme.fg("toolOutput", args.text), 0, 0));
|
|
89
|
-
if (args.previewWarning) {
|
|
90
|
-
this.addChild(new Text(args.theme.fg("muted", args.previewWarning), 0, 0));
|
|
91
|
-
}
|
|
92
|
-
if (args.base64) {
|
|
93
|
-
this.addChild(new Spacer(1));
|
|
94
|
-
this.addChild(
|
|
95
|
-
new Image(
|
|
96
|
-
args.base64,
|
|
97
|
-
IMAGE_GENERATION_MIME_TYPE,
|
|
98
|
-
{ fallbackColor: (text) => args.theme.fg("toolOutput", text) },
|
|
99
|
-
{ maxWidthCells: 60, filename: args.artifactPath },
|
|
100
|
-
),
|
|
101
|
-
);
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
matches(args: {
|
|
106
|
-
text: string;
|
|
107
|
-
artifactPath?: string;
|
|
108
|
-
fileSize?: number;
|
|
109
|
-
mtimeMs?: number;
|
|
110
|
-
showImages: boolean;
|
|
111
|
-
previewWarning?: string;
|
|
112
|
-
}): boolean {
|
|
113
|
-
return (
|
|
114
|
-
this.textValue === `${args.text}\n${args.previewWarning ?? ""}` &&
|
|
115
|
-
this.artifactPath === args.artifactPath &&
|
|
116
|
-
this.fileSize === args.fileSize &&
|
|
117
|
-
this.mtimeMs === args.mtimeMs &&
|
|
118
|
-
this.showImages === args.showImages
|
|
119
|
-
);
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
function textOnly(args: {
|
|
124
|
-
text: string;
|
|
125
|
-
theme: Theme;
|
|
126
|
-
context: ImageGenerationRenderContext;
|
|
127
|
-
previewWarning?: string;
|
|
128
|
-
}): Component {
|
|
129
|
-
const previous = args.context.lastComponent;
|
|
130
|
-
if (
|
|
131
|
-
previous instanceof DiskBackedImageResult &&
|
|
132
|
-
previous.matches({
|
|
133
|
-
text: args.text,
|
|
134
|
-
showImages: false,
|
|
135
|
-
previewWarning: args.previewWarning,
|
|
136
|
-
})
|
|
137
|
-
) {
|
|
138
|
-
return previous;
|
|
139
|
-
}
|
|
140
|
-
return new DiskBackedImageResult({
|
|
141
|
-
text: args.text,
|
|
142
|
-
showImages: false,
|
|
143
|
-
theme: args.theme,
|
|
144
|
-
previewWarning: args.previewWarning,
|
|
145
|
-
});
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
export function createImageGenerationResultRenderer(
|
|
149
|
-
deps: ImageRendererDependencies = DEFAULT_RENDER_DEPS,
|
|
150
|
-
): (
|
|
151
|
-
result: AgentToolResult<ImageGenerationDetails>,
|
|
152
|
-
options: ToolRenderResultOptions,
|
|
153
|
-
theme: Theme,
|
|
154
|
-
context: ImageGenerationRenderContext,
|
|
155
|
-
) => Component {
|
|
156
|
-
return (result, _options, theme, context) => {
|
|
157
|
-
const text = resultText(result) || "Image generation finished.";
|
|
158
|
-
if (
|
|
159
|
-
context.isError ||
|
|
160
|
-
context.isPartial ||
|
|
161
|
-
!context.showImages ||
|
|
162
|
-
!isImageGenerationDetails(result.details) ||
|
|
163
|
-
!deps.getCapabilities().images
|
|
164
|
-
) {
|
|
165
|
-
return textOnly({ text, theme, context });
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
const artifactPath = resolve(result.details.artifactPath);
|
|
169
|
-
let root: string;
|
|
170
|
-
let realArtifact: string;
|
|
171
|
-
let info: ReturnType<typeof statSync>;
|
|
172
|
-
try {
|
|
173
|
-
root = deps.realpathSync(getGeneratedImagesRoot(deps.getAgentDir()));
|
|
174
|
-
realArtifact = deps.realpathSync(artifactPath);
|
|
175
|
-
info = deps.statSync(realArtifact);
|
|
176
|
-
} catch {
|
|
177
|
-
return textOnly({
|
|
178
|
-
text,
|
|
179
|
-
theme,
|
|
180
|
-
context,
|
|
181
|
-
previewWarning: "Image preview unavailable: the saved artifact is missing.",
|
|
182
|
-
});
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
if (
|
|
186
|
-
!isPathInsideOrEqual(root, realArtifact) ||
|
|
187
|
-
!info.isFile() ||
|
|
188
|
-
info.size <= 0 ||
|
|
189
|
-
info.size > MAX_GENERATED_IMAGE_BYTES
|
|
190
|
-
) {
|
|
191
|
-
return textOnly({
|
|
192
|
-
text,
|
|
193
|
-
theme,
|
|
194
|
-
context,
|
|
195
|
-
previewWarning: "Image preview unavailable: the artifact path or file is not safe.",
|
|
196
|
-
});
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
const previous = context.lastComponent;
|
|
200
|
-
if (
|
|
201
|
-
previous instanceof DiskBackedImageResult &&
|
|
202
|
-
previous.matches({
|
|
203
|
-
text,
|
|
204
|
-
artifactPath: realArtifact,
|
|
205
|
-
fileSize: info.size,
|
|
206
|
-
mtimeMs: info.mtimeMs,
|
|
207
|
-
showImages: true,
|
|
208
|
-
})
|
|
209
|
-
) {
|
|
210
|
-
return previous;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
let bytes: Buffer;
|
|
214
|
-
try {
|
|
215
|
-
bytes = deps.readFileSync(realArtifact);
|
|
216
|
-
} catch {
|
|
217
|
-
return textOnly({
|
|
218
|
-
text,
|
|
219
|
-
theme,
|
|
220
|
-
context,
|
|
221
|
-
previewWarning: "Image preview unavailable: the artifact could not be read.",
|
|
222
|
-
});
|
|
223
|
-
}
|
|
224
|
-
if (bytes.length !== info.size || !isValidPng(bytes) || !readPngDimensions(bytes)) {
|
|
225
|
-
bytes.fill(0);
|
|
226
|
-
return textOnly({
|
|
227
|
-
text,
|
|
228
|
-
theme,
|
|
229
|
-
context,
|
|
230
|
-
previewWarning: "Image preview unavailable: the artifact is not a valid PNG.",
|
|
231
|
-
});
|
|
232
|
-
}
|
|
233
|
-
const base64 = bytes.toString("base64");
|
|
234
|
-
bytes.fill(0);
|
|
235
|
-
return new DiskBackedImageResult({
|
|
236
|
-
text,
|
|
237
|
-
artifactPath: realArtifact,
|
|
238
|
-
fileSize: info.size,
|
|
239
|
-
mtimeMs: info.mtimeMs,
|
|
240
|
-
showImages: true,
|
|
241
|
-
base64,
|
|
242
|
-
theme,
|
|
243
|
-
});
|
|
244
|
-
};
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
export const renderImageGenerationResult = createImageGenerationResultRenderer();
|
|
248
|
-
|
|
249
|
-
export const _renderTest = {
|
|
250
|
-
DiskBackedImageResult,
|
|
251
|
-
isPathInsideOrEqual,
|
|
252
|
-
};
|
|
1
|
+
import { readFileSync, realpathSync, statSync } from "node:fs";
|
|
2
|
+
import { isAbsolute, relative, resolve, sep } from "node:path";
|
|
3
|
+
import type { AgentToolResult } from "@earendil-works/pi-agent-core";
|
|
4
|
+
import {
|
|
5
|
+
getAgentDir,
|
|
6
|
+
type Theme,
|
|
7
|
+
type ToolRenderResultOptions,
|
|
8
|
+
} from "@earendil-works/pi-coding-agent";
|
|
9
|
+
import {
|
|
10
|
+
Container,
|
|
11
|
+
getCapabilities,
|
|
12
|
+
Image,
|
|
13
|
+
Spacer,
|
|
14
|
+
Text,
|
|
15
|
+
type Component,
|
|
16
|
+
} from "@earendil-works/pi-tui";
|
|
17
|
+
import { getGeneratedImagesRoot } from "./artifacts";
|
|
18
|
+
import { isValidPng, readPngDimensions } from "./protocol";
|
|
19
|
+
import {
|
|
20
|
+
IMAGE_GENERATION_MIME_TYPE,
|
|
21
|
+
MAX_GENERATED_IMAGE_BYTES,
|
|
22
|
+
isImageGenerationDetails,
|
|
23
|
+
type GenerateImageParams,
|
|
24
|
+
type ImageGenerationDetails,
|
|
25
|
+
} from "./types";
|
|
26
|
+
|
|
27
|
+
export type ImageGenerationRenderState = Record<string, never>;
|
|
28
|
+
|
|
29
|
+
export type ImageGenerationRenderContext = {
|
|
30
|
+
lastComponent: Component | undefined;
|
|
31
|
+
showImages: boolean;
|
|
32
|
+
isError: boolean;
|
|
33
|
+
isPartial: boolean;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export type ImageRendererDependencies = {
|
|
37
|
+
getAgentDir: () => string;
|
|
38
|
+
getCapabilities: typeof getCapabilities;
|
|
39
|
+
readFileSync: typeof readFileSync;
|
|
40
|
+
realpathSync: typeof realpathSync;
|
|
41
|
+
statSync: typeof statSync;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const DEFAULT_RENDER_DEPS: ImageRendererDependencies = {
|
|
45
|
+
getAgentDir,
|
|
46
|
+
getCapabilities,
|
|
47
|
+
readFileSync,
|
|
48
|
+
realpathSync,
|
|
49
|
+
statSync,
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
function isPathInsideOrEqual(root: string, candidate: string): boolean {
|
|
53
|
+
const rel = relative(root, candidate);
|
|
54
|
+
return rel === "" || (!rel.startsWith(`..${sep}`) && rel !== ".." && !isAbsolute(rel));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function resultText(result: AgentToolResult<ImageGenerationDetails>): string {
|
|
58
|
+
return result.content
|
|
59
|
+
.filter((block): block is { type: "text"; text: string } => block.type === "text")
|
|
60
|
+
.map((block) => block.text)
|
|
61
|
+
.join("\n")
|
|
62
|
+
.trim();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
class DiskBackedImageResult extends Container {
|
|
66
|
+
readonly artifactPath?: string;
|
|
67
|
+
readonly fileSize?: number;
|
|
68
|
+
readonly mtimeMs?: number;
|
|
69
|
+
readonly showImages: boolean;
|
|
70
|
+
readonly textValue: string;
|
|
71
|
+
|
|
72
|
+
constructor(args: {
|
|
73
|
+
text: string;
|
|
74
|
+
artifactPath?: string;
|
|
75
|
+
fileSize?: number;
|
|
76
|
+
mtimeMs?: number;
|
|
77
|
+
showImages: boolean;
|
|
78
|
+
base64?: string;
|
|
79
|
+
theme: Theme;
|
|
80
|
+
previewWarning?: string;
|
|
81
|
+
}) {
|
|
82
|
+
super();
|
|
83
|
+
this.artifactPath = args.artifactPath;
|
|
84
|
+
this.fileSize = args.fileSize;
|
|
85
|
+
this.mtimeMs = args.mtimeMs;
|
|
86
|
+
this.showImages = args.showImages;
|
|
87
|
+
this.textValue = `${args.text}\n${args.previewWarning ?? ""}`;
|
|
88
|
+
this.addChild(new Text(args.theme.fg("toolOutput", args.text), 0, 0));
|
|
89
|
+
if (args.previewWarning) {
|
|
90
|
+
this.addChild(new Text(args.theme.fg("muted", args.previewWarning), 0, 0));
|
|
91
|
+
}
|
|
92
|
+
if (args.base64) {
|
|
93
|
+
this.addChild(new Spacer(1));
|
|
94
|
+
this.addChild(
|
|
95
|
+
new Image(
|
|
96
|
+
args.base64,
|
|
97
|
+
IMAGE_GENERATION_MIME_TYPE,
|
|
98
|
+
{ fallbackColor: (text) => args.theme.fg("toolOutput", text) },
|
|
99
|
+
{ maxWidthCells: 60, filename: args.artifactPath },
|
|
100
|
+
),
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
matches(args: {
|
|
106
|
+
text: string;
|
|
107
|
+
artifactPath?: string;
|
|
108
|
+
fileSize?: number;
|
|
109
|
+
mtimeMs?: number;
|
|
110
|
+
showImages: boolean;
|
|
111
|
+
previewWarning?: string;
|
|
112
|
+
}): boolean {
|
|
113
|
+
return (
|
|
114
|
+
this.textValue === `${args.text}\n${args.previewWarning ?? ""}` &&
|
|
115
|
+
this.artifactPath === args.artifactPath &&
|
|
116
|
+
this.fileSize === args.fileSize &&
|
|
117
|
+
this.mtimeMs === args.mtimeMs &&
|
|
118
|
+
this.showImages === args.showImages
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function textOnly(args: {
|
|
124
|
+
text: string;
|
|
125
|
+
theme: Theme;
|
|
126
|
+
context: ImageGenerationRenderContext;
|
|
127
|
+
previewWarning?: string;
|
|
128
|
+
}): Component {
|
|
129
|
+
const previous = args.context.lastComponent;
|
|
130
|
+
if (
|
|
131
|
+
previous instanceof DiskBackedImageResult &&
|
|
132
|
+
previous.matches({
|
|
133
|
+
text: args.text,
|
|
134
|
+
showImages: false,
|
|
135
|
+
previewWarning: args.previewWarning,
|
|
136
|
+
})
|
|
137
|
+
) {
|
|
138
|
+
return previous;
|
|
139
|
+
}
|
|
140
|
+
return new DiskBackedImageResult({
|
|
141
|
+
text: args.text,
|
|
142
|
+
showImages: false,
|
|
143
|
+
theme: args.theme,
|
|
144
|
+
previewWarning: args.previewWarning,
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export function createImageGenerationResultRenderer(
|
|
149
|
+
deps: ImageRendererDependencies = DEFAULT_RENDER_DEPS,
|
|
150
|
+
): (
|
|
151
|
+
result: AgentToolResult<ImageGenerationDetails>,
|
|
152
|
+
options: ToolRenderResultOptions,
|
|
153
|
+
theme: Theme,
|
|
154
|
+
context: ImageGenerationRenderContext,
|
|
155
|
+
) => Component {
|
|
156
|
+
return (result, _options, theme, context) => {
|
|
157
|
+
const text = resultText(result) || "Image generation finished.";
|
|
158
|
+
if (
|
|
159
|
+
context.isError ||
|
|
160
|
+
context.isPartial ||
|
|
161
|
+
!context.showImages ||
|
|
162
|
+
!isImageGenerationDetails(result.details) ||
|
|
163
|
+
!deps.getCapabilities().images
|
|
164
|
+
) {
|
|
165
|
+
return textOnly({ text, theme, context });
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const artifactPath = resolve(result.details.artifactPath);
|
|
169
|
+
let root: string;
|
|
170
|
+
let realArtifact: string;
|
|
171
|
+
let info: ReturnType<typeof statSync>;
|
|
172
|
+
try {
|
|
173
|
+
root = deps.realpathSync(getGeneratedImagesRoot(deps.getAgentDir()));
|
|
174
|
+
realArtifact = deps.realpathSync(artifactPath);
|
|
175
|
+
info = deps.statSync(realArtifact);
|
|
176
|
+
} catch {
|
|
177
|
+
return textOnly({
|
|
178
|
+
text,
|
|
179
|
+
theme,
|
|
180
|
+
context,
|
|
181
|
+
previewWarning: "Image preview unavailable: the saved artifact is missing.",
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (
|
|
186
|
+
!isPathInsideOrEqual(root, realArtifact) ||
|
|
187
|
+
!info.isFile() ||
|
|
188
|
+
info.size <= 0 ||
|
|
189
|
+
info.size > MAX_GENERATED_IMAGE_BYTES
|
|
190
|
+
) {
|
|
191
|
+
return textOnly({
|
|
192
|
+
text,
|
|
193
|
+
theme,
|
|
194
|
+
context,
|
|
195
|
+
previewWarning: "Image preview unavailable: the artifact path or file is not safe.",
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const previous = context.lastComponent;
|
|
200
|
+
if (
|
|
201
|
+
previous instanceof DiskBackedImageResult &&
|
|
202
|
+
previous.matches({
|
|
203
|
+
text,
|
|
204
|
+
artifactPath: realArtifact,
|
|
205
|
+
fileSize: info.size,
|
|
206
|
+
mtimeMs: info.mtimeMs,
|
|
207
|
+
showImages: true,
|
|
208
|
+
})
|
|
209
|
+
) {
|
|
210
|
+
return previous;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
let bytes: Buffer;
|
|
214
|
+
try {
|
|
215
|
+
bytes = deps.readFileSync(realArtifact);
|
|
216
|
+
} catch {
|
|
217
|
+
return textOnly({
|
|
218
|
+
text,
|
|
219
|
+
theme,
|
|
220
|
+
context,
|
|
221
|
+
previewWarning: "Image preview unavailable: the artifact could not be read.",
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
if (bytes.length !== info.size || !isValidPng(bytes) || !readPngDimensions(bytes)) {
|
|
225
|
+
bytes.fill(0);
|
|
226
|
+
return textOnly({
|
|
227
|
+
text,
|
|
228
|
+
theme,
|
|
229
|
+
context,
|
|
230
|
+
previewWarning: "Image preview unavailable: the artifact is not a valid PNG.",
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
const base64 = bytes.toString("base64");
|
|
234
|
+
bytes.fill(0);
|
|
235
|
+
return new DiskBackedImageResult({
|
|
236
|
+
text,
|
|
237
|
+
artifactPath: realArtifact,
|
|
238
|
+
fileSize: info.size,
|
|
239
|
+
mtimeMs: info.mtimeMs,
|
|
240
|
+
showImages: true,
|
|
241
|
+
base64,
|
|
242
|
+
theme,
|
|
243
|
+
});
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
export const renderImageGenerationResult = createImageGenerationResultRenderer();
|
|
248
|
+
|
|
249
|
+
export const _renderTest = {
|
|
250
|
+
DiskBackedImageResult,
|
|
251
|
+
isPathInsideOrEqual,
|
|
252
|
+
};
|