@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
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
// src/mcp/client.ts
|
|
2
|
+
import { spawn } from "child_process";
|
|
3
|
+
import { createInterface } from "readline";
|
|
4
|
+
var MCP_PROTOCOL_VERSION = "2024-11-05";
|
|
5
|
+
var RPC_TIMEOUT_MS = 6e4;
|
|
6
|
+
var McpConnection = class _McpConnection {
|
|
7
|
+
proc;
|
|
8
|
+
nextId = 1;
|
|
9
|
+
closed = false;
|
|
10
|
+
waiters = [];
|
|
11
|
+
constructor(proc) {
|
|
12
|
+
this.proc = proc;
|
|
13
|
+
}
|
|
14
|
+
/** Spawn the server and complete the MCP `initialize` handshake. */
|
|
15
|
+
static async connect(cmd) {
|
|
16
|
+
const env = { ...process.env, ...cmd.env ?? {} };
|
|
17
|
+
const proc = spawn(cmd.command, cmd.args ?? [], {
|
|
18
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
19
|
+
env
|
|
20
|
+
});
|
|
21
|
+
if (!(proc.stdin && proc.stdout)) {
|
|
22
|
+
proc.kill();
|
|
23
|
+
throw new Error(`MCP server '${cmd.command}' stdin/stdout unavailable`);
|
|
24
|
+
}
|
|
25
|
+
if (proc.stderr) {
|
|
26
|
+
proc.stderr.on("data", (chunk) => {
|
|
27
|
+
process.stderr.write(`[mcp-server] ${chunk.toString()}`);
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
const conn = new _McpConnection(proc);
|
|
31
|
+
const rl = createInterface({
|
|
32
|
+
input: proc.stdout,
|
|
33
|
+
crlfDelay: Number.POSITIVE_INFINITY
|
|
34
|
+
});
|
|
35
|
+
rl.on("line", (rawLine) => {
|
|
36
|
+
const trimmed = rawLine.trim();
|
|
37
|
+
if (!trimmed) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
let parsed;
|
|
41
|
+
try {
|
|
42
|
+
parsed = JSON.parse(trimmed);
|
|
43
|
+
} catch {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
if (parsed.id === void 0 || parsed.id === null) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
const idx = conn.waiters.findIndex((w) => w.id === parsed.id);
|
|
50
|
+
if (idx === -1) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
const [waiter] = conn.waiters.splice(idx, 1);
|
|
54
|
+
if (!waiter) {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
clearTimeout(waiter.timer);
|
|
58
|
+
if (parsed.error) {
|
|
59
|
+
waiter.reject(new Error(`MCP error: ${JSON.stringify(parsed.error)}`));
|
|
60
|
+
} else {
|
|
61
|
+
waiter.resolve(parsed.result ?? null);
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
rl.on("close", () => {
|
|
65
|
+
for (const waiter of conn.waiters.splice(0)) {
|
|
66
|
+
clearTimeout(waiter.timer);
|
|
67
|
+
waiter.reject(new Error("MCP server closed the connection"));
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
await conn.request("initialize", {
|
|
71
|
+
protocolVersion: MCP_PROTOCOL_VERSION,
|
|
72
|
+
capabilities: {},
|
|
73
|
+
clientInfo: { name: "ryu-sdk", version: "0.0.1" }
|
|
74
|
+
});
|
|
75
|
+
conn.notify("notifications/initialized", {});
|
|
76
|
+
return conn;
|
|
77
|
+
}
|
|
78
|
+
/** Send a JSON-RPC request and return the `result` field. */
|
|
79
|
+
request(method, params) {
|
|
80
|
+
const id = this.nextId++;
|
|
81
|
+
const frame = JSON.stringify({
|
|
82
|
+
jsonrpc: "2.0",
|
|
83
|
+
id,
|
|
84
|
+
method,
|
|
85
|
+
params
|
|
86
|
+
});
|
|
87
|
+
this.write(frame);
|
|
88
|
+
return new Promise((resolve, reject) => {
|
|
89
|
+
const timer = setTimeout(
|
|
90
|
+
() => reject(new Error(`MCP request '${method}' timed out`)),
|
|
91
|
+
RPC_TIMEOUT_MS
|
|
92
|
+
);
|
|
93
|
+
this.waiters.push({ id, resolve, reject, timer });
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
/** Send a JSON-RPC notification (no response expected). */
|
|
97
|
+
notify(method, params) {
|
|
98
|
+
const frame = JSON.stringify({ jsonrpc: "2.0", method, params });
|
|
99
|
+
this.write(frame);
|
|
100
|
+
}
|
|
101
|
+
write(frame) {
|
|
102
|
+
if (this.closed) {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
this.proc.stdin?.write(`${frame}
|
|
106
|
+
`);
|
|
107
|
+
}
|
|
108
|
+
/** Graceful shutdown: close stdin, then kill. */
|
|
109
|
+
async shutdown() {
|
|
110
|
+
this.closed = true;
|
|
111
|
+
this.proc.stdin?.end();
|
|
112
|
+
await new Promise((resolve) => {
|
|
113
|
+
this.proc.once("exit", () => resolve());
|
|
114
|
+
this.proc.kill();
|
|
115
|
+
setTimeout(resolve, 500);
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
async function listTools(cmd) {
|
|
120
|
+
const conn = await McpConnection.connect(cmd);
|
|
121
|
+
let result;
|
|
122
|
+
try {
|
|
123
|
+
result = await conn.request("tools/list", {});
|
|
124
|
+
} finally {
|
|
125
|
+
await conn.shutdown();
|
|
126
|
+
}
|
|
127
|
+
const tools = result?.tools ?? [];
|
|
128
|
+
return tools.map((t) => {
|
|
129
|
+
const tool = t;
|
|
130
|
+
const name = tool.name;
|
|
131
|
+
if (typeof name !== "string") {
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
return {
|
|
135
|
+
name,
|
|
136
|
+
description: typeof tool.description === "string" ? tool.description : void 0,
|
|
137
|
+
inputSchema: tool.inputSchema
|
|
138
|
+
};
|
|
139
|
+
}).filter((t) => t !== null);
|
|
140
|
+
}
|
|
141
|
+
async function callTool(cmd, tool, args) {
|
|
142
|
+
const conn = await McpConnection.connect(cmd);
|
|
143
|
+
try {
|
|
144
|
+
return await conn.request("tools/call", { name: tool, arguments: args });
|
|
145
|
+
} finally {
|
|
146
|
+
await conn.shutdown();
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export {
|
|
151
|
+
MCP_PROTOCOL_VERSION,
|
|
152
|
+
listTools,
|
|
153
|
+
callTool
|
|
154
|
+
};
|
|
@@ -1,231 +1,15 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createPrimitives,
|
|
3
|
+
httpPrimitiveTransport
|
|
4
|
+
} from "./chunk-ZTJWBRUL.js";
|
|
5
|
+
import {
|
|
6
|
+
defineModel
|
|
7
|
+
} from "./chunk-HACAGK65.js";
|
|
1
8
|
import {
|
|
2
9
|
assertAllowedEgressUrl,
|
|
3
|
-
defineModel,
|
|
4
10
|
resolveGatewayToken,
|
|
5
11
|
resolveGatewayUrl
|
|
6
|
-
} from "./chunk-
|
|
7
|
-
|
|
8
|
-
// src/runnable/primitives.ts
|
|
9
|
-
function dataUrlToBytes(dataUrl) {
|
|
10
|
-
const match = /^data:([^;,]*)(;base64)?,([\s\S]*)$/.exec(dataUrl);
|
|
11
|
-
if (!match) {
|
|
12
|
-
throw new Error(
|
|
13
|
-
"stt.transcribe expects an `audio` value that is a data: URL (data:<mime>;base64,<data>)"
|
|
14
|
-
);
|
|
15
|
-
}
|
|
16
|
-
const mediaType = match[1] || "application/octet-stream";
|
|
17
|
-
const isBase64 = Boolean(match[2]);
|
|
18
|
-
const payload = match[3] ?? "";
|
|
19
|
-
if (isBase64) {
|
|
20
|
-
const binary = atob(payload);
|
|
21
|
-
const bytes = new Uint8Array(binary.length);
|
|
22
|
-
for (let i = 0; i < binary.length; i++) {
|
|
23
|
-
bytes[i] = binary.charCodeAt(i);
|
|
24
|
-
}
|
|
25
|
-
return { bytes, mediaType };
|
|
26
|
-
}
|
|
27
|
-
return {
|
|
28
|
-
bytes: new TextEncoder().encode(decodeURIComponent(payload)),
|
|
29
|
-
mediaType
|
|
30
|
-
};
|
|
31
|
-
}
|
|
32
|
-
function bytesToDataUrl(bytes, mediaType) {
|
|
33
|
-
let binary = "";
|
|
34
|
-
const chunk = 32768;
|
|
35
|
-
for (let i = 0; i < bytes.length; i += chunk) {
|
|
36
|
-
binary += String.fromCharCode(...bytes.subarray(i, i + chunk));
|
|
37
|
-
}
|
|
38
|
-
return `data:${mediaType};base64,${btoa(binary)}`;
|
|
39
|
-
}
|
|
40
|
-
function httpPrimitiveTransport(options) {
|
|
41
|
-
const base = options.nodeUrl.replace(/\/+$/, "");
|
|
42
|
-
assertAllowedEgressUrl(base);
|
|
43
|
-
const doFetch = options.fetchImpl ?? fetch;
|
|
44
|
-
const authHeader = () => options.token ? { authorization: `Bearer ${options.token}` } : {};
|
|
45
|
-
const failDetail = async (path, res) => {
|
|
46
|
-
const detail = await res.text().catch(() => "");
|
|
47
|
-
return new Error(
|
|
48
|
-
`Ryu primitive call ${path} failed: ${res.status} ${res.statusText}${detail ? ` \u2014 ${detail}` : ""}`
|
|
49
|
-
);
|
|
50
|
-
};
|
|
51
|
-
const post = async (path, body) => {
|
|
52
|
-
const res = await doFetch(`${base}${path}`, {
|
|
53
|
-
method: "POST",
|
|
54
|
-
headers: { "content-type": "application/json", ...authHeader() },
|
|
55
|
-
body: JSON.stringify(body ?? {})
|
|
56
|
-
});
|
|
57
|
-
if (!res.ok) {
|
|
58
|
-
throw await failDetail(path, res);
|
|
59
|
-
}
|
|
60
|
-
const text = await res.text();
|
|
61
|
-
return text ? JSON.parse(text) : void 0;
|
|
62
|
-
};
|
|
63
|
-
const transcribeDirect = async (body) => {
|
|
64
|
-
const input = body ?? {};
|
|
65
|
-
if (!input.audio) {
|
|
66
|
-
throw new Error(
|
|
67
|
-
"stt.transcribe requires an `audio` data: URL (the recorded audio)"
|
|
68
|
-
);
|
|
69
|
-
}
|
|
70
|
-
const { bytes, mediaType } = dataUrlToBytes(input.audio);
|
|
71
|
-
const form = new FormData();
|
|
72
|
-
form.append(
|
|
73
|
-
"file",
|
|
74
|
-
new Blob([bytes], { type: mediaType || "audio/wav" }),
|
|
75
|
-
input.filename ?? "recording.wav"
|
|
76
|
-
);
|
|
77
|
-
const res = await doFetch(`${base}/api/voice/transcribe`, {
|
|
78
|
-
method: "POST",
|
|
79
|
-
headers: authHeader(),
|
|
80
|
-
body: form
|
|
81
|
-
});
|
|
82
|
-
if (!res.ok) {
|
|
83
|
-
throw await failDetail("/api/voice/transcribe", res);
|
|
84
|
-
}
|
|
85
|
-
const parsed = await res.json();
|
|
86
|
-
return (parsed.text ?? "").trim();
|
|
87
|
-
};
|
|
88
|
-
const speakDirect = async (body) => {
|
|
89
|
-
const res = await doFetch(`${base}/api/voice/speak`, {
|
|
90
|
-
method: "POST",
|
|
91
|
-
headers: { "content-type": "application/json", ...authHeader() },
|
|
92
|
-
body: JSON.stringify(body ?? {})
|
|
93
|
-
});
|
|
94
|
-
if (!res.ok) {
|
|
95
|
-
throw await failDetail("/api/voice/speak", res);
|
|
96
|
-
}
|
|
97
|
-
const bytes = new Uint8Array(await res.arrayBuffer());
|
|
98
|
-
const mediaType = res.headers.get("content-type") || "audio/wav";
|
|
99
|
-
return bytesToDataUrl(bytes, mediaType);
|
|
100
|
-
};
|
|
101
|
-
const generateImageDirect = async (body) => {
|
|
102
|
-
const res = await doFetch(`${base}/api/images/generate`, {
|
|
103
|
-
method: "POST",
|
|
104
|
-
headers: { "content-type": "application/json", ...authHeader() },
|
|
105
|
-
body: JSON.stringify(body ?? {})
|
|
106
|
-
});
|
|
107
|
-
if (!res.ok) {
|
|
108
|
-
throw await failDetail("/api/images/generate", res);
|
|
109
|
-
}
|
|
110
|
-
const parsed = await res.json();
|
|
111
|
-
return (parsed.data ?? []).map(
|
|
112
|
-
(item) => item.url ? item.url : `data:image/png;base64,${item.b64_json ?? ""}`
|
|
113
|
-
);
|
|
114
|
-
};
|
|
115
|
-
return {
|
|
116
|
-
bridge(method, args) {
|
|
117
|
-
if (!options.pluginId) {
|
|
118
|
-
return Promise.reject(
|
|
119
|
-
new Error(
|
|
120
|
-
`bridge primitive "${method}" requires a pluginId (the /api/plugins/:id/host caller identity)`
|
|
121
|
-
)
|
|
122
|
-
);
|
|
123
|
-
}
|
|
124
|
-
return post(`/api/plugins/${options.pluginId}/host`, { method, args });
|
|
125
|
-
},
|
|
126
|
-
direct(path, body) {
|
|
127
|
-
if (path === "/api/voice/transcribe") {
|
|
128
|
-
return transcribeDirect(body);
|
|
129
|
-
}
|
|
130
|
-
if (path === "/api/voice/speak") {
|
|
131
|
-
return speakDirect(body);
|
|
132
|
-
}
|
|
133
|
-
if (path === "/api/images/generate") {
|
|
134
|
-
return generateImageDirect(body);
|
|
135
|
-
}
|
|
136
|
-
return post(path, body);
|
|
137
|
-
},
|
|
138
|
-
capability(cap, body) {
|
|
139
|
-
return post(`/api/host/capability/${cap}`, body);
|
|
140
|
-
}
|
|
141
|
-
};
|
|
142
|
-
}
|
|
143
|
-
var PRIMITIVE_BINDINGS = {
|
|
144
|
-
// Bridge families (existing `PluginHookBridge`).
|
|
145
|
-
"engines.complete": {
|
|
146
|
-
transport: "bridge",
|
|
147
|
-
method: "model.complete",
|
|
148
|
-
grant: "hook:side-model"
|
|
149
|
-
},
|
|
150
|
-
// Host-direct media data-path (the host holds the node token; returns data: URLs).
|
|
151
|
-
"image.generate": {
|
|
152
|
-
transport: "direct",
|
|
153
|
-
path: "/api/images/generate",
|
|
154
|
-
grant: "media:generate"
|
|
155
|
-
},
|
|
156
|
-
"tts.speak": {
|
|
157
|
-
transport: "direct",
|
|
158
|
-
path: "/api/voice/speak",
|
|
159
|
-
grant: "media:generate"
|
|
160
|
-
},
|
|
161
|
-
"stt.transcribe": {
|
|
162
|
-
transport: "direct",
|
|
163
|
-
path: "/api/voice/transcribe",
|
|
164
|
-
grant: "media:transcribe"
|
|
165
|
-
},
|
|
166
|
-
// Broker capabilities — no rpc family yet (@requires-grant).
|
|
167
|
-
"rag.retrieve": { transport: "broker", capability: "rag" },
|
|
168
|
-
"rag.embed": { transport: "broker", capability: "rag" },
|
|
169
|
-
"rag.rerank": { transport: "broker", capability: "rag" },
|
|
170
|
-
"memory.recall": { transport: "broker", capability: "memory" },
|
|
171
|
-
"memory.store": { transport: "broker", capability: "memory" },
|
|
172
|
-
"realtime.broadcast": { transport: "broker", capability: "realtime" },
|
|
173
|
-
"realtime.subscribe": { transport: "broker", capability: "realtime" },
|
|
174
|
-
"durable.checkpoint": { transport: "broker", capability: "durable" },
|
|
175
|
-
"durable.resume": { transport: "broker", capability: "durable" },
|
|
176
|
-
"engines.embed": { transport: "broker", capability: "engines" }
|
|
177
|
-
};
|
|
178
|
-
function brokerCall(transport, cap, op, input) {
|
|
179
|
-
return transport.capability(cap, { op, input });
|
|
180
|
-
}
|
|
181
|
-
function createPrimitives(transport) {
|
|
182
|
-
return {
|
|
183
|
-
rag: {
|
|
184
|
-
retrieve: (input) => brokerCall(transport, "rag", "retrieve", input),
|
|
185
|
-
embed: (input) => brokerCall(transport, "rag", "embed", input),
|
|
186
|
-
rerank: (input) => brokerCall(transport, "rag", "rerank", input)
|
|
187
|
-
},
|
|
188
|
-
memory: {
|
|
189
|
-
recall: (input) => brokerCall(transport, "memory", "recall", input),
|
|
190
|
-
store: (input) => brokerCall(transport, "memory", "store", input)
|
|
191
|
-
},
|
|
192
|
-
realtime: {
|
|
193
|
-
broadcast: (input) => brokerCall(transport, "realtime", "broadcast", input).then(
|
|
194
|
-
() => void 0
|
|
195
|
-
),
|
|
196
|
-
subscribe: (input) => brokerCall(
|
|
197
|
-
transport,
|
|
198
|
-
"realtime",
|
|
199
|
-
"subscribe",
|
|
200
|
-
input
|
|
201
|
-
)
|
|
202
|
-
},
|
|
203
|
-
durable: {
|
|
204
|
-
checkpoint: (input) => brokerCall(transport, "durable", "checkpoint", input),
|
|
205
|
-
resume: (input) => brokerCall(transport, "durable", "resume", input)
|
|
206
|
-
},
|
|
207
|
-
engines: {
|
|
208
|
-
// Bridge family: model.complete → host.sideModel (wire keys are snake_case).
|
|
209
|
-
complete: (input) => transport.bridge("model.complete", {
|
|
210
|
-
prompt: input.prompt,
|
|
211
|
-
system: input.system,
|
|
212
|
-
model: input.model,
|
|
213
|
-
model_pref_key: input.modelPrefKey,
|
|
214
|
-
effort: input.effort
|
|
215
|
-
}),
|
|
216
|
-
embed: (input) => brokerCall(transport, "engines", "embed", input)
|
|
217
|
-
},
|
|
218
|
-
tts: {
|
|
219
|
-
speak: (input) => transport.direct("/api/voice/speak", input)
|
|
220
|
-
},
|
|
221
|
-
stt: {
|
|
222
|
-
transcribe: (input) => transport.direct("/api/voice/transcribe", input)
|
|
223
|
-
},
|
|
224
|
-
image: {
|
|
225
|
-
generate: (input) => transport.direct("/api/images/generate", input)
|
|
226
|
-
}
|
|
227
|
-
};
|
|
228
|
-
}
|
|
12
|
+
} from "./chunk-IKEDLLFY.js";
|
|
229
13
|
|
|
230
14
|
// src/agent/model-call.ts
|
|
231
15
|
var CHAT_COMPLETIONS_PATH = "/v1/chat/completions";
|
|
@@ -659,9 +443,6 @@ function query(input) {
|
|
|
659
443
|
}
|
|
660
444
|
|
|
661
445
|
export {
|
|
662
|
-
httpPrimitiveTransport,
|
|
663
|
-
PRIMITIVE_BINDINGS,
|
|
664
|
-
createPrimitives,
|
|
665
446
|
callModelWithTools,
|
|
666
447
|
ryuTool,
|
|
667
448
|
resolveToolDefs,
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
import {
|
|
2
|
+
assertAllowedEgressUrl
|
|
3
|
+
} from "./chunk-IKEDLLFY.js";
|
|
4
|
+
|
|
5
|
+
// src/runnable/primitives.ts
|
|
6
|
+
function dataUrlToBytes(dataUrl) {
|
|
7
|
+
const match = /^data:([^;,]*)(;base64)?,([\s\S]*)$/.exec(dataUrl);
|
|
8
|
+
if (!match) {
|
|
9
|
+
throw new Error(
|
|
10
|
+
"stt.transcribe expects an `audio` value that is a data: URL (data:<mime>;base64,<data>)"
|
|
11
|
+
);
|
|
12
|
+
}
|
|
13
|
+
const mediaType = match[1] || "application/octet-stream";
|
|
14
|
+
const isBase64 = Boolean(match[2]);
|
|
15
|
+
const payload = match[3] ?? "";
|
|
16
|
+
if (isBase64) {
|
|
17
|
+
const binary = atob(payload);
|
|
18
|
+
const bytes = new Uint8Array(binary.length);
|
|
19
|
+
for (let i = 0; i < binary.length; i++) {
|
|
20
|
+
bytes[i] = binary.charCodeAt(i);
|
|
21
|
+
}
|
|
22
|
+
return { bytes, mediaType };
|
|
23
|
+
}
|
|
24
|
+
return {
|
|
25
|
+
bytes: new TextEncoder().encode(decodeURIComponent(payload)),
|
|
26
|
+
mediaType
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
function bytesToDataUrl(bytes, mediaType) {
|
|
30
|
+
let binary = "";
|
|
31
|
+
const chunk = 32768;
|
|
32
|
+
for (let i = 0; i < bytes.length; i += chunk) {
|
|
33
|
+
binary += String.fromCharCode(...bytes.subarray(i, i + chunk));
|
|
34
|
+
}
|
|
35
|
+
return `data:${mediaType};base64,${btoa(binary)}`;
|
|
36
|
+
}
|
|
37
|
+
function httpPrimitiveTransport(options) {
|
|
38
|
+
const base = options.nodeUrl.replace(/\/+$/, "");
|
|
39
|
+
assertAllowedEgressUrl(base);
|
|
40
|
+
const doFetch = options.fetchImpl ?? fetch;
|
|
41
|
+
const authHeader = () => options.token ? { authorization: `Bearer ${options.token}` } : {};
|
|
42
|
+
const failDetail = async (path, res) => {
|
|
43
|
+
const detail = await res.text().catch(() => "");
|
|
44
|
+
return new Error(
|
|
45
|
+
`Ryu primitive call ${path} failed: ${res.status} ${res.statusText}${detail ? ` \u2014 ${detail}` : ""}`
|
|
46
|
+
);
|
|
47
|
+
};
|
|
48
|
+
const post = async (path, body) => {
|
|
49
|
+
const res = await doFetch(`${base}${path}`, {
|
|
50
|
+
method: "POST",
|
|
51
|
+
headers: { "content-type": "application/json", ...authHeader() },
|
|
52
|
+
body: JSON.stringify(body ?? {})
|
|
53
|
+
});
|
|
54
|
+
if (!res.ok) {
|
|
55
|
+
throw await failDetail(path, res);
|
|
56
|
+
}
|
|
57
|
+
const text = await res.text();
|
|
58
|
+
return text ? JSON.parse(text) : void 0;
|
|
59
|
+
};
|
|
60
|
+
const transcribeDirect = async (body) => {
|
|
61
|
+
const input = body ?? {};
|
|
62
|
+
if (!input.audio) {
|
|
63
|
+
throw new Error(
|
|
64
|
+
"stt.transcribe requires an `audio` data: URL (the recorded audio)"
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
const { bytes, mediaType } = dataUrlToBytes(input.audio);
|
|
68
|
+
const form = new FormData();
|
|
69
|
+
form.append(
|
|
70
|
+
"file",
|
|
71
|
+
new Blob([bytes], { type: mediaType || "audio/wav" }),
|
|
72
|
+
input.filename ?? "recording.wav"
|
|
73
|
+
);
|
|
74
|
+
const res = await doFetch(`${base}/api/voice/transcribe`, {
|
|
75
|
+
method: "POST",
|
|
76
|
+
headers: authHeader(),
|
|
77
|
+
body: form
|
|
78
|
+
});
|
|
79
|
+
if (!res.ok) {
|
|
80
|
+
throw await failDetail("/api/voice/transcribe", res);
|
|
81
|
+
}
|
|
82
|
+
const parsed = await res.json();
|
|
83
|
+
return (parsed.text ?? "").trim();
|
|
84
|
+
};
|
|
85
|
+
const speakDirect = async (body) => {
|
|
86
|
+
const res = await doFetch(`${base}/api/voice/speak`, {
|
|
87
|
+
method: "POST",
|
|
88
|
+
headers: { "content-type": "application/json", ...authHeader() },
|
|
89
|
+
body: JSON.stringify(body ?? {})
|
|
90
|
+
});
|
|
91
|
+
if (!res.ok) {
|
|
92
|
+
throw await failDetail("/api/voice/speak", res);
|
|
93
|
+
}
|
|
94
|
+
const bytes = new Uint8Array(await res.arrayBuffer());
|
|
95
|
+
const mediaType = res.headers.get("content-type") || "audio/wav";
|
|
96
|
+
return bytesToDataUrl(bytes, mediaType);
|
|
97
|
+
};
|
|
98
|
+
const generateImageDirect = async (body) => {
|
|
99
|
+
const res = await doFetch(`${base}/api/images/generate`, {
|
|
100
|
+
method: "POST",
|
|
101
|
+
headers: { "content-type": "application/json", ...authHeader() },
|
|
102
|
+
body: JSON.stringify(body ?? {})
|
|
103
|
+
});
|
|
104
|
+
if (!res.ok) {
|
|
105
|
+
throw await failDetail("/api/images/generate", res);
|
|
106
|
+
}
|
|
107
|
+
const parsed = await res.json();
|
|
108
|
+
return (parsed.data ?? []).map(
|
|
109
|
+
(item) => item.url ? item.url : `data:image/png;base64,${item.b64_json ?? ""}`
|
|
110
|
+
);
|
|
111
|
+
};
|
|
112
|
+
return {
|
|
113
|
+
bridge(method, args) {
|
|
114
|
+
if (!options.pluginId) {
|
|
115
|
+
return Promise.reject(
|
|
116
|
+
new Error(
|
|
117
|
+
`bridge primitive "${method}" requires a pluginId (the /api/plugins/:id/host caller identity)`
|
|
118
|
+
)
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
return post(`/api/plugins/${options.pluginId}/host`, { method, args });
|
|
122
|
+
},
|
|
123
|
+
direct(path, body) {
|
|
124
|
+
if (path === "/api/voice/transcribe") {
|
|
125
|
+
return transcribeDirect(body);
|
|
126
|
+
}
|
|
127
|
+
if (path === "/api/voice/speak") {
|
|
128
|
+
return speakDirect(body);
|
|
129
|
+
}
|
|
130
|
+
if (path === "/api/images/generate") {
|
|
131
|
+
return generateImageDirect(body);
|
|
132
|
+
}
|
|
133
|
+
return post(path, body);
|
|
134
|
+
},
|
|
135
|
+
capability(cap, body) {
|
|
136
|
+
return post(`/api/host/capability/${cap}`, body);
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
var PRIMITIVE_BINDINGS = {
|
|
141
|
+
// Bridge families (existing `PluginHookBridge`).
|
|
142
|
+
"engines.complete": {
|
|
143
|
+
transport: "bridge",
|
|
144
|
+
method: "model.complete",
|
|
145
|
+
grant: "hook:side-model"
|
|
146
|
+
},
|
|
147
|
+
"background.list": {
|
|
148
|
+
transport: "bridge",
|
|
149
|
+
method: "background.list",
|
|
150
|
+
grant: "background:control"
|
|
151
|
+
},
|
|
152
|
+
"background.stop": {
|
|
153
|
+
transport: "bridge",
|
|
154
|
+
method: "background.stop",
|
|
155
|
+
grant: "background:control"
|
|
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
|
+
},
|
|
182
|
+
// Host-direct media data-path (the host holds the node token; returns data: URLs).
|
|
183
|
+
"image.generate": {
|
|
184
|
+
transport: "direct",
|
|
185
|
+
path: "/api/images/generate",
|
|
186
|
+
grant: "media:generate"
|
|
187
|
+
},
|
|
188
|
+
"tts.speak": {
|
|
189
|
+
transport: "direct",
|
|
190
|
+
path: "/api/voice/speak",
|
|
191
|
+
grant: "media:generate"
|
|
192
|
+
},
|
|
193
|
+
"stt.transcribe": {
|
|
194
|
+
transport: "direct",
|
|
195
|
+
path: "/api/voice/transcribe",
|
|
196
|
+
grant: "media:transcribe"
|
|
197
|
+
},
|
|
198
|
+
// Broker capabilities — no rpc family yet (@requires-grant).
|
|
199
|
+
"rag.retrieve": { transport: "broker", capability: "rag" },
|
|
200
|
+
"rag.embed": { transport: "broker", capability: "rag" },
|
|
201
|
+
"rag.rerank": { transport: "broker", capability: "rag" },
|
|
202
|
+
"memory.recall": { transport: "broker", capability: "memory" },
|
|
203
|
+
"memory.store": { transport: "broker", capability: "memory" },
|
|
204
|
+
"realtime.broadcast": { transport: "broker", capability: "realtime" },
|
|
205
|
+
"realtime.subscribe": { transport: "broker", capability: "realtime" },
|
|
206
|
+
"durable.checkpoint": { transport: "broker", capability: "durable" },
|
|
207
|
+
"durable.resume": { transport: "broker", capability: "durable" },
|
|
208
|
+
"engines.embed": { transport: "broker", capability: "engines" }
|
|
209
|
+
};
|
|
210
|
+
function brokerCall(transport, cap, op, input) {
|
|
211
|
+
return transport.capability(cap, { op, input });
|
|
212
|
+
}
|
|
213
|
+
function createPrimitives(transport) {
|
|
214
|
+
return {
|
|
215
|
+
background: {
|
|
216
|
+
list: (input) => transport.bridge("background.list", {
|
|
217
|
+
producer: input?.producer,
|
|
218
|
+
running_only: input?.runningOnly
|
|
219
|
+
}),
|
|
220
|
+
stop: (input) => transport.bridge("background.stop", {
|
|
221
|
+
process_id: input.processId
|
|
222
|
+
})
|
|
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
|
+
},
|
|
231
|
+
rag: {
|
|
232
|
+
retrieve: (input) => brokerCall(transport, "rag", "retrieve", input),
|
|
233
|
+
embed: (input) => brokerCall(transport, "rag", "embed", input),
|
|
234
|
+
rerank: (input) => brokerCall(transport, "rag", "rerank", input)
|
|
235
|
+
},
|
|
236
|
+
memory: {
|
|
237
|
+
recall: (input) => brokerCall(transport, "memory", "recall", input),
|
|
238
|
+
store: (input) => brokerCall(transport, "memory", "store", input)
|
|
239
|
+
},
|
|
240
|
+
realtime: {
|
|
241
|
+
broadcast: (input) => brokerCall(transport, "realtime", "broadcast", input).then(
|
|
242
|
+
() => void 0
|
|
243
|
+
),
|
|
244
|
+
subscribe: (input) => brokerCall(
|
|
245
|
+
transport,
|
|
246
|
+
"realtime",
|
|
247
|
+
"subscribe",
|
|
248
|
+
input
|
|
249
|
+
)
|
|
250
|
+
},
|
|
251
|
+
durable: {
|
|
252
|
+
checkpoint: (input) => brokerCall(transport, "durable", "checkpoint", input),
|
|
253
|
+
resume: (input) => brokerCall(transport, "durable", "resume", input)
|
|
254
|
+
},
|
|
255
|
+
engines: {
|
|
256
|
+
// Bridge family: model.complete → host.sideModel (wire keys are snake_case).
|
|
257
|
+
complete: (input) => transport.bridge("model.complete", {
|
|
258
|
+
prompt: input.prompt,
|
|
259
|
+
system: input.system,
|
|
260
|
+
model: input.model,
|
|
261
|
+
model_pref_key: input.modelPrefKey,
|
|
262
|
+
effort: input.effort
|
|
263
|
+
}),
|
|
264
|
+
embed: (input) => brokerCall(transport, "engines", "embed", input)
|
|
265
|
+
},
|
|
266
|
+
tts: {
|
|
267
|
+
speak: (input) => transport.direct("/api/voice/speak", input)
|
|
268
|
+
},
|
|
269
|
+
stt: {
|
|
270
|
+
transcribe: (input) => transport.direct("/api/voice/transcribe", input)
|
|
271
|
+
},
|
|
272
|
+
image: {
|
|
273
|
+
generate: (input) => transport.direct("/api/images/generate", input)
|
|
274
|
+
}
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
export {
|
|
279
|
+
httpPrimitiveTransport,
|
|
280
|
+
PRIMITIVE_BINDINGS,
|
|
281
|
+
createPrimitives
|
|
282
|
+
};
|