@ryuhq/sdk 0.1.14 → 0.2.2
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.
- package/README.md +69 -7
- package/dist/action.cjs +839 -0
- package/dist/action.d.cts +88 -0
- package/dist/action.d.ts +88 -0
- package/dist/action.js +8 -0
- package/dist/agent-plugin.cjs +46 -0
- package/dist/agent-plugin.d.cts +43 -33
- package/dist/agent-plugin.d.ts +43 -33
- package/dist/agent-plugin.js +1 -1
- package/dist/agent.cjs +16 -0
- package/dist/agent.d.cts +377 -1
- package/dist/agent.d.ts +377 -1
- package/dist/agent.js +4 -2
- package/dist/app-B0Z9Ew_R.d.cts +141 -0
- package/dist/app-C-BDJwfG.d.ts +141 -0
- package/dist/builder.cjs +1194 -0
- package/dist/builder.d.cts +217 -0
- package/dist/builder.d.ts +217 -0
- package/dist/builder.js +29 -0
- package/dist/chunk-4TPUZDTI.js +94 -0
- package/dist/chunk-BC3A7HMO.js +164 -0
- package/dist/chunk-FZSFZOIN.js +200 -0
- package/dist/{chunk-ODFEUVPW.js → chunk-HACAGK65.js} +1 -18
- package/dist/chunk-HLKJZAFK.js +361 -0
- package/dist/chunk-IKEDLLFY.js +19 -0
- package/dist/{chunk-G6FLVEC4.js → chunk-JX6DDRXV.js} +46 -0
- package/dist/{chunk-AO2KJRDD.js → chunk-NZKVOSC2.js} +192 -3
- package/dist/chunk-QYFUNJOH.js +83 -0
- package/dist/chunk-SN2QBJUF.js +148 -0
- package/dist/chunk-TLDPEGC7.js +21 -0
- package/dist/chunk-TXSHHZF2.js +0 -0
- package/dist/chunk-VLIRNNAE.js +154 -0
- package/dist/{chunk-MTUBUPIV.js → chunk-Z57QDDJR.js} +8 -227
- package/dist/chunk-ZTJWBRUL.js +282 -0
- package/dist/cli.cjs +347 -18
- package/dist/cli.js +128 -23
- package/dist/client-D5U6ssPc.d.cts +84 -0
- package/dist/client-D5U6ssPc.d.ts +84 -0
- package/dist/index.cjs +810 -86
- package/dist/index.d.cts +19 -634
- package/dist/index.d.ts +19 -634
- package/dist/index.js +82 -699
- package/dist/manifest.cjs +200 -4
- package/dist/manifest.d.cts +260 -5
- package/dist/manifest.d.ts +260 -5
- package/dist/manifest.js +15 -1
- package/dist/mcp/client.cjs +180 -0
- package/dist/mcp/client.d.cts +49 -0
- package/dist/mcp/client.d.ts +49 -0
- package/dist/mcp/client.js +10 -0
- package/dist/mcp/server.cjs +370 -0
- package/dist/mcp/server.d.cts +127 -0
- package/dist/mcp/server.d.ts +127 -0
- package/dist/mcp/server.js +9 -0
- package/dist/mcp.cjs +376 -0
- package/dist/mcp.d.cts +2 -0
- package/dist/mcp.d.ts +2 -0
- package/dist/mcp.js +17 -0
- package/dist/model.cjs +141 -0
- package/dist/model.d.cts +33 -0
- package/dist/model.d.ts +33 -0
- package/dist/model.js +18 -0
- package/dist/plugin.cjs +46 -0
- package/dist/plugin.d.cts +215 -0
- package/dist/plugin.d.ts +215 -0
- package/dist/plugin.js +8 -0
- package/dist/runnable.cjs +1438 -0
- package/dist/runnable.d.cts +274 -0
- package/dist/runnable.d.ts +274 -0
- package/dist/runnable.js +34 -0
- package/dist/{index-B6SkaAjJ.d.ts → tool-AjkdFvhE.d.ts} +88 -461
- package/dist/{index-BvAB5eMk.d.cts → tool-CgzW92O_.d.cts} +88 -461
- package/package.json +48 -3
- package/src/agent/loop.test.ts +4 -4
- package/src/agent/tools.ts +3 -3
- package/src/agent-plugin.test.ts +58 -0
- package/src/agent-plugin.ts +109 -36
- package/src/builder.ts +9 -0
- package/src/cli/dev.test.ts +26 -47
- package/src/cli/dev.ts +10 -2
- package/src/cli-security.test.ts +109 -0
- package/src/cli.ts +141 -25
- package/src/contracts-lockstep.test.ts +16 -2
- package/src/exports-lockstep.test.ts +93 -0
- package/src/generated/plugin-manifest.ts +322 -29
- package/src/index.ts +53 -0
- package/src/manifest-schema.test.ts +30 -1
- package/src/manifest.fixtures.test.ts +22 -6
- package/src/manifest.test.ts +165 -10
- package/src/manifest.ts +454 -188
- package/src/mcp/index.ts +18 -0
- package/src/mcp/server.ts +2 -1
- package/src/model/index.ts +22 -0
- package/src/plugin/ryu-plugin.ts +82 -0
- package/src/runnable/action.test.ts +128 -0
- package/src/runnable/action.ts +202 -0
- package/src/runnable/app.test.ts +2 -0
- package/src/runnable/app.ts +50 -14
- package/src/runnable/index.ts +20 -3
- package/src/runnable/primitives.test.ts +34 -0
- package/src/runnable/primitives.ts +116 -0
- package/src/runnable/runnable-types.ts +3 -0
- package/src/runnable/tool.ts +36 -5
- package/src/runnable/turn-hook.ts +8 -3
- package/src/slash-command.test.ts +69 -0
|
@@ -146,8 +146,38 @@ var WidgetContributionSchema = z.object({
|
|
|
146
146
|
/** Default display mode (`inline` | `fullscreen` | `pip`). */
|
|
147
147
|
default_display_mode: z.string().default(DEFAULT_WIDGET_DISPLAY_MODE)
|
|
148
148
|
});
|
|
149
|
+
var ChatWidgetTemplateSchema = z.object({
|
|
150
|
+
id: z.string().regex(/^[a-z0-9][a-z0-9._:-]*$/),
|
|
151
|
+
title: z.string().min(1),
|
|
152
|
+
description: z.string().optional(),
|
|
153
|
+
triggers: z.array(z.string()).default([]),
|
|
154
|
+
examples: z.array(z.string()).default([]),
|
|
155
|
+
backing: z.object({
|
|
156
|
+
tool_id: z.string().regex(/^[A-Za-z0-9][A-Za-z0-9._:/-]*$/).optional(),
|
|
157
|
+
view_id: z.string().regex(/^[a-z0-9][a-z0-9._:-]*$/).optional()
|
|
158
|
+
}),
|
|
159
|
+
display_mode: z.string().min(1),
|
|
160
|
+
safe_action_ids: z.array(z.string().regex(/^[a-z0-9][a-z0-9._-]*$/)).default([]),
|
|
161
|
+
availability: z.string().default("available")
|
|
162
|
+
}).superRefine((value, ctx) => {
|
|
163
|
+
const count = Number(Boolean(value.backing.tool_id)) + Number(Boolean(value.backing.view_id));
|
|
164
|
+
if (count !== 1 && value.availability === "available") {
|
|
165
|
+
ctx.addIssue({
|
|
166
|
+
code: "custom",
|
|
167
|
+
path: ["backing"],
|
|
168
|
+
message: "available templates need exactly one backing tool_id or view_id"
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
if (count > 1) {
|
|
172
|
+
ctx.addIssue({
|
|
173
|
+
code: "custom",
|
|
174
|
+
path: ["backing"],
|
|
175
|
+
message: "backing must declare at most one of tool_id or view_id"
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
});
|
|
149
179
|
var ToolAppConfigSchema = z.object({
|
|
150
|
-
/** MCP tool slug this runnable wraps — the fully-qualified `<server
|
|
180
|
+
/** MCP tool slug this runnable wraps — the fully-qualified `<server>.<name>` id. */
|
|
151
181
|
slug: z.string().min(1),
|
|
152
182
|
/** The tool description the model reads when choosing it. Carried here because a
|
|
153
183
|
* packed app's manifest is the only channel (there is no `generated.rs`); Core's
|
|
@@ -166,6 +196,33 @@ var ToolAppConfigSchema = z.object({
|
|
|
166
196
|
/** Optional status label shown when the render tool finishes. */
|
|
167
197
|
invoked: z.string().optional()
|
|
168
198
|
});
|
|
199
|
+
var SlashCommandOptionSchema = z.object({
|
|
200
|
+
description: z.string().optional(),
|
|
201
|
+
label: z.string().min(1),
|
|
202
|
+
value: z.string().min(1)
|
|
203
|
+
}).passthrough();
|
|
204
|
+
var SlashCommandCustomOptionSchema = z.union([
|
|
205
|
+
z.literal(true),
|
|
206
|
+
z.object({
|
|
207
|
+
description: z.string().optional(),
|
|
208
|
+
label: z.string().min(1).optional()
|
|
209
|
+
}).passthrough()
|
|
210
|
+
]);
|
|
211
|
+
var SlashCommandArgumentSchema = z.object({
|
|
212
|
+
allow_custom: z.boolean().optional(),
|
|
213
|
+
custom: SlashCommandCustomOptionSchema.optional(),
|
|
214
|
+
description: z.string().optional(),
|
|
215
|
+
name: z.string().min(1),
|
|
216
|
+
options: z.array(SlashCommandOptionSchema).optional()
|
|
217
|
+
}).passthrough();
|
|
218
|
+
var SlashCommandContributionSchema = z.object({
|
|
219
|
+
args: z.array(SlashCommandArgumentSchema).optional(),
|
|
220
|
+
body: z.string().optional(),
|
|
221
|
+
command: z.string().min(1),
|
|
222
|
+
description: z.string().optional(),
|
|
223
|
+
id: z.string().optional(),
|
|
224
|
+
parameters: z.array(SlashCommandArgumentSchema).optional()
|
|
225
|
+
}).passthrough();
|
|
169
226
|
var ContributesSchema = z.object({
|
|
170
227
|
turn_hooks: z.array(TurnHookContributionSchema).default([]),
|
|
171
228
|
/** App events this plugin EMITS — the provider half of the hook system, whose
|
|
@@ -174,13 +231,20 @@ var ContributesSchema = z.object({
|
|
|
174
231
|
* signing, leaving an app that emits events nothing is allowed to subscribe to. */
|
|
175
232
|
hook_events: z.array(HookEventContributionSchema).default([]),
|
|
176
233
|
composer_controls: z.array(z.record(z.string(), z.unknown())).default([]),
|
|
234
|
+
/** Chat feature descriptors whose behavior is implemented by the host shell.
|
|
235
|
+
* Mirrors the Rust-side `Contributes.chat_features`; keeping this field in the
|
|
236
|
+
* authoring schema prevents `ryu pack` from silently deleting a plugin's chat
|
|
237
|
+
* feature declaration before signing. */
|
|
238
|
+
chat_features: z.array(z.record(z.string(), z.unknown())).default([]),
|
|
177
239
|
settings_tabs: z.array(z.record(z.string(), z.unknown())).default([]),
|
|
178
|
-
slash_commands: z.array(
|
|
240
|
+
slash_commands: z.array(SlashCommandContributionSchema).default([]),
|
|
179
241
|
/** App widgets (Ryu Apps). Each binds a render tool id to its
|
|
180
242
|
* `ui://widget/<slug>.html` template. Mirrors the Rust-side
|
|
181
243
|
* `Contributes.widgets` field, without which the CLI's zod parse would strip
|
|
182
244
|
* every widget an app authored here declares. */
|
|
183
245
|
widgets: z.array(WidgetContributionSchema).default([]),
|
|
246
|
+
/** Metadata-only chat widget templates. */
|
|
247
|
+
chat_widget_templates: z.array(ChatWidgetTemplateSchema).optional(),
|
|
184
248
|
/** App-registered sidebar sections (header + live list) and buttons (single nav
|
|
185
249
|
* rows). Loosely typed here — the shell owns the spec vocabulary — matching how
|
|
186
250
|
* `composer_controls`/`settings_tabs` are declared. Mirrors the Rust-side
|
|
@@ -193,6 +257,13 @@ var ContributesSchema = z.object({
|
|
|
193
257
|
* Rust-side `Contributes.dock_panels`; without it the CLI's zod parse would
|
|
194
258
|
* strip the dock panel an app declares here. */
|
|
195
259
|
dock_panels: z.array(z.record(z.string(), z.unknown())).default([]),
|
|
260
|
+
/** App-registered live activities (the desktop "Dynamic Island" cards). Loosely
|
|
261
|
+
* typed for the same reason as the surfaces above — the shell owns the
|
|
262
|
+
* `spec` vocabulary. Mirrors the Rust-side `Contributes.live_activities`;
|
|
263
|
+
* without it the CLI's zod parse would strip every live activity an app
|
|
264
|
+
* declares here, so a packed bundle would ship a dock that silently stays
|
|
265
|
+
* empty. */
|
|
266
|
+
live_activities: z.array(z.record(z.string(), z.unknown())).default([]),
|
|
196
267
|
/** Deletable data categories the app owns — one "Delete all X" row in Settings
|
|
197
268
|
* → Danger Zone. Mirrors the Rust-side `Contributes.data_categories`; without
|
|
198
269
|
* it the CLI's zod parse would strip the declaration before signing, and the
|
|
@@ -230,7 +301,13 @@ var ContributesSchema = z.object({
|
|
|
230
301
|
* bug: the styles' `.md` files are not carried by the bundle either, so there
|
|
231
302
|
* would be no residue to notice — the plugin would install clean and contribute
|
|
232
303
|
* nothing. */
|
|
233
|
-
output_styles: z.array(OutputStyleContributionSchema).default([])
|
|
304
|
+
output_styles: z.array(OutputStyleContributionSchema).default([]),
|
|
305
|
+
/** Per-message actions contributed by an enabled plugin. Kept as loose records
|
|
306
|
+
* so renderer-specific `kind`/`args` payloads survive `ryu pack` unchanged. */
|
|
307
|
+
message_actions: z.array(z.record(z.string(), z.unknown())).default([]),
|
|
308
|
+
/** Buttons contributed to the floating text-selection toolbar. Kept as loose
|
|
309
|
+
* records so host-owned dispatch args survive `ryu pack` unchanged. */
|
|
310
|
+
selection_actions: z.array(z.record(z.string(), z.unknown())).default([])
|
|
234
311
|
});
|
|
235
312
|
var SetupStepSchema = z.object({
|
|
236
313
|
/** Card heading (e.g. the companion app name). */
|
|
@@ -311,6 +388,61 @@ var EnginesReqSchema = z.object({
|
|
|
311
388
|
/** Floor for the Next.js web app. */
|
|
312
389
|
web: z.string().optional()
|
|
313
390
|
});
|
|
391
|
+
var McpServerAuthSchema = z.object({
|
|
392
|
+
client_id: z.string().min(1).optional(),
|
|
393
|
+
type: z.literal("oauth")
|
|
394
|
+
}).strict();
|
|
395
|
+
var McpServerDeclSchema = z.object({
|
|
396
|
+
args: z.array(z.string()).default([]),
|
|
397
|
+
auth: McpServerAuthSchema.optional(),
|
|
398
|
+
command: z.string().optional(),
|
|
399
|
+
command_env: z.string().optional(),
|
|
400
|
+
description: z.string().optional(),
|
|
401
|
+
enabled: z.boolean().default(true),
|
|
402
|
+
env: z.record(z.string(), z.string()).default({}),
|
|
403
|
+
headers: z.record(z.string(), z.string()).default({}),
|
|
404
|
+
type: z.enum(["stdio", "http", "streamable-http", "streamable_http", "sse"]).optional(),
|
|
405
|
+
url: z.url().optional()
|
|
406
|
+
}).superRefine((server, context) => {
|
|
407
|
+
if (!(server.command || server.url)) {
|
|
408
|
+
context.addIssue({
|
|
409
|
+
code: "custom",
|
|
410
|
+
message: "an MCP server requires command or url"
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
if (!server.auth) {
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
if (server.command || server.type === "stdio" || !server.url) {
|
|
417
|
+
context.addIssue({
|
|
418
|
+
code: "custom",
|
|
419
|
+
message: "OAuth is supported only for remote HTTP MCP servers"
|
|
420
|
+
});
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
423
|
+
if (Object.keys(server.headers).some(
|
|
424
|
+
(name) => name.toLowerCase() === "authorization"
|
|
425
|
+
)) {
|
|
426
|
+
context.addIssue({
|
|
427
|
+
code: "custom",
|
|
428
|
+
message: "OAuth cannot be combined with a static Authorization header"
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
const url = new URL(server.url);
|
|
432
|
+
const loopback = url.hostname === "localhost" || url.hostname.startsWith("127.") || url.hostname === "[::1]" || url.hostname === "::1";
|
|
433
|
+
if (url.username || url.password || url.hash) {
|
|
434
|
+
context.addIssue({
|
|
435
|
+
code: "custom",
|
|
436
|
+
message: "OAuth MCP URLs cannot contain credentials or fragments"
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
if (url.protocol !== "https:" && !(url.protocol === "http:" && loopback)) {
|
|
440
|
+
context.addIssue({
|
|
441
|
+
code: "custom",
|
|
442
|
+
message: "OAuth MCP URLs must use HTTPS except on loopback"
|
|
443
|
+
});
|
|
444
|
+
}
|
|
445
|
+
});
|
|
314
446
|
var PluginManifestSchema = z.object({
|
|
315
447
|
/** Reverse-domain unique identifier (e.g. `"com.example.my-plugin"`). */
|
|
316
448
|
id: z.string().min(1, "id is required"),
|
|
@@ -325,6 +457,9 @@ var PluginManifestSchema = z.object({
|
|
|
325
457
|
/^\d+\.\d+\.\d+(?:-[\w.]+)?(?:\+[\w.]+)?$/,
|
|
326
458
|
"version must be a valid semver string (e.g. 1.0.0)"
|
|
327
459
|
),
|
|
460
|
+
/** Core-owned release maturity metadata. The Rust contract validates the
|
|
461
|
+
* richer shape; the SDK authoring parser must preserve it for pack/publish. */
|
|
462
|
+
stability: z.unknown().optional(),
|
|
328
463
|
/**
|
|
329
464
|
* Lower-case hex `sha256(utf8_bytes(ui_code))` binding the plugin's bundled
|
|
330
465
|
* sandboxed-UI code to this manifest. `ryu pack` / `ryu publish` compute it and
|
|
@@ -342,6 +477,26 @@ var PluginManifestSchema = z.object({
|
|
|
342
477
|
* Declarations only — grant enforcement is the Gateway's responsibility.
|
|
343
478
|
*/
|
|
344
479
|
permission_grants: z.array(z.string()).default([]),
|
|
480
|
+
/** Deny-by-default sandbox permissions. Core remains authoritative; this
|
|
481
|
+
* schema mirrors the fields so `ryu pack` cannot strip them. */
|
|
482
|
+
permissions: z.object({
|
|
483
|
+
fs: z.object({
|
|
484
|
+
read: z.array(z.string()).default([]),
|
|
485
|
+
write: z.array(z.string()).default([])
|
|
486
|
+
}).optional(),
|
|
487
|
+
child_process: z.boolean().optional(),
|
|
488
|
+
run: z.array(z.string()).default([]),
|
|
489
|
+
network: z.union([z.boolean(), z.array(z.string())]).optional(),
|
|
490
|
+
tool: z.array(z.string()).default([])
|
|
491
|
+
}).optional(),
|
|
492
|
+
/** Core-owned permission presentation levels. Preserved verbatim here and
|
|
493
|
+
* validated by Core's authoritative manifest contract. */
|
|
494
|
+
permission_levels: z.unknown().optional(),
|
|
495
|
+
/** Remote or stdio MCP servers registered by this plugin. */
|
|
496
|
+
mcp_servers: z.record(z.string(), McpServerDeclSchema).optional(),
|
|
497
|
+
/** Core-owned sidecar declarations. Their full process/HTTP schema stays in
|
|
498
|
+
* the Rust contract; this authoring layer must never strip them. */
|
|
499
|
+
sidecars: z.unknown().optional(),
|
|
345
500
|
/**
|
|
346
501
|
* Optional Companion surface (an in-desktop overlay or sidebar panel).
|
|
347
502
|
* Absent when the plugin has no Companion surface.
|
|
@@ -377,6 +532,9 @@ var PluginManifestSchema = z.object({
|
|
|
377
532
|
* an unsupported-target plugin stays installable and inspectable.
|
|
378
533
|
*/
|
|
379
534
|
targets: z.array(SurfaceSchema).default([]),
|
|
535
|
+
/** Rich per-surface declarations (`support`, UI, contributed commands, …).
|
|
536
|
+
* Core owns and validates the nested vocabulary. */
|
|
537
|
+
surfaces: z.unknown().optional(),
|
|
380
538
|
/**
|
|
381
539
|
* Host version floors — the semver requirement each surface must satisfy for
|
|
382
540
|
* this plugin to install. Mirrors Core's `EnginesReq`
|
|
@@ -435,10 +593,16 @@ var PluginManifestSchema = z.object({
|
|
|
435
593
|
url: z.string().optional()
|
|
436
594
|
})
|
|
437
595
|
]).optional(),
|
|
596
|
+
/** Public source repository URL (Claude/Codex `repository`). */
|
|
597
|
+
repository: z.string().url().optional(),
|
|
598
|
+
/** True when the provider operates outside the local Ryu runtime. */
|
|
599
|
+
external: z.boolean().optional(),
|
|
438
600
|
/** Project/marketing homepage — maps to the listing `website` (Claude field). */
|
|
439
601
|
homepage: z.string().optional(),
|
|
440
602
|
/** Free-text search keywords (Claude field). */
|
|
441
603
|
keywords: z.array(z.string()).optional(),
|
|
604
|
+
/** Stable Marketplace filter labels (Ryu extension). */
|
|
605
|
+
tags: z.array(z.string()).optional(),
|
|
442
606
|
/** Taxonomy category beyond the runnable kinds (Claude field). */
|
|
443
607
|
category: z.string().optional(),
|
|
444
608
|
/** SPDX-ish license identifier (Claude field). */
|
|
@@ -452,6 +616,8 @@ var PluginManifestSchema = z.object({
|
|
|
452
616
|
* from `iconUrl` (a raster logo). Falls back to `iconUrl` when omitted.
|
|
453
617
|
*/
|
|
454
618
|
icon: z.string().optional(),
|
|
619
|
+
/** Optional detail-page hero banner metadata forwarded to the marketplace. */
|
|
620
|
+
banner: z.record(z.string(), z.unknown()).optional(),
|
|
455
621
|
/**
|
|
456
622
|
* Dithered-gradient background for the card's icon square (Ryu extension),
|
|
457
623
|
* mirroring dither-kit's `DitherGradient` props. `from`/`to` are a palette-colour
|
|
@@ -483,6 +649,22 @@ var PluginManifestSchema = z.object({
|
|
|
483
649
|
* array of steps guiding the user through post-install configuration.
|
|
484
650
|
*/
|
|
485
651
|
setup: z.union([SetupStepSchema, z.array(SetupStepSchema)]).optional()
|
|
652
|
+
}).passthrough().superRefine((manifest, context) => {
|
|
653
|
+
const hasOAuthServer = Object.values(manifest.mcp_servers ?? {}).some(
|
|
654
|
+
(server) => server.auth?.type === "oauth"
|
|
655
|
+
);
|
|
656
|
+
if (!hasOAuthServer) {
|
|
657
|
+
return;
|
|
658
|
+
}
|
|
659
|
+
for (const grant of ["mcp:server", "identity.read"]) {
|
|
660
|
+
if (!manifest.permission_grants.includes(grant)) {
|
|
661
|
+
context.addIssue({
|
|
662
|
+
code: "custom",
|
|
663
|
+
message: `OAuth MCP servers require the ${grant} permission grant`,
|
|
664
|
+
path: ["permission_grants"]
|
|
665
|
+
});
|
|
666
|
+
}
|
|
667
|
+
}
|
|
486
668
|
});
|
|
487
669
|
function validatePluginId(id) {
|
|
488
670
|
loadNative().validatePluginId(id);
|
|
@@ -504,7 +686,12 @@ export {
|
|
|
504
686
|
PiExtensionContributionSchema,
|
|
505
687
|
OutputStyleContributionSchema,
|
|
506
688
|
WidgetContributionSchema,
|
|
689
|
+
ChatWidgetTemplateSchema,
|
|
507
690
|
ToolAppConfigSchema,
|
|
691
|
+
SlashCommandOptionSchema,
|
|
692
|
+
SlashCommandCustomOptionSchema,
|
|
693
|
+
SlashCommandArgumentSchema,
|
|
694
|
+
SlashCommandContributionSchema,
|
|
508
695
|
ContributesSchema,
|
|
509
696
|
SetupStepSchema,
|
|
510
697
|
AppDependencySchema,
|
|
@@ -512,6 +699,8 @@ export {
|
|
|
512
699
|
RequiresSchema,
|
|
513
700
|
SurfaceSchema,
|
|
514
701
|
EnginesReqSchema,
|
|
702
|
+
McpServerAuthSchema,
|
|
703
|
+
McpServerDeclSchema,
|
|
515
704
|
PluginManifestSchema,
|
|
516
705
|
validatePluginId,
|
|
517
706
|
validateManifestStrict,
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
// src/runnable/tool.ts
|
|
2
|
+
function validateInput(input, schema, toolId) {
|
|
3
|
+
if (typeof input !== "object" || input === null || Array.isArray(input)) {
|
|
4
|
+
throw new Error(
|
|
5
|
+
`[ryu-sdk] Tool "${toolId}" input must be an object, got ${JSON.stringify(input)}`
|
|
6
|
+
);
|
|
7
|
+
}
|
|
8
|
+
const record = input;
|
|
9
|
+
for (const requiredKey of schema.required ?? []) {
|
|
10
|
+
if (!(requiredKey in record)) {
|
|
11
|
+
throw new Error(
|
|
12
|
+
`[ryu-sdk] Tool "${toolId}" input missing required field "${requiredKey}"`
|
|
13
|
+
);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
for (const [key, prop] of Object.entries(schema.properties)) {
|
|
17
|
+
if (!(key in record)) {
|
|
18
|
+
continue;
|
|
19
|
+
}
|
|
20
|
+
const value = record[key];
|
|
21
|
+
if (!checkType(value, prop.type)) {
|
|
22
|
+
throw new Error(
|
|
23
|
+
`[ryu-sdk] Tool "${toolId}" input field "${key}" expected type "${prop.type}", got ${JSON.stringify(value)}`
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
function checkType(value, type) {
|
|
29
|
+
switch (type) {
|
|
30
|
+
case "string":
|
|
31
|
+
return typeof value === "string";
|
|
32
|
+
case "number":
|
|
33
|
+
case "integer":
|
|
34
|
+
return typeof value === "number";
|
|
35
|
+
case "boolean":
|
|
36
|
+
return typeof value === "boolean";
|
|
37
|
+
case "array":
|
|
38
|
+
return Array.isArray(value);
|
|
39
|
+
case "object":
|
|
40
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
41
|
+
default:
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function defineTool(options) {
|
|
46
|
+
const { description, id, name, schema, run } = options;
|
|
47
|
+
const code = `return await (${run.toString()})(input, host);`;
|
|
48
|
+
return {
|
|
49
|
+
id,
|
|
50
|
+
name,
|
|
51
|
+
kind: "tool",
|
|
52
|
+
schema,
|
|
53
|
+
...description === void 0 ? {} : { description },
|
|
54
|
+
code,
|
|
55
|
+
run(input, ctx) {
|
|
56
|
+
validateInput(input, schema, id);
|
|
57
|
+
return run(input, ctx);
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
function inlineToolRunnable(tool, options) {
|
|
62
|
+
return {
|
|
63
|
+
id: tool.id,
|
|
64
|
+
name: tool.name,
|
|
65
|
+
kind: "tool",
|
|
66
|
+
config: {
|
|
67
|
+
slug: tool.id,
|
|
68
|
+
backend: "inline_deno",
|
|
69
|
+
code: tool.code,
|
|
70
|
+
input_schema: tool.schema,
|
|
71
|
+
...options?.description === void 0 ? tool.description === void 0 ? {} : { description: tool.description } : { description: options.description },
|
|
72
|
+
...options?.outputSchema ? { output_schema: options.outputSchema } : {},
|
|
73
|
+
...options?.annotations ? { annotations: options.annotations } : {},
|
|
74
|
+
...options?.action === void 0 ? {} : { action: options.action },
|
|
75
|
+
...options?.needsApproval === void 0 ? {} : { needs_approval: options.needsApproval }
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export {
|
|
81
|
+
defineTool,
|
|
82
|
+
inlineToolRunnable
|
|
83
|
+
};
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import {
|
|
2
|
+
inlineToolRunnable
|
|
3
|
+
} from "./chunk-QYFUNJOH.js";
|
|
4
|
+
import {
|
|
5
|
+
PluginManifestSchema
|
|
6
|
+
} from "./chunk-NZKVOSC2.js";
|
|
7
|
+
|
|
8
|
+
// src/runnable/app.ts
|
|
9
|
+
var DEFAULT_APP_WIDGET_MIME = "text/html+skybridge";
|
|
10
|
+
var DEFAULT_APP_DISPLAY_MODE = "inline";
|
|
11
|
+
var WIDGET_RENDER_GRANT = "widget:render";
|
|
12
|
+
function withWidgetRenderGrant(grants, widgets) {
|
|
13
|
+
const out = [...grants];
|
|
14
|
+
if (widgets.length > 0 && !out.includes(WIDGET_RENDER_GRANT)) {
|
|
15
|
+
out.push(WIDGET_RENDER_GRANT);
|
|
16
|
+
}
|
|
17
|
+
return out;
|
|
18
|
+
}
|
|
19
|
+
function withToolExecuteGrant(grants, tools) {
|
|
20
|
+
const out = [...grants];
|
|
21
|
+
if (tools.some((tool) => tool.runnable !== void 0) && !out.includes("tool:execute")) {
|
|
22
|
+
out.push("tool:execute");
|
|
23
|
+
}
|
|
24
|
+
return out;
|
|
25
|
+
}
|
|
26
|
+
function appToolId(server, name) {
|
|
27
|
+
return `${server}.${name}`;
|
|
28
|
+
}
|
|
29
|
+
function defineApp(options) {
|
|
30
|
+
const server = options.server ?? options.slug;
|
|
31
|
+
const uri = `ui://widget/${options.slug}.html`;
|
|
32
|
+
const mime = options.mime ?? DEFAULT_APP_WIDGET_MIME;
|
|
33
|
+
const displayMode = options.displayMode ?? DEFAULT_APP_DISPLAY_MODE;
|
|
34
|
+
const hasCompanions = options.tools.some((t) => t.accessible === true);
|
|
35
|
+
const runnables = [];
|
|
36
|
+
const widgets = [];
|
|
37
|
+
for (const spec of options.tools) {
|
|
38
|
+
const isRender = spec.accessible !== true;
|
|
39
|
+
const id = appToolId(server, spec.name);
|
|
40
|
+
const executable = spec.runnable ? inlineToolRunnable(spec.runnable) : void 0;
|
|
41
|
+
const config = {
|
|
42
|
+
...executable?.config ?? {},
|
|
43
|
+
slug: id,
|
|
44
|
+
description: spec.description,
|
|
45
|
+
widget: isRender,
|
|
46
|
+
widget_accessible: isRender ? hasCompanions : true,
|
|
47
|
+
...spec.inputSchema ? { input_schema: spec.inputSchema } : {},
|
|
48
|
+
...spec.invoking ? { invoking: spec.invoking } : {},
|
|
49
|
+
...spec.invoked ? { invoked: spec.invoked } : {}
|
|
50
|
+
};
|
|
51
|
+
runnables.push({
|
|
52
|
+
id,
|
|
53
|
+
name: spec.name,
|
|
54
|
+
kind: "tool",
|
|
55
|
+
config
|
|
56
|
+
});
|
|
57
|
+
if (isRender) {
|
|
58
|
+
widgets.push({
|
|
59
|
+
tool_id: id,
|
|
60
|
+
uri,
|
|
61
|
+
ui_entry: options.uiEntry,
|
|
62
|
+
mime,
|
|
63
|
+
default_display_mode: displayMode
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
const contributes = {
|
|
68
|
+
turn_hooks: [],
|
|
69
|
+
chat_features: [],
|
|
70
|
+
// This builder synthesises an app from its runnables; an app that emits
|
|
71
|
+
// events declares them in a hand-authored `manifest.json`, same as
|
|
72
|
+
// `lsp_servers` below.
|
|
73
|
+
hook_events: [],
|
|
74
|
+
composer_controls: [],
|
|
75
|
+
settings_tabs: [],
|
|
76
|
+
slash_commands: options.slashCommands ?? [],
|
|
77
|
+
sidebar_sections: [],
|
|
78
|
+
sidebar_buttons: [],
|
|
79
|
+
dock_panels: [],
|
|
80
|
+
live_activities: [],
|
|
81
|
+
// Empty for the same reason as every sibling family above: this builder
|
|
82
|
+
// synthesises `widgets` from the app's own runnables and nothing else, and
|
|
83
|
+
// takes no `contributes` passthrough. An app that wants to declare language
|
|
84
|
+
// servers writes them in a hand-authored `manifest.json`.
|
|
85
|
+
lsp_servers: {},
|
|
86
|
+
// Same reason again: a danger-zone category, a Pi extension and an output
|
|
87
|
+
// style are all hand-authored declarations, not something derivable from
|
|
88
|
+
// runnables. An output style in particular points at a Markdown file next
|
|
89
|
+
// to the manifest, which this builder never writes.
|
|
90
|
+
data_categories: [],
|
|
91
|
+
pi_extensions: [],
|
|
92
|
+
output_styles: [],
|
|
93
|
+
message_actions: [],
|
|
94
|
+
selection_actions: [],
|
|
95
|
+
widgets
|
|
96
|
+
};
|
|
97
|
+
const raw = {
|
|
98
|
+
id: options.id,
|
|
99
|
+
name: options.title,
|
|
100
|
+
version: options.version,
|
|
101
|
+
runnables,
|
|
102
|
+
// An app that synthesises widgets MUST hold `widget:render`, so this
|
|
103
|
+
// builder declares it rather than leaving the author to discover it.
|
|
104
|
+
//
|
|
105
|
+
// Core gates widget promotion on declared-AND-enabled-AND-granted, and a
|
|
106
|
+
// missing grant fails as `DeniedNoGrant` — which is an `info!` log and
|
|
107
|
+
// nothing else. The widget silently renders as plain text, with no error
|
|
108
|
+
// in the UI and nothing pointing at the manifest.
|
|
109
|
+
//
|
|
110
|
+
// Added only when there is a widget to render, and unioned rather than
|
|
111
|
+
// overwritten so an author's own `grants` list survives and re-declaring
|
|
112
|
+
// it is not an error.
|
|
113
|
+
permission_grants: withToolExecuteGrant(
|
|
114
|
+
withWidgetRenderGrant(options.grants ?? [], widgets),
|
|
115
|
+
options.tools
|
|
116
|
+
),
|
|
117
|
+
activation_events: options.activationEvents ?? ["*"],
|
|
118
|
+
contributes,
|
|
119
|
+
// `targets: []` means EVERY surface, so an app that declares none is
|
|
120
|
+
// unrestricted — the backward-compatible default.
|
|
121
|
+
targets: options.targets ?? [],
|
|
122
|
+
// `requires` stays ABSENT (not `{apps:[],grants:[]}`) when undeclared, so the
|
|
123
|
+
// emitted manifest carries no key at all — matching Core's
|
|
124
|
+
// `Option<Requires>` + `skip_serializing_if = "Option::is_none"`.
|
|
125
|
+
...options.requires ? {
|
|
126
|
+
requires: {
|
|
127
|
+
apps: options.requires.apps ?? [],
|
|
128
|
+
capabilities: options.requires.capabilities ?? [],
|
|
129
|
+
grants: options.requires.grants ?? []
|
|
130
|
+
}
|
|
131
|
+
} : {}
|
|
132
|
+
};
|
|
133
|
+
const result = PluginManifestSchema.safeParse(raw);
|
|
134
|
+
if (!result.success) {
|
|
135
|
+
const first = result.error.issues[0];
|
|
136
|
+
const field = first?.path.join(".") ?? "unknown";
|
|
137
|
+
const message = first?.message ?? "validation failed";
|
|
138
|
+
throw new Error(
|
|
139
|
+
`manifest.json validation failed at '${field}': ${message}`
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
return result.data;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export {
|
|
146
|
+
appToolId,
|
|
147
|
+
defineApp
|
|
148
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// src/plugin/ryu-plugin.ts
|
|
2
|
+
function toDisposable(teardown) {
|
|
3
|
+
let done = false;
|
|
4
|
+
return {
|
|
5
|
+
dispose() {
|
|
6
|
+
if (done) {
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
done = true;
|
|
10
|
+
teardown();
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
function definePlugin(plugin) {
|
|
15
|
+
return plugin;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export {
|
|
19
|
+
toDisposable,
|
|
20
|
+
definePlugin
|
|
21
|
+
};
|
|
File without changes
|