Files
makelore/electron/coding-runtime/pi/extensions/game-assets.ts

86 lines
2.9 KiB
TypeScript

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<string, unknown> {
return value && typeof value === 'object' && !Array.isArray(value)
? value as Record<string, unknown>
: {};
}
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<GameAssetToolResult> {
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<GameAssetToolResult> {
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),
};
}
}