better-mastra 0.0.0 → 0.1.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/dist/client/actions.d.ts +85 -0
- package/dist/client/actions.d.ts.map +1 -0
- package/dist/client/actions.js +184 -0
- package/dist/client/actions.js.map +1 -0
- package/dist/client/cards.d.ts +66 -0
- package/dist/client/cards.d.ts.map +1 -0
- package/dist/client/cards.js +63 -0
- package/dist/client/cards.js.map +1 -0
- package/dist/client/chat.d.ts +25 -0
- package/dist/client/chat.d.ts.map +1 -0
- package/dist/client/chat.js +130 -0
- package/dist/client/chat.js.map +1 -0
- package/dist/client/errors.d.ts +21 -0
- package/dist/client/errors.d.ts.map +1 -0
- package/dist/client/errors.js +57 -0
- package/dist/client/errors.js.map +1 -0
- package/dist/client/fetch.d.ts +54 -0
- package/dist/client/fetch.d.ts.map +1 -0
- package/dist/client/fetch.js +197 -0
- package/dist/client/fetch.js.map +1 -0
- package/dist/client/index.d.ts +13 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +13 -0
- package/dist/client/index.js.map +1 -0
- package/dist/client/messages.d.ts +105 -0
- package/dist/client/messages.d.ts.map +1 -0
- package/dist/client/messages.js +245 -0
- package/dist/client/messages.js.map +1 -0
- package/dist/client/pending.d.ts +59 -0
- package/dist/client/pending.d.ts.map +1 -0
- package/dist/client/pending.js +164 -0
- package/dist/client/pending.js.map +1 -0
- package/dist/client/ratings.d.ts +23 -0
- package/dist/client/ratings.d.ts.map +1 -0
- package/dist/client/ratings.js +56 -0
- package/dist/client/ratings.js.map +1 -0
- package/dist/client/reveal.d.ts +57 -0
- package/dist/client/reveal.d.ts.map +1 -0
- package/dist/client/reveal.js +127 -0
- package/dist/client/reveal.js.map +1 -0
- package/dist/client/screen-context.d.ts +39 -0
- package/dist/client/screen-context.d.ts.map +1 -0
- package/dist/client/screen-context.js +60 -0
- package/dist/client/screen-context.js.map +1 -0
- package/dist/client/session.d.ts +152 -0
- package/dist/client/session.d.ts.map +1 -0
- package/dist/client/session.js +611 -0
- package/dist/client/session.js.map +1 -0
- package/dist/client/turn.d.ts +188 -0
- package/dist/client/turn.d.ts.map +1 -0
- package/dist/client/turn.js +517 -0
- package/dist/client/turn.js.map +1 -0
- package/dist/dictation/adapters.d.ts +83 -0
- package/dist/dictation/adapters.d.ts.map +1 -0
- package/dist/dictation/adapters.js +198 -0
- package/dist/dictation/adapters.js.map +1 -0
- package/dist/dictation/index.d.ts +4 -0
- package/dist/dictation/index.d.ts.map +1 -0
- package/dist/dictation/index.js +4 -0
- package/dist/dictation/index.js.map +1 -0
- package/dist/dictation/protocol.d.ts +48 -0
- package/dist/dictation/protocol.d.ts.map +1 -0
- package/dist/dictation/protocol.js +98 -0
- package/dist/dictation/protocol.js.map +1 -0
- package/dist/dictation/react/index.d.ts +30 -0
- package/dist/dictation/react/index.d.ts.map +1 -0
- package/dist/dictation/react/index.js +93 -0
- package/dist/dictation/react/index.js.map +1 -0
- package/dist/dictation/server/index.d.ts +51 -0
- package/dist/dictation/server/index.d.ts.map +1 -0
- package/dist/dictation/server/index.js +76 -0
- package/dist/dictation/server/index.js.map +1 -0
- package/dist/dictation/session.d.ts +90 -0
- package/dist/dictation/session.d.ts.map +1 -0
- package/dist/dictation/session.js +202 -0
- package/dist/dictation/session.js.map +1 -0
- package/dist/dictation/vue/index.d.ts +28 -0
- package/dist/dictation/vue/index.d.ts.map +1 -0
- package/dist/dictation/vue/index.js +79 -0
- package/dist/dictation/vue/index.js.map +1 -0
- package/dist/hono/chatRoute.d.ts +28 -0
- package/dist/hono/chatRoute.d.ts.map +1 -0
- package/dist/hono/chatRoute.js +88 -0
- package/dist/hono/chatRoute.js.map +1 -0
- package/dist/hono/index.d.ts +4 -0
- package/dist/hono/index.d.ts.map +1 -0
- package/dist/hono/index.js +3 -0
- package/dist/hono/index.js.map +1 -0
- package/dist/hono/studio.d.ts +40 -0
- package/dist/hono/studio.d.ts.map +1 -0
- package/dist/hono/studio.js +104 -0
- package/dist/hono/studio.js.map +1 -0
- package/dist/hono/types.d.ts +17 -0
- package/dist/hono/types.d.ts.map +1 -0
- package/dist/hono/types.js +2 -0
- package/dist/hono/types.js.map +1 -0
- package/dist/react/index.d.ts +60 -0
- package/dist/react/index.d.ts.map +1 -0
- package/dist/react/index.js +107 -0
- package/dist/react/index.js.map +1 -0
- package/dist/react/markdown/index.d.ts +26 -0
- package/dist/react/markdown/index.d.ts.map +1 -0
- package/dist/react/markdown/index.js +162 -0
- package/dist/react/markdown/index.js.map +1 -0
- package/dist/resumable/index.d.ts +42 -0
- package/dist/resumable/index.d.ts.map +1 -0
- package/dist/resumable/index.js +333 -0
- package/dist/resumable/index.js.map +1 -0
- package/dist/resumable/redis.d.ts +44 -0
- package/dist/resumable/redis.d.ts.map +1 -0
- package/dist/resumable/redis.js +65 -0
- package/dist/resumable/redis.js.map +1 -0
- package/dist/tools/cli.d.ts +8 -0
- package/dist/tools/cli.d.ts.map +1 -0
- package/dist/tools/cli.js +24 -0
- package/dist/tools/cli.js.map +1 -0
- package/dist/tools/clientActions.d.ts +18 -0
- package/dist/tools/clientActions.d.ts.map +1 -0
- package/dist/tools/clientActions.js +35 -0
- package/dist/tools/clientActions.js.map +1 -0
- package/dist/tools/deferredTools.d.ts +39 -0
- package/dist/tools/deferredTools.d.ts.map +1 -0
- package/dist/tools/deferredTools.js +105 -0
- package/dist/tools/deferredTools.js.map +1 -0
- package/dist/tools/defineTool.d.ts +62 -0
- package/dist/tools/defineTool.d.ts.map +1 -0
- package/dist/tools/defineTool.js +92 -0
- package/dist/tools/defineTool.js.map +1 -0
- package/dist/tools/dryRun.d.ts +19 -0
- package/dist/tools/dryRun.d.ts.map +1 -0
- package/dist/tools/dryRun.js +25 -0
- package/dist/tools/dryRun.js.map +1 -0
- package/dist/tools/index.d.ts +8 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +8 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/manifest.d.ts +21 -0
- package/dist/tools/manifest.d.ts.map +1 -0
- package/dist/tools/manifest.js +32 -0
- package/dist/tools/manifest.js.map +1 -0
- package/dist/tools/projection.d.ts +17 -0
- package/dist/tools/projection.d.ts.map +1 -0
- package/dist/tools/projection.js +13 -0
- package/dist/tools/projection.js.map +1 -0
- package/dist/tools/toolInputSchema.d.ts +35 -0
- package/dist/tools/toolInputSchema.d.ts.map +1 -0
- package/dist/tools/toolInputSchema.js +205 -0
- package/dist/tools/toolInputSchema.js.map +1 -0
- package/dist/turn/agent.d.ts +39 -0
- package/dist/turn/agent.d.ts.map +1 -0
- package/dist/turn/agent.js +125 -0
- package/dist/turn/agent.js.map +1 -0
- package/dist/turn/changes.d.ts +85 -0
- package/dist/turn/changes.d.ts.map +1 -0
- package/dist/turn/changes.js +118 -0
- package/dist/turn/changes.js.map +1 -0
- package/dist/turn/errors.d.ts +9 -0
- package/dist/turn/errors.d.ts.map +1 -0
- package/dist/turn/errors.js +24 -0
- package/dist/turn/errors.js.map +1 -0
- package/dist/turn/index.d.ts +18 -0
- package/dist/turn/index.d.ts.map +1 -0
- package/dist/turn/index.js +18 -0
- package/dist/turn/index.js.map +1 -0
- package/dist/turn/modelArms.d.ts +30 -0
- package/dist/turn/modelArms.d.ts.map +1 -0
- package/dist/turn/modelArms.js +34 -0
- package/dist/turn/modelArms.js.map +1 -0
- package/dist/turn/observer.d.ts +44 -0
- package/dist/turn/observer.d.ts.map +1 -0
- package/dist/turn/observer.js +108 -0
- package/dist/turn/observer.js.map +1 -0
- package/dist/turn/processors.d.ts +46 -0
- package/dist/turn/processors.d.ts.map +1 -0
- package/dist/turn/processors.js +105 -0
- package/dist/turn/processors.js.map +1 -0
- package/dist/turn/promptBudget.d.ts +21 -0
- package/dist/turn/promptBudget.d.ts.map +1 -0
- package/dist/turn/promptBudget.js +34 -0
- package/dist/turn/promptBudget.js.map +1 -0
- package/dist/turn/promptCache.d.ts +43 -0
- package/dist/turn/promptCache.d.ts.map +1 -0
- package/dist/turn/promptCache.js +77 -0
- package/dist/turn/promptCache.js.map +1 -0
- package/dist/turn/promptVersion.d.ts +21 -0
- package/dist/turn/promptVersion.d.ts.map +1 -0
- package/dist/turn/promptVersion.js +21 -0
- package/dist/turn/promptVersion.js.map +1 -0
- package/dist/turn/replySuggestions.d.ts +50 -0
- package/dist/turn/replySuggestions.d.ts.map +1 -0
- package/dist/turn/replySuggestions.js +95 -0
- package/dist/turn/replySuggestions.js.map +1 -0
- package/dist/turn/requestContext.d.ts +31 -0
- package/dist/turn/requestContext.d.ts.map +1 -0
- package/dist/turn/requestContext.js +57 -0
- package/dist/turn/requestContext.js.map +1 -0
- package/dist/turn/response.d.ts +9 -0
- package/dist/turn/response.d.ts.map +1 -0
- package/dist/turn/response.js +15 -0
- package/dist/turn/response.js.map +1 -0
- package/dist/turn/runChatTurn.d.ts +154 -0
- package/dist/turn/runChatTurn.d.ts.map +1 -0
- package/dist/turn/runChatTurn.js +322 -0
- package/dist/turn/runChatTurn.js.map +1 -0
- package/dist/turn/stream.d.ts +34 -0
- package/dist/turn/stream.d.ts.map +1 -0
- package/dist/turn/stream.js +91 -0
- package/dist/turn/stream.js.map +1 -0
- package/dist/turn/threads.d.ts +122 -0
- package/dist/turn/threads.d.ts.map +1 -0
- package/dist/turn/threads.js +347 -0
- package/dist/turn/threads.js.map +1 -0
- package/dist/turn/title.d.ts +18 -0
- package/dist/turn/title.d.ts.map +1 -0
- package/dist/turn/title.js +42 -0
- package/dist/turn/title.js.map +1 -0
- package/dist/turn/types.d.ts +65 -0
- package/dist/turn/types.d.ts.map +1 -0
- package/dist/turn/types.js +23 -0
- package/dist/turn/types.js.map +1 -0
- package/dist/vue/index.d.ts +88 -0
- package/dist/vue/index.d.ts.map +1 -0
- package/dist/vue/index.js +146 -0
- package/dist/vue/index.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client actions: tools the app carries out itself (open a screen, fill a
|
|
3
|
+
* form), whose definitions go along with each request and whose results go
|
|
4
|
+
* back as the turn's follow-up. A call reaches the app as the model wrote
|
|
5
|
+
* it, without the server's input validation, so the app parses it here.
|
|
6
|
+
*/
|
|
7
|
+
import type { UIMessage } from 'ai';
|
|
8
|
+
import { z } from 'zod';
|
|
9
|
+
/** A tool that runs in the client. The server never interprets its name. */
|
|
10
|
+
export interface ClientAction<TName extends string = string> {
|
|
11
|
+
readonly name: TName;
|
|
12
|
+
readonly description: string;
|
|
13
|
+
readonly inputSchema: z.ZodType;
|
|
14
|
+
readonly requiresApproval?: boolean;
|
|
15
|
+
/** 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>;
|
|
17
|
+
}
|
|
18
|
+
/** A client action as a request carries it, for the server to offer the model. */
|
|
19
|
+
export interface ClientToolDef {
|
|
20
|
+
readonly name: string;
|
|
21
|
+
readonly description: string;
|
|
22
|
+
readonly jsonSchema: unknown;
|
|
23
|
+
readonly requiresApproval?: boolean;
|
|
24
|
+
}
|
|
25
|
+
/** A tool call the stream handed to the client. */
|
|
26
|
+
export interface ClientToolCall {
|
|
27
|
+
readonly toolCallId: string;
|
|
28
|
+
readonly toolName: string;
|
|
29
|
+
readonly input: unknown;
|
|
30
|
+
}
|
|
31
|
+
/** The part of the AI SDK `Chat` a client action needs. */
|
|
32
|
+
export type ClientActionChat = {
|
|
33
|
+
readonly messages: UIMessage[];
|
|
34
|
+
readonly addToolOutput: (options: {
|
|
35
|
+
tool: string;
|
|
36
|
+
toolCallId: string;
|
|
37
|
+
output: unknown;
|
|
38
|
+
} | {
|
|
39
|
+
state: 'output-error';
|
|
40
|
+
tool: string;
|
|
41
|
+
toolCallId: string;
|
|
42
|
+
errorText: string;
|
|
43
|
+
}) => void | PromiseLike<void>;
|
|
44
|
+
};
|
|
45
|
+
/** What the model reads when an action throws something that is not an Error. */
|
|
46
|
+
export declare const CLIENT_ACTION_FAILED = "The action failed.";
|
|
47
|
+
/** What the model reads when the app cannot read the call's input. */
|
|
48
|
+
export declare const CLIENT_ACTION_INVALID_INPUT = "The app could not read this input. Call the action again with input that fits its schema.";
|
|
49
|
+
/** What the model reads for a call it already made with the same input in this turn. */
|
|
50
|
+
export declare const CLIENT_ACTION_REPEATED = "Already done with these values in this turn. Do not call it again; tell the user what changed.";
|
|
51
|
+
/**
|
|
52
|
+
* A client action whose handler gets its input parsed, with the model's
|
|
53
|
+
* `null`s taken out where the schema means "may be left out". With
|
|
54
|
+
* `resultSummary`, the output is `{ summary, data }`, for a result the data
|
|
55
|
+
* alone would leave the model guessing about.
|
|
56
|
+
*/
|
|
57
|
+
export declare function defineClientAction<const TName extends string, TSchema extends z.ZodType, TOutput>(action: {
|
|
58
|
+
readonly name: TName;
|
|
59
|
+
readonly description: string;
|
|
60
|
+
readonly inputSchema: TSchema;
|
|
61
|
+
readonly requiresApproval?: boolean;
|
|
62
|
+
readonly resultSummary?: string;
|
|
63
|
+
readonly handler: (input: z.output<TSchema>) => Promise<TOutput>;
|
|
64
|
+
}): ClientAction<TName>;
|
|
65
|
+
/**
|
|
66
|
+
* Model arguments with `null` taken out wherever the schema means "may be
|
|
67
|
+
* left out", at any depth. An OpenAI model behind Mastra's schema layer
|
|
68
|
+
* fills every field it leaves alone with `null`; a `null` stays only where
|
|
69
|
+
* the schema itself accepts it. Unions try each member and keep the first
|
|
70
|
+
* reading that parses.
|
|
71
|
+
*/
|
|
72
|
+
export declare function normalizeModelNulls(schema: z.ZodType, value: unknown): unknown;
|
|
73
|
+
/** The definitions a request carries, for the server to offer the model as client tools. */
|
|
74
|
+
export declare function toClientToolDefs(actions: readonly ClientAction[]): ClientToolDef[];
|
|
75
|
+
/**
|
|
76
|
+
* Carries out the client action a tool call names and posts its result on the
|
|
77
|
+
* chat; null when no action has that name (a server tool: its result arrives
|
|
78
|
+
* 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.
|
|
80
|
+
*
|
|
81
|
+
* Never await it inside the chat's `onToolCall`: the AI SDK runs that callback
|
|
82
|
+
* on the queue `addToolOutput` waits on, so awaiting deadlocks the stream.
|
|
83
|
+
*/
|
|
84
|
+
export declare function runClientAction(chat: ClientActionChat, actions: readonly ClientAction[], toolCall: ClientToolCall): Promise<void> | null;
|
|
85
|
+
//# sourceMappingURL=actions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"actions.d.ts","sourceRoot":"","sources":["../../src/client/actions.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client actions: tools the app carries out itself (open a screen, fill a
|
|
3
|
+
* form), whose definitions go along with each request and whose results go
|
|
4
|
+
* back as the turn's follow-up. A call reaches the app as the model wrote
|
|
5
|
+
* it, without the server's input validation, so the app parses it here.
|
|
6
|
+
*/
|
|
7
|
+
import { getToolName, isToolUIPart } from 'ai';
|
|
8
|
+
import { z } from 'zod';
|
|
9
|
+
import { currentUserTurn } from './messages.js';
|
|
10
|
+
// CONSTANTS
|
|
11
|
+
/** What the model reads when an action throws something that is not an Error. */
|
|
12
|
+
export const CLIENT_ACTION_FAILED = 'The action failed.';
|
|
13
|
+
/** What the model reads when the app cannot read the call's input. */
|
|
14
|
+
export const CLIENT_ACTION_INVALID_INPUT = 'The app could not read this input. Call the action again with input that fits its schema.';
|
|
15
|
+
/** What the model reads for a call it already made with the same input in this turn. */
|
|
16
|
+
export const CLIENT_ACTION_REPEATED = 'Already done with these values in this turn. Do not call it again; tell the user what changed.';
|
|
17
|
+
// FUNCTIONS
|
|
18
|
+
/**
|
|
19
|
+
* A client action whose handler gets its input parsed, with the model's
|
|
20
|
+
* `null`s taken out where the schema means "may be left out". With
|
|
21
|
+
* `resultSummary`, the output is `{ summary, data }`, for a result the data
|
|
22
|
+
* alone would leave the model guessing about.
|
|
23
|
+
*/
|
|
24
|
+
export function defineClientAction(action) {
|
|
25
|
+
return {
|
|
26
|
+
name: action.name,
|
|
27
|
+
description: action.description,
|
|
28
|
+
inputSchema: action.inputSchema,
|
|
29
|
+
...(action.requiresApproval === undefined
|
|
30
|
+
? {}
|
|
31
|
+
: { requiresApproval: action.requiresApproval }),
|
|
32
|
+
run: async (input) => {
|
|
33
|
+
const parsed = action.inputSchema.safeParse(normalizeModelNulls(action.inputSchema, input ?? {}));
|
|
34
|
+
if (!parsed.success) {
|
|
35
|
+
throw new Error(CLIENT_ACTION_INVALID_INPUT);
|
|
36
|
+
}
|
|
37
|
+
const data = await action.handler(parsed.data);
|
|
38
|
+
return action.resultSummary === undefined
|
|
39
|
+
? data
|
|
40
|
+
: { summary: action.resultSummary, data };
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Model arguments with `null` taken out wherever the schema means "may be
|
|
46
|
+
* left out", at any depth. An OpenAI model behind Mastra's schema layer
|
|
47
|
+
* fills every field it leaves alone with `null`; a `null` stays only where
|
|
48
|
+
* the schema itself accepts it. Unions try each member and keep the first
|
|
49
|
+
* reading that parses.
|
|
50
|
+
*/
|
|
51
|
+
export function normalizeModelNulls(schema, value) {
|
|
52
|
+
const def = schema._zod.def;
|
|
53
|
+
switch (def.type) {
|
|
54
|
+
case 'optional':
|
|
55
|
+
case 'nullable':
|
|
56
|
+
case 'default':
|
|
57
|
+
case 'prefault':
|
|
58
|
+
case 'readonly':
|
|
59
|
+
case 'catch':
|
|
60
|
+
return value == null || def.innerType === undefined
|
|
61
|
+
? value
|
|
62
|
+
: normalizeModelNulls(def.innerType, value);
|
|
63
|
+
case 'pipe':
|
|
64
|
+
return def.in === undefined ? value : normalizeModelNulls(def.in, value);
|
|
65
|
+
case 'lazy':
|
|
66
|
+
return def.getter === undefined
|
|
67
|
+
? value
|
|
68
|
+
: normalizeModelNulls(def.getter(), value);
|
|
69
|
+
case 'array': {
|
|
70
|
+
const element = def.element;
|
|
71
|
+
return Array.isArray(value) && element !== undefined
|
|
72
|
+
? value.map((item) => normalizeModelNulls(element, item))
|
|
73
|
+
: value;
|
|
74
|
+
}
|
|
75
|
+
case 'record': {
|
|
76
|
+
const valueType = def.valueType;
|
|
77
|
+
if (!isPlainObject(value) || valueType === undefined) {
|
|
78
|
+
return value;
|
|
79
|
+
}
|
|
80
|
+
return Object.fromEntries(Object.entries(value).flatMap(([key, item]) => item === null && !valueType.safeParse(null).success
|
|
81
|
+
? []
|
|
82
|
+
: [[key, normalizeModelNulls(valueType, item)]]));
|
|
83
|
+
}
|
|
84
|
+
case 'union': {
|
|
85
|
+
for (const option of def.options ?? []) {
|
|
86
|
+
const normalized = normalizeModelNulls(option, value);
|
|
87
|
+
if (option.safeParse(normalized).success) {
|
|
88
|
+
return normalized;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return value;
|
|
92
|
+
}
|
|
93
|
+
case 'object': {
|
|
94
|
+
if (!isPlainObject(value) || !(schema instanceof z.ZodObject)) {
|
|
95
|
+
return value;
|
|
96
|
+
}
|
|
97
|
+
const shape = schema.shape;
|
|
98
|
+
return Object.fromEntries(Object.entries(value).flatMap(([key, item]) => {
|
|
99
|
+
const field = shape[key];
|
|
100
|
+
if (field === undefined) {
|
|
101
|
+
return [[key, item]];
|
|
102
|
+
}
|
|
103
|
+
const leaveOut = item === null &&
|
|
104
|
+
field.safeParse(undefined).success &&
|
|
105
|
+
!field.safeParse(null).success;
|
|
106
|
+
return leaveOut ? [] : [[key, normalizeModelNulls(field, item)]];
|
|
107
|
+
}));
|
|
108
|
+
}
|
|
109
|
+
default:
|
|
110
|
+
return value;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
/** The definitions a request carries, for the server to offer the model as client tools. */
|
|
114
|
+
export function toClientToolDefs(actions) {
|
|
115
|
+
return actions.map((action) => ({
|
|
116
|
+
name: action.name,
|
|
117
|
+
description: action.description,
|
|
118
|
+
jsonSchema: z.toJSONSchema(action.inputSchema, {
|
|
119
|
+
io: 'input',
|
|
120
|
+
unrepresentable: 'any'
|
|
121
|
+
}),
|
|
122
|
+
...(action.requiresApproval === undefined
|
|
123
|
+
? {}
|
|
124
|
+
: { requiresApproval: action.requiresApproval })
|
|
125
|
+
}));
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Carries out the client action a tool call names and posts its result on the
|
|
129
|
+
* chat; null when no action has that name (a server tool: its result arrives
|
|
130
|
+
* 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
|
+
*
|
|
133
|
+
* Never await it inside the chat's `onToolCall`: the AI SDK runs that callback
|
|
134
|
+
* on the queue `addToolOutput` waits on, so awaiting deadlocks the stream.
|
|
135
|
+
*/
|
|
136
|
+
export function runClientAction(chat, actions, toolCall) {
|
|
137
|
+
const action = actions.find((candidate) => candidate.name === toolCall.toolName);
|
|
138
|
+
if (action === undefined) {
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
141
|
+
const fail = (errorText) => chat.addToolOutput({
|
|
142
|
+
state: 'output-error',
|
|
143
|
+
tool: toolCall.toolName,
|
|
144
|
+
toolCallId: toolCall.toolCallId,
|
|
145
|
+
errorText
|
|
146
|
+
});
|
|
147
|
+
return (async () => {
|
|
148
|
+
if (isRepeatInTurn(chat.messages, action, toolCall)) {
|
|
149
|
+
await fail(CLIENT_ACTION_REPEATED);
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
try {
|
|
153
|
+
const output = await action.run(toolCall.input);
|
|
154
|
+
await chat.addToolOutput({
|
|
155
|
+
tool: toolCall.toolName,
|
|
156
|
+
toolCallId: toolCall.toolCallId,
|
|
157
|
+
output
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
catch (error) {
|
|
161
|
+
await fail(error instanceof Error ? error.message : CLIENT_ACTION_FAILED);
|
|
162
|
+
}
|
|
163
|
+
})();
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* The same action with the same input already ran in this user turn. Running
|
|
167
|
+
* it again changes nothing and only feeds a model that is circling.
|
|
168
|
+
*/
|
|
169
|
+
function isRepeatInTurn(messages, action, toolCall) {
|
|
170
|
+
const key = callKey(action, toolCall.input);
|
|
171
|
+
return currentUserTurn(messages).some((message) => message.parts.some((part) => isToolUIPart(part) &&
|
|
172
|
+
part.toolCallId !== toolCall.toolCallId &&
|
|
173
|
+
part.state === 'output-available' &&
|
|
174
|
+
getToolName(part) === action.name &&
|
|
175
|
+
callKey(action, part.input) === key));
|
|
176
|
+
}
|
|
177
|
+
/** The input with the model's nulls left out, so the same call matches however the model wrote it. */
|
|
178
|
+
function callKey(action, input) {
|
|
179
|
+
return JSON.stringify(normalizeModelNulls(action.inputSchema, input ?? {}));
|
|
180
|
+
}
|
|
181
|
+
function isPlainObject(value) {
|
|
182
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
183
|
+
}
|
|
184
|
+
//# sourceMappingURL=actions.js.map
|
|
@@ -0,0 +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"]}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The card registry: which card a tool part draws, with its output checked
|
|
3
|
+
* against the card's schema when it is drawn, never trusted. A thread stored
|
|
4
|
+
* before a schema changed still opens: its card falls back to the generic
|
|
5
|
+
* row. Lookups the model makes on its way to an answer are steps, which a
|
|
6
|
+
* client folds into one activity list.
|
|
7
|
+
*/
|
|
8
|
+
import type { z } from 'zod';
|
|
9
|
+
import type { ToolPart } from './messages.js';
|
|
10
|
+
/** The output schema of each typed card, by kind. */
|
|
11
|
+
export type CardSchemas = Readonly<Record<string, z.ZodType>>;
|
|
12
|
+
/** Which card a tool draws: a typed card, the generic row, or nothing. */
|
|
13
|
+
export type CardKindOf<S extends CardSchemas> = (keyof S & string) | 'generic' | 'silent';
|
|
14
|
+
/** What a client draws for one tool part. */
|
|
15
|
+
export type Card<S extends CardSchemas> = {
|
|
16
|
+
readonly [K in keyof S & string]: {
|
|
17
|
+
readonly kind: K;
|
|
18
|
+
readonly state: 'done';
|
|
19
|
+
readonly data: z.output<S[K]>;
|
|
20
|
+
readonly part: ToolPart;
|
|
21
|
+
}
|
|
22
|
+
/** The call has no result yet: a card that shows its input can draw it. */
|
|
23
|
+
| {
|
|
24
|
+
readonly kind: K;
|
|
25
|
+
readonly state: 'running';
|
|
26
|
+
readonly part: ToolPart;
|
|
27
|
+
};
|
|
28
|
+
}[keyof S & string] | {
|
|
29
|
+
readonly kind: 'generic';
|
|
30
|
+
readonly toolName: string;
|
|
31
|
+
readonly part: ToolPart;
|
|
32
|
+
} | {
|
|
33
|
+
readonly kind: 'silent';
|
|
34
|
+
readonly part: ToolPart;
|
|
35
|
+
};
|
|
36
|
+
export interface CardRegistryOptions<S extends CardSchemas> {
|
|
37
|
+
/** The output schema each typed card reads. */
|
|
38
|
+
readonly schemas: S;
|
|
39
|
+
/** The card a tool draws, by its wire name; undefined for a tool the client does not know, which draws the generic row. */
|
|
40
|
+
readonly toolCard: (toolName: string) => CardKindOf<S> | undefined;
|
|
41
|
+
/** The card-ready data inside a tool's output, such as the `projection` of a result envelope. The output as it is when left out. */
|
|
42
|
+
readonly select?: (output: unknown, toolName: string) => unknown;
|
|
43
|
+
/** Kinds that are steps, folded into the turn's activity list; `generic` is a step unless listed false. */
|
|
44
|
+
readonly steps?: Partial<Record<CardKindOf<S>, boolean>>;
|
|
45
|
+
}
|
|
46
|
+
export interface CardRegistry<S extends CardSchemas> {
|
|
47
|
+
readonly resolve: (part: ToolPart) => Card<S>;
|
|
48
|
+
readonly isStep: (card: Card<S>) => boolean;
|
|
49
|
+
/**
|
|
50
|
+
* Neighbouring tool parts in order, each run of adjacent steps folded into
|
|
51
|
+
* one `steps` group, even a run of one, so a run keeps its key as it grows.
|
|
52
|
+
* Silent tools draw nothing and never split a run.
|
|
53
|
+
*/
|
|
54
|
+
readonly fold: (parts: readonly ToolPart[]) => FoldedCards<S>;
|
|
55
|
+
}
|
|
56
|
+
export type FoldedCards<S extends CardSchemas> = readonly ({
|
|
57
|
+
readonly kind: 'card';
|
|
58
|
+
readonly key: string;
|
|
59
|
+
readonly card: Card<S>;
|
|
60
|
+
} | {
|
|
61
|
+
readonly kind: 'steps';
|
|
62
|
+
readonly key: string;
|
|
63
|
+
readonly cards: readonly Card<S>[];
|
|
64
|
+
})[];
|
|
65
|
+
export declare function createCardRegistry<const S extends CardSchemas>(options: CardRegistryOptions<S>): CardRegistry<S>;
|
|
66
|
+
//# sourceMappingURL=cards.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cards.d.ts","sourceRoot":"","sources":["../../src/client/cards.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The card registry: which card a tool part draws, with its output checked
|
|
3
|
+
* against the card's schema when it is drawn, never trusted. A thread stored
|
|
4
|
+
* before a schema changed still opens: its card falls back to the generic
|
|
5
|
+
* row. Lookups the model makes on its way to an answer are steps, which a
|
|
6
|
+
* client folds into one activity list.
|
|
7
|
+
*/
|
|
8
|
+
import { getToolName } from 'ai';
|
|
9
|
+
// FUNCTIONS
|
|
10
|
+
export function createCardRegistry(options) {
|
|
11
|
+
const select = options.select ?? ((output) => output);
|
|
12
|
+
const resolve = (part) => {
|
|
13
|
+
const toolName = getToolName(part);
|
|
14
|
+
const kind = options.toolCard(toolName);
|
|
15
|
+
if (kind === 'silent') {
|
|
16
|
+
return { kind: 'silent', part };
|
|
17
|
+
}
|
|
18
|
+
const schema = kind === undefined || kind === 'generic'
|
|
19
|
+
? undefined
|
|
20
|
+
: options.schemas[kind];
|
|
21
|
+
if (kind === undefined || schema === undefined) {
|
|
22
|
+
return { kind: 'generic', toolName, part };
|
|
23
|
+
}
|
|
24
|
+
if (part.state === 'output-error' || part.state === 'output-denied') {
|
|
25
|
+
return { kind: 'generic', toolName, part };
|
|
26
|
+
}
|
|
27
|
+
if (part.state !== 'output-available') {
|
|
28
|
+
return { kind, state: 'running', part };
|
|
29
|
+
}
|
|
30
|
+
const parsed = schema.safeParse(select(part.output, toolName));
|
|
31
|
+
// The schema of `kind` parsed the data, so it is that card's data.
|
|
32
|
+
return parsed.success
|
|
33
|
+
? { kind, state: 'done', data: parsed.data, part }
|
|
34
|
+
: { kind: 'generic', toolName, part };
|
|
35
|
+
};
|
|
36
|
+
const isStep = (card) => options.steps?.[card.kind] ??
|
|
37
|
+
card.kind === 'generic';
|
|
38
|
+
return {
|
|
39
|
+
resolve,
|
|
40
|
+
isStep,
|
|
41
|
+
fold: (parts) => {
|
|
42
|
+
const folded = [];
|
|
43
|
+
for (const part of parts) {
|
|
44
|
+
const card = resolve(part);
|
|
45
|
+
if (card.kind === 'silent') {
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
const previous = folded.at(-1);
|
|
49
|
+
if (!isStep(card)) {
|
|
50
|
+
folded.push({ kind: 'card', key: part.toolCallId, card });
|
|
51
|
+
}
|
|
52
|
+
else if (previous?.kind === 'steps') {
|
|
53
|
+
previous.cards.push(card);
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
folded.push({ kind: 'steps', key: part.toolCallId, cards: [card] });
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return folded;
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=cards.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cards.js","sourceRoot":"","sources":["../../src/client/cards.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The card registry: which card a tool part draws, with its output checked\n * against the card's schema when it is drawn, never trusted. A thread stored\n * before a schema changed still opens: its card falls back to the generic\n * row. Lookups the model makes on its way to an answer are steps, which a\n * client folds into one activity list.\n */\n\nimport { getToolName } from 'ai'\nimport type { z } from 'zod'\n\nimport type { ToolPart } from './messages.js'\n\n// TYPES\n\n/** The output schema of each typed card, by kind. */\nexport type CardSchemas = Readonly<Record<string, z.ZodType>>\n\n/** Which card a tool draws: a typed card, the generic row, or nothing. */\nexport type CardKindOf<S extends CardSchemas> =\n\t| (keyof S & string)\n\t| 'generic'\n\t| 'silent'\n\n/** What a client draws for one tool part. */\nexport type Card<S extends CardSchemas> =\n\t| {\n\t\t\treadonly [K in keyof S & string]:\n\t\t\t\t| {\n\t\t\t\t\t\treadonly kind: K\n\t\t\t\t\t\treadonly state: 'done'\n\t\t\t\t\t\treadonly data: z.output<S[K]>\n\t\t\t\t\t\treadonly part: ToolPart\n\t\t\t\t }\n\t\t\t\t/** The call has no result yet: a card that shows its input can draw it. */\n\t\t\t\t| {\n\t\t\t\t\t\treadonly kind: K\n\t\t\t\t\t\treadonly state: 'running'\n\t\t\t\t\t\treadonly part: ToolPart\n\t\t\t\t }\n\t }[keyof S & string]\n\t| {\n\t\t\treadonly kind: 'generic'\n\t\t\treadonly toolName: string\n\t\t\treadonly part: ToolPart\n\t }\n\t| { readonly kind: 'silent'; readonly part: ToolPart }\n\nexport interface CardRegistryOptions<S extends CardSchemas> {\n\t/** The output schema each typed card reads. */\n\treadonly schemas: S\n\t/** The card a tool draws, by its wire name; undefined for a tool the client does not know, which draws the generic row. */\n\treadonly toolCard: (toolName: string) => CardKindOf<S> | undefined\n\t/** The card-ready data inside a tool's output, such as the `projection` of a result envelope. The output as it is when left out. */\n\treadonly select?: (output: unknown, toolName: string) => unknown\n\t/** Kinds that are steps, folded into the turn's activity list; `generic` is a step unless listed false. */\n\treadonly steps?: Partial<Record<CardKindOf<S>, boolean>>\n}\n\nexport interface CardRegistry<S extends CardSchemas> {\n\treadonly resolve: (part: ToolPart) => Card<S>\n\treadonly isStep: (card: Card<S>) => boolean\n\t/**\n\t * Neighbouring tool parts in order, each run of adjacent steps folded into\n\t * one `steps` group, even a run of one, so a run keeps its key as it grows.\n\t * Silent tools draw nothing and never split a run.\n\t */\n\treadonly fold: (parts: readonly ToolPart[]) => FoldedCards<S>\n}\n\nexport type FoldedCards<S extends CardSchemas> = readonly (\n\t| { readonly kind: 'card'; readonly key: string; readonly card: Card<S> }\n\t| {\n\t\t\treadonly kind: 'steps'\n\t\t\treadonly key: string\n\t\t\treadonly cards: readonly Card<S>[]\n\t }\n)[]\n\n// FUNCTIONS\n\nexport function createCardRegistry<const S extends CardSchemas>(\n\toptions: CardRegistryOptions<S>\n): CardRegistry<S> {\n\tconst select = options.select ?? ((output: unknown) => output)\n\n\tconst resolve = (part: ToolPart): Card<S> => {\n\t\tconst toolName = getToolName(part)\n\t\tconst kind = options.toolCard(toolName)\n\n\t\tif (kind === 'silent') {\n\t\t\treturn { kind: 'silent', part }\n\t\t}\n\n\t\tconst schema =\n\t\t\tkind === undefined || kind === 'generic'\n\t\t\t\t? undefined\n\t\t\t\t: options.schemas[kind]\n\n\t\tif (kind === undefined || schema === undefined) {\n\t\t\treturn { kind: 'generic', toolName, part }\n\t\t}\n\n\t\tif (part.state === 'output-error' || part.state === 'output-denied') {\n\t\t\treturn { kind: 'generic', toolName, part }\n\t\t}\n\n\t\tif (part.state !== 'output-available') {\n\t\t\treturn { kind, state: 'running', part } as Card<S>\n\t\t}\n\n\t\tconst parsed = schema.safeParse(select(part.output, toolName))\n\n\t\t// The schema of `kind` parsed the data, so it is that card's data.\n\t\treturn parsed.success\n\t\t\t? ({ kind, state: 'done', data: parsed.data, part } as Card<S>)\n\t\t\t: { kind: 'generic', toolName, part }\n\t}\n\n\tconst isStep = (card: Card<S>): boolean =>\n\t\t(options.steps?.[card.kind] as boolean | undefined) ??\n\t\tcard.kind === 'generic'\n\n\treturn {\n\t\tresolve,\n\t\tisStep,\n\t\tfold: (parts) => {\n\t\t\tconst folded: (\n\t\t\t\t| { kind: 'card'; key: string; card: Card<S> }\n\t\t\t\t| { kind: 'steps'; key: string; cards: Card<S>[] }\n\t\t\t)[] = []\n\n\t\t\tfor (const part of parts) {\n\t\t\t\tconst card = resolve(part)\n\n\t\t\t\tif (card.kind === 'silent') {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tconst previous = folded.at(-1)\n\n\t\t\t\tif (!isStep(card)) {\n\t\t\t\t\tfolded.push({ kind: 'card', key: part.toolCallId, card })\n\t\t\t\t} else if (previous?.kind === 'steps') {\n\t\t\t\t\tprevious.cards.push(card)\n\t\t\t\t} else {\n\t\t\t\t\tfolded.push({ kind: 'steps', key: part.toolCallId, cards: [card] })\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn folded\n\t\t}\n\t}\n}\n"]}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The AI SDK `Chat` the session owns, with a state any framework can
|
|
3
|
+
* subscribe to: React reads it with `useSyncExternalStore`, Vue mirrors it
|
|
4
|
+
* into a ref. The same state the AI SDK's React chat keeps, with one
|
|
5
|
+
* listener list instead of private callbacks.
|
|
6
|
+
*/
|
|
7
|
+
import { AbstractChat, type ChatInit, type ChatStatus, type UIMessage } from 'ai';
|
|
8
|
+
/** What a framework renders from the chat; a new object whenever any of it changes. */
|
|
9
|
+
export interface ChatSnapshot<M extends UIMessage = UIMessage> {
|
|
10
|
+
readonly messages: readonly M[];
|
|
11
|
+
readonly status: ChatStatus;
|
|
12
|
+
readonly error: Error | undefined;
|
|
13
|
+
}
|
|
14
|
+
/** The AI SDK chat with a subscribable state. */
|
|
15
|
+
export declare class ObservableChat<M extends UIMessage = UIMessage> extends AbstractChat<M> {
|
|
16
|
+
#private;
|
|
17
|
+
constructor({ messages, ...init }: ChatInit<M>);
|
|
18
|
+
/** The messages, status and error as one value that changes identity on every change. */
|
|
19
|
+
readonly getSnapshot: () => ChatSnapshot<M>;
|
|
20
|
+
/** Hears every change of the messages, status or error. */
|
|
21
|
+
readonly subscribe: (listener: () => void) => (() => void);
|
|
22
|
+
}
|
|
23
|
+
/** A subscribe whose listener hears at most one change per `ms`, the last one always: a render throttle for a streaming answer. */
|
|
24
|
+
export declare function throttleSubscribe(subscribe: (listener: () => void) => () => void, ms: number | undefined): (listener: () => void) => () => void;
|
|
25
|
+
//# sourceMappingURL=chat.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chat.d.ts","sourceRoot":"","sources":["../../src/client/chat.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EACN,YAAY,EACZ,KAAK,QAAQ,EAEb,KAAK,UAAU,EACf,KAAK,SAAS,EACd,MAAM,IAAI,CAAA;AAIX,uFAAuF;AACvF,MAAM,WAAW,YAAY,CAAC,CAAC,SAAS,SAAS,GAAG,SAAS;IAC5D,QAAQ,CAAC,QAAQ,EAAE,SAAS,CAAC,EAAE,CAAA;IAC/B,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAA;IAC3B,QAAQ,CAAC,KAAK,EAAE,KAAK,GAAG,SAAS,CAAA;CACjC;AAsGD,iDAAiD;AACjD,qBAAa,cAAc,CAC1B,CAAC,SAAS,SAAS,GAAG,SAAS,CAC9B,SAAQ,YAAY,CAAC,CAAC,CAAC;;gBAGZ,EAAE,QAAQ,EAAE,GAAG,IAAI,EAAE,EAAE,QAAQ,CAAC,CAAC,CAAC;IAO9C,yFAAyF;IACzF,QAAQ,CAAC,WAAW,QAAO,YAAY,CAAC,CAAC,CAAC,CAA6B;IAEvE,2DAA2D;IAC3D,QAAQ,CAAC,SAAS,GAAI,UAAU,MAAM,IAAI,KAAG,CAAC,MAAM,IAAI,CAAC,CACzB;CAChC;AAID,mIAAmI;AACnI,wBAAgB,iBAAiB,CAChC,SAAS,EAAE,CAAC,QAAQ,EAAE,MAAM,IAAI,KAAK,MAAM,IAAI,EAC/C,EAAE,EAAE,MAAM,GAAG,SAAS,GACpB,CAAC,QAAQ,EAAE,MAAM,IAAI,KAAK,MAAM,IAAI,CAmCtC"}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The AI SDK `Chat` the session owns, with a state any framework can
|
|
3
|
+
* subscribe to: React reads it with `useSyncExternalStore`, Vue mirrors it
|
|
4
|
+
* into a ref. The same state the AI SDK's React chat keeps, with one
|
|
5
|
+
* listener list instead of private callbacks.
|
|
6
|
+
*/
|
|
7
|
+
import { AbstractChat } from 'ai';
|
|
8
|
+
// CLASSES
|
|
9
|
+
class ObservableChatState {
|
|
10
|
+
#messages;
|
|
11
|
+
#status = 'ready';
|
|
12
|
+
#error = undefined;
|
|
13
|
+
#snapshot;
|
|
14
|
+
#listeners = new Set();
|
|
15
|
+
constructor(messages = []) {
|
|
16
|
+
this.#messages = messages;
|
|
17
|
+
}
|
|
18
|
+
get messages() {
|
|
19
|
+
return this.#messages;
|
|
20
|
+
}
|
|
21
|
+
set messages(messages) {
|
|
22
|
+
this.#messages = [...messages];
|
|
23
|
+
this.#notify();
|
|
24
|
+
}
|
|
25
|
+
get status() {
|
|
26
|
+
return this.#status;
|
|
27
|
+
}
|
|
28
|
+
set status(status) {
|
|
29
|
+
this.#status = status;
|
|
30
|
+
this.#notify();
|
|
31
|
+
}
|
|
32
|
+
get error() {
|
|
33
|
+
return this.#error;
|
|
34
|
+
}
|
|
35
|
+
set error(error) {
|
|
36
|
+
this.#error = error;
|
|
37
|
+
this.#notify();
|
|
38
|
+
}
|
|
39
|
+
pushMessage = (message) => {
|
|
40
|
+
this.#messages = this.#messages.concat(message);
|
|
41
|
+
this.#notify();
|
|
42
|
+
};
|
|
43
|
+
popMessage = () => {
|
|
44
|
+
this.#messages = this.#messages.slice(0, -1);
|
|
45
|
+
this.#notify();
|
|
46
|
+
};
|
|
47
|
+
replaceMessage = (index, message) => {
|
|
48
|
+
this.#messages = [
|
|
49
|
+
...this.#messages.slice(0, index),
|
|
50
|
+
this.snapshot(message),
|
|
51
|
+
...this.#messages.slice(index + 1)
|
|
52
|
+
];
|
|
53
|
+
this.#notify();
|
|
54
|
+
};
|
|
55
|
+
/** A copy of a streaming message: the AI SDK keeps writing into the one it holds. */
|
|
56
|
+
snapshot = (value) => {
|
|
57
|
+
if (value === null ||
|
|
58
|
+
typeof value !== 'object' ||
|
|
59
|
+
!('parts' in value) ||
|
|
60
|
+
!Array.isArray(value.parts)) {
|
|
61
|
+
return value;
|
|
62
|
+
}
|
|
63
|
+
return { ...value, parts: value.parts.map((part) => ({ ...part })) };
|
|
64
|
+
};
|
|
65
|
+
getSnapshot = () => {
|
|
66
|
+
this.#snapshot ??= {
|
|
67
|
+
messages: this.#messages,
|
|
68
|
+
status: this.#status,
|
|
69
|
+
error: this.#error
|
|
70
|
+
};
|
|
71
|
+
return this.#snapshot;
|
|
72
|
+
};
|
|
73
|
+
subscribe = (listener) => {
|
|
74
|
+
this.#listeners.add(listener);
|
|
75
|
+
return () => {
|
|
76
|
+
this.#listeners.delete(listener);
|
|
77
|
+
};
|
|
78
|
+
};
|
|
79
|
+
#notify() {
|
|
80
|
+
this.#snapshot = undefined;
|
|
81
|
+
for (const listener of this.#listeners) {
|
|
82
|
+
listener();
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
/** The AI SDK chat with a subscribable state. */
|
|
87
|
+
export class ObservableChat extends AbstractChat {
|
|
88
|
+
#state;
|
|
89
|
+
constructor({ messages, ...init }) {
|
|
90
|
+
const state = new ObservableChatState(messages);
|
|
91
|
+
super({ ...init, state });
|
|
92
|
+
this.#state = state;
|
|
93
|
+
}
|
|
94
|
+
/** The messages, status and error as one value that changes identity on every change. */
|
|
95
|
+
getSnapshot = () => this.#state.getSnapshot();
|
|
96
|
+
/** Hears every change of the messages, status or error. */
|
|
97
|
+
subscribe = (listener) => this.#state.subscribe(listener);
|
|
98
|
+
}
|
|
99
|
+
// FUNCTIONS
|
|
100
|
+
/** A subscribe whose listener hears at most one change per `ms`, the last one always: a render throttle for a streaming answer. */
|
|
101
|
+
export function throttleSubscribe(subscribe, ms) {
|
|
102
|
+
if (ms === undefined || ms <= 0) {
|
|
103
|
+
return subscribe;
|
|
104
|
+
}
|
|
105
|
+
return (listener) => {
|
|
106
|
+
let timer;
|
|
107
|
+
let pending = false;
|
|
108
|
+
const flush = () => {
|
|
109
|
+
timer = undefined;
|
|
110
|
+
if (pending) {
|
|
111
|
+
pending = false;
|
|
112
|
+
listener();
|
|
113
|
+
timer = setTimeout(flush, ms);
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
const unsubscribe = subscribe(() => {
|
|
117
|
+
if (timer !== undefined) {
|
|
118
|
+
pending = true;
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
listener();
|
|
122
|
+
timer = setTimeout(flush, ms);
|
|
123
|
+
});
|
|
124
|
+
return () => {
|
|
125
|
+
clearTimeout(timer);
|
|
126
|
+
unsubscribe();
|
|
127
|
+
};
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
//# sourceMappingURL=chat.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chat.js","sourceRoot":"","sources":["../../src/client/chat.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The AI SDK `Chat` the session owns, with a state any framework can\n * subscribe to: React reads it with `useSyncExternalStore`, Vue mirrors it\n * into a ref. The same state the AI SDK's React chat keeps, with one\n * listener list instead of private callbacks.\n */\n\nimport {\n\tAbstractChat,\n\ttype ChatInit,\n\ttype ChatState,\n\ttype ChatStatus,\n\ttype UIMessage\n} from 'ai'\n\n// TYPES\n\n/** What a framework renders from the chat; a new object whenever any of it changes. */\nexport interface ChatSnapshot<M extends UIMessage = UIMessage> {\n\treadonly messages: readonly M[]\n\treadonly status: ChatStatus\n\treadonly error: Error | undefined\n}\n\n// CLASSES\n\nclass ObservableChatState<M extends UIMessage> implements ChatState<M> {\n\t#messages: M[]\n\t#status: ChatStatus = 'ready'\n\t#error: Error | undefined = undefined\n\t#snapshot: ChatSnapshot<M> | undefined\n\treadonly #listeners = new Set<() => void>()\n\n\tconstructor(messages: M[] = []) {\n\t\tthis.#messages = messages\n\t}\n\n\tget messages(): M[] {\n\t\treturn this.#messages\n\t}\n\n\tset messages(messages: M[]) {\n\t\tthis.#messages = [...messages]\n\t\tthis.#notify()\n\t}\n\n\tget status(): ChatStatus {\n\t\treturn this.#status\n\t}\n\n\tset status(status: ChatStatus) {\n\t\tthis.#status = status\n\t\tthis.#notify()\n\t}\n\n\tget error(): Error | undefined {\n\t\treturn this.#error\n\t}\n\n\tset error(error: Error | undefined) {\n\t\tthis.#error = error\n\t\tthis.#notify()\n\t}\n\n\tpushMessage = (message: M): void => {\n\t\tthis.#messages = this.#messages.concat(message)\n\t\tthis.#notify()\n\t}\n\n\tpopMessage = (): void => {\n\t\tthis.#messages = this.#messages.slice(0, -1)\n\t\tthis.#notify()\n\t}\n\n\treplaceMessage = (index: number, message: M): void => {\n\t\tthis.#messages = [\n\t\t\t...this.#messages.slice(0, index),\n\t\t\tthis.snapshot(message),\n\t\t\t...this.#messages.slice(index + 1)\n\t\t]\n\t\tthis.#notify()\n\t}\n\n\t/** A copy of a streaming message: the AI SDK keeps writing into the one it holds. */\n\tsnapshot = <T>(value: T): T => {\n\t\tif (\n\t\t\tvalue === null ||\n\t\t\ttypeof value !== 'object' ||\n\t\t\t!('parts' in value) ||\n\t\t\t!Array.isArray(value.parts)\n\t\t) {\n\t\t\treturn value\n\t\t}\n\n\t\treturn { ...value, parts: value.parts.map((part: object) => ({ ...part })) }\n\t}\n\n\tgetSnapshot = (): ChatSnapshot<M> => {\n\t\tthis.#snapshot ??= {\n\t\t\tmessages: this.#messages,\n\t\t\tstatus: this.#status,\n\t\t\terror: this.#error\n\t\t}\n\n\t\treturn this.#snapshot\n\t}\n\n\tsubscribe = (listener: () => void): (() => void) => {\n\t\tthis.#listeners.add(listener)\n\n\t\treturn () => {\n\t\t\tthis.#listeners.delete(listener)\n\t\t}\n\t}\n\n\t#notify(): void {\n\t\tthis.#snapshot = undefined\n\n\t\tfor (const listener of this.#listeners) {\n\t\t\tlistener()\n\t\t}\n\t}\n}\n\n/** The AI SDK chat with a subscribable state. */\nexport class ObservableChat<\n\tM extends UIMessage = UIMessage\n> extends AbstractChat<M> {\n\treadonly #state: ObservableChatState<M>\n\n\tconstructor({ messages, ...init }: ChatInit<M>) {\n\t\tconst state = new ObservableChatState(messages)\n\n\t\tsuper({ ...init, state })\n\t\tthis.#state = state\n\t}\n\n\t/** The messages, status and error as one value that changes identity on every change. */\n\treadonly getSnapshot = (): ChatSnapshot<M> => this.#state.getSnapshot()\n\n\t/** Hears every change of the messages, status or error. */\n\treadonly subscribe = (listener: () => void): (() => void) =>\n\t\tthis.#state.subscribe(listener)\n}\n\n// FUNCTIONS\n\n/** A subscribe whose listener hears at most one change per `ms`, the last one always: a render throttle for a streaming answer. */\nexport function throttleSubscribe(\n\tsubscribe: (listener: () => void) => () => void,\n\tms: number | undefined\n): (listener: () => void) => () => void {\n\tif (ms === undefined || ms <= 0) {\n\t\treturn subscribe\n\t}\n\n\treturn (listener) => {\n\t\tlet timer: ReturnType<typeof setTimeout> | undefined\n\t\tlet pending = false\n\n\t\tconst flush = () => {\n\t\t\ttimer = undefined\n\n\t\t\tif (pending) {\n\t\t\t\tpending = false\n\t\t\t\tlistener()\n\t\t\t\ttimer = setTimeout(flush, ms)\n\t\t\t}\n\t\t}\n\n\t\tconst unsubscribe = subscribe(() => {\n\t\t\tif (timer !== undefined) {\n\t\t\t\tpending = true\n\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tlistener()\n\t\t\ttimer = setTimeout(flush, ms)\n\t\t})\n\n\t\treturn () => {\n\t\t\tclearTimeout(timer)\n\t\t\tunsubscribe()\n\t\t}\n\t}\n}\n"]}
|