@wp-typia/project-tools 0.22.3 → 0.22.5
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/dist/runtime/cli-add-block-json.d.ts +31 -0
- package/dist/runtime/cli-add-block-json.js +65 -0
- package/dist/runtime/cli-add-collision.d.ts +129 -0
- package/dist/runtime/cli-add-collision.js +293 -0
- package/dist/runtime/cli-add-filesystem.d.ts +29 -0
- package/dist/runtime/cli-add-filesystem.js +77 -0
- package/dist/runtime/cli-add-help.d.ts +4 -0
- package/dist/runtime/cli-add-help.js +41 -0
- package/dist/runtime/cli-add-shared.d.ts +6 -304
- package/dist/runtime/cli-add-shared.js +6 -524
- package/dist/runtime/cli-add-types.d.ts +247 -0
- package/dist/runtime/cli-add-types.js +64 -0
- package/dist/runtime/cli-add-validation.d.ts +87 -0
- package/dist/runtime/cli-add-validation.js +147 -0
- package/dist/runtime/cli-add-workspace-ability-scaffold.js +46 -72
- package/dist/runtime/cli-add-workspace-admin-view-scaffold.js +35 -61
- package/dist/runtime/cli-add-workspace-ai-anchors.js +3 -24
- package/dist/runtime/cli-add-workspace-ai-scaffold.js +53 -57
- package/dist/runtime/cli-add-workspace-ai-templates.js +2 -0
- package/dist/runtime/cli-add-workspace-assets.js +7 -50
- package/dist/runtime/cli-add-workspace-mutation.d.ts +30 -0
- package/dist/runtime/cli-add-workspace-mutation.js +60 -0
- package/dist/runtime/cli-add-workspace-rest-anchors.js +3 -24
- package/dist/runtime/cli-add-workspace.js +1 -79
- package/dist/runtime/cli-add.d.ts +2 -2
- package/dist/runtime/cli-add.js +2 -2
- package/dist/runtime/cli-doctor-workspace-blocks.js +1 -66
- package/dist/runtime/index.d.ts +2 -0
- package/dist/runtime/index.js +1 -0
- package/dist/runtime/migration-utils.d.ts +2 -1
- package/dist/runtime/migration-utils.js +3 -11
- package/dist/runtime/package-managers.d.ts +19 -0
- package/dist/runtime/package-managers.js +62 -0
- package/dist/runtime/template-source-cache.d.ts +59 -0
- package/dist/runtime/template-source-cache.js +160 -0
- package/dist/runtime/ts-source-masking.d.ts +28 -0
- package/dist/runtime/ts-source-masking.js +104 -0
- package/dist/runtime/typia-llm.d.ts +9 -1
- package/dist/runtime/typia-llm.js +20 -5
- package/dist/runtime/workspace-inventory.js +368 -284
- package/dist/runtime/workspace-project.d.ts +1 -1
- package/dist/runtime/workspace-project.js +2 -10
- package/package.json +2 -2
|
@@ -13,10 +13,20 @@ export const EXTERNAL_TEMPLATE_CACHE_ENV = 'WP_TYPIA_EXTERNAL_TEMPLATE_CACHE';
|
|
|
13
13
|
* Environment variable that overrides the external template cache root.
|
|
14
14
|
*/
|
|
15
15
|
export const EXTERNAL_TEMPLATE_CACHE_DIR_ENV = 'WP_TYPIA_EXTERNAL_TEMPLATE_CACHE_DIR';
|
|
16
|
+
/**
|
|
17
|
+
* Environment variable that enables TTL-based external template cache pruning.
|
|
18
|
+
*
|
|
19
|
+
* Unset, empty, zero, negative, and non-numeric values keep pruning disabled.
|
|
20
|
+
*/
|
|
21
|
+
export const EXTERNAL_TEMPLATE_CACHE_TTL_DAYS_ENV = 'WP_TYPIA_EXTERNAL_TEMPLATE_CACHE_TTL_DAYS';
|
|
16
22
|
/**
|
|
17
23
|
* Marker file written after a cache entry is fully populated.
|
|
18
24
|
*/
|
|
19
25
|
const CACHE_MARKER_FILE = 'wp-typia-template-cache.json';
|
|
26
|
+
/**
|
|
27
|
+
* Milliseconds in one TTL day.
|
|
28
|
+
*/
|
|
29
|
+
const MILLISECONDS_PER_DAY = 24 * 60 * 60 * 1000;
|
|
20
30
|
/**
|
|
21
31
|
* Private directory mode used for cache roots and entries on POSIX platforms.
|
|
22
32
|
*/
|
|
@@ -51,6 +61,10 @@ const URL_LIKE_METADATA_KEY = /(url|uri|registry|tarball)/iu;
|
|
|
51
61
|
* Cache namespaces must stay within one path segment under the cache root.
|
|
52
62
|
*/
|
|
53
63
|
const SAFE_CACHE_NAMESPACE_SEGMENT = /^[A-Za-z0-9_.-]+$/u;
|
|
64
|
+
/**
|
|
65
|
+
* Cache entries are deterministic SHA-256 digest directory names.
|
|
66
|
+
*/
|
|
67
|
+
const SAFE_CACHE_ENTRY_SEGMENT = /^[a-f0-9]{64}$/u;
|
|
54
68
|
/**
|
|
55
69
|
* Checks whether remote external template source caching is enabled.
|
|
56
70
|
*
|
|
@@ -84,6 +98,27 @@ export function getExternalTemplateCacheRoot(env = process.env) {
|
|
|
84
98
|
}
|
|
85
99
|
return path.join(os.tmpdir(), `wp-typia-template-source-cache-${getCurrentUserCacheSegment()}`);
|
|
86
100
|
}
|
|
101
|
+
function parseExternalTemplateCacheTtlDays(value) {
|
|
102
|
+
if (typeof value !== 'string' && typeof value !== 'number') {
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
const ttlDays = typeof value === 'number' ? value : Number(value.trim());
|
|
106
|
+
if (!Number.isFinite(ttlDays) || ttlDays <= 0) {
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
return ttlDays;
|
|
110
|
+
}
|
|
111
|
+
function resolveExternalTemplateCacheTtlMs(options = {}) {
|
|
112
|
+
const env = options.env ?? process.env;
|
|
113
|
+
const ttlDays = options.ttlDays === undefined
|
|
114
|
+
? parseExternalTemplateCacheTtlDays(env[EXTERNAL_TEMPLATE_CACHE_TTL_DAYS_ENV])
|
|
115
|
+
: parseExternalTemplateCacheTtlDays(options.ttlDays);
|
|
116
|
+
if (ttlDays === null) {
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
const ttlMs = ttlDays * MILLISECONDS_PER_DAY;
|
|
120
|
+
return Number.isFinite(ttlMs) ? ttlMs : null;
|
|
121
|
+
}
|
|
87
122
|
/**
|
|
88
123
|
* Creates a deterministic cache key from source identity and integrity parts.
|
|
89
124
|
*
|
|
@@ -281,6 +316,129 @@ function parseCacheMarkerMetadata(markerText) {
|
|
|
281
316
|
function cacheMetadataMatches(actual, expected) {
|
|
282
317
|
return Object.entries(expected).every(([key, value]) => actual[key] === value);
|
|
283
318
|
}
|
|
319
|
+
function getExternalTemplateCacheNowMs(now) {
|
|
320
|
+
const nowMs = now instanceof Date
|
|
321
|
+
? now.getTime()
|
|
322
|
+
: typeof now === 'number'
|
|
323
|
+
? now
|
|
324
|
+
: Date.now();
|
|
325
|
+
return Number.isFinite(nowMs) ? nowMs : Date.now();
|
|
326
|
+
}
|
|
327
|
+
function isPathInsideDirectory(directory, candidatePath) {
|
|
328
|
+
const relativePath = path.relative(directory, candidatePath);
|
|
329
|
+
return (relativePath.length > 0 &&
|
|
330
|
+
!relativePath.startsWith('..') &&
|
|
331
|
+
!path.isAbsolute(relativePath));
|
|
332
|
+
}
|
|
333
|
+
async function removeCacheEntryWithinRoot(cacheRoot, entryDir) {
|
|
334
|
+
if (!isPathInsideDirectory(cacheRoot, entryDir)) {
|
|
335
|
+
return false;
|
|
336
|
+
}
|
|
337
|
+
try {
|
|
338
|
+
await fsp.rm(entryDir, { force: true, recursive: true });
|
|
339
|
+
return true;
|
|
340
|
+
}
|
|
341
|
+
catch {
|
|
342
|
+
return false;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
/**
|
|
346
|
+
* Removes stale external template cache entries when a positive TTL is configured.
|
|
347
|
+
*
|
|
348
|
+
* The helper is best-effort: malformed cache directories are skipped, cache
|
|
349
|
+
* roots must remain private and non-symlinked, and deletes are constrained to
|
|
350
|
+
* deterministic entry directories under the configured cache root.
|
|
351
|
+
*
|
|
352
|
+
* @param options Optional TTL, clock, and environment overrides.
|
|
353
|
+
* @returns Pruning summary with counts for inspected, skipped, and removed entries.
|
|
354
|
+
*/
|
|
355
|
+
export async function pruneExternalTemplateCache(options = {}) {
|
|
356
|
+
const env = options.env ?? process.env;
|
|
357
|
+
const cacheRoot = getExternalTemplateCacheRoot(env);
|
|
358
|
+
const ttlMs = resolveExternalTemplateCacheTtlMs({
|
|
359
|
+
env,
|
|
360
|
+
ttlDays: options.ttlDays,
|
|
361
|
+
});
|
|
362
|
+
const result = {
|
|
363
|
+
cacheRoot,
|
|
364
|
+
prunedEntries: 0,
|
|
365
|
+
scannedEntries: 0,
|
|
366
|
+
skippedEntries: 0,
|
|
367
|
+
ttlMs,
|
|
368
|
+
};
|
|
369
|
+
if (ttlMs === null || !(await isPrivateCacheDirectory(cacheRoot))) {
|
|
370
|
+
return result;
|
|
371
|
+
}
|
|
372
|
+
let namespaceEntries;
|
|
373
|
+
try {
|
|
374
|
+
namespaceEntries = await fsp.readdir(cacheRoot, { withFileTypes: true });
|
|
375
|
+
}
|
|
376
|
+
catch {
|
|
377
|
+
return result;
|
|
378
|
+
}
|
|
379
|
+
const expiresBeforeMs = getExternalTemplateCacheNowMs(options.now) - ttlMs;
|
|
380
|
+
for (const namespaceEntry of namespaceEntries) {
|
|
381
|
+
if (!namespaceEntry.isDirectory()) {
|
|
382
|
+
continue;
|
|
383
|
+
}
|
|
384
|
+
const namespaceDir = resolveCacheNamespaceDir(cacheRoot, namespaceEntry.name);
|
|
385
|
+
if (!namespaceDir || !(await isPrivateCacheDirectory(namespaceDir))) {
|
|
386
|
+
result.skippedEntries += 1;
|
|
387
|
+
continue;
|
|
388
|
+
}
|
|
389
|
+
let cacheEntries;
|
|
390
|
+
try {
|
|
391
|
+
cacheEntries = await fsp.readdir(namespaceDir, { withFileTypes: true });
|
|
392
|
+
}
|
|
393
|
+
catch {
|
|
394
|
+
result.skippedEntries += 1;
|
|
395
|
+
continue;
|
|
396
|
+
}
|
|
397
|
+
for (const cacheEntry of cacheEntries) {
|
|
398
|
+
if (!cacheEntry.isDirectory()) {
|
|
399
|
+
continue;
|
|
400
|
+
}
|
|
401
|
+
if (!SAFE_CACHE_ENTRY_SEGMENT.test(cacheEntry.name)) {
|
|
402
|
+
result.skippedEntries += 1;
|
|
403
|
+
continue;
|
|
404
|
+
}
|
|
405
|
+
const entryDir = path.join(namespaceDir, cacheEntry.name);
|
|
406
|
+
result.scannedEntries += 1;
|
|
407
|
+
if (!isPathInsideDirectory(cacheRoot, entryDir)) {
|
|
408
|
+
result.skippedEntries += 1;
|
|
409
|
+
continue;
|
|
410
|
+
}
|
|
411
|
+
const markerPath = path.join(entryDir, CACHE_MARKER_FILE);
|
|
412
|
+
const sourceDir = path.join(entryDir, 'source');
|
|
413
|
+
if (!(await isReusableCacheEntry(entryDir, markerPath, sourceDir))) {
|
|
414
|
+
result.skippedEntries += 1;
|
|
415
|
+
continue;
|
|
416
|
+
}
|
|
417
|
+
let markerText;
|
|
418
|
+
try {
|
|
419
|
+
markerText = await fsp.readFile(markerPath, 'utf8');
|
|
420
|
+
}
|
|
421
|
+
catch {
|
|
422
|
+
result.skippedEntries += 1;
|
|
423
|
+
continue;
|
|
424
|
+
}
|
|
425
|
+
const marker = parseCacheMarkerMetadata(markerText);
|
|
426
|
+
if (!marker) {
|
|
427
|
+
result.skippedEntries += 1;
|
|
428
|
+
continue;
|
|
429
|
+
}
|
|
430
|
+
if (marker.createdAtMs < expiresBeforeMs) {
|
|
431
|
+
if (await removeCacheEntryWithinRoot(cacheRoot, entryDir)) {
|
|
432
|
+
result.prunedEntries += 1;
|
|
433
|
+
}
|
|
434
|
+
else {
|
|
435
|
+
result.skippedEntries += 1;
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
return result;
|
|
441
|
+
}
|
|
284
442
|
/**
|
|
285
443
|
* Finds a reusable cache entry whose marker metadata includes the expected fields.
|
|
286
444
|
*
|
|
@@ -304,6 +462,7 @@ export async function findReusableExternalTemplateSourceCache(descriptor) {
|
|
|
304
462
|
!(await isPrivateCacheDirectory(namespaceDir))) {
|
|
305
463
|
return null;
|
|
306
464
|
}
|
|
465
|
+
await pruneExternalTemplateCache();
|
|
307
466
|
let entries;
|
|
308
467
|
try {
|
|
309
468
|
entries = await fsp.readdir(namespaceDir, { withFileTypes: true });
|
|
@@ -371,6 +530,7 @@ export async function resolveExternalTemplateSourceCache(descriptor, populateSou
|
|
|
371
530
|
!(await ensurePrivateCacheDirectory(namespaceDir))) {
|
|
372
531
|
return null;
|
|
373
532
|
}
|
|
533
|
+
await pruneExternalTemplateCache();
|
|
374
534
|
if (await isReusableCacheEntry(entryDir, markerPath, sourceDir)) {
|
|
375
535
|
return {
|
|
376
536
|
cacheHit: true,
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/** Original-source range returned from a masked-source pattern match. */
|
|
2
|
+
export interface SourceRange {
|
|
3
|
+
end: number;
|
|
4
|
+
start: number;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Masks TypeScript comments with spaces while preserving newlines and offsets.
|
|
8
|
+
*/
|
|
9
|
+
export declare function maskTypeScriptComments(source: string): string;
|
|
10
|
+
/**
|
|
11
|
+
* Masks TypeScript comments and quoted/template literals with spaces while
|
|
12
|
+
* preserving source offsets. This is a lightweight lexer for runtime checks,
|
|
13
|
+
* not a full TypeScript parser, so template interpolation and regex/division
|
|
14
|
+
* ambiguity are intentionally left to callers that need deeper syntax analysis.
|
|
15
|
+
*/
|
|
16
|
+
export declare function maskTypeScriptCommentsAndLiterals(source: string): string;
|
|
17
|
+
/**
|
|
18
|
+
* Tests for a pattern after hiding comments and quoted/template literals.
|
|
19
|
+
*/
|
|
20
|
+
export declare function hasExecutablePattern(source: string, pattern: RegExp): boolean;
|
|
21
|
+
/**
|
|
22
|
+
* Tests for a pattern after hiding comments while leaving literals intact.
|
|
23
|
+
*/
|
|
24
|
+
export declare function hasUncommentedPattern(source: string, pattern: RegExp): boolean;
|
|
25
|
+
/**
|
|
26
|
+
* Finds the first masked-source match that maps back to the original source.
|
|
27
|
+
*/
|
|
28
|
+
export declare function findExecutablePatternMatch(source: string, patterns: readonly RegExp[]): SourceRange | undefined;
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
function maskSourceSegment(segment) {
|
|
2
|
+
return segment.replace(/[^\n\r]/gu, " ");
|
|
3
|
+
}
|
|
4
|
+
function testPattern(source, pattern) {
|
|
5
|
+
pattern.lastIndex = 0;
|
|
6
|
+
const matched = pattern.test(source);
|
|
7
|
+
pattern.lastIndex = 0;
|
|
8
|
+
return matched;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Masks TypeScript comments with spaces while preserving newlines and offsets.
|
|
12
|
+
*/
|
|
13
|
+
export function maskTypeScriptComments(source) {
|
|
14
|
+
return source
|
|
15
|
+
.replace(/\/\*[\s\S]*?\*\//gu, maskSourceSegment)
|
|
16
|
+
.replace(/\/\/[^\n\r]*/gu, maskSourceSegment);
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Masks TypeScript comments and quoted/template literals with spaces while
|
|
20
|
+
* preserving source offsets. This is a lightweight lexer for runtime checks,
|
|
21
|
+
* not a full TypeScript parser, so template interpolation and regex/division
|
|
22
|
+
* ambiguity are intentionally left to callers that need deeper syntax analysis.
|
|
23
|
+
*/
|
|
24
|
+
export function maskTypeScriptCommentsAndLiterals(source) {
|
|
25
|
+
let maskedSource = "";
|
|
26
|
+
let index = 0;
|
|
27
|
+
while (index < source.length) {
|
|
28
|
+
const current = source[index];
|
|
29
|
+
const next = source[index + 1];
|
|
30
|
+
if (current === "/" && next === "/") {
|
|
31
|
+
const start = index;
|
|
32
|
+
index += 2;
|
|
33
|
+
while (index < source.length &&
|
|
34
|
+
source[index] !== "\n" &&
|
|
35
|
+
source[index] !== "\r") {
|
|
36
|
+
index += 1;
|
|
37
|
+
}
|
|
38
|
+
maskedSource += maskSourceSegment(source.slice(start, index));
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
if (current === "/" && next === "*") {
|
|
42
|
+
const start = index;
|
|
43
|
+
index += 2;
|
|
44
|
+
while (index < source.length &&
|
|
45
|
+
!(source[index] === "*" && source[index + 1] === "/")) {
|
|
46
|
+
index += 1;
|
|
47
|
+
}
|
|
48
|
+
index = Math.min(index + 2, source.length);
|
|
49
|
+
maskedSource += maskSourceSegment(source.slice(start, index));
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
if (current === "'" || current === '"' || current === "`") {
|
|
53
|
+
const start = index;
|
|
54
|
+
const quote = current;
|
|
55
|
+
index += 1;
|
|
56
|
+
while (index < source.length) {
|
|
57
|
+
const char = source[index];
|
|
58
|
+
if (char === "\\") {
|
|
59
|
+
index += 2;
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
index += 1;
|
|
63
|
+
if (char === quote) {
|
|
64
|
+
break;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
maskedSource += maskSourceSegment(source.slice(start, index));
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
maskedSource += current;
|
|
71
|
+
index += 1;
|
|
72
|
+
}
|
|
73
|
+
return maskedSource;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Tests for a pattern after hiding comments and quoted/template literals.
|
|
77
|
+
*/
|
|
78
|
+
export function hasExecutablePattern(source, pattern) {
|
|
79
|
+
return testPattern(maskTypeScriptCommentsAndLiterals(source), pattern);
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Tests for a pattern after hiding comments while leaving literals intact.
|
|
83
|
+
*/
|
|
84
|
+
export function hasUncommentedPattern(source, pattern) {
|
|
85
|
+
return testPattern(maskTypeScriptComments(source), pattern);
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Finds the first masked-source match that maps back to the original source.
|
|
89
|
+
*/
|
|
90
|
+
export function findExecutablePatternMatch(source, patterns) {
|
|
91
|
+
const maskedSource = maskTypeScriptCommentsAndLiterals(source);
|
|
92
|
+
for (const pattern of patterns) {
|
|
93
|
+
pattern.lastIndex = 0;
|
|
94
|
+
const match = pattern.exec(maskedSource);
|
|
95
|
+
pattern.lastIndex = 0;
|
|
96
|
+
if (match && match.index !== undefined) {
|
|
97
|
+
return {
|
|
98
|
+
end: match.index + match[0].length,
|
|
99
|
+
start: match.index,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return undefined;
|
|
104
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ArtifactSyncExecutionOptions, EndpointManifestDefinition, EndpointManifestEndpointDefinition } from '@wp-typia/block-runtime/metadata-core';
|
|
2
2
|
import type { ILlmFunction, ILlmSchema, ILlmStructuredOutput } from 'typia';
|
|
3
|
-
import { type EndpointAuthIntent, type EndpointWordPressAuthDefinition, type OpenApiDocument } from './schema-core.js';
|
|
3
|
+
import { type EndpointAuthIntent, type EndpointWordPressAuthDefinition, type JsonSchemaObject, type OpenApiDocument } from './schema-core.js';
|
|
4
4
|
/**
|
|
5
5
|
* Method-level descriptor projected from an endpoint manifest for a generated
|
|
6
6
|
* `typia.llm` controller interface.
|
|
@@ -186,6 +186,14 @@ export interface ProjectTypiaLlmStructuredOutputArtifactOptions {
|
|
|
186
186
|
/** Compiled structured-output value produced by `typia.llm.structuredOutput(...)`. */
|
|
187
187
|
structuredOutput: TypiaLlmStructuredOutputLike;
|
|
188
188
|
}
|
|
189
|
+
/**
|
|
190
|
+
* Narrow a `typia.llm` artifact field before applying JSON Schema mutations.
|
|
191
|
+
*
|
|
192
|
+
* @param value Candidate schema value emitted by `typia.llm`.
|
|
193
|
+
* @param context Human-readable artifact path for diagnostics.
|
|
194
|
+
* @returns The value narrowed to the shared JSON Schema object shape.
|
|
195
|
+
*/
|
|
196
|
+
export declare function assertJsonSchemaObject(value: unknown, context: string): JsonSchemaObject;
|
|
189
197
|
/**
|
|
190
198
|
* Builds the generated controller method descriptors that bridge endpoint
|
|
191
199
|
* manifests to `typia.llm` TypeScript interfaces.
|
|
@@ -105,6 +105,19 @@ function cloneJsonValueIfDefined(value) {
|
|
|
105
105
|
function isJsonSchemaObject(value) {
|
|
106
106
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
107
107
|
}
|
|
108
|
+
/**
|
|
109
|
+
* Narrow a `typia.llm` artifact field before applying JSON Schema mutations.
|
|
110
|
+
*
|
|
111
|
+
* @param value Candidate schema value emitted by `typia.llm`.
|
|
112
|
+
* @param context Human-readable artifact path for diagnostics.
|
|
113
|
+
* @returns The value narrowed to the shared JSON Schema object shape.
|
|
114
|
+
*/
|
|
115
|
+
export function assertJsonSchemaObject(value, context) {
|
|
116
|
+
if (isJsonSchemaObject(value)) {
|
|
117
|
+
return value;
|
|
118
|
+
}
|
|
119
|
+
throw new Error(`${context} must be a JSON Schema object.`);
|
|
120
|
+
}
|
|
108
121
|
function decodeJsonPointerSegment(segment) {
|
|
109
122
|
return segment.replace(/~1/g, '/').replace(/~0/g, '~');
|
|
110
123
|
}
|
|
@@ -551,23 +564,25 @@ export function applyOpenApiConstraintsToTypiaLlmFunctionArtifact({ functionArti
|
|
|
551
564
|
typeof resolvedParameter.name === 'string');
|
|
552
565
|
});
|
|
553
566
|
const requestBodySchema = resolveOpenApiRequestBodySchema(operation, openApiDocument);
|
|
567
|
+
const parameterSchema = assertJsonSchemaObject(constrainedArtifact.parameters, `typia.llm parameters for "${constrainedArtifact.name}"`);
|
|
554
568
|
if (requestBodySchema) {
|
|
555
569
|
if (hasQueryParameters) {
|
|
556
|
-
mergeJsonSchemaConstraintProperties(openApiDocument, getOrCreateObjectProperty(
|
|
570
|
+
mergeJsonSchemaConstraintProperties(openApiDocument, getOrCreateObjectProperty(parameterSchema, 'body'), requestBodySchema);
|
|
557
571
|
}
|
|
558
572
|
else {
|
|
559
|
-
mergeJsonSchemaConstraintProperties(openApiDocument,
|
|
573
|
+
mergeJsonSchemaConstraintProperties(openApiDocument, parameterSchema, requestBodySchema);
|
|
560
574
|
}
|
|
561
575
|
}
|
|
562
576
|
if (hasQueryParameters) {
|
|
563
577
|
applyOpenApiQueryParameterConstraints(requestBodySchema
|
|
564
|
-
? getOrCreateObjectProperty(
|
|
565
|
-
:
|
|
578
|
+
? getOrCreateObjectProperty(parameterSchema, 'query')
|
|
579
|
+
: parameterSchema, operation, openApiDocument);
|
|
566
580
|
}
|
|
567
581
|
if (constrainedArtifact.output) {
|
|
582
|
+
const outputSchema = assertJsonSchemaObject(constrainedArtifact.output, `typia.llm output for "${constrainedArtifact.name}"`);
|
|
568
583
|
const responseSchema = resolveOpenApiSuccessResponseSchema(operation, openApiDocument);
|
|
569
584
|
if (responseSchema) {
|
|
570
|
-
mergeJsonSchemaConstraintProperties(openApiDocument,
|
|
585
|
+
mergeJsonSchemaConstraintProperties(openApiDocument, outputSchema, responseSchema);
|
|
571
586
|
}
|
|
572
587
|
}
|
|
573
588
|
return constrainedArtifact;
|