@tstdl/base 0.93.35 → 0.93.37

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/ai/ai.service.js CHANGED
@@ -209,8 +209,10 @@ let AiService = AiService_1 = class AiService {
209
209
  const inputContent = this.convertContents(request.contents);
210
210
  let iterations = 0;
211
211
  let totalPromptTokens = 0;
212
+ let totalCachedTokens = 0;
213
+ let totalThoughtsTokens = 0;
212
214
  let totalOutputTokens = 0;
213
- let totalTokens = 0;
215
+ let totalToolsTokens = 0;
214
216
  while (totalOutputTokens < maxTotalOutputTokens) {
215
217
  let generation;
216
218
  let triesLeft = 10;
@@ -250,7 +252,7 @@ let AiService = AiService_1 = class AiService {
250
252
  throw new Error('No content returned.');
251
253
  }
252
254
  inputContent.push(rawContent);
253
- const { promptTokenCount = 0, candidatesTokenCount = 0 } = generationResponse.usageMetadata ?? {};
255
+ const { cachedContentTokenCount = 0, candidatesTokenCount = 0, promptTokenCount = 0, thoughtsTokenCount = 0, toolUsePromptTokenCount = 0, totalTokenCount = 0, } = generationResponse.usageMetadata ?? {};
254
256
  lastUsageMetadata = generationResponse.usageMetadata;
255
257
  const content = this.convertGoogleContent(rawContent);
256
258
  let text;
@@ -280,16 +282,21 @@ let AiService = AiService_1 = class AiService {
280
282
  : 'unknown',
281
283
  usage: {
282
284
  iterations,
285
+ cached: totalCachedTokens + cachedContentTokenCount,
283
286
  prompt: totalPromptTokens + promptTokenCount,
287
+ thoughts: totalThoughtsTokens + thoughtsTokenCount,
284
288
  output: totalOutputTokens + candidatesTokenCount,
285
- total: totalTokens + promptTokenCount + candidatesTokenCount,
289
+ tools: totalToolsTokens + toolUsePromptTokenCount,
290
+ total: totalTokenCount,
286
291
  },
287
292
  };
288
293
  yield result;
289
294
  }
295
+ totalCachedTokens += lastUsageMetadata?.cachedContentTokenCount ?? 0;
290
296
  totalPromptTokens += lastUsageMetadata?.promptTokenCount ?? 0;
297
+ totalThoughtsTokens += lastUsageMetadata?.thoughtsTokenCount ?? 0;
291
298
  totalOutputTokens += lastUsageMetadata?.responseTokenCount ?? 0;
292
- totalTokens += lastUsageMetadata?.totalTokenCount ?? 0;
299
+ totalToolsTokens += lastUsageMetadata?.toolUsePromptTokenCount ?? 0;
293
300
  if (candidate?.finishReason != FinishReason.MAX_TOKENS) {
294
301
  break;
295
302
  }
@@ -455,7 +462,7 @@ export function mergeGenerationStreamItems(items, schema) {
455
462
  json: undefined,
456
463
  functionCalls: [],
457
464
  finishReason: 'unknown',
458
- usage: { iterations: 0, prompt: 0, output: 0, total: 0 },
465
+ usage: { iterations: 0, cached: 0, prompt: 0, thoughts: 0, output: 0, tools: 0, total: 0 },
459
466
  };
460
467
  }
461
468
  const parts = items.flatMap((item) => item.content.parts);
package/ai/types.d.ts CHANGED
@@ -168,11 +168,17 @@ export type GenerationRequest<S = unknown> = {
168
168
  export type GenerationUsage = {
169
169
  /** The number of generation iterations (usually 1, more if `maxOutputTokens` is hit and generation continues). */
170
170
  iterations: number;
171
- /** The number of tokens in the prompt. */
171
+ /** Tokens cached. */
172
+ cached: number;
173
+ /** Tokens used in the prompt. */
172
174
  prompt: number;
173
- /** The number of tokens in the generated output. */
175
+ /** Tokens used for internal thoughts. */
176
+ thoughts: number;
177
+ /** Tokens in the generated output. */
174
178
  output: number;
175
- /** The total number of tokens used. */
179
+ /** Tokens used for tools. */
180
+ tools: number;
181
+ /** Total tokens used. */
176
182
  total: number;
177
183
  };
178
184
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tstdl/base",
3
- "version": "0.93.35",
3
+ "version": "0.93.37",
4
4
  "author": "Patrick Hein",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -1,5 +1,5 @@
1
1
  import { NotSupportedError } from '../../errors/not-supported.error.js';
2
- import { fromEntries, hasOwnProperty, objectEntries } from '../../utils/object/object.js';
2
+ import { filterUndefinedObjectProperties, fromEntries, hasOwnProperty, objectEntries } from '../../utils/object/object.js';
3
3
  import { isDefined, isNotNull, isNumber, isString } from '../../utils/type-guards.js';
4
4
  import { ArraySchema } from '../schemas/array.js';
5
5
  import { BooleanSchema } from '../schemas/boolean.js';
@@ -16,29 +16,36 @@ import { schemaTestableToSchema } from '../testable.js';
16
16
  export function convertToOpenApiSchema(testable) {
17
17
  const schema = schemaTestableToSchema(testable);
18
18
  const openApiSchema = convertToOpenApiSchemaBase(schema);
19
- return {
19
+ return filterUndefinedObjectProperties({
20
20
  ...openApiSchema,
21
- ...(hasOwnProperty(openApiSchema, 'nullable') ? undefined : { nullable: false }),
22
- ...(isNotNull(schema.description) ? { description: schema.description } : undefined),
23
- ...(isDefined(schema.example) ? { example: schema.example } : undefined),
24
- };
21
+ nullable: hasOwnProperty(openApiSchema, 'nullable') ? undefined : false,
22
+ title: ((schema instanceof ObjectSchema) && (schema.name != 'Object')) ? schema.name : undefined,
23
+ description: isNotNull(schema.description) ? schema.description : undefined,
24
+ example: isDefined(schema.example) ? schema.example : undefined,
25
+ });
25
26
  }
26
27
  function convertToOpenApiSchemaBase(schema) {
27
28
  if (schema instanceof ObjectSchema) {
28
29
  const entries = objectEntries(schema.properties);
29
30
  const convertedEntries = entries.map(([property, propertySchema]) => [property, convertToOpenApiSchema(stripOptional(propertySchema))]);
30
- return {
31
+ const required = entries
32
+ .filter(([, propertySchema]) => !(propertySchema instanceof OptionalSchema) && !((propertySchema instanceof NullableSchema) && (propertySchema.schema instanceof OptionalSchema)))
33
+ .map(([property]) => property);
34
+ return filterUndefinedObjectProperties({
31
35
  type: 'object',
32
36
  properties: fromEntries(convertedEntries),
33
- required: entries
34
- .filter(([, propertySchema]) => !(propertySchema instanceof OptionalSchema) && !((propertySchema instanceof NullableSchema) && (propertySchema.schema instanceof OptionalSchema)))
35
- .map(([property]) => property),
36
- };
37
+ required: (required.length > 0) ? required : undefined,
38
+ minProperties: isNumber(schema.minimumPropertiesCount) ? schema.minimumPropertiesCount : undefined,
39
+ maxProperties: isNumber(schema.maximumPropertiesCount) ? schema.maximumPropertiesCount : undefined,
40
+ });
37
41
  }
38
42
  if (schema instanceof StringSchema) {
39
- return {
43
+ return filterUndefinedObjectProperties({
40
44
  type: 'string',
41
- };
45
+ minLength: isNumber(schema.minimumLength) ? schema.minimumLength : undefined,
46
+ maxLength: isNumber(schema.maximumLength) ? schema.maximumLength : undefined,
47
+ pattern: isString(schema.pattern) ? schema.pattern : undefined,
48
+ });
42
49
  }
43
50
  if (schema instanceof DateSchema) {
44
51
  return {
@@ -47,11 +54,11 @@ function convertToOpenApiSchemaBase(schema) {
47
54
  };
48
55
  }
49
56
  if (schema instanceof NumberSchema) {
50
- return {
57
+ return filterUndefinedObjectProperties({
51
58
  type: schema.integer ? 'integer' : 'number',
52
- ...(isNumber(schema.minimum) ? { minimum: schema.minimum } : undefined),
53
- ...(isNumber(schema.maximum) ? { maximum: schema.maximum } : undefined),
54
- };
59
+ minimum: isNumber(schema.minimum) ? schema.minimum : undefined,
60
+ maximum: isNumber(schema.maximum) ? schema.maximum : undefined,
61
+ });
55
62
  }
56
63
  if (schema instanceof BooleanSchema) {
57
64
  return {
@@ -65,20 +72,20 @@ function convertToOpenApiSchemaBase(schema) {
65
72
  };
66
73
  }
67
74
  if (schema instanceof ArraySchema) {
68
- return {
75
+ return filterUndefinedObjectProperties({
69
76
  type: 'array',
70
77
  items: convertToOpenApiSchema(schema.itemSchema),
71
- ...(isNumber(schema.minimum) ? { minItems: schema.minimum } : undefined),
72
- ...(isNumber(schema.maximum) ? { maxItems: schema.maximum } : undefined),
73
- };
78
+ minItems: isNumber(schema.minimum) ? schema.minimum : undefined,
79
+ maxItems: isNumber(schema.maximum) ? schema.maximum : undefined,
80
+ });
74
81
  }
75
82
  if (schema instanceof EnumerationSchema) {
76
83
  const hasString = schema.allowedValues.some(isString);
77
84
  const hasNumber = schema.allowedValues.some(isNumber);
78
- if (schema.allowedValues.length === 0) {
85
+ if (schema.allowedValues.length == 0) {
79
86
  throw new NotSupportedError('Enum must have at least one value.');
80
87
  }
81
- if (!hasString && !hasNumber) {
88
+ if (hasString && hasNumber) {
82
89
  throw new NotSupportedError('Enum must be either string or number but not both.');
83
90
  }
84
91
  return {
@@ -102,7 +109,7 @@ function convertToOpenApiSchemaBase(schema) {
102
109
  }
103
110
  if (schema instanceof UnionSchema) {
104
111
  return {
105
- oneOf: schema.schemas.map((innerSchema) => convertToOpenApiSchema(innerSchema)),
112
+ anyOf: schema.schemas.map((innerSchema) => convertToOpenApiSchema(innerSchema)),
106
113
  };
107
114
  }
108
115
  throw new NotSupportedError(`Schema "${schema.name}" not supported.`);
@@ -20,6 +20,8 @@ export type ObjectSchemaOptions<T extends Record = Record, K extends PropertyKey
20
20
  mask?: boolean | null;
21
21
  unknownPropertiesKey?: SchemaTestable<K> | null;
22
22
  unknownProperties?: SchemaTestable<V> | null;
23
+ minimumPropertiesCount?: number | null;
24
+ maximumPropertiesCount?: number | null;
23
25
  factory?: ObjectSchemaFactory<T> | null;
24
26
  };
25
27
  export type ObjectSchemaOrType<T extends Record = any> = ObjectSchema<T> | AbstractConstructor<T>;
@@ -40,6 +42,8 @@ export declare class ObjectSchema<T extends Record = Record> extends Schema<T> {
40
42
  readonly mask: boolean | null;
41
43
  readonly unknownProperties: Schema | null;
42
44
  readonly unknownPropertiesKey: Schema<PropertyKey> | null;
45
+ readonly minimumPropertiesCount: number | null;
46
+ readonly maximumPropertiesCount: number | null;
43
47
  readonly factory: ObjectSchemaFactory<T> | null;
44
48
  constructor(properties: ObjectSchemaProperties<T>, options?: ObjectSchemaOptions<T>);
45
49
  _test(value: any, path: JsonPath, options: SchemaTestOptions): SchemaTestResult<T>;
@@ -20,6 +20,8 @@ export class ObjectSchema extends Schema {
20
20
  mask;
21
21
  unknownProperties;
22
22
  unknownPropertiesKey;
23
+ minimumPropertiesCount;
24
+ maximumPropertiesCount;
23
25
  factory;
24
26
  constructor(properties, options = {}) {
25
27
  super(options);
@@ -27,6 +29,8 @@ export class ObjectSchema extends Schema {
27
29
  this.mask = options.mask ?? null;
28
30
  this.unknownProperties = isNotNullOrUndefined(options.unknownProperties) ? schemaTestableToSchema(options.unknownProperties) : null;
29
31
  this.unknownPropertiesKey = isNotNullOrUndefined(options.unknownPropertiesKey) ? schemaTestableToSchema(options.unknownPropertiesKey) : null;
32
+ this.minimumPropertiesCount = options.minimumPropertiesCount ?? null;
33
+ this.maximumPropertiesCount = options.maximumPropertiesCount ?? null;
30
34
  this.factory = options.factory ?? null;
31
35
  this.allowUnknownProperties = isNotNull(this.unknownProperties) || isNotNull(this.unknownPropertiesKey);
32
36
  this.propertyKeys = new Set(objectKeys(properties));
@@ -38,7 +42,14 @@ export class ObjectSchema extends Schema {
38
42
  }
39
43
  const mask = this.mask ?? options.mask ?? false;
40
44
  const resultValue = isLiteralObject(this.factory) ? new this.factory.type() : {};
41
- const valueKeys = new Set(objectKeys(value));
45
+ const keys = objectKeys(value);
46
+ if (isNotNull(this.minimumPropertiesCount) && (keys.length < this.minimumPropertiesCount)) {
47
+ return { valid: false, error: new SchemaError(`Object has too few properties (minimum ${this.minimumPropertiesCount}, got ${keys.length}).`, { path, fast: options.fastErrors }) };
48
+ }
49
+ if (isNotNull(this.maximumPropertiesCount) && (keys.length > this.maximumPropertiesCount)) {
50
+ return { valid: false, error: new SchemaError(`Object has too many properties (maximum ${this.maximumPropertiesCount}, got ${keys.length}).`, { path, fast: options.fastErrors }) };
51
+ }
52
+ const valueKeys = new Set(keys);
42
53
  const unknownValuePropertyKeys = valueKeys.difference(this.propertyKeys);
43
54
  if ((unknownValuePropertyKeys.size > 0) && !mask && !this.allowUnknownProperties) {
44
55
  return { valid: false, error: new SchemaError('Unexpected property', { path: path.add(unknownValuePropertyKeys.values().next().value), fast: options.fastErrors }) };
@@ -3,11 +3,15 @@ import { SimpleSchema, type SimpleSchemaOptions } from './simple.js';
3
3
  export type StringSchemaOptions = SimpleSchemaOptions<string> & {
4
4
  pattern?: RegExp | string;
5
5
  lowercase?: boolean;
6
+ minimumLength?: number;
7
+ maximumLength?: number;
6
8
  };
7
9
  export declare class StringSchema extends SimpleSchema<string> {
8
10
  readonly name: string;
9
11
  readonly pattern: RegExp | null;
10
12
  readonly lowercase: boolean;
13
+ readonly minimumLength: number | null;
14
+ readonly maximumLength: number | null;
11
15
  constructor(options?: StringSchemaOptions);
12
16
  }
13
17
  export declare function string(options?: StringSchemaOptions): StringSchema;
@@ -5,6 +5,8 @@ export class StringSchema extends SimpleSchema {
5
5
  name = 'string';
6
6
  pattern;
7
7
  lowercase;
8
+ minimumLength;
9
+ maximumLength;
8
10
  constructor(options) {
9
11
  super('string', isString, options, {
10
12
  coercers: {
@@ -16,6 +18,12 @@ export class StringSchema extends SimpleSchema {
16
18
  isDefined(options?.pattern)
17
19
  ? (value) => this.pattern.test(value) ? ({ success: true }) : ({ success: false, error: 'Value did not match pattern.' })
18
20
  : null,
21
+ isDefined(options?.minimumLength)
22
+ ? (value) => (value.length >= this.minimumLength) ? ({ success: true }) : ({ success: false, error: `String length is less than minimum length of ${this.minimumLength}.` })
23
+ : null,
24
+ isDefined(options?.maximumLength)
25
+ ? (value) => (value.length <= this.maximumLength) ? ({ success: true }) : ({ success: false, error: `String length exceeds maximum length of ${this.maximumLength}.` })
26
+ : null,
19
27
  ],
20
28
  transformers: (options?.lowercase ?? false) ? (value) => value.toLowerCase() : undefined,
21
29
  });
@@ -24,6 +32,9 @@ export class StringSchema extends SimpleSchema {
24
32
  : isRegExp(options?.pattern)
25
33
  ? options.pattern
26
34
  : null;
35
+ this.lowercase = options?.lowercase ?? false;
36
+ this.minimumLength = options?.minimumLength ?? null;
37
+ this.maximumLength = options?.maximumLength ?? null;
27
38
  }
28
39
  }
29
40
  export function string(options) {
package/test1.js CHANGED
@@ -14,6 +14,7 @@ import { migrateTestSchema } from './test/module.js';
14
14
  import { Test, testData } from './test/test.model.js';
15
15
  import { timedBenchmarkAsync } from './utils/benchmark.js';
16
16
  import * as configParser from './utils/config-parser.js';
17
+ import { string } from './utils/config-parser.js';
17
18
  const config = {
18
19
  database: {
19
20
  host: configParser.string('DATABASE_HOST', '127.0.0.1'),
@@ -22,6 +23,14 @@ const config = {
22
23
  pass: configParser.string('DATABASE_PASS', 'wf7rq6glrk5jykne'),
23
24
  database: configParser.string('DATABASE_NAME', 'tstdl'),
24
25
  },
26
+ ai: {
27
+ apiKey: string('AI_API_KEY', undefined),
28
+ keyFile: string('AI_API_KEY_FILE', undefined),
29
+ vertex: {
30
+ project: string('AI_VERTEX_PROJECT', undefined),
31
+ location: string('AI_VERTEX_LOCATION', undefined),
32
+ },
33
+ },
25
34
  };
26
35
  async function bootstrap() {
27
36
  const injector = inject(Injector);
@@ -30,7 +39,7 @@ async function bootstrap() {
30
39
  project: '922353391551', // insolytics-application
31
40
  location: 'europe-west4', // netherlands
32
41
  },
33
- keyFile: '/home/patrick/.secret-files/insolytics-application-service-key.json',
42
+ keyFile: '/home/patrick/.secret-files/insolytic-application-service-key.json',
34
43
  });
35
44
  /*
36
45
  configureOrm({
@@ -65,20 +74,22 @@ async function bootstrap() {
65
74
  }
66
75
  async function main(_cancellationSignal) {
67
76
  const aiService = inject(AiService);
68
- const aiResult = await aiService.generate({
77
+ const aiStream = aiService.generateStream({
69
78
  generationOptions: {
70
79
  includeThoughts: true,
80
+ thinkingBudget: 1000,
71
81
  },
72
82
  model: 'gemini-2.5-flash-lite',
73
83
  contents: [{
74
84
  role: 'user',
75
85
  parts: [
76
- { text: 'asd' },
86
+ { text: 'Count from 1 to 1000. List *every* number.' },
77
87
  ],
78
88
  }],
79
89
  });
80
- console.log('AI Result:', aiResult);
81
- console.log(aiResult.content);
90
+ for await (const { text, usage } of aiStream) {
91
+ console.log({ text, usage });
92
+ }
82
93
  if (1 + 1 == 2)
83
94
  process.exit(0);
84
95
  }