typia 14.0.6 → 15.0.0

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 (72) hide show
  1. package/README.md +3 -0
  2. package/lib/internal/_createLlmEvaluation.d.ts +1 -0
  3. package/lib/internal/_createLlmEvaluation.js +486 -0
  4. package/lib/internal/_createLlmEvaluation.js.map +1 -0
  5. package/lib/internal/_createLlmEvaluation.mjs +382 -0
  6. package/lib/internal/_createLlmEvaluation.mjs.map +1 -0
  7. package/lib/internal/_stringLengthGte.d.ts +1 -0
  8. package/lib/internal/_stringLengthGte.js +14 -0
  9. package/lib/internal/_stringLengthGte.js.map +1 -0
  10. package/lib/internal/_stringLengthGte.mjs +11 -0
  11. package/lib/internal/_stringLengthGte.mjs.map +1 -0
  12. package/lib/internal/_stringLengthLte.d.ts +1 -0
  13. package/lib/internal/_stringLengthLte.js +14 -0
  14. package/lib/internal/_stringLengthLte.js.map +1 -0
  15. package/lib/internal/_stringLengthLte.mjs +11 -0
  16. package/lib/internal/_stringLengthLte.mjs.map +1 -0
  17. package/lib/llm.d.ts +54 -1
  18. package/lib/llm.js +5 -0
  19. package/lib/llm.js.map +1 -1
  20. package/lib/llm.mjs +6 -1
  21. package/lib/llm.mjs.map +1 -1
  22. package/lib/re-exports.d.ts +1 -1
  23. package/lib/re-exports.js.map +1 -1
  24. package/native/cmd/ttsc-typia/build.go +2 -0
  25. package/native/cmd/ttsc-typia/dynamic_key_tags_transform_test.go +1 -1
  26. package/native/cmd/ttsc-typia/llm_evaluation_accepts_property_probability_test.go +27 -0
  27. package/native/cmd/ttsc-typia/llm_evaluation_accepts_unselected_source_probability_test.go +26 -0
  28. package/native/cmd/ttsc-typia/llm_evaluation_complete_object_shape_test.go +96 -0
  29. package/native/cmd/ttsc-typia/llm_evaluation_ignores_declaration_probability_without_call_test.go +18 -0
  30. package/native/cmd/ttsc-typia/llm_evaluation_plan_emission_transform_test.go +90 -0
  31. package/native/cmd/ttsc-typia/llm_evaluation_program_scan_dependencies_test.go +69 -0
  32. package/native/cmd/ttsc-typia/llm_evaluation_rejects_alias_comment_test.go +44 -0
  33. package/native/cmd/ttsc-typia/llm_evaluation_rejects_alias_refinement_test.go +45 -0
  34. package/native/cmd/ttsc-typia/llm_evaluation_rejects_computed_method_probability_test.go +30 -0
  35. package/native/cmd/ttsc-typia/llm_evaluation_rejects_imported_declaration_probability_test.go +48 -0
  36. package/native/cmd/ttsc-typia/llm_evaluation_rejects_invalid_probability_test.go +199 -0
  37. package/native/cmd/ttsc-typia/llm_evaluation_rejects_refinements_test.go +94 -0
  38. package/native/cmd/ttsc-typia/llm_evaluation_rejects_undecidable_types_test.go +232 -0
  39. package/native/cmd/ttsc-typia/metadata_type_tag_sole_malformed_diagnostic_test.go +94 -0
  40. package/native/cmd/ttsc-typia/template_literal_type_tags_transform_test.go +3 -2
  41. package/native/cmd/ttsc-typia/transform.go +2 -0
  42. package/native/cmd/ttsc-typia/transform_helpers_test.go +17 -3
  43. package/native/core/context/ITypiaContext.go +4 -0
  44. package/native/core/factories/IdentifierFactory.go +10 -0
  45. package/native/core/factories/LiteralFactory.go +16 -0
  46. package/native/core/factories/MetadataCommentTagFactory.go +12 -10
  47. package/native/core/factories/MetadataTypeTagFactory.go +97 -24
  48. package/native/core/factories/MetadataTypeTagSchemaFactory.go +5 -0
  49. package/native/core/factories/internal/metadata/IMetadataIteratorProps.go +6 -2
  50. package/native/core/factories/internal/metadata/emplace_metadata_object.go +49 -15
  51. package/native/core/factories/literal_factory_null_survives_objects_test.go +43 -0
  52. package/native/core/factories/metadata_comment_tag_factory_string_length_helpers_test.go +55 -0
  53. package/native/core/factories/metadata_type_tag_factory_reports_all_invalid_candidates_test.go +73 -0
  54. package/native/core/programmers/llm/LlmApplicationProgrammer.go +1 -0
  55. package/native/core/programmers/llm/LlmEvaluationProgrammer.go +555 -0
  56. package/native/core/programmers/llm/LlmParametersProgrammer.go +6 -5
  57. package/native/core/programmers/llm/LlmReferenceDescription.go +113 -0
  58. package/native/core/programmers/llm/llm_reference_description_test.go +134 -0
  59. package/native/core/schemas/metadata/MetadataDependency.go +9 -0
  60. package/native/transform/CallExpressionTransformer.go +1 -0
  61. package/native/transform/FileTransformer.go +5 -0
  62. package/native/transform/features/llm/LlmEvaluationProbabilityDeclarations.go +94 -0
  63. package/native/transform/features/llm/LlmEvaluationProbabilityDeclarations_test.go +50 -0
  64. package/native/transform/features/llm/LlmEvaluationTransformer.go +59 -0
  65. package/native/transform/features/llm/LlmSchemaTransformer.go +14 -11
  66. package/native/transform/features/llm/llm_transformer_entry_coverage_test.go +29 -27
  67. package/package.json +3 -3
  68. package/src/internal/_createLlmEvaluation.ts +678 -0
  69. package/src/internal/_stringLengthGte.ts +6 -0
  70. package/src/internal/_stringLengthLte.ts +6 -0
  71. package/src/llm.ts +64 -0
  72. package/src/re-exports.ts +1 -0
@@ -0,0 +1,678 @@
1
+ import { ILlmEvaluation, IValidation } from "@typia/interface";
2
+
3
+ import { _accessExpressionAsString } from "./_accessExpressionAsString";
4
+
5
+ /**
6
+ * Compile-time plan of `typia.llm.evaluation<T>()`, one entry per decision leaf
7
+ * of `T`.
8
+ *
9
+ * @internal
10
+ */
11
+ export type _ILlmEvaluationPlan =
12
+ | _ILlmEvaluationPlan.IBoolean
13
+ | _ILlmEvaluationPlan.IChoice
14
+ | _ILlmEvaluationPlan.IScore
15
+ | _ILlmEvaluationPlan.ISet;
16
+
17
+ /** @internal */
18
+ export namespace _ILlmEvaluationPlan {
19
+ interface IBase {
20
+ /** Property names from the root of `T` to the leaf. */
21
+ path: string[];
22
+ instructions: string;
23
+ }
24
+ export interface IBoolean extends IBase {
25
+ kind: "boolean";
26
+ threshold: number;
27
+ }
28
+ export interface IChoice extends IBase {
29
+ kind: "choice";
30
+ options: IMember<string>[];
31
+ }
32
+ export interface IScore extends IBase {
33
+ kind: "score";
34
+ /** Sorted in ascending value order; level `i` is `levels[i]`. */
35
+ levels: IMember<number>[];
36
+ }
37
+ export interface ISet extends IBase {
38
+ kind: "set";
39
+ members: ISetMember[];
40
+ }
41
+ export interface IMember<Value extends string | number> {
42
+ value: Value;
43
+ description?: string;
44
+ /** Acceptance minimum of the member, when it has one. */
45
+ minimum?: number;
46
+ }
47
+ export interface ISetMember {
48
+ value: string;
49
+ description?: string;
50
+ threshold: number;
51
+ }
52
+ }
53
+
54
+ /**
55
+ * Creates the `ILlmEvaluation` of `typia.llm.evaluation<T>()` from its plan.
56
+ *
57
+ * Both the question map and the decoder derive their keys from here, so the
58
+ * readable path encoding has a single owner.
59
+ *
60
+ * @internal
61
+ */
62
+ export const _createLlmEvaluation = <T>(
63
+ plan: _ILlmEvaluationPlan[],
64
+ ): ILlmEvaluation<T> => {
65
+ const questions: Record<string, ILlmEvaluation.IQuestion> = {};
66
+ for (const leaf of plan) {
67
+ if (leaf.kind === "set")
68
+ for (const member of leaf.members)
69
+ assign(questions, key([...leaf.path, member.value]), {
70
+ type: "boolean",
71
+ instructions: setInstructions(leaf, member),
72
+ });
73
+ else assign(questions, key(leaf.path), question(leaf));
74
+ }
75
+ return {
76
+ questions,
77
+ decode: (
78
+ answers: unknown,
79
+ rounding?: ILlmEvaluation.IRounding,
80
+ ): IValidation<T> => decode(plan, answers, rounding),
81
+ };
82
+ };
83
+
84
+ /* -----------------------------------------------------------
85
+ QUESTIONS
86
+ ----------------------------------------------------------- */
87
+ /**
88
+ * Encodes a property path as a question key, like `refund.requested`.
89
+ *
90
+ * Every segment after the first uses typia's accessor notation, so the key is
91
+ * the validation path without its `$input` root. The first segment is quoted
92
+ * when it is not an identifier, and always when it is `__proto__`, so no key
93
+ * can collide with another or reach an object's prototype.
94
+ */
95
+ const key = (path: string[]): string => {
96
+ const head: string = path[0]!;
97
+ const postfix: string = _accessExpressionAsString(head);
98
+ const first: string =
99
+ head === "__proto__"
100
+ ? `[${JSON.stringify(head)}]`
101
+ : postfix.startsWith(".")
102
+ ? head
103
+ : postfix;
104
+ return first + path.slice(1).map(_accessExpressionAsString).join("");
105
+ };
106
+
107
+ const question = (
108
+ leaf:
109
+ | _ILlmEvaluationPlan.IBoolean
110
+ | _ILlmEvaluationPlan.IChoice
111
+ | _ILlmEvaluationPlan.IScore,
112
+ ): ILlmEvaluation.IQuestion => {
113
+ if (leaf.kind === "boolean")
114
+ return { type: "boolean", instructions: leaf.instructions };
115
+ else if (leaf.kind === "choice") {
116
+ const criteria: Record<string, string | null> = {};
117
+ for (const option of leaf.options)
118
+ assign(criteria, option.value, option.description ?? null);
119
+ return { type: "choice", instructions: leaf.instructions, criteria };
120
+ }
121
+ return {
122
+ type: "score",
123
+ instructions: leaf.instructions,
124
+ // the transform rejects fewer than two levels
125
+ criteria: leaf.levels.map(
126
+ (level) => level.description ?? String(level.value),
127
+ ) as [string, string, ...string[]],
128
+ };
129
+ };
130
+
131
+ const setInstructions = (
132
+ leaf: _ILlmEvaluationPlan.ISet,
133
+ member: _ILlmEvaluationPlan.ISetMember,
134
+ ): string =>
135
+ [
136
+ leaf.instructions,
137
+ "",
138
+ `Does the option ${JSON.stringify(member.value)} apply?`,
139
+ ...(member.description !== undefined ? [member.description] : []),
140
+ ].join("\n");
141
+
142
+ /* -----------------------------------------------------------
143
+ VALIDATION
144
+ ----------------------------------------------------------- */
145
+ const decode = <T>(
146
+ plan: _ILlmEvaluationPlan[],
147
+ answers: unknown,
148
+ rounding?: ILlmEvaluation.IRounding,
149
+ ): IValidation<T> => {
150
+ const precision: IPrecision | null = (() => {
151
+ try {
152
+ return readPrecision(rounding);
153
+ } catch {
154
+ // A trapping rounding object is an invalid declaration.
155
+ return null;
156
+ }
157
+ })();
158
+ if (precision === null)
159
+ return {
160
+ success: false,
161
+ data: answers,
162
+ errors: [
163
+ {
164
+ path: "$input",
165
+ expected: "rounding decimals in [0, 15]",
166
+ value: rounding,
167
+ description:
168
+ "Evaluation rounding decimals must be integers between 0 and 15.",
169
+ },
170
+ ],
171
+ };
172
+ const map: Record<string, unknown> | null = object(answers);
173
+ if (map === null)
174
+ return {
175
+ success: false,
176
+ data: answers,
177
+ errors: [
178
+ {
179
+ path: "$input",
180
+ expected: "Record<string, ILlmEvaluation answer>",
181
+ value: answers,
182
+ },
183
+ ],
184
+ };
185
+
186
+ const errors: IValidation.IError[] = [];
187
+ const expected: Set<string> = new Set();
188
+ const output: Record<string, unknown> = {};
189
+ const read = (id: string): unknown => {
190
+ expected.add(id);
191
+ try {
192
+ return Object.prototype.hasOwnProperty.call(map, id)
193
+ ? map[id]
194
+ : undefined;
195
+ } catch {
196
+ // The decision reader reports the inaccessible answer on its own path.
197
+ return undefined;
198
+ }
199
+ };
200
+
201
+ for (const leaf of plan) {
202
+ const path: string = accessor(leaf.path);
203
+ if (leaf.kind === "set") {
204
+ const values: string[] = [];
205
+ for (const member of leaf.members) {
206
+ const memberPath: string = [
207
+ path,
208
+ _accessExpressionAsString(member.value),
209
+ ].join("");
210
+ const answer: unknown = read(key([...leaf.path, member.value]));
211
+ const probability: number | null = inspect(
212
+ memberPath,
213
+ answer,
214
+ errors,
215
+ () => booleanProbability(answer, memberPath, errors),
216
+ );
217
+ if (probability !== null && probability >= member.threshold)
218
+ values.push(member.value);
219
+ }
220
+ place(output, leaf.path, values);
221
+ continue;
222
+ }
223
+ const answer: unknown = read(key(leaf.path));
224
+ if (leaf.kind === "boolean") {
225
+ const probability: number | null = inspect(path, answer, errors, () =>
226
+ booleanProbability(answer, path, errors),
227
+ );
228
+ if (probability !== null)
229
+ place(output, leaf.path, probability >= leaf.threshold);
230
+ } else if (leaf.kind === "choice") {
231
+ const value: string | null = inspect(path, answer, errors, () =>
232
+ choice(leaf, answer, path, errors, precision),
233
+ );
234
+ if (value !== null) place(output, leaf.path, value);
235
+ } else {
236
+ const value: number | null = inspect(path, answer, errors, () =>
237
+ score(leaf, answer, path, errors, precision),
238
+ );
239
+ if (value !== null) place(output, leaf.path, value);
240
+ }
241
+ }
242
+ let received: string[];
243
+ try {
244
+ received = Object.keys(map);
245
+ } catch {
246
+ errors.push({
247
+ path: "$input",
248
+ expected: "readable evaluation answer map",
249
+ value: answers,
250
+ description: "Evaluation answer keys could not be inspected.",
251
+ });
252
+ received = [];
253
+ }
254
+ for (const id of received)
255
+ if (expected.has(id) === false)
256
+ try {
257
+ errors.push({
258
+ path: `$input${_accessExpressionAsString(id)}`,
259
+ expected: "undefined",
260
+ value: map[id],
261
+ description: "The answer does not belong to any question.",
262
+ });
263
+ } catch {
264
+ errors.push({
265
+ path: `$input${_accessExpressionAsString(id)}`,
266
+ expected: "readable evaluation answer",
267
+ value: undefined,
268
+ description: "The extra answer could not be inspected.",
269
+ });
270
+ }
271
+ return errors.length === 0
272
+ ? { success: true, data: output as T }
273
+ : { success: false, data: answers, errors };
274
+ };
275
+
276
+ /** Contain exceptions from getters and proxies at the decision path. */
277
+ const inspect = <T>(
278
+ path: string,
279
+ answer: unknown,
280
+ errors: IValidation.IError[],
281
+ read: () => T,
282
+ ): T | null => {
283
+ try {
284
+ return read();
285
+ } catch {
286
+ errors.push({
287
+ path,
288
+ expected: "readable evaluation answer",
289
+ value: answer,
290
+ description: "The answer could not be inspected.",
291
+ });
292
+ return null;
293
+ }
294
+ };
295
+
296
+ const booleanProbability = (
297
+ answer: unknown,
298
+ path: string,
299
+ errors: IValidation.IError[],
300
+ ): number | null => {
301
+ const record: Record<string, unknown> | null = object(answer);
302
+ const type: unknown = record?.type;
303
+ const probability: unknown =
304
+ record === null
305
+ ? undefined
306
+ : type === "boolean"
307
+ ? record.probability
308
+ : type === "noul"
309
+ ? record.noul
310
+ : undefined;
311
+ if (isProbability(probability)) return probability;
312
+ errors.push({
313
+ path,
314
+ expected:
315
+ '{ type: "boolean"; probability: number } | { type: "noul"; noul: number }',
316
+ value: answer,
317
+ description:
318
+ record === null
319
+ ? "Missing boolean answer."
320
+ : type !== "boolean" && type !== "noul"
321
+ ? `Answer type must be "boolean" or "noul", but got ${label(type)}.`
322
+ : "Boolean answer needs a probability in [0, 1].",
323
+ });
324
+ return null;
325
+ };
326
+
327
+ const choice = (
328
+ leaf: _ILlmEvaluationPlan.IChoice,
329
+ answer: unknown,
330
+ path: string,
331
+ errors: IValidation.IError[],
332
+ precision: IPrecision,
333
+ ): string | null => {
334
+ const expected: string = `{ type: "choice"; choice: ${leaf.options
335
+ .map((option) => JSON.stringify(option.value))
336
+ .join(" | ")}; probabilities?: Record<string, number> }`;
337
+ const record: Record<string, unknown> | null = object(answer);
338
+ const type: unknown = record?.type;
339
+ const selected: unknown = type === "choice" ? record?.choice : undefined;
340
+ const option: _ILlmEvaluationPlan.IMember<string> | undefined =
341
+ record !== null && type === "choice"
342
+ ? leaf.options.find((o) => o.value === selected)
343
+ : undefined;
344
+ if (record === null || option === undefined) {
345
+ errors.push({
346
+ path,
347
+ expected,
348
+ value: answer,
349
+ description:
350
+ record === null
351
+ ? "Missing choice answer."
352
+ : type !== "choice"
353
+ ? `Answer type must be "choice", but got ${label(type)}.`
354
+ : "Choice answer must select one of the declared options.",
355
+ });
356
+ return null;
357
+ }
358
+ const probabilities: Record<string, number> | null | undefined = distribution(
359
+ record.probabilities,
360
+ leaf.options.map((o) => o.value),
361
+ precision.probability,
362
+ );
363
+ if (probabilities === null) {
364
+ errors.push({
365
+ path,
366
+ expected,
367
+ value: answer,
368
+ description:
369
+ "Choice probabilities must contain every declared option exactly once, use numbers in [0, 1], and sum to 1.",
370
+ });
371
+ return null;
372
+ }
373
+ if (probabilities !== undefined) {
374
+ const selected: number = own(probabilities, option.value)!;
375
+ if (
376
+ Object.values(probabilities).some(
377
+ (probability) => probability > selected + PROBABILITY_TOLERANCE,
378
+ )
379
+ ) {
380
+ errors.push({
381
+ path,
382
+ expected,
383
+ value: answer,
384
+ description:
385
+ "Choice answer must select an option with maximum probability.",
386
+ });
387
+ return null;
388
+ }
389
+ }
390
+ return accept(
391
+ option,
392
+ option.value,
393
+ own(probabilities, option.value),
394
+ path,
395
+ answer,
396
+ expected,
397
+ errors,
398
+ )
399
+ ? option.value
400
+ : null;
401
+ };
402
+
403
+ const score = (
404
+ leaf: _ILlmEvaluationPlan.IScore,
405
+ answer: unknown,
406
+ path: string,
407
+ errors: IValidation.IError[],
408
+ precision: IPrecision,
409
+ ): number | null => {
410
+ const last: number = leaf.levels.length - 1;
411
+ const expected: string = `{ type: "score"; score: number; probabilities?: Record<string, number> }`;
412
+ const record: Record<string, unknown> | null = object(answer);
413
+ const type: unknown = record?.type;
414
+ const scored: unknown = type === "score" ? record?.score : undefined;
415
+ if (
416
+ record === null ||
417
+ type !== "score" ||
418
+ typeof scored !== "number" ||
419
+ Number.isFinite(scored) === false ||
420
+ scored < 0 ||
421
+ scored > last
422
+ ) {
423
+ errors.push({
424
+ path,
425
+ expected,
426
+ value: answer,
427
+ description:
428
+ record === null
429
+ ? "Missing score answer."
430
+ : type !== "score"
431
+ ? `Answer type must be "score", but got ${label(type)}.`
432
+ : `Score answer needs a score in [0, ${last}].`,
433
+ });
434
+ return null;
435
+ }
436
+ const probabilities: Record<string, number> | null | undefined = distribution(
437
+ record.probabilities,
438
+ leaf.levels.map((_, i) => String(i)),
439
+ precision.probability,
440
+ );
441
+ if (probabilities === null) {
442
+ errors.push({
443
+ path,
444
+ expected,
445
+ value: answer,
446
+ description:
447
+ "Score probabilities must contain every level index exactly once, use numbers in [0, 1], and sum to 1.",
448
+ });
449
+ return null;
450
+ }
451
+ if (probabilities !== undefined) {
452
+ const mean: number = leaf.levels.reduce(
453
+ (total, _, index) => total + index * own(probabilities, String(index))!,
454
+ 0,
455
+ );
456
+ const meanRoundingError: number = leaf.levels.reduce(
457
+ (total, _, index) => total + index * precision.probability,
458
+ 0,
459
+ );
460
+ if (
461
+ Math.abs(mean - scored) >
462
+ PROBABILITY_TOLERANCE + meanRoundingError + precision.score
463
+ ) {
464
+ errors.push({
465
+ path,
466
+ expected,
467
+ value: answer,
468
+ description:
469
+ "Score must equal the probability-weighted mean of its distribution.",
470
+ });
471
+ return null;
472
+ }
473
+ }
474
+
475
+ // the most probable level when a distribution exists, where a tie resolves
476
+ // to the lower level; otherwise the level nearest to the fractional score,
477
+ // where a half rounds up
478
+ let index: number = Math.round(scored);
479
+ if (probabilities !== undefined && Object.keys(probabilities).length !== 0) {
480
+ index = -1;
481
+ leaf.levels.forEach((_, i) => {
482
+ const p: number | undefined = own(probabilities, String(i));
483
+ if (
484
+ p !== undefined &&
485
+ (index === -1 || p > own(probabilities, String(index))!)
486
+ )
487
+ index = i;
488
+ });
489
+ }
490
+ const level: _ILlmEvaluationPlan.IMember<number> = leaf.levels[index]!;
491
+ return accept(
492
+ level,
493
+ level.value,
494
+ own(probabilities, String(index)),
495
+ path,
496
+ answer,
497
+ expected,
498
+ errors,
499
+ )
500
+ ? level.value
501
+ : null;
502
+ };
503
+
504
+ /** Enforces the acceptance minimum of the selected member, if any. */
505
+ const accept = (
506
+ member: { minimum?: number },
507
+ value: string | number,
508
+ probability: number | undefined,
509
+ path: string,
510
+ answer: unknown,
511
+ expected: string,
512
+ errors: IValidation.IError[],
513
+ ): boolean => {
514
+ if (member.minimum === undefined) return true;
515
+ if (probability === undefined) {
516
+ errors.push({
517
+ path,
518
+ expected,
519
+ value: answer,
520
+ description: `${JSON.stringify(value)} requires probability >= ${member.minimum}, but the answer has no probability for it.`,
521
+ });
522
+ return false;
523
+ }
524
+ if (probability < member.minimum) {
525
+ errors.push({
526
+ path,
527
+ expected,
528
+ value: answer,
529
+ description: `${JSON.stringify(value)} has probability ${probability} < ${member.minimum}.`,
530
+ });
531
+ return false;
532
+ }
533
+ return true;
534
+ };
535
+
536
+ /**
537
+ * Reads optional `probabilities`: `undefined` when absent, `null` when
538
+ * malformed.
539
+ */
540
+ const distribution = (
541
+ input: unknown,
542
+ keys: string[],
543
+ roundingError: number,
544
+ ): Record<string, number> | null | undefined => {
545
+ if (input === undefined) return undefined;
546
+ const record: Record<string, unknown> | null = object(input);
547
+ if (record === null) return null;
548
+ if (Object.keys(record).length !== keys.length) return null;
549
+ const output: Record<string, number> = {};
550
+ for (const key of keys) {
551
+ if (Object.prototype.hasOwnProperty.call(record, key) === false)
552
+ return null;
553
+ const probability: unknown = record[key];
554
+ if (isProbability(probability) === false) return null;
555
+ assign(output, key, probability);
556
+ }
557
+ if (
558
+ Math.abs(
559
+ Object.values(output).reduce((sum, probability) => sum + probability, 0) -
560
+ 1,
561
+ ) >
562
+ PROBABILITY_TOLERANCE + keys.length * roundingError
563
+ )
564
+ return null;
565
+ return output;
566
+ };
567
+
568
+ /** AI SDK's absolute tolerance for probability sums and derived values. */
569
+ const PROBABILITY_TOLERANCE = 1e-6;
570
+
571
+ interface IPrecision {
572
+ probability: number;
573
+ score: number;
574
+ }
575
+
576
+ /** AI SDK's declared decimal-precision rule, without an AI SDK dependency. */
577
+ const readPrecision = (
578
+ rounding: ILlmEvaluation.IRounding | undefined,
579
+ ): IPrecision | null => {
580
+ if (rounding === undefined) return { probability: 0, score: 0 };
581
+ if (
582
+ typeof rounding !== "object" ||
583
+ rounding === null ||
584
+ Array.isArray(rounding)
585
+ )
586
+ return null;
587
+ const record: Record<string, unknown> = rounding as Record<string, unknown>;
588
+ const decimalError = (value: unknown): number | null =>
589
+ value === undefined
590
+ ? 0
591
+ : typeof value === "number" &&
592
+ Number.isInteger(value) &&
593
+ value >= 0 &&
594
+ value <= 15
595
+ ? 0.5 * 10 ** -value
596
+ : null;
597
+ const probability: number | null = decimalError(record.probabilityDecimals);
598
+ const score: number | null = decimalError(record.scoreDecimals);
599
+ return probability === null || score === null ? null : { probability, score };
600
+ };
601
+
602
+ /* -----------------------------------------------------------
603
+ HELPERS
604
+ ----------------------------------------------------------- */
605
+ /**
606
+ * Renders an untrusted answer `type` for a message; unlike `JSON.stringify`, it
607
+ * never throws on a bigint or circular value.
608
+ */
609
+ const label = (value: unknown): string =>
610
+ typeof value === "string" ? JSON.stringify(value) : typeof value;
611
+
612
+ const accessor = (path: string[]): string =>
613
+ "$input" + path.map(_accessExpressionAsString).join("");
614
+
615
+ /** Match AI SDK's JSON-record boundary, including null-prototype dictionaries. */
616
+ const object = (input: unknown): Record<string, unknown> | null => {
617
+ if (typeof input !== "object" || input === null) return null;
618
+ try {
619
+ if (Array.isArray(input)) return null;
620
+ const prototype: object | null = Object.getPrototypeOf(input);
621
+ return prototype === Object.prototype || prototype === null
622
+ ? (input as Record<string, unknown>)
623
+ : null;
624
+ } catch {
625
+ // A revoked or trapping proxy is not a usable answer record.
626
+ return null;
627
+ }
628
+ };
629
+
630
+ /**
631
+ * Reads an own probability, so an option named like an `Object.prototype`
632
+ * member (`constructor`, `toString`, ...) never inherits a value.
633
+ */
634
+ const own = (
635
+ probabilities: Record<string, number> | undefined,
636
+ key: string,
637
+ ): number | undefined =>
638
+ probabilities !== undefined &&
639
+ Object.prototype.hasOwnProperty.call(probabilities, key)
640
+ ? probabilities[key]
641
+ : undefined;
642
+
643
+ const isProbability = (value: unknown): value is number =>
644
+ typeof value === "number" &&
645
+ Number.isFinite(value) &&
646
+ value >= 0 &&
647
+ value <= 1;
648
+
649
+ /** Writes the leaf value, creating the intermediate objects of its path. */
650
+ const place = (
651
+ output: Record<string, unknown>,
652
+ path: string[],
653
+ value: unknown,
654
+ ): void => {
655
+ let target: Record<string, unknown> = output;
656
+ path.slice(0, -1).forEach((name) => {
657
+ if (Object.prototype.hasOwnProperty.call(target, name) === false)
658
+ assign(target, name, {});
659
+ target = target[name] as Record<string, unknown>;
660
+ });
661
+ assign(target, path[path.length - 1]!, value);
662
+ };
663
+
664
+ /** Own-property assignment that cannot reach the prototype via `__proto__`. */
665
+ const assign = (
666
+ target: Record<string, unknown>,
667
+ name: string,
668
+ value: unknown,
669
+ ): void => {
670
+ if (name === "__proto__")
671
+ Object.defineProperty(target, name, {
672
+ value,
673
+ enumerable: true,
674
+ writable: true,
675
+ configurable: true,
676
+ });
677
+ else target[name] = value;
678
+ };
@@ -0,0 +1,6 @@
1
+ export const _stringLengthGte = (value: string, length: number): boolean => {
2
+ let count: number = 0;
3
+ if (length <= count) return true;
4
+ for (const _ch of value) if (length <= ++count) return true;
5
+ return false;
6
+ };
@@ -0,0 +1,6 @@
1
+ export const _stringLengthLte = (value: string, length: number): boolean => {
2
+ let count: number = 0;
3
+ if (!(count <= length)) return false;
4
+ for (const _ch of value) if (!(++count <= length)) return false;
5
+ return true;
6
+ };