diff --git a/packages/sdk/package.json b/packages/sdk/package.json index 938cecc514c9..f10cbb3dea5c 100644 --- a/packages/sdk/package.json +++ b/packages/sdk/package.json @@ -20,6 +20,7 @@ }, "dependencies": { "@openclaw/gateway-client": "workspace:*", + "@openclaw/gateway-protocol": "workspace:*", "@openclaw/normalization-core": "workspace:*" } } diff --git a/packages/sdk/src/app-sdk-external-boundary.e2e.test.ts b/packages/sdk/src/app-sdk-external-boundary.e2e.test.ts index 32e7417f844b..fee8f5af5489 100644 --- a/packages/sdk/src/app-sdk-external-boundary.e2e.test.ts +++ b/packages/sdk/src/app-sdk-external-boundary.e2e.test.ts @@ -6,6 +6,136 @@ describe("external preview App SDK boundary", () => { it("packs an external consumer that uses only the exported entrypoint and a custom transport", async () => { const consumer = await createPackedSdkConsumer(); try { + await consumer.typecheck(` + import type { + AgentsCreateParams, + AgentsDeleteParams, + AgentsUpdateParams, + ArtifactSummary, + ArtifactsDownloadResult, + EnvironmentSummary, + EnvironmentsListResult, + GatewayArtifactSummary, + SessionCreateParams, + SessionSendParams, + TaskSummary, + TasksCancelResult, + TasksGetResult, + TasksListParams, + TasksListResult, + ToolsEffectiveParams, + ToolInvokeParams, + ToolInvokeResult, + WorkerEnvironmentMetadata, + WorkerEnvironmentState, + WorkerTunnelStatus, + } from "@openclaw/sdk"; + import type { + AgentsCreateParams as ProtocolAgentsCreateParams, + AgentsDeleteParams as ProtocolAgentsDeleteParams, + AgentsUpdateParams as ProtocolAgentsUpdateParams, + ArtifactSummary as ProtocolArtifactSummary, + TaskSummary as ProtocolTaskSummary, + TasksCancelResult as ProtocolTasksCancelResult, + TasksGetResult as ProtocolTasksGetResult, + TasksListParams as ProtocolTasksListParams, + TasksListResult as ProtocolTasksListResult, + ToolsEffectiveParams as ProtocolToolsEffectiveParams, + WorkerEnvironmentMetadata as ProtocolWorkerEnvironmentMetadata, + WorkerEnvironmentState as ProtocolWorkerEnvironmentState, + WorkerTunnelStatus as ProtocolWorkerTunnelStatus, + } from "@openclaw/gateway-protocol"; + + type Equal = + (() => Value extends Left ? 1 : 2) extends + (() => Value extends Right ? 1 : 2) ? true : false; + type Assert = Condition; + + type AgentCreateIsCanonical = Assert>; + type AgentDeleteIsCanonical = Assert>; + type AgentUpdateIsCanonical = Assert>; + type GatewayArtifactIsCanonical = Assert>; + type TaskIsCanonical = Assert>; + type TaskCancelIsCanonical = Assert>; + type TaskGetIsCanonical = Assert>; + type TaskListParamsAreCanonical = Assert>; + type TaskListIsCanonical = Assert>; + type ToolsEffectiveIsCanonical = Assert>; + type WorkerMetadataIsCanonical = Assert>; + type WorkerStateIsCanonical = Assert>; + type TunnelStatusIsCanonical = Assert>; + + const legacyArtifact: ArtifactSummary = { + id: "artifact-old-consumer", + type: "file", + sessionId: "session-old-consumer", + createdAt: "2026-08-16T00:00:00Z", + expiresAt: "2026-08-17T00:00:00Z", + download: { mode: "future-delivery-mode" }, + }; + const artifactDownload: ArtifactsDownloadResult = { + artifact: legacyArtifact, + expiresAt: "2026-08-17T00:00:00Z", + }; + const environment: EnvironmentSummary = { + id: "gateway", + type: "local", + status: "available", + trust: "persistent", + desktop: true, + issues: [{ + code: "update-required", + action: "update-and-reconnect", + updateCommand: "openclaw update", + headlessReconnectCommand: "openclaw node restart", + }], + worker: { + providerId: "worker-provider", + state: "ready", + ageMs: 1, + attachedSessionIds: [], + tunnelStatus: "connected", + desktop: true, + desktopApps: ["browser"], + }, + }; + const environments: EnvironmentsListResult = { environments: [environment] }; + const task: TaskSummary = { + id: "task-canonical", + status: "completed", + toolUseCount: 1, + lastToolName: "read", + deliveryStatus: "delivered", + terminalOutcome: "succeeded", + result: "done", + prompt: "prove the SDK contract", + }; + const createSession: SessionCreateParams = { attachments: [{ kind: "custom" }] }; + const sendSession: SessionSendParams = { + key: "agent:main:external", + message: "hello", + attachments: [{ kind: "custom" }], + }; + const toolParams: ToolInvokeParams = { + sessionKey: "agent:main:external", + args: { query: "status" }, + }; + const toolResult: ToolInvokeResult = { + ok: false, + toolName: "status", + error: { message: "unavailable" }, + }; + + void [ + artifactDownload, + environments, + task, + createSession, + sendSession, + toolParams, + toolResult, + ]; + `); await consumer.run(` import { GatewayClientTransport, OpenClaw, normalizeGatewayEvent } from "@openclaw/sdk"; diff --git a/packages/sdk/src/index.ts b/packages/sdk/src/index.ts index 5932e141e354..1162a533b619 100644 --- a/packages/sdk/src/index.ts +++ b/packages/sdk/src/index.ts @@ -38,6 +38,12 @@ export type { EnvironmentsListResult, NodeWorkerBundleStatus, GatewayEvent, + GatewayArtifactSummary, + GatewayArtifactsDownloadResult, + GatewayArtifactsGetResult, + GatewayArtifactsListResult, + GatewayEnvironmentSummary, + GatewayEnvironmentsListResult, GatewayRequestOptions, JsonObject, OpenClawEvent, @@ -48,7 +54,17 @@ export type { RunStatus, RuntimeSelection, SDKError, + SDKArtifactSummary, + SDKArtifactsDownloadResult, + SDKArtifactsGetResult, + SDKArtifactsListResult, + SDKEnvironmentStatus, + SDKEnvironmentSummary, + SDKEnvironmentsListResult, + SDKEnvironmentTrust, + SDKEnvironmentType, SDKMessage, + SDKWorkerEnvironmentProfileSummary, SessionCreateParams, SessionSendParams, SessionTarget, diff --git a/packages/sdk/src/package.e2e.test-support.ts b/packages/sdk/src/package.e2e.test-support.ts index c45f4a75b5c4..42f54751ceb0 100644 --- a/packages/sdk/src/package.e2e.test-support.ts +++ b/packages/sdk/src/package.e2e.test-support.ts @@ -43,6 +43,7 @@ const SDK_PACKAGE_BUILD_LOCK_OPTIONS = { type PackedSdkConsumer = { root: string; run: (script: string) => Promise; + typecheck: (source: string) => Promise; cleanup: () => Promise; }; @@ -413,6 +414,30 @@ export async function createPackedSdkConsumer(): Promise { cwd: root, }); }, + typecheck: async (source) => { + const sourcePath = path.join(root, "consumer.ts"); + const tsconfigPath = path.join(root, "tsconfig.json"); + await fs.writeFile(sourcePath, source); + await fs.writeFile( + tsconfigPath, + JSON.stringify({ + compilerOptions: { + module: "NodeNext", + moduleResolution: "NodeNext", + noEmit: true, + skipLibCheck: true, + strict: true, + types: [], + }, + include: ["consumer.ts"], + }), + ); + await runCommand( + process.execPath, + [path.join(repoRoot, "scripts", "run-tsgo.mjs"), "-p", tsconfigPath, "--pretty", "false"], + { cwd: repoRoot }, + ); + }, cleanup: () => fs.rm(root, { recursive: true, force: true }), }; } diff --git a/packages/sdk/src/types.ts b/packages/sdk/src/types.ts index 02e6924c1eef..5e4ccdc8aece 100644 --- a/packages/sdk/src/types.ts +++ b/packages/sdk/src/types.ts @@ -1,5 +1,42 @@ // Public SDK data contracts for Gateway transport, runs, sessions, tools, // artifacts, tasks, environments, and normalized event streams. +import type { + ArtifactSummary as GatewayArtifactSummaryType, + ArtifactsDownloadResult as GatewayArtifactsDownloadResultType, + ArtifactsGetResult as GatewayArtifactsGetResultType, + ArtifactsListParams as GatewayArtifactsListParamsType, + ArtifactsListResult as GatewayArtifactsListResultType, + EnvironmentSummary as GatewayEnvironmentSummaryType, + EnvironmentsCreateParams as GatewayEnvironmentsCreateParamsType, + EnvironmentsListResult as GatewayEnvironmentsListResultType, + SessionsCreateParams as GatewaySessionsCreateParamsType, + SessionsSendParams as GatewaySessionsSendParamsType, + TaskSummary as GatewayTaskSummaryType, + ToolsInvokeParams as GatewayToolsInvokeParamsType, + ToolsInvokeResult as GatewayToolsInvokeResultType, +} from "@openclaw/gateway-protocol"; + +export type { + AgentsCreateParams, + AgentsDeleteParams, + AgentsUpdateParams, + ArtifactsDownloadResult as GatewayArtifactsDownloadResult, + ArtifactsGetResult as GatewayArtifactsGetResult, + ArtifactsListResult as GatewayArtifactsListResult, + ArtifactSummary as GatewayArtifactSummary, + EnvironmentsListResult as GatewayEnvironmentsListResult, + EnvironmentSummary as GatewayEnvironmentSummary, + TaskSummary, + TasksCancelResult, + TasksGetResult, + TasksListParams, + TasksListResult, + ToolsEffectiveParams, + WorkerEnvironmentMetadata, + WorkerEnvironmentState, + WorkerTunnelStatus, +} from "@openclaw/gateway-protocol"; + export type JsonObject = Record; /** Per-request options accepted by SDK transports. */ @@ -48,68 +85,61 @@ export type EnvironmentSelection = | { type: "managed"; provider: string; repo?: string; ref?: string } | { type: "ephemeral"; provider: string; repo?: string; ref?: string }; -export type WorkerEnvironmentState = - | "requested" - | "provisioning" - | "bootstrapping" - | "ready" - | "attached" - | "idle" - | "draining" - | "destroying" - | "destroyed" - | "failed" - | "orphaned"; +/** SDK-friendly environment type suggestions over the protocol's open string. */ +export type SDKEnvironmentType = + | "local" + | "gateway" + | "node" + | "managed" + | "ephemeral" + | (string & {}); -export type WorkerTunnelStatus = "stopped" | "connecting" | "connected" | "reconnecting"; +/** Closed SDK projection of the protocol's runtime-validated status string. */ +export type SDKEnvironmentStatus = "available" | "unavailable" | "starting" | "stopping" | "error"; -export type WorkerEnvironmentMetadata = { - providerId: string; - leaseId?: string; - state: WorkerEnvironmentState; - ageMs: number; - idleMs?: number; - attachedSessionIds: string[]; - tunnelStatus: WorkerTunnelStatus; - error?: string; +/** Closed SDK projection of the protocol's runtime-validated trust string. */ +export type SDKEnvironmentTrust = "persistent" | "disposable"; + +export type NodeWorkerBundleStatus = NonNullable; + +export type SDKEnvironmentSummary = Omit< + GatewayEnvironmentSummaryType, + "type" | "status" | "trust" +> & { + type: SDKEnvironmentType; + status: SDKEnvironmentStatus; + trust?: SDKEnvironmentTrust; }; -export type NodeWorkerBundleStatus = - | { status: "installed"; version: string } - | { status: "missing" }; +/** Compatibility name retained for the SDK environment projection. */ +export type EnvironmentSummary = SDKEnvironmentSummary; -export type EnvironmentSummary = { - id: string; - type: "local" | "gateway" | "node" | "managed" | "ephemeral" | (string & {}); - label?: string; - status: "available" | "unavailable" | "starting" | "stopping" | "error"; - platform?: string; - sessionHost?: boolean; - workerBundle?: NodeWorkerBundleStatus; - lastConnectedAtMs?: number; - lastDisconnectedAtMs?: number; - lastSeenAtMs?: number; - lastSeenReason?: string; - trust?: "persistent" | "disposable"; - capabilities?: string[]; - worker?: WorkerEnvironmentMetadata; +export type EnvironmentCreateParams = GatewayEnvironmentsCreateParamsType; + +type GatewayWorkerEnvironmentProfileSummary = NonNullable< + GatewayEnvironmentsListResultType["profiles"] +>[number]; + +export type SDKWorkerEnvironmentProfileSummary = Omit< + GatewayWorkerEnvironmentProfileSummary, + "trust" +> & { + trust?: SDKEnvironmentTrust; }; -export type EnvironmentCreateParams = { - profileId: string; - idempotencyKey: string; +/** Compatibility name retained for the SDK worker profile projection. */ +export type WorkerEnvironmentProfileSummary = SDKWorkerEnvironmentProfileSummary; + +export type SDKEnvironmentsListResult = Omit< + GatewayEnvironmentsListResultType, + "environments" | "profiles" +> & { + environments: SDKEnvironmentSummary[]; + profiles?: SDKWorkerEnvironmentProfileSummary[]; }; -export type WorkerEnvironmentProfileSummary = { - id: string; - providerId: string; - trust?: "persistent" | "disposable"; -}; - -export type EnvironmentsListResult = { - environments: EnvironmentSummary[]; - profiles?: WorkerEnvironmentProfileSummary[]; -}; +/** Compatibility name retained for the SDK environment list projection. */ +export type EnvironmentsListResult = SDKEnvironmentsListResult; export type WorkspaceSelection = { cwd?: string; @@ -126,7 +156,7 @@ export type ApprovalDecisionParams = { /** Terminal and non-terminal status values returned by Run.wait. */ export type RunStatus = "accepted" | "completed" | "failed" | "cancelled" | "timed_out"; -export type RunTimestamp = string | number; +export type RunTimestamp = NonNullable; export type SDKMessage = { role: "system" | "user" | "assistant" | "tool"; @@ -135,108 +165,70 @@ export type SDKMessage = { toolCallId?: string; }; -/** Metadata for an artifact attached to a run, task, or session. */ -export type ArtifactSummary = { - id: string; - runId?: string; - taskId?: string; +/** SDK-friendly artifact type suggestions over the protocol's open string. */ +type SDKArtifactType = + | "file" + | "patch" + | "diff" + | "log" + | "media" + | "screenshot" + | "trajectory" + | "pull_request" + | "workspace" + | (string & {}); + +/** The SDK remains forward-compatible with future artifact delivery modes. */ +type SDKArtifactDownloadMode = GatewayArtifactSummaryType["download"]["mode"] | (string & {}); + +/** + * SDK artifact projection retained for pre-protocol consumers. + * Wire responses satisfy this shape, while legacy sparse summaries remain assignable. + */ +export type SDKArtifactSummary = Omit & { + type: SDKArtifactType; + title?: GatewayArtifactSummaryType["title"]; + download?: { mode: SDKArtifactDownloadMode }; sessionId?: string; - sessionKey?: string; - type: - | "file" - | "patch" - | "diff" - | "log" - | "media" - | "screenshot" - | "trajectory" - | "pull_request" - | "workspace" - | (string & {}); - title?: string; - mimeType?: string; - sizeBytes?: number; - messageSeq?: number; - source?: string; - download?: { - mode: "bytes" | "url" | "unsupported" | (string & {}); - }; createdAt?: string; expiresAt?: string; }; +/** Compatibility name retained for the SDK artifact projection. */ +export type ArtifactSummary = SDKArtifactSummary; + +type ArtifactScopeKey = "sessionKey" | "runId" | "taskId"; +type ScopedArtifactQuery = Omit & + Required>; + +/** SDK query projection requiring at least one artifact ownership scope. */ export type ArtifactQuery = - | { sessionKey: string; runId?: string; taskId?: string; agentId?: string } - | { runId: string; sessionKey?: string; taskId?: string; agentId?: string } - | { taskId: string; sessionKey?: string; runId?: string; agentId?: string }; + | ScopedArtifactQuery<"sessionKey"> + | ScopedArtifactQuery<"runId"> + | ScopedArtifactQuery<"taskId">; -export type ArtifactsListResult = { - artifacts: ArtifactSummary[]; +export type SDKArtifactsListResult = Omit & { + artifacts: SDKArtifactSummary[]; }; -export type ArtifactsGetResult = { - artifact: ArtifactSummary; +/** Compatibility name retained for the SDK artifact list projection. */ +export type ArtifactsListResult = SDKArtifactsListResult; + +export type SDKArtifactsGetResult = Omit & { + artifact: SDKArtifactSummary; }; -export type ArtifactsDownloadResult = { - artifact: ArtifactSummary; - encoding?: "base64"; - data?: string; - url?: string; +/** Compatibility name retained for the SDK artifact get projection. */ +export type ArtifactsGetResult = SDKArtifactsGetResult; + +export type SDKArtifactsDownloadResult = Omit & { + artifact: SDKArtifactSummary; }; -export type TaskStatus = "queued" | "running" | "completed" | "failed" | "cancelled" | "timed_out"; +/** Compatibility name retained for the SDK artifact download projection. */ +export type ArtifactsDownloadResult = SDKArtifactsDownloadResult; -/** Gateway task summary returned by task list/get calls. */ -export type TaskSummary = { - id: string; - taskId?: string; - kind?: string; - runtime?: string; - status: TaskStatus; - title?: string; - agentId?: string; - sessionKey?: string; - childSessionKey?: string; - ownerKey?: string; - runId?: string; - flowId?: string; - parentTaskId?: string; - sourceId?: string; - createdAt?: RunTimestamp; - updatedAt?: RunTimestamp; - startedAt?: RunTimestamp; - endedAt?: RunTimestamp; - progressSummary?: string; - lastActivity?: string; - diffStat?: { files: number; added: number; removed: number }; - terminalSummary?: string; - error?: string; -}; - -export type TasksListParams = { - status?: TaskStatus | TaskStatus[]; - agentId?: string; - sessionKey?: string; - limit?: number; - cursor?: string; -}; - -export type TasksListResult = { - tasks: TaskSummary[]; - nextCursor?: string; -}; - -export type TasksGetResult = { - task: TaskSummary; -}; - -export type TasksCancelResult = { - found: boolean; - cancelled: boolean; - reason?: string; - task?: TaskSummary; -}; +export type TaskStatus = GatewayTaskSummaryType["status"]; export type SDKError = { code?: string; @@ -245,29 +237,18 @@ export type SDKError = { }; /** Parameters for direct tool invocation through the SDK. */ -export type ToolsEffectiveParams = { - sessionKey: string; - agentId?: string; -}; +type SDKToolInvokeParams = Omit; -export type ToolInvokeParams = { - args?: JsonObject; - sessionKey?: string; - agentId?: string; - confirm?: boolean; - idempotencyKey?: string; -}; +/** Compatibility name retained for the SDK tool invocation projection. */ +export type ToolInvokeParams = SDKToolInvokeParams; -export type ToolInvokeResult = { - ok: boolean; - toolName: string; - output?: unknown; - requiresApproval?: boolean; - approvalId?: string; - source?: string; +type SDKToolInvokeResult = Omit & { error?: SDKError; }; +/** Compatibility name retained for the SDK tool result projection. */ +export type ToolInvokeResult = SDKToolInvokeResult; + /** Normalized result returned by Run.wait. */ export type RunResult = { runId: string; @@ -357,33 +338,49 @@ export type AgentRunParams = { idempotencyKey?: string; }; -/** Parameters for creating a session. */ -export type SessionCreateParams = { - key?: string; - agentId?: string; - label?: string; - model?: string; - thinkingLevel?: string; - parentSessionKey?: string; - /** Emit command and lifecycle hooks for parent-linked creation. */ - emitCommandHooks?: boolean; - /** Whether a distinct child terminates its parent; requires command hooks. */ - succeedsParent?: boolean; - task?: string; - message?: string; +type SDKSessionCreateKeys = + | "key" + | "agentId" + | "label" + | "model" + | "thinkingLevel" + | "parentSessionKey" + | "emitCommandHooks" + | "succeedsParent" + | "task" + | "message" + | "attachments"; + +/** SDK session-create projection with transport-neutral attachment inputs. */ +type SDKSessionCreateParams = Omit< + Pick, + "attachments" +> & { attachments?: unknown[]; }; -/** Parameters for sending a message to an existing session. */ -export type SessionSendParams = { - key: string; - message: string; - thinking?: string; +/** Compatibility name retained for the SDK session-create projection. */ +export type SessionCreateParams = SDKSessionCreateParams; + +type SDKSessionSendKeys = + | "key" + | "message" + | "thinking" + | "attachments" + | "timeoutMs" + | "idempotencyKey"; + +/** SDK session-send projection with transport-neutral attachment inputs. */ +type SDKSessionSendParams = Omit< + Pick, + "attachments" +> & { attachments?: unknown[]; - timeoutMs?: number; - idempotencyKey?: string; }; +/** Compatibility name retained for the SDK session-send projection. */ +export type SessionSendParams = SDKSessionSendParams; + export type SessionTarget = { key: string; sessionId?: string; @@ -392,25 +389,3 @@ export type SessionTarget = { }; export type RunCreateParams = AgentRunParams; - -export type AgentsCreateParams = { - name: string; - workspace?: string; - model?: string; - emoji?: string; - avatar?: string; -}; - -export type AgentsUpdateParams = { - agentId: string; - name?: string; - workspace?: string; - model?: string | null; - emoji?: string; - avatar?: string; -}; - -export type AgentsDeleteParams = { - agentId: string; - deleteFiles?: boolean; -}; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 85c0df591459..e526c9e365a4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2301,6 +2301,9 @@ importers: '@openclaw/gateway-client': specifier: workspace:* version: link:../gateway-client + '@openclaw/gateway-protocol': + specifier: workspace:* + version: link:../gateway-protocol '@openclaw/normalization-core': specifier: workspace:* version: link:../normalization-core diff --git a/src/gateway/worker-environments/tunnel-contract.ts b/src/gateway/worker-environments/tunnel-contract.ts index 8fbde0c4bc0d..7f1dbffffb40 100644 --- a/src/gateway/worker-environments/tunnel-contract.ts +++ b/src/gateway/worker-environments/tunnel-contract.ts @@ -1,3 +1,4 @@ +import type { WorkerTunnelStatus } from "@openclaw/gateway-protocol"; import { NODE_WORKER_CAPACITY_EXHAUSTED_ERROR_CODE } from "../../infra/node-commands.js"; import type { SpawnResult } from "../../process/exec.js"; import type { WorkerLaunchPlan } from "../../worker/launch-descriptor.js"; @@ -7,7 +8,7 @@ import type { WorkerWorkspaceReconciliationJournalAdapter, } from "./workspace-reconcile.js"; -export type WorkerTunnelStatus = "stopped" | "connecting" | "connected" | "reconnecting"; +export type { WorkerTunnelStatus }; export class WorkerTunnelOwnerDisconnectedError extends Error { constructor() {