@ryuhq/sdk 0.1.13 → 0.2.0
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 +19 -6
- package/dist/agent-plugin.cjs +46 -0
- package/dist/agent-plugin.d.cts +42 -32
- package/dist/agent-plugin.d.ts +42 -32
- package/dist/agent-plugin.js +1 -1
- package/dist/agent.cjs +9 -0
- package/dist/agent.d.cts +377 -1
- package/dist/agent.d.ts +377 -1
- package/dist/agent.js +4 -2
- package/dist/app-Bkw7LlCK.d.ts +129 -0
- package/dist/app-DNaGmLVf.d.cts +129 -0
- package/dist/builder.cjs +1095 -0
- package/dist/builder.d.cts +212 -0
- package/dist/builder.d.ts +212 -0
- package/dist/builder.js +28 -0
- package/dist/chunk-A3RGEPDG.js +250 -0
- package/dist/chunk-FZSFZOIN.js +200 -0
- package/dist/{chunk-ODFEUVPW.js → chunk-HACAGK65.js} +1 -18
- package/dist/{chunk-AO2KJRDD.js → chunk-IEUQ3CDG.js} +125 -2
- package/dist/chunk-IKEDLLFY.js +19 -0
- package/dist/chunk-IOLP5FFE.js +354 -0
- package/dist/{chunk-G6FLVEC4.js → chunk-JX6DDRXV.js} +46 -0
- package/dist/chunk-T5676WL2.js +240 -0
- package/dist/chunk-TLDPEGC7.js +21 -0
- package/dist/chunk-TXSHHZF2.js +0 -0
- package/dist/{chunk-MTUBUPIV.js → chunk-ULSVL7EC.js} +8 -227
- package/dist/chunk-VLIRNNAE.js +154 -0
- package/dist/chunk-W3KPP4WN.js +135 -0
- package/dist/cli.cjs +258 -14
- package/dist/cli.js +96 -16
- package/dist/client-D5U6ssPc.d.cts +84 -0
- package/dist/client-D5U6ssPc.d.ts +84 -0
- package/dist/index.cjs +519 -5
- package/dist/index.d.cts +16 -632
- package/dist/index.d.ts +16 -632
- package/dist/index.js +63 -695
- package/dist/manifest.cjs +129 -3
- package/dist/manifest.d.cts +105 -2
- package/dist/manifest.d.ts +105 -2
- package/dist/manifest.js +7 -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 +126 -0
- package/dist/mcp/server.d.ts +126 -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 +1230 -0
- package/dist/runnable.d.cts +271 -0
- package/dist/runnable.d.ts +271 -0
- package/dist/runnable.js +28 -0
- package/dist/{index-B6SkaAjJ.d.ts → tool-DSx2bFx8.d.ts} +35 -458
- package/dist/{index-BvAB5eMk.d.cts → tool-u-VR0fLF.d.cts} +35 -458
- package/package.json +38 -2
- 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 +108 -35
- package/src/cli/dev.test.ts +26 -47
- package/src/cli/dev.ts +10 -2
- package/src/cli.ts +98 -15
- package/src/exports-lockstep.test.ts +92 -0
- package/src/generated/plugin-manifest.ts +243 -27
- package/src/index.ts +35 -0
- package/src/manifest-schema.test.ts +30 -1
- package/src/manifest.fixtures.test.ts +13 -3
- package/src/manifest.test.ts +96 -10
- package/src/manifest.ts +334 -187
- package/src/mcp/index.ts +18 -0
- package/src/model/index.ts +22 -0
- package/src/plugin/ryu-plugin.ts +82 -0
- package/src/runnable/app.test.ts +2 -0
- package/src/runnable/app.ts +5 -2
- package/src/runnable/index.ts +2 -0
- package/src/runnable/primitives.ts +57 -0
- package/src/runnable/tool.ts +1 -1
- package/src/runnable/turn-hook.ts +4 -1
package/src/manifest.ts
CHANGED
|
@@ -344,6 +344,35 @@ export const WidgetContributionSchema = z.object({
|
|
|
344
344
|
|
|
345
345
|
export type WidgetContribution = z.infer<typeof WidgetContributionSchema>;
|
|
346
346
|
|
|
347
|
+
/** Metadata-only chat affordance. The host owns rendering and dispatch; the
|
|
348
|
+
* manifest carries identifiers and copy only. */
|
|
349
|
+
export const ChatWidgetTemplateSchema = z.object({
|
|
350
|
+
id: z.string().regex(/^[a-z0-9][a-z0-9._:-]*$/),
|
|
351
|
+
title: z.string().min(1),
|
|
352
|
+
description: z.string().optional(),
|
|
353
|
+
triggers: z.array(z.string()).default([]),
|
|
354
|
+
examples: z.array(z.string()).default([]),
|
|
355
|
+
backing: z
|
|
356
|
+
.object({
|
|
357
|
+
tool_id: z.string().regex(/^[A-Za-z0-9][A-Za-z0-9._:/-]*$/).optional(),
|
|
358
|
+
view_id: z.string().regex(/^[a-z0-9][a-z0-9._:-]*$/).optional(),
|
|
359
|
+
}),
|
|
360
|
+
display_mode: z.string().min(1),
|
|
361
|
+
safe_action_ids: z.array(z.string().regex(/^[a-z0-9][a-z0-9._-]*$/)).default([]),
|
|
362
|
+
availability: z.string().default("available"),
|
|
363
|
+
})
|
|
364
|
+
.superRefine((value, ctx) => {
|
|
365
|
+
const count = Number(Boolean(value.backing.tool_id)) + Number(Boolean(value.backing.view_id));
|
|
366
|
+
if (count !== 1 && value.availability === "available") {
|
|
367
|
+
ctx.addIssue({ code: "custom", path: ["backing"], message: "available templates need exactly one backing tool_id or view_id" });
|
|
368
|
+
}
|
|
369
|
+
if (count > 1) {
|
|
370
|
+
ctx.addIssue({ code: "custom", path: ["backing"], message: "backing must declare at most one of tool_id or view_id" });
|
|
371
|
+
}
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
export type ChatWidgetTemplate = z.infer<typeof ChatWidgetTemplateSchema>;
|
|
375
|
+
|
|
347
376
|
// ── ToolAppConfig (Ryu Apps per-tool config) ─────────────────────────────────
|
|
348
377
|
|
|
349
378
|
/**
|
|
@@ -355,7 +384,7 @@ export type WidgetContribution = z.infer<typeof WidgetContributionSchema>;
|
|
|
355
384
|
* derives `outputTemplate` / `toolInvocation` / `widgetAccessible`.
|
|
356
385
|
*/
|
|
357
386
|
export const ToolAppConfigSchema = z.object({
|
|
358
|
-
/** MCP tool slug this runnable wraps — the fully-qualified `<server
|
|
387
|
+
/** MCP tool slug this runnable wraps — the fully-qualified `<server>.<name>` id. */
|
|
359
388
|
slug: z.string().min(1),
|
|
360
389
|
/** The tool description the model reads when choosing it. Carried here because a
|
|
361
390
|
* packed app's manifest is the only channel (there is no `generated.rs`); Core's
|
|
@@ -380,7 +409,7 @@ export type ToolAppConfig = z.infer<typeof ToolAppConfigSchema>;
|
|
|
380
409
|
/**
|
|
381
410
|
* The `contributes` block. Mirrors `Contributes` in
|
|
382
411
|
* `apps/core/src/plugin_manifest/mod.rs`. The declarative UI surfaces
|
|
383
|
-
* (`composer_controls` / `settings_tabs` / `slash_commands`) are passed verbatim
|
|
412
|
+
* (`composer_controls` / `chat_features` / `settings_tabs` / `slash_commands`) are passed verbatim
|
|
384
413
|
* to the desktop renderer, so they are typed loosely here (records).
|
|
385
414
|
*/
|
|
386
415
|
export const ContributesSchema = z.object({
|
|
@@ -391,6 +420,11 @@ export const ContributesSchema = z.object({
|
|
|
391
420
|
* signing, leaving an app that emits events nothing is allowed to subscribe to. */
|
|
392
421
|
hook_events: z.array(HookEventContributionSchema).default([]),
|
|
393
422
|
composer_controls: z.array(z.record(z.string(), z.unknown())).default([]),
|
|
423
|
+
/** Chat feature descriptors whose behavior is implemented by the host shell.
|
|
424
|
+
* Mirrors the Rust-side `Contributes.chat_features`; keeping this field in the
|
|
425
|
+
* authoring schema prevents `ryu pack` from silently deleting a plugin's chat
|
|
426
|
+
* feature declaration before signing. */
|
|
427
|
+
chat_features: z.array(z.record(z.string(), z.unknown())).default([]),
|
|
394
428
|
settings_tabs: z.array(z.record(z.string(), z.unknown())).default([]),
|
|
395
429
|
slash_commands: z.array(z.record(z.string(), z.unknown())).default([]),
|
|
396
430
|
/** App widgets (Ryu Apps). Each binds a render tool id to its
|
|
@@ -398,6 +432,8 @@ export const ContributesSchema = z.object({
|
|
|
398
432
|
* `Contributes.widgets` field, without which the CLI's zod parse would strip
|
|
399
433
|
* every widget an app authored here declares. */
|
|
400
434
|
widgets: z.array(WidgetContributionSchema).default([]),
|
|
435
|
+
/** Metadata-only chat widget templates. */
|
|
436
|
+
chat_widget_templates: z.array(ChatWidgetTemplateSchema).optional(),
|
|
401
437
|
/** App-registered sidebar sections (header + live list) and buttons (single nav
|
|
402
438
|
* rows). Loosely typed here — the shell owns the spec vocabulary — matching how
|
|
403
439
|
* `composer_controls`/`settings_tabs` are declared. Mirrors the Rust-side
|
|
@@ -410,6 +446,13 @@ export const ContributesSchema = z.object({
|
|
|
410
446
|
* Rust-side `Contributes.dock_panels`; without it the CLI's zod parse would
|
|
411
447
|
* strip the dock panel an app declares here. */
|
|
412
448
|
dock_panels: z.array(z.record(z.string(), z.unknown())).default([]),
|
|
449
|
+
/** App-registered live activities (the desktop "Dynamic Island" cards). Loosely
|
|
450
|
+
* typed for the same reason as the surfaces above — the shell owns the
|
|
451
|
+
* `spec` vocabulary. Mirrors the Rust-side `Contributes.live_activities`;
|
|
452
|
+
* without it the CLI's zod parse would strip every live activity an app
|
|
453
|
+
* declares here, so a packed bundle would ship a dock that silently stays
|
|
454
|
+
* empty. */
|
|
455
|
+
live_activities: z.array(z.record(z.string(), z.unknown())).default([]),
|
|
413
456
|
/** Deletable data categories the app owns — one "Delete all X" row in Settings
|
|
414
457
|
* → Danger Zone. Mirrors the Rust-side `Contributes.data_categories`; without
|
|
415
458
|
* it the CLI's zod parse would strip the declaration before signing, and the
|
|
@@ -450,6 +493,9 @@ export const ContributesSchema = z.object({
|
|
|
450
493
|
* would be no residue to notice — the plugin would install clean and contribute
|
|
451
494
|
* nothing. */
|
|
452
495
|
output_styles: z.array(OutputStyleContributionSchema).default([]),
|
|
496
|
+
/** Per-message actions contributed by an enabled plugin. Kept as loose records
|
|
497
|
+
* so renderer-specific `kind`/`args` payloads survive `ryu pack` unchanged. */
|
|
498
|
+
message_actions: z.array(z.record(z.string(), z.unknown())).default([]),
|
|
453
499
|
});
|
|
454
500
|
|
|
455
501
|
export type Contributes = z.infer<typeof ContributesSchema>;
|
|
@@ -615,6 +661,78 @@ export const EnginesReqSchema = z.object({
|
|
|
615
661
|
|
|
616
662
|
export type EnginesReq = z.infer<typeof EnginesReqSchema>;
|
|
617
663
|
|
|
664
|
+
export const McpServerAuthSchema = z
|
|
665
|
+
.object({
|
|
666
|
+
client_id: z.string().min(1).optional(),
|
|
667
|
+
type: z.literal("oauth"),
|
|
668
|
+
})
|
|
669
|
+
.strict();
|
|
670
|
+
|
|
671
|
+
export const McpServerDeclSchema = z
|
|
672
|
+
.object({
|
|
673
|
+
args: z.array(z.string()).default([]),
|
|
674
|
+
auth: McpServerAuthSchema.optional(),
|
|
675
|
+
command: z.string().optional(),
|
|
676
|
+
command_env: z.string().optional(),
|
|
677
|
+
description: z.string().optional(),
|
|
678
|
+
enabled: z.boolean().default(true),
|
|
679
|
+
env: z.record(z.string(), z.string()).default({}),
|
|
680
|
+
headers: z.record(z.string(), z.string()).default({}),
|
|
681
|
+
type: z
|
|
682
|
+
.enum(["stdio", "http", "streamable-http", "streamable_http", "sse"])
|
|
683
|
+
.optional(),
|
|
684
|
+
url: z.url().optional(),
|
|
685
|
+
})
|
|
686
|
+
.superRefine((server, context) => {
|
|
687
|
+
if (!(server.command || server.url)) {
|
|
688
|
+
context.addIssue({
|
|
689
|
+
code: "custom",
|
|
690
|
+
message: "an MCP server requires command or url",
|
|
691
|
+
});
|
|
692
|
+
}
|
|
693
|
+
if (!server.auth) {
|
|
694
|
+
return;
|
|
695
|
+
}
|
|
696
|
+
if (server.command || server.type === "stdio" || !server.url) {
|
|
697
|
+
context.addIssue({
|
|
698
|
+
code: "custom",
|
|
699
|
+
message: "OAuth is supported only for remote HTTP MCP servers",
|
|
700
|
+
});
|
|
701
|
+
return;
|
|
702
|
+
}
|
|
703
|
+
if (
|
|
704
|
+
Object.keys(server.headers).some(
|
|
705
|
+
(name) => name.toLowerCase() === "authorization"
|
|
706
|
+
)
|
|
707
|
+
) {
|
|
708
|
+
context.addIssue({
|
|
709
|
+
code: "custom",
|
|
710
|
+
message: "OAuth cannot be combined with a static Authorization header",
|
|
711
|
+
});
|
|
712
|
+
}
|
|
713
|
+
const url = new URL(server.url);
|
|
714
|
+
const loopback =
|
|
715
|
+
url.hostname === "localhost" ||
|
|
716
|
+
url.hostname.startsWith("127.") ||
|
|
717
|
+
url.hostname === "[::1]" ||
|
|
718
|
+
url.hostname === "::1";
|
|
719
|
+
if (url.username || url.password || url.hash) {
|
|
720
|
+
context.addIssue({
|
|
721
|
+
code: "custom",
|
|
722
|
+
message: "OAuth MCP URLs cannot contain credentials or fragments",
|
|
723
|
+
});
|
|
724
|
+
}
|
|
725
|
+
if (url.protocol !== "https:" && !(url.protocol === "http:" && loopback)) {
|
|
726
|
+
context.addIssue({
|
|
727
|
+
code: "custom",
|
|
728
|
+
message: "OAuth MCP URLs must use HTTPS except on loopback",
|
|
729
|
+
});
|
|
730
|
+
}
|
|
731
|
+
});
|
|
732
|
+
|
|
733
|
+
export type McpServerAuth = z.infer<typeof McpServerAuthSchema>;
|
|
734
|
+
export type McpServerDecl = z.infer<typeof McpServerDeclSchema>;
|
|
735
|
+
|
|
618
736
|
// ── PluginManifest ───────────────────────────────────────────────────────────
|
|
619
737
|
|
|
620
738
|
/**
|
|
@@ -627,203 +745,232 @@ export type EnginesReq = z.infer<typeof EnginesReqSchema>;
|
|
|
627
745
|
* - `runnables` may be empty for a "surface-only" plugin, but each entry must be
|
|
628
746
|
* a valid `RunnableMeta`
|
|
629
747
|
*/
|
|
630
|
-
export const PluginManifestSchema = z
|
|
631
|
-
|
|
632
|
-
|
|
748
|
+
export const PluginManifestSchema = z
|
|
749
|
+
.object({
|
|
750
|
+
/** Reverse-domain unique identifier (e.g. `"com.example.my-plugin"`). */
|
|
751
|
+
id: z.string().min(1, "id is required"),
|
|
633
752
|
|
|
634
|
-
|
|
635
|
-
|
|
753
|
+
/** Human-readable display name shown in the plugin store / launcher. */
|
|
754
|
+
name: z.string().min(1, "name is required"),
|
|
636
755
|
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
756
|
+
/**
|
|
757
|
+
* Semver version string (e.g. `"1.0.0"`). Core's loader rejects any manifest
|
|
758
|
+
* whose version is not valid semver; the regex here enforces the same rule at
|
|
759
|
+
* SDK-build time.
|
|
760
|
+
*/
|
|
761
|
+
version: z
|
|
762
|
+
.string()
|
|
763
|
+
.regex(
|
|
764
|
+
/^\d+\.\d+\.\d+(?:-[\w.]+)?(?:\+[\w.]+)?$/,
|
|
765
|
+
"version must be a valid semver string (e.g. 1.0.0)"
|
|
766
|
+
),
|
|
648
767
|
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
768
|
+
/**
|
|
769
|
+
* Lower-case hex `sha256(utf8_bytes(ui_code))` binding the plugin's bundled
|
|
770
|
+
* sandboxed-UI code to this manifest. `ryu pack` / `ryu publish` compute it and
|
|
771
|
+
* write it here BEFORE the manifest is signed, so the hash rides INSIDE the
|
|
772
|
+
* Gateway-signed surface while the `ui_code` blob rides OUTSIDE it as payload;
|
|
773
|
+
* Core's install path recomputes the hash over the fetched code and rejects a
|
|
774
|
+
* mismatch fail-closed. Absent for a manifest-only plugin (no bundled UI).
|
|
775
|
+
* Mirrors Core's `PluginManifest.ui_code_sha256`.
|
|
776
|
+
*/
|
|
777
|
+
ui_code_sha256: z.string().nullish(),
|
|
659
778
|
|
|
660
|
-
|
|
661
|
-
|
|
779
|
+
/** The Runnables this plugin bundles. */
|
|
780
|
+
runnables: z.array(RunnableMetaSchema).default([]),
|
|
662
781
|
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
782
|
+
/**
|
|
783
|
+
* Permission grants this plugin declares it needs (e.g. `"mcp:web_search"`).
|
|
784
|
+
* Declarations only — grant enforcement is the Gateway's responsibility.
|
|
785
|
+
*/
|
|
786
|
+
permission_grants: z.array(z.string()).default([]),
|
|
668
787
|
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
* Absent when the plugin has no Companion surface.
|
|
672
|
-
*/
|
|
673
|
-
companion: CompanionSurfaceSchema.optional(),
|
|
788
|
+
/** Remote or stdio MCP servers registered by this plugin. */
|
|
789
|
+
mcp_servers: z.record(z.string(), McpServerDeclSchema).optional(),
|
|
674
790
|
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
activation_events: z.array(z.string()).default([]),
|
|
791
|
+
/**
|
|
792
|
+
* Optional Companion surface (an in-desktop overlay or sidebar panel).
|
|
793
|
+
* Absent when the plugin has no Companion surface.
|
|
794
|
+
*/
|
|
795
|
+
companion: CompanionSurfaceSchema.optional(),
|
|
681
796
|
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
797
|
+
/**
|
|
798
|
+
* VS-Code-style activation events (`"*"`, `"onStartup"`, `"onChat"`,
|
|
799
|
+
* `"onCommand:<id>"`). Empty = eager. Turn-hook plugins are driven by their
|
|
800
|
+
* enabled flag, so `["*"]` is the usual value.
|
|
801
|
+
*/
|
|
802
|
+
activation_events: z.array(z.string()).default([]),
|
|
687
803
|
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
* Absent = **no dependencies**, the backward-compatible default. Kept
|
|
694
|
-
* `.optional()` (never defaulted) so a manifest that declares none serialises
|
|
695
|
-
* with no `requires` key at all, exactly like Core's
|
|
696
|
-
* `#[serde(skip_serializing_if = "Option::is_none")]`.
|
|
697
|
-
*/
|
|
698
|
-
requires: RequiresSchema.optional(),
|
|
804
|
+
/**
|
|
805
|
+
* Contribution points: server-side turn hooks + declarative UI widgets.
|
|
806
|
+
* Absent for a plugin that contributes nothing here.
|
|
807
|
+
*/
|
|
808
|
+
contributes: ContributesSchema.optional(),
|
|
699
809
|
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
810
|
+
/**
|
|
811
|
+
* **Plugin-to-plugin dependencies** — the other plugins this one needs. Core
|
|
812
|
+
* resolves them into a topological enable order (dependencies enable first;
|
|
813
|
+
* disabling one is refused while an enabled dependent needs it).
|
|
814
|
+
*
|
|
815
|
+
* Absent = **no dependencies**, the backward-compatible default. Kept
|
|
816
|
+
* `.optional()` (never defaulted) so a manifest that declares none serialises
|
|
817
|
+
* with no `requires` key at all, exactly like Core's
|
|
818
|
+
* `#[serde(skip_serializing_if = "Option::is_none")]`.
|
|
819
|
+
*/
|
|
820
|
+
requires: RequiresSchema.optional(),
|
|
708
821
|
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
*
|
|
718
|
-
* REGRESSION THIS FIXES: `engines` was absent from this schema entirely, and
|
|
719
|
-
* zod strips unlisted keys — so `ryu pack` silently dropped the whole block
|
|
720
|
-
* from every bundle it produced. A plugin could declare a Core floor, publish,
|
|
721
|
-
* and ship a bundle that declared none. Any new host floor MUST be added here
|
|
722
|
-
* as well as in the Rust contract, or it does not survive packing.
|
|
723
|
-
*/
|
|
724
|
-
engines: EnginesReqSchema.optional(),
|
|
822
|
+
/**
|
|
823
|
+
* Host surfaces this plugin runs on. **Empty or absent = runs on EVERY
|
|
824
|
+
* surface** — the backward-compatible default, which must never be read as
|
|
825
|
+
* "runs nowhere". Core filters only when the list is explicitly non-empty, and
|
|
826
|
+
* only at the read boundary (`GET /api/plugins`, keyed on `x-ryu-surface`), so
|
|
827
|
+
* an unsupported-target plugin stays installable and inspectable.
|
|
828
|
+
*/
|
|
829
|
+
targets: z.array(SurfaceSchema).default([]),
|
|
725
830
|
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
.
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
.
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
.optional(),
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
831
|
+
/**
|
|
832
|
+
* Host version floors — the semver requirement each surface must satisfy for
|
|
833
|
+
* this plugin to install. Mirrors Core's `EnginesReq`
|
|
834
|
+
* (`crates/core/kernel-contracts/src/manifest.rs`).
|
|
835
|
+
*
|
|
836
|
+
* `ryu` is the **Core** floor and the only required key (it is the legacy
|
|
837
|
+
* spelling; every manifest in the wild carries just that one). The rest are
|
|
838
|
+
* optional per-surface floors.
|
|
839
|
+
*
|
|
840
|
+
* REGRESSION THIS FIXES: `engines` was absent from this schema entirely, and
|
|
841
|
+
* zod strips unlisted keys — so `ryu pack` silently dropped the whole block
|
|
842
|
+
* from every bundle it produced. A plugin could declare a Core floor, publish,
|
|
843
|
+
* and ship a bundle that declared none. Any new host floor MUST be added here
|
|
844
|
+
* as well as in the Rust contract, or it does not survive packing.
|
|
845
|
+
*/
|
|
846
|
+
engines: EnginesReqSchema.optional(),
|
|
847
|
+
|
|
848
|
+
/**
|
|
849
|
+
* Optional per-item AFFILIATE terms: the commission paid to a referrer when a
|
|
850
|
+
* referred user buys this (paid) item. `value` is basis points for `percent`
|
|
851
|
+
* (2000 = 20%) or minor units (cents) for `flat`. Absent (or `enabled:false`)
|
|
852
|
+
* falls back to the seller org owner's default affiliate terms. This is the
|
|
853
|
+
* authoring surface for the marketplace publish body's `affiliate` field (the
|
|
854
|
+
* server re-validates it); it only takes effect on a paid item.
|
|
855
|
+
*/
|
|
856
|
+
affiliate: z
|
|
857
|
+
.object({
|
|
858
|
+
enabled: z.boolean().default(false),
|
|
859
|
+
rule: z
|
|
860
|
+
.object({
|
|
861
|
+
type: z.enum(["percent", "flat"]),
|
|
862
|
+
value: z.number().nonnegative(),
|
|
863
|
+
recurring: z.boolean().default(false),
|
|
864
|
+
durationMonths: z.number().int().positive().nullish(),
|
|
865
|
+
fundedBy: z.enum(["platform", "seller"]).default("platform"),
|
|
866
|
+
})
|
|
867
|
+
.optional(),
|
|
868
|
+
})
|
|
869
|
+
.optional(),
|
|
870
|
+
|
|
871
|
+
// ── Rich listing metadata (Phase 1.5) ──────────────────────────────────────
|
|
872
|
+
// Optional store-listing fields a plugin author declares so the marketplace
|
|
873
|
+
// detail dialog renders a richer App-Store-style preview. Field names align
|
|
874
|
+
// with the Claude `.claude-plugin/marketplace.json` plugin-entry standard where
|
|
875
|
+
// one exists (`author`, `homepage`, `keywords`, `category`, `license`); the
|
|
876
|
+
// rest are Ryu extensions. `ryu publish` forwards these FLAT into the publish
|
|
877
|
+
// body (not inside the signed manifest blob) so the control plane stores them.
|
|
878
|
+
// All optional + additive: a manifest omitting them still validates.
|
|
879
|
+
|
|
880
|
+
/** Longer plain/markdown description shown in the detail dialog. */
|
|
881
|
+
description: z.string().optional(),
|
|
882
|
+
/** Short one-line pitch shown under the name (Ryu extension). */
|
|
883
|
+
tagline: z.string().optional(),
|
|
884
|
+
/**
|
|
885
|
+
* Publisher identity. A bare string OR a Claude-style object; `ryu publish`
|
|
886
|
+
* resolves it to the display `developer` (`author.name` when an object).
|
|
887
|
+
*/
|
|
888
|
+
author: z
|
|
889
|
+
.union([
|
|
890
|
+
z.string(),
|
|
891
|
+
z.object({
|
|
892
|
+
name: z.string(),
|
|
893
|
+
email: z.string().optional(),
|
|
894
|
+
url: z.string().optional(),
|
|
895
|
+
}),
|
|
896
|
+
])
|
|
897
|
+
.optional(),
|
|
898
|
+
/** Public source repository URL (Claude/Codex `repository`). */
|
|
899
|
+
repository: z.string().url().optional(),
|
|
900
|
+
/** True when the provider operates outside the local Ryu runtime. */
|
|
901
|
+
external: z.boolean().optional(),
|
|
902
|
+
/** Project/marketing homepage — maps to the listing `website` (Claude field). */
|
|
903
|
+
homepage: z.string().optional(),
|
|
904
|
+
/** Free-text search keywords (Claude field). */
|
|
905
|
+
keywords: z.array(z.string()).optional(),
|
|
906
|
+
/** Stable Marketplace filter labels (Ryu extension). */
|
|
907
|
+
tags: z.array(z.string()).optional(),
|
|
908
|
+
/** Taxonomy category beyond the runnable kinds (Claude field). */
|
|
909
|
+
category: z.string().optional(),
|
|
910
|
+
/** SPDX-ish license identifier (Claude field). */
|
|
911
|
+
license: z.string().optional(),
|
|
912
|
+
/** Square logo/icon URL for the listing card + detail header. */
|
|
913
|
+
iconUrl: z.string().optional(),
|
|
914
|
+
/**
|
|
915
|
+
* Icon-primitive id for the listing card (Ryu extension): an Iconify/icons0
|
|
916
|
+
* `prefix:name`, a bare Hugeicons name, or a URL, resolved by the shared `Icon`
|
|
917
|
+
* primitive. A monochrome GLYPH masked with the current text colour — distinct
|
|
918
|
+
* from `iconUrl` (a raster logo). Falls back to `iconUrl` when omitted.
|
|
919
|
+
*/
|
|
920
|
+
icon: z.string().optional(),
|
|
921
|
+
/** Optional detail-page hero banner metadata forwarded to the marketplace. */
|
|
922
|
+
banner: z.record(z.string(), z.unknown()).optional(),
|
|
923
|
+
/**
|
|
924
|
+
* Dithered-gradient background for the card's icon square (Ryu extension),
|
|
925
|
+
* mirroring dither-kit's `DitherGradient` props. `from`/`to` are a palette-colour
|
|
926
|
+
* name (`green`, `blue`, `purple`, `pink`, `orange`, `red`, `grey`) or a hue
|
|
927
|
+
* number (0–360); `direction` is where `to` ends up. Renders behind the glyph in
|
|
928
|
+
* place of a flat `iconBackground`; the render layer validates + falls back.
|
|
929
|
+
*/
|
|
930
|
+
iconDither: z
|
|
931
|
+
.object({
|
|
932
|
+
from: z.union([z.string(), z.number()]),
|
|
933
|
+
to: z.union([z.string(), z.number()]).optional(),
|
|
934
|
+
direction: z.enum(["up", "down", "left", "right"]).optional(),
|
|
935
|
+
})
|
|
936
|
+
.optional(),
|
|
937
|
+
/** Ordered App-Store-style screenshot gallery URLs (Ryu extension). */
|
|
938
|
+
screenshots: z.array(z.string()).optional(),
|
|
939
|
+
/** Privacy policy URL surfaced on detail (Ryu extension). */
|
|
940
|
+
privacyPolicyUrl: z.string().optional(),
|
|
941
|
+
/** Terms-of-service URL surfaced on detail (Ryu extension). */
|
|
942
|
+
termsOfServiceUrl: z.string().optional(),
|
|
943
|
+
/**
|
|
944
|
+
* Human-readable capability strings (Ryu extension). When omitted the control
|
|
945
|
+
* plane derives a default from `permission_grants`, so declaring this is only
|
|
946
|
+
* needed to override the derived labels.
|
|
947
|
+
*/
|
|
948
|
+
capabilities: z.array(z.string()).optional(),
|
|
949
|
+
/** Example prompt chips shown on detail (Ryu extension). */
|
|
950
|
+
examplePrompts: z.array(z.string()).optional(),
|
|
951
|
+
/**
|
|
952
|
+
* Optional companion/config card (Ryu extension): a single setup step or an
|
|
953
|
+
* array of steps guiding the user through post-install configuration.
|
|
954
|
+
*/
|
|
955
|
+
setup: z.union([SetupStepSchema, z.array(SetupStepSchema)]).optional(),
|
|
956
|
+
})
|
|
957
|
+
.superRefine((manifest, context) => {
|
|
958
|
+
const hasOAuthServer = Object.values(manifest.mcp_servers ?? {}).some(
|
|
959
|
+
(server) => server.auth?.type === "oauth"
|
|
960
|
+
);
|
|
961
|
+
if (!hasOAuthServer) {
|
|
962
|
+
return;
|
|
963
|
+
}
|
|
964
|
+
for (const grant of ["mcp:server", "identity.read"] as const) {
|
|
965
|
+
if (!manifest.permission_grants.includes(grant)) {
|
|
966
|
+
context.addIssue({
|
|
967
|
+
code: "custom",
|
|
968
|
+
message: `OAuth MCP servers require the ${grant} permission grant`,
|
|
969
|
+
path: ["permission_grants"],
|
|
970
|
+
});
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
});
|
|
827
974
|
|
|
828
975
|
export type PluginManifest = z.infer<typeof PluginManifestSchema>;
|
|
829
976
|
|
package/src/mcp/index.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @ryuhq/sdk/mcp — MCP server + client surfaces.
|
|
3
|
+
*
|
|
4
|
+
* `McpServer` handles the MCP `initialize` handshake, `tools/list`,
|
|
5
|
+
* `tools/call`, and the content-block envelope over stdio; `listTools` /
|
|
6
|
+
* `callTool` drive a stdio MCP server. Re-exported here so consumers can
|
|
7
|
+
* import from `@ryuhq/sdk/mcp` (or the narrower `/mcp/server` and
|
|
8
|
+
* `/mcp/client` subpaths) as a single entry point.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export type { McpStdioCommand, McpTool } from "./client.ts";
|
|
12
|
+
export { callTool, listTools, MCP_PROTOCOL_VERSION } from "./client.ts";
|
|
13
|
+
export type {
|
|
14
|
+
JsonSchema,
|
|
15
|
+
PassthroughRegistration,
|
|
16
|
+
SdkRunnable,
|
|
17
|
+
} from "./server.ts";
|
|
18
|
+
export { McpServer, unwrapContent } from "./server.ts";
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @ryuhq/sdk/model — the gateway-mandatory model client.
|
|
3
|
+
*
|
|
4
|
+
* Every model call routes through the Ryu Gateway, never a direct provider.
|
|
5
|
+
* Re-exports the `ModelClient` / `defineModel` surface plus the gateway
|
|
6
|
+
* resolution + egress helpers, so consumers can import from
|
|
7
|
+
* `@ryuhq/sdk/model` as a single entry point.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export type {
|
|
11
|
+
ChatDelta,
|
|
12
|
+
ChatMessage,
|
|
13
|
+
ChatResult,
|
|
14
|
+
ModelClientOptions,
|
|
15
|
+
} from "./client.ts";
|
|
16
|
+
export { defineModel, ModelClient } from "./client.ts";
|
|
17
|
+
export {
|
|
18
|
+
assertAllowedEgressUrl,
|
|
19
|
+
DEFAULT_GATEWAY_URL,
|
|
20
|
+
resolveGatewayToken,
|
|
21
|
+
resolveGatewayUrl,
|
|
22
|
+
} from "./gateway.ts";
|