@rigkit/sdk 0.1.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,267 @@
1
+ import {
2
+ createDevMachineEngine,
3
+ type DevMachineEngine,
4
+ type EngineOperationSummary,
5
+ type JsonValue,
6
+ } from "@rigkit/engine";
7
+ import { normalizeRuntimeRunError } from "./errors.ts";
8
+ import {
9
+ HostCommandResultSchema,
10
+ objectSchema,
11
+ type JsonSchema,
12
+ type RuntimeOperation,
13
+ type RuntimeOperationsManifest,
14
+ } from "./protocol.ts";
15
+ import {
16
+ completeRun,
17
+ emitRunEvent,
18
+ failRun,
19
+ requestHost,
20
+ requestHostCapability,
21
+ requestHostCapabilitySession,
22
+ type RunRecord,
23
+ type RunStore,
24
+ } from "./runs.ts";
25
+ import { createRuntimeStateService } from "./state.ts";
26
+ import { RIGKIT_RUNTIME_VERSION } from "./version.ts";
27
+
28
+ export type EngineLoadOptions = {
29
+ projectId?: string;
30
+ projectDir: string;
31
+ configPath: string;
32
+ statePath?: string;
33
+ source?: JsonValue;
34
+ };
35
+
36
+ export async function loadEngine(input: EngineLoadOptions): Promise<DevMachineEngine> {
37
+ const engine = await createDevMachineEngine({
38
+ projectDir: input.projectDir,
39
+ configPath: input.configPath,
40
+ state: createRuntimeStateService({
41
+ projectId: input.projectId,
42
+ projectDir: input.projectDir,
43
+ configPath: input.configPath,
44
+ statePath: input.statePath,
45
+ runtimeVersion: RIGKIT_RUNTIME_VERSION,
46
+ source: input.source,
47
+ }),
48
+ });
49
+ await engine.load();
50
+ return engine;
51
+ }
52
+
53
+ export function runOperation(run: RunRecord, store: RunStore, options: EngineLoadOptions): void {
54
+ executeOperation(run, store, options).catch((error) => failRun(run, normalizeRuntimeRunError(error), store));
55
+ }
56
+
57
+ async function executeOperation(run: RunRecord, store: RunStore, options: EngineLoadOptions): Promise<void> {
58
+ const engine = await createDevMachineEngine({
59
+ projectDir: options.projectDir,
60
+ configPath: options.configPath,
61
+ state: createRuntimeStateService({
62
+ projectId: options.projectId,
63
+ projectDir: options.projectDir,
64
+ configPath: options.configPath,
65
+ statePath: options.statePath,
66
+ runtimeVersion: RIGKIT_RUNTIME_VERSION,
67
+ source: options.source,
68
+ }),
69
+ interaction: {
70
+ present: async (request) => {
71
+ await requestHost(store, run, "open.external", {
72
+ target: request.url,
73
+ kind: "url",
74
+ label: request.title,
75
+ });
76
+ },
77
+ },
78
+ local: {
79
+ open: async (target) => {
80
+ await requestHost(store, run, "open.external", { target, kind: guessExternalKind(target) });
81
+ },
82
+ command: async (command) => {
83
+ const result = await requestHost(store, run, "host.command.run", command);
84
+ return HostCommandResultSchema.parse(result);
85
+ },
86
+ requestCapability: async <Result = unknown>(capability: string, params: unknown) =>
87
+ await requestHostCapability(store, run, capability, params) as Result,
88
+ requestCapabilitySession: async <Result = unknown>(capability: string, params: unknown) =>
89
+ await requestHostCapabilitySession<Result>(store, run, capability, params),
90
+ },
91
+ });
92
+ engine.onEvent((event) => emitRunEvent(run, event));
93
+ await engine.load();
94
+
95
+ const result = await engine.runRuntimeOperation({ operation: run.operation, input: run.input });
96
+ completeRun(run, result, store);
97
+ }
98
+
99
+ export function operationsFor(engine: DevMachineEngine): RuntimeOperation[] {
100
+ return engine.listRuntimeOperations().map((operation) => runtimeOperationForEngineOperation(engine, operation));
101
+ }
102
+
103
+ function runtimeOperationForEngineOperation(engine: DevMachineEngine, operation: EngineOperationSummary): RuntimeOperation {
104
+ const required = operation.inputFields
105
+ .filter((field) => field.required ?? true)
106
+ .map((field) => field.name);
107
+ const properties = Object.fromEntries(
108
+ operation.inputFields.map((field) => [field.name, jsonSchemaForField(engine, operation, field)]),
109
+ );
110
+ const hasWorkspaceInput = operation.inputFields.some((field) => field.kind === "workspace");
111
+ return {
112
+ id: operation.id,
113
+ ...(operation.aliases?.length ? { aliases: [...operation.aliases] } : {}),
114
+ kind: operation.kind ?? (hasWorkspaceInput ? "workspace-action" : "command"),
115
+ source: operation.source ?? "config",
116
+ title: operation.title ?? titleize(operation.id),
117
+ description: operation.description ?? "",
118
+ createsWorkspace: operation.createsWorkspace,
119
+ requiredHostMethods: operation.requiredHostMethods?.map((method) => ({
120
+ id: method.id,
121
+ ...(method.modes?.length ? { modes: [...method.modes] } : {}),
122
+ })),
123
+ requiredHostCapabilities: operation.requiredHostCapabilities?.map((capability) => ({
124
+ id: capability.id,
125
+ ...(capability.schemaHash ? { schemaHash: capability.schemaHash } : {}),
126
+ })),
127
+ cli: operation.cli ? cloneOperationCli(operation.cli) : cliForFields(operation.inputFields),
128
+ inputSchema: objectSchema(properties, required),
129
+ };
130
+ }
131
+
132
+ function jsonSchemaForField(
133
+ engine: DevMachineEngine,
134
+ operation: EngineOperationSummary,
135
+ field: EngineOperationSummary["inputFields"][number],
136
+ ): JsonSchema {
137
+ const schema: JsonSchema = {
138
+ type: field.kind === "boolean" || field.kind === "number" ? field.kind : "string",
139
+ ...(field.description ? { description: field.description } : {}),
140
+ ...(field.defaultValue !== undefined ? { default: field.defaultValue } : {}),
141
+ };
142
+ if (field.kind === "string" && (field.required ?? true)) {
143
+ schema.minLength = 1;
144
+ }
145
+ if (field.name === "workflow") {
146
+ const workflows = engine.listWorkflows().map((workflow) => workflow.name);
147
+ Object.assign(schema, workflowJsonSchema(workflows));
148
+ }
149
+ if (field.kind === "workspace") {
150
+ schema["x-rigkit-input"] = {
151
+ kind: "workspace",
152
+ workflow: operation.workflow,
153
+ resolve: "data",
154
+ };
155
+ }
156
+ return schema;
157
+ }
158
+
159
+ function cloneOperationCli(cli: NonNullable<EngineOperationSummary["cli"]>): NonNullable<RuntimeOperation["cli"]> {
160
+ return {
161
+ ...(cli.positionals ? { positionals: cli.positionals.map((item) => ({ ...item })) } : {}),
162
+ ...(cli.options
163
+ ? {
164
+ options: cli.options.map((item) => ({
165
+ ...item,
166
+ ...(item.aliases ? { aliases: [...item.aliases] } : {}),
167
+ })),
168
+ }
169
+ : {}),
170
+ };
171
+ }
172
+
173
+ function cliForFields(fields: EngineOperationSummary["inputFields"]): NonNullable<RuntimeOperation["cli"]> {
174
+ return {
175
+ positionals: fields
176
+ .filter((field) => typeof field.position === "number")
177
+ .map((field) => ({ name: field.name, index: field.position! })),
178
+ options: fields
179
+ .filter((field) => typeof field.position !== "number")
180
+ .map((field) => ({
181
+ name: field.name,
182
+ flag: `--${dashCase(field.name)}`,
183
+ required: field.required ?? true,
184
+ type: field.kind === "boolean" || field.kind === "number" ? field.kind : "string",
185
+ })),
186
+ };
187
+ }
188
+
189
+ export function operationManifestFor(engine: DevMachineEngine): RuntimeOperationsManifest {
190
+ const operations = operationsFor(engine);
191
+ return {
192
+ hostMethods: {
193
+ known: [
194
+ { id: "message.show" },
195
+ { id: "prompt.text" },
196
+ { id: "prompt.confirm" },
197
+ { id: "prompt.select" },
198
+ { id: "open.external" },
199
+ { id: "host.command.run", modes: ["capture", "interactive"] },
200
+ ],
201
+ requiredByOperations: Object.fromEntries(
202
+ operations
203
+ .filter((operation) => operation.requiredHostMethods?.length)
204
+ .map((operation) => [
205
+ operation.id,
206
+ operation.requiredHostMethods!.flatMap((method) =>
207
+ method.modes?.length
208
+ ? method.modes.map((mode) => `${method.id}:${mode}`)
209
+ : [method.id]
210
+ ),
211
+ ]),
212
+ ),
213
+ },
214
+ hostCapabilities: {
215
+ optional: dedupeHostCapabilities(
216
+ operations.flatMap((operation) => operation.requiredHostCapabilities ?? []),
217
+ ),
218
+ requiredByOperations: Object.fromEntries(
219
+ operations
220
+ .filter((operation) => operation.requiredHostCapabilities?.length)
221
+ .map((operation) => [operation.id, operation.requiredHostCapabilities!.map((capability) => capability.id)]),
222
+ ),
223
+ },
224
+ operations,
225
+ };
226
+ }
227
+
228
+ function dedupeHostCapabilities(
229
+ capabilities: Array<{ id: string; schemaHash?: string }>,
230
+ ): Array<{ id: string; schemaHash?: string }> {
231
+ const seen = new Set<string>();
232
+ const deduped: Array<{ id: string; schemaHash?: string }> = [];
233
+ for (const capability of capabilities) {
234
+ const key = capability.schemaHash ? `${capability.id}\0${capability.schemaHash}` : capability.id;
235
+ if (seen.has(key)) continue;
236
+ seen.add(key);
237
+ deduped.push({
238
+ id: capability.id,
239
+ ...(capability.schemaHash ? { schemaHash: capability.schemaHash } : {}),
240
+ });
241
+ }
242
+ return deduped;
243
+ }
244
+
245
+ function workflowJsonSchema(workflows: string[]): JsonSchema {
246
+ return workflows.length > 0
247
+ ? { type: "string", enum: workflows }
248
+ : { type: "string" };
249
+ }
250
+
251
+ function dashCase(value: string): string {
252
+ return value.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`);
253
+ }
254
+
255
+ function titleize(value: string): string {
256
+ return value
257
+ .split(/[-_.\s]+/g)
258
+ .filter(Boolean)
259
+ .map((part) => `${part.slice(0, 1).toUpperCase()}${part.slice(1)}`)
260
+ .join(" ");
261
+ }
262
+
263
+ function guessExternalKind(target: string): "url" | "file" | "unknown" {
264
+ if (/^[a-z][a-z0-9+.-]*:/i.test(target)) return "url";
265
+ if (target.startsWith("/")) return "file";
266
+ return "unknown";
267
+ }
@@ -0,0 +1,193 @@
1
+ import { Schema } from "effect";
2
+ import type { WorkflowEvent } from "@rigkit/engine";
3
+
4
+ export const RUNTIME_API_VERSION = 1;
5
+ export const RUNTIME_PROTOCOL_HASH = "sha256:6dc28b06c95c98d310db445f1b19aef478cd828f11287467fe261483dfde4f81";
6
+ export const DEFAULT_IDLE_MS = 30 * 60 * 1000;
7
+
8
+ export class RuntimeProtocolSchemaError extends Error {
9
+ constructor(readonly cause: unknown) {
10
+ super(String(cause));
11
+ this.name = "RuntimeProtocolSchemaError";
12
+ }
13
+ }
14
+
15
+ type ParseResult<T> =
16
+ | { success: true; data: T }
17
+ | { success: false; error: RuntimeProtocolSchemaError };
18
+
19
+ type RuntimeSchema<T> = {
20
+ parse(value: unknown): T;
21
+ safeParse(value: unknown): ParseResult<T>;
22
+ };
23
+
24
+ function runtimeSchema<T, I>(schema: Schema.Schema<T, I, never>): RuntimeSchema<T> {
25
+ const decode = Schema.decodeUnknownSync(schema);
26
+ return {
27
+ parse(value) {
28
+ try {
29
+ return decode(value);
30
+ } catch (error) {
31
+ throw new RuntimeProtocolSchemaError(error);
32
+ }
33
+ },
34
+ safeParse(value) {
35
+ try {
36
+ return { success: true, data: decode(value) };
37
+ } catch (error) {
38
+ return { success: false, error: new RuntimeProtocolSchemaError(error) };
39
+ }
40
+ },
41
+ };
42
+ }
43
+
44
+ export const RunOperationRequestEffectSchema = Schema.Struct({
45
+ operation: Schema.NonEmptyString,
46
+ input: Schema.optional(Schema.Unknown),
47
+ }).annotations({ identifier: "RunOperationRequest" });
48
+
49
+ export const HostResponseEffectSchema = Schema.Union(
50
+ Schema.Struct({
51
+ error: Schema.Struct({
52
+ code: Schema.optional(Schema.String),
53
+ message: Schema.optional(Schema.String),
54
+ }),
55
+ }),
56
+ Schema.Struct({
57
+ result: Schema.optional(Schema.Unknown),
58
+ }),
59
+ ).annotations({ identifier: "HostResponse" });
60
+
61
+ export const HostCommandRequestEffectSchema = Schema.Struct({
62
+ argv: Schema.Array(Schema.String).pipe(Schema.minItems(1)),
63
+ cwd: Schema.optional(Schema.String),
64
+ env: Schema.optional(Schema.Record({ key: Schema.String, value: Schema.UndefinedOr(Schema.String) })),
65
+ stdin: Schema.optional(Schema.NullOr(Schema.String)),
66
+ mode: Schema.optional(Schema.Literal("capture", "interactive")),
67
+ reason: Schema.optional(Schema.String),
68
+ presentation: Schema.optional(Schema.Struct({
69
+ visible: Schema.optional(Schema.Boolean),
70
+ label: Schema.optional(Schema.String),
71
+ })),
72
+ }).annotations({ identifier: "HostCommandRequest" });
73
+
74
+ export const HostCommandResultEffectSchema = Schema.Struct({
75
+ exitCode: Schema.Int,
76
+ stdout: Schema.NullOr(Schema.String),
77
+ stderr: Schema.NullOr(Schema.String),
78
+ }).annotations({ identifier: "HostCommandResult" });
79
+
80
+ export type RunOperationRequest = Schema.Schema.Type<typeof RunOperationRequestEffectSchema>;
81
+ export type HostResponse = Schema.Schema.Type<typeof HostResponseEffectSchema>;
82
+ export type HostCommandRequest = Schema.Schema.Type<typeof HostCommandRequestEffectSchema>;
83
+ export type HostCommandResult = Schema.Schema.Type<typeof HostCommandResultEffectSchema>;
84
+
85
+ export const RunOperationRequestSchema: RuntimeSchema<RunOperationRequest> = runtimeSchema(RunOperationRequestEffectSchema);
86
+ export const HostResponseSchema: RuntimeSchema<HostResponse> = runtimeSchema(HostResponseEffectSchema);
87
+ export const HostCommandRequestSchema: RuntimeSchema<HostCommandRequest> = runtimeSchema(HostCommandRequestEffectSchema);
88
+ export const HostCommandResultSchema: RuntimeSchema<HostCommandResult> = runtimeSchema(HostCommandResultEffectSchema);
89
+
90
+ export type HostRequestEvent = {
91
+ type: "host.request";
92
+ requestId: string;
93
+ id: string;
94
+ method: string;
95
+ params: unknown;
96
+ };
97
+
98
+ export type HostCapabilityRequestEvent = {
99
+ type: "host.capability.request";
100
+ requestId: string;
101
+ id: string;
102
+ capability: string;
103
+ params: unknown;
104
+ };
105
+
106
+ export type RunCompletedEvent = {
107
+ type: "run.completed";
108
+ runId: string;
109
+ result: unknown;
110
+ };
111
+
112
+ export type RunFailedEvent = {
113
+ type: "run.failed";
114
+ runId: string;
115
+ error: { code?: string; message: string };
116
+ };
117
+
118
+ export type RuntimeEvent =
119
+ | WorkflowEvent
120
+ | HostRequestEvent
121
+ | HostCapabilityRequestEvent
122
+ | RunCompletedEvent
123
+ | RunFailedEvent;
124
+
125
+ export type JsonSchema = Record<string, unknown>;
126
+
127
+ export type RuntimeOperationSource = "core" | "config";
128
+
129
+ export type RuntimeOperationKind = "command" | "workspace-action";
130
+
131
+ export type RuntimeOperationCliPosition = {
132
+ name: string;
133
+ index: number;
134
+ };
135
+
136
+ export type RuntimeOperationCliOption = {
137
+ name: string;
138
+ flag: string;
139
+ aliases?: string[];
140
+ required?: boolean;
141
+ runtime?: boolean;
142
+ type?: "string" | "boolean" | "number";
143
+ };
144
+
145
+ export type RuntimeOperationCli = {
146
+ positionals?: RuntimeOperationCliPosition[];
147
+ options?: RuntimeOperationCliOption[];
148
+ };
149
+
150
+ export type RuntimeHostMethodRequirement = {
151
+ id: string;
152
+ modes?: string[];
153
+ };
154
+
155
+ export type RuntimeHostCapabilityRequirement = {
156
+ id: string;
157
+ schemaHash?: string;
158
+ };
159
+
160
+ export type RuntimeOperation = {
161
+ id: string;
162
+ aliases?: string[];
163
+ kind: RuntimeOperationKind;
164
+ source: RuntimeOperationSource;
165
+ title: string;
166
+ description: string;
167
+ createsWorkspace?: boolean;
168
+ requiredHostMethods?: RuntimeHostMethodRequirement[];
169
+ requiredHostCapabilities?: RuntimeHostCapabilityRequirement[];
170
+ cli?: RuntimeOperationCli;
171
+ inputSchema: JsonSchema;
172
+ };
173
+
174
+ export type RuntimeOperationsManifest = {
175
+ hostMethods: {
176
+ known: RuntimeHostMethodRequirement[];
177
+ requiredByOperations: Record<string, string[]>;
178
+ };
179
+ hostCapabilities: {
180
+ optional: RuntimeHostCapabilityRequirement[];
181
+ requiredByOperations: Record<string, string[]>;
182
+ };
183
+ operations: RuntimeOperation[];
184
+ };
185
+
186
+ export function objectSchema(properties: Record<string, unknown>, required: string[] = []): JsonSchema {
187
+ return {
188
+ type: "object",
189
+ additionalProperties: false,
190
+ ...(required.length > 0 ? { required } : {}),
191
+ properties,
192
+ };
193
+ }