@tstdl/base 0.92.11 → 0.92.13

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.
@@ -0,0 +1,28 @@
1
+ import '../polyfills.js';
2
+ import { Resolvable, type resolveArgumentType } from '../injector/interfaces.js';
3
+ import { FileContentPart, FileInput, AiModel } from './types.js';
4
+ export type AiFileServiceOptions = {
5
+ apiKey: string;
6
+ defaultModel?: AiModel;
7
+ };
8
+ export type AiFileServiceArgument = AiFileServiceOptions;
9
+ type File = {
10
+ id: string;
11
+ name: string;
12
+ uri: string;
13
+ mimeType: string;
14
+ };
15
+ export declare class AiFileService implements Resolvable<AiFileServiceArgument> {
16
+ #private;
17
+ readonly [resolveArgumentType]: AiFileServiceArgument;
18
+ processFile(fileInput: FileInput): Promise<FileContentPart>;
19
+ processFiles(fileInputs: FileInput[]): Promise<FileContentPart[]>;
20
+ getFileById(id: string): File | undefined;
21
+ getFileByUri(uri: string): File | undefined;
22
+ private getFile;
23
+ private getFiles;
24
+ private uploadFile;
25
+ private waitForFileActive;
26
+ private waitForFilesActive;
27
+ }
28
+ export {};
@@ -0,0 +1,174 @@
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 __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) {
8
+ if (value !== null && value !== void 0) {
9
+ if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected.");
10
+ var dispose, inner;
11
+ if (async) {
12
+ if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined.");
13
+ dispose = value[Symbol.asyncDispose];
14
+ }
15
+ if (dispose === void 0) {
16
+ if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined.");
17
+ dispose = value[Symbol.dispose];
18
+ if (async) inner = dispose;
19
+ }
20
+ if (typeof dispose !== "function") throw new TypeError("Object not disposable.");
21
+ if (inner) dispose = function() { try { inner.call(this); } catch (e) { return Promise.reject(e); } };
22
+ env.stack.push({ value: value, dispose: dispose, async: async });
23
+ }
24
+ else if (async) {
25
+ env.stack.push({ async: true });
26
+ }
27
+ return value;
28
+ };
29
+ var __disposeResources = (this && this.__disposeResources) || (function (SuppressedError) {
30
+ return function (env) {
31
+ function fail(e) {
32
+ env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e;
33
+ env.hasError = true;
34
+ }
35
+ var r, s = 0;
36
+ function next() {
37
+ while (r = env.stack.pop()) {
38
+ try {
39
+ if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);
40
+ if (r.dispose) {
41
+ var result = r.dispose.call(r.value);
42
+ if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) { fail(e); return next(); });
43
+ }
44
+ else s |= 1;
45
+ }
46
+ catch (e) {
47
+ fail(e);
48
+ }
49
+ }
50
+ if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();
51
+ if (env.hasError) throw env.error;
52
+ }
53
+ return next();
54
+ };
55
+ })(typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
56
+ var e = new Error(message);
57
+ return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
58
+ });
59
+ import '../polyfills.js';
60
+ import { stat, unlink, writeFile } from 'node:fs/promises';
61
+ import { tmpdir } from 'node:os';
62
+ import { join } from 'node:path';
63
+ import { FileState, GoogleAIFileManager } from '@google/generative-ai/server';
64
+ import { AsyncEnumerable } from '../enumerable/async-enumerable.js';
65
+ import { DetailsError } from '../errors/details.error.js';
66
+ import { Singleton } from '../injector/decorators.js';
67
+ import { inject, injectArgument } from '../injector/inject.js';
68
+ import { Logger } from '../logger/logger.js';
69
+ import { createArray } from '../utils/array/array.js';
70
+ import { formatBytes } from '../utils/format.js';
71
+ import { timeout } from '../utils/timing.js';
72
+ import { tryIgnoreAsync } from '../utils/try-ignore.js';
73
+ import { isBlob } from '../utils/type-guards.js';
74
+ import { millisecondsPerSecond } from '../utils/units.js';
75
+ let AiFileService = class AiFileService {
76
+ #options = injectArgument(this);
77
+ #fileManager = new GoogleAIFileManager(this.#options.apiKey);
78
+ #fileMap = new Map();
79
+ #fileUriMap = new Map();
80
+ #logger = inject(Logger, 'AiFileService');
81
+ async processFile(fileInput) {
82
+ const file = await this.getFile(fileInput);
83
+ this.#fileMap.set(file.id, file);
84
+ this.#fileUriMap.set(file.uri, file);
85
+ return { file: file.id };
86
+ }
87
+ async processFiles(fileInputs) {
88
+ const files = await this.getFiles(fileInputs);
89
+ return files.map((file) => {
90
+ this.#fileMap.set(file.id, file);
91
+ this.#fileUriMap.set(file.uri, file);
92
+ return { file: file.id };
93
+ });
94
+ }
95
+ getFileById(id) {
96
+ return this.#fileMap.get(id);
97
+ }
98
+ getFileByUri(uri) {
99
+ return this.#fileUriMap.get(uri);
100
+ }
101
+ async getFile(fileInput) {
102
+ const id = crypto.randomUUID();
103
+ const uploadResponse = await this.uploadFile(fileInput, id);
104
+ this.#logger.verbose(`Processing file "${id}"...`);
105
+ const response = await this.waitForFileActive(uploadResponse);
106
+ return {
107
+ id,
108
+ name: response.name,
109
+ uri: response.uri,
110
+ mimeType: response.mimeType
111
+ };
112
+ }
113
+ async getFiles(files) {
114
+ const ids = createArray(files.length, () => crypto.randomUUID());
115
+ const uploadResponses = await AsyncEnumerable.from(files).parallelMap(5, true, async (file, index) => this.uploadFile(file, ids[index])).toArray();
116
+ this.#logger.verbose(`Processing ${files.length} files...`);
117
+ const responses = await this.waitForFilesActive(uploadResponses);
118
+ return responses.map((response, index) => ({
119
+ id: ids[index],
120
+ name: response.name,
121
+ uri: response.uri,
122
+ mimeType: response.mimeType
123
+ }));
124
+ }
125
+ async uploadFile(fileInput, id) {
126
+ const env_1 = { stack: [], error: void 0, hasError: false };
127
+ try {
128
+ const path = isBlob(fileInput) ? join(tmpdir(), crypto.randomUUID()) : fileInput.path;
129
+ const mimeType = isBlob(fileInput) ? fileInput.type : fileInput.mimeType;
130
+ const stack = __addDisposableResource(env_1, new AsyncDisposableStack(), true);
131
+ if (isBlob(fileInput)) {
132
+ this.#logger.verbose(`Preparing file "${id}"...`);
133
+ stack.defer(async () => tryIgnoreAsync(async () => unlink(path)));
134
+ await writeFile(path, fileInput.stream());
135
+ }
136
+ const fileSize = isBlob(fileInput) ? fileInput.size : (await stat(path)).size;
137
+ this.#logger.verbose(`Uploading file "${id}" (${formatBytes(fileSize)})...`);
138
+ const response = await this.#fileManager.uploadFile(path, { mimeType });
139
+ return response.file;
140
+ }
141
+ catch (e_1) {
142
+ env_1.error = e_1;
143
+ env_1.hasError = true;
144
+ }
145
+ finally {
146
+ const result_1 = __disposeResources(env_1);
147
+ if (result_1)
148
+ await result_1;
149
+ }
150
+ }
151
+ async waitForFileActive(fileMetadata) {
152
+ let file = await this.#fileManager.getFile(fileMetadata.name);
153
+ while (file.state == FileState.PROCESSING) {
154
+ await timeout(millisecondsPerSecond);
155
+ file = await this.#fileManager.getFile(fileMetadata.name);
156
+ }
157
+ if (file.state == FileState.FAILED) {
158
+ throw new DetailsError(file.error?.message ?? `Failed to process file ${file.name}`, file.error?.details);
159
+ }
160
+ return file;
161
+ }
162
+ async waitForFilesActive(files) {
163
+ const responses = [];
164
+ for (const file of files) {
165
+ const respones = await this.waitForFileActive(file);
166
+ responses.push(respones);
167
+ }
168
+ return responses;
169
+ }
170
+ };
171
+ AiFileService = __decorate([
172
+ Singleton()
173
+ ], AiFileService);
174
+ export { AiFileService };
@@ -0,0 +1,9 @@
1
+ import type { TypedOmit } from '../types.js';
2
+ import type { AiService } from './ai.service.js';
3
+ import type { Content, GenerationRequest, GenerationResult } from './types.js';
4
+ export declare class AiSession {
5
+ #private;
6
+ readonly contents: Content[];
7
+ constructor(aiService: AiService);
8
+ generate(content: Content | [Content, ...Content[]], request?: TypedOmit<GenerationRequest, 'contents'>): Promise<GenerationResult>;
9
+ }
@@ -0,0 +1,18 @@
1
+ import { toArray } from '../utils/array/array.js';
2
+ export class AiSession {
3
+ #aiService;
4
+ contents = [];
5
+ constructor(aiService) {
6
+ this.#aiService = aiService;
7
+ }
8
+ async generate(content, request) {
9
+ const newContents = toArray(content);
10
+ const result = await this.#aiService.generate({
11
+ ...request,
12
+ contents: [...this.contents, ...newContents]
13
+ });
14
+ this.contents.push(...newContents);
15
+ this.contents.push(result.content);
16
+ return result;
17
+ }
18
+ }
@@ -1,53 +1,33 @@
1
1
  import '../polyfills.js';
2
- import { GoogleGenerativeAI } from '@google/generative-ai';
3
- import { type FileMetadataResponse, GoogleAIFileManager } from '@google/generative-ai/server';
4
- import { LiteralUnion } from 'type-fest';
5
2
  import { Resolvable, type resolveArgumentType } from '../injector/interfaces.js';
6
3
  import { OneOrMany, SchemaTestable } from '../schema/index.js';
7
4
  import { Enumeration as EnumerationType, EnumerationValue } from '../types.js';
8
- import { Content, FileInput, SchemaFunctionDeclarations } from './types.js';
9
- export type GenerativeAIModel = LiteralUnion<'gemini-2.0-flash-exp' | 'gemini-exp-1206' | 'gemini-2.0-flash-thinking-exp-1219', string>;
10
- export type GenerationOptions = {
11
- model?: GenerativeAIModel;
12
- systemInstruction?: string;
13
- maxOutputTokens?: number;
14
- temperature?: number;
15
- topP?: number;
16
- topK?: number;
17
- presencePenalty?: number;
18
- frequencyPenalty?: number;
19
- forceFunctionCall?: boolean;
20
- };
21
- export type GenerationResult<T> = {
5
+ import { AiSession } from './ai-session.js';
6
+ import { AiModel, Content, FileContentPart, FileInput, GenerationOptions, GenerationRequest, GenerationResult, GenerationUsage, SchemaFunctionDeclarations, SchemaFunctionDeclarationsResult } from './types.js';
7
+ export type SpecializedGenerationResult<T> = {
22
8
  result: T;
23
- usage?: {
24
- promptTokenCount: number;
25
- candidatesTokenCount: number;
26
- totalTokenCount: number;
27
- };
9
+ usage: GenerationUsage;
28
10
  };
29
11
  export type AiServiceOptions = {
30
12
  apiKey: string;
31
- defaultModel?: GenerativeAIModel;
13
+ defaultModel?: AiModel;
32
14
  };
33
15
  export type AiServiceArgument = AiServiceOptions;
34
16
  export declare class AiService implements Resolvable<AiServiceArgument> {
35
17
  #private;
36
- readonly genAI: GoogleGenerativeAI;
37
- readonly fileManager: GoogleAIFileManager;
38
- readonly defaultModel: GenerativeAIModel;
18
+ readonly defaultModel: AiModel;
39
19
  readonly [resolveArgumentType]: AiServiceArgument;
40
- getFile(fileInput: FileInput): Promise<FileMetadataResponse>;
41
- getFiles(files: readonly FileInput[]): Promise<FileMetadataResponse[]>;
42
- test(): Promise<void>;
43
- classify<T extends EnumerationType>(fileInput: OneOrMany<FileInput>, types: T, options?: GenerationOptions): Promise<GenerationResult<{
20
+ newSession(): AiSession;
21
+ processFile(fileInput: FileInput): Promise<FileContentPart>;
22
+ processFiles(fileInputs: FileInput[]): Promise<FileContentPart[]>;
23
+ classify<T extends EnumerationType>(files: OneOrMany<FileContentPart>, types: T, model?: AiModel): Promise<SpecializedGenerationResult<{
44
24
  types: {
45
25
  type: EnumerationValue<T>;
46
26
  confidence: 'high' | 'medium' | 'low';
47
27
  }[] | null;
48
28
  }>>;
49
- extractData<T>(fileInput: OneOrMany<FileInput>, schema: SchemaTestable<T>, options?: GenerationOptions): Promise<GenerationResult<T>>;
50
- analyzeDocument<T extends EnumerationType>(fileInput: FileInput, types: T, options?: GenerationOptions): Promise<GenerationResult<{
29
+ extractData<T>(files: OneOrMany<FileContentPart>, schema: SchemaTestable<T>, options?: GenerationOptions): Promise<SpecializedGenerationResult<T>>;
30
+ analyzeDocument<T extends EnumerationType>(file: FileContentPart, types: T, options?: GenerationOptions): Promise<SpecializedGenerationResult<{
51
31
  content: string[];
52
32
  documentTypes: {
53
33
  type: EnumerationValue<T>;
@@ -55,14 +35,11 @@ export declare class AiService implements Resolvable<AiServiceArgument> {
55
35
  }[];
56
36
  tags: string[];
57
37
  }>>;
58
- findActions(fileInput: OneOrMany<FileInput>, options?: GenerationOptions): Promise<GenerationResult<{
59
- text: string;
60
- functions: any;
61
- }>>;
62
- callFunctions<const T extends SchemaFunctionDeclarations>(functions: T, contents: Content[], options?: GenerationOptions): Promise<void>;
63
- private waitForFileActive;
64
- private waitForFilesActive;
38
+ callFunctions<const T extends SchemaFunctionDeclarations>(functions: T, contents: Content[], options?: Pick<GenerationRequest, 'model' | 'systemInstruction'> & GenerationOptions): Promise<SchemaFunctionDeclarationsResult<T>[]>;
39
+ generate(request: GenerationRequest): Promise<GenerationResult>;
65
40
  private convertContents;
66
41
  private convertContent;
42
+ private convertFunctions;
43
+ private convertGoogleContent;
67
44
  private getModel;
68
45
  }
package/ai/ai.service.js CHANGED
@@ -4,241 +4,74 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
4
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
5
  return c > 3 && r && Object.defineProperty(target, key, r), r;
6
6
  };
7
- var __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) {
8
- if (value !== null && value !== void 0) {
9
- if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected.");
10
- var dispose, inner;
11
- if (async) {
12
- if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined.");
13
- dispose = value[Symbol.asyncDispose];
14
- }
15
- if (dispose === void 0) {
16
- if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined.");
17
- dispose = value[Symbol.dispose];
18
- if (async) inner = dispose;
19
- }
20
- if (typeof dispose !== "function") throw new TypeError("Object not disposable.");
21
- if (inner) dispose = function() { try { inner.call(this); } catch (e) { return Promise.reject(e); } };
22
- env.stack.push({ value: value, dispose: dispose, async: async });
23
- }
24
- else if (async) {
25
- env.stack.push({ async: true });
26
- }
27
- return value;
28
- };
29
- var __disposeResources = (this && this.__disposeResources) || (function (SuppressedError) {
30
- return function (env) {
31
- function fail(e) {
32
- env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e;
33
- env.hasError = true;
34
- }
35
- var r, s = 0;
36
- function next() {
37
- while (r = env.stack.pop()) {
38
- try {
39
- if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);
40
- if (r.dispose) {
41
- var result = r.dispose.call(r.value);
42
- if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) { fail(e); return next(); });
43
- }
44
- else s |= 1;
45
- }
46
- catch (e) {
47
- fail(e);
48
- }
49
- }
50
- if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();
51
- if (env.hasError) throw env.error;
52
- }
53
- return next();
54
- };
55
- })(typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
56
- var e = new Error(message);
57
- return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
58
- });
59
7
  import '../polyfills.js';
60
- import { openAsBlob } from 'node:fs';
61
- import { unlink, writeFile } from 'node:fs/promises';
62
- import { tmpdir } from 'node:os';
63
- import { join } from 'node:path';
64
- import { inspect } from 'node:util';
65
- import { FunctionCallingMode, GoogleGenerativeAI, SchemaType } from '@google/generative-ai';
66
- import { FileState, GoogleAIFileManager } from '@google/generative-ai/server';
67
- import { AsyncEnumerable } from '../enumerable/async-enumerable.js';
68
- import { DetailsError } from '../errors/details.error.js';
8
+ import { FunctionCallingMode as GoogleFunctionCallingMode, GoogleGenerativeAI } from '@google/generative-ai';
69
9
  import { NotSupportedError } from '../errors/not-supported.error.js';
70
10
  import { Singleton } from '../injector/decorators.js';
71
11
  import { inject, injectArgument } from '../injector/inject.js';
72
- import { Logger } from '../logger/logger.js';
73
12
  import { convertToOpenApiSchema } from '../schema/converters/openapi-converter.js';
74
- import { array, enumeration, nullable, object, Schema, string } from '../schema/index.js';
13
+ import { array, enumeration, nullable, object, string } from '../schema/index.js';
75
14
  import { toArray } from '../utils/array/array.js';
76
- import { digest } from '../utils/cryptography.js';
77
- import { formatBytes } from '../utils/format.js';
78
15
  import { hasOwnProperty, objectEntries } from '../utils/object/object.js';
79
- import { timeout } from '../utils/timing.js';
80
- import { tryIgnoreAsync } from '../utils/try-ignore.js';
81
- import { assertDefinedPass, isBlob } from '../utils/type-guards.js';
82
- import { millisecondsPerSecond } from '../utils/units.js';
16
+ import { assertDefinedPass, assertNotNullPass, isDefined } from '../utils/type-guards.js';
17
+ import { AiFileService } from './ai-file.service.js';
18
+ import { AiSession } from './ai-session.js';
19
+ import { isSchemaFunctionDeclarationWithHandler } from './types.js';
20
+ const functionCallingModeMap = {
21
+ auto: GoogleFunctionCallingMode.AUTO,
22
+ force: GoogleFunctionCallingMode.ANY,
23
+ none: GoogleFunctionCallingMode.NONE
24
+ };
83
25
  let AiService = class AiService {
84
26
  #options = injectArgument(this);
85
- #fileCache = new Map();
86
- #logger = inject(Logger, 'AiService');
87
- genAI = new GoogleGenerativeAI(this.#options.apiKey);
88
- fileManager = new GoogleAIFileManager(this.#options.apiKey);
27
+ #fileService = inject(AiFileService, this.#options);
28
+ #genAI = new GoogleGenerativeAI(this.#options.apiKey);
89
29
  defaultModel = this.#options.defaultModel ?? 'gemini-2.0-flash-exp';
90
- async getFile(fileInput) {
91
- const path = isBlob(fileInput) ? join(tmpdir(), crypto.randomUUID()) : fileInput.path;
92
- const mimeType = isBlob(fileInput) ? fileInput.type : fileInput.mimeType;
93
- const blob = isBlob(fileInput) ? fileInput : await openAsBlob(path, { type: mimeType });
94
- const buffer = await blob.arrayBuffer();
95
- const byteArray = new Uint8Array(buffer);
96
- const fileHash = await digest('SHA-1', byteArray).toBase64();
97
- const fileKey = `${fileHash}:${byteArray.length}`;
98
- if (this.#fileCache.has(fileKey)) {
99
- try {
100
- this.#logger.verbose(`Fetching file "${fileHash}" from cache...`);
101
- const cachedFile = await this.#fileCache.get(fileKey);
102
- return await this.fileManager.getFile(cachedFile.name);
103
- }
104
- catch {
105
- this.#fileCache.delete(fileKey);
106
- }
107
- }
108
- const filePromise = (async () => {
109
- try {
110
- const env_1 = { stack: [], error: void 0, hasError: false };
111
- try {
112
- const stack = __addDisposableResource(env_1, new AsyncDisposableStack(), true);
113
- if (isBlob(fileInput)) {
114
- stack.defer(async () => tryIgnoreAsync(async () => unlink(path)));
115
- await writeFile(path, byteArray);
116
- }
117
- this.#logger.verbose(`Uploading file "${fileHash}" (${formatBytes(byteArray.length)})...`);
118
- const result = await this.fileManager.uploadFile(path, { mimeType });
119
- this.#logger.verbose(`Processing file "${fileHash}"...`);
120
- return await this.waitForFileActive(result.file);
121
- }
122
- catch (e_1) {
123
- env_1.error = e_1;
124
- env_1.hasError = true;
125
- }
126
- finally {
127
- const result_1 = __disposeResources(env_1);
128
- if (result_1)
129
- await result_1;
130
- }
131
- }
132
- catch (error) {
133
- this.#fileCache.delete(fileKey);
134
- throw error;
135
- }
136
- })();
137
- this.#fileCache.set(fileKey, filePromise);
138
- return filePromise;
30
+ newSession() {
31
+ return new AiSession(this);
139
32
  }
140
- async getFiles(files) {
141
- return Promise.all(files.map(async (file) => this.getFile(file)));
33
+ async processFile(fileInput) {
34
+ return this.#fileService.processFile(fileInput);
142
35
  }
143
- async test() {
144
- const result = await this.getModel('gemini-2.0-flash-exp').generateContent({
145
- generationConfig: {
146
- maxOutputTokens: 4096
147
- },
148
- tools: [{
149
- functionDeclarations: [
150
- {
151
- name: 'outputNumber',
152
- description: 'outputs a number',
153
- parameters: {
154
- type: SchemaType.OBJECT,
155
- properties: {
156
- number: { type: SchemaType.INTEGER }
157
- },
158
- required: ['number']
159
- }
160
- }
161
- ],
162
- }],
163
- toolConfig: {
164
- functionCallingConfig: {
165
- mode: FunctionCallingMode.ANY
166
- }
167
- },
168
- contents: [
169
- { role: 'user', parts: [{ text: 'Output 5 increasing numbers starting at 10' }] },
170
- {
171
- role: 'model',
172
- parts: [
173
- { functionCall: { name: 'outputNumber', args: { number: 10 } } },
174
- { functionCall: { name: 'outputNumber', args: { number: 11 } } },
175
- { functionCall: { name: 'outputNumber', args: { number: 12 } } },
176
- { functionCall: { name: 'outputNumber', args: { number: 13 } } },
177
- { functionCall: { name: 'outputNumber', args: { number: 14 } } }
178
- ]
179
- },
180
- /*{
181
- role: 'user',
182
- parts: [
183
- { functionResponse: { name: 'outputNumber', response: { result: 'outputted number 10' } } },
184
- { functionResponse: { name: 'outputNumber', response: { result: 'outputted number 11' } } },
185
- { functionResponse: { name: 'outputNumber', response: { result: 'outputted number 12' } } },
186
- { functionResponse: { name: 'outputNumber', response: { result: 'outputted number 13' } } },
187
- { functionResponse: { name: 'outputNumber', response: { result: 'outputted number 14' } } }
188
- ]
189
- },*/
190
- // { role: 'model', parts: [{ text: 'Okay, I\'ve outputted the numbers 10, 11, 12, 13, and 14.' }] },
191
- { role: 'user', parts: [{ text: 'Give me 5 more numbers. ONLY call functions, dont output text' }] }
192
- ]
193
- });
194
- console.log(inspect(result.response, { depth: null }));
36
+ async processFiles(fileInputs) {
37
+ return this.#fileService.processFiles(fileInputs);
195
38
  }
196
- async classify(fileInput, types, options) {
197
- const files = await this.getFiles(toArray(fileInput));
198
- const resultSchema = object({
39
+ async classify(files, types, model) {
40
+ const generationSchema = object({
199
41
  types: nullable(array(object({
200
42
  type: enumeration(types, { description: 'Type of document' }),
201
43
  confidence: enumeration(['high', 'medium', 'low'], { description: 'How sure/certain you are about the classficiation.' })
202
44
  }), { description: 'One or more document types that matches' }))
203
45
  });
204
- const responseSchema = convertToOpenApiSchema(resultSchema);
205
- this.#logger.verbose('Classifying...');
206
- const result = await this.getModel(options?.model ?? this.defaultModel).generateContent({
207
- generationConfig: {
208
- maxOutputTokens: 2048,
209
- temperature: 0.75,
210
- ...options,
211
- responseMimeType: 'application/json',
212
- responseSchema
46
+ const result = await this.generate({
47
+ model,
48
+ generationOptions: {
49
+ maxOutputTokens: 1048,
50
+ temperature: 0.5
213
51
  },
52
+ generationSchema,
214
53
  systemInstruction: 'You are a highly accurate document classification AI. Your task is to analyze the content of a given document and determine its type based on a predefined list of possible document types.',
215
- contents: [
216
- {
54
+ contents: [{
217
55
  role: 'user',
218
56
  parts: [
219
- ...files.map((file) => ({ fileData: { mimeType: file.mimeType, fileUri: file.uri } })),
220
- { text: `Classify the document. Output as JSON using the following schema:\n${JSON.stringify(responseSchema, null, 2)}\n\nIf none of the provided document types are a suitable match, return null for types.` }
57
+ ...toArray(files),
58
+ { text: 'Classify the document. Output as JSON using the provided schema\n\nIf none of the provided document types are a suitable match, return null for types.' }
221
59
  ]
222
- }
223
- ]
60
+ }]
224
61
  });
225
62
  return {
226
- usage: result.response.usageMetadata,
227
- result: resultSchema.parse(JSON.parse(result.response.text()))
63
+ usage: result.usage,
64
+ result: JSON.parse(assertNotNullPass(result.text, 'No text returned.'))
228
65
  };
229
66
  }
230
- async extractData(fileInput, schema, options) {
231
- const files = await this.getFiles(toArray(fileInput));
232
- const responseSchema = convertToOpenApiSchema(schema);
233
- this.#logger.verbose('Extracting data...');
234
- const result = await this.getModel(options?.model ?? this.defaultModel).generateContent({
235
- generationConfig: {
67
+ async extractData(files, schema, options) {
68
+ const result = await this.generate({
69
+ generationOptions: {
236
70
  maxOutputTokens: 8192,
237
- temperature: 0.75,
238
- ...options,
239
- responseMimeType: 'application/json',
240
- responseSchema
71
+ temperature: 0.5,
72
+ ...options
241
73
  },
74
+ generationSchema: schema,
242
75
  systemInstruction: `You are a highly skilled data extraction AI, specializing in accurately identifying and extracting information from unstructured text documents and converting it into a structured JSON format. Your primary goal is to meticulously follow the provided JSON schema and populate it with data extracted from the given document.
243
76
 
244
77
  **Instructions:**
@@ -247,37 +80,33 @@ Carefully read and analyze the provided document. Identify relevant information
247
80
  {
248
81
  role: 'user',
249
82
  parts: [
250
- ...files.map((file) => ({ fileData: { mimeType: file.mimeType, fileUri: file.uri } })),
251
- { text: `Classify the document. Output as JSON using the following schema:\n${JSON.stringify(responseSchema, null, 2)}` }
83
+ ...toArray(files),
84
+ { text: 'Classify the document. Output as JSON.' }
252
85
  ]
253
86
  }
254
87
  ]
255
88
  });
256
89
  return {
257
- usage: result.response.usageMetadata,
258
- result: Schema.parse(schema, JSON.parse(result.response.text()))
90
+ usage: result.usage,
91
+ result: JSON.parse(assertNotNullPass(result.text, 'No text returned.'))
259
92
  };
260
93
  }
261
- async analyzeDocument(fileInput, types, options) {
262
- const file = await this.getFile(fileInput);
94
+ async analyzeDocument(file, types, options) {
263
95
  const schema = object({
264
- content: array(string({ description: 'Single content without data details' }), { description: 'List of contents' }),
96
+ content: string({ description: 'Content of the document with important details only' }),
265
97
  documentTypes: array(object({
266
98
  type: enumeration(types, { description: 'Type of document' }),
267
- confidence: enumeration(['high', 'medium', 'low'], { description: 'How sure/certain you are about the classficiation.' })
99
+ confidence: enumeration(['high', 'medium', 'low'], { description: 'How sure/certain you are about the classficiation' })
268
100
  }), { description: 'One or more document types that matches' }),
269
- tags: array(string({ description: 'Tag which describes the content' }), { description: 'List of tags' })
101
+ tags: array(string({ description: 'Tag which describes the content' }), { description: 'List of tags', maximum: 5 })
270
102
  });
271
- const responseSchema = convertToOpenApiSchema(schema);
272
- this.#logger.verbose('Extracting data...');
273
- const result = await this.getModel(options?.model ?? this.defaultModel).generateContent({
274
- generationConfig: {
103
+ const result = await this.generate({
104
+ generationOptions: {
275
105
  maxOutputTokens: 2048,
276
- temperature: 0.75,
277
- ...options,
278
- responseMimeType: 'application/json',
279
- responseSchema
106
+ temperature: 0.5,
107
+ ...options
280
108
  },
109
+ generationSchema: schema,
281
110
  systemInstruction: `You are a highly skilled data extraction AI, specializing in accurately identifying and extracting information from unstructured text documents and converting it into a structured JSON format. Your primary goal is to meticulously follow the provided JSON schema and populate it with data extracted from the given document.
282
111
 
283
112
  **Instructions:**
@@ -291,175 +120,123 @@ Always output the content and tags in Deutsch.`,
291
120
  {
292
121
  role: 'user',
293
122
  parts: [
294
- { fileData: { mimeType: file.mimeType, fileUri: file.uri } },
295
- { text: `Classify the document. Output as JSON using the following schema:\n${JSON.stringify(responseSchema, null, 2)}` }
123
+ file,
124
+ { text: 'Classify the document. Output as JSON.' }
296
125
  ]
297
126
  }
298
127
  ]
299
128
  });
300
129
  return {
301
- usage: result.response.usageMetadata,
302
- result: Schema.parse(schema, JSON.parse(result.response.text()))
303
- };
304
- }
305
- async findActions(fileInput, options) {
306
- const files = await this.getFiles(toArray(fileInput));
307
- this.#logger.verbose('Extracting data...');
308
- const result = await this.getModel(options?.model ?? this.defaultModel).generateContent({
309
- generationConfig: {
310
- maxOutputTokens: 4096,
311
- temperature: 0.75,
312
- ...options
313
- },
314
- tools: [
315
- {
316
- functionDeclarations: [
317
- {
318
- name: 'createNewRealEstate',
319
- description: 'creates a new real estate',
320
- parameters: {
321
- type: SchemaType.OBJECT,
322
- properties: {
323
- address: { type: SchemaType.STRING },
324
- realEstateNumber: { type: SchemaType.STRING }
325
- },
326
- required: ['address', 'realEstateNumber']
327
- }
328
- },
329
- {
330
- name: 'createNewUnit',
331
- description: 'creates a new unit in a real estate. Make sure to get the correct unit number, each unit has to get its own',
332
- parameters: {
333
- type: SchemaType.OBJECT,
334
- description: 'data of the unit from documents',
335
- properties: {
336
- unitNumber: { type: SchemaType.STRING },
337
- owner: { type: SchemaType.STRING },
338
- dweller: { type: SchemaType.STRING }
339
- },
340
- required: ['unitNumber', 'owner', 'dweller']
341
- }
342
- },
343
- {
344
- name: 'createNewDevice',
345
- description: 'creates a new device in a real estate / unit, for example HKV or WWZ',
346
- parameters: {
347
- type: SchemaType.OBJECT,
348
- description: 'data of the device from documents like Ablesebeleg.',
349
- properties: {
350
- unitNumber: { type: SchemaType.STRING },
351
- deviceNumber: { type: SchemaType.STRING },
352
- factor: { type: SchemaType.NUMBER },
353
- deviceType: { type: SchemaType.STRING },
354
- roomType: { type: SchemaType.STRING }
355
- },
356
- required: ['unitNumber', 'deviceNumber', 'factor', 'deviceType', 'roomType']
357
- }
358
- }
359
- ]
360
- }
361
- ],
362
- toolConfig: { functionCallingConfig: { mode: FunctionCallingMode.ANY } },
363
- systemInstruction: `You are a specialized AI assistant designed to extract structured data from various document types related to real estate and device management. Your goal is to understand the document's content and identify key information to populate specific function calls. You will be provided with document text and a set of predefined functions.
364
-
365
- **Your task is:**
366
-
367
- 1. **Analyze the document:** Carefully read the provided document text.
368
- 2. **Identify relevant information:** Extract the key data points related to real estate (e.g., address, size, type) and devices (e.g., type, model, serial number, location).
369
- 3. **Determine the appropriate function:** Based on the extracted information, select the most suitable function from the list below to perform the intended action.
370
- 4. **Generate function call:** Output the selected function name along with its arguments, populated with the extracted data. Use JSON format for the output.`,
371
- contents: [
372
- {
373
- role: 'user',
374
- parts: [
375
- ...files.map((file) => ({ fileData: { mimeType: file.mimeType, fileUri: file.uri } })),
376
- { text: 'Extract the data from the document and call suitable functions.' }
377
- ]
378
- }
379
- ]
380
- });
381
- return {
382
- usage: result.response.usageMetadata,
383
- result: {
384
- functions: result.response.functionCalls(),
385
- text: result.response.text()
386
- }
130
+ usage: result.usage,
131
+ result: JSON.parse(assertNotNullPass(result.text, 'No text returned.'))
387
132
  };
388
133
  }
389
134
  async callFunctions(functions, contents, options) {
390
- const googleContent = await this.convertContents(contents);
391
- const googleFunctionDeclarations = objectEntries(functions).map(([name, declaration]) => ({
392
- name: name,
393
- description: declaration.description,
394
- parameters: convertToOpenApiSchema(declaration.parameters)
395
- }));
396
- const result = await this.getModel(options?.model ?? this.defaultModel).generateContent({
397
- generationConfig: {
135
+ const generation = await this.generate({
136
+ model: options?.model,
137
+ generationOptions: {
398
138
  maxOutputTokens: 4096,
399
- temperature: 0.75,
139
+ temperature: 0.5,
400
140
  ...options
401
141
  },
402
142
  systemInstruction: options?.systemInstruction,
403
- tools: [{ functionDeclarations: googleFunctionDeclarations }],
404
- toolConfig: {
405
- functionCallingConfig: {
406
- mode: (options?.forceFunctionCall == false) ? FunctionCallingMode.AUTO : FunctionCallingMode.ANY
407
- }
408
- },
409
- contents: googleContent
143
+ functions,
144
+ functionCallingMode: 'force',
145
+ contents
410
146
  });
411
- for (const call of result.response.functionCalls() ?? []) {
412
- const fn = assertDefinedPass(functions[call.name]);
413
- const parameters = fn.parameters.parse(call.args);
414
- const functionResult = await fn.handler(parameters);
147
+ const result = [];
148
+ for (const call of generation.functionCalls) {
149
+ const fn = assertDefinedPass(functions[call.name], 'Function in response not declared.');
150
+ const parameters = fn.parameters.parse(call.parameters);
151
+ const handlerResult = isSchemaFunctionDeclarationWithHandler(fn) ? await fn.handler(parameters) : undefined;
152
+ result.push({ functionName: call.name, parameters: parameters, handlerResult: handlerResult });
415
153
  }
154
+ return result;
416
155
  }
417
- async waitForFileActive(fileMetadata) {
418
- let file = await this.fileManager.getFile(fileMetadata.name);
419
- while (file.state == FileState.PROCESSING) {
420
- await timeout(millisecondsPerSecond);
421
- file = await this.fileManager.getFile(fileMetadata.name);
422
- }
423
- if (file.state == FileState.FAILED) {
424
- throw new DetailsError(file.error?.message ?? `Failed to process file ${file.name}`, file.error?.details);
425
- }
426
- return file;
427
- }
428
- async waitForFilesActive(...files) {
429
- const responses = [];
430
- for (const file of files) {
431
- const respones = await this.waitForFileActive(file);
432
- responses.push(respones);
433
- }
434
- return responses;
156
+ async generate(request) {
157
+ const googleContent = this.convertContents(request.contents);
158
+ const googleFunctionDeclarations = isDefined(request.functions) ? this.convertFunctions(request.functions) : undefined;
159
+ const generationConfig = isDefined(request.generationSchema)
160
+ ? { ...request.generationOptions, responseMimeType: 'application/json', responseSchema: convertToOpenApiSchema(request.generationSchema) }
161
+ : request.generationOptions;
162
+ const generation = await this.getModel(request.model ?? this.defaultModel).generateContent({
163
+ generationConfig,
164
+ systemInstruction: request.systemInstruction,
165
+ tools: isDefined(googleFunctionDeclarations) ? [{ functionDeclarations: googleFunctionDeclarations }] : undefined,
166
+ toolConfig: isDefined(request.functionCallingMode)
167
+ ? { functionCallingConfig: { mode: functionCallingModeMap[request.functionCallingMode] } }
168
+ : undefined,
169
+ contents: googleContent
170
+ });
171
+ const content = this.convertGoogleContent(generation.response.candidates.at(0).content);
172
+ const textParts = content.parts.filter((part) => hasOwnProperty(part, 'text')).map((part) => part.text);
173
+ const functionCallParts = content.parts.filter((part) => hasOwnProperty(part, 'functionCall')).map((part) => part.functionCall);
174
+ return {
175
+ content,
176
+ text: textParts.length > 0 ? textParts.join('') : null,
177
+ functionCalls: functionCallParts,
178
+ usage: {
179
+ prompt: generation.response.usageMetadata.promptTokenCount,
180
+ output: generation.response.usageMetadata.candidatesTokenCount,
181
+ total: generation.response.usageMetadata.totalTokenCount
182
+ }
183
+ };
435
184
  }
436
- async convertContents(contents) {
437
- return AsyncEnumerable.from(contents)
438
- .map(async (content) => this.convertContent(content))
439
- .toArray();
185
+ convertContents(contents) {
186
+ return contents.map((content) => this.convertContent(content));
440
187
  }
441
- async convertContent(content) {
188
+ convertContent(content) {
442
189
  return {
443
190
  role: content.role,
444
- parts: await AsyncEnumerable.from(content.parts)
445
- .map(async (part) => {
191
+ parts: content.parts.map((part) => {
446
192
  if (hasOwnProperty(part, 'text')) {
447
193
  return { text: part.text };
448
194
  }
449
195
  if (hasOwnProperty(part, 'file')) {
450
- const fileMetadata = await this.getFile(part.file);
451
- return { fileData: { fileUri: fileMetadata.uri, mimeType: fileMetadata.mimeType } };
196
+ const file = assertDefinedPass(this.#fileService.getFileById(part.file), `File ${part.file} not found.`);
197
+ return { fileData: { fileUri: file.uri, mimeType: file.mimeType } };
452
198
  }
453
199
  if (hasOwnProperty(part, 'functionResult')) {
454
- return { functionResponse: { name: part.functionResult.name, response: { result: part.functionResult.value } } };
200
+ return { functionResponse: { name: part.functionResult.name, response: part.functionResult.value } };
201
+ }
202
+ if (hasOwnProperty(part, 'functionCall')) {
203
+ return { functionCall: { name: part.functionCall.name, args: part.functionCall.parameters } };
204
+ }
205
+ throw new NotSupportedError('Unsupported content part.');
206
+ })
207
+ };
208
+ }
209
+ convertFunctions(functions) {
210
+ return objectEntries(functions).map(([name, declaration]) => ({
211
+ name,
212
+ description: declaration.description,
213
+ parameters: convertToOpenApiSchema(declaration.parameters)
214
+ }));
215
+ }
216
+ convertGoogleContent(content) {
217
+ return {
218
+ role: content.role,
219
+ parts: content.parts.map((part) => {
220
+ if (isDefined(part.text)) {
221
+ return { text: part.text };
222
+ }
223
+ if (isDefined(part.fileData)) {
224
+ const file = assertDefinedPass(this.#fileService.getFileByUri(part.fileData.fileUri), 'File not found.');
225
+ return { file: file.id };
226
+ }
227
+ ;
228
+ if (isDefined(part.functionResponse)) {
229
+ return { functionResult: { name: part.functionResponse.name, value: part.functionResponse.response } };
230
+ }
231
+ if (isDefined(part.functionCall)) {
232
+ return { functionCall: { name: part.functionCall.name, parameters: part.functionCall.args } };
455
233
  }
456
234
  throw new NotSupportedError('Unsupported content part.');
457
235
  })
458
- .toArray()
459
236
  };
460
237
  }
461
238
  getModel(model) {
462
- return this.genAI.getGenerativeModel({ model });
239
+ return this.#genAI.getGenerativeModel({ model });
463
240
  }
464
241
  };
465
242
  AiService = __decorate([
package/ai/functions.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { FunctionDeclaration } from '@google/generative-ai';
2
2
  import type { AbstractConstructor } from '../types.js';
3
3
  import type { SchemaFunctionDeclarations } from './types.js';
4
- export declare function convertFunctionDeclarations(declarations: SchemaFunctionDeclarations): void;
4
+ export declare function convertFunctionDeclarations(declarations: SchemaFunctionDeclarations): FunctionDeclaration[];
5
5
  export declare function getFunctionDeclarations(type: AbstractConstructor): FunctionDeclaration[];
package/ai/functions.js CHANGED
@@ -3,7 +3,7 @@ import { FunctionSchema, getObjectSchema, object } from '../schema/index.js';
3
3
  import { fromEntries, objectEntries } from '../utils/object/object.js';
4
4
  import { isNotNull, isNull, isString } from '../utils/type-guards.js';
5
5
  export function convertFunctionDeclarations(declarations) {
6
- const entries = objectEntries(declarations)
6
+ return objectEntries(declarations)
7
7
  .map(([name, declaration]) => ({
8
8
  name,
9
9
  description: declaration.description,
package/ai/index.d.ts CHANGED
@@ -1,3 +1,5 @@
1
+ export * from './ai-file.service.js';
2
+ export * from './ai-session.js';
1
3
  export * from './ai.service.js';
2
4
  export * from './functions.js';
3
5
  export * from './types.js';
package/ai/index.js CHANGED
@@ -1,3 +1,5 @@
1
+ export * from './ai-file.service.js';
2
+ export * from './ai-session.js';
1
3
  export * from './ai.service.js';
2
4
  export * from './functions.js';
3
5
  export * from './types.js';
package/ai/types.d.ts CHANGED
@@ -1,27 +1,84 @@
1
- import type { ObjectSchema } from '../schema/index.js';
2
- import type { Record, UndefinableJson } from '../types.js';
1
+ import type { LiteralUnion } from 'type-fest';
2
+ import type { ObjectSchema, SchemaOutput, SchemaTestable } from '../schema/index.js';
3
+ import type { Record, UndefinableJsonObject } from '../types.js';
3
4
  export type FileInput = {
4
5
  path: string;
5
6
  mimeType: string;
6
7
  } | Blob;
7
8
  export type SchemaFunctionDeclarations = Record<string, SchemaFunctionDeclaration<any>>;
8
- export type SchemaFunctionDeclaration<T extends Record = Record, R = unknown> = {
9
+ export type SchemaFunctionDeclarationWithoutHandler<T extends Record = Record> = {
9
10
  description: string;
10
11
  parameters: ObjectSchema<T>;
12
+ };
13
+ export type SchemaFunctionDeclarationWithHandler<T extends Record = Record, R = unknown> = SchemaFunctionDeclarationWithoutHandler<T> & {
11
14
  handler: (parameters: T) => R | Promise<R>;
12
15
  };
16
+ export type SchemaFunctionDeclaration<T extends Record = Record, R = unknown> = SchemaFunctionDeclarationWithoutHandler<T> | SchemaFunctionDeclarationWithHandler<T, R>;
17
+ export type SchemaFunctionDeclarationHandlerResult<T extends SchemaFunctionDeclaration> = T extends SchemaFunctionDeclarationWithHandler<any, infer R> ? R : never;
18
+ export type SchemaFunctionDeclarationsResult<T extends SchemaFunctionDeclarations = SchemaFunctionDeclarations> = {
19
+ [P in keyof T]: {
20
+ functionName: P;
21
+ parameters: SchemaOutput<T[P]['parameters']>;
22
+ handlerResult: SchemaFunctionDeclarationHandlerResult<T[P]>;
23
+ };
24
+ }[keyof T];
25
+ export type ContentRole = 'user' | 'model';
26
+ export type TextContentPart = {
27
+ text: string;
28
+ };
29
+ export type FileContentPart = {
30
+ file: string;
31
+ };
32
+ export type FunctionCall = {
33
+ name: string;
34
+ parameters: UndefinableJsonObject;
35
+ };
36
+ export type FunctionCallContentPart = {
37
+ functionCall: FunctionCall;
38
+ };
39
+ export type FunctionResult = {
40
+ name: string;
41
+ value: UndefinableJsonObject;
42
+ };
43
+ export type FunctionResultContentPart = {
44
+ functionResult: FunctionResult;
45
+ };
46
+ export type ContentPart = TextContentPart | FileContentPart | FunctionCallContentPart | FunctionResultContentPart;
13
47
  export type Content = {
14
- role: 'user' | 'model';
48
+ role: ContentRole;
15
49
  parts: ContentPart[];
16
50
  };
17
- export type ContentPart = {
18
- text: string;
19
- } | {
20
- file: FileInput;
21
- } | {
22
- functionResult: {
23
- name: string;
24
- value: NonNullable<UndefinableJson>;
25
- };
51
+ export type FunctionCallingMode = 'auto' | 'force' | 'none';
52
+ export type AiModel = LiteralUnion<'gemini-2.0-flash-exp' | 'gemini-exp-1206' | 'gemini-2.0-flash-thinking-exp-1219', string>;
53
+ export type GenerationOptions = {
54
+ maxOutputTokens?: number;
55
+ temperature?: number;
56
+ topP?: number;
57
+ topK?: number;
58
+ presencePenalty?: number;
59
+ frequencyPenalty?: number;
60
+ };
61
+ export type GenerationRequest = {
62
+ model?: AiModel;
63
+ systemInstruction?: string;
64
+ contents: Content[];
65
+ functions?: SchemaFunctionDeclarations;
66
+ functionCallingMode?: FunctionCallingMode;
67
+ generationSchema?: SchemaTestable;
68
+ generationOptions?: GenerationOptions;
69
+ };
70
+ export type GenerationUsage = {
71
+ prompt: number;
72
+ output: number;
73
+ total: number;
74
+ };
75
+ export type GenerationResult = {
76
+ content: Content;
77
+ text: string | null;
78
+ functionCalls: FunctionCall[];
79
+ usage: GenerationUsage;
26
80
  };
27
- export declare function declareFunction<T extends Record = Record, R = unknown>(description: string, parameters: ObjectSchema<T>, handler: (parameters: T) => R | Promise<R>): SchemaFunctionDeclaration<T, R>;
81
+ export declare function declareFunctions<T extends SchemaFunctionDeclarations>(declarations: T): T;
82
+ export declare function declareFunction<T extends Record = Record>(description: string, parameters: ObjectSchema<T>): SchemaFunctionDeclaration<T, never>;
83
+ export declare function declareFunction<T extends Record = Record, R = never>(description: string, parameters: ObjectSchema<T>, handler: (parameters: T) => R | Promise<R>): SchemaFunctionDeclaration<T, R>;
84
+ export declare function isSchemaFunctionDeclarationWithHandler(declaration: SchemaFunctionDeclaration): declaration is SchemaFunctionDeclarationWithHandler;
package/ai/types.js CHANGED
@@ -1,3 +1,14 @@
1
+ import { hasOwnProperty } from '../utils/object/object.js';
2
+ import { isDefined } from '../utils/type-guards.js';
3
+ export function declareFunctions(declarations) {
4
+ return declarations;
5
+ }
1
6
  export function declareFunction(description, parameters, handler) {
2
- return { description, parameters, handler };
7
+ if (isDefined(handler)) {
8
+ return { description, parameters, handler };
9
+ }
10
+ return { description, parameters };
11
+ }
12
+ export function isSchemaFunctionDeclarationWithHandler(declaration) {
13
+ return hasOwnProperty(declaration, 'handler');
3
14
  }
@@ -5,7 +5,7 @@ export declare class WeakRefMap<K, V extends object> extends Collection<[K, V],
5
5
  private finalizationRegistry;
6
6
  readonly [Symbol.toStringTag] = "WeakRefMap";
7
7
  /**
8
- * provides the real size. This is slow because it requires a cleanup iteration
8
+ * Provides the real size. This is slow because it requires a cleanup iteration
9
9
  */
10
10
  get realSize(): number;
11
11
  static get supported(): boolean;
@@ -17,7 +17,7 @@ export declare class WeakRefMap<K, V extends object> extends Collection<[K, V],
17
17
  addMany(values: Iterable<[K, V]>): void;
18
18
  set(key: K, value: V): this;
19
19
  delete(key: K): boolean;
20
- /** prune garbage collected entries */
20
+ /** Prune garbage collected entries */
21
21
  cleanup(): void;
22
22
  clone(): WeakRefMap<K, V>;
23
23
  forEach(callback: (value: V, key: K, map: WeakRefMap<K, V>) => void, thisArg?: any): void;
@@ -8,7 +8,7 @@ export class WeakRefMap extends Collection {
8
8
  finalizationRegistry;
9
9
  [Symbol.toStringTag] = 'WeakRefMap';
10
10
  /**
11
- * provides the real size. This is slow because it requires a cleanup iteration
11
+ * Provides the real size. This is slow because it requires a cleanup iteration
12
12
  */
13
13
  get realSize() {
14
14
  this.cleanup();
@@ -77,10 +77,10 @@ export class WeakRefMap extends Collection {
77
77
  }
78
78
  return deleted;
79
79
  }
80
- /** prune garbage collected entries */
80
+ /** Prune garbage collected entries */
81
81
  cleanup() {
82
82
  for (const _ of this) {
83
- // ignore
83
+ // Ignore
84
84
  }
85
85
  }
86
86
  clone() {
@@ -32,8 +32,8 @@ export declare const documentManagementApiDefinition: {
32
32
  resource: string;
33
33
  method: "GET";
34
34
  parameters: import("../../schema/index.js").ObjectSchema<{
35
- readonly title: string | null;
36
35
  readonly id: string;
36
+ readonly title: string | null;
37
37
  readonly download?: boolean | undefined;
38
38
  }>;
39
39
  result: Uint8ArrayConstructor;
@@ -43,8 +43,8 @@ export declare const documentManagementApiDefinition: {
43
43
  resource: string;
44
44
  method: "GET";
45
45
  parameters: import("../../schema/index.js").ObjectSchema<{
46
- readonly title: string | null;
47
46
  readonly id: string;
47
+ readonly title: string | null;
48
48
  readonly download?: boolean | undefined;
49
49
  }>;
50
50
  result: import("../../schema/index.js").StringSchema;
@@ -406,8 +406,8 @@ declare const _DocumentManagementApi: import("../../api/index.js").ApiClient<{
406
406
  resource: string;
407
407
  method: "GET";
408
408
  parameters: import("../../schema/index.js").ObjectSchema<{
409
- readonly title: string | null;
410
409
  readonly id: string;
410
+ readonly title: string | null;
411
411
  readonly download?: boolean | undefined;
412
412
  }>;
413
413
  result: Uint8ArrayConstructor;
@@ -417,8 +417,8 @@ declare const _DocumentManagementApi: import("../../api/index.js").ApiClient<{
417
417
  resource: string;
418
418
  method: "GET";
419
419
  parameters: import("../../schema/index.js").ObjectSchema<{
420
- readonly title: string | null;
421
420
  readonly id: string;
421
+ readonly title: string | null;
422
422
  readonly download?: boolean | undefined;
423
423
  }>;
424
424
  result: import("../../schema/index.js").StringSchema;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tstdl/base",
3
- "version": "0.92.11",
3
+ "version": "0.92.13",
4
4
  "author": "Patrick Hein",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -134,7 +134,7 @@
134
134
  "@types/node": "22",
135
135
  "@types/nodemailer": "6.4",
136
136
  "@types/pg": "8.11",
137
- "@typescript-eslint/eslint-plugin": "8.19",
137
+ "@typescript-eslint/eslint-plugin": "8.20",
138
138
  "concurrently": "9.1",
139
139
  "drizzle-kit": "0.30",
140
140
  "eslint": "8.57",
@@ -19,7 +19,7 @@ export type SchemaTestResult<T> = {
19
19
  error: SchemaError;
20
20
  };
21
21
  type NormalizePrimitiveToConstructor<T> = Or<IsEqual<T, string>, IsEqual<T, String>> extends true ? typeof String : Or<IsEqual<T, number>, IsEqual<T, Number>> extends true ? typeof Number : Or<IsEqual<T, boolean>, IsEqual<T, Boolean>> extends true ? typeof Boolean : Or<IsEqual<T, bigint>, IsEqual<T, BigInt>> extends true ? typeof BigInt : Or<IsEqual<T, symbol>, IsEqual<T, Symbol>> extends true ? typeof Symbol : never;
22
- export type SchemaTestable<T = unknown> = Schema<T> | AbstractConstructor<T> | NormalizePrimitiveToConstructor<T>;
22
+ export type SchemaTestable<T = any> = Schema<T> | AbstractConstructor<T> | NormalizePrimitiveToConstructor<T>;
23
23
  export type SchemaOutput<T extends SchemaTestable> = T extends SchemaTestable<infer U> ? U : never;
24
24
  export declare const OPTIONAL: unique symbol;
25
25
  export type SchemaOptions<_T> = {
@@ -1,6 +1,6 @@
1
1
  import { SchemaError } from '../../schema/schema.error.js';
2
2
  import { lazyProperty } from '../../utils/object/lazy-property.js';
3
- import { isArray } from '../../utils/type-guards.js';
3
+ import { isArray, isNotNull } from '../../utils/type-guards.js';
4
4
  import { typeOf } from '../../utils/type-of.js';
5
5
  import { PropertySchema } from '../decorators/index.js';
6
6
  import { Schema } from '../schema.js';
@@ -26,6 +26,12 @@ export class ArraySchema extends Schema {
26
26
  }
27
27
  return { valid: false, error: SchemaError.expectedButGot('array', typeOf(value), path) };
28
28
  }
29
+ if (isNotNull(this.maximum) && (value.length > this.maximum)) {
30
+ throw new Error(`A maximum of ${this.maximum} items are allowed.`);
31
+ }
32
+ if (isNotNull(this.minimum) && (value.length < this.minimum)) {
33
+ throw new Error(`A minimum of ${this.minimum} items are required.`);
34
+ }
29
35
  const values = [];
30
36
  for (let i = 0; i < value.length; i++) {
31
37
  const result = this.itemSchema._test(value[i], path.add(i), options);
@@ -1,6 +1,6 @@
1
1
  import type { JsonPath } from '../../json-path/json-path.js';
2
2
  import type { OneOrMany as OneOrManyType } from '../../types.js';
3
- import { type SchemaPropertyDecorator, type SchemaDecoratorOptions } from '../decorators/index.js';
3
+ import { type SchemaDecoratorOptions, type SchemaPropertyDecorator } from '../decorators/index.js';
4
4
  import { Schema, type SchemaOptions, type SchemaTestable, type SchemaTestOptions, type SchemaTestResult } from '../schema.js';
5
5
  export type OneOrManySchemaOptions<T> = SchemaOptions<T | T[]>;
6
6
  export type OneOrMany<T> = OneOrManyType<T>;