@ryuhq/sdk 0.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/LICENSE +179 -0
  2. package/README.md +31 -0
  3. package/dist/agent.cjs +761 -0
  4. package/dist/agent.d.cts +3 -0
  5. package/dist/agent.d.ts +3 -0
  6. package/dist/agent.js +23 -0
  7. package/dist/chunk-GXHL5CO7.js +353 -0
  8. package/dist/chunk-KPKMMGVC.js +671 -0
  9. package/dist/chunk-ODFEUVPW.js +100 -0
  10. package/dist/cli.cjs +858 -0
  11. package/dist/cli.d.cts +1 -0
  12. package/dist/cli.d.ts +1 -0
  13. package/dist/cli.js +454 -0
  14. package/dist/index-CEbS1SlS.d.cts +988 -0
  15. package/dist/index-DAxq7Y0R.d.ts +988 -0
  16. package/dist/index.cjs +1900 -0
  17. package/dist/index.d.cts +759 -0
  18. package/dist/index.d.ts +759 -0
  19. package/dist/index.js +771 -0
  20. package/dist/manifest.cjs +399 -0
  21. package/dist/manifest.d.cts +355 -0
  22. package/dist/manifest.d.ts +355 -0
  23. package/dist/manifest.js +38 -0
  24. package/package.json +56 -0
  25. package/src/agent/agent.ts +208 -0
  26. package/src/agent/index.ts +51 -0
  27. package/src/agent/loop.test.ts +261 -0
  28. package/src/agent/loop.ts +259 -0
  29. package/src/agent/model-call.ts +190 -0
  30. package/src/agent/query.ts +40 -0
  31. package/src/agent/tools.ts +295 -0
  32. package/src/builder.ts +473 -0
  33. package/src/cli/dev.test.ts +178 -0
  34. package/src/cli/dev.ts +425 -0
  35. package/src/cli.ts +390 -0
  36. package/src/contracts-lockstep.test.ts +77 -0
  37. package/src/generated/plugin-manifest.ts +1121 -0
  38. package/src/index.ts +141 -0
  39. package/src/manifest.test.ts +610 -0
  40. package/src/manifest.ts +589 -0
  41. package/src/mcp/bridge.test.ts +196 -0
  42. package/src/mcp/client.ts +253 -0
  43. package/src/mcp/fixture-server.ts +23 -0
  44. package/src/mcp/server.ts +351 -0
  45. package/src/model/client.test.ts +107 -0
  46. package/src/model/client.ts +179 -0
  47. package/src/model/gateway.ts +41 -0
  48. package/src/plugin/ryu-plugin.ts +191 -0
  49. package/src/runnable/agent.ts +338 -0
  50. package/src/runnable/app.ts +233 -0
  51. package/src/runnable/index.ts +61 -0
  52. package/src/runnable/primitives-hostapi.test.ts +73 -0
  53. package/src/runnable/primitives.test.ts +286 -0
  54. package/src/runnable/primitives.ts +610 -0
  55. package/src/runnable/runnable-types.ts +113 -0
  56. package/src/runnable/runnable.test.ts +397 -0
  57. package/src/runnable/skill.ts +60 -0
  58. package/src/runnable/tool.ts +260 -0
  59. package/src/runnable/turn-hook.test.ts +81 -0
  60. package/src/runnable/turn-hook.ts +191 -0
  61. package/src/runnable/workflow.ts +76 -0
package/dist/agent.cjs ADDED
@@ -0,0 +1,761 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/agent/index.ts
31
+ var agent_exports = {};
32
+ __export(agent_exports, {
33
+ Agent: () => Agent,
34
+ callModelWithTools: () => callModelWithTools,
35
+ createAgent: () => createAgent,
36
+ detectElicitation: () => detectElicitation,
37
+ executeTool: () => executeTool,
38
+ query: () => query,
39
+ resolveToolDefs: () => resolveToolDefs,
40
+ runAgentLoop: () => runAgentLoop,
41
+ ryuTool: () => ryuTool
42
+ });
43
+ module.exports = __toCommonJS(agent_exports);
44
+
45
+ // src/model/client.ts
46
+ var native = __toESM(require("@ryuhq/sdk-native"), 1);
47
+ var ModelClient2 = class {
48
+ model;
49
+ baseUrl;
50
+ native;
51
+ constructor(model, options = {}) {
52
+ this.native = new native.ModelClient(
53
+ model,
54
+ options.baseUrl ?? null,
55
+ options.token ?? null
56
+ );
57
+ this.model = model;
58
+ this.baseUrl = options.baseUrl ?? native.resolveGatewayUrl();
59
+ }
60
+ /** Send a non-streaming chat completion request to the gateway. */
61
+ async chat(messages) {
62
+ const res = await this.native.chat(messages);
63
+ const usage = res.promptTokens === void 0 && res.completionTokens === void 0 && res.totalTokens === void 0 ? void 0 : {
64
+ promptTokens: res.promptTokens ?? 0,
65
+ completionTokens: res.completionTokens ?? 0,
66
+ totalTokens: res.totalTokens ?? 0
67
+ };
68
+ return {
69
+ content: res.content,
70
+ finishReason: res.finishReason ?? null,
71
+ usage
72
+ };
73
+ }
74
+ /** Send a streaming chat completion request, yielding deltas as they arrive. */
75
+ async *stream(messages) {
76
+ const queue = [];
77
+ let done = false;
78
+ let failure = null;
79
+ let wake = null;
80
+ const notify = () => {
81
+ if (wake) {
82
+ const w = wake;
83
+ wake = null;
84
+ w();
85
+ }
86
+ };
87
+ this.native.stream(messages, (err, delta) => {
88
+ if (err) {
89
+ failure = err;
90
+ done = true;
91
+ } else if (delta === null || delta === void 0) {
92
+ done = true;
93
+ } else {
94
+ queue.push({
95
+ content: delta.content ?? null,
96
+ finishReason: delta.finishReason ?? null
97
+ });
98
+ }
99
+ notify();
100
+ });
101
+ while (true) {
102
+ while (queue.length > 0) {
103
+ const next = queue.shift();
104
+ if (next) {
105
+ yield next;
106
+ }
107
+ }
108
+ if (failure) {
109
+ throw failure;
110
+ }
111
+ if (done) {
112
+ return;
113
+ }
114
+ await new Promise((resolve) => {
115
+ wake = resolve;
116
+ });
117
+ }
118
+ }
119
+ };
120
+ function defineModel(modelId, options = {}) {
121
+ return new ModelClient2(modelId, options);
122
+ }
123
+
124
+ // src/model/gateway.ts
125
+ var native2 = __toESM(require("@ryuhq/sdk-native"), 1);
126
+ function resolveGatewayUrl3() {
127
+ return native2.resolveGatewayUrl();
128
+ }
129
+ function resolveGatewayToken2() {
130
+ return native2.resolveGatewayToken() ?? void 0;
131
+ }
132
+ function assertAllowedEgressUrl(baseUrl) {
133
+ native2.assertAllowedEgress(baseUrl);
134
+ }
135
+
136
+ // src/runnable/primitives.ts
137
+ function dataUrlToBytes(dataUrl) {
138
+ const match = /^data:([^;,]*)(;base64)?,([\s\S]*)$/.exec(dataUrl);
139
+ if (!match) {
140
+ throw new Error(
141
+ "stt.transcribe expects an `audio` value that is a data: URL (data:<mime>;base64,<data>)"
142
+ );
143
+ }
144
+ const mediaType = match[1] || "application/octet-stream";
145
+ const isBase64 = Boolean(match[2]);
146
+ const payload = match[3] ?? "";
147
+ if (isBase64) {
148
+ const binary = atob(payload);
149
+ const bytes = new Uint8Array(binary.length);
150
+ for (let i = 0; i < binary.length; i++) {
151
+ bytes[i] = binary.charCodeAt(i);
152
+ }
153
+ return { bytes, mediaType };
154
+ }
155
+ return { bytes: new TextEncoder().encode(decodeURIComponent(payload)), mediaType };
156
+ }
157
+ function bytesToDataUrl(bytes, mediaType) {
158
+ let binary = "";
159
+ const chunk = 32768;
160
+ for (let i = 0; i < bytes.length; i += chunk) {
161
+ binary += String.fromCharCode(...bytes.subarray(i, i + chunk));
162
+ }
163
+ return `data:${mediaType};base64,${btoa(binary)}`;
164
+ }
165
+ function httpPrimitiveTransport(options) {
166
+ const base = options.nodeUrl.replace(/\/+$/, "");
167
+ assertAllowedEgressUrl(base);
168
+ const doFetch = options.fetchImpl ?? fetch;
169
+ const authHeader = () => options.token ? { authorization: `Bearer ${options.token}` } : {};
170
+ const failDetail = async (path, res) => {
171
+ const detail = await res.text().catch(() => "");
172
+ return new Error(
173
+ `Ryu primitive call ${path} failed: ${res.status} ${res.statusText}${detail ? ` \u2014 ${detail}` : ""}`
174
+ );
175
+ };
176
+ const post = async (path, body) => {
177
+ const res = await doFetch(`${base}${path}`, {
178
+ method: "POST",
179
+ headers: { "content-type": "application/json", ...authHeader() },
180
+ body: JSON.stringify(body ?? {})
181
+ });
182
+ if (!res.ok) {
183
+ throw await failDetail(path, res);
184
+ }
185
+ const text = await res.text();
186
+ return text ? JSON.parse(text) : void 0;
187
+ };
188
+ const transcribeDirect = async (body) => {
189
+ const input = body ?? {};
190
+ if (!input.audio) {
191
+ throw new Error(
192
+ "stt.transcribe requires an `audio` data: URL (the recorded audio)"
193
+ );
194
+ }
195
+ const { bytes, mediaType } = dataUrlToBytes(input.audio);
196
+ const form = new FormData();
197
+ form.append(
198
+ "file",
199
+ new Blob([bytes], { type: mediaType || "audio/wav" }),
200
+ input.filename ?? "recording.wav"
201
+ );
202
+ const res = await doFetch(`${base}/api/voice/transcribe`, {
203
+ method: "POST",
204
+ headers: authHeader(),
205
+ body: form
206
+ });
207
+ if (!res.ok) {
208
+ throw await failDetail("/api/voice/transcribe", res);
209
+ }
210
+ const parsed = await res.json();
211
+ return (parsed.text ?? "").trim();
212
+ };
213
+ const speakDirect = async (body) => {
214
+ const res = await doFetch(`${base}/api/voice/speak`, {
215
+ method: "POST",
216
+ headers: { "content-type": "application/json", ...authHeader() },
217
+ body: JSON.stringify(body ?? {})
218
+ });
219
+ if (!res.ok) {
220
+ throw await failDetail("/api/voice/speak", res);
221
+ }
222
+ const bytes = new Uint8Array(await res.arrayBuffer());
223
+ const mediaType = res.headers.get("content-type") || "audio/wav";
224
+ return bytesToDataUrl(bytes, mediaType);
225
+ };
226
+ const generateImageDirect = async (body) => {
227
+ const res = await doFetch(`${base}/api/images/generate`, {
228
+ method: "POST",
229
+ headers: { "content-type": "application/json", ...authHeader() },
230
+ body: JSON.stringify(body ?? {})
231
+ });
232
+ if (!res.ok) {
233
+ throw await failDetail("/api/images/generate", res);
234
+ }
235
+ const parsed = await res.json();
236
+ return (parsed.data ?? []).map(
237
+ (item) => item.url ? item.url : `data:image/png;base64,${item.b64_json ?? ""}`
238
+ );
239
+ };
240
+ return {
241
+ bridge(method, args) {
242
+ if (!options.pluginId) {
243
+ return Promise.reject(
244
+ new Error(
245
+ `bridge primitive "${method}" requires a pluginId (the /api/plugins/:id/host caller identity)`
246
+ )
247
+ );
248
+ }
249
+ return post(`/api/plugins/${options.pluginId}/host`, { method, args });
250
+ },
251
+ direct(path, body) {
252
+ if (path === "/api/voice/transcribe") {
253
+ return transcribeDirect(body);
254
+ }
255
+ if (path === "/api/voice/speak") {
256
+ return speakDirect(body);
257
+ }
258
+ if (path === "/api/images/generate") {
259
+ return generateImageDirect(body);
260
+ }
261
+ return post(path, body);
262
+ },
263
+ capability(cap, body) {
264
+ return post(`/api/host/capability/${cap}`, body);
265
+ }
266
+ };
267
+ }
268
+ function brokerCall(transport, cap, op, input) {
269
+ return transport.capability(cap, { op, input });
270
+ }
271
+ function createPrimitives(transport) {
272
+ return {
273
+ rag: {
274
+ retrieve: (input) => brokerCall(transport, "rag", "retrieve", input),
275
+ embed: (input) => brokerCall(transport, "rag", "embed", input),
276
+ rerank: (input) => brokerCall(transport, "rag", "rerank", input)
277
+ },
278
+ memory: {
279
+ recall: (input) => brokerCall(transport, "memory", "recall", input),
280
+ store: (input) => brokerCall(transport, "memory", "store", input)
281
+ },
282
+ realtime: {
283
+ broadcast: (input) => brokerCall(transport, "realtime", "broadcast", input).then(
284
+ () => void 0
285
+ ),
286
+ subscribe: (input) => brokerCall(
287
+ transport,
288
+ "realtime",
289
+ "subscribe",
290
+ input
291
+ )
292
+ },
293
+ durable: {
294
+ checkpoint: (input) => brokerCall(transport, "durable", "checkpoint", input),
295
+ resume: (input) => brokerCall(transport, "durable", "resume", input)
296
+ },
297
+ engines: {
298
+ // Bridge family: model.complete → host.sideModel (wire keys are snake_case).
299
+ complete: (input) => transport.bridge("model.complete", {
300
+ prompt: input.prompt,
301
+ system: input.system,
302
+ model: input.model,
303
+ model_pref_key: input.modelPrefKey,
304
+ effort: input.effort
305
+ }),
306
+ embed: (input) => brokerCall(transport, "engines", "embed", input)
307
+ },
308
+ tts: {
309
+ speak: (input) => transport.direct("/api/voice/speak", input)
310
+ },
311
+ stt: {
312
+ transcribe: (input) => transport.direct("/api/voice/transcribe", input)
313
+ },
314
+ image: {
315
+ generate: (input) => transport.direct("/api/images/generate", input)
316
+ }
317
+ };
318
+ }
319
+
320
+ // src/agent/model-call.ts
321
+ var CHAT_COMPLETIONS_PATH = "/v1/chat/completions";
322
+ function normalizeBaseUrl(baseUrl) {
323
+ return baseUrl.endsWith("/") ? baseUrl.slice(0, -1) : baseUrl;
324
+ }
325
+ async function callModelWithTools(options) {
326
+ const base = normalizeBaseUrl(options.baseUrl);
327
+ assertAllowedEgressUrl(base);
328
+ const body = {
329
+ model: options.model,
330
+ messages: options.messages
331
+ };
332
+ if (options.tools && options.tools.length > 0) {
333
+ body.tools = options.tools;
334
+ if (options.toolChoice) {
335
+ body.tool_choice = options.toolChoice;
336
+ }
337
+ }
338
+ const headers = {
339
+ "content-type": "application/json",
340
+ // Force the gateway's plain-completion branch so our own tool_calls are
341
+ // returned verbatim on Composio-on managed nodes.
342
+ "x-ryu-raw-tools": "on"
343
+ };
344
+ if (options.token) {
345
+ headers.authorization = `Bearer ${options.token}`;
346
+ }
347
+ const res = await fetch(`${base}${CHAT_COMPLETIONS_PATH}`, {
348
+ method: "POST",
349
+ headers,
350
+ body: JSON.stringify(body),
351
+ signal: options.signal
352
+ });
353
+ if (!res.ok) {
354
+ const text = await res.text().catch(() => "");
355
+ throw new Error(
356
+ `[ryu-sdk] gateway ${res.status} ${res.statusText} at ${base}${CHAT_COMPLETIONS_PATH}${text ? `: ${text}` : ""}`
357
+ );
358
+ }
359
+ const json = await res.json();
360
+ const choice = json.choices?.[0];
361
+ const rawMessage = choice?.message;
362
+ const message = {
363
+ role: "assistant",
364
+ content: rawMessage?.content ?? null,
365
+ ...rawMessage?.tool_calls && rawMessage.tool_calls.length > 0 ? { tool_calls: rawMessage.tool_calls } : {}
366
+ };
367
+ const usage = json.usage ? {
368
+ promptTokens: json.usage.prompt_tokens ?? 0,
369
+ completionTokens: json.usage.completion_tokens ?? 0,
370
+ totalTokens: json.usage.total_tokens ?? 0
371
+ } : void 0;
372
+ return {
373
+ message,
374
+ finishReason: choice?.finish_reason ?? null,
375
+ usage
376
+ };
377
+ }
378
+
379
+ // src/agent/tools.ts
380
+ function ryuTool(id, opts = {}) {
381
+ return {
382
+ kind: "remote",
383
+ id,
384
+ description: opts.description,
385
+ parameters: opts.parameters
386
+ };
387
+ }
388
+ function isLocalTool(tool) {
389
+ return tool.kind === "tool";
390
+ }
391
+ var PERMISSIVE_OBJECT_SCHEMA = {
392
+ type: "object",
393
+ additionalProperties: true
394
+ };
395
+ function normalize(baseUrl) {
396
+ return baseUrl.endsWith("/") ? baseUrl.slice(0, -1) : baseUrl;
397
+ }
398
+ function authHeaders(token) {
399
+ const headers = {
400
+ "content-type": "application/json"
401
+ };
402
+ if (token) {
403
+ headers.authorization = `Bearer ${token}`;
404
+ }
405
+ return headers;
406
+ }
407
+ async function resolveToolDefs(tools, ctx) {
408
+ const defs = [];
409
+ for (const [name, tool] of Object.entries(tools)) {
410
+ if (isLocalTool(tool)) {
411
+ defs.push({
412
+ type: "function",
413
+ function: {
414
+ name,
415
+ description: tool.name,
416
+ parameters: tool.schema
417
+ }
418
+ });
419
+ continue;
420
+ }
421
+ let description = tool.description;
422
+ if (!description) {
423
+ description = await describeRemoteTool(tool.id, ctx);
424
+ }
425
+ defs.push({
426
+ type: "function",
427
+ function: {
428
+ name,
429
+ description: description ?? tool.id,
430
+ parameters: tool.parameters ?? PERMISSIVE_OBJECT_SCHEMA
431
+ }
432
+ });
433
+ }
434
+ return defs;
435
+ }
436
+ async function describeRemoteTool(id, ctx) {
437
+ const url = `${normalize(ctx.coreBaseUrl)}/api/tools/describe?id=${encodeURIComponent(id)}`;
438
+ try {
439
+ const res = await fetch(url, {
440
+ headers: authHeaders(ctx.coreToken),
441
+ signal: ctx.signal
442
+ });
443
+ if (!res.ok) {
444
+ return void 0;
445
+ }
446
+ const json = await res.json();
447
+ return json.description || json.name || void 0;
448
+ } catch {
449
+ return void 0;
450
+ }
451
+ }
452
+ async function executeTool(name, argsJson, tools, ctx) {
453
+ const tool = tools[name];
454
+ if (!tool) {
455
+ throw new Error(`[ryu-sdk] model called unknown tool "${name}"`);
456
+ }
457
+ const args = parseArgs(argsJson, name);
458
+ if (isLocalTool(tool)) {
459
+ const output = await tool.run(
460
+ args,
461
+ ctx.runnableContext
462
+ );
463
+ return { output };
464
+ }
465
+ if (!ctx.agentId) {
466
+ throw new Error(
467
+ `[ryu-sdk] remote tool "${name}" (${tool.id}) requires an agentId \u2014 set it on the Agent config so Core can govern the call`
468
+ );
469
+ }
470
+ const url = `${normalize(ctx.coreBaseUrl)}/api/mcp/tools/call`;
471
+ const res = await fetch(url, {
472
+ method: "POST",
473
+ headers: authHeaders(ctx.coreToken),
474
+ signal: ctx.signal,
475
+ body: JSON.stringify({
476
+ tool: tool.id,
477
+ arguments: args,
478
+ agent_id: ctx.agentId,
479
+ ...ctx.userId ? { user_id: ctx.userId } : {}
480
+ })
481
+ });
482
+ if (!res.ok) {
483
+ const text = await res.text().catch(() => "");
484
+ throw new Error(
485
+ `[ryu-sdk] Core tools/call ${res.status} for "${tool.id}"${text ? `: ${text}` : ""}`
486
+ );
487
+ }
488
+ const json = await res.json();
489
+ if (json.ok === false) {
490
+ throw new Error(
491
+ `[ryu-sdk] tool "${tool.id}" failed: ${json.error ?? "unknown error"}`
492
+ );
493
+ }
494
+ return { output: json.output };
495
+ }
496
+ function parseArgs(argsJson, name) {
497
+ const trimmed = (argsJson ?? "").trim();
498
+ if (trimmed === "") {
499
+ return {};
500
+ }
501
+ try {
502
+ return JSON.parse(trimmed);
503
+ } catch {
504
+ throw new Error(
505
+ `[ryu-sdk] tool "${name}" arguments were not valid JSON: ${argsJson}`
506
+ );
507
+ }
508
+ }
509
+ var ELICITATION_KEY = "__ryu_elicitation__";
510
+ function detectElicitation(output) {
511
+ if (typeof output !== "object" || output === null) {
512
+ return null;
513
+ }
514
+ const envelope = output[ELICITATION_KEY];
515
+ if (typeof envelope !== "object" || envelope === null) {
516
+ return null;
517
+ }
518
+ return envelope;
519
+ }
520
+
521
+ // src/agent/loop.ts
522
+ function safeParse(json) {
523
+ try {
524
+ return JSON.parse(json);
525
+ } catch {
526
+ return json;
527
+ }
528
+ }
529
+ function addUsage(a, b) {
530
+ if (!(a || b)) {
531
+ return void 0;
532
+ }
533
+ return {
534
+ promptTokens: (a?.promptTokens ?? 0) + (b?.promptTokens ?? 0),
535
+ completionTokens: (a?.completionTokens ?? 0) + (b?.completionTokens ?? 0),
536
+ totalTokens: (a?.totalTokens ?? 0) + (b?.totalTokens ?? 0)
537
+ };
538
+ }
539
+ function findElicitation(output) {
540
+ const direct = detectElicitation(output);
541
+ if (direct) {
542
+ return direct;
543
+ }
544
+ if (typeof output === "string") {
545
+ return detectElicitation(safeParse(output));
546
+ }
547
+ return null;
548
+ }
549
+ async function* runAgentLoop(config) {
550
+ const messages = [...config.messages];
551
+ let toolDefs;
552
+ try {
553
+ toolDefs = await resolveToolDefs(config.tools, config.toolCtx);
554
+ } catch (err) {
555
+ yield { type: "error", message: describeError(err) };
556
+ return;
557
+ }
558
+ let usage;
559
+ let lastText = "";
560
+ for (let step = 1; step <= config.maxSteps; step++) {
561
+ let result;
562
+ try {
563
+ result = await callModelWithTools({
564
+ baseUrl: config.gatewayBaseUrl,
565
+ token: config.gatewayToken,
566
+ model: config.model,
567
+ messages,
568
+ tools: toolDefs.length > 0 ? toolDefs : void 0,
569
+ toolChoice: toolDefs.length > 0 ? "auto" : void 0,
570
+ signal: config.signal
571
+ });
572
+ } catch (err) {
573
+ yield { type: "error", message: describeError(err) };
574
+ return;
575
+ }
576
+ usage = addUsage(usage, result.usage);
577
+ const assistant = result.message;
578
+ messages.push(assistant);
579
+ if (assistant.content) {
580
+ lastText = assistant.content;
581
+ yield { type: "text", content: assistant.content };
582
+ }
583
+ const toolCalls = assistant.tool_calls ?? [];
584
+ if (toolCalls.length === 0) {
585
+ yield { type: "result", text: lastText, steps: step, usage };
586
+ return;
587
+ }
588
+ const paused = yield* runToolCalls(toolCalls, messages, config);
589
+ if (paused) {
590
+ return;
591
+ }
592
+ }
593
+ yield { type: "result", text: lastText, steps: config.maxSteps, usage };
594
+ }
595
+ async function* runToolCalls(toolCalls, messages, config) {
596
+ for (const call of toolCalls) {
597
+ const name = call.function.name;
598
+ const input = safeParse(call.function.arguments);
599
+ yield { type: "tool_call", id: call.id, name, input };
600
+ let output;
601
+ try {
602
+ const res = await executeTool(
603
+ name,
604
+ call.function.arguments,
605
+ config.tools,
606
+ config.toolCtx
607
+ );
608
+ output = res.output;
609
+ } catch (err) {
610
+ const errPayload = { error: describeError(err) };
611
+ messages.push({
612
+ role: "tool",
613
+ tool_call_id: call.id,
614
+ content: JSON.stringify(errPayload)
615
+ });
616
+ yield { type: "tool_result", id: call.id, name, output: errPayload };
617
+ continue;
618
+ }
619
+ const elicitation = findElicitation(output);
620
+ if (elicitation) {
621
+ yield {
622
+ type: "auth_required",
623
+ tool: name,
624
+ url: elicitation.url,
625
+ message: elicitation.message
626
+ };
627
+ return true;
628
+ }
629
+ messages.push({
630
+ role: "tool",
631
+ tool_call_id: call.id,
632
+ content: typeof output === "string" ? output : JSON.stringify(output)
633
+ });
634
+ yield { type: "tool_result", id: call.id, name, output };
635
+ }
636
+ return false;
637
+ }
638
+ function describeError(err) {
639
+ return err instanceof Error ? err.message : String(err);
640
+ }
641
+
642
+ // src/agent/agent.ts
643
+ var DEFAULT_MAX_STEPS = 10;
644
+ var DEFAULT_CORE_URL = "http://127.0.0.1:7980";
645
+ function env(key) {
646
+ const value = globalThis.process?.env?.[key];
647
+ return value && value !== "" ? value : void 0;
648
+ }
649
+ function lazyGatewayClient(model, baseUrl, token) {
650
+ let client = null;
651
+ const get = () => {
652
+ client ??= defineModel(model, { baseUrl, token });
653
+ return client;
654
+ };
655
+ return {
656
+ chat: (messages) => get().chat(messages),
657
+ stream: (messages) => get().stream(messages)
658
+ };
659
+ }
660
+ var Agent = class {
661
+ config;
662
+ constructor(config) {
663
+ this.config = config;
664
+ }
665
+ /** Assemble the loop config for a given prompt. */
666
+ buildLoopConfig(prompt, signal) {
667
+ const gatewayBaseUrl = this.config.node?.baseUrl ?? resolveGatewayUrl3();
668
+ const gatewayToken = this.config.node?.token ?? resolveGatewayToken2();
669
+ const coreBaseUrl = this.config.core?.baseUrl ?? env("RYU_CORE_URL") ?? DEFAULT_CORE_URL;
670
+ const coreToken = this.config.core?.token ?? env("RYU_TOKEN");
671
+ const messages = [];
672
+ if (this.config.instructions) {
673
+ messages.push({ role: "system", content: this.config.instructions });
674
+ }
675
+ messages.push({ role: "user", content: prompt });
676
+ const toolCtx = {
677
+ agentId: this.config.agentId,
678
+ coreBaseUrl,
679
+ coreToken,
680
+ userId: this.config.userId,
681
+ signal,
682
+ runnableContext: {
683
+ gateway: lazyGatewayClient(
684
+ this.config.model,
685
+ gatewayBaseUrl,
686
+ gatewayToken
687
+ ),
688
+ signal,
689
+ // Mount the composable primitive surface only when a plugin id is
690
+ // present — the bridge families authenticate it, so we never attach a
691
+ // half-wired transport (advisor guidance / §6b).
692
+ ...this.config.pluginId ? createPrimitives(
693
+ httpPrimitiveTransport({
694
+ nodeUrl: coreBaseUrl,
695
+ token: coreToken,
696
+ pluginId: this.config.pluginId
697
+ })
698
+ ) : {}
699
+ }
700
+ };
701
+ return {
702
+ model: this.config.model,
703
+ gatewayBaseUrl,
704
+ gatewayToken,
705
+ messages,
706
+ tools: this.config.tools ?? {},
707
+ toolCtx,
708
+ maxSteps: this.config.maxSteps ?? DEFAULT_MAX_STEPS,
709
+ signal
710
+ };
711
+ }
712
+ /** Stream loop events (text / tool_call / tool_result / auth_required / …). */
713
+ stream(prompt, signal) {
714
+ return runAgentLoop(this.buildLoopConfig(prompt, signal));
715
+ }
716
+ /** Run to completion and return the final text, step count, and usage. */
717
+ async generate(prompt, signal) {
718
+ let text = "";
719
+ let steps = 0;
720
+ let usage;
721
+ let authRequired;
722
+ for await (const event of this.stream(prompt, signal)) {
723
+ if (event.type === "result") {
724
+ text = event.text;
725
+ steps = event.steps;
726
+ usage = event.usage;
727
+ } else if (event.type === "auth_required") {
728
+ authRequired = event;
729
+ } else if (event.type === "error") {
730
+ throw new Error(
731
+ `[ryu-sdk] agent "${this.config.name}": ${event.message}`
732
+ );
733
+ }
734
+ }
735
+ return { text, steps, usage, authRequired };
736
+ }
737
+ };
738
+ function createAgent(config) {
739
+ return new Agent(config);
740
+ }
741
+
742
+ // src/agent/query.ts
743
+ function query(input) {
744
+ const agent = new Agent({
745
+ name: input.options.name ?? "agent",
746
+ ...input.options
747
+ });
748
+ return agent.stream(input.prompt);
749
+ }
750
+ // Annotate the CommonJS export names for ESM import in node:
751
+ 0 && (module.exports = {
752
+ Agent,
753
+ callModelWithTools,
754
+ createAgent,
755
+ detectElicitation,
756
+ executeTool,
757
+ query,
758
+ resolveToolDefs,
759
+ runAgentLoop,
760
+ ryuTool
761
+ });