ai 7.0.102 → 7.0.104

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/dist/index.d.ts +90 -6
  3. package/dist/index.js +900 -299
  4. package/dist/index.js.map +1 -1
  5. package/dist/internal/index.d.ts +1 -0
  6. package/dist/internal/index.js +5 -1
  7. package/dist/internal/index.js.map +1 -1
  8. package/dist/test/index.d.ts +16 -2
  9. package/dist/test/index.js +17 -0
  10. package/dist/test/index.js.map +1 -1
  11. package/docs/03-ai-sdk-core/18-code-mode.mdx +50 -0
  12. package/docs/03-ai-sdk-core/19-tool-search.mdx +80 -0
  13. package/docs/03-ai-sdk-core/32-evaluation.mdx +255 -0
  14. package/docs/03-ai-sdk-core/42-batch.mdx +1 -2
  15. package/docs/03-ai-sdk-core/45-provider-management.mdx +10 -0
  16. package/docs/03-ai-sdk-core/index.mdx +6 -0
  17. package/docs/04-ai-sdk-ui/03-chatbot-message-persistence.mdx +35 -0
  18. package/docs/06-advanced/11-secure-url-fetching.mdx +8 -2
  19. package/docs/07-reference/01-ai-sdk-core/14-evaluate.mdx +67 -0
  20. package/docs/07-reference/01-ai-sdk-core/20-tool.mdx +7 -0
  21. package/docs/07-reference/01-ai-sdk-core/22-dynamic-tool.mdx +7 -0
  22. package/docs/07-reference/01-ai-sdk-core/23-tool-search.mdx +75 -0
  23. package/docs/07-reference/01-ai-sdk-core/32-validate-ui-messages.mdx +12 -0
  24. package/docs/07-reference/01-ai-sdk-core/33-safe-validate-ui-messages.mdx +12 -0
  25. package/docs/07-reference/01-ai-sdk-core/40-provider-registry.mdx +17 -0
  26. package/docs/07-reference/01-ai-sdk-core/42-custom-provider.mdx +16 -0
  27. package/docs/07-reference/01-ai-sdk-core/index.mdx +6 -0
  28. package/docs/07-reference/02-ai-sdk-ui/31-convert-to-model-messages.mdx +12 -0
  29. package/docs/07-reference/05-ai-sdk-errors/ai-evaluation-unsupported-question-type-error.mdx +31 -0
  30. package/docs/07-reference/05-ai-sdk-errors/ai-no-such-model-error.mdx +4 -0
  31. package/docs/07-reference/05-ai-sdk-errors/ai-no-such-provider-error.mdx +4 -0
  32. package/package.json +12 -12
  33. package/src/error/index.ts +1 -0
  34. package/src/evaluate/evaluate.ts +106 -0
  35. package/src/evaluate/evaluation-provider.ts +6 -0
  36. package/src/evaluate/evaluation-result.ts +39 -0
  37. package/src/evaluate/index.ts +7 -0
  38. package/src/evaluate/validate-evaluation.ts +298 -0
  39. package/src/generate-text/generate-text.ts +25 -13
  40. package/src/generate-text/stream-text.ts +15 -2
  41. package/src/generate-text/tool-caller-configuration.ts +59 -5
  42. package/src/global.ts +1 -0
  43. package/src/index.ts +2 -0
  44. package/src/model/resolve-model.ts +55 -10
  45. package/src/prompt/prepare-tools.ts +1 -1
  46. package/src/realtime/browser-realtime-transport.ts +12 -1
  47. package/src/realtime/realtime-event-channel.ts +4 -0
  48. package/src/realtime/realtime-session.ts +9 -4
  49. package/src/registry/custom-provider.ts +37 -0
  50. package/src/registry/index.ts +2 -0
  51. package/src/registry/no-such-provider-error.ts +2 -1
  52. package/src/registry/provider-registry.ts +65 -6
  53. package/src/test/evaluation-mock-model-v4.ts +27 -0
  54. package/src/tool-search/prepare-tool-search.ts +143 -0
  55. package/src/tool-search/tool-search.ts +59 -0
  56. package/src/ui/convert-to-model-messages.ts +3 -0
  57. package/src/ui/process-ui-message-stream.ts +4 -0
  58. package/src/ui/ui-messages.ts +5 -1
  59. package/src/ui/validate-ui-messages.ts +3 -0
  60. package/src/ui/warn-if-ui-message-has-deprecated-raw-input.ts +36 -0
@@ -0,0 +1,143 @@
1
+ import {
2
+ experimental_getToolCaller,
3
+ type ToolSet,
4
+ type InferToolSetContext,
5
+ type Experimental_SandboxSession as SandboxSession,
6
+ } from '@ai-sdk/provider-utils';
7
+ import { InvalidArgumentError } from '../error/invalid-argument-error';
8
+ import {
9
+ DIRECT_TOOL_CALL,
10
+ type ResolvedToolCallers,
11
+ } from '../generate-text/tool-caller-configuration';
12
+ import { resolveToolDescription } from '../prompt/prepare-tools';
13
+ import { getOwn } from '../util/get-own';
14
+ import { isToolSearch } from './tool-search';
15
+
16
+ /** Create discovery state for one generation, never for a shared tool instance. */
17
+ export function createToolSearchState({
18
+ tools,
19
+ toolCallers,
20
+ }: {
21
+ tools: ToolSet | undefined;
22
+ toolCallers: ResolvedToolCallers | undefined;
23
+ }): (
24
+ activeTools: ToolSet | undefined,
25
+ options?: {
26
+ toolsContext?: InferToolSetContext<ToolSet>;
27
+ experimental_sandbox?: SandboxSession;
28
+ },
29
+ ) => ToolSet | undefined {
30
+ const searchTools = Object.entries(tools ?? {}).filter(
31
+ ([, tool]) => tool.deferLoading || isToolSearch(tool),
32
+ );
33
+ if (searchTools.length === 0) {
34
+ return activeTools => activeTools;
35
+ }
36
+
37
+ const discovered = new Set<string>();
38
+ // An omitted entry permits direct calls; an explicit empty list permits none.
39
+ const getCallers = (name: string) =>
40
+ getOwn(toolCallers, name) ?? [DIRECT_TOOL_CALL];
41
+
42
+ for (const [name, tool] of searchTools) {
43
+ const callers = getCallers(name);
44
+ if (
45
+ callers.some(name => {
46
+ if (name === DIRECT_TOOL_CALL) {
47
+ return false;
48
+ }
49
+ const caller = experimental_getToolCaller(tools?.[name]);
50
+ return caller?.type !== 'local' || caller.prepareModelMessage == null;
51
+ }) ||
52
+ (isToolSearch(tool) && tool.deferLoading)
53
+ ) {
54
+ throw new InvalidArgumentError({
55
+ parameter: 'tools',
56
+ value: name,
57
+ message: `tool "${name}" must be callable directly or through code mode with toolDiscovery: 'conversation'. The search tool itself must not defer loading.`,
58
+ });
59
+ }
60
+ }
61
+
62
+ return (activeTools, { toolsContext = {}, experimental_sandbox } = {}) => {
63
+ if (activeTools == null) {
64
+ return undefined;
65
+ }
66
+
67
+ // Snapshot eligibility before binding callers. Newly discovered tools are
68
+ // intentionally absent from this step's execution bindings and catalog.
69
+ const entries = Object.entries(activeTools);
70
+ return Object.fromEntries(
71
+ entries
72
+ .filter(([name, tool]) => !tool.deferLoading || discovered.has(name))
73
+ .map(([searchName, tool]) => {
74
+ if (!isToolSearch(tool)) {
75
+ return [searchName, tool];
76
+ }
77
+
78
+ const callers = getCallers(searchName).filter(
79
+ name =>
80
+ name === DIRECT_TOOL_CALL || Object.hasOwn(activeTools, name),
81
+ );
82
+ const candidates = entries.filter(
83
+ ([name, candidate]) =>
84
+ candidate.deferLoading &&
85
+ !isToolSearch(candidate) &&
86
+ callers.some(caller => getCallers(name).includes(caller)),
87
+ );
88
+
89
+ return [
90
+ searchName,
91
+ {
92
+ ...tool,
93
+ execute: ({ query }: { query: string }) => {
94
+ const terms = [...new Set(tokenize(query))];
95
+ const matches = candidates
96
+ .map(([name, candidate]) => {
97
+ const description = resolveToolDescription({
98
+ tool: candidate,
99
+ toolName: name,
100
+ toolsContext,
101
+ experimental_sandbox,
102
+ });
103
+ const nameTerms = tokenize(name);
104
+ const descriptionTerms = tokenize(description ?? '');
105
+ const score = terms.reduce(
106
+ (score, term) =>
107
+ score +
108
+ (nameTerms.includes(term) ? 2 : 0) +
109
+ (descriptionTerms.includes(term) ? 1 : 0),
110
+ 0,
111
+ );
112
+ return { name, description, score };
113
+ })
114
+ .filter(match => match.score > 0)
115
+ .sort((a, b) => b.score - a.score)
116
+ .slice(0, 5);
117
+
118
+ for (const { name } of matches) {
119
+ discovered.add(name);
120
+ }
121
+
122
+ return {
123
+ tools: matches.map(({ name, description }) => ({
124
+ name,
125
+ ...(description == null ? {} : { description }),
126
+ })),
127
+ };
128
+ },
129
+ },
130
+ ];
131
+ }),
132
+ );
133
+ };
134
+ }
135
+
136
+ function tokenize(text: string): string[] {
137
+ return (
138
+ text
139
+ .replace(/([a-z\d])([A-Z])/g, '$1 $2')
140
+ .toLowerCase()
141
+ .match(/[\p{L}\p{N}]+/gu) ?? []
142
+ );
143
+ }
@@ -0,0 +1,59 @@
1
+ import { jsonSchema, tool, type Tool } from '@ai-sdk/provider-utils';
2
+
3
+ const toolSearchSymbol = Symbol.for('vercel.ai.toolSearch');
4
+
5
+ type ToolSearchInput = { query: string };
6
+ type ToolSearchOutput = {
7
+ tools: Array<{ name: string; description?: string }>;
8
+ };
9
+
10
+ /**
11
+ * Search deferred tools by name and description. The surrounding generation
12
+ * binds the search registry and makes matches available on the next step.
13
+ * Use directly or through code mode with `toolDiscovery: 'conversation'`.
14
+ */
15
+ export function toolSearch(): Tool<ToolSearchInput, ToolSearchOutput> & {
16
+ type: 'function';
17
+ } {
18
+ return Object.assign(
19
+ tool({
20
+ description:
21
+ 'Search for tools by keywords in their names and descriptions. Returns up to five matching tools. Matches become available on the next model step, after this execution finishes. Wait for their tool definitions before calling the discovered tools. If no tools match, try different keywords.',
22
+ inputSchema: jsonSchema<ToolSearchInput>({
23
+ type: 'object',
24
+ properties: { query: { type: 'string', minLength: 1 } },
25
+ required: ['query'],
26
+ additionalProperties: false,
27
+ }),
28
+ outputSchema: jsonSchema<ToolSearchOutput>({
29
+ type: 'object',
30
+ properties: {
31
+ tools: {
32
+ type: 'array',
33
+ items: {
34
+ type: 'object',
35
+ properties: {
36
+ name: { type: 'string' },
37
+ description: { type: 'string' },
38
+ },
39
+ required: ['name'],
40
+ additionalProperties: false,
41
+ },
42
+ },
43
+ },
44
+ required: ['tools'],
45
+ additionalProperties: false,
46
+ }),
47
+ execute: () => {
48
+ throw new Error('toolSearch must be bound by an AI SDK generation.');
49
+ },
50
+ }),
51
+ { type: 'function' as const, [toolSearchSymbol]: true },
52
+ );
53
+ }
54
+
55
+ export function isToolSearch(tool: Tool): boolean {
56
+ return (
57
+ (tool as Tool & { [toolSearchSymbol]?: boolean })[toolSearchSymbol] === true
58
+ );
59
+ }
@@ -33,6 +33,7 @@ import {
33
33
  type ToolUIPart,
34
34
  type UIMessage,
35
35
  } from './ui-messages';
36
+ import { warnIfUIMessageHasDeprecatedRawInput } from './warn-if-ui-message-has-deprecated-raw-input';
36
37
  /**
37
38
  * Converts an array of UI messages from useChat into an array of ModelMessages that can be used
38
39
  * with the AI functions (e.g. `streamText`, `generateText`).
@@ -56,6 +57,8 @@ export async function convertToModelMessages<UI_MESSAGE extends UIMessage>(
56
57
  ): Promise<ModelMessage[]> {
57
58
  const modelMessages: ModelMessage[] = [];
58
59
 
60
+ warnIfUIMessageHasDeprecatedRawInput(messages);
61
+
59
62
  if (options?.ignoreIncompleteToolCalls) {
60
63
  messages = messages.map(message => ({
61
64
  ...message,
@@ -31,6 +31,8 @@ import {
31
31
  type UIMessage,
32
32
  type UIMessagePart,
33
33
  } from './ui-messages';
34
+ import { warnIfUIMessageHasDeprecatedRawInput } from './warn-if-ui-message-has-deprecated-raw-input';
35
+
34
36
  export type StreamingUIMessageState<UI_MESSAGE extends UIMessage> = {
35
37
  message: UI_MESSAGE;
36
38
  activeTextParts: Record<string, TextUIPart>;
@@ -737,6 +739,8 @@ export function processUIMessageStream<UI_MESSAGE extends UIMessage>({
737
739
  providerMetadata: chunk.providerMetadata,
738
740
  toolMetadata: chunk.toolMetadata,
739
741
  });
742
+
743
+ warnIfUIMessageHasDeprecatedRawInput([state.message]);
740
744
  }
741
745
 
742
746
  write();
@@ -363,7 +363,11 @@ export type UIToolInvocation<TOOL extends UITool | Tool> = {
363
363
  | {
364
364
  state: 'output-error';
365
365
  input: asUITool<TOOL>['input'] | undefined;
366
- rawInput?: unknown; // TODO AI SDK 6: remove this field, input should be unknown
366
+ /**
367
+ * @deprecated Use `input` instead. This field will be removed in the next
368
+ * major version.
369
+ */
370
+ rawInput?: unknown;
367
371
  output?: never;
368
372
  errorText: string;
369
373
  callProviderMetadata?: ProviderMetadata;
@@ -20,6 +20,7 @@ import type {
20
20
  ToolUIPart,
21
21
  UIMessage,
22
22
  } from './ui-messages';
23
+ import { warnIfUIMessageHasDeprecatedRawInput } from './warn-if-ui-message-has-deprecated-raw-input';
23
24
 
24
25
  const toolMetadataSchema: ZodType<JSONObject> = z.record(
25
26
  z.string(),
@@ -484,6 +485,8 @@ async function safeValidateUIMessagesInternal<UI_MESSAGE extends UIMessage>(
484
485
  schema: uiMessagesSchema,
485
486
  });
486
487
 
488
+ warnIfUIMessageHasDeprecatedRawInput(validatedMessages);
489
+
487
490
  if (metadataSchema) {
488
491
  for (const [msgIdx, message] of validatedMessages.entries()) {
489
492
  await validateTypes({
@@ -0,0 +1,36 @@
1
+ import { logWarnings } from '../logger/log-warnings';
2
+
3
+ const rawInputDeprecationWarning = {
4
+ type: 'deprecated',
5
+ setting: 'rawInput in output-error UI message parts',
6
+ message:
7
+ 'Use the "input" field instead. The "rawInput" field will be removed in the next major version.',
8
+ } as const;
9
+
10
+ export function warnIfUIMessageHasDeprecatedRawInput(
11
+ messages: ReadonlyArray<{
12
+ parts: ReadonlyArray<unknown>;
13
+ }>,
14
+ ): void {
15
+ const hasDeprecatedRawInput = messages.some(message =>
16
+ message.parts.some(
17
+ part =>
18
+ part != null &&
19
+ typeof part === 'object' &&
20
+ 'type' in part &&
21
+ typeof part.type === 'string' &&
22
+ (part.type === 'dynamic-tool' || part.type.startsWith('tool-')) &&
23
+ 'state' in part &&
24
+ part.state === 'output-error' &&
25
+ Object.prototype.hasOwnProperty.call(part, 'rawInput') &&
26
+ 'rawInput' in part &&
27
+ part.rawInput !== undefined,
28
+ ),
29
+ );
30
+
31
+ if (hasDeprecatedRawInput) {
32
+ logWarnings({
33
+ warnings: [rawInputDeprecationWarning],
34
+ });
35
+ }
36
+ }