@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
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { ModuleRef } from '@nestjs/core';
|
|
2
|
+
import { WorkflowClient, WorkflowSignal, type WorkflowContext } from '@nestjs/workflows';
|
|
3
|
+
import type { ModelMessage } from 'ai';
|
|
4
|
+
import type { AiStep } from '../interfaces/ai-result.interface.js';
|
|
5
|
+
import type { AiUsage } from '../interfaces/ai-usage.interface.js';
|
|
6
|
+
import type { AiAgentRunOptions, AiAgentRunResult, AiPendingApproval, AiToolApproval } from './ai-agent.interfaces.js';
|
|
7
|
+
/** The signal a tool call that waits for approval takes, keyed by the call (`AiPendingApproval.key`). */
|
|
8
|
+
export declare const TOOL_APPROVAL: WorkflowSignal<AiToolApproval>;
|
|
9
|
+
/** A run's options, resolved once and stored with it: tools as names. Internal. */
|
|
10
|
+
export interface AgentSettings extends Omit<AiAgentRunOptions, 'tools' | 'prompt' | 'messages'> {
|
|
11
|
+
tools: string[];
|
|
12
|
+
approve: string[];
|
|
13
|
+
maxSteps: number;
|
|
14
|
+
stepsPerInstance: number;
|
|
15
|
+
}
|
|
16
|
+
/** Where a run is: what each instance replays to, and what a child instance starts from. Internal. */
|
|
17
|
+
export interface AgentState {
|
|
18
|
+
/** The instance `run()` was called in: approval keys name it. */
|
|
19
|
+
runId: string;
|
|
20
|
+
index: number;
|
|
21
|
+
messages: ModelMessage[];
|
|
22
|
+
responseMessages: ModelMessage[];
|
|
23
|
+
steps: AiStep[];
|
|
24
|
+
usage: AiUsage;
|
|
25
|
+
cost?: number;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* The agent loop as a durable workflow: inside a `@Workflow()`'s `run()`, each model call is a step and each tool
|
|
29
|
+
* call (an agent function's) another, so that a restart resumes where the run was, without calling a model or an agent
|
|
30
|
+
* function again for what is journaled. Calls of the functions named in `approve` wait for a person
|
|
31
|
+
* (`waitForSignal()`), for days if need be. After `stepsPerInstance` model calls the run goes on in a child workflow.
|
|
32
|
+
*
|
|
33
|
+
* ```ts
|
|
34
|
+
* @Workflow('refund-request')
|
|
35
|
+
* export class RefundRequest {
|
|
36
|
+
* constructor(private readonly agents: AiAgents) {}
|
|
37
|
+
*
|
|
38
|
+
* run(ctx: WorkflowContext, { email, customerId }: RefundInput) {
|
|
39
|
+
* return this.agents.run(ctx, {
|
|
40
|
+
* prompt: email,
|
|
41
|
+
* tools: ['find-order', 'refund'],
|
|
42
|
+
* approve: ['refund'],
|
|
43
|
+
* caller: { user: { id: customerId } },
|
|
44
|
+
* });
|
|
45
|
+
* }
|
|
46
|
+
* }
|
|
47
|
+
* ```
|
|
48
|
+
*/
|
|
49
|
+
export declare class AiAgents {
|
|
50
|
+
private readonly moduleRef;
|
|
51
|
+
private readonly client;
|
|
52
|
+
constructor(moduleRef: ModuleRef, client: WorkflowClient);
|
|
53
|
+
/** Runs the agent loop in the workflow `ctx` is the context of; resolves with its result. */
|
|
54
|
+
run(ctx: WorkflowContext, options: AiAgentRunOptions): Promise<AiAgentRunResult>;
|
|
55
|
+
/** Approves a tool call waiting for it; `key` is `AiPendingApproval.key`. The first answer for a call counts. */
|
|
56
|
+
approve(key: string, { by }?: {
|
|
57
|
+
by?: unknown;
|
|
58
|
+
}): Promise<void>;
|
|
59
|
+
/** Rejects a tool call waiting for approval: the agent function does not run, and the model is told `reason`. */
|
|
60
|
+
reject(key: string, { reason, by }?: {
|
|
61
|
+
reason?: string;
|
|
62
|
+
by?: unknown;
|
|
63
|
+
}): Promise<void>;
|
|
64
|
+
/** The tool calls of run `runId` that wait for approval now, in it or in the child instances it goes on in. */
|
|
65
|
+
pendingApprovals(runId: string): Promise<AiPendingApproval[]>;
|
|
66
|
+
/** @internal Runs from `state` on: `run()`'s instance, and each child instance a run goes on in. */
|
|
67
|
+
resume(ctx: WorkflowContext, settings: AgentSettings, state: AgentState): Promise<AiAgentRunResult>;
|
|
68
|
+
/**
|
|
69
|
+
* The step's valid tool calls (`ai` answered the invalid ones), one durable step each, after their approval when
|
|
70
|
+
* they need one. An agent function's failure is a result the model reads, not the step's failure: the step is
|
|
71
|
+
* retried only after a crash, with the same `idempotencyKey` in the function's context.
|
|
72
|
+
*/
|
|
73
|
+
private runTools;
|
|
74
|
+
private settings;
|
|
75
|
+
/** The module's `defaults`, which a run's `maxSteps` and `budget` fall back to, as a call's do. */
|
|
76
|
+
private defaults;
|
|
77
|
+
private answer;
|
|
78
|
+
private ai;
|
|
79
|
+
private tools;
|
|
80
|
+
}
|
|
81
|
+
/** The workflow a run goes on in after `stepsPerInstance` model calls (`AiAgentSegment`). */
|
|
82
|
+
export declare const AGENT_WORKFLOW = "nestjs-ai.agent";
|
|
83
|
+
export declare function approvalKey(runId: string, toolCallId: string): string;
|
|
@@ -0,0 +1,277 @@
|
|
|
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
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
8
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
9
|
+
};
|
|
10
|
+
import { Injectable } from '@nestjs/common';
|
|
11
|
+
import { ModuleRef } from '@nestjs/core';
|
|
12
|
+
import { NonRetryableStepError, WorkflowClient, WorkflowSignal } from '@nestjs/workflows';
|
|
13
|
+
import { AI_MODULE_OPTIONS } from '../ai.constants.js';
|
|
14
|
+
import { AiConfigurationError, AiToolError } from '../errors/ai.errors.js';
|
|
15
|
+
import { assertWithinBudget } from '../runtime/agent-loop.js';
|
|
16
|
+
import { AiService } from '../services/ai.service.js';
|
|
17
|
+
import { AiTools } from '../tools/ai-tools.service.js';
|
|
18
|
+
import { INTERNAL_ERROR_MESSAGE } from '../utils/errors.util.js';
|
|
19
|
+
import { addCost, addUsage, ZERO_USAGE } from '../utils/usage.util.js';
|
|
20
|
+
/** The signal a tool call that waits for approval takes, keyed by the call (`AiPendingApproval.key`). */
|
|
21
|
+
export const TOOL_APPROVAL = new WorkflowSignal('nestjs-ai.tool-approval');
|
|
22
|
+
const DEFAULT_MAX_STEPS = 25;
|
|
23
|
+
const DEFAULT_STEPS_PER_INSTANCE = 10;
|
|
24
|
+
/**
|
|
25
|
+
* The agent loop as a durable workflow: inside a `@Workflow()`'s `run()`, each model call is a step and each tool
|
|
26
|
+
* call (an agent function's) another, so that a restart resumes where the run was, without calling a model or an agent
|
|
27
|
+
* function again for what is journaled. Calls of the functions named in `approve` wait for a person
|
|
28
|
+
* (`waitForSignal()`), for days if need be. After `stepsPerInstance` model calls the run goes on in a child workflow.
|
|
29
|
+
*
|
|
30
|
+
* ```ts
|
|
31
|
+
* @Workflow('refund-request')
|
|
32
|
+
* export class RefundRequest {
|
|
33
|
+
* constructor(private readonly agents: AiAgents) {}
|
|
34
|
+
*
|
|
35
|
+
* run(ctx: WorkflowContext, { email, customerId }: RefundInput) {
|
|
36
|
+
* return this.agents.run(ctx, {
|
|
37
|
+
* prompt: email,
|
|
38
|
+
* tools: ['find-order', 'refund'],
|
|
39
|
+
* approve: ['refund'],
|
|
40
|
+
* caller: { user: { id: customerId } },
|
|
41
|
+
* });
|
|
42
|
+
* }
|
|
43
|
+
* }
|
|
44
|
+
* ```
|
|
45
|
+
*/
|
|
46
|
+
let AiAgents = class AiAgents {
|
|
47
|
+
constructor(moduleRef, client) {
|
|
48
|
+
this.moduleRef = moduleRef;
|
|
49
|
+
this.client = client;
|
|
50
|
+
}
|
|
51
|
+
/** Runs the agent loop in the workflow `ctx` is the context of; resolves with its result. */
|
|
52
|
+
run(ctx, options) {
|
|
53
|
+
const settings = this.settings(options);
|
|
54
|
+
const messages = initialMessages(options);
|
|
55
|
+
return this.resume(ctx, settings, {
|
|
56
|
+
runId: ctx.workflowId,
|
|
57
|
+
index: 0,
|
|
58
|
+
messages,
|
|
59
|
+
responseMessages: [],
|
|
60
|
+
steps: [],
|
|
61
|
+
usage: ZERO_USAGE,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
/** Approves a tool call waiting for it; `key` is `AiPendingApproval.key`. The first answer for a call counts. */
|
|
65
|
+
async approve(key, { by } = {}) {
|
|
66
|
+
await this.answer(key, { approved: true, ...(by !== undefined && { by }) });
|
|
67
|
+
}
|
|
68
|
+
/** Rejects a tool call waiting for approval: the agent function does not run, and the model is told `reason`. */
|
|
69
|
+
async reject(key, { reason, by } = {}) {
|
|
70
|
+
await this.answer(key, { approved: false, ...(reason !== undefined && { reason }), ...(by !== undefined && { by }) });
|
|
71
|
+
}
|
|
72
|
+
/** The tool calls of run `runId` that wait for approval now, in it or in the child instances it goes on in. */
|
|
73
|
+
async pendingApprovals(runId) {
|
|
74
|
+
const pending = [];
|
|
75
|
+
const visit = async (id) => {
|
|
76
|
+
const details = await this.client.getStatus(id, { children: true });
|
|
77
|
+
if (!details) {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
pending.push(...(details.customStatus?.pendingApprovals ?? []));
|
|
81
|
+
for (const child of details.children ?? []) {
|
|
82
|
+
if (child.status !== 'completed' && child.status !== 'failed' && child.status !== 'cancelled') {
|
|
83
|
+
await visit(child.id);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
await visit(runId);
|
|
88
|
+
return pending;
|
|
89
|
+
}
|
|
90
|
+
/** @internal Runs from `state` on: `run()`'s instance, and each child instance a run goes on in. */
|
|
91
|
+
async resume(ctx, settings, state) {
|
|
92
|
+
const last = state.index + settings.stepsPerInstance;
|
|
93
|
+
while (state.index < settings.maxSteps) {
|
|
94
|
+
if (state.index >= last) {
|
|
95
|
+
const result = await ctx.executeChild(AGENT_WORKFLOW, { settings, state }, { id: `${state.runId}:from-${state.index}` });
|
|
96
|
+
return result;
|
|
97
|
+
}
|
|
98
|
+
assertWithinBudget(settings, state.usage, state.cost);
|
|
99
|
+
const index = state.index;
|
|
100
|
+
const { outcome, attempts } = await ctx.step(`model-${index}`, (step) => this.ai()
|
|
101
|
+
.callModel(callOptions(settings), { index, messages: state.messages, signal: step.signal })
|
|
102
|
+
.catch((error) => {
|
|
103
|
+
// A mistake in the options fails the same way every time: not worth a durable retry.
|
|
104
|
+
throw error instanceof AiConfigurationError ? new NonRetryableStepError(error.message) : error;
|
|
105
|
+
}));
|
|
106
|
+
const step = outcome.step;
|
|
107
|
+
const results = await this.runTools(ctx, settings, state, index, step);
|
|
108
|
+
state.messages.push(...outcome.responseMessages);
|
|
109
|
+
state.responseMessages.push(...outcome.responseMessages);
|
|
110
|
+
if (results.length > 0) {
|
|
111
|
+
const message = toolMessage(results);
|
|
112
|
+
state.messages.push(message);
|
|
113
|
+
state.responseMessages.push(message);
|
|
114
|
+
}
|
|
115
|
+
state.steps.push({ ...step, toolResults: [...step.toolResults, ...results], attempts });
|
|
116
|
+
state.usage = addUsage(state.usage, step.usage);
|
|
117
|
+
state.cost = addCost(state.cost, step.cost);
|
|
118
|
+
state.index++;
|
|
119
|
+
if (step.finishReason !== 'tool-calls' || step.toolCalls.length === 0) {
|
|
120
|
+
break;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return resultOf(state);
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* The step's valid tool calls (`ai` answered the invalid ones), one durable step each, after their approval when
|
|
127
|
+
* they need one. An agent function's failure is a result the model reads, not the step's failure: the step is
|
|
128
|
+
* retried only after a crash, with the same `idempotencyKey` in the function's context.
|
|
129
|
+
*/
|
|
130
|
+
async runTools(ctx, settings, state, index, step) {
|
|
131
|
+
const answered = new Set(step.toolResults.map((result) => result.toolCallId));
|
|
132
|
+
const calls = step.toolCalls.filter((call) => !answered.has(call.toolCallId));
|
|
133
|
+
// Every call of the step that needs approval is listed at once; they are answered in any order.
|
|
134
|
+
const waiting = calls
|
|
135
|
+
.filter((call) => settings.approve.includes(call.toolName))
|
|
136
|
+
.map((call) => ({
|
|
137
|
+
key: approvalKey(state.runId, call.toolCallId),
|
|
138
|
+
runId: state.runId,
|
|
139
|
+
toolCallId: call.toolCallId,
|
|
140
|
+
toolName: call.toolName,
|
|
141
|
+
input: call.input,
|
|
142
|
+
}));
|
|
143
|
+
if (waiting.length > 0) {
|
|
144
|
+
ctx.setStatus({ pendingApprovals: waiting });
|
|
145
|
+
}
|
|
146
|
+
const results = [];
|
|
147
|
+
for (const call of calls) {
|
|
148
|
+
const approval = waiting.find((pending) => pending.toolCallId === call.toolCallId);
|
|
149
|
+
if (approval) {
|
|
150
|
+
const decision = await ctx.waitForSignal(`approval-${index}-${call.toolCallId}`, TOOL_APPROVAL, {
|
|
151
|
+
key: approval.key,
|
|
152
|
+
...(settings.approvalTimeout !== undefined && { timeout: settings.approvalTimeout }),
|
|
153
|
+
});
|
|
154
|
+
waiting.splice(waiting.indexOf(approval), 1);
|
|
155
|
+
ctx.setStatus(waiting.length > 0 ? { pendingApprovals: waiting } : null);
|
|
156
|
+
if (!decision?.approved) {
|
|
157
|
+
const error = decision ? `The call was rejected${decision.reason ? `: ${decision.reason}` : ''}` : 'No one approved the call in time';
|
|
158
|
+
results.push({ ...call, error });
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
results.push((await ctx.step(`tool-${index}-${call.toolCallId}`, async (toolStep) => {
|
|
163
|
+
const tools = this.tools();
|
|
164
|
+
const entry = tools.tools.get(call.toolName);
|
|
165
|
+
if (!entry) {
|
|
166
|
+
return { ...call, error: `There is no tool named "${call.toolName}"` };
|
|
167
|
+
}
|
|
168
|
+
try {
|
|
169
|
+
const output = await tools.call(entry, call.input, settings.caller ?? {}, {
|
|
170
|
+
toolCallId: call.toolCallId,
|
|
171
|
+
messages: state.messages,
|
|
172
|
+
abortSignal: toolStep.signal,
|
|
173
|
+
idempotencyKey: toolStep.idempotencyKey,
|
|
174
|
+
});
|
|
175
|
+
return { ...call, output };
|
|
176
|
+
}
|
|
177
|
+
catch (error) {
|
|
178
|
+
return { ...call, error: error instanceof AiToolError ? error.message : INTERNAL_ERROR_MESSAGE };
|
|
179
|
+
}
|
|
180
|
+
})));
|
|
181
|
+
}
|
|
182
|
+
return results;
|
|
183
|
+
}
|
|
184
|
+
settings(options) {
|
|
185
|
+
if (typeof options !== 'object' || options === null) {
|
|
186
|
+
throw new AiConfigurationError('AiAgents.run() takes options: { prompt } or { messages }, and the rest.');
|
|
187
|
+
}
|
|
188
|
+
const { tools, prompt: _prompt, messages: _messages, ...rest } = options;
|
|
189
|
+
const names = tools?.length ? this.tools().resolve(tools).map((entry) => entry.name) : [];
|
|
190
|
+
const approve = options.approve ?? [];
|
|
191
|
+
for (const name of approve) {
|
|
192
|
+
if (!names.includes(name)) {
|
|
193
|
+
throw new AiConfigurationError(`\`approve\` names "${name}", which is not one of the run's \`tools\`.`);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
const defaults = this.defaults();
|
|
197
|
+
const budget = options.budget ?? defaults.budget;
|
|
198
|
+
return {
|
|
199
|
+
...rest,
|
|
200
|
+
tools: names,
|
|
201
|
+
approve,
|
|
202
|
+
maxSteps: options.maxSteps ?? defaults.maxSteps ?? DEFAULT_MAX_STEPS,
|
|
203
|
+
stepsPerInstance: options.stepsPerInstance ?? DEFAULT_STEPS_PER_INSTANCE,
|
|
204
|
+
...(budget && { budget }),
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
/** The module's `defaults`, which a run's `maxSteps` and `budget` fall back to, as a call's do. */
|
|
208
|
+
defaults() {
|
|
209
|
+
return this.moduleRef.get(AI_MODULE_OPTIONS, { strict: false }).defaults ?? {};
|
|
210
|
+
}
|
|
211
|
+
answer(key, decision) {
|
|
212
|
+
return this.client.signal(TOOL_APPROVAL, decision, { key, id: `${key}:answer` });
|
|
213
|
+
}
|
|
214
|
+
ai() {
|
|
215
|
+
return this.moduleRef.get(AiService, { strict: false });
|
|
216
|
+
}
|
|
217
|
+
tools() {
|
|
218
|
+
return this.moduleRef.get(AiTools, { strict: false });
|
|
219
|
+
}
|
|
220
|
+
};
|
|
221
|
+
AiAgents = __decorate([
|
|
222
|
+
Injectable(),
|
|
223
|
+
__metadata("design:paramtypes", [ModuleRef,
|
|
224
|
+
WorkflowClient])
|
|
225
|
+
], AiAgents);
|
|
226
|
+
export { AiAgents };
|
|
227
|
+
/** The workflow a run goes on in after `stepsPerInstance` model calls (`AiAgentSegment`). */
|
|
228
|
+
export const AGENT_WORKFLOW = 'nestjs-ai.agent';
|
|
229
|
+
export function approvalKey(runId, toolCallId) {
|
|
230
|
+
return `${runId}:${toolCallId}`;
|
|
231
|
+
}
|
|
232
|
+
function initialMessages(options) {
|
|
233
|
+
if (options.prompt !== undefined && options.messages !== undefined) {
|
|
234
|
+
throw new AiConfigurationError('Pass `prompt` or `messages`, not both.');
|
|
235
|
+
}
|
|
236
|
+
if (typeof options.prompt === 'string') {
|
|
237
|
+
return [{ role: 'user', content: options.prompt }];
|
|
238
|
+
}
|
|
239
|
+
if (Array.isArray(options.messages) && options.messages.length > 0) {
|
|
240
|
+
return [...options.messages];
|
|
241
|
+
}
|
|
242
|
+
throw new AiConfigurationError('Pass `prompt` (a string) or `messages` (a non-empty array of model messages).');
|
|
243
|
+
}
|
|
244
|
+
function callOptions(settings) {
|
|
245
|
+
const { approve: _approve, approvalTimeout: _timeout, stepsPerInstance: _steps, maxSteps: _max, budget: _budget, caller, tools, ...rest } = settings;
|
|
246
|
+
return { ...rest, tools, caller: { ...(caller?.user !== undefined && { user: caller.user }), ...(caller?.scopes && { scopes: caller.scopes }) } };
|
|
247
|
+
}
|
|
248
|
+
/** The results of tool calls as the tool message the model reads next. */
|
|
249
|
+
function toolMessage(results) {
|
|
250
|
+
return {
|
|
251
|
+
role: 'tool',
|
|
252
|
+
content: results.map((result) => ({
|
|
253
|
+
type: 'tool-result',
|
|
254
|
+
toolCallId: result.toolCallId,
|
|
255
|
+
toolName: result.toolName,
|
|
256
|
+
output: result.error !== undefined
|
|
257
|
+
? { type: 'error-text', value: result.error }
|
|
258
|
+
: typeof result.output === 'string'
|
|
259
|
+
? { type: 'text', value: result.output }
|
|
260
|
+
: { type: 'json', value: (result.output ?? null) },
|
|
261
|
+
})),
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
function resultOf(state) {
|
|
265
|
+
const final = state.steps[state.steps.length - 1];
|
|
266
|
+
return {
|
|
267
|
+
text: final.text,
|
|
268
|
+
finishReason: final.finishReason,
|
|
269
|
+
steps: state.steps,
|
|
270
|
+
usage: state.usage,
|
|
271
|
+
...(state.cost !== undefined && { cost: state.cost }),
|
|
272
|
+
toolCalls: state.steps.flatMap((step) => step.toolCalls),
|
|
273
|
+
toolResults: state.steps.flatMap((step) => step.toolResults),
|
|
274
|
+
responseMessages: state.responseMessages,
|
|
275
|
+
model: final.model,
|
|
276
|
+
};
|
|
277
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
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 { AiAgentSegment } from './ai-agent.workflow.js';
|
|
9
|
+
import { AiAgents } from './ai-agents.service.js';
|
|
10
|
+
/**
|
|
11
|
+
* Durable agent runs on `@nestjs/workflows`: import it next to `AiModule` and `WorkflowsModule`, and inject
|
|
12
|
+
* `AiAgents` into your `@Workflow()` classes.
|
|
13
|
+
*/
|
|
14
|
+
let AiWorkflowsModule = class AiWorkflowsModule {
|
|
15
|
+
};
|
|
16
|
+
AiWorkflowsModule = __decorate([
|
|
17
|
+
Module({
|
|
18
|
+
providers: [AiAgents, AiAgentSegment],
|
|
19
|
+
exports: [AiAgents],
|
|
20
|
+
})
|
|
21
|
+
], AiWorkflowsModule);
|
|
22
|
+
export { AiWorkflowsModule };
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@nestjs/ai/workflows`: the agent loop as a durable workflow, on `@nestjs/workflows`.
|
|
3
|
+
*/
|
|
4
|
+
export { AiWorkflowsModule } from './ai-workflows.module.js';
|
|
5
|
+
export { AiAgents, TOOL_APPROVAL } from './ai-agents.service.js';
|
|
6
|
+
export type { AiAgentCaller, AiAgentRunOptions, AiAgentRunResult, AiPendingApproval, AiToolApproval, } from './ai-agent.interfaces.js';
|
package/package.json
CHANGED
|
@@ -1,6 +1,102 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nestjs/ai",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Nest - modern, fast, powerful node.js web framework (@ai)",
|
|
5
|
+
"author": "Kamil Mysliwiec",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"url": "https://github.com/nestjs/ai#readme",
|
|
8
|
+
"type": "module",
|
|
9
|
+
"main": "./dist/index.js",
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"import": "./dist/index.js",
|
|
15
|
+
"default": "./dist/index.js"
|
|
16
|
+
},
|
|
17
|
+
"./testing": {
|
|
18
|
+
"types": "./dist/testing/index.d.ts",
|
|
19
|
+
"import": "./dist/testing/index.js",
|
|
20
|
+
"default": "./dist/testing/index.js"
|
|
21
|
+
},
|
|
22
|
+
"./workflows": {
|
|
23
|
+
"types": "./dist/workflows/index.d.ts",
|
|
24
|
+
"import": "./dist/workflows/index.js",
|
|
25
|
+
"default": "./dist/workflows/index.js"
|
|
26
|
+
},
|
|
27
|
+
"./package.json": "./package.json"
|
|
28
|
+
},
|
|
29
|
+
"engines": {
|
|
30
|
+
"node": ">=22"
|
|
31
|
+
},
|
|
32
|
+
"scripts": {
|
|
33
|
+
"build": "rm -rf dist && tsc -p tsconfig.build.json",
|
|
34
|
+
"format": "prettier --write \"**/*.ts\"",
|
|
35
|
+
"lint": "oxlint lib/",
|
|
36
|
+
"prepublish:npm": "npm run build",
|
|
37
|
+
"publish:npm": "npm publish --access public",
|
|
38
|
+
"prepublish:next": "npm run build",
|
|
39
|
+
"publish:next": "npm publish --access public --tag next",
|
|
40
|
+
"test:e2e": "vitest run",
|
|
41
|
+
"test:e2e:dev": "vitest",
|
|
42
|
+
"prerelease": "npm run build",
|
|
43
|
+
"release": "release-it",
|
|
44
|
+
"prepare": "husky"
|
|
45
|
+
},
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"@ai-sdk/anthropic": "4.0.96",
|
|
48
|
+
"@commitlint/cli": "21.2.4",
|
|
49
|
+
"@commitlint/config-angular": "21.2.4",
|
|
50
|
+
"@modelcontextprotocol/client": "2.4.0",
|
|
51
|
+
"@modelcontextprotocol/server": "2.4.0",
|
|
52
|
+
"@nestjs/authentication": "0.1.4",
|
|
53
|
+
"@nestjs/authorization": "0.1.2",
|
|
54
|
+
"@nestjs/common": "12.3.3",
|
|
55
|
+
"@nestjs/core": "12.3.3",
|
|
56
|
+
"@nestjs/mcp": "^0.0.3",
|
|
57
|
+
"@nestjs/platform-express": "12.3.3",
|
|
58
|
+
"@nestjs/platform-fastify": "12.3.3",
|
|
59
|
+
"@nestjs/platform-node": "12.3.3",
|
|
60
|
+
"@nestjs/resilience": "0.0.3",
|
|
61
|
+
"@nestjs/testing": "12.3.3",
|
|
62
|
+
"@nestjs/workflows": "0.0.4",
|
|
63
|
+
"@types/node": "24.19.2",
|
|
64
|
+
"ai": "7.0.162",
|
|
65
|
+
"husky": "9.1.7",
|
|
66
|
+
"lint-staged": "17.6.0",
|
|
67
|
+
"oxlint": "1.91.0",
|
|
68
|
+
"prettier": "3.9.10",
|
|
69
|
+
"reflect-metadata": "0.2.2",
|
|
70
|
+
"release-it": "21.1.2",
|
|
71
|
+
"rxjs": "7.8.2",
|
|
72
|
+
"typescript": "7.0.2",
|
|
73
|
+
"vite": "8.3.5",
|
|
74
|
+
"vitest": "5.1.0",
|
|
75
|
+
"zod": "4.6.7"
|
|
76
|
+
},
|
|
77
|
+
"peerDependencies": {
|
|
78
|
+
"@nestjs/common": "^12.0.0",
|
|
79
|
+
"@nestjs/core": "^12.0.0",
|
|
80
|
+
"@nestjs/resilience": ">=0.0.3 <1",
|
|
81
|
+
"@nestjs/testing": "^12.0.0",
|
|
82
|
+
"@nestjs/workflows": ">=0.0.4 <1",
|
|
83
|
+
"ai": "^7.0.0",
|
|
84
|
+
"reflect-metadata": "^0.1.13 || ^0.2.0",
|
|
85
|
+
"rxjs": "^7.1.0"
|
|
86
|
+
},
|
|
87
|
+
"peerDependenciesMeta": {
|
|
88
|
+
"@nestjs/testing": {
|
|
89
|
+
"optional": true
|
|
90
|
+
},
|
|
91
|
+
"@nestjs/workflows": {
|
|
92
|
+
"optional": true
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
"lint-staged": {
|
|
96
|
+
"**/*.{ts,json}": []
|
|
97
|
+
},
|
|
98
|
+
"repository": {
|
|
99
|
+
"type": "git",
|
|
100
|
+
"url": "https://github.com/nestjs/ai"
|
|
101
|
+
}
|
|
102
|
+
}
|