dopus 0.2.9 → 0.2.10

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
@@ -256,27 +256,35 @@ export function Agent({ userId, ...props }: AgentChatProps & { userId: string })
256
256
  }
257
257
  ```
258
258
 
259
- In your database: save on the server, where you know the user, and load from the client.
259
+ In your database: save on the server, where you know the user, and load from the client. `onFinish` gives you who
260
+ (`user`, from `identify`), which conversation (`conversationId`) and what this request added (`newMessages`: the user's
261
+ message and the agent's reply, with tool calls and results inside).
260
262
 
261
263
  ```ts
262
- // agent.config.ts
263
- onFinish: async ({ user, messages }) => {
264
- if (user) await db.chat.upsert({ userId: user.id, messages }) // needs `identify`
264
+ // agent.config.ts (needs `identify`, so `user` is set)
265
+ onFinish: async ({ user, conversationId, newMessages }) => {
266
+ if (!user) return
267
+ for (const message of newMessages) {
268
+ // Upsert by id: after an approval the reply arrives again, continued. Key by user too, since
269
+ // conversationId comes from the browser.
270
+ await db.chatMessage.upsert({ id: message.id, userId: user.id, conversationId, message })
271
+ }
265
272
  },
266
273
  ```
267
274
 
268
275
  ```tsx
269
- // components/agent/agent.tsx: loadChat is your server action returning the saved messages
270
- ;<AgentWidget
271
- history={{
272
- load: () => loadChat(),
273
- save: (m) => {
274
- if (m.length === 0) clearChat()
275
- },
276
- }}
277
- />
276
+ // components/agent/agent.tsx: loadChat is your server action returning the latest conversation's messages
277
+ ;<AgentWidget history={{ load: () => loadChat() }} />
278
278
  ```
279
279
 
280
+ - **`conversationId`** is the id of the conversation's first message: the same after a reload (when `history.load`
281
+ restores it), new after "New chat". A list of past conversations is yours to build from it.
282
+ - **`newMessages`** rather than the whole history: the browser sends the whole conversation with every request, so
283
+ saving only what this request added keeps earlier stored messages out of its reach. `messages` (the whole
284
+ conversation) is there too if one snapshot per conversation is enough.
285
+ - **Stopped or failed turns** do not reach `onFinish`. To keep the user's message anyway, save it in `beforeRequest`
286
+ (it gets `user` and `messages`).
287
+
280
288
  Things to know:
281
289
 
282
290
  - **Tool results are stored too.** Whatever your tools returned (orders, names) is in the saved conversation. Keep it
@@ -340,7 +348,7 @@ defineAgent({
340
348
  },
341
349
 
342
350
  // Once per request, when the reply is done: tokens summed over all steps.
343
- // also gets `messages`: the whole conversation after this reply (see "Chat history")
351
+ // also gets conversationId, newMessages and messages, to save the chat (see "Chat history")
344
352
  onFinish: async ({ user, model, usage, steps, finishReason, durationMs }) => {
345
353
  await db.usage.insert({ userId: user?.id, model: model.modelId, ...usage })
346
354
  },
package/dist/cli.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { n as PROVIDERS, t as VERSION } from "./version-NYbd20Zw.js";
2
+ import { n as PROVIDERS, t as VERSION } from "./version-CWmiLdVn.js";
3
3
  import { execFileSync, spawn } from "node:child_process";
4
4
  import { parseArgs } from "node:util";
5
5
  import * as p from "@clack/prompts";
package/dist/index.d.ts CHANGED
@@ -112,8 +112,8 @@ interface AgentConfig<TOOLS extends DopusToolSet = DopusToolSet> {
112
112
  /** Called once per tool call: ran (`success` / `error`) or declined by the user (`denied`). For audit logs. */
113
113
  onToolCall?: (event: ToolCallEvent) => void | Promise<void>;
114
114
  /**
115
- * Called once per request when the reply is finished: tokens, steps, model (usage tracking), and the whole
116
- * conversation including the reply (to save chat history in your database).
115
+ * Called once per request when the reply is finished: tokens, steps, model (usage tracking), and the conversation
116
+ * (to save chat history in your database: `user`, `conversationId`, `newMessages`).
117
117
  */
118
118
  onFinish?: (event: FinishEvent) => void | Promise<void>;
119
119
  /** Maximum model steps (tool round trips) per request. Default 10. */
@@ -173,8 +173,19 @@ interface FinishEvent {
173
173
  finishReason: string;
174
174
  durationMs: number;
175
175
  /**
176
- * The conversation after this reply, as the chat UI holds it (`UIMessage[]`). Save it to restore the chat later
177
- * (the chat UI's `history.load`). It came from the browser, so treat it as the user's own data, not as trusted.
176
+ * Which conversation this is: the id of its first message. The same across reloads (when the chat UI restores
177
+ * its `history`), new after "New chat". Sent by the browser, so store it under `user`, never on its own.
178
+ */
179
+ conversationId: string;
180
+ /**
181
+ * What this request added or changed: the user's message (when the request started with one) and the
182
+ * assistant's reply. After an approval the reply is the same message, continued, so upsert by `id`.
183
+ * Saving only these keeps earlier stored messages out of the browser's reach.
184
+ */
185
+ newMessages: UIMessage[];
186
+ /**
187
+ * The whole conversation after this reply, as the chat UI holds it. Handy to store one snapshot per
188
+ * conversation; it comes from the browser, so it can differ from what you stored earlier.
178
189
  */
179
190
  messages: UIMessage[];
180
191
  }
@@ -224,5 +235,5 @@ interface ProviderInfo {
224
235
  }
225
236
  /** Verified against @ai-sdk/openai@4.0.111, @ai-sdk/anthropic@4.0.99, @ai-sdk/google@4.0.114. */
226
237
  export declare const PROVIDERS: Record<ProviderId, ProviderInfo>;
227
- export declare const VERSION = "0.2.9";
238
+ export declare const VERSION = "0.2.10";
228
239
  export type { AgentConfig, AgentUser, ClientContext, ConfirmOption, DopusTool, DopusToolSet, FinishEvent, HandlerOptions, PromptLayers, ProviderId, ProviderInfo, RuntimeContext, ToolCallEvent, ToolContext, ToolOptions };
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { n as PROVIDERS, t as VERSION } from "./version-NYbd20Zw.js";
1
+ import { n as PROVIDERS, t as VERSION } from "./version-CWmiLdVn.js";
2
2
  import { convertToModelMessages, createUIMessageStreamResponse, getToolName, isStepCount, isToolUIPart, safeValidateUIMessages, streamText, toUIMessageStream, tool } from "ai";
3
3
  // src/job.ts
4
4
  /**
@@ -379,11 +379,11 @@ function createAgentHandler(config, options = {}) {
379
379
  };
380
380
  const approvalSecret = config.approvalSecret ?? process.env.DOPUS_APPROVAL_SECRET;
381
381
  const finish = { async emit() {
382
- if (this.done || !this.usage || !this.messages) return;
382
+ if (this.done || !this.usage || !this.conversation) return;
383
383
  this.done = true;
384
384
  await runHook(config.onFinish, {
385
385
  ...this.usage,
386
- messages: this.messages
386
+ ...this.conversation
387
387
  }, report);
388
388
  } };
389
389
  const result = streamText({
@@ -441,9 +441,14 @@ function createAgentHandler(config, options = {}) {
441
441
  stream: result.stream,
442
442
  tools,
443
443
  originalMessages: messages,
444
- onEnd: async ({ messages: final, isAborted }) => {
444
+ onEnd: async ({ messages: final, responseMessage, isAborted }) => {
445
445
  if (isAborted) return;
446
- finish.messages = final;
446
+ const last = messages.at(-1);
447
+ finish.conversation = {
448
+ conversationId: messages[0]?.id ?? responseMessage.id,
449
+ newMessages: last?.role === "user" ? [last, responseMessage] : [responseMessage],
450
+ messages: final
451
+ };
447
452
  await finish.emit();
448
453
  },
449
454
  onError: (error) => {
@@ -27,5 +27,5 @@ const PROVIDERS = {
27
27
  }
28
28
  };
29
29
  // src/version.ts
30
- const VERSION = "0.2.9";
30
+ const VERSION = "0.2.10";
31
31
  export { PROVIDERS as n, VERSION as t };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dopus",
3
- "version": "0.2.9",
3
+ "version": "0.2.10",
4
4
  "description": "A lightweight agent for Next.js: your existing server functions become its tools, run with the signed-in user's permissions.",
5
5
  "license": "MIT",
6
6
  "author": "akodm",