refactor(sdk): source wire models from gateway-protocol schemas (#124943)

This commit is contained in:
Peter Steinberger
2026-08-16 17:49:42 -07:00
committed by GitHub
parent d3447dfc2b
commit 9b5950874f
7 changed files with 355 additions and 204 deletions
+1
View File
@@ -20,6 +20,7 @@
},
"dependencies": {
"@openclaw/gateway-client": "workspace:*",
"@openclaw/gateway-protocol": "workspace:*",
"@openclaw/normalization-core": "workspace:*"
}
}
@@ -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<Left, Right> =
(<Value>() => Value extends Left ? 1 : 2) extends
(<Value>() => Value extends Right ? 1 : 2) ? true : false;
type Assert<Condition extends true> = Condition;
type AgentCreateIsCanonical = Assert<Equal<AgentsCreateParams, ProtocolAgentsCreateParams>>;
type AgentDeleteIsCanonical = Assert<Equal<AgentsDeleteParams, ProtocolAgentsDeleteParams>>;
type AgentUpdateIsCanonical = Assert<Equal<AgentsUpdateParams, ProtocolAgentsUpdateParams>>;
type GatewayArtifactIsCanonical = Assert<Equal<GatewayArtifactSummary, ProtocolArtifactSummary>>;
type TaskIsCanonical = Assert<Equal<TaskSummary, ProtocolTaskSummary>>;
type TaskCancelIsCanonical = Assert<Equal<TasksCancelResult, ProtocolTasksCancelResult>>;
type TaskGetIsCanonical = Assert<Equal<TasksGetResult, ProtocolTasksGetResult>>;
type TaskListParamsAreCanonical = Assert<Equal<TasksListParams, ProtocolTasksListParams>>;
type TaskListIsCanonical = Assert<Equal<TasksListResult, ProtocolTasksListResult>>;
type ToolsEffectiveIsCanonical = Assert<Equal<ToolsEffectiveParams, ProtocolToolsEffectiveParams>>;
type WorkerMetadataIsCanonical = Assert<Equal<WorkerEnvironmentMetadata, ProtocolWorkerEnvironmentMetadata>>;
type WorkerStateIsCanonical = Assert<Equal<WorkerEnvironmentState, ProtocolWorkerEnvironmentState>>;
type TunnelStatusIsCanonical = Assert<Equal<WorkerTunnelStatus, ProtocolWorkerTunnelStatus>>;
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";
+16
View File
@@ -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,
@@ -43,6 +43,7 @@ const SDK_PACKAGE_BUILD_LOCK_OPTIONS = {
type PackedSdkConsumer = {
root: string;
run: (script: string) => Promise<void>;
typecheck: (source: string) => Promise<void>;
cleanup: () => Promise<void>;
};
@@ -413,6 +414,30 @@ export async function createPackedSdkConsumer(): Promise<PackedSdkConsumer> {
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 }),
};
}
+178 -203
View File
@@ -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<string, unknown>;
/** 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<GatewayEnvironmentSummaryType["workerBundle"]>;
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<GatewayTaskSummaryType["createdAt"]>;
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<GatewayArtifactSummaryType, "type" | "title" | "download"> & {
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<Key extends ArtifactScopeKey> = Omit<GatewayArtifactsListParamsType, Key> &
Required<Pick<GatewayArtifactsListParamsType, Key>>;
/** 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<GatewayArtifactsListResultType, "artifacts"> & {
artifacts: SDKArtifactSummary[];
};
export type ArtifactsGetResult = {
artifact: ArtifactSummary;
/** Compatibility name retained for the SDK artifact list projection. */
export type ArtifactsListResult = SDKArtifactsListResult;
export type SDKArtifactsGetResult = Omit<GatewayArtifactsGetResultType, "artifact"> & {
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<GatewayArtifactsDownloadResultType, "artifact"> & {
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<GatewayToolsInvokeParamsType, "name" | "conversationReadOrigin">;
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<GatewayToolsInvokeResultType, "error"> & {
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<GatewaySessionsCreateParamsType, SDKSessionCreateKeys>,
"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<GatewaySessionsSendParamsType, SDKSessionSendKeys>,
"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;
};
+3
View File
@@ -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
@@ -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() {