@pikku/inspector 0.12.2 → 0.12.4
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/CHANGELOG.md +46 -0
- package/dist/add/add-ai-agent.js +4 -0
- package/dist/add/add-approval-description.d.ts +5 -0
- package/dist/add/add-approval-description.js +52 -0
- package/dist/add/add-channel.js +42 -4
- package/dist/add/add-cli.js +73 -13
- package/dist/add/add-file-with-factory.js +1 -0
- package/dist/add/add-functions.js +22 -3
- package/dist/add/add-gateway.js +5 -0
- package/dist/add/add-http-route.js +5 -0
- package/dist/add/add-mcp-prompt.js +5 -0
- package/dist/add/add-mcp-resource.js +5 -0
- package/dist/add/add-middleware.js +6 -10
- package/dist/add/add-permission.js +10 -12
- package/dist/add/add-queue-worker.js +5 -0
- package/dist/add/add-schedule.js +5 -0
- package/dist/add/add-wire-addon.js +7 -0
- package/dist/add/add-workflow.js +7 -1
- package/dist/error-codes.d.ts +1 -0
- package/dist/error-codes.js +2 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/inspector.js +21 -7
- package/dist/types.d.ts +12 -0
- package/dist/utils/custom-types-generator.js +1 -0
- package/dist/utils/load-addon-functions-meta.d.ts +12 -0
- package/dist/utils/load-addon-functions-meta.js +76 -0
- package/dist/utils/post-process.d.ts +9 -0
- package/dist/utils/post-process.js +72 -0
- package/dist/utils/resolve-function-meta.d.ts +11 -0
- package/dist/utils/resolve-function-meta.js +17 -0
- package/dist/utils/schema-generator.js +26 -6
- package/dist/utils/serialize-inspector-state.d.ts +2 -0
- package/dist/utils/serialize-inspector-state.js +5 -0
- package/dist/utils/serialize-mcp-json.js +13 -7
- package/dist/utils/workflow/graph/convert-dsl-to-graph.js +1 -0
- package/dist/utils/workflow/graph/workflow-graph.types.d.ts +2 -0
- package/dist/visit.js +2 -0
- package/package.json +4 -3
- package/src/add/add-ai-agent.ts +6 -0
- package/src/add/add-approval-description.ts +76 -0
- package/src/add/add-channel.ts +44 -4
- package/src/add/add-cli.ts +108 -21
- package/src/add/add-file-with-factory.ts +1 -0
- package/src/add/add-functions.ts +28 -3
- package/src/add/add-gateway.ts +6 -0
- package/src/add/add-http-route.ts +6 -0
- package/src/add/add-mcp-prompt.ts +6 -0
- package/src/add/add-mcp-resource.ts +6 -0
- package/src/add/add-middleware.ts +6 -14
- package/src/add/add-permission.ts +10 -16
- package/src/add/add-queue-worker.ts +6 -0
- package/src/add/add-schedule.ts +6 -0
- package/src/add/add-wire-addon.ts +8 -0
- package/src/add/add-workflow.ts +11 -1
- package/src/error-codes.ts +3 -0
- package/src/index.ts +1 -0
- package/src/inspector.ts +33 -6
- package/src/types.ts +13 -0
- package/src/utils/custom-types-generator.ts +1 -0
- package/src/utils/load-addon-functions-meta.ts +94 -0
- package/src/utils/post-process.ts +84 -0
- package/src/utils/resolve-function-meta.ts +25 -0
- package/src/utils/schema-generator.ts +38 -10
- package/src/utils/serialize-inspector-state.ts +7 -0
- package/src/utils/serialize-mcp-json.ts +12 -7
- package/src/utils/workflow/graph/convert-dsl-to-graph.ts +1 -0
- package/src/utils/workflow/graph/workflow-graph.types.ts +2 -0
- package/src/visit.ts +2 -0
- package/tsconfig.tsbuildinfo +1 -1
package/dist/add/add-schedule.js
CHANGED
|
@@ -4,6 +4,7 @@ import { extractFunctionName, makeContextBasedId, } from '../utils/extract-funct
|
|
|
4
4
|
import { getPropertyAssignmentInitializer } from '../utils/type-utils.js';
|
|
5
5
|
import { resolveMiddleware } from '../utils/middleware.js';
|
|
6
6
|
import { extractWireNames } from '../utils/post-process.js';
|
|
7
|
+
import { resolveAddonName } from '../utils/resolve-addon-package.js';
|
|
7
8
|
import { ErrorCode } from '../error-codes.js';
|
|
8
9
|
export const addSchedule = (logger, node, checker, state, options) => {
|
|
9
10
|
if (!ts.isCallExpression(node)) {
|
|
@@ -36,6 +37,9 @@ export const addSchedule = (logger, node, checker, state, options) => {
|
|
|
36
37
|
if (pikkuFuncId.startsWith('__temp_') && nameValue) {
|
|
37
38
|
pikkuFuncId = makeContextBasedId('scheduler', nameValue);
|
|
38
39
|
}
|
|
40
|
+
const packageName = ts.isIdentifier(funcInitializer)
|
|
41
|
+
? resolveAddonName(funcInitializer, checker, state.rpc.wireAddonDeclarations)
|
|
42
|
+
: null;
|
|
39
43
|
if (!nameValue || !scheduleValue) {
|
|
40
44
|
return;
|
|
41
45
|
}
|
|
@@ -47,6 +51,7 @@ export const addSchedule = (logger, node, checker, state, options) => {
|
|
|
47
51
|
state.scheduledTasks.files.add(node.getSourceFile().fileName);
|
|
48
52
|
state.scheduledTasks.meta[nameValue] = {
|
|
49
53
|
pikkuFuncId,
|
|
54
|
+
...(packageName && { packageName }),
|
|
50
55
|
name: nameValue,
|
|
51
56
|
schedule: scheduleValue,
|
|
52
57
|
summary,
|
|
@@ -32,6 +32,7 @@ export function addWireAddon(node, state, logger) {
|
|
|
32
32
|
let name;
|
|
33
33
|
let pkg;
|
|
34
34
|
let rpcEndpoint;
|
|
35
|
+
let mcp;
|
|
35
36
|
let secretOverrides;
|
|
36
37
|
let variableOverrides;
|
|
37
38
|
for (const prop of firstArg.properties) {
|
|
@@ -47,6 +48,11 @@ export function addWireAddon(node, state, logger) {
|
|
|
47
48
|
else if (key === 'rpcEndpoint' && ts.isStringLiteral(prop.initializer)) {
|
|
48
49
|
rpcEndpoint = prop.initializer.text;
|
|
49
50
|
}
|
|
51
|
+
else if (key === 'mcp' &&
|
|
52
|
+
(prop.initializer.kind === ts.SyntaxKind.TrueKeyword ||
|
|
53
|
+
prop.initializer.kind === ts.SyntaxKind.FalseKeyword)) {
|
|
54
|
+
mcp = prop.initializer.kind === ts.SyntaxKind.TrueKeyword;
|
|
55
|
+
}
|
|
50
56
|
else if (key === 'secretOverrides' &&
|
|
51
57
|
ts.isObjectLiteralExpression(prop.initializer)) {
|
|
52
58
|
secretOverrides = parseStringRecord(prop.initializer);
|
|
@@ -62,6 +68,7 @@ export function addWireAddon(node, state, logger) {
|
|
|
62
68
|
state.rpc.wireAddonDeclarations.set(name, {
|
|
63
69
|
package: pkg,
|
|
64
70
|
rpcEndpoint,
|
|
71
|
+
mcp,
|
|
65
72
|
secretOverrides,
|
|
66
73
|
variableOverrides,
|
|
67
74
|
});
|
package/dist/add/add-workflow.js
CHANGED
|
@@ -3,7 +3,7 @@ import { extractFunctionName } from '../utils/extract-function-name.js';
|
|
|
3
3
|
import { extractFunctionNode } from '../utils/extract-function-node.js';
|
|
4
4
|
import { ErrorCode } from '../error-codes.js';
|
|
5
5
|
import { extractStringLiteral, isStringLike, isFunctionLike, extractDescription, extractDuration, } from '../utils/extract-node-value.js';
|
|
6
|
-
import { getCommonWireMetaData } from '../utils/get-property-value.js';
|
|
6
|
+
import { getCommonWireMetaData, getPropertyValue, } from '../utils/get-property-value.js';
|
|
7
7
|
import { extractDSLWorkflow } from '../utils/workflow/dsl/extract-dsl-workflow.js';
|
|
8
8
|
/**
|
|
9
9
|
* Recursively check if any step has inline type (non-serializable)
|
|
@@ -182,6 +182,7 @@ export const addWorkflow = (logger, node, checker, state) => {
|
|
|
182
182
|
let summary;
|
|
183
183
|
let description;
|
|
184
184
|
let errors;
|
|
185
|
+
let inline;
|
|
185
186
|
if (ts.isObjectLiteralExpression(firstArg)) {
|
|
186
187
|
const metadata = getCommonWireMetaData(firstArg, 'Workflow', workflowName, logger);
|
|
187
188
|
if (metadata.disabled)
|
|
@@ -190,6 +191,10 @@ export const addWorkflow = (logger, node, checker, state) => {
|
|
|
190
191
|
summary = metadata.summary;
|
|
191
192
|
description = metadata.description;
|
|
192
193
|
errors = metadata.errors;
|
|
194
|
+
const inlineProp = getPropertyValue(firstArg, 'inline');
|
|
195
|
+
if (inlineProp === true) {
|
|
196
|
+
inline = true;
|
|
197
|
+
}
|
|
193
198
|
}
|
|
194
199
|
// Validate that we got a valid function
|
|
195
200
|
if (ts.isObjectLiteralExpression(firstArg) &&
|
|
@@ -272,5 +277,6 @@ export const addWorkflow = (logger, node, checker, state) => {
|
|
|
272
277
|
description,
|
|
273
278
|
errors,
|
|
274
279
|
tags,
|
|
280
|
+
inline,
|
|
275
281
|
};
|
|
276
282
|
};
|
package/dist/error-codes.d.ts
CHANGED
|
@@ -49,6 +49,7 @@ export declare enum ErrorCode {
|
|
|
49
49
|
MANIFEST_INTEGRITY_ERROR = "PKU865",
|
|
50
50
|
MISSING_MODEL = "PKU145",
|
|
51
51
|
INVALID_MODEL = "PKU146",
|
|
52
|
+
SCHEMA_AND_WIRING_COLOCATED = "PKU490",
|
|
52
53
|
SERVICES_NOT_DESTRUCTURED = "PKU410",
|
|
53
54
|
WIRES_NOT_DESTRUCTURED = "PKU411",
|
|
54
55
|
WORKFLOW_MULTI_QUEUE_NOT_SUPPORTED = "PKU901"
|
package/dist/error-codes.js
CHANGED
|
@@ -58,6 +58,8 @@ export var ErrorCode;
|
|
|
58
58
|
// Model configuration errors
|
|
59
59
|
ErrorCode["MISSING_MODEL"] = "PKU145";
|
|
60
60
|
ErrorCode["INVALID_MODEL"] = "PKU146";
|
|
61
|
+
// File structure errors
|
|
62
|
+
ErrorCode["SCHEMA_AND_WIRING_COLOCATED"] = "PKU490";
|
|
61
63
|
// Optimization diagnostics
|
|
62
64
|
ErrorCode["SERVICES_NOT_DESTRUCTURED"] = "PKU410";
|
|
63
65
|
ErrorCode["WIRES_NOT_DESTRUCTURED"] = "PKU411";
|
package/dist/index.d.ts
CHANGED
|
@@ -13,4 +13,5 @@ export { serializeMCPJson } from './utils/serialize-mcp-json.js';
|
|
|
13
13
|
export type { OpenAPISpecInfo } from './utils/serialize-openapi-json.js';
|
|
14
14
|
export { deserializeDslWorkflow, deserializeGraphWorkflow, deserializeAllDslWorkflows, } from './utils/workflow/dsl/index.js';
|
|
15
15
|
export { getFilesAndMethods } from './utils/get-files-and-methods.js';
|
|
16
|
+
export { resolveFunctionMeta } from './utils/resolve-function-meta.js';
|
|
16
17
|
export type { SerializedWorkflowGraph, SerializedWorkflowGraphs, } from './utils/workflow/graph/index.js';
|
package/dist/index.js
CHANGED
|
@@ -7,3 +7,4 @@ export { createEmptyManifest, serializeManifest, } from './utils/contract-hashes
|
|
|
7
7
|
export { serializeMCPJson } from './utils/serialize-mcp-json.js';
|
|
8
8
|
export { deserializeDslWorkflow, deserializeGraphWorkflow, deserializeAllDslWorkflows, } from './utils/workflow/dsl/index.js';
|
|
9
9
|
export { getFilesAndMethods } from './utils/get-files-and-methods.js';
|
|
10
|
+
export { resolveFunctionMeta } from './utils/resolve-function-meta.js';
|
package/dist/inspector.js
CHANGED
|
@@ -4,13 +4,14 @@ import { visitSetup, visitRoutes } from './visit.js';
|
|
|
4
4
|
import { TypesMap } from './types-map.js';
|
|
5
5
|
import { getFilesAndMethods } from './utils/get-files-and-methods.js';
|
|
6
6
|
import { findCommonAncestor } from './utils/find-root-dir.js';
|
|
7
|
-
import { aggregateRequiredServices, validateAgentModels, validateAgentOverrides, validateSecretOverrides, validateVariableOverrides, computeResolvedIOTypes, computeMiddlewareGroupsMeta, computePermissionsGroupsMeta, computeRequiredSchemas, computeDiagnostics, } from './utils/post-process.js';
|
|
7
|
+
import { aggregateRequiredServices, validateAgentModels, validateAgentOverrides, validateSecretOverrides, validateVariableOverrides, computeResolvedIOTypes, computeMiddlewareGroupsMeta, computePermissionsGroupsMeta, computeRequiredSchemas, computeDiagnostics, validateSchemaWiringSeparation, } from './utils/post-process.js';
|
|
8
8
|
import { generateOpenAPISpec } from './utils/serialize-openapi-json.js';
|
|
9
9
|
import { pikkuState } from '@pikku/core/internal';
|
|
10
10
|
import { resolveLatestVersions } from './utils/resolve-versions.js';
|
|
11
11
|
import { finalizeWorkflows } from './utils/workflow/graph/finalize-workflows.js';
|
|
12
12
|
import { finalizeWorkflowHelperTypes, finalizeWorkflowWires, } from './utils/workflow/graph/finalize-workflow-wires.js';
|
|
13
13
|
import { generateAllSchemas } from './utils/schema-generator.js';
|
|
14
|
+
import { loadAddonFunctionsMeta, loadAddonSchemas, } from './utils/load-addon-functions-meta.js';
|
|
14
15
|
import { computeContractHashes, extractContractsFromMeta, updateManifest, createEmptyManifest, validateContracts, } from './utils/contract-hashes.js';
|
|
15
16
|
/**
|
|
16
17
|
* Creates an initial/empty inspector state with all required properties initialized
|
|
@@ -37,6 +38,7 @@ export function getInitialInspectorState(rootDir) {
|
|
|
37
38
|
typesMap: new TypesMap(),
|
|
38
39
|
meta: {},
|
|
39
40
|
files: new Map(),
|
|
41
|
+
approvalDescriptions: {},
|
|
40
42
|
},
|
|
41
43
|
http: {
|
|
42
44
|
metaInputTypes: new Map(),
|
|
@@ -171,11 +173,12 @@ export function getInitialInspectorState(rootDir) {
|
|
|
171
173
|
requiredSchemas: new Set(),
|
|
172
174
|
openAPISpec: null,
|
|
173
175
|
diagnostics: [],
|
|
176
|
+
addonFunctions: {},
|
|
177
|
+
program: null,
|
|
174
178
|
};
|
|
175
179
|
}
|
|
176
180
|
export const inspect = async (logger, routeFiles, options = {}) => {
|
|
177
|
-
const
|
|
178
|
-
const program = ts.createProgram(routeFiles, {
|
|
181
|
+
const compilerOptions = {
|
|
179
182
|
target: ts.ScriptTarget.ESNext,
|
|
180
183
|
module: ts.ModuleKind.Node16,
|
|
181
184
|
skipLibCheck: true,
|
|
@@ -184,8 +187,11 @@ export const inspect = async (logger, routeFiles, options = {}) => {
|
|
|
184
187
|
types: [],
|
|
185
188
|
allowJs: false,
|
|
186
189
|
checkJs: false,
|
|
187
|
-
}
|
|
188
|
-
|
|
190
|
+
};
|
|
191
|
+
const startProgram = performance.now();
|
|
192
|
+
const program = ts.createProgram(routeFiles, compilerOptions, undefined, // host
|
|
193
|
+
options.oldProgram);
|
|
194
|
+
logger.debug(`Created program in ${(performance.now() - startProgram).toFixed(0)}ms (${routeFiles.length} files${options.oldProgram ? ', incremental' : ''})`);
|
|
189
195
|
const startChecker = performance.now();
|
|
190
196
|
const checker = program.getTypeChecker();
|
|
191
197
|
logger.debug(`Got type checker in ${(performance.now() - startChecker).toFixed(2)}ms`);
|
|
@@ -204,20 +210,26 @@ export const inspect = async (logger, routeFiles, options = {}) => {
|
|
|
204
210
|
for (const sourceFile of sourceFiles) {
|
|
205
211
|
ts.forEachChild(sourceFile, (child) => visitSetup(logger, checker, child, state, options));
|
|
206
212
|
}
|
|
207
|
-
logger.debug(`Visit setup phase completed in ${(performance.now() - startSetup).toFixed(
|
|
213
|
+
logger.debug(`Visit setup phase completed in ${(performance.now() - startSetup).toFixed(0)}ms`);
|
|
214
|
+
// Load addon function metadata so wirings can reference addon functions
|
|
215
|
+
await loadAddonFunctionsMeta(logger, state);
|
|
208
216
|
if (!options.setupOnly) {
|
|
209
217
|
// Second sweep: add all transports
|
|
210
218
|
const startRoutes = performance.now();
|
|
211
219
|
for (const sourceFile of sourceFiles) {
|
|
212
220
|
ts.forEachChild(sourceFile, (child) => visitRoutes(logger, checker, child, state, options));
|
|
213
221
|
}
|
|
214
|
-
logger.debug(`Visit routes phase completed in ${(performance.now() - startRoutes).toFixed(
|
|
222
|
+
logger.debug(`Visit routes phase completed in ${(performance.now() - startRoutes).toFixed(0)}ms`);
|
|
215
223
|
resolveLatestVersions(state, logger);
|
|
216
224
|
if (options.schemaConfig) {
|
|
225
|
+
const startSchemas = performance.now();
|
|
217
226
|
state.schemas = await generateAllSchemas(logger, options.schemaConfig, state);
|
|
227
|
+
logger.debug(`generateAllSchemas took ${(performance.now() - startSchemas).toFixed(0)}ms`);
|
|
218
228
|
computeContractHashes(state.schemas, state.functions.typesMap, state.functions.meta);
|
|
219
229
|
computeRequiredSchemas(state, options);
|
|
220
230
|
}
|
|
231
|
+
// Re-load addon schemas (generateAllSchemas replaces state.schemas)
|
|
232
|
+
await loadAddonSchemas(logger, state);
|
|
221
233
|
state.manifest.initial = options.manifest ?? null;
|
|
222
234
|
const contracts = extractContractsFromMeta(state.functions.meta);
|
|
223
235
|
const baseManifest = state.manifest.initial ?? createEmptyManifest();
|
|
@@ -241,6 +253,7 @@ export const inspect = async (logger, routeFiles, options = {}) => {
|
|
|
241
253
|
computeMiddlewareGroupsMeta(state);
|
|
242
254
|
computePermissionsGroupsMeta(state);
|
|
243
255
|
computeDiagnostics(state);
|
|
256
|
+
validateSchemaWiringSeparation(logger, state);
|
|
244
257
|
if (options.openAPI) {
|
|
245
258
|
state.openAPISpec = await generateOpenAPISpec(logger, state.functions.meta, state.http.meta, state.schemas, options.openAPI.additionalInfo, pikkuState(null, 'misc', 'errors'));
|
|
246
259
|
}
|
|
@@ -249,5 +262,6 @@ export const inspect = async (logger, routeFiles, options = {}) => {
|
|
|
249
262
|
validateSecretOverrides(logger, state);
|
|
250
263
|
validateVariableOverrides(logger, state);
|
|
251
264
|
}
|
|
265
|
+
state.program = program;
|
|
252
266
|
return state;
|
|
253
267
|
};
|
package/dist/types.d.ts
CHANGED
|
@@ -73,6 +73,7 @@ export interface InspectorFunctionState {
|
|
|
73
73
|
path: string;
|
|
74
74
|
exportedName: string;
|
|
75
75
|
}>;
|
|
76
|
+
approvalDescriptions: Record<string, InspectorApprovalDescriptionDefinition>;
|
|
76
77
|
}
|
|
77
78
|
export interface InspectorChannelState {
|
|
78
79
|
meta: ChannelsMeta;
|
|
@@ -108,6 +109,13 @@ export interface InspectorChannelMiddlewareState {
|
|
|
108
109
|
export interface InspectorAIMiddlewareState {
|
|
109
110
|
definitions: Record<string, InspectorMiddlewareDefinition>;
|
|
110
111
|
}
|
|
112
|
+
export interface InspectorApprovalDescriptionDefinition {
|
|
113
|
+
services: FunctionServicesMeta;
|
|
114
|
+
wires?: FunctionWiresMeta;
|
|
115
|
+
sourceFile: string;
|
|
116
|
+
position: number;
|
|
117
|
+
exportedName: string | null;
|
|
118
|
+
}
|
|
111
119
|
export interface InspectorPermissionDefinition {
|
|
112
120
|
services: FunctionServicesMeta;
|
|
113
121
|
wires?: FunctionWiresMeta;
|
|
@@ -185,6 +193,7 @@ export type InspectorOptions = Partial<{
|
|
|
185
193
|
tags: string[];
|
|
186
194
|
manifest: VersionManifest;
|
|
187
195
|
modelConfig: InspectorModelConfig;
|
|
196
|
+
oldProgram: ts.Program | undefined;
|
|
188
197
|
}>;
|
|
189
198
|
export interface InspectorLogger {
|
|
190
199
|
info: (message: string) => void;
|
|
@@ -309,6 +318,7 @@ export interface InspectorState {
|
|
|
309
318
|
wireAddonDeclarations: Map<string, {
|
|
310
319
|
package: string;
|
|
311
320
|
rpcEndpoint?: string;
|
|
321
|
+
mcp?: boolean;
|
|
312
322
|
secretOverrides?: Record<string, string>;
|
|
313
323
|
variableOverrides?: Record<string, string>;
|
|
314
324
|
}>;
|
|
@@ -383,4 +393,6 @@ export interface InspectorState {
|
|
|
383
393
|
requiredSchemas: Set<string>;
|
|
384
394
|
openAPISpec: Record<string, any> | null;
|
|
385
395
|
diagnostics: InspectorDiagnostic[];
|
|
396
|
+
addonFunctions: Record<string, FunctionsMeta>;
|
|
397
|
+
program: ts.Program | null;
|
|
386
398
|
}
|
|
@@ -9,6 +9,7 @@ export function sanitizeTypeName(name) {
|
|
|
9
9
|
}
|
|
10
10
|
export function generateCustomTypes(typesMap, requiredTypes) {
|
|
11
11
|
const typeDeclarations = Array.from(typesMap.customTypes.entries())
|
|
12
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
12
13
|
.filter(([_name, { type }]) => {
|
|
13
14
|
const hasUndefinedGeneric = /\b(Name|In|Out|Key)\b/.test(type) && /\[.*\]/.test(type);
|
|
14
15
|
return !hasUndefinedGeneric;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { InspectorState, InspectorLogger } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* After the setup sweep discovers wireAddon() declarations, load each addon
|
|
4
|
+
* package's function metadata so that wiring handlers (channels, HTTP routes,
|
|
5
|
+
* schedules, etc.) can look up addon function types during the routes sweep.
|
|
6
|
+
*/
|
|
7
|
+
export declare function loadAddonFunctionsMeta(logger: InspectorLogger, state: InspectorState): Promise<void>;
|
|
8
|
+
/**
|
|
9
|
+
* Load addon schemas into state.schemas. Called after generateAllSchemas
|
|
10
|
+
* to ensure addon schemas aren't overwritten.
|
|
11
|
+
*/
|
|
12
|
+
export declare function loadAddonSchemas(logger: InspectorLogger, state: InspectorState): Promise<void>;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { readFile, readdir } from 'fs/promises';
|
|
2
|
+
import { createRequire } from 'module';
|
|
3
|
+
import { join, dirname } from 'path';
|
|
4
|
+
/**
|
|
5
|
+
* After the setup sweep discovers wireAddon() declarations, load each addon
|
|
6
|
+
* package's function metadata so that wiring handlers (channels, HTTP routes,
|
|
7
|
+
* schedules, etc.) can look up addon function types during the routes sweep.
|
|
8
|
+
*/
|
|
9
|
+
export async function loadAddonFunctionsMeta(logger, state) {
|
|
10
|
+
const { wireAddonDeclarations } = state.rpc;
|
|
11
|
+
if (wireAddonDeclarations.size === 0)
|
|
12
|
+
return;
|
|
13
|
+
const require = createRequire(join(state.rootDir, 'package.json'));
|
|
14
|
+
for (const [namespace, decl] of wireAddonDeclarations) {
|
|
15
|
+
try {
|
|
16
|
+
const metaPath = require.resolve(`${decl.package}/.pikku/function/pikku-functions-meta.gen.json`);
|
|
17
|
+
const raw = await readFile(metaPath, 'utf-8');
|
|
18
|
+
const meta = JSON.parse(raw);
|
|
19
|
+
state.addonFunctions[namespace] = meta;
|
|
20
|
+
logger.debug(`Loaded ${Object.keys(meta).length} addon functions for '${namespace}' from ${decl.package}`);
|
|
21
|
+
// If wireAddon has mcp: true, expose addon functions with mcp: true as MCP tools
|
|
22
|
+
if (decl.mcp) {
|
|
23
|
+
for (const [funcName, funcMeta] of Object.entries(meta)) {
|
|
24
|
+
if (funcMeta.mcp) {
|
|
25
|
+
const toolName = `${namespace}:${funcName}`;
|
|
26
|
+
state.mcpEndpoints.toolsMeta[toolName] = {
|
|
27
|
+
pikkuFuncId: `${namespace}:${funcName}`,
|
|
28
|
+
name: toolName,
|
|
29
|
+
description: funcMeta.description || funcMeta.title || funcName,
|
|
30
|
+
inputSchema: funcMeta.inputSchemaName ?? null,
|
|
31
|
+
outputSchema: funcMeta.outputSchemaName ?? null,
|
|
32
|
+
tags: funcMeta.tags,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
logger.warn(`Failed to load addon function metadata for '${namespace}' (${decl.package}): ${error.message}`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Load addon schemas into state.schemas. Called after generateAllSchemas
|
|
45
|
+
* to ensure addon schemas aren't overwritten.
|
|
46
|
+
*/
|
|
47
|
+
export async function loadAddonSchemas(logger, state) {
|
|
48
|
+
const { wireAddonDeclarations } = state.rpc;
|
|
49
|
+
if (wireAddonDeclarations.size === 0)
|
|
50
|
+
return;
|
|
51
|
+
const require = createRequire(join(state.rootDir, 'package.json'));
|
|
52
|
+
for (const [namespace, decl] of wireAddonDeclarations) {
|
|
53
|
+
try {
|
|
54
|
+
const metaPath = require.resolve(`${decl.package}/.pikku/function/pikku-functions-meta.gen.json`);
|
|
55
|
+
const schemasDir = join(dirname(metaPath), '..', 'schemas', 'schemas');
|
|
56
|
+
try {
|
|
57
|
+
const schemaFiles = await readdir(schemasDir);
|
|
58
|
+
for (const file of schemaFiles) {
|
|
59
|
+
if (!file.endsWith('.schema.json'))
|
|
60
|
+
continue;
|
|
61
|
+
const schemaName = file.replace('.schema.json', '');
|
|
62
|
+
if (!state.schemas[schemaName]) {
|
|
63
|
+
const schemaRaw = await readFile(join(schemasDir, file), 'utf-8');
|
|
64
|
+
state.schemas[schemaName] = JSON.parse(schemaRaw);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
// No schemas directory — that's fine
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
logger.warn(`Failed to load addon schemas for '${namespace}' (${decl.package}): ${error.message}`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -22,4 +22,13 @@ export declare function computePermissionsGroupsMeta(state: InspectorState): voi
|
|
|
22
22
|
export declare function computeRequiredSchemas(state: InspectorState, options: InspectorOptions): void;
|
|
23
23
|
export declare function validateAgentModels(logger: InspectorLogger, state: InspectorState | Omit<InspectorState, 'typesLookup'>, modelConfig?: InspectorModelConfig): void;
|
|
24
24
|
export declare function validateAgentOverrides(logger: InspectorLogger, state: InspectorState | Omit<InspectorState, 'typesLookup'>, modelConfig?: InspectorModelConfig): void;
|
|
25
|
+
/**
|
|
26
|
+
* Validates that Zod schemas and wiring side-effects (wireHTTPRoutes,
|
|
27
|
+
* addPermission, addHTTPMiddleware, etc.) do not coexist in the same file.
|
|
28
|
+
*
|
|
29
|
+
* The CLI uses tsImport to extract Zod schemas at runtime, which executes
|
|
30
|
+
* all top-level code in the file. Wiring calls crash during this process
|
|
31
|
+
* because the pikku state metadata doesn't exist in the CLI context.
|
|
32
|
+
*/
|
|
33
|
+
export declare function validateSchemaWiringSeparation(logger: InspectorLogger, state: InspectorState): void;
|
|
25
34
|
export declare function computeDiagnostics(state: InspectorState): void;
|
|
@@ -217,6 +217,32 @@ export function computeResolvedIOTypes(state) {
|
|
|
217
217
|
}
|
|
218
218
|
}
|
|
219
219
|
state.resolvedIOTypes[pikkuFuncId] = { inputType, outputType };
|
|
220
|
+
if (meta.inputSchemaName && inputType !== 'null') {
|
|
221
|
+
meta.inputSchemaName = inputType;
|
|
222
|
+
}
|
|
223
|
+
if (meta.outputSchemaName && outputType !== 'null') {
|
|
224
|
+
meta.outputSchemaName = outputType;
|
|
225
|
+
}
|
|
226
|
+
if (meta.inputs) {
|
|
227
|
+
meta.inputs = meta.inputs.map((name) => {
|
|
228
|
+
try {
|
|
229
|
+
return functions.typesMap.getTypeMeta(name).uniqueName;
|
|
230
|
+
}
|
|
231
|
+
catch {
|
|
232
|
+
return name;
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
if (meta.outputs) {
|
|
237
|
+
meta.outputs = meta.outputs.map((name) => {
|
|
238
|
+
try {
|
|
239
|
+
return functions.typesMap.getTypeMeta(name).uniqueName;
|
|
240
|
+
}
|
|
241
|
+
catch {
|
|
242
|
+
return name;
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
}
|
|
220
246
|
}
|
|
221
247
|
}
|
|
222
248
|
const serializeGroupMap = (groupMap) => {
|
|
@@ -352,6 +378,52 @@ export function validateAgentOverrides(logger, state, modelConfig) {
|
|
|
352
378
|
}
|
|
353
379
|
}
|
|
354
380
|
}
|
|
381
|
+
/**
|
|
382
|
+
* Validates that Zod schemas and wiring side-effects (wireHTTPRoutes,
|
|
383
|
+
* addPermission, addHTTPMiddleware, etc.) do not coexist in the same file.
|
|
384
|
+
*
|
|
385
|
+
* The CLI uses tsImport to extract Zod schemas at runtime, which executes
|
|
386
|
+
* all top-level code in the file. Wiring calls crash during this process
|
|
387
|
+
* because the pikku state metadata doesn't exist in the CLI context.
|
|
388
|
+
*/
|
|
389
|
+
export function validateSchemaWiringSeparation(logger, state) {
|
|
390
|
+
// Collect files that contain schemas
|
|
391
|
+
const schemaFiles = new Set();
|
|
392
|
+
for (const ref of state.schemaLookup.values()) {
|
|
393
|
+
schemaFiles.add(ref.sourceFile);
|
|
394
|
+
}
|
|
395
|
+
// Collect files that contain wiring side-effects
|
|
396
|
+
const wiringFiles = new Set();
|
|
397
|
+
// HTTP route wirings
|
|
398
|
+
for (const file of state.http.files) {
|
|
399
|
+
wiringFiles.add(file);
|
|
400
|
+
}
|
|
401
|
+
// Permission wirings (addPermission calls)
|
|
402
|
+
for (const meta of state.permissions.tagPermissions.values()) {
|
|
403
|
+
wiringFiles.add(meta.sourceFile);
|
|
404
|
+
}
|
|
405
|
+
for (const meta of state.http.routePermissions.values()) {
|
|
406
|
+
wiringFiles.add(meta.sourceFile);
|
|
407
|
+
}
|
|
408
|
+
// Middleware wirings (addHTTPMiddleware calls)
|
|
409
|
+
for (const meta of state.http.routeMiddleware.values()) {
|
|
410
|
+
wiringFiles.add(meta.sourceFile);
|
|
411
|
+
}
|
|
412
|
+
for (const meta of state.middleware.tagMiddleware.values()) {
|
|
413
|
+
wiringFiles.add(meta.sourceFile);
|
|
414
|
+
}
|
|
415
|
+
// Check for overlap
|
|
416
|
+
for (const file of schemaFiles) {
|
|
417
|
+
if (wiringFiles.has(file)) {
|
|
418
|
+
const schemas = Array.from(state.schemaLookup.entries())
|
|
419
|
+
.filter(([, ref]) => ref.sourceFile === file)
|
|
420
|
+
.map(([name]) => name);
|
|
421
|
+
logger.critical(ErrorCode.SCHEMA_AND_WIRING_COLOCATED, `File '${file}' contains both Zod schemas (${schemas.join(', ')}) and wiring calls (wireHTTPRoutes, addPermission, etc.). ` +
|
|
422
|
+
`These must be in separate files because the CLI imports schema files at runtime, which triggers wiring side-effects that crash without server context. ` +
|
|
423
|
+
`Move the route/wiring definitions to a dedicated wiring file.`);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
}
|
|
355
427
|
export function computeDiagnostics(state) {
|
|
356
428
|
const diagnostics = [];
|
|
357
429
|
for (const [id, meta] of Object.entries(state.functions.meta)) {
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { FunctionMeta, FunctionsMeta } from '@pikku/core';
|
|
2
|
+
/**
|
|
3
|
+
* Look up function metadata by pikkuFuncId, checking both local functions
|
|
4
|
+
* and addon functions. Addon functions use namespaced IDs like 'namespace:funcName'.
|
|
5
|
+
*/
|
|
6
|
+
export declare function resolveFunctionMeta(state: {
|
|
7
|
+
functions: {
|
|
8
|
+
meta: FunctionsMeta;
|
|
9
|
+
};
|
|
10
|
+
addonFunctions: Record<string, FunctionsMeta>;
|
|
11
|
+
}, pikkuFuncId: string): FunctionMeta | undefined;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Look up function metadata by pikkuFuncId, checking both local functions
|
|
3
|
+
* and addon functions. Addon functions use namespaced IDs like 'namespace:funcName'.
|
|
4
|
+
*/
|
|
5
|
+
export function resolveFunctionMeta(state, pikkuFuncId) {
|
|
6
|
+
// Check local functions first
|
|
7
|
+
const local = state.functions.meta[pikkuFuncId];
|
|
8
|
+
if (local)
|
|
9
|
+
return local;
|
|
10
|
+
// Check addon functions (namespaced like 'swaggerPetstore:addPet')
|
|
11
|
+
const colonIndex = pikkuFuncId.indexOf(':');
|
|
12
|
+
if (colonIndex === -1)
|
|
13
|
+
return undefined;
|
|
14
|
+
const namespace = pikkuFuncId.substring(0, colonIndex);
|
|
15
|
+
const funcName = pikkuFuncId.substring(colonIndex + 1);
|
|
16
|
+
return state.addonFunctions[namespace]?.[funcName];
|
|
17
|
+
}
|
|
@@ -46,14 +46,25 @@ function primitiveTypeToSchema(typeStr) {
|
|
|
46
46
|
}
|
|
47
47
|
return null;
|
|
48
48
|
}
|
|
49
|
+
// Cached state for schema program reuse across inspect() calls
|
|
50
|
+
let cachedSchemaProgram;
|
|
51
|
+
let cachedParsedConfig;
|
|
52
|
+
let cachedTsconfigPath;
|
|
53
|
+
let cachedCustomTypesContent;
|
|
54
|
+
let cachedTSSchemas;
|
|
49
55
|
function createProgramWithVirtualFile(tsconfig, virtualFilePath, virtualFileContent) {
|
|
50
56
|
const configPath = resolve(tsconfig);
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
57
|
+
// Cache the parsed tsconfig — it doesn't change between runs
|
|
58
|
+
if (!cachedParsedConfig || cachedTsconfigPath !== configPath) {
|
|
59
|
+
const configFile = ts.readConfigFile(configPath, ts.sys.readFile);
|
|
60
|
+
const basePath = dirname(configPath);
|
|
61
|
+
cachedParsedConfig = ts.parseJsonConfigFileContent(configFile.config, ts.sys, basePath);
|
|
62
|
+
cachedTsconfigPath = configPath;
|
|
63
|
+
cachedSchemaProgram = undefined;
|
|
64
|
+
}
|
|
54
65
|
const resolvedVirtualPath = resolve(virtualFilePath);
|
|
55
|
-
const fileNames = [...
|
|
56
|
-
const defaultHost = ts.createCompilerHost(
|
|
66
|
+
const fileNames = [...cachedParsedConfig.fileNames, resolvedVirtualPath];
|
|
67
|
+
const defaultHost = ts.createCompilerHost(cachedParsedConfig.options);
|
|
57
68
|
const customHost = {
|
|
58
69
|
...defaultHost,
|
|
59
70
|
getSourceFile(fileName, languageVersionOrOptions, onError, shouldCreateNewSourceFile) {
|
|
@@ -73,7 +84,10 @@ function createProgramWithVirtualFile(tsconfig, virtualFilePath, virtualFileCont
|
|
|
73
84
|
return defaultHost.readFile(fileName);
|
|
74
85
|
},
|
|
75
86
|
};
|
|
76
|
-
|
|
87
|
+
const program = ts.createProgram(fileNames, cachedParsedConfig.options, customHost, cachedSchemaProgram // reuse previous program for incremental compilation
|
|
88
|
+
);
|
|
89
|
+
cachedSchemaProgram = program;
|
|
90
|
+
return program;
|
|
77
91
|
}
|
|
78
92
|
function generateTSSchemas(logger, tsconfig, customTypesContent, typesMap, functionMeta, httpWiringsMeta, additionalTypes, additionalProperties = false, schemaLookup) {
|
|
79
93
|
const schemasSet = new Set(typesMap.customTypes.keys());
|
|
@@ -204,6 +218,12 @@ export async function generateAllSchemas(logger, config, state) {
|
|
|
204
218
|
const zodSchemas = await generateZodSchemas(logger, state.schemaLookup, state.functions.typesMap);
|
|
205
219
|
const requiredTypes = new Set();
|
|
206
220
|
const customTypesContent = generateCustomTypes(state.functions.typesMap, requiredTypes);
|
|
221
|
+
if (cachedTSSchemas && cachedCustomTypesContent === customTypesContent) {
|
|
222
|
+
logger.debug('Reusing cached TS schemas (types unchanged)');
|
|
223
|
+
return { ...cachedTSSchemas, ...zodSchemas };
|
|
224
|
+
}
|
|
207
225
|
const tsSchemas = generateTSSchemas(logger, config.tsconfig, customTypesContent, state.functions.typesMap, state.functions.meta, state.http.meta, config.schemasFromTypes, config.schema?.additionalProperties, state.schemaLookup);
|
|
226
|
+
cachedCustomTypesContent = customTypesContent;
|
|
227
|
+
cachedTSSchemas = tsSchemas;
|
|
208
228
|
return { ...tsSchemas, ...zodSchemas };
|
|
209
229
|
}
|
|
@@ -100,6 +100,7 @@ export interface SerializableInspectorState {
|
|
|
100
100
|
path: string;
|
|
101
101
|
exportedName: string;
|
|
102
102
|
}]>;
|
|
103
|
+
approvalDescriptions: InspectorState['functions']['approvalDescriptions'];
|
|
103
104
|
};
|
|
104
105
|
http: {
|
|
105
106
|
metaInputTypes: Array<[
|
|
@@ -253,6 +254,7 @@ export interface SerializableInspectorState {
|
|
|
253
254
|
requiredSchemas: string[];
|
|
254
255
|
openAPISpec: Record<string, any> | null;
|
|
255
256
|
diagnostics: InspectorDiagnostic[];
|
|
257
|
+
addonFunctions: InspectorState['addonFunctions'];
|
|
256
258
|
}
|
|
257
259
|
/**
|
|
258
260
|
* Serializes InspectorState to a JSON-compatible format
|
|
@@ -32,6 +32,7 @@ export function serializeInspectorState(state) {
|
|
|
32
32
|
typesMap: serializeTypesMap(state.functions.typesMap),
|
|
33
33
|
meta: state.functions.meta,
|
|
34
34
|
files: Array.from(state.functions.files.entries()),
|
|
35
|
+
approvalDescriptions: state.functions.approvalDescriptions,
|
|
35
36
|
},
|
|
36
37
|
http: {
|
|
37
38
|
metaInputTypes: Array.from(state.http.metaInputTypes.entries()),
|
|
@@ -137,6 +138,7 @@ export function serializeInspectorState(state) {
|
|
|
137
138
|
requiredSchemas: Array.from(state.requiredSchemas),
|
|
138
139
|
openAPISpec: state.openAPISpec,
|
|
139
140
|
diagnostics: state.diagnostics,
|
|
141
|
+
addonFunctions: state.addonFunctions,
|
|
140
142
|
};
|
|
141
143
|
}
|
|
142
144
|
/**
|
|
@@ -173,6 +175,7 @@ export function deserializeInspectorState(data) {
|
|
|
173
175
|
typesMap: deserializeTypesMap(data.functions.typesMap),
|
|
174
176
|
meta: data.functions.meta,
|
|
175
177
|
files: new Map(data.functions.files),
|
|
178
|
+
approvalDescriptions: data.functions.approvalDescriptions || {},
|
|
176
179
|
},
|
|
177
180
|
http: {
|
|
178
181
|
metaInputTypes: new Map(data.http.metaInputTypes),
|
|
@@ -288,5 +291,7 @@ export function deserializeInspectorState(data) {
|
|
|
288
291
|
requiredSchemas: new Set(data.requiredSchemas || []),
|
|
289
292
|
openAPISpec: data.openAPISpec || null,
|
|
290
293
|
diagnostics: data.diagnostics || [],
|
|
294
|
+
addonFunctions: data.addonFunctions || {},
|
|
295
|
+
program: null,
|
|
291
296
|
};
|
|
292
297
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import { resolveFunctionMeta } from './resolve-function-meta.js';
|
|
1
2
|
export const serializeMCPJson = (logger, state) => {
|
|
2
3
|
const { mcpEndpoints, functions, schemas } = state;
|
|
3
|
-
const {
|
|
4
|
+
const { typesMap } = functions;
|
|
4
5
|
const { resourcesMeta, toolsMeta, promptsMeta } = mcpEndpoints;
|
|
5
6
|
const tools = [];
|
|
6
7
|
const resources = [];
|
|
@@ -19,9 +20,14 @@ export const serializeMCPJson = (logger, state) => {
|
|
|
19
20
|
].includes(typeName)) {
|
|
20
21
|
return undefined;
|
|
21
22
|
}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
// Try local typesMap first, fall back to direct schema lookup (for addon types)
|
|
24
|
+
let uniqueName;
|
|
25
|
+
try {
|
|
26
|
+
uniqueName = typesMap.getUniqueName(typeName);
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
// Type not in local typesMap — try direct schema lookup (addon schemas)
|
|
30
|
+
uniqueName = typeName;
|
|
25
31
|
}
|
|
26
32
|
const schema = schemas[uniqueName];
|
|
27
33
|
if (!schema) {
|
|
@@ -31,7 +37,7 @@ export const serializeMCPJson = (logger, state) => {
|
|
|
31
37
|
return schema;
|
|
32
38
|
};
|
|
33
39
|
for (const [name, endpointMeta] of Object.entries(resourcesMeta)) {
|
|
34
|
-
const functionMeta =
|
|
40
|
+
const functionMeta = resolveFunctionMeta(state, endpointMeta.pikkuFuncId);
|
|
35
41
|
if (!functionMeta) {
|
|
36
42
|
logger.warn(`Function ${endpointMeta.pikkuFuncId} not found in functionsMeta. Skipping resource ${name}.`);
|
|
37
43
|
continue;
|
|
@@ -50,7 +56,7 @@ export const serializeMCPJson = (logger, state) => {
|
|
|
50
56
|
});
|
|
51
57
|
}
|
|
52
58
|
for (const [name, endpointMeta] of Object.entries(toolsMeta)) {
|
|
53
|
-
const functionMeta =
|
|
59
|
+
const functionMeta = resolveFunctionMeta(state, endpointMeta.pikkuFuncId);
|
|
54
60
|
if (!functionMeta) {
|
|
55
61
|
logger.warn(`Function ${endpointMeta.pikkuFuncId} not found in functionsMeta. Skipping tool ${name}.`);
|
|
56
62
|
continue;
|
|
@@ -68,7 +74,7 @@ export const serializeMCPJson = (logger, state) => {
|
|
|
68
74
|
});
|
|
69
75
|
}
|
|
70
76
|
for (const [name, endpointMeta] of Object.entries(promptsMeta)) {
|
|
71
|
-
const functionMeta =
|
|
77
|
+
const functionMeta = resolveFunctionMeta(state, endpointMeta.pikkuFuncId);
|
|
72
78
|
if (!functionMeta) {
|
|
73
79
|
logger.warn(`Function ${endpointMeta.pikkuFuncId} not found in functionsMeta. Skipping prompt ${name}.`);
|
|
74
80
|
continue;
|