@ryuhq/sdk 0.0.5

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.
Files changed (61) hide show
  1. package/LICENSE +179 -0
  2. package/README.md +31 -0
  3. package/dist/agent.cjs +761 -0
  4. package/dist/agent.d.cts +3 -0
  5. package/dist/agent.d.ts +3 -0
  6. package/dist/agent.js +23 -0
  7. package/dist/chunk-GXHL5CO7.js +353 -0
  8. package/dist/chunk-KPKMMGVC.js +671 -0
  9. package/dist/chunk-ODFEUVPW.js +100 -0
  10. package/dist/cli.cjs +858 -0
  11. package/dist/cli.d.cts +1 -0
  12. package/dist/cli.d.ts +1 -0
  13. package/dist/cli.js +454 -0
  14. package/dist/index-CEbS1SlS.d.cts +988 -0
  15. package/dist/index-DAxq7Y0R.d.ts +988 -0
  16. package/dist/index.cjs +1900 -0
  17. package/dist/index.d.cts +759 -0
  18. package/dist/index.d.ts +759 -0
  19. package/dist/index.js +771 -0
  20. package/dist/manifest.cjs +399 -0
  21. package/dist/manifest.d.cts +355 -0
  22. package/dist/manifest.d.ts +355 -0
  23. package/dist/manifest.js +38 -0
  24. package/package.json +56 -0
  25. package/src/agent/agent.ts +208 -0
  26. package/src/agent/index.ts +51 -0
  27. package/src/agent/loop.test.ts +261 -0
  28. package/src/agent/loop.ts +259 -0
  29. package/src/agent/model-call.ts +190 -0
  30. package/src/agent/query.ts +40 -0
  31. package/src/agent/tools.ts +295 -0
  32. package/src/builder.ts +473 -0
  33. package/src/cli/dev.test.ts +178 -0
  34. package/src/cli/dev.ts +425 -0
  35. package/src/cli.ts +390 -0
  36. package/src/contracts-lockstep.test.ts +77 -0
  37. package/src/generated/plugin-manifest.ts +1121 -0
  38. package/src/index.ts +141 -0
  39. package/src/manifest.test.ts +610 -0
  40. package/src/manifest.ts +589 -0
  41. package/src/mcp/bridge.test.ts +196 -0
  42. package/src/mcp/client.ts +253 -0
  43. package/src/mcp/fixture-server.ts +23 -0
  44. package/src/mcp/server.ts +351 -0
  45. package/src/model/client.test.ts +107 -0
  46. package/src/model/client.ts +179 -0
  47. package/src/model/gateway.ts +41 -0
  48. package/src/plugin/ryu-plugin.ts +191 -0
  49. package/src/runnable/agent.ts +338 -0
  50. package/src/runnable/app.ts +233 -0
  51. package/src/runnable/index.ts +61 -0
  52. package/src/runnable/primitives-hostapi.test.ts +73 -0
  53. package/src/runnable/primitives.test.ts +286 -0
  54. package/src/runnable/primitives.ts +610 -0
  55. package/src/runnable/runnable-types.ts +113 -0
  56. package/src/runnable/runnable.test.ts +397 -0
  57. package/src/runnable/skill.ts +60 -0
  58. package/src/runnable/tool.ts +260 -0
  59. package/src/runnable/turn-hook.test.ts +81 -0
  60. package/src/runnable/turn-hook.ts +191 -0
  61. package/src/runnable/workflow.ts +76 -0
package/dist/cli.d.cts ADDED
@@ -0,0 +1 @@
1
+ #!/usr/bin/env bun
package/dist/cli.d.ts ADDED
@@ -0,0 +1 @@
1
+ #!/usr/bin/env bun
package/dist/cli.js ADDED
@@ -0,0 +1,454 @@
1
+ #!/usr/bin/env bun
2
+ import {
3
+ PluginManifestSchema
4
+ } from "./chunk-GXHL5CO7.js";
5
+ import {
6
+ ModelClient,
7
+ resolveGatewayUrl
8
+ } from "./chunk-ODFEUVPW.js";
9
+
10
+ // src/cli.ts
11
+ import { createHash } from "crypto";
12
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
13
+ import { join, resolve } from "path";
14
+
15
+ // src/cli/dev.ts
16
+ import { createInterface } from "readline";
17
+ var RE_ABSOLUTE_PATH = /^[A-Za-z]:[\\/]/;
18
+ async function probeGateway(baseUrl) {
19
+ try {
20
+ await fetch(`${baseUrl}/health`, {
21
+ method: "HEAD",
22
+ signal: AbortSignal.timeout(3e3)
23
+ });
24
+ return true;
25
+ } catch {
26
+ return false;
27
+ }
28
+ }
29
+ async function loadRunnable(entryPath) {
30
+ const abs = entryPath.startsWith("/") || RE_ABSOLUTE_PATH.test(entryPath) ? entryPath : `${process.cwd()}/${entryPath}`;
31
+ const mod = await import(abs);
32
+ const candidate = mod.default ?? mod.runnable;
33
+ if (!candidate || typeof candidate.run !== "function") {
34
+ throw new Error(
35
+ `[ryu dev] Module at "${entryPath}" must export a Runnable as "default" or "runnable". A Runnable has a "run(messages, model)" generator method.`
36
+ );
37
+ }
38
+ return candidate;
39
+ }
40
+ var ANSI = {
41
+ reset: "\x1B[0m",
42
+ bold: "\x1B[1m",
43
+ cyan: "\x1B[36m",
44
+ yellow: "\x1B[33m",
45
+ green: "\x1B[32m",
46
+ red: "\x1B[31m",
47
+ dim: "\x1B[2m"
48
+ };
49
+ function printBanner(runnableName, gatewayUrl) {
50
+ process.stdout.write(
51
+ [
52
+ "",
53
+ `${ANSI.bold}${ANSI.cyan}ryu dev${ANSI.reset} \u2014 local Runnable playground`,
54
+ `${ANSI.dim}runnable : ${runnableName}${ANSI.reset}`,
55
+ `${ANSI.dim}gateway : ${gatewayUrl}${ANSI.reset}`,
56
+ `${ANSI.dim}type "/quit" or Ctrl+C to exit${ANSI.reset}`,
57
+ ""
58
+ ].join("\n")
59
+ );
60
+ }
61
+ function printPrompt() {
62
+ process.stdout.write(`${ANSI.bold}> ${ANSI.reset}`);
63
+ }
64
+ function printAssistantStart() {
65
+ process.stdout.write(`
66
+ ${ANSI.green}assistant:${ANSI.reset} `);
67
+ }
68
+ function printAssistantEnd() {
69
+ process.stdout.write("\n");
70
+ }
71
+ function printToolCall(event) {
72
+ process.stdout.write(
73
+ `
74
+ ${ANSI.yellow}tool-call${ANSI.reset} [${event.id}] ${event.title} (${event.kind})`
75
+ );
76
+ if (event.input !== null && event.input !== void 0) {
77
+ process.stdout.write(
78
+ ` ${ANSI.dim}${JSON.stringify(event.input)}${ANSI.reset}`
79
+ );
80
+ }
81
+ process.stdout.write("\n");
82
+ }
83
+ function statusAnsiColor(status) {
84
+ if (status === "completed") {
85
+ return ANSI.green;
86
+ }
87
+ if (status === "failed") {
88
+ return ANSI.red;
89
+ }
90
+ return ANSI.dim;
91
+ }
92
+ function printToolResult(event) {
93
+ const statusColor = statusAnsiColor(event.status);
94
+ process.stdout.write(
95
+ `${ANSI.dim}tool-result${ANSI.reset} [${event.id}] ${statusColor}${event.status}${ANSI.reset}`
96
+ );
97
+ if (event.output !== null && event.output !== void 0) {
98
+ process.stdout.write(
99
+ ` ${ANSI.dim}${JSON.stringify(event.output)}${ANSI.reset}`
100
+ );
101
+ }
102
+ process.stdout.write("\n");
103
+ }
104
+ function printError(message) {
105
+ process.stderr.write(`
106
+ ${ANSI.red}error:${ANSI.reset} ${message}
107
+ `);
108
+ }
109
+ async function runDevLoop(runnable, model) {
110
+ const history = [];
111
+ const rl = createInterface({
112
+ input: process.stdin,
113
+ output: process.stdout,
114
+ terminal: false
115
+ });
116
+ const linePromise = () => new Promise((resolve2) => {
117
+ rl.once("line", resolve2);
118
+ rl.once("close", () => resolve2(null));
119
+ });
120
+ printPrompt();
121
+ while (true) {
122
+ const line = await linePromise();
123
+ if (line === null) {
124
+ process.stdout.write("\n");
125
+ break;
126
+ }
127
+ const trimmed = line.trim();
128
+ if (trimmed === "") {
129
+ printPrompt();
130
+ continue;
131
+ }
132
+ if (trimmed === "/quit" || trimmed === "/exit") {
133
+ process.stdout.write("bye\n");
134
+ break;
135
+ }
136
+ history.push({ role: "user", content: trimmed });
137
+ let assistantReply = "";
138
+ printAssistantStart();
139
+ const gen = runnable.run([...history], model);
140
+ for await (const event of gen) {
141
+ switch (event.type) {
142
+ case "text": {
143
+ process.stdout.write(event.content);
144
+ assistantReply += event.content;
145
+ break;
146
+ }
147
+ case "tool_call": {
148
+ printToolCall(event);
149
+ break;
150
+ }
151
+ case "tool_result": {
152
+ printToolResult(event);
153
+ break;
154
+ }
155
+ case "error": {
156
+ printAssistantEnd();
157
+ printError(event.message);
158
+ history.pop();
159
+ assistantReply = "";
160
+ break;
161
+ }
162
+ default:
163
+ break;
164
+ }
165
+ }
166
+ printAssistantEnd();
167
+ if (assistantReply) {
168
+ history.push({ role: "assistant", content: assistantReply });
169
+ }
170
+ printPrompt();
171
+ }
172
+ rl.close();
173
+ }
174
+ async function commandDev(entryPath) {
175
+ const gatewayUrl = resolveGatewayUrl();
176
+ process.stdout.write(`checking gateway at ${gatewayUrl} ...
177
+ `);
178
+ const reachable = await probeGateway(gatewayUrl);
179
+ if (!reachable) {
180
+ process.stderr.write(
181
+ [
182
+ "",
183
+ `${ANSI.red}error:${ANSI.reset} gateway not reachable at ${gatewayUrl}`,
184
+ "",
185
+ "The ryu dev playground requires a running Ryu gateway.",
186
+ "Start the gateway with: ryu gateway start",
187
+ "Or set RYU_GATEWAY_URL to point at a remote gateway.",
188
+ "",
189
+ "No provider fallback is attempted. Fix the gateway connection and retry.",
190
+ ""
191
+ ].join("\n")
192
+ );
193
+ process.exit(1);
194
+ }
195
+ let runnable;
196
+ try {
197
+ runnable = await loadRunnable(entryPath);
198
+ } catch (err) {
199
+ process.stderr.write(`error: ${String(err)}
200
+ `);
201
+ process.exit(1);
202
+ }
203
+ const model = new ModelClient("default", { baseUrl: gatewayUrl });
204
+ printBanner(runnable.name, gatewayUrl);
205
+ await runDevLoop(runnable, model);
206
+ }
207
+
208
+ // src/cli.ts
209
+ function uiCodeSha256(code) {
210
+ return createHash("sha256").update(code, "utf8").digest("hex");
211
+ }
212
+ function printUsage() {
213
+ process.stderr.write(
214
+ [
215
+ "Ryu dev SDK",
216
+ "",
217
+ "Usage:",
218
+ " bunx ryu pack <dir> Validate and bundle a plugin.json Plugin",
219
+ " bunx ryu publish <dir> Validate and publish a plugin.json Plugin to the Ryu Marketplace",
220
+ " bunx ryu dev <entry> Run a Runnable locally with an interactive chat loop",
221
+ ""
222
+ ].join("\n")
223
+ );
224
+ }
225
+ function exitError(message) {
226
+ process.stderr.write(`error: ${message}
227
+ `);
228
+ process.exit(1);
229
+ }
230
+ function loadManifest(dir) {
231
+ const manifestPath = join(dir, "plugin.json");
232
+ if (!existsSync(manifestPath)) {
233
+ exitError(`plugin.json not found in: ${dir}`);
234
+ }
235
+ let raw;
236
+ try {
237
+ raw = readFileSync(manifestPath, "utf8");
238
+ } catch (err) {
239
+ exitError(`could not read ${manifestPath}: ${String(err)}`);
240
+ }
241
+ let parsed;
242
+ try {
243
+ parsed = JSON.parse(raw);
244
+ } catch {
245
+ exitError(`plugin.json is not valid JSON: ${manifestPath}`);
246
+ }
247
+ const result = PluginManifestSchema.safeParse(parsed);
248
+ if (!result.success) {
249
+ const first = result.error.issues[0];
250
+ const field = first?.path.join(".") ?? "unknown";
251
+ const message = first?.message ?? "validation failed";
252
+ exitError(`plugin.json validation failed at '${field}': ${message}`);
253
+ }
254
+ return result.data;
255
+ }
256
+ function resolveUiEntry(manifest) {
257
+ for (const runnable of manifest.runnables) {
258
+ if (runnable.kind !== "companion") {
259
+ continue;
260
+ }
261
+ const entry = runnable.config?.ui_entry;
262
+ if (typeof entry === "string" && entry.trim().length > 0) {
263
+ return entry;
264
+ }
265
+ }
266
+ for (const widget of manifest.contributes?.widgets ?? []) {
267
+ const entry = widget.ui_entry;
268
+ if (typeof entry === "string" && entry.trim().length > 0) {
269
+ return entry;
270
+ }
271
+ }
272
+ return null;
273
+ }
274
+ function resolveUiFormat(manifest) {
275
+ for (const runnable of manifest.runnables) {
276
+ if (runnable.kind !== "companion") {
277
+ continue;
278
+ }
279
+ const fmt = runnable.config?.ui_format;
280
+ if (typeof fmt === "string" && fmt.trim().toLowerCase() === "html") {
281
+ return "html";
282
+ }
283
+ }
284
+ return "js";
285
+ }
286
+ function readUiEntryHtml(dir, uiEntry) {
287
+ const entryPath = resolve(dir, uiEntry);
288
+ if (!existsSync(entryPath)) {
289
+ exitError(`companion ui_entry (html) not found: ${entryPath}`);
290
+ }
291
+ return readFileSync(entryPath, "utf8");
292
+ }
293
+ async function bundleUiEntry(dir, uiEntry) {
294
+ const entryPath = resolve(dir, uiEntry);
295
+ if (!existsSync(entryPath)) {
296
+ exitError(`companion ui_entry not found: ${entryPath}`);
297
+ }
298
+ const result = await Bun.build({
299
+ entrypoints: [entryPath],
300
+ target: "browser",
301
+ format: "esm",
302
+ minify: false
303
+ });
304
+ if (!result.success) {
305
+ const messages = result.logs.map((l) => String(l.message)).join("; ");
306
+ exitError(`failed to bundle ui_entry '${uiEntry}': ${messages}`);
307
+ }
308
+ const output = result.outputs[0];
309
+ if (!output) {
310
+ exitError(`bundling ui_entry '${uiEntry}' produced no output`);
311
+ }
312
+ return await output.text();
313
+ }
314
+ async function commandPack(rawDir) {
315
+ const dir = resolve(rawDir);
316
+ const manifest = loadManifest(dir);
317
+ const uiEntry = resolveUiEntry(manifest);
318
+ const uiCode = uiEntry ? resolveUiFormat(manifest) === "html" ? readUiEntryHtml(dir, uiEntry) : await bundleUiEntry(dir, uiEntry) : null;
319
+ const manifestWithHash = uiCode ? { ...manifest, ui_code_sha256: uiCodeSha256(uiCode) } : manifest;
320
+ const outDir = join(dir, "dist");
321
+ if (!existsSync(outDir)) {
322
+ mkdirSync(outDir, { recursive: true });
323
+ }
324
+ const outPath = join(outDir, "plugin.bundle.json");
325
+ const bundle = uiCode ? { ...manifestWithHash, ui_code: uiCode } : manifestWithHash;
326
+ writeFileSync(outPath, JSON.stringify(bundle, null, 2), "utf8");
327
+ const codeNote = uiCode ? ` (+${uiCode.length}B ui_code)` : "";
328
+ process.stdout.write(
329
+ `packed ${manifest.id}@${manifest.version}${codeNote} \u2192 ${outPath}
330
+ `
331
+ );
332
+ }
333
+ var TRAILING_SLASHES = /\/+$/;
334
+ function publishBaseUrl() {
335
+ const raw = (process.env.RYU_MARKETPLACE_API_URL ?? "").trim();
336
+ return (raw || "http://localhost:3000").replace(TRAILING_SLASHES, "");
337
+ }
338
+ function authToken() {
339
+ const token = (process.env.RYU_AUTH_TOKEN ?? "").trim();
340
+ if (!token) {
341
+ exitError(
342
+ "publish requires an auth token: set RYU_AUTH_TOKEN to your Ryu access token"
343
+ );
344
+ }
345
+ return token;
346
+ }
347
+ var SDK_PUBLISH_KIND = "plugin";
348
+ async function commandPublish(rawDir) {
349
+ const dir = resolve(rawDir);
350
+ const manifest = loadManifest(dir);
351
+ const token = authToken();
352
+ const kind = SDK_PUBLISH_KIND;
353
+ const uiEntry = resolveUiEntry(manifest);
354
+ const uiCode = uiEntry ? resolveUiFormat(manifest) === "html" ? readUiEntryHtml(dir, uiEntry) : await bundleUiEntry(dir, uiEntry) : null;
355
+ const manifestWithHash = uiCode ? { ...manifest, ui_code_sha256: uiCodeSha256(uiCode) } : manifest;
356
+ const developer = typeof manifest.author === "string" ? manifest.author : manifest.author?.name;
357
+ const runnablesForDisplay = manifest.runnables.map((r) => ({
358
+ id: r.id,
359
+ kind: r.kind,
360
+ name: r.name,
361
+ enabled: true
362
+ }));
363
+ const listingMetadata = {
364
+ ...manifest.description ? { description: manifest.description } : {},
365
+ ...manifest.tagline ? { tagline: manifest.tagline } : {},
366
+ ...developer ? { developer } : {},
367
+ ...manifest.category ? { category: manifest.category } : {},
368
+ ...manifest.iconUrl ? { iconUrl: manifest.iconUrl } : {},
369
+ ...manifest.screenshots?.length ? { screenshots: manifest.screenshots } : {},
370
+ ...manifest.homepage ? { website: manifest.homepage } : {},
371
+ ...manifest.privacyPolicyUrl ? { privacyPolicyUrl: manifest.privacyPolicyUrl } : {},
372
+ ...manifest.termsOfServiceUrl ? { termsOfServiceUrl: manifest.termsOfServiceUrl } : {},
373
+ ...manifest.capabilities?.length ? { capabilities: manifest.capabilities } : {},
374
+ ...manifest.examplePrompts?.length ? { examplePrompts: manifest.examplePrompts } : {},
375
+ ...manifest.setup ? { setup: manifest.setup } : {},
376
+ ...runnablesForDisplay.length ? { runnables: runnablesForDisplay } : {}
377
+ };
378
+ const url = `${publishBaseUrl()}/api/marketplace/publish`;
379
+ const body = {
380
+ id: manifest.id,
381
+ kind,
382
+ name: manifest.name,
383
+ version: manifest.version,
384
+ manifest: manifestWithHash,
385
+ // The descriptor is the manifest itself for a plugin/skill Plugin; Core maps
386
+ // it on install. Grants are read from the manifest server-side too.
387
+ descriptor: manifestWithHash,
388
+ grants: manifest.permission_grants ?? [],
389
+ // Rich listing metadata (Phase 1.5) forwarded flat; see above.
390
+ ...listingMetadata,
391
+ // Per-item affiliate terms (optional): the commission a referrer earns when
392
+ // a referred user buys this paid item. The server re-validates the rule and
393
+ // stores it as the item's override (else the seller default applies).
394
+ ...manifest.affiliate?.enabled ? { affiliate: manifest.affiliate } : {},
395
+ // The bundled UI code rides OUTSIDE the signed manifest as payload; the
396
+ // server stores it and serves it on detail. Omitted for manifest-only.
397
+ ...uiCode ? { ui_code: uiCode } : {}
398
+ };
399
+ let resp;
400
+ try {
401
+ resp = await fetch(url, {
402
+ method: "POST",
403
+ headers: {
404
+ "content-type": "application/json",
405
+ authorization: `Bearer ${token}`
406
+ },
407
+ body: JSON.stringify(body)
408
+ });
409
+ } catch (err) {
410
+ exitError(`could not reach ${url}: ${String(err)}`);
411
+ }
412
+ const text = await resp.text();
413
+ if (!resp.ok) {
414
+ exitError(`publish failed (${resp.status}): ${text}`);
415
+ }
416
+ process.stdout.write(
417
+ `published ${manifest.id}@${manifest.version} (${kind}) \u2192 pending moderation
418
+ ${text}
419
+ `
420
+ );
421
+ }
422
+ var [, , command, ...args] = process.argv;
423
+ if (!command) {
424
+ printUsage();
425
+ process.exit(1);
426
+ }
427
+ if (command === "pack") {
428
+ const dir = args[0];
429
+ if (!dir) {
430
+ exitError("pack requires a directory argument: bunx ryu pack <dir>");
431
+ }
432
+ commandPack(dir).catch((err) => {
433
+ exitError(String(err));
434
+ });
435
+ } else if (command === "publish") {
436
+ const dir = args[0];
437
+ if (!dir) {
438
+ exitError("publish requires a directory argument: bunx ryu publish <dir>");
439
+ }
440
+ commandPublish(dir).catch((err) => {
441
+ exitError(String(err));
442
+ });
443
+ } else if (command === "dev") {
444
+ const entry = args[0];
445
+ if (!entry) {
446
+ exitError("dev requires an entry argument: bunx ryu dev <entry>");
447
+ }
448
+ commandDev(entry).catch((err) => {
449
+ exitError(String(err));
450
+ });
451
+ } else {
452
+ printUsage();
453
+ exitError(`unknown command: ${command}`);
454
+ }