feat(coding): add model tools and device packages
This commit is contained in:
173
electron/coding-packages/device-package-tools.ts
Normal file
173
electron/coding-packages/device-package-tools.ts
Normal file
@@ -0,0 +1,173 @@
|
||||
import type { CodingPluginToolDefinition } from '../../shared/coding-plugins';
|
||||
import type {
|
||||
DevicePackageIndexV1,
|
||||
DevicePackageToolDetailsV1,
|
||||
DevicePackageToolOperation,
|
||||
InstallPreviewV1,
|
||||
} from '../../shared/device-packages';
|
||||
import type { DevicePackageToolName } from '../../shared/device-packages';
|
||||
import type { PiProductToolResult } from '../coding-runtime/pi/product-tools';
|
||||
import type { DevicePackageManager } from './device-package-manager';
|
||||
|
||||
export { DEVICE_PACKAGE_TOOL_NAMES } from '../../shared/device-packages';
|
||||
|
||||
const EMPTY_OBJECT_SCHEMA = Object.freeze({
|
||||
type: 'object', additionalProperties: false, properties: {},
|
||||
});
|
||||
|
||||
function tool(
|
||||
name: DevicePackageToolName,
|
||||
label: string,
|
||||
description: string,
|
||||
operation: DevicePackageToolOperation,
|
||||
mutation: 'read' | 'write' | 'destructive',
|
||||
inputSchema: Readonly<Record<string, unknown>>,
|
||||
): CodingPluginToolDefinition {
|
||||
return Object.freeze({
|
||||
name,
|
||||
label,
|
||||
description,
|
||||
capabilityId: 'makelore.device-packages',
|
||||
operation,
|
||||
roles: ['parent'],
|
||||
mutation,
|
||||
projectWriteLease: false,
|
||||
permissions: ['device-packages'],
|
||||
inputSchema,
|
||||
});
|
||||
}
|
||||
|
||||
export const DEVICE_PACKAGE_TOOL_DEFINITIONS = Object.freeze([
|
||||
tool(
|
||||
'local_package_prepare',
|
||||
'Prepare local package',
|
||||
'Resolve an npm, Git, or absolute local Pi package/Skill and show an exact installation preview. This does not install it.',
|
||||
'prepare',
|
||||
'read',
|
||||
{
|
||||
type: 'object', additionalProperties: false, required: ['source'],
|
||||
properties: { source: { type: 'string', minLength: 1, maxLength: 2048 } },
|
||||
},
|
||||
),
|
||||
tool(
|
||||
'local_package_commit',
|
||||
'Install local package',
|
||||
'Install a prepared device package only after the user confirms in a later message.',
|
||||
'commit',
|
||||
'write',
|
||||
{
|
||||
type: 'object', additionalProperties: false, required: ['planId', 'confirmed'],
|
||||
properties: {
|
||||
planId: { type: 'string', minLength: 1, maxLength: 128 },
|
||||
confirmed: { const: true },
|
||||
},
|
||||
},
|
||||
),
|
||||
tool('local_package_list', 'List local packages', 'List packages installed on this device.', 'list', 'read', EMPTY_OBJECT_SCHEMA),
|
||||
tool(
|
||||
'local_package_set_enabled',
|
||||
'Enable or disable local package',
|
||||
'Enable or disable an installed device package for future parent workers.',
|
||||
'set_enabled',
|
||||
'write',
|
||||
{
|
||||
type: 'object', additionalProperties: false, required: ['packageId', 'enabled'],
|
||||
properties: {
|
||||
packageId: { type: 'string', minLength: 1, maxLength: 128 },
|
||||
enabled: { type: 'boolean' },
|
||||
},
|
||||
},
|
||||
),
|
||||
tool(
|
||||
'local_package_uninstall',
|
||||
'Remove local package',
|
||||
'Remove an installed package from this device after explicit confirmation.',
|
||||
'uninstall',
|
||||
'destructive',
|
||||
{
|
||||
type: 'object', additionalProperties: false, required: ['packageId', 'confirmed'],
|
||||
properties: {
|
||||
packageId: { type: 'string', minLength: 1, maxLength: 128 },
|
||||
confirmed: { const: true },
|
||||
},
|
||||
},
|
||||
),
|
||||
]);
|
||||
|
||||
function record(value: unknown): Record<string, unknown> {
|
||||
if (!value || typeof value !== 'object' || Array.isArray(value)) throw new Error('Local package tool input is invalid');
|
||||
return value as Record<string, unknown>;
|
||||
}
|
||||
|
||||
function exact(input: Record<string, unknown>, keys: readonly string[]): void {
|
||||
const actual = Object.keys(input).sort();
|
||||
const expected = [...keys].sort();
|
||||
if (actual.length !== expected.length || actual.some((key, index) => key !== expected[index])) {
|
||||
throw new Error('Local package tool input has unexpected fields');
|
||||
}
|
||||
}
|
||||
|
||||
function boundedString(value: unknown, name: string, maximum: number): string {
|
||||
if (typeof value !== 'string' || !value.trim() || value.length > maximum) throw new Error(`${name} is invalid`);
|
||||
return value.trim();
|
||||
}
|
||||
|
||||
function result(
|
||||
operation: DevicePackageToolOperation,
|
||||
value: { preview?: InstallPreviewV1; index?: DevicePackageIndexV1 },
|
||||
): PiProductToolResult {
|
||||
const details: DevicePackageToolDetailsV1 = {
|
||||
schema: 'makelore-device-package.v1', operation, success: true, ...value,
|
||||
};
|
||||
return { content: [{ type: 'text', text: JSON.stringify(details) }], details };
|
||||
}
|
||||
|
||||
export class DevicePackageTools {
|
||||
readonly tools = DEVICE_PACKAGE_TOOL_DEFINITIONS;
|
||||
|
||||
constructor(private readonly manager: DevicePackageManager) {}
|
||||
|
||||
async invoke(toolName: string, turnId: string, value: unknown): Promise<PiProductToolResult> {
|
||||
const input = record(value);
|
||||
switch (toolName) {
|
||||
case 'local_package_prepare': {
|
||||
exact(input, ['source']);
|
||||
return result('prepare', {
|
||||
preview: await this.manager.prepare(boundedString(input.source, 'source', 2048), turnId),
|
||||
});
|
||||
}
|
||||
case 'local_package_commit': {
|
||||
exact(input, ['planId', 'confirmed']);
|
||||
return result('commit', {
|
||||
index: await this.manager.commit(
|
||||
boundedString(input.planId, 'planId', 128),
|
||||
input.confirmed as true,
|
||||
turnId,
|
||||
),
|
||||
});
|
||||
}
|
||||
case 'local_package_list':
|
||||
exact(input, []);
|
||||
return result('list', { index: await this.manager.list() });
|
||||
case 'local_package_set_enabled': {
|
||||
exact(input, ['packageId', 'enabled']);
|
||||
if (typeof input.enabled !== 'boolean') throw new Error('enabled is invalid');
|
||||
return result('set_enabled', {
|
||||
index: await this.manager.setEnabled(
|
||||
boundedString(input.packageId, 'packageId', 128),
|
||||
input.enabled,
|
||||
),
|
||||
});
|
||||
}
|
||||
case 'local_package_uninstall': {
|
||||
exact(input, ['packageId', 'confirmed']);
|
||||
if (input.confirmed !== true) throw new Error('Literal confirmation is required');
|
||||
return result('uninstall', {
|
||||
index: await this.manager.uninstall(boundedString(input.packageId, 'packageId', 128)),
|
||||
});
|
||||
}
|
||||
default:
|
||||
throw new Error('Local package tool is unavailable');
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user