@d4y/agent-runtime-nuxt 0.1.4 → 0.1.8

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 (38) hide show
  1. package/dist/module.d.mts +15 -1
  2. package/dist/module.json +1 -1
  3. package/dist/module.mjs +9 -2
  4. package/dist/runtime/components/AgentRuntimeMarkdown.d.vue.ts +10 -0
  5. package/dist/runtime/components/AgentRuntimeMarkdown.vue +17 -7
  6. package/dist/runtime/components/AgentRuntimeMarkdown.vue.d.ts +10 -0
  7. package/dist/runtime/composables/useAgentRuntime.d.ts +118 -1
  8. package/dist/runtime/composables/useAgentRuntime.js +220 -44
  9. package/dist/runtime/composables/useAgentRuntimeMarkdown.d.ts +5 -0
  10. package/dist/runtime/composables/useAgentRuntimeMarkdown.js +11 -8
  11. package/dist/runtime/server/api/app.get.d.ts +3 -0
  12. package/dist/runtime/server/api/app.get.js +5 -3
  13. package/dist/runtime/server/api/conversations/[id]/abort.post.js +1 -1
  14. package/dist/runtime/server/api/conversations/[id]/env.post.js +1 -1
  15. package/dist/runtime/server/api/conversations/[id]/files/preview/[...path].get.js +1 -1
  16. package/dist/runtime/server/api/conversations/[id]/files/raw/[...path].get.js +1 -1
  17. package/dist/runtime/server/api/conversations/[id]/files.get.js +1 -1
  18. package/dist/runtime/server/api/conversations/[id]/files.post.d.ts +2 -0
  19. package/dist/runtime/server/api/conversations/[id]/files.post.js +34 -0
  20. package/dist/runtime/server/api/conversations/[id]/history.get.js +1 -1
  21. package/dist/runtime/server/api/conversations/[id]/messages.post.js +1 -1
  22. package/dist/runtime/server/api/conversations/[id]/runs/[runId].get.d.ts +2 -0
  23. package/dist/runtime/server/api/conversations/[id]/runs/[runId].get.js +19 -0
  24. package/dist/runtime/server/api/conversations/[id]/runs.get.d.ts +2 -0
  25. package/dist/runtime/server/api/conversations/[id]/runs.get.js +17 -0
  26. package/dist/runtime/server/api/conversations/[id]/stream.get.js +1 -1
  27. package/dist/runtime/server/api/conversations/[id]/workflow.get.d.ts +2 -0
  28. package/dist/runtime/server/api/conversations/[id]/workflow.get.js +17 -0
  29. package/dist/runtime/server/api/conversations/[id]/workflows.post.d.ts +2 -0
  30. package/dist/runtime/server/api/conversations/[id]/workflows.post.js +27 -0
  31. package/dist/runtime/server/api/conversations/[id].delete.js +1 -1
  32. package/dist/runtime/server/api/conversations.post.js +9 -2
  33. package/dist/runtime/server/utils/agent-runtime.d.ts +2 -7
  34. package/dist/runtime/server/utils/agent-runtime.js +11 -3
  35. package/dist/runtime/shared.d.ts +6 -0
  36. package/dist/runtime/shared.js +8 -4
  37. package/dist/types.d.mts +1 -1
  38. package/package.json +15 -15
@@ -1,4 +1,4 @@
1
- import { computed, onMounted, ref, shallowRef } from "vue";
1
+ import { computed, onMounted, ref, shallowRef, unref, watch } from "vue";
2
2
  import { useRuntimeConfig } from "nuxt/app";
3
3
  const newId = () => typeof crypto !== "undefined" && "randomUUID" in crypto ? crypto.randomUUID() : `id-${Date.now()}-${Math.random().toString(36).slice(2)}`;
4
4
  const authStorageKey = (appId) => `agent-runtime.app-auth.${appId}`;
@@ -35,9 +35,21 @@ const computeAuthReady = (auth, app) => {
35
35
  return required.every((k) => (auth.values[k] ?? "").trim().length > 0);
36
36
  };
37
37
  const encodeRelPath = (relPath) => relPath.split("/").map(encodeURIComponent).join("/");
38
- export const useAgentRuntime = () => {
38
+ export const useAgentRuntime = (options = {}) => {
39
39
  const cfg = useRuntimeConfig().public.agentRuntime;
40
- const apiPrefix = (cfg?.apiPrefix ?? "/api/agent-runtime").replace(/\/+$/, "");
40
+ const apiPrefix = computed(() => {
41
+ const raw = options.apiPrefix === void 0 ? cfg?.apiPrefix : unref(options.apiPrefix);
42
+ return `${raw || "/api/agent-runtime"}`.replace(/\/+$/, "");
43
+ });
44
+ const selectedAppId = computed(() => {
45
+ const raw = options.appId === void 0 ? cfg?.appId : unref(options.appId);
46
+ return `${raw ?? ""}`.trim();
47
+ });
48
+ const withAppQuery = (url) => {
49
+ const appId = selectedAppId.value;
50
+ if (!appId) return url;
51
+ return `${url}${url.includes("?") ? "&" : "?"}appId=${encodeURIComponent(appId)}`;
52
+ };
41
53
  const conversationId = ref(null);
42
54
  const messages = shallowRef([]);
43
55
  const status = ref("idle");
@@ -49,25 +61,69 @@ export const useAgentRuntime = () => {
49
61
  const app = ref(null);
50
62
  const auth = ref(emptyAuth());
51
63
  const authReady = computed(() => computeAuthReady(auth.value, app.value));
52
- onMounted(async () => {
64
+ let abortController = null;
65
+ let messageAbortController = null;
66
+ let currentAssistantId = null;
67
+ const clearLocalConversation = () => {
68
+ abortController?.abort();
69
+ abortController = null;
70
+ messageAbortController?.abort();
71
+ messageAbortController = null;
72
+ conversationId.value = null;
73
+ currentAssistantId = null;
74
+ replaceMessages([]);
75
+ uiActions.value = [];
76
+ contextUsage.value = null;
77
+ compactionInProgress.value = false;
78
+ compactionLog.value = [];
79
+ files.value = [];
80
+ runs.value = [];
81
+ status.value = "idle";
82
+ error.value = null;
83
+ };
84
+ const appUrl = () => {
85
+ const appId = selectedAppId.value;
86
+ const suffix = appId ? `?appId=${encodeURIComponent(appId)}` : "";
87
+ return `${apiPrefix.value}/app${suffix}`;
88
+ };
89
+ let appLoadSeq = 0;
90
+ const loadApp = async () => {
91
+ const seq = ++appLoadSeq;
92
+ if (options.appId !== void 0 && !selectedAppId.value) {
93
+ app.value = null;
94
+ auth.value = emptyAuth();
95
+ clearLocalConversation();
96
+ return;
97
+ }
53
98
  try {
54
- const info = await $fetch(`${apiPrefix}/app`);
99
+ const info = await $fetch(appUrl());
100
+ if (seq !== appLoadSeq) return;
55
101
  app.value = info;
56
102
  auth.value = loadAuth(info.appId);
57
103
  } catch (err) {
104
+ if (seq !== appLoadSeq) return;
58
105
  console.warn("[agent-runtime] failed to load app manifest", err);
106
+ app.value = null;
107
+ auth.value = emptyAuth();
59
108
  }
109
+ };
110
+ onMounted(loadApp);
111
+ watch(selectedAppId, async (next, previous) => {
112
+ if (next === previous) return;
113
+ clearLocalConversation();
114
+ await loadApp();
60
115
  });
61
116
  const files = ref([]);
117
+ const runs = ref([]);
62
118
  const fileUrl = (relPath) => {
63
119
  const cid = conversationId.value;
64
120
  if (!cid) return "";
65
- return `${apiPrefix}/conversations/${cid}/files/raw/${encodeRelPath(relPath)}`;
121
+ return withAppQuery(`${apiPrefix.value}/conversations/${cid}/files/raw/${encodeRelPath(relPath)}`);
66
122
  };
67
123
  const filePreviewUrl = (relPath) => {
68
124
  const cid = conversationId.value;
69
125
  if (!cid) return "";
70
- return `${apiPrefix}/conversations/${cid}/files/preview/${encodeRelPath(relPath)}`;
126
+ return withAppQuery(`${apiPrefix.value}/conversations/${cid}/files/preview/${encodeRelPath(relPath)}`);
71
127
  };
72
128
  const refreshFiles = async () => {
73
129
  const cid = conversationId.value;
@@ -76,14 +132,29 @@ export const useAgentRuntime = () => {
76
132
  return;
77
133
  }
78
134
  try {
79
- const res = await $fetch(`${apiPrefix}/conversations/${cid}/files`);
135
+ const res = await $fetch(withAppQuery(`${apiPrefix.value}/conversations/${cid}/files`));
80
136
  files.value = res.items;
81
137
  } catch (err) {
82
138
  console.warn("[agent-runtime] file list failed", err);
83
139
  }
84
140
  };
85
- let abortController = null;
86
- let currentAssistantId = null;
141
+ const refreshRuns = async () => {
142
+ const cid = conversationId.value;
143
+ if (!cid) {
144
+ runs.value = [];
145
+ return;
146
+ }
147
+ try {
148
+ const res = await $fetch(withAppQuery(`${apiPrefix.value}/conversations/${cid}/runs`));
149
+ runs.value = res.runs;
150
+ } catch (err) {
151
+ console.warn("[agent-runtime] run list failed", err);
152
+ }
153
+ };
154
+ const upsertRun = (run) => {
155
+ const next = [run, ...runs.value.filter((item) => item.runId !== run.runId)];
156
+ runs.value = next.sort((a, b) => b.createdAt.localeCompare(a.createdAt));
157
+ };
87
158
  const replaceMessages = (next) => {
88
159
  messages.value = next;
89
160
  };
@@ -114,13 +185,24 @@ export const useAgentRuntime = () => {
114
185
  });
115
186
  };
116
187
  const upsertToolPart = (toolName, toolCallId, mut) => {
188
+ let found = false;
189
+ const nextMessages = messages.value.map((message) => {
190
+ if (message.role !== "assistant") return message;
191
+ const parts = message.parts.slice();
192
+ const idx = parts.findIndex((p) => p.type === `tool-${toolName}` && p.toolCallId === toolCallId);
193
+ if (idx < 0) return message;
194
+ found = true;
195
+ parts[idx] = mut(parts[idx]);
196
+ return { ...message, parts };
197
+ });
198
+ if (found) {
199
+ replaceMessages(nextMessages);
200
+ return;
201
+ }
117
202
  ensureAssistant();
118
203
  updateAssistant((msg) => {
119
204
  const parts = msg.parts.slice();
120
- const idx = parts.findIndex((p) => p.type === `tool-${toolName}` && p.toolCallId === toolCallId);
121
- const next = mut(idx >= 0 ? parts[idx] : void 0);
122
- if (idx >= 0) parts[idx] = next;
123
- else parts.push(next);
205
+ parts.push(mut(void 0));
124
206
  return { ...msg, parts };
125
207
  });
126
208
  };
@@ -208,11 +290,21 @@ export const useAgentRuntime = () => {
208
290
  uiActions.value = [{ toolCallId: d.toolCallId, type: d.type, payload: d.payload, at: Date.now() }, ...uiActions.value].slice(0, 50);
209
291
  break;
210
292
  }
293
+ case "run_started":
294
+ case "run_updated":
295
+ case "run_completed":
296
+ case "run_failed":
297
+ case "run_aborted": {
298
+ const d = data;
299
+ if (d.run?.runId) upsertRun(d.run);
300
+ break;
301
+ }
211
302
  case "end":
212
303
  compactionInProgress.value = false;
213
304
  status.value = "ready";
214
305
  currentAssistantId = null;
215
306
  void refreshFiles();
307
+ void refreshRuns();
216
308
  break;
217
309
  case "error": {
218
310
  const d = data;
@@ -227,7 +319,7 @@ export const useAgentRuntime = () => {
227
319
  }
228
320
  };
229
321
  const consume = async (signal) => {
230
- const res = await fetch(`${apiPrefix}/conversations/${conversationId.value}/stream`, { signal });
322
+ const res = await fetch(withAppQuery(`${apiPrefix.value}/conversations/${conversationId.value}/stream`), { signal });
231
323
  if (!res.ok || !res.body) throw new Error(`stream failed: ${res.status}`);
232
324
  const reader = res.body.getReader();
233
325
  const decoder = new TextDecoder();
@@ -259,7 +351,17 @@ export const useAgentRuntime = () => {
259
351
  }
260
352
  }
261
353
  };
262
- const start = async (options = {}) => {
354
+ const openStream = () => {
355
+ abortController?.abort();
356
+ abortController = new AbortController();
357
+ const signal = abortController.signal;
358
+ consume(signal).catch((err) => {
359
+ if (signal.aborted) return;
360
+ error.value = err instanceof Error ? err : new Error(String(err));
361
+ status.value = "error";
362
+ });
363
+ };
364
+ const start = async (options2 = {}) => {
263
365
  if (!authReady.value) {
264
366
  const missing = app.value ? Object.entries(app.value.envSchema).filter(([k, f]) => f.required && !(auth.value.values[k] ?? "").trim()).map(([k]) => k) : [];
265
367
  throw new Error(
@@ -267,31 +369,79 @@ export const useAgentRuntime = () => {
267
369
  );
268
370
  }
269
371
  error.value = null;
270
- const res = await $fetch(`${apiPrefix}/conversations`, {
372
+ const res = await $fetch(`${apiPrefix.value}/conversations`, {
271
373
  method: "POST",
272
374
  body: {
273
375
  env: buildEnvFromAuth(auth.value),
274
- language: options.language,
275
- requestOptions: options.requestOptions
376
+ appId: (app.value?.appId ?? selectedAppId.value) || void 0,
377
+ chatType: options2.chatType,
378
+ initialPrompt: options2.initialPrompt,
379
+ workflow: options2.workflow,
380
+ parentConversationId: options2.parentConversationId,
381
+ parentContextMode: options2.parentContextMode,
382
+ language: options2.language,
383
+ requestOptions: options2.requestOptions
276
384
  }
277
385
  });
278
386
  conversationId.value = res.conversationId;
279
- abortController?.abort();
280
- abortController = new AbortController();
281
- const signal = abortController.signal;
282
- consume(signal).catch((err) => {
283
- if (signal.aborted) return;
284
- error.value = err instanceof Error ? err : new Error(String(err));
285
- status.value = "error";
286
- });
387
+ openStream();
287
388
  void refreshFiles();
389
+ void refreshRuns();
288
390
  return res.conversationId;
289
391
  };
392
+ const open = async (id) => {
393
+ const nextId = id.trim();
394
+ if (!nextId) return;
395
+ abortController?.abort();
396
+ abortController = null;
397
+ messageAbortController?.abort();
398
+ messageAbortController = null;
399
+ error.value = null;
400
+ status.value = "ready";
401
+ currentAssistantId = null;
402
+ conversationId.value = nextId;
403
+ uiActions.value = [];
404
+ contextUsage.value = null;
405
+ compactionInProgress.value = false;
406
+ compactionLog.value = [];
407
+ const history = await $fetch(
408
+ withAppQuery(`${apiPrefix.value}/conversations/${nextId}/history`)
409
+ );
410
+ replaceMessages(history.messages ?? []);
411
+ openStream();
412
+ await Promise.all([refreshFiles(), refreshRuns()]);
413
+ };
414
+ const uploadFiles = async (filesInput, options2 = {}) => {
415
+ const cid = conversationId.value;
416
+ if (!cid) throw new Error("Start or open a conversation before uploading files.");
417
+ const filesArray = typeof FileList !== "undefined" && filesInput instanceof FileList ? Array.from(filesInput) : Array.isArray(filesInput) ? filesInput : [filesInput];
418
+ const form = new FormData();
419
+ if (options2.targetDir) form.append("targetDir", options2.targetDir);
420
+ for (const file of filesArray) form.append("files", file, file.name);
421
+ const res = await $fetch(withAppQuery(`${apiPrefix.value}/conversations/${cid}/files`), {
422
+ method: "POST",
423
+ body: form
424
+ });
425
+ await refreshFiles();
426
+ return res.items;
427
+ };
428
+ const startWorkflow = async (options2) => {
429
+ const cid = conversationId.value;
430
+ if (!cid) throw new Error("Start or open a conversation before running a workflow.");
431
+ error.value = null;
432
+ status.value = "submitted";
433
+ if (!abortController) openStream();
434
+ await $fetch(withAppQuery(`${apiPrefix.value}/conversations/${cid}/workflows`), {
435
+ method: "POST",
436
+ body: options2
437
+ });
438
+ await refreshRuns();
439
+ };
290
440
  const saveAuth = async (next) => {
291
441
  auth.value = { values: { ...next.values } };
292
442
  if (app.value) persistAuth(app.value.appId, auth.value);
293
443
  if (conversationId.value) {
294
- await $fetch(`${apiPrefix}/conversations/${conversationId.value}/env`, {
444
+ await $fetch(withAppQuery(`${apiPrefix.value}/conversations/${conversationId.value}/env`), {
295
445
  method: "POST",
296
446
  body: { env: buildEnvFromAuth(auth.value), merge: true }
297
447
  }).catch((err) => {
@@ -299,43 +449,63 @@ export const useAgentRuntime = () => {
299
449
  });
300
450
  }
301
451
  };
302
- const send = async (text, options = {}) => {
452
+ const send = async (text, options2 = {}) => {
303
453
  if (!text.trim()) return;
304
454
  if (!conversationId.value) {
305
455
  try {
306
- await start({ language: options.language });
456
+ await start({ language: options2.language });
307
457
  } catch (err) {
308
458
  error.value = err instanceof Error ? err : new Error(String(err));
309
459
  status.value = "error";
310
460
  return;
311
461
  }
462
+ } else if (!abortController) {
463
+ openStream();
312
464
  }
313
465
  error.value = null;
314
466
  status.value = "submitted";
315
467
  currentAssistantId = null;
468
+ messageAbortController?.abort();
469
+ const messageController = new AbortController();
470
+ messageAbortController = messageController;
316
471
  replaceMessages([
317
472
  ...messages.value,
318
473
  { id: newId(), role: "user", parts: [{ type: "text", text }] }
319
474
  ]);
320
- const wireContent = options.rewriteContent ? options.rewriteContent(text) : text;
321
- await $fetch(`${apiPrefix}/conversations/${conversationId.value}/messages`, {
322
- method: "POST",
323
- body: {
324
- content: wireContent,
325
- context: options.context,
326
- language: options.language,
327
- requestOptions: options.requestOptions
328
- }
329
- }).catch((err) => {
475
+ const wireContent = options2.rewriteContent ? options2.rewriteContent(text) : text;
476
+ try {
477
+ await $fetch(withAppQuery(`${apiPrefix.value}/conversations/${conversationId.value}/messages`), {
478
+ method: "POST",
479
+ signal: messageController.signal,
480
+ body: {
481
+ content: wireContent,
482
+ context: options2.context,
483
+ language: options2.language,
484
+ requestOptions: options2.requestOptions
485
+ }
486
+ });
487
+ } catch (err) {
488
+ if (messageController.signal.aborted) return;
330
489
  error.value = err instanceof Error ? err : new Error(String(err));
331
490
  status.value = "error";
332
- });
491
+ } finally {
492
+ if (messageAbortController === messageController) {
493
+ messageAbortController = null;
494
+ }
495
+ }
333
496
  };
334
497
  const abort = async () => {
335
- if (!conversationId.value) return;
336
- await $fetch(`${apiPrefix}/conversations/${conversationId.value}/abort`, { method: "POST" }).catch(() => {
337
- });
498
+ const cid = conversationId.value;
499
+ if (!cid) return;
500
+ abortController?.abort();
501
+ abortController = null;
502
+ messageAbortController?.abort();
503
+ messageAbortController = null;
504
+ compactionInProgress.value = false;
338
505
  status.value = "ready";
506
+ currentAssistantId = null;
507
+ void $fetch(withAppQuery(`${apiPrefix.value}/conversations/${cid}/abort`), { method: "POST" }).catch(() => {
508
+ });
339
509
  };
340
510
  const reset = async () => {
341
511
  abortController?.abort();
@@ -348,6 +518,7 @@ export const useAgentRuntime = () => {
348
518
  compactionInProgress.value = false;
349
519
  compactionLog.value = [];
350
520
  files.value = [];
521
+ runs.value = [];
351
522
  status.value = "idle";
352
523
  error.value = null;
353
524
  };
@@ -367,9 +538,14 @@ export const useAgentRuntime = () => {
367
538
  auth,
368
539
  authReady,
369
540
  files,
541
+ runs,
370
542
  fileUrl,
371
543
  filePreviewUrl,
372
544
  refreshFiles,
545
+ refreshRuns,
546
+ open,
547
+ uploadFiles,
548
+ startWorkflow,
373
549
  saveAuth,
374
550
  start,
375
551
  send,
@@ -1,6 +1,11 @@
1
1
  import MarkdownIt from 'markdown-it';
2
2
  import { type AgentRuntimeAssetResolverOptions } from '../utils/files.js';
3
3
  export interface AgentRuntimeMarkdownRenderOptions extends AgentRuntimeAssetResolverOptions {
4
+ previewLinkLabel?: string;
5
+ downloadLinkLabel?: string;
6
+ pdfPreviewTitleLabel?: string;
7
+ htmlPreviewTitleLabel?: string;
8
+ showPdfEmbedToolbar?: boolean;
4
9
  }
5
10
  export declare const createAgentRuntimeMarkdownRenderer: (options?: AgentRuntimeMarkdownRenderOptions) => MarkdownIt;
6
11
  export declare const useAgentRuntimeMarkdown: (options?: AgentRuntimeMarkdownRenderOptions) => {
@@ -33,7 +33,7 @@ const buildImageMarkup = (md, asset, alt, title) => {
33
33
  const attrs = previewAttrs(md, asset);
34
34
  return `<a href="${safePreviewUrl}" target="_blank" rel="noopener noreferrer" class="agent-runtime-md-image-link" ${attrs}><img src="${safePreviewUrl}" alt="${safeAlt}" loading="lazy" class="agent-runtime-md-image"${titleAttr} /></a>`;
35
35
  };
36
- const buildPreviewShellMarkup = (md, asset, label) => {
36
+ const buildPreviewShellMarkup = (md, asset, label, options) => {
37
37
  const previewUrl = asset.previewUrl;
38
38
  if (!previewUrl) {
39
39
  return buildDownloadLinkMarkup(md, asset, label);
@@ -44,16 +44,19 @@ const buildPreviewShellMarkup = (md, asset, label) => {
44
44
  const shellClass = asset.kind === "pdf" ? "agent-runtime-md-pdf-shell" : "agent-runtime-md-html-shell";
45
45
  const frameClass = asset.kind === "pdf" ? "agent-runtime-md-pdf-frame" : "agent-runtime-md-html-frame";
46
46
  const frameAttrs = asset.kind === "pdf" ? "" : ' sandbox="" referrerpolicy="no-referrer"';
47
- const title = asset.kind === "pdf" ? `PDF preview: ${label}` : `HTML preview: ${label}`;
48
- const downloadButton = asset.canDownload && asset.rawUrl ? `<a href="${escapeAttr(md, asset.rawUrl)}" target="_blank" rel="noopener noreferrer" class="agent-runtime-md-download-link">Download</a>` : "";
49
- return `<span class="${shellClass}"><span class="agent-runtime-md-embed-toolbar"><span class="agent-runtime-md-embed-label">${safeLabel}</span><span class="agent-runtime-md-embed-actions"><a href="${safePreviewUrl}" target="_blank" rel="noopener noreferrer" class="agent-runtime-md-preview-link" ${attrs}>Preview</a>${downloadButton}</span></span><iframe src="${safePreviewUrl}" title="${escapeAttr(md, title)}" loading="lazy"${frameAttrs} class="${frameClass}"></iframe></span>`;
47
+ const titleLabel = asset.kind === "pdf" ? options.pdfPreviewTitleLabel ?? "PDF preview" : options.htmlPreviewTitleLabel ?? "HTML preview";
48
+ const title = `${titleLabel}: ${label}`;
49
+ const showToolbar = asset.kind !== "pdf" || options.showPdfEmbedToolbar === true;
50
+ const downloadButton = asset.canDownload && asset.rawUrl ? `<a href="${escapeAttr(md, asset.rawUrl)}" target="_blank" rel="noopener noreferrer" class="agent-runtime-md-download-link">${escapeAttr(md, options.downloadLinkLabel ?? "Download")}</a>` : "";
51
+ const toolbar = showToolbar ? `<span class="agent-runtime-md-embed-toolbar"><span class="agent-runtime-md-embed-label">${safeLabel}</span><span class="agent-runtime-md-embed-actions"><a href="${safePreviewUrl}" target="_blank" rel="noopener noreferrer" class="agent-runtime-md-preview-link" ${attrs}>${escapeAttr(md, options.previewLinkLabel ?? "Preview")}</a>${downloadButton}</span></span>` : "";
52
+ return `<span class="${shellClass}">${toolbar}<iframe src="${safePreviewUrl}" title="${escapeAttr(md, title)}" loading="lazy"${frameAttrs} class="${frameClass}"></iframe></span>`;
50
53
  };
51
54
  const toHtmlToken = (state, markup) => {
52
55
  const token = new state.Token("html_inline", "", 0);
53
56
  token.content = markup;
54
57
  return token;
55
58
  };
56
- const renderWorkspaceLink = (md, asset, label) => {
59
+ const renderWorkspaceLink = (md, asset, label, options) => {
57
60
  if (asset.kind === "blocked") {
58
61
  return escapeAttr(md, label);
59
62
  }
@@ -61,7 +64,7 @@ const renderWorkspaceLink = (md, asset, label) => {
61
64
  return buildImageMarkup(md, asset, label);
62
65
  }
63
66
  if (asset.kind === "html" || asset.kind === "pdf") {
64
- return buildPreviewShellMarkup(md, asset, label);
67
+ return buildPreviewShellMarkup(md, asset, label, options);
65
68
  }
66
69
  return buildDownloadLinkMarkup(md, asset, label);
67
70
  };
@@ -107,7 +110,7 @@ export const createAgentRuntimeMarkdownRenderer = (options = {}) => {
107
110
  if (asset.kind === "image") {
108
111
  return buildImageMarkup(md, asset, alt, title);
109
112
  }
110
- return renderWorkspaceLink(md, asset, alt || asset.label);
113
+ return renderWorkspaceLink(md, asset, alt || asset.label, options);
111
114
  };
112
115
  md.core.ruler.after("inline", "agent_runtime_workspace_links", (state) => {
113
116
  for (const blockToken of state.tokens) {
@@ -128,7 +131,7 @@ export const createAgentRuntimeMarkdownRenderer = (options = {}) => {
128
131
  out.push(child);
129
132
  continue;
130
133
  }
131
- out.push(toHtmlToken(state, renderWorkspaceLink(md, asset, next.content || asset.label)));
134
+ out.push(toHtmlToken(state, renderWorkspaceLink(md, asset, next.content || asset.label, options)));
132
135
  idx += 2;
133
136
  }
134
137
  blockToken.children = out;
@@ -2,6 +2,7 @@ interface AppEnvField {
2
2
  required?: boolean;
3
3
  secret?: boolean;
4
4
  description?: string;
5
+ default?: string;
5
6
  }
6
7
  /**
7
8
  * GET `${apiPrefix}/app` — returns the manifest of the configured app
@@ -11,5 +12,7 @@ declare const _default: import("h3").EventHandler<import("h3").EventHandlerReque
11
12
  appId: string;
12
13
  name: string;
13
14
  envSchema: Record<string, AppEnvField>;
15
+ start: unknown;
16
+ workflows: Record<string, unknown>;
14
17
  }>>;
15
18
  export default _default;
@@ -1,7 +1,7 @@
1
1
  import { createError, defineEventHandler } from "h3";
2
2
  import { agentRuntime, agentRuntimeHeaders } from "../utils/agent-runtime.js";
3
- export default defineEventHandler(async () => {
4
- const cfg = agentRuntime();
3
+ export default defineEventHandler(async (event) => {
4
+ const cfg = agentRuntime(event);
5
5
  const response = await fetch(`${cfg.baseUrl}/v1/apps`, {
6
6
  headers: agentRuntimeHeaders(cfg)
7
7
  });
@@ -22,6 +22,8 @@ export default defineEventHandler(async () => {
22
22
  return {
23
23
  appId: app.appId,
24
24
  name: app.name,
25
- envSchema: app.envSchema ?? {}
25
+ envSchema: app.envSchema ?? {},
26
+ start: app.start,
27
+ workflows: app.workflows ?? {}
26
28
  };
27
29
  });
@@ -1,7 +1,7 @@
1
1
  import { createError, defineEventHandler, getRouterParam, setResponseStatus } from "h3";
2
2
  import { agentRuntime, agentRuntimeHeaders } from "../../../utils/agent-runtime.js";
3
3
  export default defineEventHandler(async (event) => {
4
- const cfg = agentRuntime();
4
+ const cfg = agentRuntime(event);
5
5
  const id = getRouterParam(event, "id");
6
6
  if (!id) throw createError({ statusCode: 400, statusMessage: "missing conversation id" });
7
7
  const response = await fetch(`${cfg.baseUrl}/v1/conversations/${id}/abort`, {
@@ -1,7 +1,7 @@
1
1
  import { createError, defineEventHandler, getRouterParam, readBody } from "h3";
2
2
  import { agentRuntime, agentRuntimeHeaders } from "../../../utils/agent-runtime.js";
3
3
  export default defineEventHandler(async (event) => {
4
- const cfg = agentRuntime();
4
+ const cfg = agentRuntime(event);
5
5
  const id = getRouterParam(event, "id");
6
6
  if (!id) throw createError({ statusCode: 400, statusMessage: "missing conversation id" });
7
7
  const body = await readBody(event).catch(() => ({}));
@@ -11,7 +11,7 @@ const PASS_THROUGH_HEADERS = [
11
11
  "x-content-type-options"
12
12
  ];
13
13
  export default defineEventHandler(async (event) => {
14
- const cfg = agentRuntime();
14
+ const cfg = agentRuntime(event);
15
15
  const id = getRouterParam(event, "id");
16
16
  if (!id) throw createError({ statusCode: 400, statusMessage: "missing conversation id" });
17
17
  const apiPrefix = (useRuntimeConfig().public.agentRuntime?.apiPrefix ?? "/api/agent-runtime").replace(/\/+$/, "");
@@ -4,7 +4,7 @@ import { createError, defineEventHandler, getRouterParam, sendStream, setHeader
4
4
  import { agentRuntime } from "../../../../../utils/agent-runtime.js";
5
5
  const PASS_THROUGH_HEADERS = ["content-type", "content-length", "content-disposition", "cache-control"];
6
6
  export default defineEventHandler(async (event) => {
7
- const cfg = agentRuntime();
7
+ const cfg = agentRuntime(event);
8
8
  const id = getRouterParam(event, "id");
9
9
  if (!id) throw createError({ statusCode: 400, statusMessage: "missing conversation id" });
10
10
  const apiPrefix = (useRuntimeConfig().public.agentRuntime?.apiPrefix ?? "/api/agent-runtime").replace(/\/+$/, "");
@@ -1,7 +1,7 @@
1
1
  import { createError, defineEventHandler, getRouterParam } from "h3";
2
2
  import { agentRuntime } from "../../../utils/agent-runtime.js";
3
3
  export default defineEventHandler(async (event) => {
4
- const cfg = agentRuntime();
4
+ const cfg = agentRuntime(event);
5
5
  const id = getRouterParam(event, "id");
6
6
  if (!id) throw createError({ statusCode: 400, statusMessage: "missing conversation id" });
7
7
  const response = await fetch(`${cfg.baseUrl}/v1/conversations/${id}/files`, {
@@ -0,0 +1,2 @@
1
+ declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<any>>;
2
+ export default _default;
@@ -0,0 +1,34 @@
1
+ import { createError, defineEventHandler, getRouterParam, readMultipartFormData } from "h3";
2
+ import { agentRuntime } from "../../../utils/agent-runtime.js";
3
+ export default defineEventHandler(async (event) => {
4
+ const cfg = agentRuntime(event);
5
+ const id = getRouterParam(event, "id");
6
+ if (!id) throw createError({ statusCode: 400, statusMessage: "missing conversation id" });
7
+ const parts = await readMultipartFormData(event);
8
+ if (!parts?.length) throw createError({ statusCode: 400, statusMessage: "expected multipart/form-data" });
9
+ const form = new FormData();
10
+ for (const part of parts) {
11
+ if (!part.name) continue;
12
+ if (part.filename) {
13
+ const bytes = new Uint8Array(part.data.length);
14
+ bytes.set(part.data);
15
+ form.append(part.name, new Blob([bytes], { type: part.type || "application/octet-stream" }), part.filename);
16
+ } else {
17
+ form.append(part.name, part.data.toString("utf8"));
18
+ }
19
+ }
20
+ const response = await fetch(`${cfg.baseUrl}/v1/conversations/${id}/files`, {
21
+ method: "POST",
22
+ headers: {
23
+ "X-Agent-Runtime-App-Key": cfg.appKey
24
+ },
25
+ body: form
26
+ });
27
+ if (!response.ok) {
28
+ throw createError({
29
+ statusCode: response.status,
30
+ statusMessage: await response.text()
31
+ });
32
+ }
33
+ return await response.json();
34
+ });
@@ -1,7 +1,7 @@
1
1
  import { createError, defineEventHandler, getRouterParam } from "h3";
2
2
  import { agentRuntime } from "../../../utils/agent-runtime.js";
3
3
  export default defineEventHandler(async (event) => {
4
- const cfg = agentRuntime();
4
+ const cfg = agentRuntime(event);
5
5
  const id = getRouterParam(event, "id");
6
6
  if (!id) throw createError({ statusCode: 400, statusMessage: "missing conversation id" });
7
7
  const response = await fetch(`${cfg.baseUrl}/v1/conversations/${id}/history`, {
@@ -1,7 +1,7 @@
1
1
  import { createError, defineEventHandler, getRouterParam, readBody } from "h3";
2
2
  import { agentRuntime, agentRuntimeHeaders } from "../../../utils/agent-runtime.js";
3
3
  export default defineEventHandler(async (event) => {
4
- const cfg = agentRuntime();
4
+ const cfg = agentRuntime(event);
5
5
  const id = getRouterParam(event, "id");
6
6
  if (!id) throw createError({ statusCode: 400, statusMessage: "missing conversation id" });
7
7
  const body = await readBody(event);
@@ -0,0 +1,2 @@
1
+ declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<any>>;
2
+ export default _default;
@@ -0,0 +1,19 @@
1
+ import { createError, defineEventHandler, getRouterParam } from "h3";
2
+ import { agentRuntime } from "../../../../utils/agent-runtime.js";
3
+ export default defineEventHandler(async (event) => {
4
+ const cfg = agentRuntime(event);
5
+ const id = getRouterParam(event, "id");
6
+ const runId = getRouterParam(event, "runId");
7
+ if (!id) throw createError({ statusCode: 400, statusMessage: "missing conversation id" });
8
+ if (!runId) throw createError({ statusCode: 400, statusMessage: "missing run id" });
9
+ const response = await fetch(`${cfg.baseUrl}/v1/conversations/${id}/runs/${runId}`, {
10
+ headers: { "X-Agent-Runtime-App-Key": cfg.appKey }
11
+ });
12
+ if (!response.ok) {
13
+ throw createError({
14
+ statusCode: response.status,
15
+ statusMessage: await response.text()
16
+ });
17
+ }
18
+ return await response.json();
19
+ });
@@ -0,0 +1,2 @@
1
+ declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<any>>;
2
+ export default _default;