import { readFile, stat } from 'node:fs/promises'; import path from 'node:path'; import type { CodingPluginToolDefinition } from '../../../shared/coding-plugins'; import type { MarketplaceHostedAdmissionResolver } from '../hosted-admission'; import { MarketplaceHostedAdmissionError } from '../hosted-admission'; import type { AdapterInvocationResult, CodingPluginAdapter, TrustedCodingCapabilityContext } from '../registry'; import { audioRecord, GameAudioClientError, type AudioCommand, type GameAudioClient } from '../../services/game-audio-client'; import type { AudioDeliveryResult, GameAudioDeliveryCoordinator } from '../../services/game-audio-delivery'; import { getWorksSquareAccountBinding } from '../../services/works-square-session'; interface Options { client: Pick; delivery: Pick; admission: Pick; getPricingVersion: () => string | null; getAccountKey?: () => string | null; } function invalid(): never { throw new GameAudioClientError('plugin_input_invalid', 422, 'Audio generation input is invalid'); } function result(value: AudioDeliveryResult, projectId: string): AdapterInvocationResult { const { billing, executionId, progressPercent, ...remaining } = value; const data = { ...remaining, projectId, ...(executionId === null ? {} : { executionId }), ...(progressPercent === null ? {} : { progressPercent }) }; if (value.providerStatus === 'failed') return { success: false, status: 503, code: value.errorCode ?? 'game_audio_generation_failed', error: 'Audio generation failed; inspect the billing receipt before a new attempt', retryable: false, payload_schema: 'game-audio.v1', data: null, billing, }; return { success: true, status: value.deliveryStatus === 'saved' ? 200 : 202, code: null, error: null, retryable: false, payload_schema: 'game-audio.v1', data, billing }; } async function references(project: string, value: unknown) { if (value === undefined) return []; if (!Array.isArray(value) || value.length > 4) invalid(); const items: { name: string; mime_type: string; data_base64: string }[] = []; let total = 0; for (const candidate of value) { if (typeof candidate !== 'string' || !candidate || candidate.length > 1024) invalid(); const absolute = path.resolve(project, candidate); const relative = path.relative(path.resolve(project), absolute); if (!relative || relative === '..' || relative.startsWith('..' + path.sep) || path.isAbsolute(relative)) invalid(); const mime = ({ '.png': 'image/png', '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg', '.webp': 'image/webp' } as Record)[path.extname(absolute).toLowerCase()]; const metadata = await stat(absolute); if (!mime || !metadata.isFile() || metadata.size <= 0 || metadata.size > 8 * 1024 * 1024) invalid(); const content = await readFile(absolute); total += content.byteLength; if (!content.length || content.length > 8 * 1024 * 1024 || total > 16 * 1024 * 1024) invalid(); items.push({ name: path.basename(absolute).slice(0, 160), mime_type: mime, data_base64: content.toString('base64') }); } return items; } export class GameAudioPluginAdapter implements CodingPluginAdapter { readonly pluginId = 'makelore.game-audio'; constructor(private readonly options: Options) {} async inspect() { return { status: 'ready' as const }; } async invoke(context: TrustedCodingCapabilityContext, tool: CodingPluginToolDefinition, input: unknown, onProgress?: (value: AdapterInvocationResult) => void): Promise { let submitted = false; try { if (!audioRecord(input)) invalid(); const account = this.options.getAccountKey ? this.options.getAccountKey() : getWorksSquareAccountBinding()?.accountKey; if (!account) throw new GameAudioClientError('authentication_required', 401, 'Sign-in is required'); if (tool.name === 'game_audio_cancel') { if (typeof input.executionId !== 'string' || Object.keys(input).some((key) => key !== 'executionId')) invalid(); const generation = await this.options.client.cancel(input.executionId, account); return { success: true, status: 200, code: null, error: null, retryable: false, payload_schema: 'game-audio.v1', data: { executionId: generation.execution_id, providerStatus: generation.provider_status, outputCount: generation.outputs.length, generationBilling: Object.fromEntries(Object.entries(generation.billing).filter(([, value]) => value !== null)) } }; } if (input.confirmed !== true) throw new GameAudioClientError('confirmation_required', 400, 'Confirm Token Point generation and automatic project save first'); const sound = tool.name === 'game_sound_generate'; if (!sound && tool.name !== 'game_music_generate' && tool.name !== 'game_music_preview') invalid(); const allowed = sound ? ['prompt', 'confirmed', 'mode', 'duration', 'count', 'loop'] : ['prompt', 'confirmed', 'referencePaths']; if (Object.keys(input).some((key) => !allowed.includes(key)) || typeof input.prompt !== 'string' || !input.prompt.trim() || input.prompt.trim().length > 4000) invalid(); const pricing = this.options.getPricingVersion(); if (!pricing) throw new GameAudioClientError('plugin_billing_unavailable', 503, 'Current audio pricing is unavailable'); let specification: Pick, 'kind' | 'mode' | 'duration' | 'count' | 'loop'> | Pick, 'kind' | 'mode' | 'reference_files'>; if (sound) { const mode = input.mode ?? 'single'; const duration = input.duration ?? 2; const count = input.count ?? (mode === 'single' ? 1 : 4); const loop = input.loop ?? false; if (!['single', 'pack', 'variants'].includes(String(mode)) || typeof mode !== 'string' || typeof duration !== 'number' || !(duration === 0.5 || Number.isInteger(duration) && duration >= 1 && duration <= 10) || typeof count !== 'number' || !Number.isInteger(count) || count < 1 || count > 10 || (mode === 'single' && count !== 1) || typeof loop !== 'boolean') invalid(); specification = { kind: 'sound', mode: mode as 'single' | 'pack' | 'variants', duration, count, loop }; } else { specification = { kind: 'music', mode: tool.name === 'game_music_preview' ? 'demo' : 'full', reference_files: await references(context.projectPath, input.referencePaths) }; } const admission = await this.options.admission.resolve({ pluginId: this.pluginId, workerSnapshot: { requestId: context.requestId, pluginReleaseId: context.pluginReleaseId } }); submitted = true; const delivered = await this.options.delivery.generateAndMaterialize({ accountKey: account, conversationId: context.conversationId, runId: context.runId, localProjectId: context.localProjectId, durableProjectId: context.durableProjectId, projectPath: context.projectPath, logicalOperationId: context.requestId, }, { ...specification, release_id: admission.releaseId, release_admission_id: admission.releaseAdmissionId, project_id: context.durableProjectId, logical_operation_id: context.requestId, pricing_version_id: pricing, prompt: input.prompt.trim(), confirmed: true }, (progress) => onProgress?.(result(progress, context.localProjectId))); return result(delivered, context.localProjectId); } catch (error) { const known = error instanceof GameAudioClientError || error instanceof MarketplaceHostedAdmissionError; return { success: false, status: known ? error.status : 503, code: known ? error.code : 'plugin_backend_unavailable', error: known ? error.message : submitted ? 'Audio receipt is unavailable; recover the original operation instead of generating again' : 'Audio service is unavailable; no new generation was started', retryable: false, payload_schema: 'game-audio.v1', data: null, billing: { mode: 'platform_metered', status: submitted ? 'receipt_unavailable' : 'not_started' } }; } } }