better-mastra 0.1.1 → 0.3.0
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 +35 -0
- package/dist/client/actions.d.ts +21 -3
- package/dist/client/actions.d.ts.map +1 -1
- package/dist/client/actions.js +30 -5
- package/dist/client/actions.js.map +1 -1
- package/dist/client/index.d.ts +1 -0
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +1 -0
- package/dist/client/index.js.map +1 -1
- package/dist/client/messages.d.ts +28 -3
- package/dist/client/messages.d.ts.map +1 -1
- package/dist/client/messages.js +54 -14
- package/dist/client/messages.js.map +1 -1
- package/dist/client/resume.d.ts +27 -0
- package/dist/client/resume.d.ts.map +1 -0
- package/dist/client/resume.js +46 -0
- package/dist/client/resume.js.map +1 -0
- package/dist/client/reveal.d.ts +2 -0
- package/dist/client/reveal.d.ts.map +1 -1
- package/dist/client/reveal.js +5 -0
- package/dist/client/reveal.js.map +1 -1
- package/dist/client/session.d.ts +13 -7
- package/dist/client/session.d.ts.map +1 -1
- package/dist/client/session.js +37 -1
- package/dist/client/session.js.map +1 -1
- package/dist/client/turn.d.ts +6 -2
- package/dist/client/turn.d.ts.map +1 -1
- package/dist/client/turn.js +16 -0
- package/dist/client/turn.js.map +1 -1
- package/dist/dictation/react/index.d.ts +4 -2
- package/dist/dictation/react/index.d.ts.map +1 -1
- package/dist/dictation/react/index.js +9 -1
- package/dist/dictation/react/index.js.map +1 -1
- package/dist/dictation/session.d.ts +5 -1
- package/dist/dictation/session.d.ts.map +1 -1
- package/dist/dictation/session.js +25 -5
- package/dist/dictation/session.js.map +1 -1
- package/dist/dictation/vue/index.d.ts +4 -2
- package/dist/dictation/vue/index.d.ts.map +1 -1
- package/dist/dictation/vue/index.js +9 -1
- package/dist/dictation/vue/index.js.map +1 -1
- package/dist/evals/checks.d.ts +2 -1
- package/dist/evals/checks.d.ts.map +1 -1
- package/dist/evals/checks.js +1 -1
- package/dist/evals/checks.js.map +1 -1
- package/dist/evals/cli.js +21 -2
- package/dist/evals/cli.js.map +1 -1
- package/dist/evals/judge.d.ts +16 -2
- package/dist/evals/judge.d.ts.map +1 -1
- package/dist/evals/judge.js +13 -4
- package/dist/evals/judge.js.map +1 -1
- package/dist/evals/mastra.d.ts.map +1 -1
- package/dist/evals/mastra.js +17 -2
- package/dist/evals/mastra.js.map +1 -1
- package/dist/evals/run.d.ts +6 -3
- package/dist/evals/run.d.ts.map +1 -1
- package/dist/evals/run.js +6 -8
- package/dist/evals/run.js.map +1 -1
- package/dist/hono/chatRoute.d.ts +17 -3
- package/dist/hono/chatRoute.d.ts.map +1 -1
- package/dist/hono/chatRoute.js +15 -10
- package/dist/hono/chatRoute.js.map +1 -1
- package/dist/hono/index.d.ts +1 -1
- package/dist/hono/index.d.ts.map +1 -1
- package/dist/hono/index.js.map +1 -1
- package/dist/libsql/index.d.ts +7 -0
- package/dist/libsql/index.d.ts.map +1 -0
- package/dist/libsql/index.js +13 -0
- package/dist/libsql/index.js.map +1 -0
- package/dist/models/providers/openrouter.d.ts +5 -3
- package/dist/models/providers/openrouter.d.ts.map +1 -1
- package/dist/models/providers/openrouter.js +11 -6
- package/dist/models/providers/openrouter.js.map +1 -1
- package/dist/openrouter/index.d.ts +2 -0
- package/dist/openrouter/index.d.ts.map +1 -0
- package/dist/openrouter/index.js +2 -0
- package/dist/openrouter/index.js.map +1 -0
- package/dist/pg/index.d.ts +11 -0
- package/dist/pg/index.d.ts.map +1 -0
- package/dist/pg/index.js +85 -0
- package/dist/pg/index.js.map +1 -0
- package/dist/react/index.d.ts +1 -1
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +1 -0
- package/dist/react/index.js.map +1 -1
- package/dist/react/markdown/index.d.ts +2 -0
- package/dist/react/markdown/index.d.ts.map +1 -1
- package/dist/react/markdown/index.js +6 -3
- package/dist/react/markdown/index.js.map +1 -1
- package/dist/resumable/index.d.ts +21 -2
- package/dist/resumable/index.d.ts.map +1 -1
- package/dist/resumable/index.js +17 -8
- package/dist/resumable/index.js.map +1 -1
- package/dist/resumable/redis.d.ts +29 -0
- package/dist/resumable/redis.d.ts.map +1 -1
- package/dist/resumable/redis.js +60 -0
- package/dist/resumable/redis.js.map +1 -1
- package/dist/runtime/storage.d.ts +8 -6
- package/dist/runtime/storage.d.ts.map +1 -1
- package/dist/runtime/storage.js +9 -89
- package/dist/runtime/storage.js.map +1 -1
- package/dist/turn/agent.d.ts.map +1 -1
- package/dist/turn/agent.js +4 -1
- package/dist/turn/agent.js.map +1 -1
- package/dist/turn/index.d.ts +3 -3
- package/dist/turn/index.d.ts.map +1 -1
- package/dist/turn/index.js +1 -1
- package/dist/turn/index.js.map +1 -1
- package/dist/turn/runChatTurn.d.ts +10 -5
- package/dist/turn/runChatTurn.d.ts.map +1 -1
- package/dist/turn/runChatTurn.js +38 -2
- package/dist/turn/runChatTurn.js.map +1 -1
- package/dist/turn/stream.d.ts +8 -0
- package/dist/turn/stream.d.ts.map +1 -1
- package/dist/turn/stream.js +11 -0
- package/dist/turn/stream.js.map +1 -1
- package/dist/turn/threads.d.ts +24 -3
- package/dist/turn/threads.d.ts.map +1 -1
- package/dist/turn/threads.js +23 -37
- package/dist/turn/threads.js.map +1 -1
- package/dist/turn/types.d.ts +23 -4
- package/dist/turn/types.d.ts.map +1 -1
- package/dist/turn/types.js.map +1 -1
- package/dist/vue/index.d.ts +1 -1
- package/dist/vue/index.d.ts.map +1 -1
- package/dist/vue/index.js +1 -0
- package/dist/vue/index.js.map +1 -1
- package/package.json +14 -1
- package/src/client/actions.ts +72 -7
- package/src/client/index.ts +1 -0
- package/src/client/messages.ts +85 -16
- package/src/client/resume.ts +82 -0
- package/src/client/reveal.ts +6 -0
- package/src/client/session.ts +68 -10
- package/src/client/turn.ts +21 -2
- package/src/dictation/react/index.ts +16 -3
- package/src/dictation/session.ts +38 -5
- package/src/dictation/vue/index.ts +16 -3
- package/src/evals/checks.ts +3 -3
- package/src/evals/cli.ts +24 -2
- package/src/evals/judge.ts +38 -6
- package/src/evals/mastra.ts +23 -4
- package/src/evals/run.ts +8 -8
- package/src/hono/chatRoute.ts +34 -13
- package/src/hono/index.ts +5 -1
- package/src/libsql/index.ts +19 -0
- package/src/models/providers/openrouter.ts +19 -7
- package/src/openrouter/index.ts +1 -0
- package/src/pg/index.ts +108 -0
- package/src/react/index.ts +2 -0
- package/src/react/markdown/index.ts +9 -5
- package/src/resumable/index.ts +47 -18
- package/src/resumable/redis.ts +91 -0
- package/src/runtime/storage.ts +10 -106
- package/src/turn/agent.ts +6 -2
- package/src/turn/index.ts +3 -0
- package/src/turn/runChatTurn.ts +61 -5
- package/src/turn/stream.ts +15 -0
- package/src/turn/threads.ts +67 -41
- package/src/turn/types.ts +25 -4
- package/src/vue/index.ts +2 -0
package/README.md
CHANGED
|
@@ -4,6 +4,26 @@ A small layer on Mastra and the AI SDK: model registry, chat turns, resumable st
|
|
|
4
4
|
|
|
5
5
|
Work in progress.
|
|
6
6
|
|
|
7
|
+
## Runtime presets
|
|
8
|
+
|
|
9
|
+
Storage and memory presets are synchronous; pools connect on their first query, so a process can build its Mastra at import time:
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
import { Mastra } from '@mastra/core'
|
|
13
|
+
import { createMemory, shutdown } from 'better-mastra' // presets come from the package root
|
|
14
|
+
import { openRouterRoute } from 'better-mastra/openrouter'
|
|
15
|
+
import { pgStorage } from 'better-mastra/pg' // or libsqlStorage from 'better-mastra/libsql'
|
|
16
|
+
|
|
17
|
+
const storage = pgStorage({ connectionString: process.env.DATABASE_URL })
|
|
18
|
+
const models = openRouterRoute({ apiKey: process.env.OPENROUTER_API_KEY })
|
|
19
|
+
const memory = createMemory({ storage: storage.store, observer: models.model('google/gemini-3.5-flash') })
|
|
20
|
+
export const mastra = new Mastra({ storage: storage.store })
|
|
21
|
+
|
|
22
|
+
await storage.init() // once, in the process that migrates
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
`pgStorage` and `libsqlStorage` from the package root are the same presets loaded on call (they return promises); they stay for compatibility.
|
|
26
|
+
|
|
7
27
|
## Resumable streams (`better-mastra/resumable`)
|
|
8
28
|
|
|
9
29
|
Opt-in. A dropped connection no longer aborts the turn: the client reconnects with AI SDK `reconnectToStream` (`useChat({ resume: true })`) and gets a full replay of the current assistant message plus the live tail (`resumeStreamIsReplay: true`, the `DefaultChatTransport` default).
|
|
@@ -31,6 +51,21 @@ await resumable.close()
|
|
|
31
51
|
|
|
32
52
|
Use `ioRedis(client)` for ioredis. Both adapters open extra connections with `duplicate()`: one subscriber per replica, and one per cross-replica reader (for `XREAD BLOCK`).
|
|
33
53
|
|
|
54
|
+
Use `upstashRedis(client)` for `@upstash/redis`. Upstash has no blocking `XREAD`, so a cross-replica reader polls every `pollMs` (200 ms); pass `() => Redis.fromEnv()` to create the client on first use.
|
|
55
|
+
|
|
56
|
+
An app that already holds a per-thread lock can keep the locator in the lock's value with `locator: { read, write, expire }`. The locator then lives exactly as long as the lock. `expire` renews it while the turn runs and shortens it to `graceMs` after; leave it out when the lock renews itself:
|
|
57
|
+
|
|
58
|
+
```ts
|
|
59
|
+
createResumableStreams({
|
|
60
|
+
redis: upstashRedis(() => Redis.fromEnv()),
|
|
61
|
+
locator: {
|
|
62
|
+
read: (threadId) => readLockValue(threadId), // what follows the lock token
|
|
63
|
+
write: (threadId, locator, ttlMs) => writeLockValue(threadId, locator, ttlMs),
|
|
64
|
+
expire: (threadId, ttlMs) => redis.pexpire(lockKey(threadId), ttlMs)
|
|
65
|
+
}
|
|
66
|
+
})
|
|
67
|
+
```
|
|
68
|
+
|
|
34
69
|
How it works: chunks are buffered in memory (consecutive `text-delta`s merged) and fanned out to subscribers. A per-thread locator `{ streamId, owner }` in Redis says which replica owns the turn. Reconnecting to the owner is served from memory. Reconnecting to another replica publishes a spill request on the owner's control channel; the owner writes its buffer in one `XADD`, then mirrors new chunks in 50 ms batches (first one immediate) until a terminal marker, and the reader follows with `XREAD BLOCK`.
|
|
35
70
|
|
|
36
71
|
Redis ops for a ~1,500-chunk turn:
|
package/dist/client/actions.d.ts
CHANGED
|
@@ -6,6 +6,15 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import type { UIMessage } from 'ai';
|
|
8
8
|
import { z } from 'zod';
|
|
9
|
+
type MessagePart = UIMessage['parts'][number];
|
|
10
|
+
/** Where the call sits, for an action with its own policy on repeats or shared steps. */
|
|
11
|
+
export interface ClientActionContext {
|
|
12
|
+
readonly toolCallId: string;
|
|
13
|
+
/** The chat's messages when the call arrived; the call is in the last one. */
|
|
14
|
+
readonly messages: readonly UIMessage[];
|
|
15
|
+
/** The parts of the model step that made the call, the call and any server tool beside it included. */
|
|
16
|
+
readonly step: readonly MessagePart[];
|
|
17
|
+
}
|
|
9
18
|
/** A tool that runs in the client. The server never interprets its name. */
|
|
10
19
|
export interface ClientAction<TName extends string = string> {
|
|
11
20
|
readonly name: TName;
|
|
@@ -13,7 +22,13 @@ export interface ClientAction<TName extends string = string> {
|
|
|
13
22
|
readonly inputSchema: z.ZodType;
|
|
14
23
|
readonly requiresApproval?: boolean;
|
|
15
24
|
/** Runs the action on the model's raw input. Throw to report a failure: the model reads it as an error it can act on. */
|
|
16
|
-
readonly run: (input: unknown) => Promise<unknown>;
|
|
25
|
+
readonly run: (input: unknown, context: ClientActionContext) => Promise<unknown>;
|
|
26
|
+
/**
|
|
27
|
+
* A call the user turn already made with the same input. Its result is
|
|
28
|
+
* posted as the call's output; throw to post an error. Default: an error
|
|
29
|
+
* with `CLIENT_ACTION_REPEATED`.
|
|
30
|
+
*/
|
|
31
|
+
readonly onRepeat?: (input: unknown, context: ClientActionContext) => Promise<unknown>;
|
|
17
32
|
}
|
|
18
33
|
/** A client action as a request carries it, for the server to offer the model. */
|
|
19
34
|
export interface ClientToolDef {
|
|
@@ -60,7 +75,8 @@ export declare function defineClientAction<const TName extends string, TSchema e
|
|
|
60
75
|
readonly inputSchema: TSchema;
|
|
61
76
|
readonly requiresApproval?: boolean;
|
|
62
77
|
readonly resultSummary?: string;
|
|
63
|
-
readonly handler: (input: z.output<TSchema
|
|
78
|
+
readonly handler: (input: z.output<TSchema>, context: ClientActionContext) => Promise<TOutput>;
|
|
79
|
+
readonly onRepeat?: ClientAction['onRepeat'];
|
|
64
80
|
}): ClientAction<TName>;
|
|
65
81
|
/**
|
|
66
82
|
* Model arguments with `null` taken out wherever the schema means "may be
|
|
@@ -76,10 +92,12 @@ export declare function toClientToolDefs(actions: readonly ClientAction[]): Clie
|
|
|
76
92
|
* Carries out the client action a tool call names and posts its result on the
|
|
77
93
|
* chat; null when no action has that name (a server tool: its result arrives
|
|
78
94
|
* on the stream). A call the user turn already made with the same input
|
|
79
|
-
* (`null`s aside) is refused instead of carried out again
|
|
95
|
+
* (`null`s aside) is refused instead of carried out again, unless the action
|
|
96
|
+
* has its own `onRepeat`.
|
|
80
97
|
*
|
|
81
98
|
* Never await it inside the chat's `onToolCall`: the AI SDK runs that callback
|
|
82
99
|
* on the queue `addToolOutput` waits on, so awaiting deadlocks the stream.
|
|
83
100
|
*/
|
|
84
101
|
export declare function runClientAction(chat: ClientActionChat, actions: readonly ClientAction[], toolCall: ClientToolCall): Promise<void> | null;
|
|
102
|
+
export {};
|
|
85
103
|
//# sourceMappingURL=actions.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"actions.d.ts","sourceRoot":"","sources":["../../src/client/actions.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,IAAI,CAAA;AAEnC,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAMvB,4EAA4E;AAC5E,MAAM,WAAW,YAAY,CAAC,KAAK,SAAS,MAAM,GAAG,MAAM;IAC1D,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAA;IACpB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAC5B,QAAQ,CAAC,WAAW,EAAE,CAAC,CAAC,OAAO,CAAA;IAC/B,QAAQ,CAAC,gBAAgB,CAAC,EAAE,OAAO,CAAA;IACnC,yHAAyH;IACzH,QAAQ,CAAC,GAAG,EAAE,
|
|
1
|
+
{"version":3,"file":"actions.d.ts","sourceRoot":"","sources":["../../src/client/actions.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,IAAI,CAAA;AAEnC,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAMvB,KAAK,WAAW,GAAG,SAAS,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,CAAA;AAE7C,yFAAyF;AACzF,MAAM,WAAW,mBAAmB;IACnC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,8EAA8E;IAC9E,QAAQ,CAAC,QAAQ,EAAE,SAAS,SAAS,EAAE,CAAA;IACvC,uGAAuG;IACvG,QAAQ,CAAC,IAAI,EAAE,SAAS,WAAW,EAAE,CAAA;CACrC;AAED,4EAA4E;AAC5E,MAAM,WAAW,YAAY,CAAC,KAAK,SAAS,MAAM,GAAG,MAAM;IAC1D,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAA;IACpB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAC5B,QAAQ,CAAC,WAAW,EAAE,CAAC,CAAC,OAAO,CAAA;IAC/B,QAAQ,CAAC,gBAAgB,CAAC,EAAE,OAAO,CAAA;IACnC,yHAAyH;IACzH,QAAQ,CAAC,GAAG,EAAE,CACb,KAAK,EAAE,OAAO,EACd,OAAO,EAAE,mBAAmB,KACxB,OAAO,CAAC,OAAO,CAAC,CAAA;IACrB;;;;OAIG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,CACnB,KAAK,EAAE,OAAO,EACd,OAAO,EAAE,mBAAmB,KACxB,OAAO,CAAC,OAAO,CAAC,CAAA;CACrB;AAED,kFAAkF;AAClF,MAAM,WAAW,aAAa;IAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAC5B,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAA;IAC5B,QAAQ,CAAC,gBAAgB,CAAC,EAAE,OAAO,CAAA;CACnC;AAED,mDAAmD;AACnD,MAAM,WAAW,cAAc;IAC9B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAA;CACvB;AAED,2DAA2D;AAC3D,MAAM,MAAM,gBAAgB,GAAG;IAC9B,QAAQ,CAAC,QAAQ,EAAE,SAAS,EAAE,CAAA;IAC9B,QAAQ,CAAC,aAAa,EAAE,CACvB,OAAO,EACJ;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,OAAO,CAAA;KAAE,GACrD;QACA,KAAK,EAAE,cAAc,CAAA;QACrB,IAAI,EAAE,MAAM,CAAA;QACZ,UAAU,EAAE,MAAM,CAAA;QAClB,SAAS,EAAE,MAAM,CAAA;KAChB,KACA,IAAI,GAAG,WAAW,CAAC,IAAI,CAAC,CAAA;CAC7B,CAAA;AAcD,iFAAiF;AACjF,eAAO,MAAM,oBAAoB,uBAAuB,CAAA;AAExD,sEAAsE;AACtE,eAAO,MAAM,2BAA2B,8FACoD,CAAA;AAE5F,wFAAwF;AACxF,eAAO,MAAM,sBAAsB,mGAC8D,CAAA;AAIjG;;;;;GAKG;AACH,wBAAgB,kBAAkB,CACjC,KAAK,CAAC,KAAK,SAAS,MAAM,EAC1B,OAAO,SAAS,CAAC,CAAC,OAAO,EACzB,OAAO,EACN,MAAM,EAAE;IACT,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAA;IACpB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAC5B,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAA;IAC7B,QAAQ,CAAC,gBAAgB,CAAC,EAAE,OAAO,CAAA;IACnC,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAA;IAC/B,QAAQ,CAAC,OAAO,EAAE,CACjB,KAAK,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,EACxB,OAAO,EAAE,mBAAmB,KACxB,OAAO,CAAC,OAAO,CAAC,CAAA;IACrB,QAAQ,CAAC,QAAQ,CAAC,EAAE,YAAY,CAAC,UAAU,CAAC,CAAA;CAC5C,GAAG,YAAY,CAAC,KAAK,CAAC,CAyBtB;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAClC,MAAM,EAAE,CAAC,CAAC,OAAO,EACjB,KAAK,EAAE,OAAO,GACZ,OAAO,CA+ET;AAED,4FAA4F;AAC5F,wBAAgB,gBAAgB,CAC/B,OAAO,EAAE,SAAS,YAAY,EAAE,GAC9B,aAAa,EAAE,CAYjB;AAED;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAC9B,IAAI,EAAE,gBAAgB,EACtB,OAAO,EAAE,SAAS,YAAY,EAAE,EAChC,QAAQ,EAAE,cAAc,GACtB,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CA8CtB"}
|
package/dist/client/actions.js
CHANGED
|
@@ -29,12 +29,13 @@ export function defineClientAction(action) {
|
|
|
29
29
|
...(action.requiresApproval === undefined
|
|
30
30
|
? {}
|
|
31
31
|
: { requiresApproval: action.requiresApproval }),
|
|
32
|
-
|
|
32
|
+
...(action.onRepeat === undefined ? {} : { onRepeat: action.onRepeat }),
|
|
33
|
+
run: async (input, context) => {
|
|
33
34
|
const parsed = action.inputSchema.safeParse(normalizeModelNulls(action.inputSchema, input ?? {}));
|
|
34
35
|
if (!parsed.success) {
|
|
35
36
|
throw new Error(CLIENT_ACTION_INVALID_INPUT);
|
|
36
37
|
}
|
|
37
|
-
const data = await action.handler(parsed.data);
|
|
38
|
+
const data = await action.handler(parsed.data, context);
|
|
38
39
|
return action.resultSummary === undefined
|
|
39
40
|
? data
|
|
40
41
|
: { summary: action.resultSummary, data };
|
|
@@ -128,7 +129,8 @@ export function toClientToolDefs(actions) {
|
|
|
128
129
|
* Carries out the client action a tool call names and posts its result on the
|
|
129
130
|
* chat; null when no action has that name (a server tool: its result arrives
|
|
130
131
|
* on the stream). A call the user turn already made with the same input
|
|
131
|
-
* (`null`s aside) is refused instead of carried out again
|
|
132
|
+
* (`null`s aside) is refused instead of carried out again, unless the action
|
|
133
|
+
* has its own `onRepeat`.
|
|
132
134
|
*
|
|
133
135
|
* Never await it inside the chat's `onToolCall`: the AI SDK runs that callback
|
|
134
136
|
* on the queue `addToolOutput` waits on, so awaiting deadlocks the stream.
|
|
@@ -144,13 +146,21 @@ export function runClientAction(chat, actions, toolCall) {
|
|
|
144
146
|
toolCallId: toolCall.toolCallId,
|
|
145
147
|
errorText
|
|
146
148
|
});
|
|
149
|
+
const context = {
|
|
150
|
+
toolCallId: toolCall.toolCallId,
|
|
151
|
+
messages: chat.messages,
|
|
152
|
+
step: stepOf(chat.messages, toolCall.toolCallId)
|
|
153
|
+
};
|
|
147
154
|
return (async () => {
|
|
148
|
-
|
|
155
|
+
const repeated = isRepeatInTurn(chat.messages, action, toolCall);
|
|
156
|
+
if (repeated && action.onRepeat === undefined) {
|
|
149
157
|
await fail(CLIENT_ACTION_REPEATED);
|
|
150
158
|
return;
|
|
151
159
|
}
|
|
152
160
|
try {
|
|
153
|
-
const output = await action.
|
|
161
|
+
const output = await (repeated && action.onRepeat
|
|
162
|
+
? action.onRepeat(toolCall.input, context)
|
|
163
|
+
: action.run(toolCall.input, context));
|
|
154
164
|
await chat.addToolOutput({
|
|
155
165
|
tool: toolCall.toolName,
|
|
156
166
|
toolCallId: toolCall.toolCallId,
|
|
@@ -174,6 +184,21 @@ function isRepeatInTurn(messages, action, toolCall) {
|
|
|
174
184
|
getToolName(part) === action.name &&
|
|
175
185
|
callKey(action, part.input) === key));
|
|
176
186
|
}
|
|
187
|
+
/** The parts between the `step-start` before the call and the next one; none when the call is not in the last message. */
|
|
188
|
+
function stepOf(messages, toolCallId) {
|
|
189
|
+
const parts = messages.at(-1)?.parts ?? [];
|
|
190
|
+
const at = parts.findIndex((part) => isToolUIPart(part) && part.toolCallId === toolCallId);
|
|
191
|
+
if (at === -1) {
|
|
192
|
+
return [];
|
|
193
|
+
}
|
|
194
|
+
// The tsconfig lib predates `findLastIndex`.
|
|
195
|
+
const start = parts
|
|
196
|
+
.slice(0, at)
|
|
197
|
+
.map((part) => part.type)
|
|
198
|
+
.lastIndexOf('step-start');
|
|
199
|
+
const end = parts.findIndex((part, index) => index > at && part.type === 'step-start');
|
|
200
|
+
return parts.slice(start + 1, end === -1 ? undefined : end);
|
|
201
|
+
}
|
|
177
202
|
/** The input with the model's nulls left out, so the same call matches however the model wrote it. */
|
|
178
203
|
function callKey(action, input) {
|
|
179
204
|
return JSON.stringify(normalizeModelNulls(action.inputSchema, input ?? {}));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"actions.js","sourceRoot":"","sources":["../../src/client/actions.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Client actions: tools the app carries out itself (open a screen, fill a\n * form), whose definitions go along with each request and whose results go\n * back as the turn's follow-up. A call reaches the app as the model wrote\n * it, without the server's input validation, so the app parses it here.\n */\n\nimport type { UIMessage } from 'ai'\nimport { getToolName, isToolUIPart } from 'ai'\nimport { z } from 'zod'\n\nimport { currentUserTurn } from './messages.js'\n\n// TYPES\n\n/** A tool that runs in the client. The server never interprets its name. */\nexport interface ClientAction<TName extends string = string> {\n\treadonly name: TName\n\treadonly description: string\n\treadonly inputSchema: z.ZodType\n\treadonly requiresApproval?: boolean\n\t/** Runs the action on the model's raw input. Throw to report a failure: the model reads it as an error it can act on. */\n\treadonly run: (input: unknown) => Promise<unknown>\n}\n\n/** A client action as a request carries it, for the server to offer the model. */\nexport interface ClientToolDef {\n\treadonly name: string\n\treadonly description: string\n\treadonly jsonSchema: unknown\n\treadonly requiresApproval?: boolean\n}\n\n/** A tool call the stream handed to the client. */\nexport interface ClientToolCall {\n\treadonly toolCallId: string\n\treadonly toolName: string\n\treadonly input: unknown\n}\n\n/** The part of the AI SDK `Chat` a client action needs. */\nexport type ClientActionChat = {\n\treadonly messages: UIMessage[]\n\treadonly addToolOutput: (\n\t\toptions:\n\t\t\t| { tool: string; toolCallId: string; output: unknown }\n\t\t\t| {\n\t\t\t\t\tstate: 'output-error'\n\t\t\t\t\ttool: string\n\t\t\t\t\ttoolCallId: string\n\t\t\t\t\terrorText: string\n\t\t\t }\n\t) => void | PromiseLike<void>\n}\n\ntype SchemaDef = {\n\treadonly type: string\n\treadonly innerType?: z.ZodType\n\treadonly element?: z.ZodType\n\treadonly valueType?: z.ZodType\n\treadonly in?: z.ZodType\n\treadonly options?: readonly z.ZodType[]\n\treadonly getter?: () => z.ZodType\n}\n\n// CONSTANTS\n\n/** What the model reads when an action throws something that is not an Error. */\nexport const CLIENT_ACTION_FAILED = 'The action failed.'\n\n/** What the model reads when the app cannot read the call's input. */\nexport const CLIENT_ACTION_INVALID_INPUT =\n\t'The app could not read this input. Call the action again with input that fits its schema.'\n\n/** What the model reads for a call it already made with the same input in this turn. */\nexport const CLIENT_ACTION_REPEATED =\n\t'Already done with these values in this turn. Do not call it again; tell the user what changed.'\n\n// FUNCTIONS\n\n/**\n * A client action whose handler gets its input parsed, with the model's\n * `null`s taken out where the schema means \"may be left out\". With\n * `resultSummary`, the output is `{ summary, data }`, for a result the data\n * alone would leave the model guessing about.\n */\nexport function defineClientAction<\n\tconst TName extends string,\n\tTSchema extends z.ZodType,\n\tTOutput\n>(action: {\n\treadonly name: TName\n\treadonly description: string\n\treadonly inputSchema: TSchema\n\treadonly requiresApproval?: boolean\n\treadonly resultSummary?: string\n\treadonly handler: (input: z.output<TSchema>) => Promise<TOutput>\n}): ClientAction<TName> {\n\treturn {\n\t\tname: action.name,\n\t\tdescription: action.description,\n\t\tinputSchema: action.inputSchema,\n\t\t...(action.requiresApproval === undefined\n\t\t\t? {}\n\t\t\t: { requiresApproval: action.requiresApproval }),\n\t\trun: async (input) => {\n\t\t\tconst parsed = action.inputSchema.safeParse(\n\t\t\t\tnormalizeModelNulls(action.inputSchema, input ?? {})\n\t\t\t)\n\n\t\t\tif (!parsed.success) {\n\t\t\t\tthrow new Error(CLIENT_ACTION_INVALID_INPUT)\n\t\t\t}\n\n\t\t\tconst data = await action.handler(parsed.data)\n\n\t\t\treturn action.resultSummary === undefined\n\t\t\t\t? data\n\t\t\t\t: { summary: action.resultSummary, data }\n\t\t}\n\t}\n}\n\n/**\n * Model arguments with `null` taken out wherever the schema means \"may be\n * left out\", at any depth. An OpenAI model behind Mastra's schema layer\n * fills every field it leaves alone with `null`; a `null` stays only where\n * the schema itself accepts it. Unions try each member and keep the first\n * reading that parses.\n */\nexport function normalizeModelNulls(\n\tschema: z.ZodType,\n\tvalue: unknown\n): unknown {\n\tconst def = schema._zod.def as unknown as SchemaDef\n\n\tswitch (def.type) {\n\t\tcase 'optional':\n\t\tcase 'nullable':\n\t\tcase 'default':\n\t\tcase 'prefault':\n\t\tcase 'readonly':\n\t\tcase 'catch':\n\t\t\treturn value == null || def.innerType === undefined\n\t\t\t\t? value\n\t\t\t\t: normalizeModelNulls(def.innerType, value)\n\t\tcase 'pipe':\n\t\t\treturn def.in === undefined ? value : normalizeModelNulls(def.in, value)\n\t\tcase 'lazy':\n\t\t\treturn def.getter === undefined\n\t\t\t\t? value\n\t\t\t\t: normalizeModelNulls(def.getter(), value)\n\t\tcase 'array': {\n\t\t\tconst element = def.element\n\n\t\t\treturn Array.isArray(value) && element !== undefined\n\t\t\t\t? value.map((item) => normalizeModelNulls(element, item))\n\t\t\t\t: value\n\t\t}\n\t\tcase 'record': {\n\t\t\tconst valueType = def.valueType\n\n\t\t\tif (!isPlainObject(value) || valueType === undefined) {\n\t\t\t\treturn value\n\t\t\t}\n\n\t\t\treturn Object.fromEntries(\n\t\t\t\tObject.entries(value).flatMap(([key, item]) =>\n\t\t\t\t\titem === null && !valueType.safeParse(null).success\n\t\t\t\t\t\t? []\n\t\t\t\t\t\t: [[key, normalizeModelNulls(valueType, item)]]\n\t\t\t\t)\n\t\t\t)\n\t\t}\n\t\tcase 'union': {\n\t\t\tfor (const option of def.options ?? []) {\n\t\t\t\tconst normalized = normalizeModelNulls(option, value)\n\n\t\t\t\tif (option.safeParse(normalized).success) {\n\t\t\t\t\treturn normalized\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn value\n\t\t}\n\t\tcase 'object': {\n\t\t\tif (!isPlainObject(value) || !(schema instanceof z.ZodObject)) {\n\t\t\t\treturn value\n\t\t\t}\n\n\t\t\tconst shape = schema.shape as Record<string, z.ZodType>\n\n\t\t\treturn Object.fromEntries(\n\t\t\t\tObject.entries(value).flatMap(([key, item]) => {\n\t\t\t\t\tconst field = shape[key]\n\n\t\t\t\t\tif (field === undefined) {\n\t\t\t\t\t\treturn [[key, item]]\n\t\t\t\t\t}\n\n\t\t\t\t\tconst leaveOut =\n\t\t\t\t\t\titem === null &&\n\t\t\t\t\t\tfield.safeParse(undefined).success &&\n\t\t\t\t\t\t!field.safeParse(null).success\n\n\t\t\t\t\treturn leaveOut ? [] : [[key, normalizeModelNulls(field, item)]]\n\t\t\t\t})\n\t\t\t)\n\t\t}\n\t\tdefault:\n\t\t\treturn value\n\t}\n}\n\n/** The definitions a request carries, for the server to offer the model as client tools. */\nexport function toClientToolDefs(\n\tactions: readonly ClientAction[]\n): ClientToolDef[] {\n\treturn actions.map((action) => ({\n\t\tname: action.name,\n\t\tdescription: action.description,\n\t\tjsonSchema: z.toJSONSchema(action.inputSchema, {\n\t\t\tio: 'input',\n\t\t\tunrepresentable: 'any'\n\t\t}),\n\t\t...(action.requiresApproval === undefined\n\t\t\t? {}\n\t\t\t: { requiresApproval: action.requiresApproval })\n\t}))\n}\n\n/**\n * Carries out the client action a tool call names and posts its result on the\n * chat; null when no action has that name (a server tool: its result arrives\n * on the stream). A call the user turn already made with the same input\n * (`null`s aside) is refused instead of carried out again.\n *\n * Never await it inside the chat's `onToolCall`: the AI SDK runs that callback\n * on the queue `addToolOutput` waits on, so awaiting deadlocks the stream.\n */\nexport function runClientAction(\n\tchat: ClientActionChat,\n\tactions: readonly ClientAction[],\n\ttoolCall: ClientToolCall\n): Promise<void> | null {\n\tconst action = actions.find(\n\t\t(candidate) => candidate.name === toolCall.toolName\n\t)\n\n\tif (action === undefined) {\n\t\treturn null\n\t}\n\n\tconst fail = (errorText: string) =>\n\t\tchat.addToolOutput({\n\t\t\tstate: 'output-error',\n\t\t\ttool: toolCall.toolName,\n\t\t\ttoolCallId: toolCall.toolCallId,\n\t\t\terrorText\n\t\t})\n\n\treturn (async () => {\n\t\tif (isRepeatInTurn(chat.messages, action, toolCall)) {\n\t\t\tawait fail(CLIENT_ACTION_REPEATED)\n\n\t\t\treturn\n\t\t}\n\n\t\ttry {\n\t\t\tconst output = await action.run(toolCall.input)\n\n\t\t\tawait chat.addToolOutput({\n\t\t\t\ttool: toolCall.toolName,\n\t\t\t\ttoolCallId: toolCall.toolCallId,\n\t\t\t\toutput\n\t\t\t})\n\t\t} catch (error) {\n\t\t\tawait fail(error instanceof Error ? error.message : CLIENT_ACTION_FAILED)\n\t\t}\n\t})()\n}\n\n/**\n * The same action with the same input already ran in this user turn. Running\n * it again changes nothing and only feeds a model that is circling.\n */\nfunction isRepeatInTurn(\n\tmessages: readonly UIMessage[],\n\taction: ClientAction,\n\ttoolCall: ClientToolCall\n): boolean {\n\tconst key = callKey(action, toolCall.input)\n\n\treturn currentUserTurn(messages).some((message) =>\n\t\tmessage.parts.some(\n\t\t\t(part) =>\n\t\t\t\tisToolUIPart(part) &&\n\t\t\t\tpart.toolCallId !== toolCall.toolCallId &&\n\t\t\t\tpart.state === 'output-available' &&\n\t\t\t\tgetToolName(part) === action.name &&\n\t\t\t\tcallKey(action, part.input) === key\n\t\t)\n\t)\n}\n\n/** The input with the model's nulls left out, so the same call matches however the model wrote it. */\nfunction callKey(action: ClientAction, input: unknown): string {\n\treturn JSON.stringify(normalizeModelNulls(action.inputSchema, input ?? {}))\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n"]}
|
|
1
|
+
{"version":3,"file":"actions.js","sourceRoot":"","sources":["../../src/client/actions.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Client actions: tools the app carries out itself (open a screen, fill a\n * form), whose definitions go along with each request and whose results go\n * back as the turn's follow-up. A call reaches the app as the model wrote\n * it, without the server's input validation, so the app parses it here.\n */\n\nimport type { UIMessage } from 'ai'\nimport { getToolName, isToolUIPart } from 'ai'\nimport { z } from 'zod'\n\nimport { currentUserTurn } from './messages.js'\n\n// TYPES\n\ntype MessagePart = UIMessage['parts'][number]\n\n/** Where the call sits, for an action with its own policy on repeats or shared steps. */\nexport interface ClientActionContext {\n\treadonly toolCallId: string\n\t/** The chat's messages when the call arrived; the call is in the last one. */\n\treadonly messages: readonly UIMessage[]\n\t/** The parts of the model step that made the call, the call and any server tool beside it included. */\n\treadonly step: readonly MessagePart[]\n}\n\n/** A tool that runs in the client. The server never interprets its name. */\nexport interface ClientAction<TName extends string = string> {\n\treadonly name: TName\n\treadonly description: string\n\treadonly inputSchema: z.ZodType\n\treadonly requiresApproval?: boolean\n\t/** Runs the action on the model's raw input. Throw to report a failure: the model reads it as an error it can act on. */\n\treadonly run: (\n\t\tinput: unknown,\n\t\tcontext: ClientActionContext\n\t) => Promise<unknown>\n\t/**\n\t * A call the user turn already made with the same input. Its result is\n\t * posted as the call's output; throw to post an error. Default: an error\n\t * with `CLIENT_ACTION_REPEATED`.\n\t */\n\treadonly onRepeat?: (\n\t\tinput: unknown,\n\t\tcontext: ClientActionContext\n\t) => Promise<unknown>\n}\n\n/** A client action as a request carries it, for the server to offer the model. */\nexport interface ClientToolDef {\n\treadonly name: string\n\treadonly description: string\n\treadonly jsonSchema: unknown\n\treadonly requiresApproval?: boolean\n}\n\n/** A tool call the stream handed to the client. */\nexport interface ClientToolCall {\n\treadonly toolCallId: string\n\treadonly toolName: string\n\treadonly input: unknown\n}\n\n/** The part of the AI SDK `Chat` a client action needs. */\nexport type ClientActionChat = {\n\treadonly messages: UIMessage[]\n\treadonly addToolOutput: (\n\t\toptions:\n\t\t\t| { tool: string; toolCallId: string; output: unknown }\n\t\t\t| {\n\t\t\t\t\tstate: 'output-error'\n\t\t\t\t\ttool: string\n\t\t\t\t\ttoolCallId: string\n\t\t\t\t\terrorText: string\n\t\t\t }\n\t) => void | PromiseLike<void>\n}\n\ntype SchemaDef = {\n\treadonly type: string\n\treadonly innerType?: z.ZodType\n\treadonly element?: z.ZodType\n\treadonly valueType?: z.ZodType\n\treadonly in?: z.ZodType\n\treadonly options?: readonly z.ZodType[]\n\treadonly getter?: () => z.ZodType\n}\n\n// CONSTANTS\n\n/** What the model reads when an action throws something that is not an Error. */\nexport const CLIENT_ACTION_FAILED = 'The action failed.'\n\n/** What the model reads when the app cannot read the call's input. */\nexport const CLIENT_ACTION_INVALID_INPUT =\n\t'The app could not read this input. Call the action again with input that fits its schema.'\n\n/** What the model reads for a call it already made with the same input in this turn. */\nexport const CLIENT_ACTION_REPEATED =\n\t'Already done with these values in this turn. Do not call it again; tell the user what changed.'\n\n// FUNCTIONS\n\n/**\n * A client action whose handler gets its input parsed, with the model's\n * `null`s taken out where the schema means \"may be left out\". With\n * `resultSummary`, the output is `{ summary, data }`, for a result the data\n * alone would leave the model guessing about.\n */\nexport function defineClientAction<\n\tconst TName extends string,\n\tTSchema extends z.ZodType,\n\tTOutput\n>(action: {\n\treadonly name: TName\n\treadonly description: string\n\treadonly inputSchema: TSchema\n\treadonly requiresApproval?: boolean\n\treadonly resultSummary?: string\n\treadonly handler: (\n\t\tinput: z.output<TSchema>,\n\t\tcontext: ClientActionContext\n\t) => Promise<TOutput>\n\treadonly onRepeat?: ClientAction['onRepeat']\n}): ClientAction<TName> {\n\treturn {\n\t\tname: action.name,\n\t\tdescription: action.description,\n\t\tinputSchema: action.inputSchema,\n\t\t...(action.requiresApproval === undefined\n\t\t\t? {}\n\t\t\t: { requiresApproval: action.requiresApproval }),\n\t\t...(action.onRepeat === undefined ? {} : { onRepeat: action.onRepeat }),\n\t\trun: async (input, context) => {\n\t\t\tconst parsed = action.inputSchema.safeParse(\n\t\t\t\tnormalizeModelNulls(action.inputSchema, input ?? {})\n\t\t\t)\n\n\t\t\tif (!parsed.success) {\n\t\t\t\tthrow new Error(CLIENT_ACTION_INVALID_INPUT)\n\t\t\t}\n\n\t\t\tconst data = await action.handler(parsed.data, context)\n\n\t\t\treturn action.resultSummary === undefined\n\t\t\t\t? data\n\t\t\t\t: { summary: action.resultSummary, data }\n\t\t}\n\t}\n}\n\n/**\n * Model arguments with `null` taken out wherever the schema means \"may be\n * left out\", at any depth. An OpenAI model behind Mastra's schema layer\n * fills every field it leaves alone with `null`; a `null` stays only where\n * the schema itself accepts it. Unions try each member and keep the first\n * reading that parses.\n */\nexport function normalizeModelNulls(\n\tschema: z.ZodType,\n\tvalue: unknown\n): unknown {\n\tconst def = schema._zod.def as unknown as SchemaDef\n\n\tswitch (def.type) {\n\t\tcase 'optional':\n\t\tcase 'nullable':\n\t\tcase 'default':\n\t\tcase 'prefault':\n\t\tcase 'readonly':\n\t\tcase 'catch':\n\t\t\treturn value == null || def.innerType === undefined\n\t\t\t\t? value\n\t\t\t\t: normalizeModelNulls(def.innerType, value)\n\t\tcase 'pipe':\n\t\t\treturn def.in === undefined ? value : normalizeModelNulls(def.in, value)\n\t\tcase 'lazy':\n\t\t\treturn def.getter === undefined\n\t\t\t\t? value\n\t\t\t\t: normalizeModelNulls(def.getter(), value)\n\t\tcase 'array': {\n\t\t\tconst element = def.element\n\n\t\t\treturn Array.isArray(value) && element !== undefined\n\t\t\t\t? value.map((item) => normalizeModelNulls(element, item))\n\t\t\t\t: value\n\t\t}\n\t\tcase 'record': {\n\t\t\tconst valueType = def.valueType\n\n\t\t\tif (!isPlainObject(value) || valueType === undefined) {\n\t\t\t\treturn value\n\t\t\t}\n\n\t\t\treturn Object.fromEntries(\n\t\t\t\tObject.entries(value).flatMap(([key, item]) =>\n\t\t\t\t\titem === null && !valueType.safeParse(null).success\n\t\t\t\t\t\t? []\n\t\t\t\t\t\t: [[key, normalizeModelNulls(valueType, item)]]\n\t\t\t\t)\n\t\t\t)\n\t\t}\n\t\tcase 'union': {\n\t\t\tfor (const option of def.options ?? []) {\n\t\t\t\tconst normalized = normalizeModelNulls(option, value)\n\n\t\t\t\tif (option.safeParse(normalized).success) {\n\t\t\t\t\treturn normalized\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn value\n\t\t}\n\t\tcase 'object': {\n\t\t\tif (!isPlainObject(value) || !(schema instanceof z.ZodObject)) {\n\t\t\t\treturn value\n\t\t\t}\n\n\t\t\tconst shape = schema.shape as Record<string, z.ZodType>\n\n\t\t\treturn Object.fromEntries(\n\t\t\t\tObject.entries(value).flatMap(([key, item]) => {\n\t\t\t\t\tconst field = shape[key]\n\n\t\t\t\t\tif (field === undefined) {\n\t\t\t\t\t\treturn [[key, item]]\n\t\t\t\t\t}\n\n\t\t\t\t\tconst leaveOut =\n\t\t\t\t\t\titem === null &&\n\t\t\t\t\t\tfield.safeParse(undefined).success &&\n\t\t\t\t\t\t!field.safeParse(null).success\n\n\t\t\t\t\treturn leaveOut ? [] : [[key, normalizeModelNulls(field, item)]]\n\t\t\t\t})\n\t\t\t)\n\t\t}\n\t\tdefault:\n\t\t\treturn value\n\t}\n}\n\n/** The definitions a request carries, for the server to offer the model as client tools. */\nexport function toClientToolDefs(\n\tactions: readonly ClientAction[]\n): ClientToolDef[] {\n\treturn actions.map((action) => ({\n\t\tname: action.name,\n\t\tdescription: action.description,\n\t\tjsonSchema: z.toJSONSchema(action.inputSchema, {\n\t\t\tio: 'input',\n\t\t\tunrepresentable: 'any'\n\t\t}),\n\t\t...(action.requiresApproval === undefined\n\t\t\t? {}\n\t\t\t: { requiresApproval: action.requiresApproval })\n\t}))\n}\n\n/**\n * Carries out the client action a tool call names and posts its result on the\n * chat; null when no action has that name (a server tool: its result arrives\n * on the stream). A call the user turn already made with the same input\n * (`null`s aside) is refused instead of carried out again, unless the action\n * has its own `onRepeat`.\n *\n * Never await it inside the chat's `onToolCall`: the AI SDK runs that callback\n * on the queue `addToolOutput` waits on, so awaiting deadlocks the stream.\n */\nexport function runClientAction(\n\tchat: ClientActionChat,\n\tactions: readonly ClientAction[],\n\ttoolCall: ClientToolCall\n): Promise<void> | null {\n\tconst action = actions.find(\n\t\t(candidate) => candidate.name === toolCall.toolName\n\t)\n\n\tif (action === undefined) {\n\t\treturn null\n\t}\n\n\tconst fail = (errorText: string) =>\n\t\tchat.addToolOutput({\n\t\t\tstate: 'output-error',\n\t\t\ttool: toolCall.toolName,\n\t\t\ttoolCallId: toolCall.toolCallId,\n\t\t\terrorText\n\t\t})\n\n\tconst context: ClientActionContext = {\n\t\ttoolCallId: toolCall.toolCallId,\n\t\tmessages: chat.messages,\n\t\tstep: stepOf(chat.messages, toolCall.toolCallId)\n\t}\n\n\treturn (async () => {\n\t\tconst repeated = isRepeatInTurn(chat.messages, action, toolCall)\n\n\t\tif (repeated && action.onRepeat === undefined) {\n\t\t\tawait fail(CLIENT_ACTION_REPEATED)\n\n\t\t\treturn\n\t\t}\n\n\t\ttry {\n\t\t\tconst output = await (repeated && action.onRepeat\n\t\t\t\t? action.onRepeat(toolCall.input, context)\n\t\t\t\t: action.run(toolCall.input, context))\n\n\t\t\tawait chat.addToolOutput({\n\t\t\t\ttool: toolCall.toolName,\n\t\t\t\ttoolCallId: toolCall.toolCallId,\n\t\t\t\toutput\n\t\t\t})\n\t\t} catch (error) {\n\t\t\tawait fail(error instanceof Error ? error.message : CLIENT_ACTION_FAILED)\n\t\t}\n\t})()\n}\n\n/**\n * The same action with the same input already ran in this user turn. Running\n * it again changes nothing and only feeds a model that is circling.\n */\nfunction isRepeatInTurn(\n\tmessages: readonly UIMessage[],\n\taction: ClientAction,\n\ttoolCall: ClientToolCall\n): boolean {\n\tconst key = callKey(action, toolCall.input)\n\n\treturn currentUserTurn(messages).some((message) =>\n\t\tmessage.parts.some(\n\t\t\t(part) =>\n\t\t\t\tisToolUIPart(part) &&\n\t\t\t\tpart.toolCallId !== toolCall.toolCallId &&\n\t\t\t\tpart.state === 'output-available' &&\n\t\t\t\tgetToolName(part) === action.name &&\n\t\t\t\tcallKey(action, part.input) === key\n\t\t)\n\t)\n}\n\n/** The parts between the `step-start` before the call and the next one; none when the call is not in the last message. */\nfunction stepOf(\n\tmessages: readonly UIMessage[],\n\ttoolCallId: string\n): MessagePart[] {\n\tconst parts = messages.at(-1)?.parts ?? []\n\tconst at = parts.findIndex(\n\t\t(part) => isToolUIPart(part) && part.toolCallId === toolCallId\n\t)\n\n\tif (at === -1) {\n\t\treturn []\n\t}\n\n\t// The tsconfig lib predates `findLastIndex`.\n\tconst start = parts\n\t\t.slice(0, at)\n\t\t.map((part) => part.type)\n\t\t.lastIndexOf('step-start')\n\tconst end = parts.findIndex(\n\t\t(part, index) => index > at && part.type === 'step-start'\n\t)\n\n\treturn parts.slice(start + 1, end === -1 ? undefined : end)\n}\n\n/** The input with the model's nulls left out, so the same call matches however the model wrote it. */\nfunction callKey(action: ClientAction, input: unknown): string {\n\treturn JSON.stringify(normalizeModelNulls(action.inputSchema, input ?? {}))\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n"]}
|
package/dist/client/index.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAA;AAC5B,cAAc,YAAY,CAAA;AAC1B,cAAc,WAAW,CAAA;AACzB,cAAc,aAAa,CAAA;AAC3B,cAAc,YAAY,CAAA;AAC1B,cAAc,eAAe,CAAA;AAC7B,cAAc,cAAc,CAAA;AAC5B,cAAc,cAAc,CAAA;AAC5B,cAAc,aAAa,CAAA;AAC3B,cAAc,qBAAqB,CAAA;AACnC,cAAc,cAAc,CAAA;AAC5B,cAAc,WAAW,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAA;AAC5B,cAAc,YAAY,CAAA;AAC1B,cAAc,WAAW,CAAA;AACzB,cAAc,aAAa,CAAA;AAC3B,cAAc,YAAY,CAAA;AAC1B,cAAc,eAAe,CAAA;AAC7B,cAAc,cAAc,CAAA;AAC5B,cAAc,cAAc,CAAA;AAC5B,cAAc,aAAa,CAAA;AAC3B,cAAc,aAAa,CAAA;AAC3B,cAAc,qBAAqB,CAAA;AACnC,cAAc,cAAc,CAAA;AAC5B,cAAc,WAAW,CAAA"}
|
package/dist/client/index.js
CHANGED
package/dist/client/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAA;AAC5B,cAAc,YAAY,CAAA;AAC1B,cAAc,WAAW,CAAA;AACzB,cAAc,aAAa,CAAA;AAC3B,cAAc,YAAY,CAAA;AAC1B,cAAc,eAAe,CAAA;AAC7B,cAAc,cAAc,CAAA;AAC5B,cAAc,cAAc,CAAA;AAC5B,cAAc,aAAa,CAAA;AAC3B,cAAc,qBAAqB,CAAA;AACnC,cAAc,cAAc,CAAA;AAC5B,cAAc,WAAW,CAAA","sourcesContent":["export * from './actions.js'\nexport * from './cards.js'\nexport * from './chat.js'\nexport * from './errors.js'\nexport * from './fetch.js'\nexport * from './messages.js'\nexport * from './pending.js'\nexport * from './ratings.js'\nexport * from './reveal.js'\nexport * from './screen-context.js'\nexport * from './session.js'\nexport * from './turn.js'\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAA;AAC5B,cAAc,YAAY,CAAA;AAC1B,cAAc,WAAW,CAAA;AACzB,cAAc,aAAa,CAAA;AAC3B,cAAc,YAAY,CAAA;AAC1B,cAAc,eAAe,CAAA;AAC7B,cAAc,cAAc,CAAA;AAC5B,cAAc,cAAc,CAAA;AAC5B,cAAc,aAAa,CAAA;AAC3B,cAAc,aAAa,CAAA;AAC3B,cAAc,qBAAqB,CAAA;AACnC,cAAc,cAAc,CAAA;AAC5B,cAAc,WAAW,CAAA","sourcesContent":["export * from './actions.js'\nexport * from './cards.js'\nexport * from './chat.js'\nexport * from './errors.js'\nexport * from './fetch.js'\nexport * from './messages.js'\nexport * from './pending.js'\nexport * from './ratings.js'\nexport * from './resume.js'\nexport * from './reveal.js'\nexport * from './screen-context.js'\nexport * from './session.js'\nexport * from './turn.js'\n"]}
|
|
@@ -31,6 +31,8 @@ export interface RequestMessageOptions {
|
|
|
31
31
|
/** The transient data part a server streams after an answer: up to three replies the user can tap to send. */
|
|
32
32
|
export declare const REPLY_SUGGESTIONS_PART = "data-reply-suggestions";
|
|
33
33
|
export declare const REPLY_SUGGESTIONS_MAX = 3;
|
|
34
|
+
/** The transient data part a turn streams right after its start: the user's message, for a client that reattaches before the thread stores it. */
|
|
35
|
+
export declare const USER_MESSAGE_PART = "data-user-message";
|
|
34
36
|
/** The longest reply offered, in characters; a longer one is dropped. */
|
|
35
37
|
export declare const REPLY_SUGGESTION_MAX_LENGTH = 80;
|
|
36
38
|
/**
|
|
@@ -61,13 +63,28 @@ export declare function toMessageBlocks(message: UIMessage): MessageBlock[];
|
|
|
61
63
|
export declare function selectRequestMessages(messages: readonly UIMessage[], options?: RequestMessageOptions): UIMessage[];
|
|
62
64
|
/** From the last user message to the end; the trailing message alone when there is no user message. */
|
|
63
65
|
export declare function currentUserTurn(messages: readonly UIMessage[]): UIMessage[];
|
|
66
|
+
/** Which parts an answer resumes after: Mastra stores the continuation as a message of its own. */
|
|
67
|
+
export type ResumePointOptions = {
|
|
68
|
+
/** Default `isToolResultPart`: after any tool's result. */
|
|
69
|
+
readonly isResumePoint?: (part: MessagePart) => boolean;
|
|
70
|
+
};
|
|
71
|
+
/** A tool call with its output or error, the default resume point. */
|
|
72
|
+
export declare function isToolResultPart(part: MessagePart): boolean;
|
|
73
|
+
/**
|
|
74
|
+
* Joins an assistant message into the assistant message before it when that
|
|
75
|
+
* one ends at a resume point. Mastra stores the text a run wrote after
|
|
76
|
+
* resuming from a client tool's result as a new message, while the client
|
|
77
|
+
* streamed it into the message that holds the call; joining keeps that
|
|
78
|
+
* message and its id.
|
|
79
|
+
*/
|
|
80
|
+
export declare function joinResumedAnswers<M extends UIMessage>(messages: readonly M[], options?: ResumePointOptions): M[];
|
|
64
81
|
/**
|
|
65
82
|
* The stored messages, oldest first, then the live ones. A message in both
|
|
66
83
|
* shows once, as the live copy, which is the one still changing. An answer
|
|
67
|
-
* resumed after a client action's result shows as one answer
|
|
68
|
-
*
|
|
84
|
+
* resumed after a client action's result shows as one answer (see
|
|
85
|
+
* `joinResumedAnswers`).
|
|
69
86
|
*/
|
|
70
|
-
export declare function mergeThreadMessages<M extends UIMessage>(stored: readonly M[], live: readonly M[]): M[];
|
|
87
|
+
export declare function mergeThreadMessages<M extends UIMessage>(stored: readonly M[], live: readonly M[], options?: ResumePointOptions): M[];
|
|
71
88
|
/**
|
|
72
89
|
* Server tool calls a request left at `input-available`: Mastra drops a
|
|
73
90
|
* server result when a client action shares its step. The session answers
|
|
@@ -98,6 +115,14 @@ export declare function countShownParts(message: UIMessage | undefined, isUserFa
|
|
|
98
115
|
* too long or repeated ones, at most three; undefined when the data is not a
|
|
99
116
|
* reply list.
|
|
100
117
|
*/
|
|
118
|
+
/** The user message a `data-user-message` part carries, or undefined for any other data. */
|
|
119
|
+
export declare function readUserMessage(data: unknown): UIMessage | undefined;
|
|
120
|
+
/**
|
|
121
|
+
* Why the server stopped the answer early, such as `tool_call_budget`
|
|
122
|
+
* (`TOOL_CALL_BUDGET_STOP_REASON`): the `reason` of its tripwire part.
|
|
123
|
+
* Undefined for an answer that ended on its own.
|
|
124
|
+
*/
|
|
125
|
+
export declare function readStopReason(message: UIMessage | undefined): string | undefined;
|
|
101
126
|
export declare function readReplySuggestions(data: unknown): readonly string[] | undefined;
|
|
102
127
|
/** The replies worth offering from a model's list, in its order. */
|
|
103
128
|
export declare function toReplySuggestions(suggestions: readonly (string | undefined)[]): readonly string[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"messages.d.ts","sourceRoot":"","sources":["../../src/client/messages.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAKN,KAAK,SAAS,EACd,MAAM,IAAI,CAAA;AAMX,KAAK,WAAW,GAAG,SAAS,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,CAAA;AAE7C,oEAAoE;AACpE,MAAM,MAAM,QAAQ,GAAG,OAAO,CAAC,WAAW,EAAE;IAAE,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;CAAE,CAAC,CAAA;AAE5E,4DAA4D;AAC5D,MAAM,MAAM,YAAY,GACrB;IACA,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,6CAA6C;IAC7C,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAA;CAC1B;AACH,yFAAyF;GACvF;IACA,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAA;IACtB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,KAAK,EAAE,SAAS,QAAQ,EAAE,CAAA;CAClC,CAAA;AAEJ,MAAM,WAAW,qBAAqB;IACrC,+GAA+G;IAC/G,QAAQ,CAAC,cAAc,CAAC,EAAE,CAAC,IAAI,EAAE,QAAQ,KAAK,OAAO,CAAA;CACrD;AAMD,8GAA8G;AAC9G,eAAO,MAAM,sBAAsB,2BAA2B,CAAA;AAE9D,eAAO,MAAM,qBAAqB,IAAI,CAAA;AAEtC,yEAAyE;AACzE,eAAO,MAAM,2BAA2B,KAAK,CAAA;AAE7C;;;GAGG;AACH,eAAO,MAAM,mBAAmB,yIACuG,CAAA;AAIvI,+GAA+G;AAC/G,wBAAgB,gBAAgB,CAAC,IAAI,EAAE;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAEzE;AAED,mDAAmD;AACnD,wBAAgB,mBAAmB,CAAC,CAAC,SAAS,SAAS,EAAE,OAAO,EAAE,CAAC,GAAG,CAAC,CAEtE;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,SAAS,GAAG,YAAY,EAAE,CA+BlE;AAED;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CACpC,QAAQ,EAAE,SAAS,SAAS,EAAE,EAC9B,OAAO,GAAE,qBAA0B,GACjC,SAAS,EAAE,CAuBb;AAED,uGAAuG;AACvG,wBAAgB,eAAe,CAAC,QAAQ,EAAE,SAAS,SAAS,EAAE,GAAG,SAAS,EAAE,CAU3E;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,CAAC,SAAS,SAAS,EACtD,MAAM,EAAE,SAAS,CAAC,EAAE,EACpB,IAAI,EAAE,SAAS,CAAC,EAAE,
|
|
1
|
+
{"version":3,"file":"messages.d.ts","sourceRoot":"","sources":["../../src/client/messages.ts"],"names":[],"mappings":"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"}
|
package/dist/client/messages.js
CHANGED
|
@@ -11,6 +11,10 @@ const OBSERVATIONAL_MEMORY_PART_PREFIX = 'data-om-';
|
|
|
11
11
|
/** The transient data part a server streams after an answer: up to three replies the user can tap to send. */
|
|
12
12
|
export const REPLY_SUGGESTIONS_PART = 'data-reply-suggestions';
|
|
13
13
|
export const REPLY_SUGGESTIONS_MAX = 3;
|
|
14
|
+
/** The transient data part a turn streams right after its start: the user's message, for a client that reattaches before the thread stores it. */
|
|
15
|
+
export const USER_MESSAGE_PART = 'data-user-message';
|
|
16
|
+
/** The tripwire part a run that a processor stopped ends with; its `reason` says why. */
|
|
17
|
+
const TRIPWIRE_PART = 'data-tripwire';
|
|
14
18
|
/** The longest reply offered, in characters; a longer one is dropped. */
|
|
15
19
|
export const REPLY_SUGGESTION_MAX_LENGTH = 80;
|
|
16
20
|
/**
|
|
@@ -98,27 +102,27 @@ export function currentUserTurn(messages) {
|
|
|
98
102
|
const trailing = messages.at(-1);
|
|
99
103
|
return trailing === undefined ? [] : [trailing];
|
|
100
104
|
}
|
|
105
|
+
/** A tool call with its output or error, the default resume point. */
|
|
106
|
+
export function isToolResultPart(part) {
|
|
107
|
+
return (isToolUIPart(part) &&
|
|
108
|
+
(part.state === 'output-available' || part.state === 'output-error'));
|
|
109
|
+
}
|
|
101
110
|
/**
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
* the
|
|
111
|
+
* Joins an assistant message into the assistant message before it when that
|
|
112
|
+
* one ends at a resume point. Mastra stores the text a run wrote after
|
|
113
|
+
* resuming from a client tool's result as a new message, while the client
|
|
114
|
+
* streamed it into the message that holds the call; joining keeps that
|
|
115
|
+
* message and its id.
|
|
106
116
|
*/
|
|
107
|
-
export function
|
|
108
|
-
const
|
|
109
|
-
|
|
110
|
-
...stored.filter((message) => !liveIds.has(message.id)),
|
|
111
|
-
...live
|
|
112
|
-
];
|
|
113
|
-
return merged.reduce((joined, message) => {
|
|
117
|
+
export function joinResumedAnswers(messages, options = {}) {
|
|
118
|
+
const isResumePoint = options.isResumePoint ?? isToolResultPart;
|
|
119
|
+
return messages.reduce((joined, message) => {
|
|
114
120
|
const previous = joined.at(-1);
|
|
115
121
|
const lastPart = previous?.parts.at(-1);
|
|
116
122
|
const resumesPrevious = message.role === 'assistant' &&
|
|
117
123
|
previous?.role === 'assistant' &&
|
|
118
124
|
lastPart !== undefined &&
|
|
119
|
-
|
|
120
|
-
(lastPart.state === 'output-available' ||
|
|
121
|
-
lastPart.state === 'output-error');
|
|
125
|
+
isResumePoint(lastPart);
|
|
122
126
|
if (previous !== undefined && resumesPrevious) {
|
|
123
127
|
joined[joined.length - 1] = {
|
|
124
128
|
...previous,
|
|
@@ -131,6 +135,16 @@ export function mergeThreadMessages(stored, live) {
|
|
|
131
135
|
return joined;
|
|
132
136
|
}, []);
|
|
133
137
|
}
|
|
138
|
+
/**
|
|
139
|
+
* The stored messages, oldest first, then the live ones. A message in both
|
|
140
|
+
* shows once, as the live copy, which is the one still changing. An answer
|
|
141
|
+
* resumed after a client action's result shows as one answer (see
|
|
142
|
+
* `joinResumedAnswers`).
|
|
143
|
+
*/
|
|
144
|
+
export function mergeThreadMessages(stored, live, options = {}) {
|
|
145
|
+
const liveIds = new Set(live.map((message) => message.id));
|
|
146
|
+
return joinResumedAnswers([...stored.filter((message) => !liveIds.has(message.id)), ...live], options);
|
|
147
|
+
}
|
|
134
148
|
/**
|
|
135
149
|
* Server tool calls a request left at `input-available`: Mastra drops a
|
|
136
150
|
* server result when a client action shares its step. The session answers
|
|
@@ -202,6 +216,32 @@ export function countShownParts(message, isUserFacing = isUserFacingPart) {
|
|
|
202
216
|
* too long or repeated ones, at most three; undefined when the data is not a
|
|
203
217
|
* reply list.
|
|
204
218
|
*/
|
|
219
|
+
/** The user message a `data-user-message` part carries, or undefined for any other data. */
|
|
220
|
+
export function readUserMessage(data) {
|
|
221
|
+
const message = data?.message;
|
|
222
|
+
return typeof message?.id === 'string' &&
|
|
223
|
+
message.role === 'user' &&
|
|
224
|
+
Array.isArray(message.parts)
|
|
225
|
+
? message
|
|
226
|
+
: undefined;
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Why the server stopped the answer early, such as `tool_call_budget`
|
|
230
|
+
* (`TOOL_CALL_BUDGET_STOP_REASON`): the `reason` of its tripwire part.
|
|
231
|
+
* Undefined for an answer that ended on its own.
|
|
232
|
+
*/
|
|
233
|
+
export function readStopReason(message) {
|
|
234
|
+
if (message?.role !== 'assistant')
|
|
235
|
+
return undefined;
|
|
236
|
+
for (const part of message.parts) {
|
|
237
|
+
const reason = part.type === TRIPWIRE_PART
|
|
238
|
+
? part.data?.reason
|
|
239
|
+
: undefined;
|
|
240
|
+
if (typeof reason === 'string')
|
|
241
|
+
return reason;
|
|
242
|
+
}
|
|
243
|
+
return undefined;
|
|
244
|
+
}
|
|
205
245
|
export function readReplySuggestions(data) {
|
|
206
246
|
const suggestions = data?.suggestions;
|
|
207
247
|
if (!Array.isArray(suggestions)) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"messages.js","sourceRoot":"","sources":["../../src/client/messages.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Message helpers every client shares: which messages one request sends,\n * which parts reach the user, the stored thread merged with the live one,\n * tool calls a request left without a result, and the reply suggestions an\n * answer offers.\n */\n\nimport {\n\tgetToolName,\n\tisReasoningUIPart,\n\tisTextUIPart,\n\tisToolUIPart,\n\ttype UIMessage\n} from 'ai'\n\nimport { readSuspendedRun } from './pending.js'\n\n// TYPES\n\ntype MessagePart = UIMessage['parts'][number]\n\n/** A tool call in a message, from a typed tool or a dynamic one. */\nexport type ToolPart = Extract<MessagePart, { readonly toolCallId: string }>\n\n/** One block a message draws, in the order of its parts. */\nexport type MessageBlock =\n\t| {\n\t\t\treadonly kind: 'text'\n\t\t\treadonly key: string\n\t\t\treadonly text: string\n\t\t\t/** The AI SDK is still writing this text. */\n\t\t\treadonly streaming: boolean\n\t }\n\t/** Neighbouring tool parts: the cards of one step sit in one place between the texts. */\n\t| {\n\t\t\treadonly kind: 'tools'\n\t\t\treadonly key: string\n\t\t\treadonly parts: readonly ToolPart[]\n\t }\n\nexport interface RequestMessageOptions {\n\t/** Whether a tool part holds a result the client produced: a client action's result, or an error it posted. */\n\treadonly isClientResult?: (part: ToolPart) => boolean\n}\n\n// CONSTANTS\n\nconst OBSERVATIONAL_MEMORY_PART_PREFIX = 'data-om-'\n\n/** The transient data part a server streams after an answer: up to three replies the user can tap to send. */\nexport const REPLY_SUGGESTIONS_PART = 'data-reply-suggestions'\n\nexport const REPLY_SUGGESTIONS_MAX = 3\n\n/** The longest reply offered, in characters; a longer one is dropped. */\nexport const REPLY_SUGGESTION_MAX_LENGTH = 80\n\n/**\n * What the model reads for a server tool call a request left without a\n * result: Mastra drops a server result when a client action shares its step.\n */\nexport const STRANDED_TOOL_ERROR =\n\t'This tool call was interrupted because it ran in the same step as a client action. Call the tool again on its own to get the result.'\n\n// FUNCTIONS\n\n/** Whether a message part may reach the user: observational memory's `data-om-*` bookkeeping parts may not. */\nexport function isUserFacingPart(part: { readonly type: string }): boolean {\n\treturn !part.type.startsWith(OBSERVATIONAL_MEMORY_PART_PREFIX)\n}\n\n/** The message with only its user-facing parts. */\nexport function toUserFacingMessage<M extends UIMessage>(message: M): M {\n\treturn { ...message, parts: message.parts.filter(isUserFacingPart) }\n}\n\n/**\n * The blocks one message draws: its non-blank texts and its runs of tool\n * parts. Every other part, and every part observational memory adds, draws\n * nothing here; a client draws the data parts it knows itself.\n */\nexport function toMessageBlocks(message: UIMessage): MessageBlock[] {\n\tconst blocks: MessageBlock[] = []\n\n\tfor (const [index, part] of message.parts.entries()) {\n\t\tconst key = `${message.id}:${index}`\n\t\tconst previous = blocks.at(-1)\n\n\t\tif (!isUserFacingPart(part)) {\n\t\t\tcontinue\n\t\t}\n\n\t\tif (isTextUIPart(part) && part.text.trim() !== '') {\n\t\t\tblocks.push({\n\t\t\t\tkind: 'text',\n\t\t\t\tkey,\n\t\t\t\ttext: part.text,\n\t\t\t\tstreaming: part.state === 'streaming'\n\t\t\t})\n\t\t} else if (isToolUIPart(part)) {\n\t\t\tif (previous?.kind === 'tools') {\n\t\t\t\tblocks[blocks.length - 1] = {\n\t\t\t\t\t...previous,\n\t\t\t\t\tparts: [...previous.parts, part]\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tblocks.push({ kind: 'tools', key, parts: [part] })\n\t\t\t}\n\t\t}\n\t}\n\n\treturn blocks\n}\n\n/**\n * The messages one request sends. Observational memory supplies the thread's\n * history, so a new user turn sends only the user's new message. When the\n * trailing assistant message carries results the client produced (client\n * action results, approval answers), the messages of the current user turn,\n * ending with that message. To resume a suspended run, the trailing assistant\n * message alone: Mastra resumes the run from its snapshot.\n */\nexport function selectRequestMessages(\n\tmessages: readonly UIMessage[],\n\toptions: RequestMessageOptions = {}\n): UIMessage[] {\n\tconst lastMessage = messages.at(-1)\n\n\tif (lastMessage === undefined) {\n\t\treturn []\n\t}\n\n\tswitch (lastMessage.role) {\n\t\tcase 'user':\n\t\t\treturn [lastMessage]\n\t\tcase 'assistant':\n\t\t\tif (\n\t\t\t\tlastMessage.parts.some((part) => readSuspendedRun(part) !== undefined)\n\t\t\t) {\n\t\t\t\treturn [lastMessage]\n\t\t\t}\n\n\t\t\treturn carriesClientResults(lastMessage, options)\n\t\t\t\t? currentUserTurn(messages)\n\t\t\t\t: lastUserMessage(messages)\n\t\tcase 'system':\n\t\t\treturn lastUserMessage(messages)\n\t}\n}\n\n/** From the last user message to the end; the trailing message alone when there is no user message. */\nexport function currentUserTurn(messages: readonly UIMessage[]): UIMessage[] {\n\tconst lastUserIndex = lastIndexOfUser(messages)\n\n\tif (lastUserIndex !== -1) {\n\t\treturn messages.slice(lastUserIndex)\n\t}\n\n\tconst trailing = messages.at(-1)\n\n\treturn trailing === undefined ? [] : [trailing]\n}\n\n/**\n * The stored messages, oldest first, then the live ones. A message in both\n * shows once, as the live copy, which is the one still changing. An answer\n * resumed after a client action's result shows as one answer: Mastra stores\n * the continuation as its own message.\n */\nexport function mergeThreadMessages<M extends UIMessage>(\n\tstored: readonly M[],\n\tlive: readonly M[]\n): M[] {\n\tconst liveIds = new Set(live.map((message) => message.id))\n\tconst merged = [\n\t\t...stored.filter((message) => !liveIds.has(message.id)),\n\t\t...live\n\t]\n\n\treturn merged.reduce<M[]>((joined, message) => {\n\t\tconst previous = joined.at(-1)\n\t\tconst lastPart = previous?.parts.at(-1)\n\t\tconst resumesPrevious =\n\t\t\tmessage.role === 'assistant' &&\n\t\t\tprevious?.role === 'assistant' &&\n\t\t\tlastPart !== undefined &&\n\t\t\tisToolUIPart(lastPart) &&\n\t\t\t(lastPart.state === 'output-available' ||\n\t\t\t\tlastPart.state === 'output-error')\n\n\t\tif (previous !== undefined && resumesPrevious) {\n\t\t\tjoined[joined.length - 1] = {\n\t\t\t\t...previous,\n\t\t\t\tparts: [...previous.parts, ...message.parts]\n\t\t\t}\n\t\t} else {\n\t\t\tjoined.push(message)\n\t\t}\n\n\t\treturn joined\n\t}, [])\n}\n\n/**\n * Server tool calls a request left at `input-available`: Mastra drops a\n * server result when a client action shares its step. The session answers\n * them with `STRANDED_TOOL_ERROR`, so the follow-up goes on instead of\n * hanging.\n */\nexport function findStrandedToolCalls(\n\tmessage: UIMessage | undefined,\n\tisClientAction: (toolName: string) => boolean\n): { readonly toolCallId: string; readonly toolName: string }[] {\n\tif (message?.role !== 'assistant') {\n\t\treturn []\n\t}\n\n\tconst suspended = new Set(\n\t\tmessage.parts.flatMap((part) => readSuspendedRun(part)?.toolCallId ?? [])\n\t)\n\n\treturn message.parts.flatMap((part) => {\n\t\tif (\n\t\t\t!isToolUIPart(part) ||\n\t\t\tpart.state !== 'input-available' ||\n\t\t\tpart.providerExecuted === true ||\n\t\t\tsuspended.has(part.toolCallId)\n\t\t) {\n\t\t\treturn []\n\t\t}\n\n\t\tconst toolName = getToolName(part)\n\n\t\treturn isClientAction(toolName)\n\t\t\t? []\n\t\t\t: [{ toolCallId: part.toolCallId, toolName }]\n\t})\n}\n\n/**\n * Whether the newest answer's latest step puts something on screen: answer\n * text, a running tool card, or shown reasoning. A step with only finished\n * tools or hidden reasoning is the model working out of sight, and a\n * \"Thinking…\" indicator has to cover that gap.\n */\nexport function isShowingProgress(\n\tmessages: readonly UIMessage[],\n\toptions: { readonly reasoningVisible?: boolean } = {}\n): boolean {\n\tconst newest = messages.at(-1)\n\n\tif (newest?.role !== 'assistant') {\n\t\treturn false\n\t}\n\n\tfor (let index = newest.parts.length - 1; index >= 0; index -= 1) {\n\t\tconst part = newest.parts[index]\n\n\t\tif (part === undefined || part.type === 'step-start') {\n\t\t\treturn false\n\t\t}\n\n\t\tif (isTextUIPart(part) && part.text.length > 0) {\n\t\t\treturn true\n\t\t}\n\n\t\tif (isToolUIPart(part)) {\n\t\t\treturn (\n\t\t\t\tpart.state === 'input-streaming' || part.state === 'input-available'\n\t\t\t)\n\t\t}\n\n\t\tif (\n\t\t\toptions.reasoningVisible &&\n\t\t\tisReasoningUIPart(part) &&\n\t\t\tpart.state === 'streaming'\n\t\t) {\n\t\t\treturn true\n\t\t}\n\t}\n\n\treturn false\n}\n\n/**\n * How many parts of an answer are on screen: answer text that is not blank,\n * and every tool part. Reasoning is not shown, and observational memory's\n * parts never are.\n */\nexport function countShownParts(\n\tmessage: UIMessage | undefined,\n\tisUserFacing: (part: MessagePart) => boolean = isUserFacingPart\n): number {\n\tif (message?.role !== 'assistant') {\n\t\treturn 0\n\t}\n\n\treturn message.parts.filter(\n\t\t(part) =>\n\t\t\tisUserFacing(part) &&\n\t\t\t((isTextUIPart(part) && part.text.trim() !== '') || isToolUIPart(part))\n\t).length\n}\n\n/**\n * The replies a reply-suggestions data part carries, trimmed, without empty,\n * too long or repeated ones, at most three; undefined when the data is not a\n * reply list.\n */\nexport function readReplySuggestions(\n\tdata: unknown\n): readonly string[] | undefined {\n\tconst suggestions = (data as { suggestions?: unknown } | null)?.suggestions\n\n\tif (!Array.isArray(suggestions)) {\n\t\treturn undefined\n\t}\n\n\treturn toReplySuggestions(\n\t\tsuggestions.map((suggestion) =>\n\t\t\ttypeof suggestion === 'string' ? suggestion : undefined\n\t\t)\n\t)\n}\n\n/** The replies worth offering from a model's list, in its order. */\nexport function toReplySuggestions(\n\tsuggestions: readonly (string | undefined)[]\n): readonly string[] {\n\tconst replies: string[] = []\n\n\tfor (const suggestion of suggestions) {\n\t\tconst reply = suggestion?.trim() ?? ''\n\t\tconst isOffered =\n\t\t\treply.length > 0 &&\n\t\t\treply.length <= REPLY_SUGGESTION_MAX_LENGTH &&\n\t\t\t!replies.some((offered) => offered.toLowerCase() === reply.toLowerCase())\n\n\t\tif (isOffered) {\n\t\t\treplies.push(reply)\n\t\t}\n\t}\n\n\treturn replies.slice(0, REPLY_SUGGESTIONS_MAX)\n}\n\nfunction carriesClientResults(\n\tmessage: UIMessage,\n\toptions: RequestMessageOptions\n): boolean {\n\treturn message.parts.some(\n\t\t(part) =>\n\t\t\tisToolUIPart(part) &&\n\t\t\t(part.state === 'approval-responded' ||\n\t\t\t\t((part.state === 'output-available' || part.state === 'output-error') &&\n\t\t\t\t\toptions.isClientResult?.(part) === true))\n\t)\n}\n\nfunction lastUserMessage(messages: readonly UIMessage[]): UIMessage[] {\n\tconst message = messages[lastIndexOfUser(messages)]\n\n\treturn message === undefined ? [] : [message]\n}\n\n/** `findLastIndex`, which the ES2022 build target lacks. */\nfunction lastIndexOfUser(messages: readonly UIMessage[]): number {\n\tfor (let index = messages.length - 1; index >= 0; index -= 1) {\n\t\tif (messages[index]?.role === 'user') {\n\t\t\treturn index\n\t\t}\n\t}\n\n\treturn -1\n}\n"]}
|
|
1
|
+
{"version":3,"file":"messages.js","sourceRoot":"","sources":["../../src/client/messages.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Message helpers every client shares: which messages one request sends,\n * which parts reach the user, the stored thread merged with the live one,\n * tool calls a request left without a result, and the reply suggestions an\n * answer offers.\n */\n\nimport {\n\tgetToolName,\n\tisReasoningUIPart,\n\tisTextUIPart,\n\tisToolUIPart,\n\ttype UIMessage\n} from 'ai'\n\nimport { readSuspendedRun } from './pending.js'\n\n// TYPES\n\ntype MessagePart = UIMessage['parts'][number]\n\n/** A tool call in a message, from a typed tool or a dynamic one. */\nexport type ToolPart = Extract<MessagePart, { readonly toolCallId: string }>\n\n/** One block a message draws, in the order of its parts. */\nexport type MessageBlock =\n\t| {\n\t\t\treadonly kind: 'text'\n\t\t\treadonly key: string\n\t\t\treadonly text: string\n\t\t\t/** The AI SDK is still writing this text. */\n\t\t\treadonly streaming: boolean\n\t }\n\t/** Neighbouring tool parts: the cards of one step sit in one place between the texts. */\n\t| {\n\t\t\treadonly kind: 'tools'\n\t\t\treadonly key: string\n\t\t\treadonly parts: readonly ToolPart[]\n\t }\n\nexport interface RequestMessageOptions {\n\t/** Whether a tool part holds a result the client produced: a client action's result, or an error it posted. */\n\treadonly isClientResult?: (part: ToolPart) => boolean\n}\n\n// CONSTANTS\n\nconst OBSERVATIONAL_MEMORY_PART_PREFIX = 'data-om-'\n\n/** The transient data part a server streams after an answer: up to three replies the user can tap to send. */\nexport const REPLY_SUGGESTIONS_PART = 'data-reply-suggestions'\n\nexport const REPLY_SUGGESTIONS_MAX = 3\n\n/** The transient data part a turn streams right after its start: the user's message, for a client that reattaches before the thread stores it. */\nexport const USER_MESSAGE_PART = 'data-user-message'\n\n/** The tripwire part a run that a processor stopped ends with; its `reason` says why. */\nconst TRIPWIRE_PART = 'data-tripwire'\n\n/** The longest reply offered, in characters; a longer one is dropped. */\nexport const REPLY_SUGGESTION_MAX_LENGTH = 80\n\n/**\n * What the model reads for a server tool call a request left without a\n * result: Mastra drops a server result when a client action shares its step.\n */\nexport const STRANDED_TOOL_ERROR =\n\t'This tool call was interrupted because it ran in the same step as a client action. Call the tool again on its own to get the result.'\n\n// FUNCTIONS\n\n/** Whether a message part may reach the user: observational memory's `data-om-*` bookkeeping parts may not. */\nexport function isUserFacingPart(part: { readonly type: string }): boolean {\n\treturn !part.type.startsWith(OBSERVATIONAL_MEMORY_PART_PREFIX)\n}\n\n/** The message with only its user-facing parts. */\nexport function toUserFacingMessage<M extends UIMessage>(message: M): M {\n\treturn { ...message, parts: message.parts.filter(isUserFacingPart) }\n}\n\n/**\n * The blocks one message draws: its non-blank texts and its runs of tool\n * parts. Every other part, and every part observational memory adds, draws\n * nothing here; a client draws the data parts it knows itself.\n */\nexport function toMessageBlocks(message: UIMessage): MessageBlock[] {\n\tconst blocks: MessageBlock[] = []\n\n\tfor (const [index, part] of message.parts.entries()) {\n\t\tconst key = `${message.id}:${index}`\n\t\tconst previous = blocks.at(-1)\n\n\t\tif (!isUserFacingPart(part)) {\n\t\t\tcontinue\n\t\t}\n\n\t\tif (isTextUIPart(part) && part.text.trim() !== '') {\n\t\t\tblocks.push({\n\t\t\t\tkind: 'text',\n\t\t\t\tkey,\n\t\t\t\ttext: part.text,\n\t\t\t\tstreaming: part.state === 'streaming'\n\t\t\t})\n\t\t} else if (isToolUIPart(part)) {\n\t\t\tif (previous?.kind === 'tools') {\n\t\t\t\tblocks[blocks.length - 1] = {\n\t\t\t\t\t...previous,\n\t\t\t\t\tparts: [...previous.parts, part]\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tblocks.push({ kind: 'tools', key, parts: [part] })\n\t\t\t}\n\t\t}\n\t}\n\n\treturn blocks\n}\n\n/**\n * The messages one request sends. Observational memory supplies the thread's\n * history, so a new user turn sends only the user's new message. When the\n * trailing assistant message carries results the client produced (client\n * action results, approval answers), the messages of the current user turn,\n * ending with that message. To resume a suspended run, the trailing assistant\n * message alone: Mastra resumes the run from its snapshot.\n */\nexport function selectRequestMessages(\n\tmessages: readonly UIMessage[],\n\toptions: RequestMessageOptions = {}\n): UIMessage[] {\n\tconst lastMessage = messages.at(-1)\n\n\tif (lastMessage === undefined) {\n\t\treturn []\n\t}\n\n\tswitch (lastMessage.role) {\n\t\tcase 'user':\n\t\t\treturn [lastMessage]\n\t\tcase 'assistant':\n\t\t\tif (\n\t\t\t\tlastMessage.parts.some((part) => readSuspendedRun(part) !== undefined)\n\t\t\t) {\n\t\t\t\treturn [lastMessage]\n\t\t\t}\n\n\t\t\treturn carriesClientResults(lastMessage, options)\n\t\t\t\t? currentUserTurn(messages)\n\t\t\t\t: lastUserMessage(messages)\n\t\tcase 'system':\n\t\t\treturn lastUserMessage(messages)\n\t}\n}\n\n/** From the last user message to the end; the trailing message alone when there is no user message. */\nexport function currentUserTurn(messages: readonly UIMessage[]): UIMessage[] {\n\tconst lastUserIndex = lastIndexOfUser(messages)\n\n\tif (lastUserIndex !== -1) {\n\t\treturn messages.slice(lastUserIndex)\n\t}\n\n\tconst trailing = messages.at(-1)\n\n\treturn trailing === undefined ? [] : [trailing]\n}\n\n/** Which parts an answer resumes after: Mastra stores the continuation as a message of its own. */\nexport type ResumePointOptions = {\n\t/** Default `isToolResultPart`: after any tool's result. */\n\treadonly isResumePoint?: (part: MessagePart) => boolean\n}\n\n/** A tool call with its output or error, the default resume point. */\nexport function isToolResultPart(part: MessagePart): boolean {\n\treturn (\n\t\tisToolUIPart(part) &&\n\t\t(part.state === 'output-available' || part.state === 'output-error')\n\t)\n}\n\n/**\n * Joins an assistant message into the assistant message before it when that\n * one ends at a resume point. Mastra stores the text a run wrote after\n * resuming from a client tool's result as a new message, while the client\n * streamed it into the message that holds the call; joining keeps that\n * message and its id.\n */\nexport function joinResumedAnswers<M extends UIMessage>(\n\tmessages: readonly M[],\n\toptions: ResumePointOptions = {}\n): M[] {\n\tconst isResumePoint = options.isResumePoint ?? isToolResultPart\n\n\treturn messages.reduce<M[]>((joined, message) => {\n\t\tconst previous = joined.at(-1)\n\t\tconst lastPart = previous?.parts.at(-1)\n\t\tconst resumesPrevious =\n\t\t\tmessage.role === 'assistant' &&\n\t\t\tprevious?.role === 'assistant' &&\n\t\t\tlastPart !== undefined &&\n\t\t\tisResumePoint(lastPart)\n\n\t\tif (previous !== undefined && resumesPrevious) {\n\t\t\tjoined[joined.length - 1] = {\n\t\t\t\t...previous,\n\t\t\t\tparts: [...previous.parts, ...message.parts]\n\t\t\t}\n\t\t} else {\n\t\t\tjoined.push(message)\n\t\t}\n\n\t\treturn joined\n\t}, [])\n}\n\n/**\n * The stored messages, oldest first, then the live ones. A message in both\n * shows once, as the live copy, which is the one still changing. An answer\n * resumed after a client action's result shows as one answer (see\n * `joinResumedAnswers`).\n */\nexport function mergeThreadMessages<M extends UIMessage>(\n\tstored: readonly M[],\n\tlive: readonly M[],\n\toptions: ResumePointOptions = {}\n): M[] {\n\tconst liveIds = new Set(live.map((message) => message.id))\n\n\treturn joinResumedAnswers(\n\t\t[...stored.filter((message) => !liveIds.has(message.id)), ...live],\n\t\toptions\n\t)\n}\n\n/**\n * Server tool calls a request left at `input-available`: Mastra drops a\n * server result when a client action shares its step. The session answers\n * them with `STRANDED_TOOL_ERROR`, so the follow-up goes on instead of\n * hanging.\n */\nexport function findStrandedToolCalls(\n\tmessage: UIMessage | undefined,\n\tisClientAction: (toolName: string) => boolean\n): { readonly toolCallId: string; readonly toolName: string }[] {\n\tif (message?.role !== 'assistant') {\n\t\treturn []\n\t}\n\n\tconst suspended = new Set(\n\t\tmessage.parts.flatMap((part) => readSuspendedRun(part)?.toolCallId ?? [])\n\t)\n\n\treturn message.parts.flatMap((part) => {\n\t\tif (\n\t\t\t!isToolUIPart(part) ||\n\t\t\tpart.state !== 'input-available' ||\n\t\t\tpart.providerExecuted === true ||\n\t\t\tsuspended.has(part.toolCallId)\n\t\t) {\n\t\t\treturn []\n\t\t}\n\n\t\tconst toolName = getToolName(part)\n\n\t\treturn isClientAction(toolName)\n\t\t\t? []\n\t\t\t: [{ toolCallId: part.toolCallId, toolName }]\n\t})\n}\n\n/**\n * Whether the newest answer's latest step puts something on screen: answer\n * text, a running tool card, or shown reasoning. A step with only finished\n * tools or hidden reasoning is the model working out of sight, and a\n * \"Thinking…\" indicator has to cover that gap.\n */\nexport function isShowingProgress(\n\tmessages: readonly UIMessage[],\n\toptions: { readonly reasoningVisible?: boolean } = {}\n): boolean {\n\tconst newest = messages.at(-1)\n\n\tif (newest?.role !== 'assistant') {\n\t\treturn false\n\t}\n\n\tfor (let index = newest.parts.length - 1; index >= 0; index -= 1) {\n\t\tconst part = newest.parts[index]\n\n\t\tif (part === undefined || part.type === 'step-start') {\n\t\t\treturn false\n\t\t}\n\n\t\tif (isTextUIPart(part) && part.text.length > 0) {\n\t\t\treturn true\n\t\t}\n\n\t\tif (isToolUIPart(part)) {\n\t\t\treturn (\n\t\t\t\tpart.state === 'input-streaming' || part.state === 'input-available'\n\t\t\t)\n\t\t}\n\n\t\tif (\n\t\t\toptions.reasoningVisible &&\n\t\t\tisReasoningUIPart(part) &&\n\t\t\tpart.state === 'streaming'\n\t\t) {\n\t\t\treturn true\n\t\t}\n\t}\n\n\treturn false\n}\n\n/**\n * How many parts of an answer are on screen: answer text that is not blank,\n * and every tool part. Reasoning is not shown, and observational memory's\n * parts never are.\n */\nexport function countShownParts(\n\tmessage: UIMessage | undefined,\n\tisUserFacing: (part: MessagePart) => boolean = isUserFacingPart\n): number {\n\tif (message?.role !== 'assistant') {\n\t\treturn 0\n\t}\n\n\treturn message.parts.filter(\n\t\t(part) =>\n\t\t\tisUserFacing(part) &&\n\t\t\t((isTextUIPart(part) && part.text.trim() !== '') || isToolUIPart(part))\n\t).length\n}\n\n/**\n * The replies a reply-suggestions data part carries, trimmed, without empty,\n * too long or repeated ones, at most three; undefined when the data is not a\n * reply list.\n */\n/** The user message a `data-user-message` part carries, or undefined for any other data. */\nexport function readUserMessage(data: unknown): UIMessage | undefined {\n\tconst message = (data as { message?: unknown } | null)?.message as\n\t\t| Partial<UIMessage>\n\t\t| undefined\n\n\treturn typeof message?.id === 'string' &&\n\t\tmessage.role === 'user' &&\n\t\tArray.isArray(message.parts)\n\t\t? (message as UIMessage)\n\t\t: undefined\n}\n\n/**\n * Why the server stopped the answer early, such as `tool_call_budget`\n * (`TOOL_CALL_BUDGET_STOP_REASON`): the `reason` of its tripwire part.\n * Undefined for an answer that ended on its own.\n */\nexport function readStopReason(\n\tmessage: UIMessage | undefined\n): string | undefined {\n\tif (message?.role !== 'assistant') return undefined\n\n\tfor (const part of message.parts) {\n\t\tconst reason =\n\t\t\tpart.type === TRIPWIRE_PART\n\t\t\t\t? (part as { data?: { reason?: unknown } }).data?.reason\n\t\t\t\t: undefined\n\n\t\tif (typeof reason === 'string') return reason\n\t}\n\n\treturn undefined\n}\n\nexport function readReplySuggestions(\n\tdata: unknown\n): readonly string[] | undefined {\n\tconst suggestions = (data as { suggestions?: unknown } | null)?.suggestions\n\n\tif (!Array.isArray(suggestions)) {\n\t\treturn undefined\n\t}\n\n\treturn toReplySuggestions(\n\t\tsuggestions.map((suggestion) =>\n\t\t\ttypeof suggestion === 'string' ? suggestion : undefined\n\t\t)\n\t)\n}\n\n/** The replies worth offering from a model's list, in its order. */\nexport function toReplySuggestions(\n\tsuggestions: readonly (string | undefined)[]\n): readonly string[] {\n\tconst replies: string[] = []\n\n\tfor (const suggestion of suggestions) {\n\t\tconst reply = suggestion?.trim() ?? ''\n\t\tconst isOffered =\n\t\t\treply.length > 0 &&\n\t\t\treply.length <= REPLY_SUGGESTION_MAX_LENGTH &&\n\t\t\t!replies.some((offered) => offered.toLowerCase() === reply.toLowerCase())\n\n\t\tif (isOffered) {\n\t\t\treplies.push(reply)\n\t\t}\n\t}\n\n\treturn replies.slice(0, REPLY_SUGGESTIONS_MAX)\n}\n\nfunction carriesClientResults(\n\tmessage: UIMessage,\n\toptions: RequestMessageOptions\n): boolean {\n\treturn message.parts.some(\n\t\t(part) =>\n\t\t\tisToolUIPart(part) &&\n\t\t\t(part.state === 'approval-responded' ||\n\t\t\t\t((part.state === 'output-available' || part.state === 'output-error') &&\n\t\t\t\t\toptions.isClientResult?.(part) === true))\n\t)\n}\n\nfunction lastUserMessage(messages: readonly UIMessage[]): UIMessage[] {\n\tconst message = messages[lastIndexOfUser(messages)]\n\n\treturn message === undefined ? [] : [message]\n}\n\n/** `findLastIndex`, which the ES2022 build target lacks. */\nfunction lastIndexOfUser(messages: readonly UIMessage[]): number {\n\tfor (let index = messages.length - 1; index >= 0; index -= 1) {\n\t\tif (messages[index]?.role === 'user') {\n\t\t\treturn index\n\t\t}\n\t}\n\n\treturn -1\n}\n"]}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The moments an app may have missed part of a running answer, as resume
|
|
3
|
+
* signals for `createChatSession({ resumeOn })`. The React Native ones take
|
|
4
|
+
* `AppState` and NetInfo as arguments, so the client imports neither.
|
|
5
|
+
*/
|
|
6
|
+
import type { ResumeSignal } from './session.js';
|
|
7
|
+
/** The subset of React Native's `AppState` the signal listens to. */
|
|
8
|
+
export interface AppStateLike {
|
|
9
|
+
addEventListener(type: 'change', listener: (state: string) => void): {
|
|
10
|
+
remove(): void;
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
/** The subset of `@react-native-community/netinfo` the signal listens to. */
|
|
14
|
+
export interface NetInfoLike {
|
|
15
|
+
addEventListener(listener: (state: {
|
|
16
|
+
readonly isConnected: boolean | null;
|
|
17
|
+
}) => void): () => void;
|
|
18
|
+
}
|
|
19
|
+
/** The browser tab came back into view. */
|
|
20
|
+
export declare const resumeOnVisible: ResumeSignal;
|
|
21
|
+
/** The browser is online again. */
|
|
22
|
+
export declare const resumeOnOnline: ResumeSignal;
|
|
23
|
+
/** The app came back to the foreground. */
|
|
24
|
+
export declare function resumeOnAppActive(appState: AppStateLike): ResumeSignal;
|
|
25
|
+
/** The device has a connection again after losing it; NetInfo's first report is not a regain. */
|
|
26
|
+
export declare function resumeOnNetworkRegain(netInfo: NetInfoLike): ResumeSignal;
|
|
27
|
+
//# sourceMappingURL=resume.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resume.d.ts","sourceRoot":"","sources":["../../src/client/resume.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA;AAEhD,qEAAqE;AACrE,MAAM,WAAW,YAAY;IAC5B,gBAAgB,CACf,IAAI,EAAE,QAAQ,EACd,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,GAC/B;QAAE,MAAM,IAAI,IAAI,CAAA;KAAE,CAAA;CACrB;AAED,6EAA6E;AAC7E,MAAM,WAAW,WAAW;IAC3B,gBAAgB,CACf,QAAQ,EAAE,CAAC,KAAK,EAAE;QAAE,QAAQ,CAAC,WAAW,EAAE,OAAO,GAAG,IAAI,CAAA;KAAE,KAAK,IAAI,GACjE,MAAM,IAAI,CAAA;CACb;AAeD,2CAA2C;AAC3C,eAAO,MAAM,eAAe,EAAE,YAS7B,CAAA;AAED,mCAAmC;AACnC,eAAO,MAAM,cAAc,EAAE,YAM5B,CAAA;AAED,2CAA2C;AAC3C,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,YAAY,GAAG,YAAY,CAQtE;AAED,iGAAiG;AACjG,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,WAAW,GAAG,YAAY,CAYxE"}
|