@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,32 @@
1
+ import { type ModelMessage, type Schema, type UIMessageChunk } from 'ai';
2
+ import type { AiStep } from '../interfaces/ai-result.interface.js';
3
+ import type { StepAttempt } from './model-policy.js';
4
+ import type { CallPlan } from './call-plan.js';
5
+ /** One model call's outcome, as the loop reads it. */
6
+ export interface StepOutcome {
7
+ step: Omit<AiStep, 'attempts'>;
8
+ /** The assistant (and tool) messages the step added to the conversation. */
9
+ responseMessages: ModelMessage[];
10
+ /** The parsed answer, for structured output on a final step. */
11
+ output?: unknown;
12
+ durationMs: number;
13
+ }
14
+ /** Structured output: the `ai` schema of the answer, with its name and description. */
15
+ export interface StepOutput {
16
+ schema: Schema<unknown>;
17
+ name?: string;
18
+ description?: string;
19
+ }
20
+ /** Whether the loop goes on after a step: the model asked for tools, and they ran. */
21
+ export declare function wantsAnotherStep(outcome: StepOutcome): boolean;
22
+ /** One model call with `generateText()`: its tool calls run inside it. */
23
+ export declare function generateStep(plan: CallPlan, index: number, messages: ModelMessage[], attempt: StepAttempt, output?: StepOutput): Promise<StepOutcome>;
24
+ /**
25
+ * One model call with `streamText()`, its UI message chunks sent with `attempt.emit()` as they come. Until the first
26
+ * chunk with content arrives the attempt can still fail and be retried: the chunks before it (`start`, `start-step`)
27
+ * are held. The first one sent commits the attempt, and a failure after it is final. The step's `finish` chunk is
28
+ * held for the loop, which writes one when the last step ends.
29
+ */
30
+ export declare function streamStep(plan: CallPlan, index: number, messages: ModelMessage[], attempt: StepAttempt): Promise<StepOutcome & {
31
+ finish?: UIMessageChunk;
32
+ }>;
@@ -0,0 +1,190 @@
1
+ import { generateText, NoObjectGeneratedError, Output, streamText } from 'ai';
2
+ import { AiOutputError, AiToolError } from '../errors/ai.errors.js';
3
+ import { costOf, toUsage } from '../utils/usage.util.js';
4
+ /** Whether the loop goes on after a step: the model asked for tools, and they ran. */
5
+ export function wantsAnotherStep(outcome) {
6
+ return outcome.step.finishReason === 'tool-calls' && outcome.step.toolResults.length > 0;
7
+ }
8
+ function callSettings(plan, model, messages, attempt, output) {
9
+ return {
10
+ model: model.model,
11
+ ...(plan.instructions !== undefined && { instructions: plan.instructions }),
12
+ messages,
13
+ ...(plan.tools && { tools: plan.tools }),
14
+ ...(plan.toolChoice !== undefined && { toolChoice: plan.toolChoice }),
15
+ ...(output && {
16
+ output: Output.object({
17
+ schema: output.schema,
18
+ ...(output.name !== undefined && { name: output.name }),
19
+ ...(output.description !== undefined && { description: output.description }),
20
+ }),
21
+ }),
22
+ ...plan.settingsFor(model),
23
+ // Retries are the call's resilience policies'.
24
+ maxRetries: 0,
25
+ abortSignal: attempt.signal,
26
+ };
27
+ }
28
+ /** One model call with `generateText()`: its tool calls run inside it. */
29
+ export async function generateStep(plan, index, messages, attempt, output) {
30
+ const started = performance.now();
31
+ // A tool that starts commits the attempt (`onExecute`): another attempt would run it again.
32
+ plan.running = attempt;
33
+ let result;
34
+ try {
35
+ result = await generateText(callSettings(plan, attempt.model, messages, attempt, output));
36
+ }
37
+ catch (error) {
38
+ // The model answered, and its answer is not the object: no retry or other model is tried.
39
+ if (output && NoObjectGeneratedError.isInstance(error)) {
40
+ throw toOutputError(error, error.text ?? '');
41
+ }
42
+ throw error;
43
+ }
44
+ finally {
45
+ plan.running = undefined;
46
+ }
47
+ const outcome = {
48
+ step: describeStep(index, attempt.model, {
49
+ text: result.text,
50
+ content: result.content,
51
+ finishReason: result.finishReason,
52
+ usage: result.usage,
53
+ }),
54
+ responseMessages: [...result.responseMessages],
55
+ durationMs: performance.now() - started,
56
+ };
57
+ if (output && !wantsAnotherStep(outcome)) {
58
+ try {
59
+ outcome.output = result.output;
60
+ }
61
+ catch (error) {
62
+ throw toOutputError(error, result.text);
63
+ }
64
+ }
65
+ return outcome;
66
+ }
67
+ /** An answer that is not JSON, or does not match the schema. */
68
+ function toOutputError(error, text) {
69
+ const cause = error?.cause ?? error;
70
+ return new AiOutputError(`The model's answer does not match the schema: ${cause?.message ?? String(cause)}`, text, { cause: error });
71
+ }
72
+ /**
73
+ * One model call with `streamText()`, its UI message chunks sent with `attempt.emit()` as they come. Until the first
74
+ * chunk with content arrives the attempt can still fail and be retried: the chunks before it (`start`, `start-step`)
75
+ * are held. The first one sent commits the attempt, and a failure after it is final. The step's `finish` chunk is
76
+ * held for the loop, which writes one when the last step ends.
77
+ */
78
+ export async function streamStep(plan, index, messages, attempt) {
79
+ const emit = (chunk) => attempt.emit(chunk);
80
+ plan.running = attempt;
81
+ try {
82
+ return await streamAttempt(plan, index, messages, attempt, emit);
83
+ }
84
+ finally {
85
+ plan.running = undefined;
86
+ }
87
+ }
88
+ async function streamAttempt(plan, index, messages, attempt, emit) {
89
+ const started = performance.now();
90
+ let failure;
91
+ const result = streamText({
92
+ ...callSettings(plan, attempt.model, messages, attempt),
93
+ // Errors are read from the chunks; `ai` would log them to the console.
94
+ onError: () => { },
95
+ });
96
+ const chunks = result.toUIMessageStream({
97
+ sendStart: index === 0,
98
+ sendFinish: true,
99
+ onError: (error) => {
100
+ // A tool's error is what the model was told; anything else is the stream's failure.
101
+ if (error instanceof AiToolError) {
102
+ return error.message;
103
+ }
104
+ failure = error;
105
+ return 'An error occurred.';
106
+ },
107
+ });
108
+ const held = [];
109
+ let opened = false;
110
+ let finish;
111
+ const reader = chunks.getReader();
112
+ try {
113
+ for (;;) {
114
+ const { done, value } = await reader.read();
115
+ if (done) {
116
+ break;
117
+ }
118
+ if (value.type === 'error') {
119
+ // `failure` was set by `onError` for this chunk.
120
+ throw failure ?? new Error(value.errorText);
121
+ }
122
+ if (value.type === 'abort') {
123
+ attempt.signal.throwIfAborted();
124
+ continue;
125
+ }
126
+ if (value.type === 'finish') {
127
+ finish = value;
128
+ continue;
129
+ }
130
+ if (!opened && (value.type === 'start' || value.type === 'start-step')) {
131
+ held.push(value);
132
+ continue;
133
+ }
134
+ if (!opened) {
135
+ opened = true;
136
+ held.splice(0).forEach(emit);
137
+ }
138
+ emit(value);
139
+ }
140
+ }
141
+ finally {
142
+ reader.releaseLock();
143
+ }
144
+ attempt.signal.throwIfAborted();
145
+ if (!opened) {
146
+ // An empty answer (no content at all): what it held still opens the message.
147
+ held.splice(0).forEach(emit);
148
+ }
149
+ const [text, content, finishReason, usage, responseMessages] = await Promise.all([
150
+ result.text,
151
+ result.content,
152
+ result.finishReason,
153
+ result.usage,
154
+ result.responseMessages,
155
+ ]);
156
+ return {
157
+ step: describeStep(index, attempt.model, { text, content, finishReason, usage }),
158
+ responseMessages: [...responseMessages],
159
+ durationMs: performance.now() - started,
160
+ ...(finish && { finish }),
161
+ };
162
+ }
163
+ function describeStep(index, model, result) {
164
+ const toolCalls = [];
165
+ const toolResults = [];
166
+ for (const part of result.content) {
167
+ const call = { toolCallId: part.toolCallId ?? '', toolName: part.toolName ?? '', input: part.input };
168
+ if (part.type === 'tool-call') {
169
+ toolCalls.push(call);
170
+ }
171
+ else if (part.type === 'tool-result') {
172
+ toolResults.push({ ...call, output: part.output });
173
+ }
174
+ else if (part.type === 'tool-error') {
175
+ toolResults.push({ ...call, error: String(part.error) });
176
+ }
177
+ }
178
+ const usage = toUsage(result.usage);
179
+ const cost = costOf(usage, model.pricing);
180
+ return {
181
+ index,
182
+ model: model.info,
183
+ text: result.text,
184
+ toolCalls,
185
+ toolResults,
186
+ finishReason: result.finishReason,
187
+ usage,
188
+ ...(cost !== undefined && { cost }),
189
+ };
190
+ }
@@ -0,0 +1,64 @@
1
+ import { type FinishReason, type ModelMessage, type UIMessageChunk } from 'ai';
2
+ import type { Duration } from '@nestjs/resilience';
3
+ import type { AiStep, AiTextResult } from '../interfaces/ai-result.interface.js';
4
+ import type { AiUsage } from '../interfaces/ai-usage.interface.js';
5
+ /** What the browser is told about a failure. Default: `An error occurred.`, so that no server detail leaks. */
6
+ export interface AiUIMessageStreamOptions {
7
+ errorText?: (error: unknown) => string;
8
+ }
9
+ export interface AiStreamResponseOptions extends AiUIMessageStreamOptions {
10
+ /** Default 200. */
11
+ status?: number;
12
+ headers?: Record<string, string>;
13
+ /**
14
+ * A comment line (`: keep-alive`) sent after this long without a chunk, while a tool runs or the model thinks, so
15
+ * that proxies and load balancers do not close the connection as idle. Default `'15s'`; `false` for none.
16
+ */
17
+ keepAlive?: Duration | false;
18
+ }
19
+ /**
20
+ * A streamed answer: `streamText()`'s result. The call runs once, from the moment it is made, whatever reads it: the
21
+ * UI message stream (`toUIMessageStream()`, `pipeUIMessageStreamToResponse()`), the text (`textStream`,
22
+ * `pipeTextStreamToResponse()`), or the promises, which settle when the call ends. Each reader gets the whole
23
+ * stream from its start. A reader that cancels (a browser that goes away) aborts the call.
24
+ */
25
+ export declare class AiTextStream {
26
+ private readonly controller;
27
+ /** Everything about the call, when it ends; rejects with its error. */
28
+ readonly result: Promise<AiTextResult>;
29
+ private readonly entries;
30
+ private readonly waiting;
31
+ private ended;
32
+ /** Settles with the first chunk, or with the call's error when it fails before one. */
33
+ private readonly opened;
34
+ /** @internal */
35
+ constructor(controller: AbortController, produce: (emit: (chunk: UIMessageChunk) => void) => Promise<AiTextResult>);
36
+ get text(): Promise<string>;
37
+ get usage(): Promise<AiUsage>;
38
+ get steps(): Promise<AiStep[]>;
39
+ get finishReason(): Promise<FinishReason>;
40
+ get responseMessages(): Promise<ModelMessage[]>;
41
+ /** The text as it is generated, over every step. Throws the call's error. */
42
+ get textStream(): AsyncIterable<string>;
43
+ /**
44
+ * The AI SDK's UI message stream: what `useChat()` reads. One message: `start`, each step's chunks (text, tool
45
+ * calls and their results), `finish`; or an `error` chunk when the call fails.
46
+ */
47
+ toUIMessageStream({ errorText }?: AiUIMessageStreamOptions): ReadableStream<UIMessageChunk>;
48
+ /** The UI message stream as a web-standard `Response` (server-sent events). */
49
+ toUIMessageStreamResponse({ status, headers, keepAlive, ...options }?: AiStreamResponseOptions): Response;
50
+ /**
51
+ * Sends the UI message stream to the browser, as server-sent events, on the response `@Res()` injects (Express,
52
+ * Fastify or the Node adapter). Resolves when the stream has been sent. A call that fails before its first chunk
53
+ * rejects instead, with nothing sent, so the route's exception filters answer it.
54
+ */
55
+ pipeUIMessageStreamToResponse(response: unknown, options?: AiStreamResponseOptions): Promise<void>;
56
+ /** Sends the text alone, as `text/plain` chunks; a failure after the first chunk ends the response early. */
57
+ pipeTextStreamToResponse(response: unknown, { status, headers }?: Omit<AiStreamResponseOptions, 'errorText' | 'keepAlive'>): Promise<void>;
58
+ /** Aborts the call: the model call in flight and the tools running. */
59
+ abort(reason?: unknown): void;
60
+ private send;
61
+ /** Every entry from the first, then the new ones as they come, until the call ends. */
62
+ private read;
63
+ private wakeAll;
64
+ }
@@ -0,0 +1,211 @@
1
+ import { createTextStreamResponse, createUIMessageStreamResponse } from 'ai';
2
+ import { withKeepAlive } from '../http/keep-alive.util.js';
3
+ import { toNodeResponse, writeWebResponse } from '../http/node-response.util.js';
4
+ import { toMilliseconds } from '../utils/duration.util.js';
5
+ const DEFAULT_KEEP_ALIVE_MS = 15_000;
6
+ const DEFAULT_ERROR_TEXT = 'An error occurred.';
7
+ /**
8
+ * A streamed answer: `streamText()`'s result. The call runs once, from the moment it is made, whatever reads it: the
9
+ * UI message stream (`toUIMessageStream()`, `pipeUIMessageStreamToResponse()`), the text (`textStream`,
10
+ * `pipeTextStreamToResponse()`), or the promises, which settle when the call ends. Each reader gets the whole
11
+ * stream from its start. A reader that cancels (a browser that goes away) aborts the call.
12
+ */
13
+ export class AiTextStream {
14
+ /** @internal */
15
+ constructor(controller, produce) {
16
+ this.controller = controller;
17
+ this.entries = [];
18
+ this.waiting = new Set();
19
+ this.ended = false;
20
+ let open;
21
+ let fail;
22
+ this.opened = new Promise((resolve, reject) => {
23
+ open = resolve;
24
+ fail = reject;
25
+ });
26
+ const push = (entry) => {
27
+ this.entries.push(entry);
28
+ for (const wake of this.waiting) {
29
+ wake();
30
+ }
31
+ this.waiting.clear();
32
+ };
33
+ const emit = (chunk) => {
34
+ if (!this.ended) {
35
+ push({ chunk });
36
+ open();
37
+ }
38
+ };
39
+ this.result = Promise.resolve()
40
+ .then(() => produce(emit))
41
+ .then((result) => {
42
+ this.ended = true;
43
+ open();
44
+ this.wakeAll();
45
+ return result;
46
+ }, (error) => {
47
+ push({ error });
48
+ this.ended = true;
49
+ fail(error);
50
+ this.wakeAll();
51
+ throw error;
52
+ });
53
+ // Rejections are the readers' to handle: an unread stream must not crash the process.
54
+ this.result.catch(() => { });
55
+ this.opened.catch(() => { });
56
+ }
57
+ get text() {
58
+ return this.result.then((result) => result.text);
59
+ }
60
+ get usage() {
61
+ return this.result.then((result) => result.usage);
62
+ }
63
+ get steps() {
64
+ return this.result.then((result) => result.steps);
65
+ }
66
+ get finishReason() {
67
+ return this.result.then((result) => result.finishReason);
68
+ }
69
+ get responseMessages() {
70
+ return this.result.then((result) => result.responseMessages);
71
+ }
72
+ /** The text as it is generated, over every step. Throws the call's error. */
73
+ get textStream() {
74
+ const entries = this.read();
75
+ return {
76
+ [Symbol.asyncIterator]: async function* () {
77
+ for await (const entry of entries) {
78
+ if ('error' in entry) {
79
+ throw entry.error;
80
+ }
81
+ if (entry.chunk.type === 'text-delta') {
82
+ yield entry.chunk.delta;
83
+ }
84
+ }
85
+ },
86
+ };
87
+ }
88
+ /**
89
+ * The AI SDK's UI message stream: what `useChat()` reads. One message: `start`, each step's chunks (text, tool
90
+ * calls and their results), `finish`; or an `error` chunk when the call fails.
91
+ */
92
+ toUIMessageStream({ errorText = () => DEFAULT_ERROR_TEXT } = {}) {
93
+ const entries = this.read()[Symbol.asyncIterator]();
94
+ return new ReadableStream({
95
+ pull: async (controller) => {
96
+ const next = await entries.next();
97
+ if (next.done) {
98
+ controller.close();
99
+ }
100
+ else if ('error' in next.value) {
101
+ controller.enqueue({ type: 'error', errorText: safely(errorText, next.value.error) });
102
+ }
103
+ else {
104
+ controller.enqueue(next.value.chunk);
105
+ }
106
+ },
107
+ cancel: () => this.abort(),
108
+ });
109
+ }
110
+ /** The UI message stream as a web-standard `Response` (server-sent events). */
111
+ toUIMessageStreamResponse({ status, headers, keepAlive, ...options } = {}) {
112
+ const response = createUIMessageStreamResponse({
113
+ stream: this.toUIMessageStream(options),
114
+ ...(status !== undefined && { status }),
115
+ ...(headers && { headers }),
116
+ });
117
+ const interval = keepAlive === false ? undefined : toMilliseconds(keepAlive ?? DEFAULT_KEEP_ALIVE_MS, 'keepAlive');
118
+ if (!interval || !response.body) {
119
+ return response;
120
+ }
121
+ return new Response(withKeepAlive(response.body, interval), {
122
+ status: response.status,
123
+ statusText: response.statusText,
124
+ headers: response.headers,
125
+ });
126
+ }
127
+ /**
128
+ * Sends the UI message stream to the browser, as server-sent events, on the response `@Res()` injects (Express,
129
+ * Fastify or the Node adapter). Resolves when the stream has been sent. A call that fails before its first chunk
130
+ * rejects instead, with nothing sent, so the route's exception filters answer it.
131
+ */
132
+ async pipeUIMessageStreamToResponse(response, options = {}) {
133
+ await this.send(response, () => this.toUIMessageStreamResponse(options));
134
+ }
135
+ /** Sends the text alone, as `text/plain` chunks; a failure after the first chunk ends the response early. */
136
+ async pipeTextStreamToResponse(response, { status, headers } = {}) {
137
+ const text = this.textStream[Symbol.asyncIterator]();
138
+ await this.send(response, () => createTextStreamResponse({
139
+ stream: new ReadableStream({
140
+ pull: async (controller) => {
141
+ const next = await text.next();
142
+ if (next.done) {
143
+ controller.close();
144
+ }
145
+ else {
146
+ controller.enqueue(next.value);
147
+ }
148
+ },
149
+ cancel: () => this.abort(),
150
+ }),
151
+ ...(status !== undefined && { status }),
152
+ ...(headers && { headers }),
153
+ }));
154
+ }
155
+ /** Aborts the call: the model call in flight and the tools running. */
156
+ abort(reason) {
157
+ this.controller.abort(reason ?? new DOMException('The stream was cancelled', 'AbortError'));
158
+ }
159
+ async send(platformResponse, create) {
160
+ const node = toNodeResponse(platformResponse);
161
+ const gone = () => {
162
+ if (!node.res.writableFinished) {
163
+ this.abort();
164
+ }
165
+ };
166
+ node.res.once('close', gone);
167
+ try {
168
+ await this.opened;
169
+ }
170
+ catch (error) {
171
+ node.res.off('close', gone);
172
+ throw error;
173
+ }
174
+ node.open();
175
+ await writeWebResponse(create(), node.res);
176
+ }
177
+ /** Every entry from the first, then the new ones as they come, until the call ends. */
178
+ read() {
179
+ return {
180
+ [Symbol.asyncIterator]: async function* () {
181
+ let position = 0;
182
+ for (;;) {
183
+ if (position < this.entries.length) {
184
+ yield this.entries[position++];
185
+ continue;
186
+ }
187
+ if (this.ended) {
188
+ return;
189
+ }
190
+ await new Promise((resolve) => {
191
+ this.waiting.add(resolve);
192
+ });
193
+ }
194
+ }.bind(this),
195
+ };
196
+ }
197
+ wakeAll() {
198
+ for (const wake of this.waiting) {
199
+ wake();
200
+ }
201
+ this.waiting.clear();
202
+ }
203
+ }
204
+ function safely(errorText, error) {
205
+ try {
206
+ return errorText(error);
207
+ }
208
+ catch {
209
+ return DEFAULT_ERROR_TEXT;
210
+ }
211
+ }
@@ -0,0 +1,70 @@
1
+ import { ModuleRef } from '@nestjs/core';
2
+ import type { ModelMessage } from 'ai';
3
+ import type { AiCallOptions, AiObjectCallOptions } from '../interfaces/ai-call-options.interface.js';
4
+ import type { AiEmbedOptions, AiEmbedManyOptions, AiEmbedResult, AiEmbedManyResult } from '../interfaces/ai-embedding.interface.js';
5
+ import type { AiModuleOptions } from '../interfaces/ai-module-options.interface.js';
6
+ import type { AiObjectResult, AiTextResult } from '../interfaces/ai-result.interface.js';
7
+ import type { AiSchema, InferSchemaOutput } from '../interfaces/ai-schema.interface.js';
8
+ import { AiModels } from '../models/ai-models.service.js';
9
+ import { AiEmbeddingModels } from '../models/ai-embedding-models.service.js';
10
+ import { type StepOutcome } from '../runtime/steps.js';
11
+ import { AiTextStream } from '../runtime/text-stream.js';
12
+ import { AiTools } from '../tools/ai-tools.service.js';
13
+ /**
14
+ * Model calls: text, structured output and streams, with the application's agent functions as tools, through the
15
+ * configured models, with timeouts, retries, fallback, budgets and usage events.
16
+ *
17
+ * ```ts
18
+ * const { text } = await this.ai.generateText({ prompt: 'Summarize this ticket: ...' });
19
+ * ```
20
+ */
21
+ export declare class AiService {
22
+ private readonly models;
23
+ private readonly tools;
24
+ private readonly options;
25
+ private readonly moduleRef;
26
+ private readonly embeddingModels;
27
+ private readonly logger;
28
+ constructor(models: AiModels, tools: AiTools, options: AiModuleOptions, moduleRef: ModuleRef, embeddingModels: AiEmbeddingModels);
29
+ /**
30
+ * Embeds one string, with the embedding model's settings, Nest policies and embedding usage
31
+ * events.
32
+ */
33
+ embed(options: AiEmbedOptions): Promise<AiEmbedResult>;
34
+ /**
35
+ * Embeds strings in input order; the SDK batches requests, with bounded concurrency (default 4).
36
+ * Timeouts, retries and policies apply to each request.
37
+ */
38
+ embedMany(options: AiEmbedManyOptions): Promise<AiEmbedManyResult>;
39
+ /** Generates text; with `tools`, calls them as the model asks, and answers when it is done. */
40
+ generateText(options: AiCallOptions): Promise<AiTextResult>;
41
+ /**
42
+ * Generates an object that matches `schema`: an invoice from an email, a triage decision from a ticket. Rejects
43
+ * with `AiOutputError` when the answer does not match it.
44
+ */
45
+ generateObject<S extends AiSchema>(options: AiObjectCallOptions<S>): Promise<AiObjectResult<InferSchemaOutput<S>>>;
46
+ /**
47
+ * Streams text: the call starts now, and its answer can be sent to the browser as it is generated
48
+ * (`pipeUIMessageStreamToResponse(res)`), read (`textStream`) or awaited (`text`). Mistakes in the options throw
49
+ * here; failures of the call reach the stream.
50
+ */
51
+ streamText(options: AiCallOptions): AiTextStream;
52
+ /**
53
+ * @internal One model call of a durable run (`@nestjs/ai/workflows`), with `messages` so far: the tools are offered
54
+ * and their calls returned, not run. Retries, timeouts, fallback and usage events as for any call.
55
+ */
56
+ callModel(options: AiCallOptions, { index, messages, signal }: {
57
+ index: number;
58
+ messages: ModelMessage[];
59
+ signal: AbortSignal;
60
+ }): Promise<{
61
+ outcome: StepOutcome;
62
+ attempts: number;
63
+ }>;
64
+ private plan;
65
+ /** A preset of the application's `ResilienceService`, which `ResilienceModule.forRoot({ presets })` configures. */
66
+ private preset;
67
+ /** Calls the usage listeners. They never fail the call: what they throw is logged. */
68
+ private report;
69
+ private logListenerError;
70
+ }