| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| import type { |
| PaperclipPluginManifestV1, |
| PluginLauncherBounds, |
| PluginLauncherRenderContextSnapshot, |
| PluginLauncherRenderEnvironment, |
| PluginStateScopeKind, |
| Company, |
| Project, |
| Issue, |
| IssueComment, |
| IssueDocument, |
| IssueDocumentSummary, |
| Agent, |
| Goal, |
| } from "@paperclipai/shared"; |
| export type { PluginLauncherRenderContextSnapshot } from "@paperclipai/shared"; |
|
|
| import type { |
| PluginEvent, |
| PluginJobContext, |
| PluginWorkspace, |
| ToolRunContext, |
| ToolResult, |
| } from "./types.js"; |
| import type { |
| PluginHealthDiagnostics, |
| PluginConfigValidationResult, |
| PluginWebhookInput, |
| } from "./define-plugin.js"; |
|
|
| |
| |
| |
|
|
| |
| export const JSONRPC_VERSION = "2.0" as const; |
|
|
| |
| |
| |
| |
| export type JsonRpcId = string | number; |
|
|
| |
| |
| |
| |
| |
| |
| export interface JsonRpcRequest< |
| TMethod extends string = string, |
| TParams = unknown, |
| > { |
| readonly jsonrpc: typeof JSONRPC_VERSION; |
| |
| readonly id: JsonRpcId; |
| |
| readonly method: TMethod; |
| |
| readonly params: TParams; |
| } |
|
|
| |
| |
| |
| export interface JsonRpcSuccessResponse<TResult = unknown> { |
| readonly jsonrpc: typeof JSONRPC_VERSION; |
| |
| readonly id: JsonRpcId; |
| |
| readonly result: TResult; |
| readonly error?: never; |
| } |
|
|
| |
| |
| |
| export interface JsonRpcError<TData = unknown> { |
| |
| readonly code: number; |
| |
| readonly message: string; |
| |
| readonly data?: TData; |
| } |
|
|
| |
| |
| |
| export interface JsonRpcErrorResponse<TData = unknown> { |
| readonly jsonrpc: typeof JSONRPC_VERSION; |
| |
| readonly id: JsonRpcId | null; |
| readonly result?: never; |
| |
| readonly error: JsonRpcError<TData>; |
| } |
|
|
| |
| |
| |
| export type JsonRpcResponse<TResult = unknown, TData = unknown> = |
| | JsonRpcSuccessResponse<TResult> |
| | JsonRpcErrorResponse<TData>; |
|
|
| |
| |
| |
| |
| |
| export interface JsonRpcNotification< |
| TMethod extends string = string, |
| TParams = unknown, |
| > { |
| readonly jsonrpc: typeof JSONRPC_VERSION; |
| readonly id?: never; |
| |
| readonly method: TMethod; |
| |
| readonly params: TParams; |
| } |
|
|
| |
| |
| |
| export type JsonRpcMessage = |
| | JsonRpcRequest |
| | JsonRpcResponse |
| | JsonRpcNotification; |
|
|
| |
| |
| |
|
|
| |
| |
| |
| |
| |
| export const JSONRPC_ERROR_CODES = { |
| |
| PARSE_ERROR: -32700, |
| |
| INVALID_REQUEST: -32600, |
| |
| METHOD_NOT_FOUND: -32601, |
| |
| INVALID_PARAMS: -32602, |
| |
| INTERNAL_ERROR: -32603, |
| } as const; |
|
|
| export type JsonRpcErrorCode = |
| (typeof JSONRPC_ERROR_CODES)[keyof typeof JSONRPC_ERROR_CODES]; |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| export const PLUGIN_RPC_ERROR_CODES = { |
| |
| WORKER_UNAVAILABLE: -32000, |
| |
| CAPABILITY_DENIED: -32001, |
| |
| WORKER_ERROR: -32002, |
| |
| TIMEOUT: -32003, |
| |
| METHOD_NOT_IMPLEMENTED: -32004, |
| |
| UNKNOWN: -32099, |
| } as const; |
|
|
| export type PluginRpcErrorCode = |
| (typeof PLUGIN_RPC_ERROR_CODES)[keyof typeof PLUGIN_RPC_ERROR_CODES]; |
|
|
| |
| |
| |
|
|
| |
| |
| |
| |
| |
| export interface InitializeParams { |
| |
| manifest: PaperclipPluginManifestV1; |
| |
| config: Record<string, unknown>; |
| |
| instanceInfo: { |
| |
| instanceId: string; |
| |
| hostVersion: string; |
| }; |
| |
| apiVersion: number; |
| } |
|
|
| |
| |
| |
| export interface InitializeResult { |
| |
| ok: boolean; |
| |
| supportedMethods?: string[]; |
| } |
|
|
| |
| |
| |
| |
| |
| export interface ConfigChangedParams { |
| |
| config: Record<string, unknown>; |
| } |
|
|
| |
| |
| |
| |
| |
| export interface ValidateConfigParams { |
| |
| config: Record<string, unknown>; |
| } |
|
|
| |
| |
| |
| |
| |
| export interface OnEventParams { |
| |
| event: PluginEvent; |
| } |
|
|
| |
| |
| |
| |
| |
| export interface RunJobParams { |
| |
| job: PluginJobContext; |
| } |
|
|
| |
| |
| |
| |
| |
| export interface GetDataParams { |
| |
| key: string; |
| |
| params: Record<string, unknown>; |
| |
| renderEnvironment?: PluginLauncherRenderContextSnapshot | null; |
| } |
|
|
| |
| |
| |
| |
| |
| export interface PerformActionParams { |
| |
| key: string; |
| |
| params: Record<string, unknown>; |
| |
| renderEnvironment?: PluginLauncherRenderContextSnapshot | null; |
| } |
|
|
| |
| |
| |
| |
| |
| export interface ExecuteToolParams { |
| |
| toolName: string; |
| |
| parameters: unknown; |
| |
| runContext: ToolRunContext; |
| } |
|
|
| |
| |
| |
|
|
| |
| |
| |
| |
| export interface PluginModalBoundsRequest { |
| |
| bounds: PluginLauncherBounds; |
| |
| width?: number; |
| |
| height?: number; |
| |
| minWidth?: number; |
| minHeight?: number; |
| |
| maxWidth?: number; |
| maxHeight?: number; |
| } |
|
|
| |
| |
| |
| export interface PluginRenderCloseEvent { |
| reason: |
| | "escapeKey" |
| | "backdrop" |
| | "hostNavigation" |
| | "programmatic" |
| | "submit" |
| | "unknown"; |
| nativeEvent?: unknown; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| export interface HostToWorkerMethods { |
| |
| initialize: [params: InitializeParams, result: InitializeResult]; |
| |
| health: [params: Record<string, never>, result: PluginHealthDiagnostics]; |
| |
| shutdown: [params: Record<string, never>, result: void]; |
| |
| validateConfig: [params: ValidateConfigParams, result: PluginConfigValidationResult]; |
| |
| configChanged: [params: ConfigChangedParams, result: void]; |
| |
| onEvent: [params: OnEventParams, result: void]; |
| |
| runJob: [params: RunJobParams, result: void]; |
| |
| handleWebhook: [params: PluginWebhookInput, result: void]; |
| |
| getData: [params: GetDataParams, result: unknown]; |
| |
| performAction: [params: PerformActionParams, result: unknown]; |
| |
| executeTool: [params: ExecuteToolParams, result: ToolResult]; |
| } |
|
|
| |
| export type HostToWorkerMethodName = keyof HostToWorkerMethods; |
|
|
| |
| export const HOST_TO_WORKER_REQUIRED_METHODS: readonly HostToWorkerMethodName[] = [ |
| "initialize", |
| "health", |
| "shutdown", |
| ] as const; |
|
|
| |
| export const HOST_TO_WORKER_OPTIONAL_METHODS: readonly HostToWorkerMethodName[] = [ |
| "validateConfig", |
| "configChanged", |
| "onEvent", |
| "runJob", |
| "handleWebhook", |
| "getData", |
| "performAction", |
| "executeTool", |
| ] as const; |
|
|
| |
| |
| |
|
|
| |
| |
| |
| |
| |
| |
| export interface WorkerToHostMethods { |
| |
| "config.get": [params: Record<string, never>, result: Record<string, unknown>]; |
|
|
| |
| "state.get": [ |
| params: { scopeKind: string; scopeId?: string; namespace?: string; stateKey: string }, |
| result: unknown, |
| ]; |
| "state.set": [ |
| params: { scopeKind: string; scopeId?: string; namespace?: string; stateKey: string; value: unknown }, |
| result: void, |
| ]; |
| "state.delete": [ |
| params: { scopeKind: string; scopeId?: string; namespace?: string; stateKey: string }, |
| result: void, |
| ]; |
|
|
| // Entities |
| "entities.upsert": [ |
| params: { |
| entityType: string; |
| scopeKind: PluginStateScopeKind; |
| scopeId?: string; |
| externalId?: string; |
| title?: string; |
| status?: string; |
| data: Record<string, unknown>; |
| }, |
| result: { |
| id: string; |
| entityType: string; |
| scopeKind: PluginStateScopeKind; |
| scopeId: string | null; |
| externalId: string | null; |
| title: string | null; |
| status: string | null; |
| data: Record<string, unknown>; |
| createdAt: string; |
| updatedAt: string; |
| }, |
| ]; |
| "entities.list": [ |
| params: { |
| entityType?: string; |
| scopeKind?: PluginStateScopeKind; |
| scopeId?: string; |
| externalId?: string; |
| limit?: number; |
| offset?: number; |
| }, |
| result: Array<{ |
| id: string; |
| entityType: string; |
| scopeKind: PluginStateScopeKind; |
| scopeId: string | null; |
| externalId: string | null; |
| title: string | null; |
| status: string | null; |
| data: Record<string, unknown>; |
| createdAt: string; |
| updatedAt: string; |
| }>, |
| ]; |
|
|
| |
| "events.emit": [ |
| params: { name: string; companyId: string; payload: unknown }, |
| result: void, |
| ]; |
| "events.subscribe": [ |
| params: { eventPattern: string; filter?: Record<string, unknown> | null }, |
| result: void, |
| ]; |
|
|
| |
| "http.fetch": [ |
| params: { url: string; init?: Record<string, unknown> }, |
| result: { status: number; statusText: string; headers: Record<string, string>; body: string }, |
| ]; |
|
|
| |
| "secrets.resolve": [ |
| params: { secretRef: string }, |
| result: string, |
| ]; |
|
|
| |
| "activity.log": [ |
| params: { |
| companyId: string; |
| message: string; |
| entityType?: string; |
| entityId?: string; |
| metadata?: Record<string, unknown>; |
| }, |
| result: void, |
| ]; |
|
|
| |
| "metrics.write": [ |
| params: { name: string; value: number; tags?: Record<string, string> }, |
| result: void, |
| ]; |
|
|
| |
| "log": [ |
| params: { level: "info" | "warn" | "error" | "debug"; message: string; meta?: Record<string, unknown> }, |
| result: void, |
| ]; |
|
|
| |
| "companies.list": [ |
| params: { limit?: number; offset?: number }, |
| result: Company[], |
| ]; |
| "companies.get": [ |
| params: { companyId: string }, |
| result: Company | null, |
| ]; |
|
|
| |
| "projects.list": [ |
| params: { companyId: string; limit?: number; offset?: number }, |
| result: Project[], |
| ]; |
| "projects.get": [ |
| params: { projectId: string; companyId: string }, |
| result: Project | null, |
| ]; |
| "projects.listWorkspaces": [ |
| params: { projectId: string; companyId: string }, |
| result: PluginWorkspace[], |
| ]; |
| "projects.getPrimaryWorkspace": [ |
| params: { projectId: string; companyId: string }, |
| result: PluginWorkspace | null, |
| ]; |
| "projects.getWorkspaceForIssue": [ |
| params: { issueId: string; companyId: string }, |
| result: PluginWorkspace | null, |
| ]; |
|
|
| |
| "issues.list": [ |
| params: { |
| companyId: string; |
| projectId?: string; |
| assigneeAgentId?: string; |
| status?: string; |
| limit?: number; |
| offset?: number; |
| }, |
| result: Issue[], |
| ]; |
| "issues.get": [ |
| params: { issueId: string; companyId: string }, |
| result: Issue | null, |
| ]; |
| "issues.create": [ |
| params: { |
| companyId: string; |
| projectId?: string; |
| goalId?: string; |
| parentId?: string; |
| title: string; |
| description?: string; |
| priority?: string; |
| assigneeAgentId?: string; |
| }, |
| result: Issue, |
| ]; |
| "issues.update": [ |
| params: { |
| issueId: string; |
| patch: Record<string, unknown>; |
| companyId: string; |
| }, |
| result: Issue, |
| ]; |
| "issues.listComments": [ |
| params: { issueId: string; companyId: string }, |
| result: IssueComment[], |
| ]; |
| "issues.createComment": [ |
| params: { issueId: string; body: string; companyId: string }, |
| result: IssueComment, |
| ]; |
|
|
| |
| "issues.documents.list": [ |
| params: { issueId: string; companyId: string }, |
| result: IssueDocumentSummary[], |
| ]; |
| "issues.documents.get": [ |
| params: { issueId: string; key: string; companyId: string }, |
| result: IssueDocument | null, |
| ]; |
| "issues.documents.upsert": [ |
| params: { |
| issueId: string; |
| key: string; |
| body: string; |
| companyId: string; |
| title?: string; |
| format?: string; |
| changeSummary?: string; |
| }, |
| result: IssueDocument, |
| ]; |
| "issues.documents.delete": [ |
| params: { issueId: string; key: string; companyId: string }, |
| result: void, |
| ]; |
|
|
| |
| "agents.list": [ |
| params: { companyId: string; status?: string; limit?: number; offset?: number }, |
| result: Agent[], |
| ]; |
| "agents.get": [ |
| params: { agentId: string; companyId: string }, |
| result: Agent | null, |
| ]; |
|
|
| |
| "agents.pause": [ |
| params: { agentId: string; companyId: string }, |
| result: Agent, |
| ]; |
| "agents.resume": [ |
| params: { agentId: string; companyId: string }, |
| result: Agent, |
| ]; |
| "agents.invoke": [ |
| params: { agentId: string; companyId: string; prompt: string; reason?: string }, |
| result: { runId: string }, |
| ]; |
|
|
| |
| "agents.sessions.create": [ |
| params: { agentId: string; companyId: string; taskKey?: string; reason?: string }, |
| result: { sessionId: string; agentId: string; companyId: string; status: "active" | "closed"; createdAt: string }, |
| ]; |
| "agents.sessions.list": [ |
| params: { agentId: string; companyId: string }, |
| result: Array<{ sessionId: string; agentId: string; companyId: string; status: "active" | "closed"; createdAt: string }>, |
| ]; |
| "agents.sessions.sendMessage": [ |
| params: { sessionId: string; companyId: string; prompt: string; reason?: string }, |
| result: { runId: string }, |
| ]; |
| "agents.sessions.close": [ |
| params: { sessionId: string; companyId: string }, |
| result: void, |
| ]; |
|
|
| |
| "goals.list": [ |
| params: { companyId: string; level?: string; status?: string; limit?: number; offset?: number }, |
| result: Goal[], |
| ]; |
| "goals.get": [ |
| params: { goalId: string; companyId: string }, |
| result: Goal | null, |
| ]; |
| "goals.create": [ |
| params: { |
| companyId: string; |
| title: string; |
| description?: string; |
| level?: string; |
| status?: string; |
| parentId?: string; |
| ownerAgentId?: string; |
| }, |
| result: Goal, |
| ]; |
| "goals.update": [ |
| params: { |
| goalId: string; |
| patch: Record<string, unknown>; |
| companyId: string; |
| }, |
| result: Goal, |
| ]; |
| } |
|
|
| |
| export type WorkerToHostMethodName = keyof WorkerToHostMethods; |
|
|
| |
| |
| |
|
|
| |
| |
| |
| |
| |
| |
| export interface WorkerToHostNotifications { |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| "streams.emit": { |
| channel: string; |
| companyId: string; |
| event: unknown; |
| }; |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| "streams.open": { |
| channel: string; |
| companyId: string; |
| }; |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| "streams.close": { |
| channel: string; |
| companyId: string; |
| }; |
| } |
|
|
| |
| export type WorkerToHostNotificationName = keyof WorkerToHostNotifications; |
|
|
| |
| |
| |
|
|
| |
| |
| |
| export type HostToWorkerRequest<M extends HostToWorkerMethodName> = |
| JsonRpcRequest<M, HostToWorkerMethods[M][0]>; |
|
|
| |
| |
| |
| export type HostToWorkerResponse<M extends HostToWorkerMethodName> = |
| JsonRpcSuccessResponse<HostToWorkerMethods[M][1]>; |
|
|
| |
| |
| |
| export type WorkerToHostRequest<M extends WorkerToHostMethodName> = |
| JsonRpcRequest<M, WorkerToHostMethods[M][0]>; |
|
|
| |
| |
| |
| export type WorkerToHostResponse<M extends WorkerToHostMethodName> = |
| JsonRpcSuccessResponse<WorkerToHostMethods[M][1]>; |
|
|
| |
| |
| |
|
|
| |
| let _nextId = 1; |
|
|
| |
| const MAX_SAFE_RPC_ID = Number.MAX_SAFE_INTEGER - 1; |
|
|
| |
| |
| |
| |
| |
| |
| |
| export function createRequest<TMethod extends string>( |
| method: TMethod, |
| params: unknown, |
| id?: JsonRpcId, |
| ): JsonRpcRequest<TMethod> { |
| if (_nextId >= MAX_SAFE_RPC_ID) { |
| _nextId = 1; |
| } |
| return { |
| jsonrpc: JSONRPC_VERSION, |
| id: id ?? _nextId++, |
| method, |
| params, |
| }; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| export function createSuccessResponse<TResult>( |
| id: JsonRpcId, |
| result: TResult, |
| ): JsonRpcSuccessResponse<TResult> { |
| return { |
| jsonrpc: JSONRPC_VERSION, |
| id, |
| result, |
| }; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| export function createErrorResponse<TData = unknown>( |
| id: JsonRpcId | null, |
| code: number, |
| message: string, |
| data?: TData, |
| ): JsonRpcErrorResponse<TData> { |
| const response: JsonRpcErrorResponse<TData> = { |
| jsonrpc: JSONRPC_VERSION, |
| id, |
| error: data !== undefined |
| ? { code, message, data } |
| : { code, message } as JsonRpcError<TData>, |
| }; |
| return response; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| export function createNotification<TMethod extends string>( |
| method: TMethod, |
| params: unknown, |
| ): JsonRpcNotification<TMethod> { |
| return { |
| jsonrpc: JSONRPC_VERSION, |
| method, |
| params, |
| }; |
| } |
|
|
| |
| |
| |
|
|
| |
| |
| |
| |
| |
| export function isJsonRpcRequest(value: unknown): value is JsonRpcRequest { |
| if (typeof value !== "object" || value === null) return false; |
| const obj = value as Record<string, unknown>; |
| return ( |
| obj.jsonrpc === JSONRPC_VERSION && |
| typeof obj.method === "string" && |
| "id" in obj && |
| obj.id !== undefined && |
| obj.id !== null |
| ); |
| } |
|
|
| |
| |
| |
| |
| |
| export function isJsonRpcNotification( |
| value: unknown, |
| ): value is JsonRpcNotification { |
| if (typeof value !== "object" || value === null) return false; |
| const obj = value as Record<string, unknown>; |
| return ( |
| obj.jsonrpc === JSONRPC_VERSION && |
| typeof obj.method === "string" && |
| !("id" in obj) |
| ); |
| } |
|
|
| |
| |
| |
| export function isJsonRpcResponse(value: unknown): value is JsonRpcResponse { |
| if (typeof value !== "object" || value === null) return false; |
| const obj = value as Record<string, unknown>; |
| return ( |
| obj.jsonrpc === JSONRPC_VERSION && |
| "id" in obj && |
| ("result" in obj || "error" in obj) |
| ); |
| } |
|
|
| |
| |
| |
| export function isJsonRpcSuccessResponse( |
| response: JsonRpcResponse, |
| ): response is JsonRpcSuccessResponse { |
| return "result" in response && !("error" in response && response.error !== undefined); |
| } |
|
|
| |
| |
| |
| export function isJsonRpcErrorResponse( |
| response: JsonRpcResponse, |
| ): response is JsonRpcErrorResponse { |
| return "error" in response && response.error !== undefined; |
| } |
|
|
| |
| |
| |
|
|
| |
| |
| |
| |
| |
| |
| export const MESSAGE_DELIMITER = "\n" as const; |
|
|
| |
| |
| |
| |
| |
| |
| |
| export function serializeMessage(message: JsonRpcMessage): string { |
| return JSON.stringify(message) + MESSAGE_DELIMITER; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| export function parseMessage(line: string): JsonRpcMessage { |
| const trimmed = line.trim(); |
| if (trimmed.length === 0) { |
| throw new JsonRpcParseError("Empty message"); |
| } |
|
|
| let parsed: unknown; |
| try { |
| parsed = JSON.parse(trimmed); |
| } catch { |
| throw new JsonRpcParseError(`Invalid JSON: ${trimmed.slice(0, 200)}`); |
| } |
|
|
| if (typeof parsed !== "object" || parsed === null) { |
| throw new JsonRpcParseError("Message must be a JSON object"); |
| } |
|
|
| const obj = parsed as Record<string, unknown>; |
|
|
| if (obj.jsonrpc !== JSONRPC_VERSION) { |
| throw new JsonRpcParseError( |
| `Invalid or missing jsonrpc version (expected "${JSONRPC_VERSION}", got ${JSON.stringify(obj.jsonrpc)})`, |
| ); |
| } |
|
|
| |
| |
| return parsed as JsonRpcMessage; |
| } |
|
|
| |
| |
| |
|
|
| |
| |
| |
| export class JsonRpcParseError extends Error { |
| override readonly name = "JsonRpcParseError"; |
| constructor(message: string) { |
| super(message); |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| export class JsonRpcCallError extends Error { |
| override readonly name = "JsonRpcCallError"; |
| |
| readonly code: number; |
| |
| readonly data: unknown; |
|
|
| constructor(error: JsonRpcError) { |
| super(error.message); |
| this.code = error.code; |
| this.data = error.data; |
| } |
| } |
|
|
| |
| |
| |
|
|
| |
| |
| |
| |
| |
| export function _resetIdCounter(): void { |
| _nextId = 1; |
| } |
|
|