Files
oh-my-pi/packages/coding-agent/src/internal-urls/artifact-protocol.ts
T
can1357 779ca4872b style(deps): migrated from Prettier to Biome and updated formatting rules
- Removed Prettier configuration files (.prettierignore and .prettierrc) and migrated formatting to Biome.
- Updated Biome configuration from version 2.3.11 to 2.3.12 and changed arrowParentheses rule from 'always' to 'asNeeded'.
- Pinned @biomejs/biome dependency to exact version 2.3.12 in package.json and bun.lock.
- Applied consistent arrow function formatting across 489 files by removing unnecessary parentheses around single parameters.
- Removed blank lines after comment blocks and reorganized imports for consistency across the codebase.
2026-01-24 04:20:19 +01:00

98 lines
2.6 KiB
TypeScript

/**
* Protocol handler for artifact:// URLs.
*
* Resolves artifact IDs to files in the session artifacts directory.
* Unlike agent://, artifacts are raw text with no JSON extraction.
*
* URL form:
* - artifact://<id> - Full artifact content
*
* Pagination is handled by the read tool via offset/limit parameters.
*/
import * as fs from "node:fs/promises";
import * as path from "node:path";
import { isEnoent } from "@oh-my-pi/pi-utils";
import type { InternalResource, InternalUrl, ProtocolHandler } from "./types";
export interface ArtifactProtocolOptions {
/**
* Returns the artifacts directory path, or null if no session.
*/
getArtifactsDir: () => string | null;
}
/**
* List available artifact IDs in the directory.
*/
async function listAvailableArtifacts(artifactsDir: string): Promise<string[]> {
try {
const files = await fs.readdir(artifactsDir);
return files
.filter(f => /^\d+\./.test(f))
.map(f => f.split(".")[0])
.sort((a, b) => Number(a) - Number(b));
} catch {
return [];
}
}
/**
* Handler for artifact:// URLs.
*
* Resolves numeric artifact IDs to their text content.
* Artifacts are created by tools when output is truncated.
*/
export class ArtifactProtocolHandler implements ProtocolHandler {
readonly scheme = "artifact";
constructor(private readonly options: ArtifactProtocolOptions) {}
async resolve(url: InternalUrl): Promise<InternalResource> {
const artifactsDir = this.options.getArtifactsDir();
if (!artifactsDir) {
throw new Error("No session - artifacts unavailable");
}
// Extract artifact ID from host
const id = url.rawHost || url.hostname;
if (!id) {
throw new Error("artifact:// URL requires a numeric ID: artifact://0");
}
// Validate ID is numeric
if (!/^\d+$/.test(id)) {
throw new Error(`artifact:// ID must be numeric, got: ${id}`);
}
// Check directory exists and find file matching ID prefix
let files: string[];
try {
files = await fs.readdir(artifactsDir);
} catch (err) {
if (isEnoent(err)) {
throw new Error("No artifacts directory found");
}
throw err;
}
const match = files.find(f => f.startsWith(`${id}.`));
if (!match) {
const available = await listAvailableArtifacts(artifactsDir);
const availableStr = available.length > 0 ? available.join(", ") : "none";
throw new Error(`Artifact ${id} not found. Available: ${availableStr}`);
}
const filePath = path.join(artifactsDir, match);
const content = await Bun.file(filePath).text();
return {
url: url.href,
content,
contentType: "text/plain",
size: Buffer.byteLength(content, "utf-8"),
sourcePath: filePath,
};
}
}