@nestjs/ai 0.0.0-stage → 0.0.1
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/LICENSE +23 -0
- package/README.md +108 -2
- package/dist/ai.constants.d.ts +22 -0
- package/dist/ai.constants.js +24 -0
- package/dist/ai.module-definition.d.ts +23 -0
- package/dist/ai.module-definition.js +30 -0
- package/dist/ai.module.d.ts +11 -0
- package/dist/ai.module.js +36 -0
- package/dist/context/ai-execution-context.d.ts +32 -0
- package/dist/context/ai-execution-context.js +41 -0
- package/dist/context/ai-params.d.ts +20 -0
- package/dist/context/ai-params.js +44 -0
- package/dist/decorators/agent-function.decorator.d.ts +22 -0
- package/dist/decorators/agent-function.decorator.js +36 -0
- package/dist/decorators/index.d.ts +2 -0
- package/dist/decorators/index.js +2 -0
- package/dist/decorators/inject-model.decorator.d.ts +10 -0
- package/dist/decorators/inject-model.decorator.js +16 -0
- package/dist/errors/ai.errors.d.ts +54 -0
- package/dist/errors/ai.errors.js +62 -0
- package/dist/errors/index.d.ts +1 -0
- package/dist/errors/index.js +1 -0
- package/dist/http/keep-alive.util.d.ts +6 -0
- package/dist/http/keep-alive.util.js +38 -0
- package/dist/http/node-response.util.d.ts +14 -0
- package/dist/http/node-response.util.js +104 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +15 -0
- package/dist/interfaces/agent-function.interface.d.ts +53 -0
- package/dist/interfaces/agent-function.interface.js +1 -0
- package/dist/interfaces/ai-call-options.interface.d.ts +65 -0
- package/dist/interfaces/ai-call-options.interface.js +1 -0
- package/dist/interfaces/ai-module-options.interface.d.ts +88 -0
- package/dist/interfaces/ai-module-options.interface.js +1 -0
- package/dist/interfaces/ai-result.interface.d.ts +54 -0
- package/dist/interfaces/ai-result.interface.js +1 -0
- package/dist/interfaces/ai-schema.interface.d.ts +44 -0
- package/dist/interfaces/ai-schema.interface.js +1 -0
- package/dist/interfaces/ai-tool.interface.d.ts +22 -0
- package/dist/interfaces/ai-tool.interface.js +1 -0
- package/dist/interfaces/ai-usage.interface.d.ts +30 -0
- package/dist/interfaces/ai-usage.interface.js +1 -0
- package/dist/interfaces/index.d.ts +7 -0
- package/dist/interfaces/index.js +7 -0
- package/dist/models/ai-models.service.d.ts +29 -0
- package/dist/models/ai-models.service.js +113 -0
- package/dist/models/model-requests.d.ts +2 -0
- package/dist/models/model-requests.js +12 -0
- package/dist/runtime/agent-loop.d.ts +30 -0
- package/dist/runtime/agent-loop.js +81 -0
- package/dist/runtime/call-plan.d.ts +54 -0
- package/dist/runtime/call-plan.js +105 -0
- package/dist/runtime/model-policy.d.ts +48 -0
- package/dist/runtime/model-policy.js +202 -0
- package/dist/runtime/steps.d.ts +32 -0
- package/dist/runtime/steps.js +190 -0
- package/dist/runtime/text-stream.d.ts +64 -0
- package/dist/runtime/text-stream.js +211 -0
- package/dist/services/ai.service.d.ts +57 -0
- package/dist/services/ai.service.js +139 -0
- package/dist/testing/fake-model.d.ts +96 -0
- package/dist/testing/fake-model.js +201 -0
- package/dist/testing/index.d.ts +26 -0
- package/dist/testing/index.js +19 -0
- package/dist/tools/ai-tools.service.d.ts +77 -0
- package/dist/tools/ai-tools.service.js +234 -0
- package/dist/tools/tool-output.util.d.ts +6 -0
- package/dist/tools/tool-output.util.js +38 -0
- package/dist/utils/duration.util.d.ts +3 -0
- package/dist/utils/duration.util.js +17 -0
- package/dist/utils/errors.util.d.ts +16 -0
- package/dist/utils/errors.util.js +35 -0
- package/dist/utils/names.util.d.ts +2 -0
- package/dist/utils/names.util.js +10 -0
- package/dist/utils/schema.util.d.ts +16 -0
- package/dist/utils/schema.util.js +70 -0
- package/dist/utils/usage.util.d.ts +10 -0
- package/dist/utils/usage.util.js +42 -0
- package/dist/workflows/ai-agent.interfaces.d.ts +70 -0
- package/dist/workflows/ai-agent.interfaces.js +1 -0
- package/dist/workflows/ai-agent.workflow.d.ts +15 -0
- package/dist/workflows/ai-agent.workflow.js +25 -0
- package/dist/workflows/ai-agents.service.d.ts +83 -0
- package/dist/workflows/ai-agents.service.js +277 -0
- package/dist/workflows/ai-workflows.module.d.ts +6 -0
- package/dist/workflows/ai-workflows.module.js +22 -0
- package/dist/workflows/index.d.ts +6 -0
- package/dist/workflows/index.js +5 -0
- package/package.json +100 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
(The MIT License)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Kamil Mysliwiec
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
6
|
+
a copy of this software and associated documentation files (the
|
|
7
|
+
'Software'), to deal in the Software without restriction, including
|
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
11
|
+
the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be
|
|
14
|
+
included in all copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
19
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
|
20
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
|
21
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
|
22
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
23
|
+
|
package/README.md
CHANGED
|
@@ -1,3 +1,109 @@
|
|
|
1
|
-
|
|
1
|
+
<p align="center">
|
|
2
|
+
<a href="http://nestjs.com/" target="blank"><img src="https://nestjs.com/img/logo-small.svg" width="120" alt="Nest Logo" /></a>
|
|
3
|
+
</p>
|
|
2
4
|
|
|
3
|
-
|
|
5
|
+
[travis-image]: https://api.travis-ci.org/nestjs/nest.svg?branch=master
|
|
6
|
+
[travis-url]: https://travis-ci.org/nestjs/nest
|
|
7
|
+
[linux-image]: https://img.shields.io/travis/nestjs/nest/master.svg?label=linux
|
|
8
|
+
[linux-url]: https://travis-ci.org/nestjs/nest
|
|
9
|
+
|
|
10
|
+
<p align="center">A progressive <a href="http://nodejs.org" target="blank">Node.js</a> framework for building efficient and scalable server-side applications.</p>
|
|
11
|
+
<p align="center">
|
|
12
|
+
<a href="https://www.npmjs.com/~nestjscore"><img src="https://img.shields.io/npm/v/@nestjs/core.svg" alt="NPM Version" /></a>
|
|
13
|
+
<a href="https://www.npmjs.com/~nestjscore"><img src="https://img.shields.io/npm/l/@nestjs/core.svg" alt="Package License" /></a>
|
|
14
|
+
<a href="https://www.npmjs.com/~nestjscore"><img src="https://img.shields.io/npm/dm/@nestjs/core.svg" alt="NPM Downloads" /></a>
|
|
15
|
+
<a href="https://discord.gg/G7Qnnhy" target="_blank"><img src="https://img.shields.io/badge/discord-online-brightgreen.svg" alt="Discord"/></a>
|
|
16
|
+
<a href="https://opencollective.com/nest#backer"><img src="https://opencollective.com/nest/backers/badge.svg" alt="Backers on Open Collective" /></a>
|
|
17
|
+
<a href="https://opencollective.com/nest#sponsor"><img src="https://opencollective.com/nest/sponsors/badge.svg" alt="Sponsors on Open Collective" /></a>
|
|
18
|
+
<a href="https://paypal.me/kamilmysliwiec"><img src="https://img.shields.io/badge/Donate-PayPal-dc3d53.svg"/></a>
|
|
19
|
+
<a href="https://twitter.com/nestframework"><img src="https://img.shields.io/twitter/follow/nestframework.svg?style=social&label=Follow"></a>
|
|
20
|
+
</p>
|
|
21
|
+
<!--[](https://opencollective.com/nest#backer)
|
|
22
|
+
[](https://opencollective.com/nest#sponsor)-->
|
|
23
|
+
|
|
24
|
+
## Description
|
|
25
|
+
|
|
26
|
+
Language models for [Nest](https://github.com/nestjs/nest): named models through dependency injection, model calls with timeouts, retries and fallback models (run on [`@nestjs/resilience`](https://github.com/nestjs/resilience) policies, or your own), budgets and usage events, structured output validated by a schema, the application's own provider methods as agent functions its agents call (through Nest's guards, interceptors, pipes and exception filters, as the user the agent acts for), answers streamed to the browser on Express, Fastify and the Node adapter, and durable agent runs on [`@nestjs/workflows`](https://github.com/nestjs/workflows) (`@nestjs/ai/workflows`) that survive restarts and wait for a person's approval. You write an agent function with `@AgentFunction()`, and the model is offered it as a tool; a method can be an MCP tool too, served by [`@nestjs/mcp`](https://github.com/nestjs/mcp), with its `@Tool()` stacked on it. Comes with a fake model for tests. Built on the [AI SDK](https://ai-sdk.dev) (`ai`) and its provider packages.
|
|
27
|
+
|
|
28
|
+
## Installation
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
$ npm i --save @nestjs/ai @nestjs/resilience ai @ai-sdk/anthropic zod
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Any `@ai-sdk/*` provider package works (`@ai-sdk/openai`, `@ai-sdk/google`, ...). Zod 4.2 or later for the schemas; any other [Standard Schema](https://standardschema.dev) library that produces JSON Schema works too. Node.js 22 or later.
|
|
35
|
+
|
|
36
|
+
Each major version of `@nestjs/ai` supports one major version of the AI SDK: this one, `ai` 7.
|
|
37
|
+
|
|
38
|
+
## Quick Start
|
|
39
|
+
|
|
40
|
+
Register the models, and call them:
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
import { anthropic } from '@ai-sdk/anthropic';
|
|
44
|
+
import { Injectable, Module } from '@nestjs/common';
|
|
45
|
+
import { AgentFunction, AiModule, AiService, type AgentFunctionContext } from '@nestjs/ai';
|
|
46
|
+
import * as z from 'zod';
|
|
47
|
+
|
|
48
|
+
@Injectable()
|
|
49
|
+
export class OrdersTools {
|
|
50
|
+
constructor(private readonly orders: OrdersService) {}
|
|
51
|
+
|
|
52
|
+
@AgentFunction({
|
|
53
|
+
name: 'find-order',
|
|
54
|
+
description: 'Find an order by its id',
|
|
55
|
+
inputSchema: z.object({ orderId: z.string() }),
|
|
56
|
+
})
|
|
57
|
+
find({ orderId }: { orderId: string }, ctx: AgentFunctionContext) {
|
|
58
|
+
return this.orders.findFor(ctx.request?.user, orderId);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
@Injectable()
|
|
63
|
+
export class SupportService {
|
|
64
|
+
constructor(private readonly ai: AiService) {}
|
|
65
|
+
|
|
66
|
+
async answer(question: string, request: unknown) {
|
|
67
|
+
const { text } = await this.ai.generateText({
|
|
68
|
+
prompt: question,
|
|
69
|
+
tools: ['find-order'],
|
|
70
|
+
caller: { request },
|
|
71
|
+
});
|
|
72
|
+
return text;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
@Module({
|
|
77
|
+
imports: [
|
|
78
|
+
AiModule.forRoot({
|
|
79
|
+
models: {
|
|
80
|
+
default: anthropic('claude-opus-5-5'),
|
|
81
|
+
fast: anthropic('claude-haiku-5-5'),
|
|
82
|
+
},
|
|
83
|
+
defaults: { timeout: '30s', retry: { attempts: 3 } },
|
|
84
|
+
}),
|
|
85
|
+
],
|
|
86
|
+
providers: [OrdersService, OrdersTools, SupportService],
|
|
87
|
+
})
|
|
88
|
+
export class AppModule {}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
A method that MCP clients call too also carries `@nestjs/mcp`'s `@Tool()`, with options of its own: the two decorators don't read each other's, and one schema constant serves both. `generateObject({ schema })` answers a typed object, and `streamText()` an answer a controller sends to the browser with `pipeUIMessageStreamToResponse(res)`, as the AI SDK's `useChat()` reads it. Agent function calls run through the guards, interceptors, pipes and filters of the class and the method, with the context type `'ai'`, so `AiExecutionContext` reads them in enhancers (on a method with both decorators, they see `'mcp'` for MCP clients' calls), and `@nestjs/authentication` (0.1.4 or later) and `@nestjs/authorization` (0.1.2 or later) authenticate and authorize them. `@nestjs/ai/testing` has `FakeModel`, which answers with text, tool calls, objects or errors, without a network.
|
|
92
|
+
|
|
93
|
+
The [sample](sample) is a cat store: it triages support emails, and runs a chat assistant that calls the same `find-order` method its MCP server serves. `npm run build && npx tsc -p sample/tsconfig.json`, then `node sample/dist/main.js` (with `ANTHROPIC_API_KEY` set, Claude answers; without it, fake models do).
|
|
94
|
+
|
|
95
|
+
Read [Overview](https://docs.nestjs.com/ai/overview) to get started and for the module's options, [Models](https://docs.nestjs.com/ai/models) for retries, timeouts, fallback models and budgets, [Tools](https://docs.nestjs.com/ai/tools) for agent functions, the call context, guards and authorization, [Agents](https://docs.nestjs.com/ai/agents) for the agent loop, streaming to the browser and durable runs, and [Testing](https://docs.nestjs.com/ai/testing) for the fake model.
|
|
96
|
+
|
|
97
|
+
## Support
|
|
98
|
+
|
|
99
|
+
Nest is an MIT-licensed open source project. It can grow thanks to the sponsors and support by the amazing backers. If you'd like to join them, please [read more here](https://docs.nestjs.com/support).
|
|
100
|
+
|
|
101
|
+
## Stay in touch
|
|
102
|
+
|
|
103
|
+
- Author - [Kamil Myśliwiec](https://twitter.com/kammysliwiec)
|
|
104
|
+
- Website - [https://nestjs.com](https://nestjs.com/)
|
|
105
|
+
- Twitter - [@nestframework](https://twitter.com/nestframework)
|
|
106
|
+
|
|
107
|
+
## License
|
|
108
|
+
|
|
109
|
+
Nest is [MIT licensed](LICENSE).
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/** The module options (`forRoot()`'s, or what the `forRootAsync()` factory returns). */
|
|
2
|
+
export declare const AI_MODULE_OPTIONS = "AI_MODULE_OPTIONS";
|
|
3
|
+
/** Where `@AgentFunction()` records its options, on the method. */
|
|
4
|
+
export declare const AGENT_FUNCTION_METADATA = "nestjs:ai:agent-function";
|
|
5
|
+
/**
|
|
6
|
+
* The `ExecutionContext` type of agent function calls: `context.getType() === 'ai'` in a guard, an interceptor or an
|
|
7
|
+
* exception filter, which reads the call with `AiExecutionContext`.
|
|
8
|
+
*/
|
|
9
|
+
export declare const AI_CONTEXT_TYPE = "ai";
|
|
10
|
+
/**
|
|
11
|
+
* The parameter metadata of agent functions, read by `ExternalContextCreator` (so that pipes run on the input): Nest's
|
|
12
|
+
* own route-arguments key, where `createParamDecorator()` decorators record themselves. `@nestjs/mcp` records the two
|
|
13
|
+
* parameters of its methods under the same key, with the same numbers (`AiParamtype`), so a method that is both an
|
|
14
|
+
* agent function and an MCP tool is recorded once, and both read it.
|
|
15
|
+
*/
|
|
16
|
+
export declare const AI_PARAMS_METADATA = "__routeArguments__";
|
|
17
|
+
/** The name of the model calls use when they name none. */
|
|
18
|
+
export declare const DEFAULT_MODEL_NAME = "default";
|
|
19
|
+
/** Models that replace configured ones by name, from `@nestjs/ai/testing`'s `overrideModels()`. Internal. */
|
|
20
|
+
export declare const AI_MODEL_OVERRIDES: unique symbol;
|
|
21
|
+
/** The injection token of the model called `name`, which `@InjectModel(name)` injects. */
|
|
22
|
+
export declare function getModelToken(name?: string): string;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/** The module options (`forRoot()`'s, or what the `forRootAsync()` factory returns). */
|
|
2
|
+
export const AI_MODULE_OPTIONS = 'AI_MODULE_OPTIONS';
|
|
3
|
+
/** Where `@AgentFunction()` records its options, on the method. */
|
|
4
|
+
export const AGENT_FUNCTION_METADATA = 'nestjs:ai:agent-function';
|
|
5
|
+
/**
|
|
6
|
+
* The `ExecutionContext` type of agent function calls: `context.getType() === 'ai'` in a guard, an interceptor or an
|
|
7
|
+
* exception filter, which reads the call with `AiExecutionContext`.
|
|
8
|
+
*/
|
|
9
|
+
export const AI_CONTEXT_TYPE = 'ai';
|
|
10
|
+
/**
|
|
11
|
+
* The parameter metadata of agent functions, read by `ExternalContextCreator` (so that pipes run on the input): Nest's
|
|
12
|
+
* own route-arguments key, where `createParamDecorator()` decorators record themselves. `@nestjs/mcp` records the two
|
|
13
|
+
* parameters of its methods under the same key, with the same numbers (`AiParamtype`), so a method that is both an
|
|
14
|
+
* agent function and an MCP tool is recorded once, and both read it.
|
|
15
|
+
*/
|
|
16
|
+
export const AI_PARAMS_METADATA = '__routeArguments__';
|
|
17
|
+
/** The name of the model calls use when they name none. */
|
|
18
|
+
export const DEFAULT_MODEL_NAME = 'default';
|
|
19
|
+
/** Models that replace configured ones by name, from `@nestjs/ai/testing`'s `overrideModels()`. Internal. */
|
|
20
|
+
export const AI_MODEL_OVERRIDES = Symbol('AI_MODEL_OVERRIDES');
|
|
21
|
+
/** The injection token of the model called `name`, which `@InjectModel(name)` injects. */
|
|
22
|
+
export function getModelToken(name = DEFAULT_MODEL_NAME) {
|
|
23
|
+
return `nestjs:ai:model:${name}`;
|
|
24
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type ConfigurableModuleAsyncOptions } from '@nestjs/common';
|
|
2
|
+
import type { AiModuleOptions, AiModuleStructure, AiOptionsFactory } from './interfaces/ai-module-options.interface.js';
|
|
3
|
+
import { AI_MODULE_OPTIONS } from './ai.constants.js';
|
|
4
|
+
/** `forRoot()`'s options: the module options, and `isGlobal` (default false). */
|
|
5
|
+
export type AiModuleRootOptions = AiModuleOptions & {
|
|
6
|
+
isGlobal?: boolean;
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* What `forRootAsync()` takes: `modelNames` and `isGlobal` at the top level, and one of `useFactory`, `useClass` or
|
|
10
|
+
* `useExisting` for the options.
|
|
11
|
+
*/
|
|
12
|
+
export type AiModuleAsyncOptions = ConfigurableModuleAsyncOptions<AiModuleOptions, 'createAiOptions'> & AiModuleStructure & {
|
|
13
|
+
isGlobal?: boolean;
|
|
14
|
+
};
|
|
15
|
+
export type { AiOptionsFactory };
|
|
16
|
+
export { AI_MODULE_OPTIONS };
|
|
17
|
+
export declare const ConfigurableModuleClass: import("@nestjs/common").ConfigurableModuleCls<AiModuleOptions, "forRoot", "createAiOptions", AiModuleStructure & {
|
|
18
|
+
isGlobal?: boolean;
|
|
19
|
+
}>, OPTIONS_TYPE: AiModuleOptions & Partial<AiModuleStructure & {
|
|
20
|
+
isGlobal?: boolean;
|
|
21
|
+
}>, ASYNC_OPTIONS_TYPE: ConfigurableModuleAsyncOptions<AiModuleOptions, "createAiOptions"> & Partial<AiModuleStructure & {
|
|
22
|
+
isGlobal?: boolean;
|
|
23
|
+
}>;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { ConfigurableModuleBuilder, Scope } from '@nestjs/common';
|
|
2
|
+
import { INQUIRER } from '@nestjs/core';
|
|
3
|
+
import { AI_MODULE_OPTIONS, getModelToken } from './ai.constants.js';
|
|
4
|
+
import { AiModels } from './models/ai-models.service.js';
|
|
5
|
+
import { requestedModelNames } from './models/model-requests.js';
|
|
6
|
+
export { AI_MODULE_OPTIONS };
|
|
7
|
+
export const { ConfigurableModuleClass, OPTIONS_TYPE, ASYNC_OPTIONS_TYPE } = new ConfigurableModuleBuilder({
|
|
8
|
+
moduleName: 'Ai',
|
|
9
|
+
optionsInjectionToken: AI_MODULE_OPTIONS,
|
|
10
|
+
})
|
|
11
|
+
.setClassMethodName('forRoot')
|
|
12
|
+
.setFactoryMethodName('createAiOptions')
|
|
13
|
+
.setExtras({ isGlobal: false, modelNames: undefined }, (definition, { isGlobal, modelNames }) => {
|
|
14
|
+
const names = new Set([...(modelNames ?? []), ...requestedModelNames()]);
|
|
15
|
+
// Transient, so that each class that injects a model is known (INQUIRER), and one that asks for a model the
|
|
16
|
+
// module does not configure is named in the error.
|
|
17
|
+
const tokens = [...names].map((name) => ({
|
|
18
|
+
provide: getModelToken(name),
|
|
19
|
+
scope: Scope.TRANSIENT,
|
|
20
|
+
inject: [AiModels, INQUIRER],
|
|
21
|
+
useFactory: (models, inquirer) => models.injected(name, inquirer),
|
|
22
|
+
}));
|
|
23
|
+
return {
|
|
24
|
+
...definition,
|
|
25
|
+
global: isGlobal,
|
|
26
|
+
providers: [...(definition.providers ?? []), ...tokens],
|
|
27
|
+
exports: [...(definition.exports ?? []), ...tokens.map((provider) => provider.provide)],
|
|
28
|
+
};
|
|
29
|
+
})
|
|
30
|
+
.build();
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type DynamicModule } from '@nestjs/common';
|
|
2
|
+
import { ConfigurableModuleClass, type AiModuleAsyncOptions, type AiModuleRootOptions } from './ai.module-definition.js';
|
|
3
|
+
/**
|
|
4
|
+
* `AiModule.forRoot({ models: { default: ... } })`, or `forRootAsync({ imports, inject, useFactory })`: the
|
|
5
|
+
* application's models by name, `AiService` for model calls, and the `@AgentFunction()` methods of its providers,
|
|
6
|
+
* which its agents call as tools. Global with `isGlobal: true`.
|
|
7
|
+
*/
|
|
8
|
+
export declare class AiModule extends ConfigurableModuleClass {
|
|
9
|
+
static forRoot(options: AiModuleRootOptions): DynamicModule;
|
|
10
|
+
static forRootAsync(options: AiModuleAsyncOptions): DynamicModule;
|
|
11
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
import { Module } from '@nestjs/common';
|
|
8
|
+
import { DiscoveryModule } from '@nestjs/core';
|
|
9
|
+
import { AI_MODEL_OVERRIDES } from './ai.constants.js';
|
|
10
|
+
import { ConfigurableModuleClass, } from './ai.module-definition.js';
|
|
11
|
+
import { AiModels } from './models/ai-models.service.js';
|
|
12
|
+
import { AiService } from './services/ai.service.js';
|
|
13
|
+
import { AiTools } from './tools/ai-tools.service.js';
|
|
14
|
+
/**
|
|
15
|
+
* `AiModule.forRoot({ models: { default: ... } })`, or `forRootAsync({ imports, inject, useFactory })`: the
|
|
16
|
+
* application's models by name, `AiService` for model calls, and the `@AgentFunction()` methods of its providers,
|
|
17
|
+
* which its agents call as tools. Global with `isGlobal: true`.
|
|
18
|
+
*/
|
|
19
|
+
let AiModule = class AiModule extends ConfigurableModuleClass {
|
|
20
|
+
static forRoot(options) {
|
|
21
|
+
// `@InjectModel()` may ask for every model `forRoot()` names.
|
|
22
|
+
const modelNames = Object.keys(options?.models ?? {});
|
|
23
|
+
return super.forRoot({ ...options, modelNames });
|
|
24
|
+
}
|
|
25
|
+
static forRootAsync(options) {
|
|
26
|
+
return super.forRootAsync(options);
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
AiModule = __decorate([
|
|
30
|
+
Module({
|
|
31
|
+
imports: [DiscoveryModule],
|
|
32
|
+
providers: [AiModels, AiTools, AiService, { provide: AI_MODEL_OVERRIDES, useValue: null }],
|
|
33
|
+
exports: [AiModels, AiTools, AiService],
|
|
34
|
+
})
|
|
35
|
+
], AiModule);
|
|
36
|
+
export { AiModule };
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { ArgumentsHost, ExecutionContext } from '@nestjs/common';
|
|
2
|
+
import { ExecutionContextHost } from '@nestjs/core/helpers/execution-context-host.js';
|
|
3
|
+
import type { AgentFunctionContext } from '../interfaces/agent-function.interface.js';
|
|
4
|
+
/** `context.getType<AiContextType>() === 'ai'` in an enhancer tells an agent function call from an HTTP request. */
|
|
5
|
+
export type AiContextType = 'ai';
|
|
6
|
+
/**
|
|
7
|
+
* The execution context of an agent function call, for guards, interceptors and exception filters, as
|
|
8
|
+
* `McpExecutionContext` is for MCP calls:
|
|
9
|
+
*
|
|
10
|
+
* ```ts
|
|
11
|
+
* canActivate(context: ExecutionContext) {
|
|
12
|
+
* if (context.getType<AiContextType>() !== 'ai') {
|
|
13
|
+
* return true;
|
|
14
|
+
* }
|
|
15
|
+
* const call = AiExecutionContext.create(context);
|
|
16
|
+
* return call.getRequest()?.user?.role === 'support';
|
|
17
|
+
* }
|
|
18
|
+
* ```
|
|
19
|
+
*
|
|
20
|
+
* The arguments of an agent function call are `[input, context]`: `getInput()` (also `switchToRpc().getData()`) and
|
|
21
|
+
* `getContext()`.
|
|
22
|
+
*/
|
|
23
|
+
export declare class AiExecutionContext extends ExecutionContextHost {
|
|
24
|
+
static create(context: ExecutionContext | ArgumentsHost): AiExecutionContext;
|
|
25
|
+
/** The arguments, validated against the function's `inputSchema`. */
|
|
26
|
+
getInput<T = any>(): T;
|
|
27
|
+
getContext<TRequest = any, TUser = any>(): AgentFunctionContext<TRequest, TUser>;
|
|
28
|
+
/** The agent function's name. */
|
|
29
|
+
getName(): string | undefined;
|
|
30
|
+
/** The HTTP platform's request the agent runs in (`caller.request`); `undefined` where there is none, as in a job. */
|
|
31
|
+
getRequest<TRequest = any>(): TRequest | undefined;
|
|
32
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { ExecutionContextHost } from '@nestjs/core/helpers/execution-context-host.js';
|
|
2
|
+
/**
|
|
3
|
+
* The execution context of an agent function call, for guards, interceptors and exception filters, as
|
|
4
|
+
* `McpExecutionContext` is for MCP calls:
|
|
5
|
+
*
|
|
6
|
+
* ```ts
|
|
7
|
+
* canActivate(context: ExecutionContext) {
|
|
8
|
+
* if (context.getType<AiContextType>() !== 'ai') {
|
|
9
|
+
* return true;
|
|
10
|
+
* }
|
|
11
|
+
* const call = AiExecutionContext.create(context);
|
|
12
|
+
* return call.getRequest()?.user?.role === 'support';
|
|
13
|
+
* }
|
|
14
|
+
* ```
|
|
15
|
+
*
|
|
16
|
+
* The arguments of an agent function call are `[input, context]`: `getInput()` (also `switchToRpc().getData()`) and
|
|
17
|
+
* `getContext()`.
|
|
18
|
+
*/
|
|
19
|
+
export class AiExecutionContext extends ExecutionContextHost {
|
|
20
|
+
static create(context) {
|
|
21
|
+
const host = context;
|
|
22
|
+
const ai = new AiExecutionContext(context.getArgs(), host.getClass?.(), host.getHandler?.());
|
|
23
|
+
ai.setType(context.getType());
|
|
24
|
+
return ai;
|
|
25
|
+
}
|
|
26
|
+
/** The arguments, validated against the function's `inputSchema`. */
|
|
27
|
+
getInput() {
|
|
28
|
+
return this.getArgByIndex(0);
|
|
29
|
+
}
|
|
30
|
+
getContext() {
|
|
31
|
+
return this.getArgByIndex(1);
|
|
32
|
+
}
|
|
33
|
+
/** The agent function's name. */
|
|
34
|
+
getName() {
|
|
35
|
+
return this.getContext()?.name;
|
|
36
|
+
}
|
|
37
|
+
/** The HTTP platform's request the agent runs in (`caller.request`); `undefined` where there is none, as in a job. */
|
|
38
|
+
getRequest() {
|
|
39
|
+
return this.getContext()?.request;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { ParamsFactory } from '@nestjs/core';
|
|
2
|
+
/**
|
|
3
|
+
* The parameters of an agent function. Pipes see the input as a `body` parameter (so `ValidationPipe` validates a DTO
|
|
4
|
+
* class) and the context as a `custom` one (which it skips). The numbers are `@nestjs/mcp`'s, and must stay so: a
|
|
5
|
+
* method that is an MCP tool too has its parameters recorded once, under the key both packages read, and either
|
|
6
|
+
* package's factory reads them.
|
|
7
|
+
*/
|
|
8
|
+
export declare enum AiParamtype {
|
|
9
|
+
INPUT = 3,
|
|
10
|
+
CONTEXT = 1001
|
|
11
|
+
}
|
|
12
|
+
export declare class AiParamsFactory implements ParamsFactory {
|
|
13
|
+
exchangeKeyForValue(type: number, data: unknown, args: unknown[]): unknown;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Records the two parameters of an agent function, `(input, context)`, as parameter metadata, where no parameter
|
|
17
|
+
* decorator took their place. `ExternalContextCreator` runs pipes only on parameters it has metadata for, and calls
|
|
18
|
+
* the method with those alone: custom decorators (`createParamDecorator()`) on other parameters keep theirs.
|
|
19
|
+
*/
|
|
20
|
+
export declare function recordHandlerParams(target: Function, methodName: string): void;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { RouteParamtypes } from '@nestjs/common/enums/route-paramtypes.enum.js';
|
|
2
|
+
import { AI_PARAMS_METADATA } from '../ai.constants.js';
|
|
3
|
+
/**
|
|
4
|
+
* The parameters of an agent function. Pipes see the input as a `body` parameter (so `ValidationPipe` validates a DTO
|
|
5
|
+
* class) and the context as a `custom` one (which it skips). The numbers are `@nestjs/mcp`'s, and must stay so: a
|
|
6
|
+
* method that is an MCP tool too has its parameters recorded once, under the key both packages read, and either
|
|
7
|
+
* package's factory reads them.
|
|
8
|
+
*/
|
|
9
|
+
export var AiParamtype;
|
|
10
|
+
(function (AiParamtype) {
|
|
11
|
+
AiParamtype[AiParamtype["INPUT"] = 3] = "INPUT";
|
|
12
|
+
AiParamtype[AiParamtype["CONTEXT"] = 1001] = "CONTEXT";
|
|
13
|
+
})(AiParamtype || (AiParamtype = {}));
|
|
14
|
+
export class AiParamsFactory {
|
|
15
|
+
exchangeKeyForValue(type, data, args) {
|
|
16
|
+
switch (type) {
|
|
17
|
+
case AiParamtype.INPUT: {
|
|
18
|
+
const input = args[0];
|
|
19
|
+
return typeof data === 'string' && input ? input[data] : input;
|
|
20
|
+
}
|
|
21
|
+
case AiParamtype.CONTEXT:
|
|
22
|
+
return args[1];
|
|
23
|
+
default:
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Records the two parameters of an agent function, `(input, context)`, as parameter metadata, where no parameter
|
|
30
|
+
* decorator took their place. `ExternalContextCreator` runs pipes only on parameters it has metadata for, and calls
|
|
31
|
+
* the method with those alone: custom decorators (`createParamDecorator()`) on other parameters keep theirs.
|
|
32
|
+
*/
|
|
33
|
+
export function recordHandlerParams(target, methodName) {
|
|
34
|
+
const existing = Reflect.getMetadata(AI_PARAMS_METADATA, target, methodName) ?? {};
|
|
35
|
+
const taken = new Set(Object.values(existing).map((param) => param.index));
|
|
36
|
+
if (taken.has(0) && taken.has(1)) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
Reflect.defineMetadata(AI_PARAMS_METADATA, {
|
|
40
|
+
...existing,
|
|
41
|
+
...(!taken.has(0) && { [`${AiParamtype.INPUT}:0`]: { index: 0, data: undefined, pipes: [] } }),
|
|
42
|
+
...(!taken.has(1) && { [`${AiParamtype.CONTEXT}:1`]: { index: 1, data: undefined, pipes: [] } }),
|
|
43
|
+
}, target, methodName);
|
|
44
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { AgentFunctionOptions } from '../interfaces/agent-function.interface.js';
|
|
2
|
+
/**
|
|
3
|
+
* Makes the method an agent function: one the application's agents may call, which a call lists in `tools`, and the
|
|
4
|
+
* model is offered as a tool. It is called as `(input, context)`: `input` is the arguments, validated against
|
|
5
|
+
* `inputSchema`, and `context` the `AgentFunctionContext`. Guards, interceptors, pipes and exception filters run
|
|
6
|
+
* around it, with the context type `'ai'` (`AiExecutionContext` reads the call).
|
|
7
|
+
*
|
|
8
|
+
* ```ts
|
|
9
|
+
* @AgentFunction({
|
|
10
|
+
* name: 'find-order',
|
|
11
|
+
* description: 'Find an order by its id',
|
|
12
|
+
* inputSchema: z.object({ orderId: z.string() }),
|
|
13
|
+
* })
|
|
14
|
+
* find({ orderId }: { orderId: string }, ctx: AgentFunctionContext) {
|
|
15
|
+
* return this.orders.find(orderId);
|
|
16
|
+
* }
|
|
17
|
+
* ```
|
|
18
|
+
*
|
|
19
|
+
* A method that MCP clients call too also carries `@nestjs/mcp`'s `@Tool()`, with options of its own: neither
|
|
20
|
+
* decorator reads the other's.
|
|
21
|
+
*/
|
|
22
|
+
export declare function AgentFunction(options?: AgentFunctionOptions): MethodDecorator;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { AGENT_FUNCTION_METADATA } from '../ai.constants.js';
|
|
2
|
+
/**
|
|
3
|
+
* Makes the method an agent function: one the application's agents may call, which a call lists in `tools`, and the
|
|
4
|
+
* model is offered as a tool. It is called as `(input, context)`: `input` is the arguments, validated against
|
|
5
|
+
* `inputSchema`, and `context` the `AgentFunctionContext`. Guards, interceptors, pipes and exception filters run
|
|
6
|
+
* around it, with the context type `'ai'` (`AiExecutionContext` reads the call).
|
|
7
|
+
*
|
|
8
|
+
* ```ts
|
|
9
|
+
* @AgentFunction({
|
|
10
|
+
* name: 'find-order',
|
|
11
|
+
* description: 'Find an order by its id',
|
|
12
|
+
* inputSchema: z.object({ orderId: z.string() }),
|
|
13
|
+
* })
|
|
14
|
+
* find({ orderId }: { orderId: string }, ctx: AgentFunctionContext) {
|
|
15
|
+
* return this.orders.find(orderId);
|
|
16
|
+
* }
|
|
17
|
+
* ```
|
|
18
|
+
*
|
|
19
|
+
* A method that MCP clients call too also carries `@nestjs/mcp`'s `@Tool()`, with options of its own: neither
|
|
20
|
+
* decorator reads the other's.
|
|
21
|
+
*/
|
|
22
|
+
export function AgentFunction(options = {}) {
|
|
23
|
+
const metadata = { ...options };
|
|
24
|
+
return (target, key, descriptor) => {
|
|
25
|
+
const method = descriptor.value;
|
|
26
|
+
const where = `${typeof target === 'function' ? target.name : target.constructor.name}.${String(key)}`;
|
|
27
|
+
if (typeof method !== 'function') {
|
|
28
|
+
throw new TypeError(`@AgentFunction() goes on a method, and ${where} is not one.`);
|
|
29
|
+
}
|
|
30
|
+
if (Reflect.hasOwnMetadata(AGENT_FUNCTION_METADATA, method)) {
|
|
31
|
+
throw new TypeError(`${where} has @AgentFunction() twice: give it one, with all its options.`);
|
|
32
|
+
}
|
|
33
|
+
Reflect.defineMetadata(AGENT_FUNCTION_METADATA, metadata, method);
|
|
34
|
+
return descriptor;
|
|
35
|
+
};
|
|
36
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Injects the configured `LanguageModel` called `name` (default `'default'`), for code that calls `ai` itself.
|
|
3
|
+
* `AiService` calls get retries, timeouts, fallback and usage events; a direct `ai` call gets none of them. A name
|
|
4
|
+
* the module does not configure fails at startup.
|
|
5
|
+
*
|
|
6
|
+
* ```ts
|
|
7
|
+
* constructor(@InjectModel('fast') private readonly model: LanguageModel) {}
|
|
8
|
+
* ```
|
|
9
|
+
*/
|
|
10
|
+
export declare function InjectModel(name?: string): PropertyDecorator & ParameterDecorator;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Inject } from '@nestjs/common';
|
|
2
|
+
import { DEFAULT_MODEL_NAME, getModelToken } from '../ai.constants.js';
|
|
3
|
+
import { recordModelRequest } from '../models/model-requests.js';
|
|
4
|
+
/**
|
|
5
|
+
* Injects the configured `LanguageModel` called `name` (default `'default'`), for code that calls `ai` itself.
|
|
6
|
+
* `AiService` calls get retries, timeouts, fallback and usage events; a direct `ai` call gets none of them. A name
|
|
7
|
+
* the module does not configure fails at startup.
|
|
8
|
+
*
|
|
9
|
+
* ```ts
|
|
10
|
+
* constructor(@InjectModel('fast') private readonly model: LanguageModel) {}
|
|
11
|
+
* ```
|
|
12
|
+
*/
|
|
13
|
+
export function InjectModel(name = DEFAULT_MODEL_NAME) {
|
|
14
|
+
recordModelRequest(name);
|
|
15
|
+
return Inject(getModelToken(name));
|
|
16
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { AiModelInfo } from '../interfaces/ai-result.interface.js';
|
|
2
|
+
import type { AiUsage } from '../interfaces/ai-usage.interface.js';
|
|
3
|
+
/** The base class of the package's errors. */
|
|
4
|
+
export declare class AiError extends Error {
|
|
5
|
+
constructor(message: string, options?: ErrorOptions);
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* A model name, an agent function or an option the package cannot use. Thrown at startup or when the call is made.
|
|
9
|
+
*/
|
|
10
|
+
export declare class AiConfigurationError extends AiError {
|
|
11
|
+
}
|
|
12
|
+
/** One failed attempt of a model call. */
|
|
13
|
+
export interface AiAttemptFailure {
|
|
14
|
+
model: AiModelInfo;
|
|
15
|
+
attempt: number;
|
|
16
|
+
error: unknown;
|
|
17
|
+
}
|
|
18
|
+
/** Every attempt of a model call failed, on every model tried. `cause` is the last error. */
|
|
19
|
+
export declare class AiModelError extends AiError {
|
|
20
|
+
readonly attempts: AiAttemptFailure[];
|
|
21
|
+
constructor(attempts: AiAttemptFailure[]);
|
|
22
|
+
}
|
|
23
|
+
/** The call would have started a model call over its `budget`. */
|
|
24
|
+
export declare class AiBudgetExceededError extends AiError {
|
|
25
|
+
readonly usage: AiUsage;
|
|
26
|
+
readonly cost: number | undefined;
|
|
27
|
+
readonly limit: {
|
|
28
|
+
maxTokens?: number;
|
|
29
|
+
maxCost?: number;
|
|
30
|
+
};
|
|
31
|
+
constructor(usage: AiUsage, cost: number | undefined, limit: {
|
|
32
|
+
maxTokens?: number;
|
|
33
|
+
maxCost?: number;
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
/** The model's answer to `generateObject()` is not JSON, or does not match the schema. */
|
|
37
|
+
export declare class AiOutputError extends AiError {
|
|
38
|
+
/** What the model answered. */
|
|
39
|
+
readonly text: string;
|
|
40
|
+
constructor(message: string,
|
|
41
|
+
/** What the model answered. */
|
|
42
|
+
text: string, options?: ErrorOptions);
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* What the model is told about a tool call that failed: a guard's refusal (`Forbidden resource`), an
|
|
46
|
+
* `HttpException`'s message, `Internal server error` for anything else. `toolName` is the agent function's name.
|
|
47
|
+
*/
|
|
48
|
+
export declare class AiToolError extends AiError {
|
|
49
|
+
readonly toolName: string;
|
|
50
|
+
constructor(message: string, toolName: string, options?: ErrorOptions);
|
|
51
|
+
/** The model reads `String(error)`: the message alone, not the class name. */
|
|
52
|
+
toString(): string;
|
|
53
|
+
}
|
|
54
|
+
export declare function describeModel(model: AiModelInfo): string;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/** The base class of the package's errors. */
|
|
2
|
+
export class AiError extends Error {
|
|
3
|
+
constructor(message, options) {
|
|
4
|
+
super(message, options);
|
|
5
|
+
this.name = new.target.name;
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* A model name, an agent function or an option the package cannot use. Thrown at startup or when the call is made.
|
|
10
|
+
*/
|
|
11
|
+
export class AiConfigurationError extends AiError {
|
|
12
|
+
}
|
|
13
|
+
/** Every attempt of a model call failed, on every model tried. `cause` is the last error. */
|
|
14
|
+
export class AiModelError extends AiError {
|
|
15
|
+
constructor(attempts) {
|
|
16
|
+
const last = attempts[attempts.length - 1];
|
|
17
|
+
const tried = [...new Set(attempts.map((attempt) => describeModel(attempt.model)))].join(', ');
|
|
18
|
+
super(`The model call failed after ${attempts.length} attempt(s) (${tried}): ${messageOf(last?.error)}`, {
|
|
19
|
+
cause: last?.error,
|
|
20
|
+
});
|
|
21
|
+
this.attempts = attempts;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
/** The call would have started a model call over its `budget`. */
|
|
25
|
+
export class AiBudgetExceededError extends AiError {
|
|
26
|
+
constructor(usage, cost, limit) {
|
|
27
|
+
const spent = limit.maxCost !== undefined && cost !== undefined ? `cost ${cost}` : `${usage.totalTokens} tokens`;
|
|
28
|
+
super(`The call's budget is spent (${spent}), so it stopped before the next model call`);
|
|
29
|
+
this.usage = usage;
|
|
30
|
+
this.cost = cost;
|
|
31
|
+
this.limit = limit;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/** The model's answer to `generateObject()` is not JSON, or does not match the schema. */
|
|
35
|
+
export class AiOutputError extends AiError {
|
|
36
|
+
constructor(message,
|
|
37
|
+
/** What the model answered. */
|
|
38
|
+
text, options) {
|
|
39
|
+
super(message, options);
|
|
40
|
+
this.text = text;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* What the model is told about a tool call that failed: a guard's refusal (`Forbidden resource`), an
|
|
45
|
+
* `HttpException`'s message, `Internal server error` for anything else. `toolName` is the agent function's name.
|
|
46
|
+
*/
|
|
47
|
+
export class AiToolError extends AiError {
|
|
48
|
+
constructor(message, toolName, options) {
|
|
49
|
+
super(message, options);
|
|
50
|
+
this.toolName = toolName;
|
|
51
|
+
}
|
|
52
|
+
/** The model reads `String(error)`: the message alone, not the class name. */
|
|
53
|
+
toString() {
|
|
54
|
+
return this.message;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
export function describeModel(model) {
|
|
58
|
+
return model.name ?? `${model.provider}:${model.modelId}`;
|
|
59
|
+
}
|
|
60
|
+
function messageOf(error) {
|
|
61
|
+
return error instanceof Error ? error.message : String(error);
|
|
62
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './ai.errors.js';
|