@nestjs/ai 0.0.0-stage → 0.0.2

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 (101) hide show
  1. package/LICENSE +23 -0
  2. package/README.md +108 -2
  3. package/dist/ai.constants.d.ts +30 -0
  4. package/dist/ai.constants.js +34 -0
  5. package/dist/ai.module-definition.d.ts +23 -0
  6. package/dist/ai.module-definition.js +39 -0
  7. package/dist/ai.module.d.ts +11 -0
  8. package/dist/ai.module.js +45 -0
  9. package/dist/context/ai-execution-context.d.ts +32 -0
  10. package/dist/context/ai-execution-context.js +41 -0
  11. package/dist/context/ai-params.d.ts +20 -0
  12. package/dist/context/ai-params.js +44 -0
  13. package/dist/decorators/agent-function.decorator.d.ts +22 -0
  14. package/dist/decorators/agent-function.decorator.js +36 -0
  15. package/dist/decorators/index.d.ts +3 -0
  16. package/dist/decorators/index.js +3 -0
  17. package/dist/decorators/inject-embedding-model.decorator.d.ts +10 -0
  18. package/dist/decorators/inject-embedding-model.decorator.js +16 -0
  19. package/dist/decorators/inject-model.decorator.d.ts +10 -0
  20. package/dist/decorators/inject-model.decorator.js +16 -0
  21. package/dist/errors/ai.errors.d.ts +54 -0
  22. package/dist/errors/ai.errors.js +62 -0
  23. package/dist/errors/index.d.ts +1 -0
  24. package/dist/errors/index.js +1 -0
  25. package/dist/http/keep-alive.util.d.ts +6 -0
  26. package/dist/http/keep-alive.util.js +38 -0
  27. package/dist/http/node-response.util.d.ts +14 -0
  28. package/dist/http/node-response.util.js +104 -0
  29. package/dist/index.d.ts +14 -0
  30. package/dist/index.js +16 -0
  31. package/dist/interfaces/agent-function.interface.d.ts +53 -0
  32. package/dist/interfaces/agent-function.interface.js +1 -0
  33. package/dist/interfaces/ai-call-options.interface.d.ts +65 -0
  34. package/dist/interfaces/ai-call-options.interface.js +1 -0
  35. package/dist/interfaces/ai-embedding.interface.d.ts +79 -0
  36. package/dist/interfaces/ai-embedding.interface.js +1 -0
  37. package/dist/interfaces/ai-module-options.interface.d.ts +110 -0
  38. package/dist/interfaces/ai-module-options.interface.js +1 -0
  39. package/dist/interfaces/ai-result.interface.d.ts +57 -0
  40. package/dist/interfaces/ai-result.interface.js +1 -0
  41. package/dist/interfaces/ai-schema.interface.d.ts +44 -0
  42. package/dist/interfaces/ai-schema.interface.js +1 -0
  43. package/dist/interfaces/ai-tool.interface.d.ts +22 -0
  44. package/dist/interfaces/ai-tool.interface.js +1 -0
  45. package/dist/interfaces/ai-usage.interface.d.ts +30 -0
  46. package/dist/interfaces/ai-usage.interface.js +1 -0
  47. package/dist/interfaces/index.d.ts +8 -0
  48. package/dist/interfaces/index.js +8 -0
  49. package/dist/models/ai-embedding-models.service.d.ts +31 -0
  50. package/dist/models/ai-embedding-models.service.js +138 -0
  51. package/dist/models/ai-models.service.d.ts +29 -0
  52. package/dist/models/ai-models.service.js +112 -0
  53. package/dist/models/model-registry.util.d.ts +6 -0
  54. package/dist/models/model-registry.util.js +21 -0
  55. package/dist/models/model-requests.d.ts +5 -0
  56. package/dist/models/model-requests.js +20 -0
  57. package/dist/runtime/agent-loop.d.ts +30 -0
  58. package/dist/runtime/agent-loop.js +81 -0
  59. package/dist/runtime/call-plan.d.ts +54 -0
  60. package/dist/runtime/call-plan.js +105 -0
  61. package/dist/runtime/embedding-call.d.ts +38 -0
  62. package/dist/runtime/embedding-call.js +120 -0
  63. package/dist/runtime/model-policy.d.ts +53 -0
  64. package/dist/runtime/model-policy.js +202 -0
  65. package/dist/runtime/steps.d.ts +32 -0
  66. package/dist/runtime/steps.js +190 -0
  67. package/dist/runtime/text-stream.d.ts +64 -0
  68. package/dist/runtime/text-stream.js +211 -0
  69. package/dist/services/ai.service.d.ts +70 -0
  70. package/dist/services/ai.service.js +183 -0
  71. package/dist/testing/fake-embedding-model.d.ts +57 -0
  72. package/dist/testing/fake-embedding-model.js +82 -0
  73. package/dist/testing/fake-model.d.ts +96 -0
  74. package/dist/testing/fake-model.js +201 -0
  75. package/dist/testing/index.d.ts +32 -0
  76. package/dist/testing/index.js +27 -0
  77. package/dist/tools/ai-tools.service.d.ts +77 -0
  78. package/dist/tools/ai-tools.service.js +234 -0
  79. package/dist/tools/tool-output.util.d.ts +6 -0
  80. package/dist/tools/tool-output.util.js +38 -0
  81. package/dist/utils/duration.util.d.ts +3 -0
  82. package/dist/utils/duration.util.js +17 -0
  83. package/dist/utils/errors.util.d.ts +16 -0
  84. package/dist/utils/errors.util.js +35 -0
  85. package/dist/utils/names.util.d.ts +2 -0
  86. package/dist/utils/names.util.js +10 -0
  87. package/dist/utils/schema.util.d.ts +16 -0
  88. package/dist/utils/schema.util.js +70 -0
  89. package/dist/utils/usage.util.d.ts +10 -0
  90. package/dist/utils/usage.util.js +42 -0
  91. package/dist/workflows/ai-agent.interfaces.d.ts +70 -0
  92. package/dist/workflows/ai-agent.interfaces.js +1 -0
  93. package/dist/workflows/ai-agent.workflow.d.ts +15 -0
  94. package/dist/workflows/ai-agent.workflow.js +25 -0
  95. package/dist/workflows/ai-agents.service.d.ts +83 -0
  96. package/dist/workflows/ai-agents.service.js +277 -0
  97. package/dist/workflows/ai-workflows.module.d.ts +6 -0
  98. package/dist/workflows/ai-workflows.module.js +22 -0
  99. package/dist/workflows/index.d.ts +6 -0
  100. package/dist/workflows/index.js +5 -0
  101. package/package.json +115 -4
@@ -0,0 +1,70 @@
1
+ import type { Duration } from '@nestjs/workflows';
2
+ import type { ModelMessage } from 'ai';
3
+ import type { AiToolChoice } from '../interfaces/ai-call-options.interface.js';
4
+ import type { AiBudget } from '../interfaces/ai-module-options.interface.js';
5
+ import type { AiTextResult } from '../interfaces/ai-result.interface.js';
6
+ import type { AiToolSelection } from '../interfaces/ai-tool.interface.js';
7
+ /** Who a durable run acts for: data, stored with the run, since a request does not survive a restart. */
8
+ export interface AiAgentCaller<TUser = unknown> {
9
+ /** JSON: an id, the claims the agent functions and their guards need. */
10
+ user?: TUser;
11
+ scopes?: string[];
12
+ }
13
+ /**
14
+ * The options of `AiAgents.run()`: those of a call that a run stores and resumes with, so JSON only. Models and agent
15
+ * functions are named; what the module configures for a model (pricing, fallback, retries) applies as for any call.
16
+ */
17
+ export interface AiAgentRunOptions {
18
+ /** A model's name. Default `'default'`. */
19
+ model?: string;
20
+ instructions?: string;
21
+ prompt?: string;
22
+ messages?: ModelMessage[];
23
+ /** Agent function names and provider classes (all their agent functions), stored as names. */
24
+ tools?: AiToolSelection;
25
+ /**
26
+ * The agent functions, among `tools`, whose calls wait for a person's approval (`AiAgents.approve()` or `reject()`)
27
+ * before they run. A rejected call, or one nobody answered within `approvalTimeout`, is a tool error the model reads.
28
+ */
29
+ approve?: string[];
30
+ /** How long a call waits for its approval. Default: until it gets one (or the workflow's own timeout). */
31
+ approvalTimeout?: Duration;
32
+ caller?: AiAgentCaller;
33
+ /** Model calls the run may make while the model asks for tools. Default: `defaults.maxSteps`, else 25. */
34
+ maxSteps?: number;
35
+ /**
36
+ * Model calls per workflow instance: after that many, the run goes on in a child workflow, which keeps each
37
+ * instance's journal small (a journal is replayed on every resume, and capped). Default 10.
38
+ */
39
+ stepsPerInstance?: number;
40
+ /** `maxTokens` and `maxCost`, over the whole run. Default: the module's `defaults.budget`. */
41
+ budget?: AiBudget;
42
+ /** Names the run's model calls in usage events. */
43
+ label?: string;
44
+ toolChoice?: AiToolChoice;
45
+ /** Models tried in order when the model fails after its retries, by name; `false` for none. */
46
+ fallback?: string | string[] | false;
47
+ maxOutputTokens?: number;
48
+ temperature?: number;
49
+ providerOptions?: Record<string, Record<string, any>>;
50
+ }
51
+ /** What `AiAgents.run()` resolves with: a call's result, as JSON. */
52
+ export type AiAgentRunResult = AiTextResult;
53
+ /** A person's answer to a tool call that waits for approval: the `TOOL_APPROVAL` signal's payload. */
54
+ export interface AiToolApproval {
55
+ approved: boolean;
56
+ /** Told to the model when the call is rejected. */
57
+ reason?: string;
58
+ /** Who answered, for your records. */
59
+ by?: unknown;
60
+ }
61
+ /** A tool call waiting for approval, as `AiAgents.pendingApprovals()` lists it. */
62
+ export interface AiPendingApproval {
63
+ /** What `approve()` and `reject()` take. */
64
+ key: string;
65
+ /** The run (the instance `AiAgents.run()` runs in). */
66
+ runId: string;
67
+ toolCallId: string;
68
+ toolName: string;
69
+ input: unknown;
70
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,15 @@
1
+ import { type WorkflowContext, type WorkflowRunner } from '@nestjs/workflows';
2
+ import { AiAgents, type AgentSettings, type AgentState } from './ai-agents.service.js';
3
+ import type { AiAgentRunResult } from './ai-agent.interfaces.js';
4
+ /** A run of `AiAgents.run()` going on in a child instance, from where its parent stopped. Internal. */
5
+ export declare class AiAgentSegment implements WorkflowRunner<{
6
+ settings: AgentSettings;
7
+ state: AgentState;
8
+ }, AiAgentRunResult> {
9
+ private readonly agents;
10
+ constructor(agents: AiAgents);
11
+ run(ctx: WorkflowContext, { settings, state }: {
12
+ settings: AgentSettings;
13
+ state: AgentState;
14
+ }): Promise<AiAgentRunResult>;
15
+ }
@@ -0,0 +1,25 @@
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 { Workflow } from '@nestjs/workflows';
11
+ import { AGENT_WORKFLOW, AiAgents } from './ai-agents.service.js';
12
+ /** A run of `AiAgents.run()` going on in a child instance, from where its parent stopped. Internal. */
13
+ let AiAgentSegment = class AiAgentSegment {
14
+ constructor(agents) {
15
+ this.agents = agents;
16
+ }
17
+ run(ctx, { settings, state }) {
18
+ return this.agents.resume(ctx, settings, state);
19
+ }
20
+ };
21
+ AiAgentSegment = __decorate([
22
+ Workflow(AGENT_WORKFLOW),
23
+ __metadata("design:paramtypes", [AiAgents])
24
+ ], AiAgentSegment);
25
+ export { AiAgentSegment };
@@ -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,6 @@
1
+ /**
2
+ * Durable agent runs on `@nestjs/workflows`: import it next to `AiModule` and `WorkflowsModule`, and inject
3
+ * `AiAgents` into your `@Workflow()` classes.
4
+ */
5
+ export declare class AiWorkflowsModule {
6
+ }
@@ -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';
@@ -0,0 +1,5 @@
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';
package/package.json CHANGED
@@ -1,6 +1,117 @@
1
1
  {
2
2
  "name": "@nestjs/ai",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.0.2",
4
+ "description": "Language models for NestJS: model calls with timeouts, retries and fallbacks, structured output, tools and agents, and streaming to the browser.",
5
+ "keywords": [
6
+ "nestjs",
7
+ "nest",
8
+ "ai",
9
+ "llm",
10
+ "agents",
11
+ "ai-agent",
12
+ "tools",
13
+ "tool-calling",
14
+ "openai",
15
+ "anthropic",
16
+ "ai-sdk",
17
+ "streaming",
18
+ "chat"
19
+ ],
20
+ "author": "Kamil Mysliwiec",
21
+ "license": "MIT",
22
+ "url": "https://github.com/nestjs/ai#readme",
23
+ "type": "module",
24
+ "main": "./dist/index.js",
25
+ "types": "./dist/index.d.ts",
26
+ "exports": {
27
+ ".": {
28
+ "types": "./dist/index.d.ts",
29
+ "import": "./dist/index.js",
30
+ "default": "./dist/index.js"
31
+ },
32
+ "./testing": {
33
+ "types": "./dist/testing/index.d.ts",
34
+ "import": "./dist/testing/index.js",
35
+ "default": "./dist/testing/index.js"
36
+ },
37
+ "./workflows": {
38
+ "types": "./dist/workflows/index.d.ts",
39
+ "import": "./dist/workflows/index.js",
40
+ "default": "./dist/workflows/index.js"
41
+ },
42
+ "./package.json": "./package.json"
43
+ },
44
+ "engines": {
45
+ "node": ">=22"
46
+ },
47
+ "scripts": {
48
+ "build": "rm -rf dist && tsc -p tsconfig.build.json",
49
+ "format": "prettier --write \"**/*.ts\"",
50
+ "lint": "oxlint lib/",
51
+ "prepublish:npm": "npm run build",
52
+ "publish:npm": "npm publish --access public",
53
+ "prepublish:next": "npm run build",
54
+ "publish:next": "npm publish --access public --tag next",
55
+ "test:e2e": "vitest run",
56
+ "test:e2e:dev": "vitest",
57
+ "prerelease": "npm run build",
58
+ "release": "release-it",
59
+ "prepare": "husky"
60
+ },
61
+ "devDependencies": {
62
+ "@ai-sdk/anthropic": "4.0.96",
63
+ "@commitlint/cli": "21.2.4",
64
+ "@commitlint/config-angular": "21.2.4",
65
+ "@modelcontextprotocol/client": "2.4.0",
66
+ "@modelcontextprotocol/server": "2.4.0",
67
+ "@nestjs/authentication": "0.1.4",
68
+ "@nestjs/authorization": "0.1.2",
69
+ "@nestjs/common": "12.3.3",
70
+ "@nestjs/core": "12.3.3",
71
+ "@nestjs/mcp": "^0.0.3",
72
+ "@nestjs/platform-express": "12.3.3",
73
+ "@nestjs/platform-fastify": "12.3.3",
74
+ "@nestjs/platform-node": "12.3.3",
75
+ "@nestjs/resilience": "0.0.3",
76
+ "@nestjs/testing": "12.3.3",
77
+ "@nestjs/workflows": "0.0.4",
78
+ "@types/node": "24.19.2",
79
+ "ai": "7.0.162",
80
+ "husky": "9.1.7",
81
+ "lint-staged": "17.6.0",
82
+ "oxlint": "1.91.0",
83
+ "prettier": "3.9.10",
84
+ "reflect-metadata": "0.2.2",
85
+ "release-it": "21.1.2",
86
+ "rxjs": "7.8.2",
87
+ "typescript": "7.0.2",
88
+ "vite": "8.3.5",
89
+ "vitest": "5.1.0",
90
+ "zod": "4.6.7"
91
+ },
92
+ "peerDependencies": {
93
+ "@nestjs/common": "^12.0.0",
94
+ "@nestjs/core": "^12.0.0",
95
+ "@nestjs/resilience": ">=0.0.3 <1",
96
+ "@nestjs/testing": "^12.0.0",
97
+ "@nestjs/workflows": ">=0.0.4 <1",
98
+ "ai": "^7.0.0",
99
+ "reflect-metadata": "^0.1.13 || ^0.2.0",
100
+ "rxjs": "^7.1.0"
101
+ },
102
+ "peerDependenciesMeta": {
103
+ "@nestjs/testing": {
104
+ "optional": true
105
+ },
106
+ "@nestjs/workflows": {
107
+ "optional": true
108
+ }
109
+ },
110
+ "lint-staged": {
111
+ "**/*.{ts,json}": []
112
+ },
113
+ "repository": {
114
+ "type": "git",
115
+ "url": "https://github.com/nestjs/ai"
116
+ }
117
+ }