import type { DataServiceCollectionRemoval, DataServiceDocument, DataServiceDocumentList, DataServiceErrorContext, DataServiceHostResult, DataServiceInstanceList, DataServiceInstanceRemoval, DataServiceInstanceState, } from '../../../shared/data-service'; import { DATA_SERVICE_PLUGIN_ID, type CodingPluginToolDefinition, } from '../../../shared/coding-plugins'; import type { DataServiceOperations } from '../../services/data-service-client'; import type { AdapterInvocationResult, CodingPluginAdapter, PluginBackendProjection, TrustedCodingCapabilityContext, } from '../registry'; const MAX_REQUEST_BYTES = 98_304; const CONTEXT_KEYS = new Set([ 'resource', 'limit', 'current', 'attempted', 'actual', 'current_revision', ]); const CONTEXT_RESOURCES = new Set(['instances', 'collections', 'documents', 'bytes']); type InputRecord = Record; function isRecord(value: unknown): value is InputRecord { return value !== null && typeof value === 'object' && !Array.isArray(value); } function boundedContext(value: DataServiceErrorContext | undefined): DataServiceErrorContext | undefined { if (!value) return undefined; const projected: DataServiceErrorContext = {}; for (const [key, item] of Object.entries(value)) { if (!CONTEXT_KEYS.has(key)) continue; if (key === 'resource') { if (typeof item !== 'string' || !CONTEXT_RESOURCES.has(item) || item.length > 32) return undefined; projected[key] = item; } else if (Number.isSafeInteger(item) && (item as number) >= 0) { projected[key] = item as number; } } return Object.keys(projected).length > 0 ? projected : undefined; } function validRetryAfter(value: unknown): value is number { return Number.isSafeInteger(value) && (value as number) >= 0 && (value as number) <= 86_400; } function boundedError(error: unknown, fallback: string): string { return typeof error === 'string' && error.trim().length > 0 && error.length <= 2_000 ? error : fallback; } function failure( code: string, error: string, status = 422, retryable = false, ): AdapterInvocationResult { return { success: false, status, code, error, retryable, payload_schema: 'data-service.v1', data: null, }; } function projectResult(result: DataServiceHostResult): AdapterInvocationResult { if (result.success) { return { success: true, status: result.status, code: null, error: null, retryable: false, payload_schema: 'data-service.v1', data: result.data, }; } const context = boundedContext(result.context); return { success: false, status: result.status, code: result.code ?? 'plugin_backend_unavailable', error: boundedError(result.error, 'Data Service request was rejected'), retryable: result.retryable, ...(validRetryAfter(result.retry_after_seconds) ? { retry_after_seconds: result.retry_after_seconds } : {}), ...(context ? { context } : {}), payload_schema: 'data-service.v1', data: null, }; } function schemaValid(value: unknown, schema: Readonly>): boolean { if (schema.type === 'object') { if (!isRecord(value)) return false; const properties = isRecord(schema.properties) ? schema.properties : {}; if (schema.additionalProperties === false && Object.keys(value).some((key) => !Object.prototype.hasOwnProperty.call(properties, key))) { return false; } const required = Array.isArray(schema.required) ? schema.required : []; if (required.some((key) => typeof key !== 'string' || !Object.prototype.hasOwnProperty.call(value, key))) { return false; } return Object.entries(value).every(([key, item]) => { const child = properties[key]; return !child || (isRecord(child) && schemaValid(item, child)); }); } if (schema.type === 'array') { if (!Array.isArray(value)) return false; if (typeof schema.maxItems === 'number' && value.length > schema.maxItems) return false; const items = isRecord(schema.items) ? schema.items : null; return !items || value.every((item) => schemaValid(item, items)); } if (schema.type === 'string') { if (typeof value !== 'string') return false; if (typeof schema.minLength === 'number' && value.length < schema.minLength) return false; if (typeof schema.maxLength === 'number' && value.length > schema.maxLength) return false; if (typeof schema.pattern === 'string' && !(new RegExp(schema.pattern, 'u')).test(value)) return false; return true; } if (schema.type === 'integer') { if (!Number.isSafeInteger(value)) return false; if (typeof schema.minimum === 'number' && (value as number) < schema.minimum) return false; if (typeof schema.maximum === 'number' && (value as number) > schema.maximum) return false; return true; } if (schema.type === 'boolean') return typeof value === 'boolean' && (schema.const === undefined || value === schema.const); return true; } function boundedDocumentData(value: unknown): boolean { if (!isRecord(value)) return false; try { const encoded = JSON.stringify({ data: value }); return typeof encoded === 'string' && Buffer.byteLength(encoded, 'utf8') <= MAX_REQUEST_BYTES; } catch { return false; } } function validateInput(tool: CodingPluginToolDefinition, input: unknown): boolean { if (!schemaValid(input, tool.inputSchema)) return false; if (!isRecord(input)) return false; if (tool.name === 'data_service_put_document' && !boundedDocumentData(input.data)) return false; return true; } type DataServiceResult = | DataServiceInstanceState | DataServiceInstanceList | DataServiceDocument | DataServiceDocumentList | DataServiceCollectionRemoval | DataServiceInstanceRemoval | null; async function callDataService( operations: DataServiceOperations, toolName: string, input: InputRecord, projectPath: string, ): Promise> { switch (toolName) { case 'data_service_configure': return await operations.configure({ collections: input.collections as string[] }, projectPath); case 'data_service_inspect': return await operations.inspect(projectPath); case 'data_service_list_projects': return await operations.listProjects(); case 'data_service_get_document': return await operations.getDocument({ collection: input.collection as string, document_id: input.document_id as string, }, projectPath); case 'data_service_list_documents': return await operations.listDocuments({ collection: input.collection as string, ...(input.limit === undefined ? {} : { limit: input.limit as number }), ...(input.cursor === undefined ? {} : { cursor: input.cursor as string }), }, projectPath); case 'data_service_put_document': return await operations.putDocument({ collection: input.collection as string, document_id: input.document_id as string, data: input.data as Record, ...(input.if_revision === undefined ? {} : { if_revision: input.if_revision as number }), }, projectPath); case 'data_service_delete_document': return await operations.deleteDocument({ collection: input.collection as string, document_id: input.document_id as string, ...(input.if_revision === undefined ? {} : { if_revision: input.if_revision as number }), confirmed: true, }, projectPath); case 'data_service_remove_collection': return await operations.removeCollection({ collection: input.collection as string, confirmed: true, }, projectPath); case 'data_service_reset': return await operations.reset({ confirmed: true }, projectPath); case 'data_service_remove_project': return await operations.removeProject({ confirmed: true }, projectPath); default: throw new Error('Data Service tool is unavailable'); } } function projection(result: DataServiceHostResult): PluginBackendProjection { if (result.success) return { status: 'ready' }; switch (result.code) { case 'authentication_required': return { status: 'authentication_required' }; case 'project_identity_required': return { status: 'identity_required' }; case 'active_project_required': case 'instance_not_found': return { status: 'unconfigured' }; case 'active_project_path_mismatch': case 'active_project_invalid': return { status: 'degraded', code: result.code, message: 'Data Service project is unavailable', retryable: false }; default: return { status: 'degraded', code: result.code ?? 'plugin_backend_unavailable', message: 'Data Service is temporarily unavailable', retryable: result.retryable, ...(validRetryAfter(result.retry_after_seconds) ? { retry_after_seconds: result.retry_after_seconds } : {}), }; } } export class DataServicePluginAdapter implements CodingPluginAdapter { readonly pluginId = DATA_SERVICE_PLUGIN_ID; constructor(private readonly operations: DataServiceOperations) {} async inspect(projectPath: string): Promise { try { return projection(await this.operations.inspect(projectPath)); } catch { return { status: 'degraded', code: 'plugin_backend_unavailable', message: 'Data Service is temporarily unavailable', retryable: true, }; } } async invoke( _context: TrustedCodingCapabilityContext, tool: CodingPluginToolDefinition, input: unknown, ): Promise> { if (!validateInput(tool, input)) { return failure('plugin_input_invalid', 'Data Service tool input is invalid'); } try { return projectResult(await callDataService( this.operations, tool.name, input as InputRecord, _context.projectPath, )); } catch { return { success: false, status: 503, code: 'plugin_backend_unavailable', error: 'Data Service is temporarily unavailable', retryable: true, payload_schema: 'data-service.v1', data: null, }; } } async deactivate(_projectPath: string): Promise { // Data Service deactivation invalidates preview sessions in the dedicated // lifecycle owner; this adapter owns no independent process or token. } } export const createDataServicePluginAdapter = ( operations: DataServiceOperations, ): DataServicePluginAdapter => new DataServicePluginAdapter(operations);