@d4y/agent-runtime-nuxt 0.1.1 → 0.1.3

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 (45) hide show
  1. package/dist/module.d.mts +2 -1
  2. package/dist/module.json +1 -1
  3. package/dist/module.mjs +3 -1
  4. package/dist/runtime/components/AgentRuntimeArtifactDialog.d.vue.ts +20 -0
  5. package/dist/runtime/components/AgentRuntimeArtifactDialog.vue +90 -0
  6. package/dist/runtime/components/AgentRuntimeArtifactDialog.vue.d.ts +20 -0
  7. package/dist/runtime/components/AgentRuntimeArtifactPreview.d.vue.ts +0 -2
  8. package/dist/runtime/components/AgentRuntimeArtifactPreview.vue +19 -64
  9. package/dist/runtime/components/AgentRuntimeArtifactPreview.vue.d.ts +0 -2
  10. package/dist/runtime/components/AgentRuntimeMarkdown.d.vue.ts +19 -0
  11. package/dist/runtime/components/AgentRuntimeMarkdown.vue +77 -0
  12. package/dist/runtime/components/AgentRuntimeMarkdown.vue.d.ts +19 -0
  13. package/dist/runtime/composables/useAgentRuntime.d.ts +3 -0
  14. package/dist/runtime/composables/useAgentRuntime.js +6 -0
  15. package/dist/runtime/composables/useAgentRuntimeMarkdown.d.ts +2 -7
  16. package/dist/runtime/composables/useAgentRuntimeMarkdown.js +94 -56
  17. package/dist/runtime/frontend.d.ts +4 -2
  18. package/dist/runtime/frontend.js +8 -2
  19. package/dist/runtime/server/api/app.get.d.ts +10 -1
  20. package/dist/runtime/server/api/app.get.js +1 -0
  21. package/dist/runtime/server/api/conversations/[id]/abort.post.d.ts +1 -1
  22. package/dist/runtime/server/api/conversations/[id]/abort.post.js +1 -0
  23. package/dist/runtime/server/api/conversations/[id]/env.post.d.ts +1 -1
  24. package/dist/runtime/server/api/conversations/[id]/env.post.js +1 -0
  25. package/dist/runtime/server/api/conversations/[id]/files/preview/[...path].get.d.ts +2 -0
  26. package/dist/runtime/server/api/conversations/[id]/files/preview/[...path].get.js +42 -0
  27. package/dist/runtime/server/api/conversations/[id]/files/raw/[...path].get.d.ts +1 -1
  28. package/dist/runtime/server/api/conversations/[id]/files/raw/[...path].get.js +2 -0
  29. package/dist/runtime/server/api/conversations/[id]/files.get.d.ts +4 -1
  30. package/dist/runtime/server/api/conversations/[id]/files.get.js +1 -0
  31. package/dist/runtime/server/api/conversations/[id]/history.get.d.ts +1 -1
  32. package/dist/runtime/server/api/conversations/[id]/history.get.js +1 -0
  33. package/dist/runtime/server/api/conversations/[id]/messages.post.d.ts +1 -1
  34. package/dist/runtime/server/api/conversations/[id]/messages.post.js +1 -0
  35. package/dist/runtime/server/api/conversations/[id]/stream.get.d.ts +1 -1
  36. package/dist/runtime/server/api/conversations/[id]/stream.get.js +1 -0
  37. package/dist/runtime/server/api/conversations/[id].delete.d.ts +1 -1
  38. package/dist/runtime/server/api/conversations/[id].delete.js +1 -0
  39. package/dist/runtime/server/api/conversations.post.d.ts +1 -1
  40. package/dist/runtime/server/api/conversations.post.js +1 -0
  41. package/dist/runtime/server/utils/agent-runtime.js +2 -0
  42. package/dist/runtime/utils/files.d.ts +21 -3
  43. package/dist/runtime/utils/files.js +63 -18
  44. package/dist/types.d.mts +3 -1
  45. package/package.json +2 -2
package/dist/module.d.mts CHANGED
@@ -1,7 +1,8 @@
1
1
  import * as _nuxt_schema from '@nuxt/schema';
2
2
  export { AppAuth, AppEnvField, AppInfo, ChatStatus, FileEntry, SendOptions, UIMessage, UiAction, UseAgentRuntime } from '../dist/runtime/composables/useAgentRuntime.js';
3
3
  export { AgentRuntimeMarkdownRenderOptions } from '../dist/runtime/composables/useAgentRuntimeMarkdown.js';
4
- export { AgentRuntimeFileLike, AgentRuntimeFilePreviewKind } from '../dist/runtime/utils/files.js';
4
+ export { AgentRuntimeFileLike, AgentRuntimeFilePreviewKind, AgentRuntimeResolvedAsset } from '../dist/runtime/utils/files.js';
5
+ export { createAgentRuntimeRequestHeaders, createScopeFingerprint, normalizeAgentRuntimeBaseUrl, resolveAgentRuntimeConfig } from '../dist/runtime/shared.js';
5
6
 
6
7
  /**
7
8
  * Module options for `@d4y/agent-runtime-nuxt`.
package/dist/module.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "compatibility": {
5
5
  "nuxt": ">=3.13.0"
6
6
  },
7
- "version": "0.1.1",
7
+ "version": "0.1.3",
8
8
  "builder": {
9
9
  "@nuxt/module-builder": "1.0.2",
10
10
  "unbuild": "unknown"
package/dist/module.mjs CHANGED
@@ -1,5 +1,6 @@
1
1
  import { defineNuxtModule, createResolver, addImportsDir, addTypeTemplate, addServerHandler } from '@nuxt/kit';
2
2
  import { defu } from 'defu';
3
+ export { createAgentRuntimeRequestHeaders, createScopeFingerprint, normalizeAgentRuntimeBaseUrl, resolveAgentRuntimeConfig } from '../dist/runtime/shared.js';
3
4
 
4
5
  const DEFAULTS = {
5
6
  baseUrl: "http://127.0.0.1:18791",
@@ -35,7 +36,7 @@ const module$1 = defineNuxtModule({
35
36
  `// Auto-generated by @d4y/agent-runtime-nuxt`,
36
37
  `export type { UseAgentRuntime, AppInfo, AppAuth, AppEnvField, FileEntry, UiAction, SendOptions, ChatStatus } from '${resolver.resolve("./runtime/composables/useAgentRuntime")}'`,
37
38
  `export type { AgentRuntimeMarkdownRenderOptions } from '${resolver.resolve("./runtime/composables/useAgentRuntimeMarkdown")}'`,
38
- `export type { AgentRuntimeFileLike, AgentRuntimeFilePreviewKind } from '${resolver.resolve("./runtime/utils/files")}'`,
39
+ `export type { AgentRuntimeFileLike, AgentRuntimeFilePreviewKind, AgentRuntimeResolvedAsset } from '${resolver.resolve("./runtime/utils/files")}'`,
39
40
  ""
40
41
  ].join("\n")
41
42
  });
@@ -53,6 +54,7 @@ const module$1 = defineNuxtModule({
53
54
  route("/conversations/:id/abort", "conversations/[id]/abort.post", "post");
54
55
  route("/conversations/:id/env", "conversations/[id]/env.post", "post");
55
56
  route("/conversations/:id/files", "conversations/[id]/files.get", "get");
57
+ route("/conversations/:id/files/preview/**", "conversations/[id]/files/preview/[...path].get", "get");
56
58
  route("/conversations/:id/files/raw/**", "conversations/[id]/files/raw/[...path].get", "get");
57
59
  }
58
60
  });
@@ -0,0 +1,20 @@
1
+ import { type AgentRuntimeFileLike } from '../utils/files.js';
2
+ type __VLS_Props = {
3
+ open: boolean;
4
+ file?: AgentRuntimeFileLike | null;
5
+ title?: string | null;
6
+ previewSrc?: string | null;
7
+ downloadSrc?: string | null;
8
+ };
9
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
10
+ "update:open": (open: boolean) => any;
11
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
12
+ "onUpdate:open"?: ((open: boolean) => any) | undefined;
13
+ }>, {
14
+ file: AgentRuntimeFileLike | null;
15
+ title: string | null;
16
+ previewSrc: string | null;
17
+ downloadSrc: string | null;
18
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
19
+ declare const _default: typeof __VLS_export;
20
+ export default _default;
@@ -0,0 +1,90 @@
1
+ <script setup>
2
+ import { computed, onBeforeUnmount, watch } from "vue";
3
+ import AgentRuntimeArtifactPreview from "./AgentRuntimeArtifactPreview.vue";
4
+ import { getAgentRuntimeFilePreviewKind } from "../utils/files";
5
+ const props = defineProps({
6
+ open: { type: Boolean, required: true },
7
+ file: { type: [Object, null], required: false, default: null },
8
+ title: { type: [String, null], required: false, default: null },
9
+ previewSrc: { type: [String, null], required: false, default: null },
10
+ downloadSrc: { type: [String, null], required: false, default: null }
11
+ });
12
+ const emit = defineEmits(["update:open"]);
13
+ const previewKind = computed(() => props.file ? getAgentRuntimeFilePreviewKind(props.file) : "download");
14
+ const isPreviewable = computed(() => previewKind.value === "image" || previewKind.value === "html" || previewKind.value === "pdf");
15
+ const dialogTitle = computed(() => props.title ?? props.file?.name ?? props.file?.relPath ?? "Artifact preview");
16
+ const close = () => emit("update:open", false);
17
+ const onBackdropClick = () => close();
18
+ const onKeydown = (event) => {
19
+ if (event.key === "Escape") {
20
+ close();
21
+ }
22
+ };
23
+ watch(() => props.open, (open) => {
24
+ if (!import.meta.client) {
25
+ return;
26
+ }
27
+ if (open) {
28
+ window.addEventListener("keydown", onKeydown);
29
+ return;
30
+ }
31
+ window.removeEventListener("keydown", onKeydown);
32
+ }, { immediate: true });
33
+ onBeforeUnmount(() => {
34
+ if (!import.meta.client) {
35
+ return;
36
+ }
37
+ window.removeEventListener("keydown", onKeydown);
38
+ });
39
+ </script>
40
+
41
+ <template>
42
+ <Teleport to="body">
43
+ <Transition
44
+ enter-active-class="transition duration-200 ease-out"
45
+ enter-from-class="opacity-0"
46
+ enter-to-class="opacity-100"
47
+ leave-active-class="transition duration-150 ease-in"
48
+ leave-from-class="opacity-100"
49
+ leave-to-class="opacity-0">
50
+ <div
51
+ v-if="open"
52
+ class="agent-runtime-artifact-dialog"
53
+ aria-modal="true"
54
+ role="dialog"
55
+ :aria-label="dialogTitle"
56
+ @click="onBackdropClick">
57
+ <div class="agent-runtime-artifact-dialog__viewport">
58
+ <div
59
+ class="agent-runtime-artifact-dialog__surface"
60
+ :class="{ 'agent-runtime-artifact-dialog__surface--preview': isPreviewable }"
61
+ @click.stop>
62
+ <AgentRuntimeArtifactPreview
63
+ v-if="file"
64
+ :file="file"
65
+ :src="previewSrc" />
66
+
67
+ <div
68
+ v-if="downloadSrc && isPreviewable"
69
+ class="agent-runtime-artifact-dialog__actions">
70
+ <UButton
71
+ size="sm"
72
+ color="neutral"
73
+ variant="soft"
74
+ icon="material-symbols:download-rounded"
75
+ label="Download"
76
+ :href="downloadSrc"
77
+ external
78
+ :download="file?.name ?? file?.relPath ?? void 0"
79
+ target="_blank" />
80
+ </div>
81
+ </div>
82
+ </div>
83
+ </div>
84
+ </Transition>
85
+ </Teleport>
86
+ </template>
87
+
88
+ <style scoped>
89
+ .agent-runtime-artifact-dialog{backdrop-filter:blur(4px);background:rgba(24,24,27,.46);inset:0;position:fixed;z-index:200}.agent-runtime-artifact-dialog__viewport{align-items:center;display:flex;justify-content:center;min-height:100vh;padding:1rem;width:100%}.agent-runtime-artifact-dialog__surface{background:transparent;max-width:none;overflow:visible;position:relative;width:min(96vw,72rem)}.agent-runtime-artifact-dialog__surface--preview{align-items:center;display:flex;justify-content:center}.agent-runtime-artifact-dialog__actions{bottom:1rem;left:1rem;position:absolute;z-index:1}
90
+ </style>
@@ -0,0 +1,20 @@
1
+ import { type AgentRuntimeFileLike } from '../utils/files.js';
2
+ type __VLS_Props = {
3
+ open: boolean;
4
+ file?: AgentRuntimeFileLike | null;
5
+ title?: string | null;
6
+ previewSrc?: string | null;
7
+ downloadSrc?: string | null;
8
+ };
9
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
10
+ "update:open": (open: boolean) => any;
11
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
12
+ "onUpdate:open"?: ((open: boolean) => any) | undefined;
13
+ }>, {
14
+ file: AgentRuntimeFileLike | null;
15
+ title: string | null;
16
+ previewSrc: string | null;
17
+ downloadSrc: string | null;
18
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
19
+ declare const _default: typeof __VLS_export;
20
+ export default _default;
@@ -3,12 +3,10 @@ type __VLS_Props = {
3
3
  file: AgentRuntimeFileLike;
4
4
  src?: string | null;
5
5
  resolveSrc?: ((relPath: string) => string | null | undefined) | null;
6
- textMaxChars?: number;
7
6
  };
8
7
  declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
9
8
  src: string | null;
10
9
  resolveSrc: ((relPath: string) => string | null | undefined) | null;
11
- textMaxChars: number;
12
10
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
13
11
  declare const _default: typeof __VLS_export;
14
12
  export default _default;
@@ -1,11 +1,10 @@
1
1
  <script setup>
2
- import { computed, onBeforeUnmount, ref, watch } from "vue";
2
+ import { computed } from "vue";
3
3
  import { getAgentRuntimeFilePreviewKind } from "../utils/files";
4
4
  const props = defineProps({
5
5
  file: { type: Object, required: true },
6
6
  src: { type: [String, null], required: false, default: null },
7
- resolveSrc: { type: [Function, null], required: false, default: null },
8
- textMaxChars: { type: Number, required: false, default: 12e4 }
7
+ resolveSrc: { type: [Function, null], required: false, default: null }
9
8
  });
10
9
  const kind = computed(() => getAgentRuntimeFilePreviewKind(props.file));
11
10
  const resolvedSrc = computed(() => {
@@ -17,54 +16,6 @@ const resolvedSrc = computed(() => {
17
16
  }
18
17
  return props.resolveSrc(props.file.relPath) ?? null;
19
18
  });
20
- const textContent = ref("");
21
- const textError = ref(null);
22
- const textLoading = ref(false);
23
- let textAbortController = null;
24
- const abortTextLoad = () => {
25
- textAbortController?.abort();
26
- textAbortController = null;
27
- };
28
- const loadTextPreview = async () => {
29
- abortTextLoad();
30
- textContent.value = "";
31
- textError.value = null;
32
- if (kind.value !== "text" || !resolvedSrc.value) {
33
- textLoading.value = false;
34
- return;
35
- }
36
- const controller = new AbortController();
37
- textAbortController = controller;
38
- textLoading.value = true;
39
- try {
40
- const response = await fetch(resolvedSrc.value, {
41
- signal: controller.signal
42
- });
43
- if (!response.ok) {
44
- throw new Error(`Failed to load preview (${response.status})`);
45
- }
46
- const body = await response.text();
47
- textContent.value = body.length > props.textMaxChars ? `${body.slice(0, props.textMaxChars).trimEnd()}
48
-
49
- \u2026` : body;
50
- } catch (error) {
51
- if (controller.signal.aborted) {
52
- return;
53
- }
54
- textError.value = error instanceof Error ? error.message : "Failed to load preview";
55
- } finally {
56
- if (textAbortController === controller) {
57
- textAbortController = null;
58
- }
59
- textLoading.value = false;
60
- }
61
- };
62
- watch([kind, resolvedSrc], () => {
63
- void loadTextPreview();
64
- }, { immediate: true });
65
- onBeforeUnmount(() => {
66
- abortTextLoad();
67
- });
68
19
  </script>
69
20
 
70
21
  <template>
@@ -76,22 +27,20 @@ onBeforeUnmount(() => {
76
27
  class="agent-runtime-artifact-preview__image">
77
28
 
78
29
  <iframe
79
- v-else-if="kind === 'pdf' && resolvedSrc"
30
+ v-else-if="kind === 'html' && resolvedSrc"
80
31
  :src="resolvedSrc"
81
- :title="file.name ?? file.relPath ?? 'PDF preview'"
32
+ :title="file.name ?? file.relPath ?? 'Artifact preview'"
33
+ sandbox=""
34
+ referrerpolicy="no-referrer"
82
35
  class="agent-runtime-artifact-preview__frame" />
83
36
 
84
- <div v-else-if="kind === 'text'" class="agent-runtime-artifact-preview__text-shell">
85
- <div v-if="textLoading" class="agent-runtime-artifact-preview__note">
86
- Loading preview…
87
- </div>
88
- <div v-else-if="textError" class="agent-runtime-artifact-preview__note agent-runtime-artifact-preview__note--error">
89
- {{ textError }}
90
- </div>
91
- <pre v-else class="agent-runtime-artifact-preview__text">{{ textContent }}</pre>
92
- </div>
37
+ <iframe
38
+ v-else-if="kind === 'pdf' && resolvedSrc"
39
+ :src="resolvedSrc"
40
+ :title="file.name ?? file.relPath ?? 'Artifact preview'"
41
+ class="agent-runtime-artifact-preview__frame" />
93
42
 
94
- <div v-else class="agent-runtime-artifact-preview__fallback">
43
+ <div v-else-if="kind !== 'blocked'" class="agent-runtime-artifact-preview__fallback">
95
44
  <p class="agent-runtime-artifact-preview__note">
96
45
  Preview unavailable for this file type.
97
46
  </p>
@@ -104,9 +53,15 @@ onBeforeUnmount(() => {
104
53
  Open file
105
54
  </a>
106
55
  </div>
56
+
57
+ <div v-else class="agent-runtime-artifact-preview__fallback">
58
+ <p class="agent-runtime-artifact-preview__note">
59
+ This file type is not available in the user-facing UI.
60
+ </p>
61
+ </div>
107
62
  </div>
108
63
  </template>
109
64
 
110
65
  <style scoped>
111
- .agent-runtime-artifact-preview{display:block;min-height:12rem;width:100%}.agent-runtime-artifact-preview__frame,.agent-runtime-artifact-preview__image{background:rgba(15,23,42,.04);border:0;border-radius:.875rem;display:block;width:100%}.agent-runtime-artifact-preview__image{max-height:min(70vh,48rem);-o-object-fit:contain;object-fit:contain}.agent-runtime-artifact-preview__frame{min-height:min(78vh,52rem)}.agent-runtime-artifact-preview__fallback,.agent-runtime-artifact-preview__text-shell{background:rgba(15,23,42,.03);border:1px solid rgba(15,23,42,.1);border-radius:.875rem;min-height:18rem}.agent-runtime-artifact-preview__text{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:.8rem;line-height:1.55;margin:0;max-height:min(72vh,52rem);overflow:auto;padding:1rem;white-space:pre-wrap;word-break:break-word}.agent-runtime-artifact-preview__note{color:rgba(15,23,42,.72);font-size:.875rem;padding:1rem}.agent-runtime-artifact-preview__note--error{color:#b91c1c}.agent-runtime-artifact-preview__link{color:inherit;display:inline-flex;margin:0 1rem 1rem;text-decoration:underline;text-underline-offset:.18em}
66
+ .agent-runtime-artifact-preview{display:block;min-height:12rem;width:100%}.agent-runtime-artifact-preview__frame,.agent-runtime-artifact-preview__image{background:rgba(15,23,42,.04);border:0;border-radius:.875rem;display:block;width:100%}.agent-runtime-artifact-preview__image{max-height:min(70vh,48rem);-o-object-fit:contain;object-fit:contain}.agent-runtime-artifact-preview__frame{min-height:min(68vh,48rem)}.agent-runtime-artifact-preview__fallback{background:rgba(15,23,42,.03);border:1px solid rgba(15,23,42,.1);border-radius:.875rem;min-height:18rem}.agent-runtime-artifact-preview__note{color:rgba(15,23,42,.72);font-size:.875rem;padding:1rem}.agent-runtime-artifact-preview__link{color:inherit;display:inline-flex;margin:0 1rem 1rem;text-decoration:underline;text-underline-offset:.18em}
112
67
  </style>
@@ -3,12 +3,10 @@ type __VLS_Props = {
3
3
  file: AgentRuntimeFileLike;
4
4
  src?: string | null;
5
5
  resolveSrc?: ((relPath: string) => string | null | undefined) | null;
6
- textMaxChars?: number;
7
6
  };
8
7
  declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
9
8
  src: string | null;
10
9
  resolveSrc: ((relPath: string) => string | null | undefined) | null;
11
- textMaxChars: number;
12
10
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
13
11
  declare const _default: typeof __VLS_export;
14
12
  export default _default;
@@ -0,0 +1,19 @@
1
+ type __VLS_Props = {
2
+ text: string;
3
+ class?: string | Record<string, boolean> | string[];
4
+ streaming?: boolean;
5
+ resolveWorkspacePath?: ((relPath: string) => string | null | undefined) | null;
6
+ resolveWorkspacePreviewPath?: ((relPath: string) => string | null | undefined) | null;
7
+ };
8
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
9
+ renderedChange: (rendered: boolean) => any;
10
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
11
+ onRenderedChange?: ((rendered: boolean) => any) | undefined;
12
+ }>, {
13
+ class: string | Record<string, boolean> | string[];
14
+ streaming: boolean;
15
+ resolveWorkspacePath: ((relPath: string) => string | null | undefined) | null;
16
+ resolveWorkspacePreviewPath: ((relPath: string) => string | null | undefined) | null;
17
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
18
+ declare const _default: typeof __VLS_export;
19
+ export default _default;
@@ -0,0 +1,77 @@
1
+ <script setup>
2
+ import { computed, nextTick, ref, watch } from "vue";
3
+ import { useAgentRuntimeMarkdown } from "../composables/useAgentRuntimeMarkdown";
4
+ import AgentRuntimeArtifactDialog from "./AgentRuntimeArtifactDialog.vue";
5
+ const emit = defineEmits(["renderedChange"]);
6
+ const props = defineProps({
7
+ text: { type: String, required: true },
8
+ class: { type: [String, Object, Array], required: false, default: "" },
9
+ streaming: { type: Boolean, required: false, default: false },
10
+ resolveWorkspacePath: { type: [Function, null], required: false, default: null },
11
+ resolveWorkspacePreviewPath: { type: [Function, null], required: false, default: null }
12
+ });
13
+ const { render } = useAgentRuntimeMarkdown({
14
+ resolveWorkspacePath: (relPath) => props.resolveWorkspacePath?.(relPath) ?? null,
15
+ resolveWorkspacePreviewPath: (relPath) => props.resolveWorkspacePreviewPath?.(relPath) ?? null
16
+ });
17
+ const html = computed(() => props.text ? render(props.text) : "");
18
+ const dialogOpen = ref(false);
19
+ const dialogFile = ref(null);
20
+ const dialogPreviewSrc = ref(null);
21
+ const dialogDownloadSrc = ref(null);
22
+ const rendered = computed(() => html.value.length > 0);
23
+ const onClick = (event) => {
24
+ const target = event.target instanceof HTMLElement ? event.target : null;
25
+ const trigger = target?.closest('[data-agent-runtime-preview="true"]');
26
+ if (!trigger) {
27
+ return;
28
+ }
29
+ const previewUrl = trigger.dataset.agentRuntimePreviewUrl;
30
+ const kind = trigger.dataset.agentRuntimePreviewKind;
31
+ const label = trigger.dataset.agentRuntimePreviewLabel;
32
+ if (!previewUrl || !kind) {
33
+ return;
34
+ }
35
+ event.preventDefault();
36
+ dialogFile.value = {
37
+ name: label ?? void 0,
38
+ relPath: null,
39
+ mimeType: kind === "image" ? "image/*" : kind === "pdf" ? "application/pdf" : "text/html",
40
+ kind
41
+ };
42
+ dialogPreviewSrc.value = previewUrl;
43
+ dialogDownloadSrc.value = trigger.dataset.agentRuntimeDownloadUrl ?? previewUrl;
44
+ dialogOpen.value = true;
45
+ };
46
+ watch(rendered, async (value) => {
47
+ if (!value) {
48
+ emit("renderedChange", false);
49
+ return;
50
+ }
51
+ await nextTick();
52
+ emit("renderedChange", true);
53
+ }, { immediate: true });
54
+ </script>
55
+
56
+ <template>
57
+ <div
58
+ v-if="rendered"
59
+ class="agent-runtime-markdown"
60
+ :class="[props.class, streaming ? 'agent-runtime-markdown--streaming' : '']"
61
+ @click="onClick"
62
+ v-html="
63
+ html
64
+ /* eslint-disable-line vue/no-v-html -- output is escaped by markdown-it; raw HTML disabled */
65
+ " />
66
+
67
+ <AgentRuntimeArtifactDialog
68
+ v-model:open="dialogOpen"
69
+ :file="dialogFile"
70
+ :title="dialogFile?.name ?? dialogFile?.relPath ?? null"
71
+ :preview-src="dialogPreviewSrc"
72
+ :download-src="dialogDownloadSrc" />
73
+ </template>
74
+
75
+ <style scoped>
76
+ .agent-runtime-markdown{line-height:1.65}.agent-runtime-markdown--streaming{animation:agent-runtime-markdown-pulse .16s ease-out}.agent-runtime-markdown :deep(.agent-runtime-md-image-link){cursor:zoom-in;display:inline-block}.agent-runtime-markdown :deep(.agent-runtime-md-image){border-radius:.95rem;display:block;max-height:20rem;max-width:min(100%,30rem);-o-object-fit:contain;object-fit:contain}.agent-runtime-markdown :deep(.agent-runtime-md-file-link){color:inherit;text-decoration:underline;text-underline-offset:.18em}.agent-runtime-markdown :deep(.agent-runtime-md-html-shell),.agent-runtime-markdown :deep(.agent-runtime-md-pdf-shell){background:color-mix(in srgb,var(--ui-bg-elevated,rgba(248,250,252,.9)) 82%,transparent);border:1px solid color-mix(in srgb,var(--ui-border,rgba(148,163,184,.28)) 80%,transparent);border-radius:1rem;display:block;margin-top:.75rem;overflow:hidden}.agent-runtime-markdown :deep(.agent-runtime-md-embed-toolbar){align-items:center;border-bottom:1px solid color-mix(in srgb,var(--ui-border,rgba(148,163,184,.28)) 70%,transparent);display:flex;flex-wrap:wrap;font-size:.75rem;gap:.75rem;justify-content:space-between;padding:.75rem .9rem}.agent-runtime-markdown :deep(.agent-runtime-md-embed-label){font-family:var(--font-mono,ui-monospace,monospace);min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.agent-runtime-markdown :deep(.agent-runtime-md-embed-actions){display:inline-flex;flex-wrap:wrap;gap:.5rem}.agent-runtime-markdown :deep(.agent-runtime-md-download-link),.agent-runtime-markdown :deep(.agent-runtime-md-preview-link){color:inherit;text-decoration:underline;text-underline-offset:.18em}.agent-runtime-markdown :deep(.agent-runtime-md-preview-link[data-agent-runtime-preview=true]){cursor:zoom-in}.agent-runtime-markdown :deep(.agent-runtime-md-html-frame),.agent-runtime-markdown :deep(.agent-runtime-md-pdf-frame){background:color-mix(in srgb,var(--ui-bg,hsla(0,0%,100%,.94)) 85%,transparent);border:0;display:block;height:22rem;width:100%}@keyframes agent-runtime-markdown-pulse{0%{opacity:.88;transform:translateY(1px)}to{opacity:1;transform:translateY(0)}}
77
+ </style>
@@ -0,0 +1,19 @@
1
+ type __VLS_Props = {
2
+ text: string;
3
+ class?: string | Record<string, boolean> | string[];
4
+ streaming?: boolean;
5
+ resolveWorkspacePath?: ((relPath: string) => string | null | undefined) | null;
6
+ resolveWorkspacePreviewPath?: ((relPath: string) => string | null | undefined) | null;
7
+ };
8
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
9
+ renderedChange: (rendered: boolean) => any;
10
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
11
+ onRenderedChange?: ((rendered: boolean) => any) | undefined;
12
+ }>, {
13
+ class: string | Record<string, boolean> | string[];
14
+ streaming: boolean;
15
+ resolveWorkspacePath: ((relPath: string) => string | null | undefined) | null;
16
+ resolveWorkspacePreviewPath: ((relPath: string) => string | null | undefined) | null;
17
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
18
+ declare const _default: typeof __VLS_export;
19
+ export default _default;
@@ -45,6 +45,7 @@ export interface FileEntry {
45
45
  size: number;
46
46
  mtime: string;
47
47
  mimeType: string;
48
+ kind: 'image' | 'html' | 'pdf' | 'download';
48
49
  }
49
50
  /** Per-turn options for `send`. */
50
51
  export interface SendOptions {
@@ -89,6 +90,8 @@ export interface UseAgentRuntime {
89
90
  files: Ref<FileEntry[]>;
90
91
  /** Build the proxied download URL for a workspace-relative path. */
91
92
  fileUrl: (relPath: string) => string;
93
+ /** Build the proxied preview URL for a workspace-relative path. */
94
+ filePreviewUrl: (relPath: string) => string;
92
95
  /** Manually re-fetch the workspace file listing. */
93
96
  refreshFiles: () => Promise<void>;
94
97
  /** Persist a new auth map; if a conversation is open, push the change to its env. */
@@ -61,6 +61,11 @@ export const useAgentRuntime = () => {
61
61
  if (!cid) return "";
62
62
  return `${apiPrefix}/conversations/${cid}/files/raw/${encodeRelPath(relPath)}`;
63
63
  };
64
+ const filePreviewUrl = (relPath) => {
65
+ const cid = conversationId.value;
66
+ if (!cid) return "";
67
+ return `${apiPrefix}/conversations/${cid}/files/preview/${encodeRelPath(relPath)}`;
68
+ };
64
69
  const refreshFiles = async () => {
65
70
  const cid = conversationId.value;
66
71
  if (!cid) {
@@ -296,6 +301,7 @@ export const useAgentRuntime = () => {
296
301
  authReady,
297
302
  files,
298
303
  fileUrl,
304
+ filePreviewUrl,
299
305
  refreshFiles,
300
306
  saveAuth,
301
307
  start,
@@ -1,11 +1,6 @@
1
1
  import MarkdownIt from 'markdown-it';
2
- export interface AgentRuntimeMarkdownRenderOptions {
3
- /**
4
- * Resolve a workspace-relative path (e.g. `outputs/foo.pdf`) to a fetchable
5
- * URL on the runtime backend. Returns null/empty if the path can't be
6
- * resolved, in which case the renderer falls back to the original text.
7
- */
8
- resolveWorkspacePath?: (relPath: string) => string | null | undefined;
2
+ import { type AgentRuntimeAssetResolverOptions } from '../utils/files.js';
3
+ export interface AgentRuntimeMarkdownRenderOptions extends AgentRuntimeAssetResolverOptions {
9
4
  }
10
5
  export declare const createAgentRuntimeMarkdownRenderer: (options?: AgentRuntimeMarkdownRenderOptions) => MarkdownIt;
11
6
  export declare const useAgentRuntimeMarkdown: (options?: AgentRuntimeMarkdownRenderOptions) => {
@@ -1,10 +1,70 @@
1
1
  import MarkdownIt from "markdown-it";
2
- import {
3
- isAgentRuntimeImagePath,
4
- resolveAgentRuntimeWorkspaceUri,
5
- toAgentRuntimeWorkspaceRelativePath
6
- } from "../utils/files.js";
7
- const BARE_WORKSPACE_PATH_RE = /(sandbox:[^\s)\]"'<>]+|\/workspace\/[^\s)\]"'<>]+)/g;
2
+ import { resolveAgentRuntimeWorkspaceAsset, resolveAgentRuntimeWorkspaceUri } from "../utils/files.js";
3
+ const escapeAttr = (md, value) => md.utils.escapeHtml(value);
4
+ const previewAttrs = (md, asset) => {
5
+ if (!asset.canPreview || !asset.previewUrl) {
6
+ return "";
7
+ }
8
+ const attrs = [
9
+ ["data-agent-runtime-preview", "true"],
10
+ ["data-agent-runtime-preview-kind", asset.kind],
11
+ ["data-agent-runtime-preview-label", asset.label],
12
+ ["data-agent-runtime-preview-url", asset.previewUrl]
13
+ ];
14
+ if (asset.canDownload && asset.rawUrl) {
15
+ attrs.push(["data-agent-runtime-download-url", asset.rawUrl]);
16
+ }
17
+ return attrs.map(([name, value]) => `${name}="${escapeAttr(md, value)}"`).join(" ");
18
+ };
19
+ const buildDownloadLinkMarkup = (md, asset, label) => {
20
+ if (!asset.canDownload || !asset.rawUrl) {
21
+ return escapeAttr(md, label);
22
+ }
23
+ return `<a href="${escapeAttr(md, asset.rawUrl)}" target="_blank" rel="noopener noreferrer" class="agent-runtime-md-file-link">${escapeAttr(md, label)}</a>`;
24
+ };
25
+ const buildImageMarkup = (md, asset, alt, title) => {
26
+ const previewUrl = asset.previewUrl ?? asset.rawUrl;
27
+ if (!previewUrl) {
28
+ return escapeAttr(md, alt || asset.label);
29
+ }
30
+ const safeAlt = escapeAttr(md, alt || asset.label);
31
+ const safePreviewUrl = escapeAttr(md, previewUrl);
32
+ const titleAttr = title ? ` title="${escapeAttr(md, title)}"` : "";
33
+ const attrs = previewAttrs(md, asset);
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
+ };
36
+ const buildPreviewShellMarkup = (md, asset, label) => {
37
+ const previewUrl = asset.previewUrl;
38
+ if (!previewUrl) {
39
+ return buildDownloadLinkMarkup(md, asset, label);
40
+ }
41
+ const safeLabel = escapeAttr(md, label);
42
+ const safePreviewUrl = escapeAttr(md, previewUrl);
43
+ const attrs = previewAttrs(md, asset);
44
+ const shellClass = asset.kind === "pdf" ? "agent-runtime-md-pdf-shell" : "agent-runtime-md-html-shell";
45
+ const frameClass = asset.kind === "pdf" ? "agent-runtime-md-pdf-frame" : "agent-runtime-md-html-frame";
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>`;
50
+ };
51
+ const toHtmlToken = (state, markup) => {
52
+ const token = new state.Token("html_inline", "", 0);
53
+ token.content = markup;
54
+ return token;
55
+ };
56
+ const renderWorkspaceLink = (md, asset, label) => {
57
+ if (asset.kind === "blocked") {
58
+ return escapeAttr(md, label);
59
+ }
60
+ if (asset.kind === "image") {
61
+ return buildImageMarkup(md, asset, label);
62
+ }
63
+ if (asset.kind === "html" || asset.kind === "pdf") {
64
+ return buildPreviewShellMarkup(md, asset, label);
65
+ }
66
+ return buildDownloadLinkMarkup(md, asset, label);
67
+ };
8
68
  export const createAgentRuntimeMarkdownRenderer = (options = {}) => {
9
69
  const md = new MarkdownIt({
10
70
  html: false,
@@ -12,15 +72,15 @@ export const createAgentRuntimeMarkdownRenderer = (options = {}) => {
12
72
  breaks: true,
13
73
  typographer: false
14
74
  });
15
- const resolve = (uri) => resolveAgentRuntimeWorkspaceUri(uri, options.resolveWorkspacePath);
75
+ const resolveAsset = (uri) => resolveAgentRuntimeWorkspaceAsset(uri, options);
16
76
  const defaultLinkOpen = md.renderer.rules.link_open ?? ((tokens, idx, opts, _env, self) => self.renderToken(tokens, idx, opts));
17
77
  md.renderer.rules.link_open = (tokens, idx, opts, env, self) => {
18
78
  const token = tokens[idx];
19
79
  if (token) {
20
80
  const href = token.attrGet("href") ?? "";
21
- const rewritten = resolve(href);
22
- if (rewritten) {
23
- token.attrSet("href", rewritten);
81
+ const resolved = resolveAgentRuntimeWorkspaceUri(href, options.resolveWorkspacePath);
82
+ if (resolved) {
83
+ token.attrSet("href", resolved);
24
84
  token.attrSet("target", "_blank");
25
85
  token.attrSet("rel", "noopener noreferrer");
26
86
  token.attrSet("class", "agent-runtime-md-file-link");
@@ -34,64 +94,42 @@ export const createAgentRuntimeMarkdownRenderer = (options = {}) => {
34
94
  md.renderer.rules.image = (tokens, idx) => {
35
95
  const token = tokens[idx];
36
96
  if (!token) return "";
37
- const rawSrc = token.attrGet("src") ?? "";
38
- const rewritten = resolve(rawSrc);
39
- const src = rewritten ?? rawSrc;
97
+ const src = token.attrGet("src") ?? "";
40
98
  const alt = token.content || "";
41
99
  const title = token.attrGet("title");
42
- const titleAttr = title ? ` title="${md.utils.escapeHtml(title)}"` : "";
43
- const safeAlt = md.utils.escapeHtml(alt);
44
- const safeSrc = md.utils.escapeHtml(src);
45
- const looksLikeImage = isAgentRuntimeImagePath(rawSrc) || isAgentRuntimeImagePath(src);
46
- if (!looksLikeImage && rewritten) {
47
- return `<a href="${safeSrc}" target="_blank" rel="noopener noreferrer" class="agent-runtime-md-file-link">${safeAlt || safeSrc}</a>`;
100
+ const asset = resolveAsset(src);
101
+ if (!asset) {
102
+ const safeSrc = escapeAttr(md, src);
103
+ const safeAlt = escapeAttr(md, alt);
104
+ const titleAttr = title ? ` title="${escapeAttr(md, title)}"` : "";
105
+ return `<img src="${safeSrc}" alt="${safeAlt}" loading="lazy" class="agent-runtime-md-image"${titleAttr} />`;
106
+ }
107
+ if (asset.kind === "image") {
108
+ return buildImageMarkup(md, asset, alt, title);
48
109
  }
49
- return `<a href="${safeSrc}" target="_blank" rel="noopener noreferrer" class="agent-runtime-md-image-link"><img src="${safeSrc}" alt="${safeAlt}" loading="lazy" class="agent-runtime-md-image"${titleAttr} /></a>`;
110
+ return renderWorkspaceLink(md, asset, alt || asset.label);
50
111
  };
51
- md.core.ruler.after("inline", "agent_runtime_workspace_autolink", (state) => {
112
+ md.core.ruler.after("inline", "agent_runtime_workspace_links", (state) => {
52
113
  for (const blockToken of state.tokens) {
53
114
  if (blockToken.type !== "inline" || !blockToken.children) continue;
54
115
  const out = [];
55
- for (const child of blockToken.children) {
56
- if (child.type !== "text") {
57
- out.push(child);
116
+ const children = blockToken.children;
117
+ for (let idx = 0; idx < children.length; idx++) {
118
+ const child = children[idx];
119
+ if (!child) {
58
120
  continue;
59
121
  }
60
- const text = child.content;
61
- BARE_WORKSPACE_PATH_RE.lastIndex = 0;
62
- if (!BARE_WORKSPACE_PATH_RE.test(text)) {
122
+ const next = children[idx + 1];
123
+ const nextNext = children[idx + 2];
124
+ const href = child.type === "link_open" && typeof child.attrGet === "function" ? child.attrGet("href") ?? "" : "";
125
+ const asset = href ? resolveAsset(href) : null;
126
+ const isWorkspaceLink = asset && child.type === "link_open" && next?.type === "text" && nextNext?.type === "link_close";
127
+ if (!isWorkspaceLink) {
63
128
  out.push(child);
64
129
  continue;
65
130
  }
66
- BARE_WORKSPACE_PATH_RE.lastIndex = 0;
67
- let lastIndex = 0;
68
- let match;
69
- while ((match = BARE_WORKSPACE_PATH_RE.exec(text)) !== null) {
70
- const uri = match[0];
71
- const url = resolve(uri);
72
- if (!url) continue;
73
- if (match.index > lastIndex) {
74
- const before = new state.Token("text", "", 0);
75
- before.content = text.slice(lastIndex, match.index);
76
- out.push(before);
77
- }
78
- const open = new state.Token("link_open", "a", 1);
79
- open.attrSet("href", url);
80
- open.attrSet("target", "_blank");
81
- open.attrSet("rel", "noopener noreferrer");
82
- open.attrSet("class", "agent-runtime-md-file-link");
83
- const inner = new state.Token("text", "", 0);
84
- const relPath = toAgentRuntimeWorkspaceRelativePath(uri) ?? uri;
85
- inner.content = relPath.split("/").pop() || relPath;
86
- const close = new state.Token("link_close", "a", -1);
87
- out.push(open, inner, close);
88
- lastIndex = match.index + uri.length;
89
- }
90
- if (lastIndex < text.length) {
91
- const tail = new state.Token("text", "", 0);
92
- tail.content = text.slice(lastIndex);
93
- out.push(tail);
94
- }
131
+ out.push(toHtmlToken(state, renderWorkspaceLink(md, asset, next.content || asset.label)));
132
+ idx += 2;
95
133
  }
96
134
  blockToken.children = out;
97
135
  }
@@ -1,5 +1,7 @@
1
1
  export type { AgentRuntimeMarkdownRenderOptions } from './composables/useAgentRuntimeMarkdown.js';
2
- export type { AgentRuntimeFileLike, AgentRuntimeFilePreviewKind } from './utils/files.js';
2
+ export type { AgentRuntimeFileLike, AgentRuntimeFilePreviewKind, AgentRuntimeResolvedAsset } from './utils/files.js';
3
+ export { default as AgentRuntimeArtifactDialog } from './components/AgentRuntimeArtifactDialog.vue.js';
3
4
  export { default as AgentRuntimeArtifactPreview } from './components/AgentRuntimeArtifactPreview.vue.js';
5
+ export { default as AgentRuntimeMarkdown } from './components/AgentRuntimeMarkdown.vue.js';
4
6
  export { createAgentRuntimeMarkdownRenderer, useAgentRuntimeMarkdown } from './composables/useAgentRuntimeMarkdown.js';
5
- export { canPreviewAgentRuntimeFileInline, getAgentRuntimeFilePreviewKind, isAgentRuntimeImageMimeType, isAgentRuntimeImagePath, isAgentRuntimePdfMimeType, isAgentRuntimePdfPath, isAgentRuntimeTextMimeType, isAgentRuntimeTextPath, resolveAgentRuntimeWorkspaceUri, toAgentRuntimeWorkspaceRelativePath, } from './utils/files.js';
7
+ export { canPreviewAgentRuntimeFileInline, isAgentRuntimeBlockedMimeType, isAgentRuntimeBlockedPath, isAgentRuntimeHtmlMimeType, isAgentRuntimeHtmlPath, getAgentRuntimeFilePreviewKind, isAgentRuntimeImageMimeType, isAgentRuntimeImagePath, isAgentRuntimePdfMimeType, isAgentRuntimePdfPath, resolveAgentRuntimeFileAsset, resolveAgentRuntimeWorkspaceUri, resolveAgentRuntimeWorkspaceAsset, toAgentRuntimeWorkspaceRelativePath, } from './utils/files.js';
@@ -1,14 +1,20 @@
1
+ export { default as AgentRuntimeArtifactDialog } from "./components/AgentRuntimeArtifactDialog.vue";
1
2
  export { default as AgentRuntimeArtifactPreview } from "./components/AgentRuntimeArtifactPreview.vue";
3
+ export { default as AgentRuntimeMarkdown } from "./components/AgentRuntimeMarkdown.vue";
2
4
  export { createAgentRuntimeMarkdownRenderer, useAgentRuntimeMarkdown } from "./composables/useAgentRuntimeMarkdown.js";
3
5
  export {
4
6
  canPreviewAgentRuntimeFileInline,
7
+ isAgentRuntimeBlockedMimeType,
8
+ isAgentRuntimeBlockedPath,
9
+ isAgentRuntimeHtmlMimeType,
10
+ isAgentRuntimeHtmlPath,
5
11
  getAgentRuntimeFilePreviewKind,
6
12
  isAgentRuntimeImageMimeType,
7
13
  isAgentRuntimeImagePath,
8
14
  isAgentRuntimePdfMimeType,
9
15
  isAgentRuntimePdfPath,
10
- isAgentRuntimeTextMimeType,
11
- isAgentRuntimeTextPath,
16
+ resolveAgentRuntimeFileAsset,
12
17
  resolveAgentRuntimeWorkspaceUri,
18
+ resolveAgentRuntimeWorkspaceAsset,
13
19
  toAgentRuntimeWorkspaceRelativePath
14
20
  } from "./utils/files.js";
@@ -1,6 +1,15 @@
1
+ interface AppEnvField {
2
+ required?: boolean;
3
+ secret?: boolean;
4
+ description?: string;
5
+ }
1
6
  /**
2
7
  * GET `${apiPrefix}/app` — returns the manifest of the configured app
3
8
  * (so the client can render its env-schema form generically).
4
9
  */
5
- declare const _default: any;
10
+ declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<{
11
+ appId: string;
12
+ name: string;
13
+ envSchema: Record<string, AppEnvField>;
14
+ }>>;
6
15
  export default _default;
@@ -1,3 +1,4 @@
1
+ import { createError, defineEventHandler } from "h3";
1
2
  import { agentRuntime, agentRuntimeHeaders } from "../utils/agent-runtime.js";
2
3
  export default defineEventHandler(async () => {
3
4
  const cfg = agentRuntime();
@@ -2,5 +2,5 @@
2
2
  * POST `${apiPrefix}/conversations/:id/abort` — cancels the in-flight
3
3
  * agent run for this conversation. Returns 204 on success.
4
4
  */
5
- declare const _default: any;
5
+ declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<null>>;
6
6
  export default _default;
@@ -1,3 +1,4 @@
1
+ import { createError, defineEventHandler, getRouterParam, setResponseStatus } from "h3";
1
2
  import { agentRuntime, agentRuntimeHeaders } from "../../../utils/agent-runtime.js";
2
3
  export default defineEventHandler(async (event) => {
3
4
  const cfg = agentRuntime();
@@ -6,5 +6,5 @@
6
6
  * Triggers any `bootstrap[]` step on the harness side whose `rerunOn` keys
7
7
  * intersect the changed set, so this is also how you re-import auth.
8
8
  */
9
- declare const _default: any;
9
+ declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<any>>;
10
10
  export default _default;
@@ -1,3 +1,4 @@
1
+ import { createError, defineEventHandler, getRouterParam, readBody } from "h3";
1
2
  import { agentRuntime, agentRuntimeHeaders } from "../../../utils/agent-runtime.js";
2
3
  export default defineEventHandler(async (event) => {
3
4
  const cfg = agentRuntime();
@@ -0,0 +1,2 @@
1
+ declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<void>>;
2
+ export default _default;
@@ -0,0 +1,42 @@
1
+ import { Readable } from "node:stream";
2
+ import { useRuntimeConfig } from "#imports";
3
+ import { createError, defineEventHandler, getRouterParam, sendStream, setHeader } from "h3";
4
+ import { agentRuntime } from "../../../../../utils/agent-runtime.js";
5
+ const PASS_THROUGH_HEADERS = [
6
+ "content-type",
7
+ "content-length",
8
+ "content-disposition",
9
+ "cache-control",
10
+ "content-security-policy",
11
+ "x-content-type-options"
12
+ ];
13
+ export default defineEventHandler(async (event) => {
14
+ const cfg = agentRuntime();
15
+ const id = getRouterParam(event, "id");
16
+ if (!id) throw createError({ statusCode: 400, statusMessage: "missing conversation id" });
17
+ const apiPrefix = (useRuntimeConfig().public.agentRuntime?.apiPrefix ?? "/api/agent-runtime").replace(/\/+$/, "");
18
+ const marker = `${apiPrefix}/conversations/${id}/files/preview/`;
19
+ const idx = event.path.indexOf(marker);
20
+ const tail = idx >= 0 ? event.path.slice(idx + marker.length) : "";
21
+ if (!tail) throw createError({ statusCode: 400, statusMessage: "missing file path" });
22
+ const upstreamUrl = `${cfg.baseUrl}/v1/conversations/${id}/files/preview/${tail}`;
23
+ const controller = new AbortController();
24
+ event.node.req.on("close", () => controller.abort());
25
+ const upstream = await fetch(upstreamUrl, {
26
+ headers: { "X-Agent-Runtime-App-Key": cfg.appKey },
27
+ signal: controller.signal
28
+ });
29
+ if (!upstream.ok || !upstream.body) {
30
+ throw createError({
31
+ statusCode: upstream.status || 502,
32
+ statusMessage: `agent-runtime file preview failed: ${upstream.status} ${upstream.statusText}`
33
+ });
34
+ }
35
+ for (const name of PASS_THROUGH_HEADERS) {
36
+ const value = upstream.headers.get(name);
37
+ if (value) {
38
+ setHeader(event, name, value);
39
+ }
40
+ }
41
+ return sendStream(event, Readable.fromWeb(upstream.body));
42
+ });
@@ -7,5 +7,5 @@
7
7
  * forwarded verbatim — re-encoding after Nitro's decoding step is fragile
8
8
  * for filenames containing spaces or unicode.
9
9
  */
10
- declare const _default: any;
10
+ declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<void>>;
11
11
  export default _default;
@@ -1,4 +1,6 @@
1
1
  import { Readable } from "node:stream";
2
+ import { useRuntimeConfig } from "#imports";
3
+ import { createError, defineEventHandler, getRouterParam, sendStream, setHeader } from "h3";
2
4
  import { agentRuntime } from "../../../../../utils/agent-runtime.js";
3
5
  const PASS_THROUGH_HEADERS = ["content-type", "content-length", "content-disposition", "cache-control"];
4
6
  export default defineEventHandler(async (event) => {
@@ -5,10 +5,13 @@ export interface FileEntry {
5
5
  size: number;
6
6
  mtime: string;
7
7
  mimeType: string;
8
+ kind: 'image' | 'html' | 'pdf' | 'download';
8
9
  }
9
10
  /**
10
11
  * GET `${apiPrefix}/conversations/:id/files` — lists the workspace files
11
12
  * the agent has produced (or read in) so the UI can show a side panel.
12
13
  */
13
- declare const _default: any;
14
+ declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<{
15
+ items: FileEntry[];
16
+ }>>;
14
17
  export default _default;
@@ -1,3 +1,4 @@
1
+ import { createError, defineEventHandler, getRouterParam } from "h3";
1
2
  import { agentRuntime } from "../../../utils/agent-runtime.js";
2
3
  export default defineEventHandler(async (event) => {
3
4
  const cfg = agentRuntime();
@@ -1,2 +1,2 @@
1
- declare const _default: any;
1
+ declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<any>>;
2
2
  export default _default;
@@ -1,3 +1,4 @@
1
+ import { createError, defineEventHandler, getRouterParam } from "h3";
1
2
  import { agentRuntime } from "../../../utils/agent-runtime.js";
2
3
  export default defineEventHandler(async (event) => {
3
4
  const cfg = agentRuntime();
@@ -3,5 +3,5 @@
3
3
  * The optional `context` field is forwarded verbatim to agent-runtime, where
4
4
  * the agent picks it up as per-turn dynamic context.
5
5
  */
6
- declare const _default: any;
6
+ declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<any>>;
7
7
  export default _default;
@@ -1,3 +1,4 @@
1
+ import { createError, defineEventHandler, getRouterParam, readBody } from "h3";
1
2
  import { agentRuntime, agentRuntimeHeaders } from "../../../utils/agent-runtime.js";
2
3
  export default defineEventHandler(async (event) => {
3
4
  const cfg = agentRuntime();
@@ -5,5 +5,5 @@
5
5
  * Closing the client connection aborts the upstream fetch so we don't leave
6
6
  * orphaned conversations hanging.
7
7
  */
8
- declare const _default: any;
8
+ declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<void>>;
9
9
  export default _default;
@@ -1,4 +1,5 @@
1
1
  import { Readable } from "node:stream";
2
+ import { createError, defineEventHandler, getRouterParam, sendStream, setHeader } from "h3";
2
3
  import { agentRuntime } from "../../../utils/agent-runtime.js";
3
4
  export default defineEventHandler(async (event) => {
4
5
  const cfg = agentRuntime();
@@ -1,2 +1,2 @@
1
- declare const _default: any;
1
+ declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<null>>;
2
2
  export default _default;
@@ -1,3 +1,4 @@
1
+ import { createError, defineEventHandler, getRouterParam, setResponseStatus } from "h3";
1
2
  import { agentRuntime } from "../../utils/agent-runtime.js";
2
3
  export default defineEventHandler(async (event) => {
3
4
  const cfg = agentRuntime();
@@ -3,5 +3,5 @@
3
3
  * configured app, optionally seeded with an `env` map (which the harness
4
4
  * uses to populate the workspace environment) and a model override.
5
5
  */
6
- declare const _default: any;
6
+ declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<any>>;
7
7
  export default _default;
@@ -1,3 +1,4 @@
1
+ import { createError, defineEventHandler, readBody } from "h3";
1
2
  import { agentRuntime, agentRuntimeHeaders } from "../utils/agent-runtime.js";
2
3
  export default defineEventHandler(async (event) => {
3
4
  const cfg = agentRuntime();
@@ -1,3 +1,5 @@
1
+ import { useRuntimeConfig } from "#imports";
2
+ import { createError } from "h3";
1
3
  import { createAgentRuntimeRequestHeaders, resolveAgentRuntimeConfig } from "../../shared.js";
2
4
  export const agentRuntime = () => {
3
5
  try {
@@ -2,15 +2,33 @@ export interface AgentRuntimeFileLike {
2
2
  name?: string | null;
3
3
  relPath?: string | null;
4
4
  mimeType?: string | null;
5
+ kind?: AgentRuntimeFilePreviewKind | null;
6
+ }
7
+ export type AgentRuntimeFilePreviewKind = 'image' | 'html' | 'pdf' | 'download' | 'blocked';
8
+ export interface AgentRuntimeResolvedAsset extends AgentRuntimeFileLike {
9
+ kind: AgentRuntimeFilePreviewKind;
10
+ label: string;
11
+ relPath: string | null;
12
+ rawUrl: string | null;
13
+ previewUrl: string | null;
14
+ canPreview: boolean;
15
+ canDownload: boolean;
16
+ }
17
+ export interface AgentRuntimeAssetResolverOptions {
18
+ resolveWorkspacePath?: (relPath: string) => string | null | undefined;
19
+ resolveWorkspacePreviewPath?: (relPath: string) => string | null | undefined;
5
20
  }
6
- export type AgentRuntimeFilePreviewKind = 'image' | 'pdf' | 'text' | 'download';
7
21
  export declare const toAgentRuntimeWorkspaceRelativePath: (uri: string) => string | null;
8
22
  export declare const resolveAgentRuntimeWorkspaceUri: (uri: string, resolveWorkspacePath?: (relPath: string) => string | null | undefined) => string | null;
23
+ export declare const isAgentRuntimeBlockedMimeType: (mimeType: string | null | undefined) => boolean;
9
24
  export declare const isAgentRuntimeImageMimeType: (mimeType: string | null | undefined) => boolean;
25
+ export declare const isAgentRuntimeHtmlMimeType: (mimeType: string | null | undefined) => boolean;
10
26
  export declare const isAgentRuntimePdfMimeType: (mimeType: string | null | undefined) => boolean;
11
- export declare const isAgentRuntimeTextMimeType: (mimeType: string | null | undefined) => boolean;
12
27
  export declare const isAgentRuntimeImagePath: (value: string | null | undefined) => boolean;
28
+ export declare const isAgentRuntimeHtmlPath: (value: string | null | undefined) => boolean;
13
29
  export declare const isAgentRuntimePdfPath: (value: string | null | undefined) => boolean;
14
- export declare const isAgentRuntimeTextPath: (value: string | null | undefined) => boolean;
30
+ export declare const isAgentRuntimeBlockedPath: (value: string | null | undefined) => boolean;
15
31
  export declare const getAgentRuntimeFilePreviewKind: (file: AgentRuntimeFileLike) => AgentRuntimeFilePreviewKind;
16
32
  export declare const canPreviewAgentRuntimeFileInline: (file: AgentRuntimeFileLike) => boolean;
33
+ export declare const resolveAgentRuntimeFileAsset: (file: AgentRuntimeFileLike, options?: AgentRuntimeAssetResolverOptions) => AgentRuntimeResolvedAsset;
34
+ export declare const resolveAgentRuntimeWorkspaceAsset: (uri: string, options?: AgentRuntimeAssetResolverOptions) => AgentRuntimeResolvedAsset | null;
@@ -1,42 +1,87 @@
1
1
  const IMAGE_EXT_RE = /\.(?:png|jpe?g|gif|webp|svg|avif|bmp)(?:[?#].*)?$/i;
2
+ const HTML_EXT_RE = /\.html?(?:[?#].*)?$/i;
2
3
  const PDF_EXT_RE = /\.pdf(?:[?#].*)?$/i;
3
- const TEXT_EXT_RE = /\.(?:txt|md|json|csv|tsv|xml|ya?ml|log|html?|css|js|mjs|cjs|ts|tsx|jsx|vue|py|rb|sh|sql)(?:[?#].*)?$/i;
4
+ const DOWNLOAD_EXT_RE = /\.(?:txt|md|json|csv|tsv|xml|ya?ml|log)(?:[?#].*)?$/i;
5
+ const BLOCKED_EXT_RE = /\.(?:py|rb|pl|php|sh|bash|zsh|fish|ps1|bat|cmd|js|mjs|cjs|ts|tsx|jsx|vue)(?:[?#].*)?$/i;
6
+ const BLOCKED_MIME_RE = /^(?:application\/javascript|application\/typescript|application\/x-httpd-php|application\/x-python-code|application\/x-sh|text\/javascript|text\/x-python|text\/x-script\.python|text\/x-shellscript|text\/x-typescript)(?:$|;)/i;
7
+ const PREVIEWABLE_KINDS = /* @__PURE__ */ new Set(["image", "html", "pdf"]);
8
+ const FILE_KIND_ORDER = ["image", "html", "pdf", "download", "blocked"];
9
+ const basename = (value) => value.split("/").pop() || value;
10
+ const normalizeKind = (value) => typeof value === "string" && FILE_KIND_ORDER.includes(value) ? value : null;
4
11
  export const toAgentRuntimeWorkspaceRelativePath = (uri) => {
5
- if (!uri) return null;
6
- if (uri.startsWith("sandbox:")) return uri.slice("sandbox:".length).replace(/^\/+/, "");
7
- if (uri.startsWith("/workspace/")) return uri.slice("/workspace/".length);
8
- if (uri === "/workspace") return "";
9
- if (uri.startsWith("workspace/")) return uri.slice("workspace/".length);
10
- return null;
12
+ if (!uri.startsWith("sandbox:")) {
13
+ return null;
14
+ }
15
+ const relPath = uri.slice("sandbox:".length).replace(/^\/+/, "");
16
+ return relPath.length > 0 ? relPath : null;
11
17
  };
12
18
  export const resolveAgentRuntimeWorkspaceUri = (uri, resolveWorkspacePath) => {
13
19
  const relPath = toAgentRuntimeWorkspaceRelativePath(uri);
14
- if (relPath === null) return null;
15
- const resolved = resolveWorkspacePath?.(relPath);
16
- return resolved || null;
20
+ if (relPath === null) {
21
+ return null;
22
+ }
23
+ return resolveWorkspacePath?.(relPath) ?? null;
17
24
  };
25
+ export const isAgentRuntimeBlockedMimeType = (mimeType) => typeof mimeType === "string" && BLOCKED_MIME_RE.test(mimeType);
18
26
  export const isAgentRuntimeImageMimeType = (mimeType) => typeof mimeType === "string" && /^image\//i.test(mimeType);
27
+ export const isAgentRuntimeHtmlMimeType = (mimeType) => typeof mimeType === "string" && /^text\/html(?:$|;)/i.test(mimeType);
19
28
  export const isAgentRuntimePdfMimeType = (mimeType) => typeof mimeType === "string" && mimeType.toLowerCase() === "application/pdf";
20
- export const isAgentRuntimeTextMimeType = (mimeType) => {
29
+ export const isAgentRuntimeImagePath = (value) => typeof value === "string" && IMAGE_EXT_RE.test(value);
30
+ export const isAgentRuntimeHtmlPath = (value) => typeof value === "string" && HTML_EXT_RE.test(value);
31
+ export const isAgentRuntimePdfPath = (value) => typeof value === "string" && PDF_EXT_RE.test(value);
32
+ export const isAgentRuntimeBlockedPath = (value) => typeof value === "string" && BLOCKED_EXT_RE.test(value);
33
+ const isAgentRuntimeDownloadMimeType = (mimeType) => {
21
34
  if (typeof mimeType !== "string" || !mimeType) {
22
35
  return false;
23
36
  }
24
37
  const normalized = mimeType.toLowerCase();
25
- return normalized.startsWith("text/") || normalized === "application/json" || normalized === "application/ld+json" || normalized === "application/xml" || normalized === "application/yaml" || normalized === "application/x-yaml";
38
+ return normalized.startsWith("text/") && !isAgentRuntimeHtmlMimeType(normalized) && !isAgentRuntimeBlockedMimeType(normalized) || normalized === "application/json" || normalized === "application/ld+json" || normalized === "application/xml" || normalized === "application/yaml" || normalized === "application/x-yaml";
26
39
  };
27
- export const isAgentRuntimeImagePath = (value) => typeof value === "string" && IMAGE_EXT_RE.test(value);
28
- export const isAgentRuntimePdfPath = (value) => typeof value === "string" && PDF_EXT_RE.test(value);
29
- export const isAgentRuntimeTextPath = (value) => typeof value === "string" && TEXT_EXT_RE.test(value);
40
+ const isAgentRuntimeDownloadPath = (value) => typeof value === "string" && DOWNLOAD_EXT_RE.test(value);
30
41
  export const getAgentRuntimeFilePreviewKind = (file) => {
42
+ const explicitKind = normalizeKind(file.kind);
43
+ if (explicitKind) {
44
+ return explicitKind;
45
+ }
46
+ if (isAgentRuntimeBlockedMimeType(file.mimeType) || isAgentRuntimeBlockedPath(file.name) || isAgentRuntimeBlockedPath(file.relPath)) {
47
+ return "blocked";
48
+ }
31
49
  if (isAgentRuntimeImageMimeType(file.mimeType) || isAgentRuntimeImagePath(file.name) || isAgentRuntimeImagePath(file.relPath)) {
32
50
  return "image";
33
51
  }
52
+ if (isAgentRuntimeHtmlMimeType(file.mimeType) || isAgentRuntimeHtmlPath(file.name) || isAgentRuntimeHtmlPath(file.relPath)) {
53
+ return "html";
54
+ }
34
55
  if (isAgentRuntimePdfMimeType(file.mimeType) || isAgentRuntimePdfPath(file.name) || isAgentRuntimePdfPath(file.relPath)) {
35
56
  return "pdf";
36
57
  }
37
- if (isAgentRuntimeTextMimeType(file.mimeType) || isAgentRuntimeTextPath(file.name) || isAgentRuntimeTextPath(file.relPath)) {
38
- return "text";
58
+ if (isAgentRuntimeDownloadMimeType(file.mimeType) || isAgentRuntimeDownloadPath(file.name) || isAgentRuntimeDownloadPath(file.relPath)) {
59
+ return "download";
39
60
  }
40
61
  return "download";
41
62
  };
42
- export const canPreviewAgentRuntimeFileInline = (file) => getAgentRuntimeFilePreviewKind(file) !== "download";
63
+ export const canPreviewAgentRuntimeFileInline = (file) => PREVIEWABLE_KINDS.has(getAgentRuntimeFilePreviewKind(file));
64
+ export const resolveAgentRuntimeFileAsset = (file, options = {}) => {
65
+ const relPath = file.relPath ?? null;
66
+ const kind = getAgentRuntimeFilePreviewKind(file);
67
+ const label = file.name ?? (relPath ? basename(relPath) : "Attachment");
68
+ const rawUrl = relPath ? options.resolveWorkspacePath?.(relPath) || null : null;
69
+ const previewUrl = relPath && PREVIEWABLE_KINDS.has(kind) ? options.resolveWorkspacePreviewPath?.(relPath) || rawUrl : null;
70
+ return {
71
+ ...file,
72
+ kind,
73
+ label,
74
+ relPath,
75
+ rawUrl,
76
+ previewUrl,
77
+ canPreview: kind !== "blocked" && previewUrl !== null,
78
+ canDownload: kind !== "blocked" && rawUrl !== null
79
+ };
80
+ };
81
+ export const resolveAgentRuntimeWorkspaceAsset = (uri, options = {}) => {
82
+ const relPath = toAgentRuntimeWorkspaceRelativePath(uri);
83
+ if (relPath === null) {
84
+ return null;
85
+ }
86
+ return resolveAgentRuntimeFileAsset({ relPath, name: basename(relPath) }, options);
87
+ };
package/dist/types.d.mts CHANGED
@@ -2,7 +2,9 @@ export { type AppAuth, type AppEnvField, type AppInfo, type ChatStatus, type Fil
2
2
 
3
3
  export { type AgentRuntimeMarkdownRenderOptions } from '../dist/runtime/composables/useAgentRuntimeMarkdown.js'
4
4
 
5
- export { type AgentRuntimeFileLike, type AgentRuntimeFilePreviewKind } from '../dist/runtime/utils/files.js'
5
+ export { type AgentRuntimeFileLike, type AgentRuntimeFilePreviewKind, type AgentRuntimeResolvedAsset } from '../dist/runtime/utils/files.js'
6
+
7
+ export { type createAgentRuntimeRequestHeaders, type createScopeFingerprint, type normalizeAgentRuntimeBaseUrl, type resolveAgentRuntimeConfig } from '../dist/runtime/shared.js'
6
8
 
7
9
  export { default } from './module.mjs'
8
10
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@d4y/agent-runtime-nuxt",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Headless Nuxt module that connects a Nuxt app to an agent-runtime server. Ships server-side proxy routes (so your X-Agent-Runtime-App-Key never leaves the server) and a single composable, useAgentRuntime(), that exposes a typed chat client.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -60,13 +60,13 @@
60
60
  "@nuxt/kit": "^4.4.2",
61
61
  "ai": "^6.0.168",
62
62
  "defu": "^6.1.4",
63
+ "h3": "^1.15.11",
63
64
  "markdown-it": "^14.1.1"
64
65
  },
65
66
  "devDependencies": {
66
67
  "@nuxt/module-builder": "^1.0.2",
67
68
  "@nuxt/schema": "^4.4.2",
68
69
  "@types/markdown-it": "^14.1.2",
69
- "h3": "^1.15.11",
70
70
  "nuxt": "^4.4.2",
71
71
  "typescript": "^6.0.2",
72
72
  "vue": "^3.5.32",