@ryuhq/sdk 0.2.0 → 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 +51 -2
- 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.d.cts +1 -1
- package/dist/agent-plugin.d.ts +1 -1
- package/dist/agent.cjs +7 -0
- package/dist/agent.d.cts +1 -1
- package/dist/agent.d.ts +1 -1
- package/dist/agent.js +2 -2
- package/dist/{app-DNaGmLVf.d.cts → app-B0Z9Ew_R.d.cts} +18 -6
- package/dist/{app-Bkw7LlCK.d.ts → app-C-BDJwfG.d.ts} +18 -6
- package/dist/builder.cjs +110 -11
- package/dist/builder.d.cts +7 -2
- package/dist/builder.d.ts +7 -2
- package/dist/builder.js +4 -3
- package/dist/chunk-4TPUZDTI.js +94 -0
- package/dist/{chunk-T5676WL2.js → chunk-BC3A7HMO.js} +1 -77
- package/dist/{chunk-IOLP5FFE.js → chunk-HLKJZAFK.js} +9 -2
- package/dist/{chunk-IEUQ3CDG.js → chunk-NZKVOSC2.js} +71 -5
- package/dist/chunk-QYFUNJOH.js +83 -0
- package/dist/{chunk-W3KPP4WN.js → chunk-SN2QBJUF.js} +20 -7
- package/dist/{chunk-ULSVL7EC.js → chunk-Z57QDDJR.js} +1 -1
- package/dist/{chunk-A3RGEPDG.js → chunk-ZTJWBRUL.js} +32 -0
- package/dist/cli.cjs +93 -8
- package/dist/cli.js +33 -8
- package/dist/index.cjs +297 -87
- package/dist/index.d.cts +10 -9
- package/dist/index.d.ts +10 -9
- package/dist/index.js +34 -19
- package/dist/manifest.cjs +76 -6
- package/dist/manifest.d.cts +157 -5
- package/dist/manifest.d.ts +157 -5
- package/dist/manifest.js +9 -1
- package/dist/mcp/server.d.cts +2 -1
- package/dist/mcp/server.d.ts +2 -1
- package/dist/runnable.cjs +279 -71
- package/dist/runnable.d.cts +6 -3
- package/dist/runnable.d.ts +6 -3
- package/dist/runnable.js +11 -5
- package/dist/{tool-DSx2bFx8.d.ts → tool-AjkdFvhE.d.ts} +54 -4
- package/dist/{tool-u-VR0fLF.d.cts → tool-CgzW92O_.d.cts} +54 -4
- package/package.json +12 -3
- package/src/agent-plugin.ts +1 -1
- package/src/builder.ts +9 -0
- package/src/cli-security.test.ts +109 -0
- package/src/cli.ts +43 -10
- package/src/contracts-lockstep.test.ts +16 -2
- package/src/exports-lockstep.test.ts +1 -0
- package/src/generated/plugin-manifest.ts +82 -5
- package/src/index.ts +18 -0
- package/src/manifest.fixtures.test.ts +9 -3
- package/src/manifest.test.ts +69 -0
- package/src/manifest.ts +137 -18
- package/src/mcp/server.ts +2 -1
- package/src/runnable/action.test.ts +128 -0
- package/src/runnable/action.ts +202 -0
- package/src/runnable/app.ts +45 -12
- package/src/runnable/index.ts +18 -3
- package/src/runnable/primitives.test.ts +34 -0
- package/src/runnable/primitives.ts +59 -0
- package/src/runnable/runnable-types.ts +3 -0
- package/src/runnable/tool.ts +35 -4
- package/src/runnable/turn-hook.ts +4 -2
- package/src/slash-command.test.ts +69 -0
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import {
|
|
2
|
+
defineTool,
|
|
3
|
+
inlineToolRunnable
|
|
4
|
+
} from "./chunk-QYFUNJOH.js";
|
|
5
|
+
import {
|
|
6
|
+
PluginManifestSchema
|
|
7
|
+
} from "./chunk-NZKVOSC2.js";
|
|
8
|
+
|
|
9
|
+
// src/runnable/action.ts
|
|
10
|
+
function deriveAnnotations(effect, annotations) {
|
|
11
|
+
const resolved = {
|
|
12
|
+
...annotations,
|
|
13
|
+
readOnlyHint: annotations?.readOnlyHint ?? effect === "read",
|
|
14
|
+
destructiveHint: annotations?.destructiveHint ?? effect === "mutate"
|
|
15
|
+
};
|
|
16
|
+
if (effect === "read" && resolved.destructiveHint) {
|
|
17
|
+
throw new Error(
|
|
18
|
+
"[ryu-sdk] read actions cannot set annotations.destructiveHint=true"
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
if (effect === "mutate" && resolved.readOnlyHint) {
|
|
22
|
+
throw new Error(
|
|
23
|
+
"[ryu-sdk] mutate actions cannot set annotations.readOnlyHint=true"
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
return resolved;
|
|
27
|
+
}
|
|
28
|
+
function actionManifestEntry(action) {
|
|
29
|
+
const options = {
|
|
30
|
+
action: true,
|
|
31
|
+
annotations: action.annotations,
|
|
32
|
+
description: action.description,
|
|
33
|
+
needsApproval: action.needsApproval,
|
|
34
|
+
...action.outputSchema ? { outputSchema: action.outputSchema } : {}
|
|
35
|
+
};
|
|
36
|
+
return inlineToolRunnable(action, options);
|
|
37
|
+
}
|
|
38
|
+
function actionToManifest(action, options) {
|
|
39
|
+
const grants = [.../* @__PURE__ */ new Set([...options.grants ?? [], "tool:execute"])];
|
|
40
|
+
const raw = {
|
|
41
|
+
id: options.id,
|
|
42
|
+
name: options.name ?? action.name,
|
|
43
|
+
version: options.version,
|
|
44
|
+
runnables: [actionManifestEntry(action)],
|
|
45
|
+
permission_grants: grants,
|
|
46
|
+
activation_events: [...options.activationEvents ?? ["*"]],
|
|
47
|
+
targets: [...options.targets ?? []]
|
|
48
|
+
};
|
|
49
|
+
const result = PluginManifestSchema.safeParse(raw);
|
|
50
|
+
if (!result.success) {
|
|
51
|
+
const first = result.error.issues[0];
|
|
52
|
+
const field = first?.path.join(".") ?? "unknown";
|
|
53
|
+
const message = first?.message ?? "validation failed";
|
|
54
|
+
throw new Error(
|
|
55
|
+
`[ryu-sdk] action manifest validation failed at '${field}': ${message}`
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
return result.data;
|
|
59
|
+
}
|
|
60
|
+
function defineAction(options) {
|
|
61
|
+
const annotations = deriveAnnotations(options.effect, options.annotations);
|
|
62
|
+
const tool = defineTool({
|
|
63
|
+
description: options.description,
|
|
64
|
+
id: options.id,
|
|
65
|
+
name: options.name,
|
|
66
|
+
run: options.run,
|
|
67
|
+
schema: options.schema
|
|
68
|
+
});
|
|
69
|
+
const action = {
|
|
70
|
+
...tool,
|
|
71
|
+
action: true,
|
|
72
|
+
annotations,
|
|
73
|
+
description: options.description,
|
|
74
|
+
effect: options.effect,
|
|
75
|
+
needsApproval: options.needsApproval ?? false,
|
|
76
|
+
...options.outputSchema ? { outputSchema: options.outputSchema } : {},
|
|
77
|
+
toManifest(manifestOptions) {
|
|
78
|
+
return actionToManifest(action, manifestOptions);
|
|
79
|
+
},
|
|
80
|
+
toMcpTool(context) {
|
|
81
|
+
return {
|
|
82
|
+
name: action.id,
|
|
83
|
+
description: action.description,
|
|
84
|
+
inputSchema: action.schema,
|
|
85
|
+
run: (input) => action.run(input, context)
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
return action;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export {
|
|
93
|
+
defineAction
|
|
94
|
+
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
PluginManifestSchema
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-NZKVOSC2.js";
|
|
4
4
|
|
|
5
5
|
// src/runnable/agent.ts
|
|
6
6
|
var CAPABILITY_SLOTS = ["rag", "memory", "tts", "stt"];
|
|
@@ -146,80 +146,6 @@ function defineSkill(options) {
|
|
|
146
146
|
};
|
|
147
147
|
}
|
|
148
148
|
|
|
149
|
-
// src/runnable/tool.ts
|
|
150
|
-
function validateInput(input, schema, toolId) {
|
|
151
|
-
if (typeof input !== "object" || input === null || Array.isArray(input)) {
|
|
152
|
-
throw new Error(
|
|
153
|
-
`[ryu-sdk] Tool "${toolId}" input must be an object, got ${JSON.stringify(input)}`
|
|
154
|
-
);
|
|
155
|
-
}
|
|
156
|
-
const record = input;
|
|
157
|
-
for (const requiredKey of schema.required ?? []) {
|
|
158
|
-
if (!(requiredKey in record)) {
|
|
159
|
-
throw new Error(
|
|
160
|
-
`[ryu-sdk] Tool "${toolId}" input missing required field "${requiredKey}"`
|
|
161
|
-
);
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
for (const [key, prop] of Object.entries(schema.properties)) {
|
|
165
|
-
if (!(key in record)) {
|
|
166
|
-
continue;
|
|
167
|
-
}
|
|
168
|
-
const value = record[key];
|
|
169
|
-
if (!checkType(value, prop.type)) {
|
|
170
|
-
throw new Error(
|
|
171
|
-
`[ryu-sdk] Tool "${toolId}" input field "${key}" expected type "${prop.type}", got ${JSON.stringify(value)}`
|
|
172
|
-
);
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
function checkType(value, type) {
|
|
177
|
-
switch (type) {
|
|
178
|
-
case "string":
|
|
179
|
-
return typeof value === "string";
|
|
180
|
-
case "number":
|
|
181
|
-
case "integer":
|
|
182
|
-
return typeof value === "number";
|
|
183
|
-
case "boolean":
|
|
184
|
-
return typeof value === "boolean";
|
|
185
|
-
case "array":
|
|
186
|
-
return Array.isArray(value);
|
|
187
|
-
case "object":
|
|
188
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
189
|
-
default:
|
|
190
|
-
return false;
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
function defineTool(options) {
|
|
194
|
-
const { id, name, schema, run } = options;
|
|
195
|
-
const code = `return await (${run.toString()})(input, host);`;
|
|
196
|
-
return {
|
|
197
|
-
id,
|
|
198
|
-
name,
|
|
199
|
-
kind: "tool",
|
|
200
|
-
schema,
|
|
201
|
-
code,
|
|
202
|
-
run(input, ctx) {
|
|
203
|
-
validateInput(input, schema, id);
|
|
204
|
-
return run(input, ctx);
|
|
205
|
-
}
|
|
206
|
-
};
|
|
207
|
-
}
|
|
208
|
-
function inlineToolRunnable(tool, options) {
|
|
209
|
-
return {
|
|
210
|
-
id: tool.id,
|
|
211
|
-
name: tool.name,
|
|
212
|
-
kind: "tool",
|
|
213
|
-
config: {
|
|
214
|
-
slug: tool.id,
|
|
215
|
-
backend: "inline_deno",
|
|
216
|
-
code: tool.code,
|
|
217
|
-
input_schema: tool.schema,
|
|
218
|
-
...options?.description ? { description: options.description } : {}
|
|
219
|
-
}
|
|
220
|
-
};
|
|
221
|
-
}
|
|
222
|
-
|
|
223
149
|
// src/runnable/workflow.ts
|
|
224
150
|
function defineWorkflow(options) {
|
|
225
151
|
const { id, name, run } = options;
|
|
@@ -234,7 +160,5 @@ function defineWorkflow(options) {
|
|
|
234
160
|
export {
|
|
235
161
|
defineAgent,
|
|
236
162
|
defineSkill,
|
|
237
|
-
defineTool,
|
|
238
|
-
inlineToolRunnable,
|
|
239
163
|
defineWorkflow
|
|
240
164
|
};
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
defineApp
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-SN2QBJUF.js";
|
|
4
4
|
import {
|
|
5
5
|
PluginManifestSchema,
|
|
6
6
|
RunnableMetaSchema
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-NZKVOSC2.js";
|
|
8
8
|
|
|
9
9
|
// src/builder.ts
|
|
10
10
|
var RunnableBuilder = class {
|
|
@@ -218,6 +218,7 @@ var AppBuilder = class {
|
|
|
218
218
|
_dependencies = [];
|
|
219
219
|
_requiredCapabilities = [];
|
|
220
220
|
_requiredGrants = [];
|
|
221
|
+
_slashCommands = [];
|
|
221
222
|
_targets = [];
|
|
222
223
|
/** Set the reverse-domain app id (e.g. `"com.example.checklist"`). */
|
|
223
224
|
id(value) {
|
|
@@ -274,6 +275,11 @@ var AppBuilder = class {
|
|
|
274
275
|
this._tools.push(spec);
|
|
275
276
|
return this;
|
|
276
277
|
}
|
|
278
|
+
/** Add a slash command and its optional sequential argument choices. */
|
|
279
|
+
slashCommand(command) {
|
|
280
|
+
this._slashCommands.push(command);
|
|
281
|
+
return this;
|
|
282
|
+
}
|
|
277
283
|
/**
|
|
278
284
|
* Declare a **plugin-to-plugin dependency** (auto-enabled, in dependency order,
|
|
279
285
|
* before this app). `minVersion` is a MINIMUM (`"1.2.0"` = `">=1.2.0"`).
|
|
@@ -321,6 +327,7 @@ var AppBuilder = class {
|
|
|
321
327
|
uiEntry: this._uiEntry,
|
|
322
328
|
tools: this._tools,
|
|
323
329
|
grants: this._grants,
|
|
330
|
+
slashCommands: this._slashCommands,
|
|
324
331
|
...this._server ? { server: this._server } : {},
|
|
325
332
|
...this._displayMode ? { displayMode: this._displayMode } : {},
|
|
326
333
|
...this._mime ? { mime: this._mime } : {},
|
|
@@ -162,10 +162,18 @@ var ChatWidgetTemplateSchema = z.object({
|
|
|
162
162
|
}).superRefine((value, ctx) => {
|
|
163
163
|
const count = Number(Boolean(value.backing.tool_id)) + Number(Boolean(value.backing.view_id));
|
|
164
164
|
if (count !== 1 && value.availability === "available") {
|
|
165
|
-
ctx.addIssue({
|
|
165
|
+
ctx.addIssue({
|
|
166
|
+
code: "custom",
|
|
167
|
+
path: ["backing"],
|
|
168
|
+
message: "available templates need exactly one backing tool_id or view_id"
|
|
169
|
+
});
|
|
166
170
|
}
|
|
167
171
|
if (count > 1) {
|
|
168
|
-
ctx.addIssue({
|
|
172
|
+
ctx.addIssue({
|
|
173
|
+
code: "custom",
|
|
174
|
+
path: ["backing"],
|
|
175
|
+
message: "backing must declare at most one of tool_id or view_id"
|
|
176
|
+
});
|
|
169
177
|
}
|
|
170
178
|
});
|
|
171
179
|
var ToolAppConfigSchema = z.object({
|
|
@@ -188,6 +196,33 @@ var ToolAppConfigSchema = z.object({
|
|
|
188
196
|
/** Optional status label shown when the render tool finishes. */
|
|
189
197
|
invoked: z.string().optional()
|
|
190
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();
|
|
191
226
|
var ContributesSchema = z.object({
|
|
192
227
|
turn_hooks: z.array(TurnHookContributionSchema).default([]),
|
|
193
228
|
/** App events this plugin EMITS — the provider half of the hook system, whose
|
|
@@ -202,7 +237,7 @@ var ContributesSchema = z.object({
|
|
|
202
237
|
* feature declaration before signing. */
|
|
203
238
|
chat_features: z.array(z.record(z.string(), z.unknown())).default([]),
|
|
204
239
|
settings_tabs: z.array(z.record(z.string(), z.unknown())).default([]),
|
|
205
|
-
slash_commands: z.array(
|
|
240
|
+
slash_commands: z.array(SlashCommandContributionSchema).default([]),
|
|
206
241
|
/** App widgets (Ryu Apps). Each binds a render tool id to its
|
|
207
242
|
* `ui://widget/<slug>.html` template. Mirrors the Rust-side
|
|
208
243
|
* `Contributes.widgets` field, without which the CLI's zod parse would strip
|
|
@@ -269,7 +304,10 @@ var ContributesSchema = z.object({
|
|
|
269
304
|
output_styles: z.array(OutputStyleContributionSchema).default([]),
|
|
270
305
|
/** Per-message actions contributed by an enabled plugin. Kept as loose records
|
|
271
306
|
* so renderer-specific `kind`/`args` payloads survive `ryu pack` unchanged. */
|
|
272
|
-
message_actions: z.array(z.record(z.string(), z.unknown())).default([])
|
|
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([])
|
|
273
311
|
});
|
|
274
312
|
var SetupStepSchema = z.object({
|
|
275
313
|
/** Card heading (e.g. the companion app name). */
|
|
@@ -419,6 +457,9 @@ var PluginManifestSchema = z.object({
|
|
|
419
457
|
/^\d+\.\d+\.\d+(?:-[\w.]+)?(?:\+[\w.]+)?$/,
|
|
420
458
|
"version must be a valid semver string (e.g. 1.0.0)"
|
|
421
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(),
|
|
422
463
|
/**
|
|
423
464
|
* Lower-case hex `sha256(utf8_bytes(ui_code))` binding the plugin's bundled
|
|
424
465
|
* sandboxed-UI code to this manifest. `ryu pack` / `ryu publish` compute it and
|
|
@@ -436,8 +477,26 @@ var PluginManifestSchema = z.object({
|
|
|
436
477
|
* Declarations only — grant enforcement is the Gateway's responsibility.
|
|
437
478
|
*/
|
|
438
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(),
|
|
439
495
|
/** Remote or stdio MCP servers registered by this plugin. */
|
|
440
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(),
|
|
441
500
|
/**
|
|
442
501
|
* Optional Companion surface (an in-desktop overlay or sidebar panel).
|
|
443
502
|
* Absent when the plugin has no Companion surface.
|
|
@@ -473,6 +532,9 @@ var PluginManifestSchema = z.object({
|
|
|
473
532
|
* an unsupported-target plugin stays installable and inspectable.
|
|
474
533
|
*/
|
|
475
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(),
|
|
476
538
|
/**
|
|
477
539
|
* Host version floors — the semver requirement each surface must satisfy for
|
|
478
540
|
* this plugin to install. Mirrors Core's `EnginesReq`
|
|
@@ -587,7 +649,7 @@ var PluginManifestSchema = z.object({
|
|
|
587
649
|
* array of steps guiding the user through post-install configuration.
|
|
588
650
|
*/
|
|
589
651
|
setup: z.union([SetupStepSchema, z.array(SetupStepSchema)]).optional()
|
|
590
|
-
}).superRefine((manifest, context) => {
|
|
652
|
+
}).passthrough().superRefine((manifest, context) => {
|
|
591
653
|
const hasOAuthServer = Object.values(manifest.mcp_servers ?? {}).some(
|
|
592
654
|
(server) => server.auth?.type === "oauth"
|
|
593
655
|
);
|
|
@@ -626,6 +688,10 @@ export {
|
|
|
626
688
|
WidgetContributionSchema,
|
|
627
689
|
ChatWidgetTemplateSchema,
|
|
628
690
|
ToolAppConfigSchema,
|
|
691
|
+
SlashCommandOptionSchema,
|
|
692
|
+
SlashCommandCustomOptionSchema,
|
|
693
|
+
SlashCommandArgumentSchema,
|
|
694
|
+
SlashCommandContributionSchema,
|
|
629
695
|
ContributesSchema,
|
|
630
696
|
SetupStepSchema,
|
|
631
697
|
AppDependencySchema,
|
|
@@ -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
|
+
};
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
import {
|
|
2
|
+
inlineToolRunnable
|
|
3
|
+
} from "./chunk-QYFUNJOH.js";
|
|
1
4
|
import {
|
|
2
5
|
PluginManifestSchema
|
|
3
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-NZKVOSC2.js";
|
|
4
7
|
|
|
5
8
|
// src/runnable/app.ts
|
|
6
9
|
var DEFAULT_APP_WIDGET_MIME = "text/html+skybridge";
|
|
@@ -13,6 +16,13 @@ function withWidgetRenderGrant(grants, widgets) {
|
|
|
13
16
|
}
|
|
14
17
|
return out;
|
|
15
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
|
+
}
|
|
16
26
|
function appToolId(server, name) {
|
|
17
27
|
return `${server}.${name}`;
|
|
18
28
|
}
|
|
@@ -27,7 +37,9 @@ function defineApp(options) {
|
|
|
27
37
|
for (const spec of options.tools) {
|
|
28
38
|
const isRender = spec.accessible !== true;
|
|
29
39
|
const id = appToolId(server, spec.name);
|
|
40
|
+
const executable = spec.runnable ? inlineToolRunnable(spec.runnable) : void 0;
|
|
30
41
|
const config = {
|
|
42
|
+
...executable?.config ?? {},
|
|
31
43
|
slug: id,
|
|
32
44
|
description: spec.description,
|
|
33
45
|
widget: isRender,
|
|
@@ -61,7 +73,7 @@ function defineApp(options) {
|
|
|
61
73
|
hook_events: [],
|
|
62
74
|
composer_controls: [],
|
|
63
75
|
settings_tabs: [],
|
|
64
|
-
slash_commands: [],
|
|
76
|
+
slash_commands: options.slashCommands ?? [],
|
|
65
77
|
sidebar_sections: [],
|
|
66
78
|
sidebar_buttons: [],
|
|
67
79
|
dock_panels: [],
|
|
@@ -79,6 +91,7 @@ function defineApp(options) {
|
|
|
79
91
|
pi_extensions: [],
|
|
80
92
|
output_styles: [],
|
|
81
93
|
message_actions: [],
|
|
94
|
+
selection_actions: [],
|
|
82
95
|
widgets
|
|
83
96
|
};
|
|
84
97
|
const raw = {
|
|
@@ -92,15 +105,15 @@ function defineApp(options) {
|
|
|
92
105
|
// Core gates widget promotion on declared-AND-enabled-AND-granted, and a
|
|
93
106
|
// missing grant fails as `DeniedNoGrant` — which is an `info!` log and
|
|
94
107
|
// nothing else. The widget silently renders as plain text, with no error
|
|
95
|
-
// in the UI and nothing pointing at the manifest.
|
|
96
|
-
// through `defineApp` hit that, because the only fix was a grant string
|
|
97
|
-
// the templates never mention and the builder never added; the one
|
|
98
|
-
// working example on disk hand-writes it.
|
|
108
|
+
// in the UI and nothing pointing at the manifest.
|
|
99
109
|
//
|
|
100
110
|
// Added only when there is a widget to render, and unioned rather than
|
|
101
111
|
// overwritten so an author's own `grants` list survives and re-declaring
|
|
102
112
|
// it is not an error.
|
|
103
|
-
permission_grants:
|
|
113
|
+
permission_grants: withToolExecuteGrant(
|
|
114
|
+
withWidgetRenderGrant(options.grants ?? [], widgets),
|
|
115
|
+
options.tools
|
|
116
|
+
),
|
|
104
117
|
activation_events: options.activationEvents ?? ["*"],
|
|
105
118
|
contributes,
|
|
106
119
|
// `targets: []` means EVERY surface, so an app that declares none is
|
|
@@ -154,6 +154,31 @@ var PRIMITIVE_BINDINGS = {
|
|
|
154
154
|
method: "background.stop",
|
|
155
155
|
grant: "background:control"
|
|
156
156
|
},
|
|
157
|
+
"storage.get": {
|
|
158
|
+
transport: "bridge",
|
|
159
|
+
method: "storage.get",
|
|
160
|
+
grant: "storage:kv"
|
|
161
|
+
},
|
|
162
|
+
"storage.set": {
|
|
163
|
+
transport: "bridge",
|
|
164
|
+
method: "storage.set",
|
|
165
|
+
grant: "storage:kv"
|
|
166
|
+
},
|
|
167
|
+
"storage.delete": {
|
|
168
|
+
transport: "bridge",
|
|
169
|
+
method: "storage.delete",
|
|
170
|
+
grant: "storage:kv"
|
|
171
|
+
},
|
|
172
|
+
"storage.keys": {
|
|
173
|
+
transport: "bridge",
|
|
174
|
+
method: "storage.keys",
|
|
175
|
+
grant: "storage:kv"
|
|
176
|
+
},
|
|
177
|
+
"storage.compareAndSet": {
|
|
178
|
+
transport: "bridge",
|
|
179
|
+
method: "storage.compareAndSet",
|
|
180
|
+
grant: "storage:kv"
|
|
181
|
+
},
|
|
157
182
|
// Host-direct media data-path (the host holds the node token; returns data: URLs).
|
|
158
183
|
"image.generate": {
|
|
159
184
|
transport: "direct",
|
|
@@ -196,6 +221,13 @@ function createPrimitives(transport) {
|
|
|
196
221
|
process_id: input.processId
|
|
197
222
|
})
|
|
198
223
|
},
|
|
224
|
+
storage: {
|
|
225
|
+
get: (input) => transport.bridge("storage.get", input),
|
|
226
|
+
set: (input) => transport.bridge("storage.set", input).then(() => void 0),
|
|
227
|
+
delete: (input) => transport.bridge("storage.delete", input).then(() => void 0),
|
|
228
|
+
keys: (input) => transport.bridge("storage.keys", input ?? {}).then((value) => Array.isArray(value) ? value : []),
|
|
229
|
+
compareAndSet: (input) => transport.bridge("storage.compareAndSet", input)
|
|
230
|
+
},
|
|
199
231
|
rag: {
|
|
200
232
|
retrieve: (input) => brokerCall(transport, "rag", "retrieve", input),
|
|
201
233
|
embed: (input) => brokerCall(transport, "rag", "embed", input),
|