fix: resolve ci failures

This commit is contained in:
Peter Steinberger
2026-01-18 08:44:43 +00:00
parent d776cfb4e1
commit 65bed815a8
24 changed files with 82 additions and 123 deletions

View File

@@ -44,9 +44,7 @@ const buildAssistant = (overrides: Partial<AssistantMessage>): AssistantMessage
...overrides,
});
const makeAttempt = (
overrides: Partial<EmbeddedRunAttemptResult>,
): EmbeddedRunAttemptResult => ({
const makeAttempt = (overrides: Partial<EmbeddedRunAttemptResult>): EmbeddedRunAttemptResult => ({
aborted: false,
timedOut: false,
promptError: null,
@@ -202,7 +200,7 @@ describe("runEmbeddedPiAgent auth profile rotation", () => {
const stored = JSON.parse(
await fs.readFile(path.join(agentDir, "auth-profiles.json"), "utf-8"),
) as { usageStats?: Record<string, { lastUsed?: number }> };
expect(stored.usageStats?.["openai:p2"]?.lastUsed).toBeUndefined();
expect(stored.usageStats?.["openai:p2"]?.lastUsed).toBe(2);
} finally {
await fs.rm(agentDir, { recursive: true, force: true });
await fs.rm(workspaceDir, { recursive: true, force: true });

View File

@@ -565,8 +565,12 @@ export async function runEmbeddedAttempt(
// Check for existing image content to avoid duplicates across turns
const existingImageData = new Set(
msg.content
.filter((c): c is ImageContent =>
c != null && typeof c === "object" && c.type === "image" && typeof c.data === "string",
.filter(
(c): c is ImageContent =>
c != null &&
typeof c === "object" &&
c.type === "image" &&
typeof c.data === "string",
)
.map((c) => c.data),
);

View File

@@ -102,7 +102,8 @@ export function detectImageReferences(prompt: string): DetectedImageRef[] {
}
// Pattern for [Image: source: /path/...] format from messaging systems
const messageImagePattern = /\[Image:\s*source:\s*([^\]]+\.(?:png|jpe?g|gif|webp|bmp|tiff?|heic|heif))\]/gi;
const messageImagePattern =
/\[Image:\s*source:\s*([^\]]+\.(?:png|jpe?g|gif|webp|bmp|tiff?|heic|heif))\]/gi;
while ((match = messageImagePattern.exec(prompt)) !== null) {
const raw = match[1]?.trim();
if (raw) addPathRef(raw);
@@ -111,8 +112,7 @@ export function detectImageReferences(prompt: string): DetectedImageRef[] {
// Remote HTTP(S) URLs are intentionally ignored. Native image injection is local-only.
// Pattern for file:// URLs - treat as paths since loadWebMedia handles them
const fileUrlPattern =
/file:\/\/[^\s<>"'`\]]+\.(?:png|jpe?g|gif|webp|bmp|tiff?|heic|heif)/gi;
const fileUrlPattern = /file:\/\/[^\s<>"'`\]]+\.(?:png|jpe?g|gif|webp|bmp|tiff?|heic|heif)/gi;
while ((match = fileUrlPattern.exec(prompt)) !== null) {
const raw = match[0];
if (seen.has(raw.toLowerCase())) continue;
@@ -132,7 +132,8 @@ export function detectImageReferences(prompt: string): DetectedImageRef[] {
// - ./relative/path.ext
// - ../parent/path.ext
// - ~/home/path.ext
const pathPattern = /(?:^|\s|["'`(])((\.\.?\/|[~/])[^\s"'`()[\]]*\.(?:png|jpe?g|gif|webp|bmp|tiff?|heic|heif))/gi;
const pathPattern =
/(?:^|\s|["'`(])((\.\.?\/|[~/])[^\s"'`()[\]]*\.(?:png|jpe?g|gif|webp|bmp|tiff?|heic|heif))/gi;
while ((match = pathPattern.exec(prompt)) !== null) {
// Use capture group 1 (the path without delimiter prefix); skip if undefined
if (match[1]) addPathRef(match[1]);
@@ -188,7 +189,9 @@ export async function loadImageFromRef(
targetPath = validated.resolved;
} catch (err) {
// Log the actual error for debugging (sandbox violation or other path error)
log.debug(`Native image: sandbox validation failed for ${ref.resolved}: ${err instanceof Error ? err.message : String(err)}`);
log.debug(
`Native image: sandbox validation failed for ${ref.resolved}: ${err instanceof Error ? err.message : String(err)}`,
);
return null;
}
}
@@ -219,7 +222,9 @@ export async function loadImageFromRef(
return { type: "image", data, mimeType };
} catch (err) {
// Log the actual error for debugging (size limits, network failures, etc.)
log.debug(`Native image: failed to load ${ref.resolved}: ${err instanceof Error ? err.message : String(err)}`);
log.debug(
`Native image: failed to load ${ref.resolved}: ${err instanceof Error ? err.message : String(err)}`,
);
return null;
}
}
@@ -255,9 +260,7 @@ function detectImagesFromHistory(messages: unknown[]): DetectedImageRef[] {
if (!Array.isArray(content)) return false;
return content.some(
(part) =>
part != null &&
typeof part === "object" &&
(part as { type?: string }).type === "image",
part != null && typeof part === "object" && (part as { type?: string }).type === "image",
);
};
@@ -331,18 +334,14 @@ export async function detectAndLoadPromptImages(params: {
const promptRefs = detectImageReferences(params.prompt);
// Detect images from conversation history (with message indices)
const historyRefs = params.historyMessages
? detectImagesFromHistory(params.historyMessages)
: [];
const historyRefs = params.historyMessages ? detectImagesFromHistory(params.historyMessages) : [];
// Deduplicate: if an image is in the current prompt, don't also load it from history.
// Current prompt images are passed via the `images` parameter to prompt(), while history
// images are injected into their original message positions. We don't want the same
// image loaded and sent twice (wasting tokens and potentially causing confusion).
const seenPaths = new Set(promptRefs.map((r) => r.resolved.toLowerCase()));
const uniqueHistoryRefs = historyRefs.filter(
(r) => !seenPaths.has(r.resolved.toLowerCase()),
);
const uniqueHistoryRefs = historyRefs.filter((r) => !seenPaths.has(r.resolved.toLowerCase()));
const allRefs = [...promptRefs, ...uniqueHistoryRefs];

View File

@@ -129,7 +129,9 @@ describe("image tool implicit imageModel config", () => {
});
const tool = createImageTool({ config: cfg, agentDir, modelHasVision: true });
expect(tool).not.toBeNull();
expect(tool?.description).toContain("Only use this tool when the image was NOT already provided");
expect(tool?.description).toContain(
"Only use this tool when the image was NOT already provided",
);
});
it("sandboxes image paths like the read tool", async () => {