import { loadGameAssetCandidates, type GameAssetCandidate, } from '../../../coding-projects/game-asset-browser'; import { loadGameAssetReview, type GameAssetReviewSnapshot, } from '../../../coding-projects/game-asset-review'; import type { GameAssetsDetailsV1 } from '../../contracts'; export interface GameAssetToolResult { content: Array<{ type: 'text'; text: string }>; details: GameAssetsDetailsV1; } function record(value: unknown): Record { return value && typeof value === 'object' && !Array.isArray(value) ? value as Record : {}; } function ids(value: unknown): string[] { if (value === undefined) return []; if (!Array.isArray(value) || value.length > 200 || value.some((item) => typeof item !== 'string')) { throw new Error('Game asset candidate ids are invalid'); } return [...new Set(value.map((item) => item.trim()).filter(Boolean))]; } function details(review: GameAssetReviewSnapshot): GameAssetsDetailsV1 { return { schema: 'game-assets.v1', invocationId: review.invocationId, candidateIds: review.candidateIds, status: review.status, pendingAssetIds: review.pendingAssetIds, approvedAssetIds: review.approvedAssetIds, discardedAssetIds: review.discardedAssetIds, }; } function safeCandidate(candidate: GameAssetCandidate) { return { id: candidate.id, name: candidate.name, category: candidate.category, status: candidate.status, purpose: candidate.purpose, source: candidate.source, license: candidate.license, mediaKind: candidate.mediaKind, manifest: candidate.manifest, ...(candidate.unavailableReason ? { unavailableReason: candidate.unavailableReason } : {}), }; } export class PiGameAssetTools { async browse(projectPath: string, input: unknown, fallbackInvocationId: string): Promise { const body = record(input); const invocationId = typeof body.invocationId === 'string' && body.invocationId.trim() ? body.invocationId.trim() : fallbackInvocationId; const candidates = await loadGameAssetCandidates(projectPath); const review = await loadGameAssetReview(projectPath, invocationId, candidates.map(({ id }) => id)); return { content: [{ type: 'text', text: JSON.stringify({ candidates: candidates.map(safeCandidate), review: details(review) }), }], details: details(review), }; } async review(projectPath: string, input: unknown, fallbackInvocationId: string): Promise { const body = record(input); const invocationId = typeof body.invocationId === 'string' && body.invocationId.trim() ? body.invocationId.trim() : fallbackInvocationId; const review = await loadGameAssetReview(projectPath, invocationId, ids(body.candidateIds)); return { content: [{ type: 'text', text: JSON.stringify(details(review)) }], details: details(review), }; } }