feat: integrate automatic game resource delivery
This commit is contained in:
@@ -8,6 +8,11 @@ import type { CodingPluginToolDefinition } from '../../shared/coding-plugins';
|
||||
import {
|
||||
GameResourcePluginAdapter,
|
||||
} from '../../electron/coding-plugins/adapters/game-resource';
|
||||
import { PiProjectWriteLeaseCoordinator } from '../../electron/coding-runtime/pi/write-lease';
|
||||
import {
|
||||
GameResourceDeliveryCoordinator,
|
||||
GameResourceDeliveryReceiptStore,
|
||||
} from '../../electron/services/game-resource-delivery';
|
||||
import type { GameResourceClient, GameResourceGeneration } from '../../electron/services/game-resource-client';
|
||||
import type { MarketplacePackageClientPort, PluginPackageStore } from '../../electron/coding-plugins/package-store';
|
||||
import type { TrustedCodingCapabilityContext } from '../../electron/coding-plugins/registry';
|
||||
@@ -52,8 +57,8 @@ function tool(
|
||||
capabilityId,
|
||||
operation,
|
||||
roles: ['parent'],
|
||||
mutation: name === 'game_resource_status' ? 'read' : 'write',
|
||||
projectWriteLease: name === 'game_resource_save_output',
|
||||
mutation: operation === 'templates' ? 'read' : 'write',
|
||||
projectWriteLease: false,
|
||||
permissions: [`hosted.game-resource.${operation.replaceAll('_', '-')}`],
|
||||
inputSchema: { type: 'object' },
|
||||
};
|
||||
@@ -61,7 +66,8 @@ function tool(
|
||||
|
||||
async function fixture() {
|
||||
const projectPath = await mkdtemp(path.join(tmpdir(), 'makelore-game-resource-'));
|
||||
roots.push(projectPath);
|
||||
const userDataDir = await mkdtemp(path.join(tmpdir(), 'makelore-game-resource-receipts-'));
|
||||
roots.push(projectPath, userDataDir);
|
||||
const resolve = vi.fn(async () => ({
|
||||
resolveRequestId: 'pi:run-a:resource-a',
|
||||
resolveRequestDigest: 'a'.repeat(64),
|
||||
@@ -84,8 +90,15 @@ async function fixture() {
|
||||
bytes: new Uint8Array([1, 2, 3]), fileName: 'hero.png', contentType: 'image/png',
|
||||
})),
|
||||
};
|
||||
const delivery = new GameResourceDeliveryCoordinator({
|
||||
client: client as unknown as GameResourceClient,
|
||||
receipts: new GameResourceDeliveryReceiptStore(path.join(userDataDir, 'receipts.json')),
|
||||
leases: new PiProjectWriteLeaseCoordinator(),
|
||||
sleep: async () => undefined,
|
||||
});
|
||||
const adapter = new GameResourcePluginAdapter({
|
||||
client: client as unknown as GameResourceClient,
|
||||
delivery,
|
||||
marketplace: { resolve } as unknown as MarketplacePackageClientPort,
|
||||
packageStore: {
|
||||
getInstalled: vi.fn(async () => ({
|
||||
@@ -113,15 +126,16 @@ afterEach(async () => {
|
||||
});
|
||||
|
||||
describe('GameResourcePluginAdapter', () => {
|
||||
it('resolves a fresh admission and keeps project reference bytes behind the hosted boundary', async () => {
|
||||
it('resolves admission, hides polling, and automatically saves generated output in the project', async () => {
|
||||
const { adapter, client, context, projectPath, resolve } = await fixture();
|
||||
await writeFile(path.join(projectPath, 'reference.png'), new Uint8Array([4, 5, 6]));
|
||||
const onProgress = vi.fn();
|
||||
|
||||
const result = await adapter.invoke(context, tool('game_resource_generate'), {
|
||||
kind: 'pixel', templateName: 'character', requirement: 'Blue-armored hero',
|
||||
confirmed: true,
|
||||
referencePaths: ['reference.png'],
|
||||
});
|
||||
}, onProgress);
|
||||
|
||||
expect(resolve).toHaveBeenCalledWith(expect.objectContaining({
|
||||
resolveRequestId: 'pi:run-a:resource-a', channel: 'stable',
|
||||
@@ -133,9 +147,26 @@ describe('GameResourcePluginAdapter', () => {
|
||||
referenceFiles: [{ name: 'reference.png', mimeType: 'image/png', dataBase64: 'BAUG' }],
|
||||
}));
|
||||
expect(result).toMatchObject({
|
||||
success: true, status: 202, billing,
|
||||
data: { executionId: EXECUTION_ID, status: 'accepted', outputCount: 0 },
|
||||
success: true, status: 200, billing,
|
||||
data: {
|
||||
executionId: EXECUTION_ID,
|
||||
providerStatus: 'succeeded',
|
||||
deliveryStatus: 'saved',
|
||||
files: [{
|
||||
path: `assets/generated/game-resource/${EXECUTION_ID}/output-1.png`,
|
||||
bytes: 3,
|
||||
}],
|
||||
},
|
||||
});
|
||||
expect(client.get).toHaveBeenCalledTimes(1);
|
||||
expect(client.download).toHaveBeenCalledWith(EXECUTION_ID, 0);
|
||||
await expect(readFile(path.join(
|
||||
projectPath,
|
||||
'assets', 'generated', 'game-resource', EXECUTION_ID, 'output-1.png',
|
||||
))).resolves.toEqual(Buffer.from([1, 2, 3]));
|
||||
expect(onProgress.mock.calls.map(([progress]) => progress.data.phase)).toEqual([
|
||||
'submitted', 'generating', 'saving', 'saved',
|
||||
]);
|
||||
expect(JSON.stringify(result)).not.toContain(projectPath);
|
||||
});
|
||||
|
||||
@@ -151,28 +182,6 @@ describe('GameResourcePluginAdapter', () => {
|
||||
expect(client.generate).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('requires explicit confirmation and never overwrites a project file', async () => {
|
||||
const { adapter, client, context, projectPath } = await fixture();
|
||||
const saveTool = tool('game_resource_save_output', 'game-resource.library', 'save_output');
|
||||
|
||||
await expect(adapter.invoke(context, saveTool, {
|
||||
executionId: EXECUTION_ID, relativePath: 'assets/hero.png', confirmed: false,
|
||||
})).resolves.toMatchObject({ success: false, code: 'confirmation_required' });
|
||||
expect(client.download).not.toHaveBeenCalled();
|
||||
|
||||
await expect(adapter.invoke(context, saveTool, {
|
||||
executionId: EXECUTION_ID, relativePath: 'assets/hero.png', confirmed: true,
|
||||
})).resolves.toMatchObject({
|
||||
success: true, data: { savedPath: 'assets/hero.png', bytes: 3 },
|
||||
});
|
||||
await expect(readFile(path.join(projectPath, 'assets/hero.png'))).resolves.toEqual(Buffer.from([1, 2, 3]));
|
||||
|
||||
await expect(adapter.invoke(context, saveTool, {
|
||||
executionId: EXECUTION_ID, relativePath: 'assets/hero.png', confirmed: true,
|
||||
})).resolves.toMatchObject({ success: false, code: 'game_resource_destination_exists' });
|
||||
await expect(readFile(path.join(projectPath, 'assets/hero.png'))).resolves.toEqual(Buffer.from([1, 2, 3]));
|
||||
});
|
||||
|
||||
it('lists server-owned templates through the same release admission', async () => {
|
||||
const { adapter, context, client } = await fixture();
|
||||
await expect(adapter.invoke(context, tool('game_resource_templates'), { kind: 'pixel' }))
|
||||
|
||||
Reference in New Issue
Block a user