@d4y/agent-runtime-nuxt 0.1.3 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -108,7 +108,7 @@ The composable returns AI-SDK / Nuxt UI compatible `UIMessage` shapes, so
108
108
  | `/conversations/:id` | DELETE | Delete a persisted conversation. |
109
109
  | `/conversations/:id/history` | GET | Return the persisted conversation history. |
110
110
  | `/conversations/:id/stream` | GET | Pipes the upstream SSE stream verbatim. |
111
- | `/conversations/:id/messages` | POST | Append a user turn. Body: `{ content, context? }`. |
111
+ | `/conversations/:id/messages` | POST | Append a user turn. Body: `{ content, context?, requestOptions? }`. |
112
112
  | `/conversations/:id/abort` | POST | Cancel the in-flight agent run. |
113
113
  | `/conversations/:id/env` | POST | Patch workspace env. Body: `{ env, merge? }`. |
114
114
  | `/conversations/:id/files` | GET | List workspace files. |
@@ -160,9 +160,32 @@ workspace via `POST /conversations/:id/env`, which retriggers any matching
160
160
  #### `send(text, options?)`
161
161
 
162
162
  Lazily calls `start()` if no conversation exists. The displayed user bubble
163
- always shows `text`; pass `options.rewriteContent` to mutate the *forwarded*
164
- prompt without changing what the user sees. Useful for model-specific soft
165
- switches:
163
+ always shows `text`.
164
+
165
+ Use `requestOptions` for provider-side controls such as Qwen/vLLM
166
+ `chat_template_kwargs`:
167
+
168
+ ```ts
169
+ await chat.start({
170
+ requestOptions: {
171
+ temperature: 0.7,
172
+ topP: 0.8,
173
+ presencePenalty: 1.5
174
+ }
175
+ })
176
+
177
+ await chat.send(input.value, {
178
+ requestOptions: {
179
+ extraBody: {
180
+ top_k: 20,
181
+ chat_template_kwargs: { enable_thinking: false }
182
+ }
183
+ }
184
+ })
185
+ ```
186
+
187
+ `rewriteContent` is still available as a fallback when a model expects prompt
188
+ text toggles rather than provider payload fields:
166
189
 
167
190
  ```ts
168
191
  await chat.send(input.value, {
package/dist/module.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "compatibility": {
5
5
  "nuxt": ">=3.13.0"
6
6
  },
7
- "version": "0.1.3",
7
+ "version": "0.1.4",
8
8
  "builder": {
9
9
  "@nuxt/module-builder": "1.0.2",
10
10
  "unbuild": "unknown"
@@ -38,6 +38,22 @@ export interface UiAction {
38
38
  payload: unknown;
39
39
  at: number;
40
40
  }
41
+ export interface ContextUsage {
42
+ tokens: number | null;
43
+ contextWindow: number;
44
+ percent: number | null;
45
+ }
46
+ export interface CompactionLogEntry {
47
+ phase: 'start' | 'end';
48
+ at: number;
49
+ reason?: string;
50
+ tokensBefore?: number;
51
+ tokensAfter?: number;
52
+ compacted?: boolean;
53
+ aborted?: boolean;
54
+ willRetry?: boolean;
55
+ error?: string;
56
+ }
41
57
  /** Workspace file entry, mirrors the backend response. */
42
58
  export interface FileEntry {
43
59
  name: string;
@@ -47,6 +63,19 @@ export interface FileEntry {
47
63
  mimeType: string;
48
64
  kind: 'image' | 'html' | 'pdf' | 'download';
49
65
  }
66
+ /**
67
+ * Generic provider request options forwarded to the runtime.
68
+ * `extraBody` is merged verbatim into the provider payload, which makes this
69
+ * suitable for model-specific flags such as Qwen's `chat_template_kwargs`.
70
+ */
71
+ export interface RequestOptions {
72
+ temperature?: number;
73
+ maxTokens?: number;
74
+ topP?: number;
75
+ presencePenalty?: number;
76
+ frequencyPenalty?: number;
77
+ extraBody?: Record<string, unknown>;
78
+ }
50
79
  /** Per-turn options for `send`. */
51
80
  export interface SendOptions {
52
81
  /**
@@ -62,11 +91,15 @@ export interface SendOptions {
62
91
  rewriteContent?: (text: string) => string;
63
92
  /** Optional language override for this turn / conversation, e.g. `en` or `de-CH`. */
64
93
  language?: string;
94
+ /** Optional provider request overrides for this turn only. */
95
+ requestOptions?: RequestOptions;
65
96
  }
66
97
  /** Options for `start`. */
67
98
  export interface StartOptions {
68
99
  /** Optional language override for the newly created conversation. */
69
100
  language?: string;
101
+ /** Optional default provider request options for the conversation. */
102
+ requestOptions?: RequestOptions;
70
103
  }
71
104
  /** Return shape of {@link useAgentRuntime}. All members are reactive. */
72
105
  export interface UseAgentRuntime {
@@ -80,6 +113,12 @@ export interface UseAgentRuntime {
80
113
  error: Ref<Error | null>;
81
114
  /** Most recent UI-action events, newest first, capped at 50. */
82
115
  uiActions: Ref<UiAction[]>;
116
+ /** Latest context usage sample from the active PI session. */
117
+ contextUsage: Ref<ContextUsage | null>;
118
+ /** True iff a compaction run is currently in progress. */
119
+ compactionInProgress: Ref<boolean>;
120
+ /** Most recent compaction events, newest first. */
121
+ compactionLog: Ref<CompactionLogEntry[]>;
83
122
  /** Active app manifest. `null` until the first `onMounted` fetch resolves. */
84
123
  app: Ref<AppInfo | null>;
85
124
  /** Current auth values (persisted in localStorage, scoped per appId). */
@@ -43,6 +43,9 @@ export const useAgentRuntime = () => {
43
43
  const status = ref("idle");
44
44
  const error = ref(null);
45
45
  const uiActions = ref([]);
46
+ const contextUsage = ref(null);
47
+ const compactionInProgress = ref(false);
48
+ const compactionLog = ref([]);
46
49
  const app = ref(null);
47
50
  const auth = ref(emptyAuth());
48
51
  const authReady = computed(() => computeAuthReady(auth.value, app.value));
@@ -121,6 +124,12 @@ export const useAgentRuntime = () => {
121
124
  return { ...msg, parts };
122
125
  });
123
126
  };
127
+ const normalizePercent = (value) => Math.max(0, Math.min(100, value > 1 ? value : value * 100));
128
+ const toNumber = (value) => typeof value === "number" && Number.isFinite(value) ? value : void 0;
129
+ const toNullableNumber = (value) => value === null ? null : toNumber(value);
130
+ const pushCompactionLog = (entry) => {
131
+ compactionLog.value = [entry, ...compactionLog.value].slice(0, 20);
132
+ };
124
133
  const onSseEvent = (eventName, data) => {
125
134
  switch (eventName) {
126
135
  case "agent_start":
@@ -148,23 +157,66 @@ export const useAgentRuntime = () => {
148
157
  type: `tool-${d.toolName}`,
149
158
  toolCallId: d.toolCallId,
150
159
  state: d.isError ? "output-error" : "output-available",
151
- output: d.isError ? void 0 : "ok",
152
- errorText: d.isError ? "Tool returned an error" : void 0
160
+ output: d.isError ? void 0 : d.output,
161
+ errorText: d.isError ? d.errorText ?? "Tool returned an error" : void 0
153
162
  }));
154
163
  break;
155
164
  }
165
+ case "context_usage": {
166
+ const d = data;
167
+ const tokens = toNullableNumber(d.tokens);
168
+ const contextWindow = toNumber(d.contextWindow);
169
+ if (tokens === void 0 || contextWindow === void 0) break;
170
+ const fallbackPercent = typeof tokens === "number" && contextWindow > 0 ? tokens / contextWindow * 100 : null;
171
+ const percent = toNullableNumber(d.percent) ?? fallbackPercent;
172
+ if (percent === void 0) break;
173
+ contextUsage.value = {
174
+ tokens,
175
+ contextWindow,
176
+ percent: typeof percent === "number" ? normalizePercent(percent) : null
177
+ };
178
+ break;
179
+ }
180
+ case "compaction_start": {
181
+ const d = data;
182
+ compactionInProgress.value = true;
183
+ pushCompactionLog({
184
+ phase: "start",
185
+ at: Date.now(),
186
+ reason: typeof d.reason === "string" && d.reason.trim().length > 0 ? d.reason.trim() : void 0
187
+ });
188
+ break;
189
+ }
190
+ case "compaction_end": {
191
+ const d = data;
192
+ const result = d.result && typeof d.result === "object" ? d.result : null;
193
+ compactionInProgress.value = false;
194
+ pushCompactionLog({
195
+ phase: "end",
196
+ at: Date.now(),
197
+ reason: typeof d.reason === "string" && d.reason.trim().length > 0 ? d.reason.trim() : void 0,
198
+ compacted: typeof d.aborted === "boolean" ? !d.aborted && !!result : !!result,
199
+ aborted: typeof d.aborted === "boolean" ? d.aborted : void 0,
200
+ willRetry: typeof d.willRetry === "boolean" ? d.willRetry : void 0,
201
+ tokensBefore: toNumber(result?.tokensBefore),
202
+ error: typeof d.errorMessage === "string" && d.errorMessage.trim().length > 0 ? d.errorMessage : void 0
203
+ });
204
+ break;
205
+ }
156
206
  case "ui_action": {
157
207
  const d = data;
158
208
  uiActions.value = [{ toolCallId: d.toolCallId, type: d.type, payload: d.payload, at: Date.now() }, ...uiActions.value].slice(0, 50);
159
209
  break;
160
210
  }
161
211
  case "end":
212
+ compactionInProgress.value = false;
162
213
  status.value = "ready";
163
214
  currentAssistantId = null;
164
215
  void refreshFiles();
165
216
  break;
166
217
  case "error": {
167
218
  const d = data;
219
+ compactionInProgress.value = false;
168
220
  error.value = new Error(d?.message ?? "stream error");
169
221
  status.value = "error";
170
222
  currentAssistantId = null;
@@ -217,7 +269,11 @@ export const useAgentRuntime = () => {
217
269
  error.value = null;
218
270
  const res = await $fetch(`${apiPrefix}/conversations`, {
219
271
  method: "POST",
220
- body: { env: buildEnvFromAuth(auth.value), language: options.language }
272
+ body: {
273
+ env: buildEnvFromAuth(auth.value),
274
+ language: options.language,
275
+ requestOptions: options.requestOptions
276
+ }
221
277
  });
222
278
  conversationId.value = res.conversationId;
223
279
  abortController?.abort();
@@ -264,7 +320,12 @@ export const useAgentRuntime = () => {
264
320
  const wireContent = options.rewriteContent ? options.rewriteContent(text) : text;
265
321
  await $fetch(`${apiPrefix}/conversations/${conversationId.value}/messages`, {
266
322
  method: "POST",
267
- body: { content: wireContent, context: options.context, language: options.language }
323
+ body: {
324
+ content: wireContent,
325
+ context: options.context,
326
+ language: options.language,
327
+ requestOptions: options.requestOptions
328
+ }
268
329
  }).catch((err) => {
269
330
  error.value = err instanceof Error ? err : new Error(String(err));
270
331
  status.value = "error";
@@ -283,6 +344,9 @@ export const useAgentRuntime = () => {
283
344
  currentAssistantId = null;
284
345
  replaceMessages([]);
285
346
  uiActions.value = [];
347
+ contextUsage.value = null;
348
+ compactionInProgress.value = false;
349
+ compactionLog.value = [];
286
350
  files.value = [];
287
351
  status.value = "idle";
288
352
  error.value = null;
@@ -296,6 +360,9 @@ export const useAgentRuntime = () => {
296
360
  status,
297
361
  error,
298
362
  uiActions,
363
+ contextUsage,
364
+ compactionInProgress,
365
+ compactionLog,
299
366
  app,
300
367
  auth,
301
368
  authReady,
@@ -9,7 +9,12 @@ export default defineEventHandler(async (event) => {
9
9
  const response = await fetch(`${cfg.baseUrl}/v1/conversations/${id}/messages`, {
10
10
  method: "POST",
11
11
  headers: agentRuntimeHeaders(cfg),
12
- body: JSON.stringify({ content: body.content, context: body.context, language: body.language })
12
+ body: JSON.stringify({
13
+ content: body.content,
14
+ context: body.context,
15
+ requestOptions: body.requestOptions,
16
+ language: body.language
17
+ })
13
18
  });
14
19
  if (!response.ok) {
15
20
  throw createError({
@@ -6,7 +6,13 @@ export default defineEventHandler(async (event) => {
6
6
  const response = await fetch(`${cfg.baseUrl}/v1/conversations`, {
7
7
  method: "POST",
8
8
  headers: agentRuntimeHeaders(cfg),
9
- body: JSON.stringify({ appId: cfg.appId, env: body.env ?? {}, model: body.model, language: body.language })
9
+ body: JSON.stringify({
10
+ appId: cfg.appId,
11
+ env: body.env ?? {},
12
+ model: body.model,
13
+ requestOptions: body.requestOptions,
14
+ language: body.language
15
+ })
10
16
  });
11
17
  if (!response.ok) {
12
18
  throw createError({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@d4y/agent-runtime-nuxt",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
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",