feat(agents): 完成云智能体工作台与交互流程
This commit is contained in:
86
electron/services/cloud-agent-operations.ts
Normal file
86
electron/services/cloud-agent-operations.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
import type { CloudAgentOperations } from '../../shared/cloud-agents';
|
||||
|
||||
type OperationSpec = {
|
||||
method: string; path: string; body?: readonly string[]; output: readonly string[];
|
||||
query?: Record<string, string>; cloud?: 'ws';
|
||||
};
|
||||
const prompt = ['request_id', 'thread_id', 'query', 'expected_revision', 'attachment_file_ids'];
|
||||
const request = ['request_id', 'thread_id', 'run_id', 'status', 'version'];
|
||||
const schedule = ['name', 'prompt', 'cron_expression', 'timezone', 'enabled'];
|
||||
const job = ['id', 'agent_slug', ...schedule, 'next_run_at', 'runs'];
|
||||
const entry = ['slug', 'name', 'purpose', 'published_version', 'is_creator', 'payer'];
|
||||
/** Only these product operations can cross the Main boundary; no arbitrary upstream URL or credentials. */
|
||||
const operations: Record<keyof CloudAgentOperations, OperationSpec> = {
|
||||
knowledge: { method: 'GET', path: '/agents/:slug/knowledge', output: ['databases', 'models'] },
|
||||
createKnowledge: { method: 'POST', path: '/agents/:slug/knowledge', body: ['operation_id', 'name', 'embedding_model'], output: ['kb_id', 'name', 'description', 'embedding_model'] },
|
||||
knowledgeFiles: { method: 'GET', path: '/agents/:slug/knowledge/:kb_id/files', query: { offset: 'offset' }, output: ['files', 'next_offset'] },
|
||||
processKnowledge: { method: 'POST', path: '/agents/:slug/knowledge/:kb_id/files/:file_id/process', body: ['operation_id'], output: ['task_id'] },
|
||||
catalog: { method: 'GET', path: '/catalog', output: ['models', 'resources', 'pricing'] },
|
||||
received: { method: 'GET', path: '/received', output: ['agents'] },
|
||||
entry: { method: 'GET', path: '/entry/:slug', output: entry },
|
||||
publish: { method: 'POST', path: '/agents/:slug/publish', body: ['operation_id', 'expected_revision'], output: ['version', 'draft_revision'] },
|
||||
access: { method: 'GET', path: '/agents/:slug/access', output: ['published_version', 'enabled', 'share_url', 'versions', 'grants', 'applications'] },
|
||||
setEnabled: { method: 'PATCH', path: '/agents/:slug/enabled', body: ['enabled'], output: ['enabled'] },
|
||||
users: { method: 'GET', cloud: 'ws', path: '/users', query: { query: 'query' }, output: ['users'] },
|
||||
share: { method: 'PUT', path: '/agents/:slug/shares/:account_id', body: ['enabled'], output: ['account_id', 'enabled'] },
|
||||
createApplication: { method: 'POST', path: '/agents/:slug/applications', body: ['operation_id', 'name'], output: ['id', 'name', 'enabled'] },
|
||||
setApplicationEnabled: { method: 'PATCH', path: '/applications/:application_id', body: ['enabled'], output: ['enabled'] },
|
||||
keys: { method: 'GET', path: '/applications/:application_id/keys', output: ['keys'] },
|
||||
createKey: { method: 'POST', path: '/applications/:application_id/keys', body: ['operation_id'], output: ['id', 'prefix', 'secret'] },
|
||||
revokeKey: { method: 'DELETE', path: '/applications/:application_id/keys/:key_id', output: ['revoked'] },
|
||||
costs: { method: 'GET', cloud: 'ws', path: '/costs', query: { slug: 'agent_slug' }, output: ['unit', 'items'] },
|
||||
threads: { method: 'GET', path: '/threads', query: { slug: 'slug', offset: 'offset' }, output: ['threads', 'next_offset'] },
|
||||
createThread: { method: 'POST', path: '/agents/:slug/threads', body: ['thread_id', 'preview', 'expected_revision'], output: ['thread_id', 'client_thread_id'] },
|
||||
history: { method: 'GET', path: '/threads/:thread_id', query: { offset: 'offset' }, output: ['thread_id', 'messages', 'run', 'next_offset'] },
|
||||
viewed: { method: 'POST', path: '/threads/:thread_id/viewed', body: ['run_id'], output: ['viewed'] },
|
||||
submit: { method: 'POST', path: '/agents/:slug/requests', body: prompt, output: request },
|
||||
preview: { method: 'POST', path: '/agents/:slug/preview', body: prompt, output: request },
|
||||
request: { method: 'GET', path: '/requests/:request_id', output: request },
|
||||
cancelRequest: { method: 'POST', path: '/requests/:request_id/cancel', output: ['status'] },
|
||||
run: { method: 'GET', path: '/runs/:run_id', output: ['agent_run_id', 'request_id', 'thread_id', 'agent_slug', 'status', 'output', 'version', 'error', 'interrupt'] },
|
||||
cancelRun: { method: 'POST', path: '/runs/:run_id/cancel', output: ['status'] },
|
||||
resume: { method: 'POST', path: '/runs/:run_id/resume', body: ['operation_id', 'decision'], output: ['run_id', 'status'] },
|
||||
schedules: { method: 'GET', path: '/agents/:slug/schedules', output: ['jobs'] },
|
||||
createSchedule: { method: 'POST', path: '/agents/:slug/schedules', body: ['operation_id', ...schedule], output: job },
|
||||
updateSchedule: { method: 'PUT', path: '/agents/:slug/schedules/:job_id', body: schedule, output: job },
|
||||
deleteSchedule: { method: 'DELETE', path: '/agents/:slug/schedules/:job_id', output: ['deleted'] },
|
||||
runSchedule: { method: 'POST', path: '/agents/:slug/schedules/:job_id/run-now', body: ['operation_id'], output: ['thread_id', 'status'] },
|
||||
attachments: { method: 'GET', path: '/threads/:thread_id/attachments', output: ['attachments'] },
|
||||
parseAttachment: { method: 'POST', path: '/attachments/tmp/parse', body: ['object_name', 'parse_method'], output: ['parsed_object_name'] },
|
||||
confirmAttachment: { method: 'POST', path: '/threads/:thread_id/attachments/confirm', body: ['attachments'], output: ['attachments'] },
|
||||
deleteAttachment: { method: 'DELETE', path: '/threads/:thread_id/attachments/:file_id', output: ['message'] },
|
||||
files: { method: 'GET', path: '/threads/:thread_id/files', query: { path: 'path' }, output: ['files'] },
|
||||
};
|
||||
|
||||
export function operationPlan(value: unknown) {
|
||||
if (!value || typeof value !== 'object') throw new Error('invalid_operation');
|
||||
const { operation, input } = value as { operation?: unknown; input?: unknown };
|
||||
if (typeof operation !== 'string' || !Object.hasOwn(operations, operation)
|
||||
|| !input || typeof input !== 'object' || Array.isArray(input)) throw new Error('invalid_operation');
|
||||
const spec = operations[operation as keyof CloudAgentOperations];
|
||||
const args = input as Record<string, unknown>;
|
||||
const path = spec.path.replace(/:([a-z_]+)/g, (_, key: string) => {
|
||||
const id = args[key];
|
||||
if (typeof id !== 'string' || !/^[a-zA-Z0-9_-]{1,128}$/.test(id)
|
||||
|| (key === 'slug' && !/^ml-[a-f0-9]{32}$/.test(id))) throw new Error('invalid_id');
|
||||
return encodeURIComponent(id);
|
||||
});
|
||||
const query = new URLSearchParams();
|
||||
for (const [key, name] of Object.entries(spec.query ?? {})) {
|
||||
const v = args[key];
|
||||
if (v === undefined) continue;
|
||||
if (key === 'offset' ? !Number.isSafeInteger(v) || Number(v) < 0 : typeof v !== 'string' || v.length > (key === 'path' ? 2048 : 100)) {
|
||||
throw new Error('invalid_query');
|
||||
}
|
||||
query.set(name, String(v));
|
||||
}
|
||||
return {
|
||||
...spec, path: (spec.cloud === 'ws' ? '/api/cloud-agents' : '/api/makelore') + path + (query.size ? '?' + query : ''),
|
||||
body: spec.body ? Object.fromEntries(spec.body.filter(key => args[key] !== undefined).map(key => [key, args[key]])) : undefined,
|
||||
project: (result: unknown) => {
|
||||
if (!result || typeof result !== 'object' || Array.isArray(result)) throw new Error('invalid_response');
|
||||
const record = result as Record<string, unknown>;
|
||||
return Object.fromEntries(spec.output.filter(key => record[key] !== undefined).map(key => [key, record[key]]));
|
||||
},
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user