@pikku/inspector 0.11.1 → 0.12.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.
- package/CHANGELOG.md +26 -1
- package/OPTIMIZATION-PLAN.md +195 -0
- package/dist/add/add-ai-agent.d.ts +2 -0
- package/dist/add/add-ai-agent.js +314 -0
- package/dist/add/add-channel.js +69 -61
- package/dist/add/add-cli.js +36 -18
- package/dist/add/add-file-with-factory.js +2 -0
- package/dist/add/add-functions.js +327 -59
- package/dist/add/add-http-route.d.ts +19 -10
- package/dist/add/add-http-route.js +153 -44
- package/dist/add/add-http-routes.d.ts +5 -0
- package/dist/add/add-http-routes.js +159 -0
- package/dist/add/add-keyed-wiring.d.ts +12 -0
- package/dist/add/add-keyed-wiring.js +97 -0
- package/dist/add/add-mcp-prompt.js +14 -9
- package/dist/add/add-mcp-resource.js +14 -9
- package/dist/add/add-middleware.d.ts +1 -4
- package/dist/add/add-middleware.js +364 -79
- package/dist/add/add-permission.d.ts +1 -1
- package/dist/add/add-permission.js +152 -40
- package/dist/add/add-queue-worker.js +18 -12
- package/dist/add/add-rpc-invocations.d.ts +3 -0
- package/dist/add/add-rpc-invocations.js +65 -25
- package/dist/add/add-schedule.js +11 -5
- package/dist/add/add-secret.d.ts +3 -0
- package/dist/add/add-secret.js +82 -0
- package/dist/add/add-trigger.d.ts +2 -0
- package/dist/add/add-trigger.js +87 -0
- package/dist/add/add-variable.d.ts +1 -0
- package/dist/add/add-variable.js +8 -0
- package/dist/add/add-workflow-graph.d.ts +7 -0
- package/dist/add/add-workflow-graph.js +396 -0
- package/dist/add/add-workflow.js +124 -26
- package/dist/error-codes.d.ts +16 -1
- package/dist/error-codes.js +21 -1
- package/dist/index.d.ts +9 -5
- package/dist/index.js +5 -2
- package/dist/inspector.d.ts +1 -1
- package/dist/inspector.js +106 -13
- package/dist/schema-generator.d.ts +1 -0
- package/dist/schema-generator.js +1 -0
- package/dist/types-map.js +10 -1
- package/dist/types.d.ts +180 -30
- package/dist/utils/compute-required-schemas.d.ts +4 -0
- package/dist/utils/compute-required-schemas.js +41 -0
- package/dist/utils/contract-hashes.d.ts +35 -0
- package/dist/utils/contract-hashes.js +202 -0
- package/dist/utils/custom-types-generator.d.ts +9 -0
- package/dist/utils/custom-types-generator.js +71 -0
- package/dist/utils/detect-schema-vendor.d.ts +22 -0
- package/dist/utils/detect-schema-vendor.js +76 -0
- package/dist/utils/ensure-function-metadata.d.ts +5 -2
- package/dist/utils/ensure-function-metadata.js +220 -6
- package/dist/utils/extract-function-name.d.ts +5 -16
- package/dist/utils/extract-function-name.js +93 -298
- package/dist/utils/extract-services.d.ts +2 -1
- package/dist/utils/extract-services.js +25 -1
- package/dist/utils/filter-inspector-state.js +107 -23
- package/dist/utils/get-property-value.d.ts +8 -2
- package/dist/utils/get-property-value.js +33 -4
- package/dist/utils/hash.d.ts +2 -0
- package/dist/utils/hash.js +23 -0
- package/dist/utils/middleware.d.ts +7 -30
- package/dist/utils/middleware.js +80 -66
- package/dist/utils/permissions.d.ts +2 -2
- package/dist/utils/permissions.js +10 -10
- package/dist/utils/post-process.d.ts +9 -10
- package/dist/utils/post-process.js +231 -24
- package/dist/utils/resolve-external-package.d.ts +12 -0
- package/dist/utils/resolve-external-package.js +34 -0
- package/dist/utils/resolve-function-types.d.ts +6 -0
- package/dist/utils/resolve-function-types.js +29 -0
- package/dist/utils/resolve-identifier.d.ts +10 -0
- package/dist/utils/resolve-identifier.js +36 -0
- package/dist/utils/resolve-versions.d.ts +2 -0
- package/dist/utils/resolve-versions.js +78 -0
- package/dist/utils/schema-generator.d.ts +9 -0
- package/dist/utils/schema-generator.js +209 -0
- package/dist/utils/serialize-inspector-state.d.ts +73 -13
- package/dist/utils/serialize-inspector-state.js +102 -6
- package/dist/utils/serialize-mcp-json.d.ts +2 -0
- package/dist/utils/serialize-mcp-json.js +99 -0
- package/dist/utils/serialize-middleware-groups-meta.d.ts +12 -0
- package/dist/utils/serialize-middleware-groups-meta.js +28 -0
- package/dist/utils/serialize-openapi-json.d.ts +85 -0
- package/dist/utils/serialize-openapi-json.js +151 -0
- package/dist/utils/serialize-permissions-groups-meta.d.ts +6 -0
- package/dist/utils/serialize-permissions-groups-meta.js +31 -0
- package/dist/utils/workflow/dsl/deserialize-dsl-workflow.d.ts +24 -0
- package/dist/utils/workflow/dsl/deserialize-dsl-workflow.js +830 -0
- package/dist/{workflow/extract-simple-workflow.d.ts → utils/workflow/dsl/extract-dsl-workflow.d.ts} +4 -2
- package/dist/{workflow/extract-simple-workflow.js → utils/workflow/dsl/extract-dsl-workflow.js} +572 -72
- package/dist/utils/workflow/dsl/index.d.ts +7 -0
- package/dist/utils/workflow/dsl/index.js +7 -0
- package/dist/{workflow → utils/workflow/dsl}/patterns.d.ts +21 -0
- package/dist/{workflow → utils/workflow/dsl}/patterns.js +90 -10
- package/dist/{workflow → utils/workflow/dsl}/validation.d.ts +2 -0
- package/dist/{workflow → utils/workflow/dsl}/validation.js +25 -7
- package/dist/utils/workflow/graph/convert-dsl-to-graph.d.ts +13 -0
- package/dist/utils/workflow/graph/convert-dsl-to-graph.js +318 -0
- package/dist/utils/workflow/graph/finalize-workflow-wires.d.ts +3 -0
- package/dist/utils/workflow/graph/finalize-workflow-wires.js +276 -0
- package/dist/utils/workflow/graph/finalize-workflows.d.ts +2 -0
- package/dist/utils/workflow/graph/finalize-workflows.js +75 -0
- package/dist/utils/workflow/graph/index.d.ts +8 -0
- package/dist/utils/workflow/graph/index.js +8 -0
- package/dist/utils/workflow/graph/serialize-workflow-graph.d.ts +35 -0
- package/dist/utils/workflow/graph/serialize-workflow-graph.js +150 -0
- package/dist/utils/workflow/graph/workflow-graph.types.d.ts +203 -0
- package/dist/utils/workflow/graph/workflow-graph.types.js +38 -0
- package/dist/visit.js +13 -2
- package/package.json +26 -4
- package/src/add/add-ai-agent.ts +468 -0
- package/src/add/add-channel.ts +82 -79
- package/src/add/add-cli.ts +49 -20
- package/src/add/add-file-with-factory.ts +2 -0
- package/src/add/add-functions.ts +429 -71
- package/src/add/add-http-route.ts +246 -65
- package/src/add/add-http-routes.ts +228 -0
- package/src/add/add-keyed-wiring.ts +151 -0
- package/src/add/add-mcp-prompt.ts +26 -15
- package/src/add/add-mcp-resource.ts +27 -15
- package/src/add/add-middleware.ts +482 -80
- package/src/add/add-permission.ts +199 -40
- package/src/add/add-queue-worker.ts +24 -19
- package/src/add/add-rpc-invocations.ts +78 -31
- package/src/add/add-schedule.ts +16 -11
- package/src/add/add-secret.ts +140 -0
- package/src/add/add-trigger.ts +154 -0
- package/src/add/add-variable.ts +9 -0
- package/src/add/add-workflow-graph.ts +522 -0
- package/src/add/add-workflow.ts +117 -30
- package/src/error-codes.ts +26 -1
- package/src/index.ts +27 -8
- package/src/inspector.ts +145 -17
- package/src/schema-generator.ts +1 -0
- package/src/types-map.ts +12 -1
- package/src/types.ts +192 -51
- package/src/utils/compute-required-schemas.ts +49 -0
- package/src/utils/contract-hashes.test.ts +528 -0
- package/src/utils/contract-hashes.ts +290 -0
- package/src/utils/custom-types-generator.ts +88 -0
- package/src/utils/detect-schema-vendor.ts +90 -0
- package/src/utils/ensure-function-metadata.ts +324 -7
- package/src/utils/extract-function-name.ts +108 -358
- package/src/utils/extract-services.ts +35 -2
- package/src/utils/filter-inspector-state.test.ts +34 -20
- package/src/utils/filter-inspector-state.ts +140 -31
- package/src/utils/get-property-value.ts +50 -5
- package/src/utils/hash.ts +26 -0
- package/src/utils/middleware.test.ts +204 -0
- package/src/utils/middleware.ts +129 -67
- package/src/utils/permissions.test.ts +35 -12
- package/src/utils/permissions.ts +10 -10
- package/src/utils/post-process.ts +283 -43
- package/src/utils/resolve-external-package.ts +42 -0
- package/src/utils/resolve-function-types.ts +42 -0
- package/src/utils/resolve-identifier.ts +46 -0
- package/src/utils/resolve-versions.test.ts +249 -0
- package/src/utils/resolve-versions.ts +105 -0
- package/src/utils/schema-generator.ts +329 -0
- package/src/utils/serialize-inspector-state.ts +181 -20
- package/src/utils/serialize-mcp-json.ts +145 -0
- package/src/utils/serialize-middleware-groups-meta.ts +33 -0
- package/src/utils/serialize-openapi-json.ts +277 -0
- package/src/utils/serialize-permissions-groups-meta.ts +35 -0
- package/src/utils/test-data/inspector-state.json +69 -66
- package/src/utils/workflow/dsl/deserialize-dsl-workflow.ts +1104 -0
- package/src/{workflow/extract-simple-workflow.ts → utils/workflow/dsl/extract-dsl-workflow.ts} +678 -85
- package/src/utils/workflow/dsl/index.ts +11 -0
- package/src/{workflow → utils/workflow/dsl}/patterns.ts +108 -11
- package/src/{workflow → utils/workflow/dsl}/validation.ts +34 -7
- package/src/utils/workflow/graph/convert-dsl-to-graph.ts +422 -0
- package/src/utils/workflow/graph/finalize-workflow-wires.ts +310 -0
- package/src/utils/workflow/graph/finalize-workflows.ts +100 -0
- package/src/utils/workflow/graph/index.ts +11 -0
- package/src/utils/workflow/graph/serialize-workflow-graph.ts +216 -0
- package/src/utils/workflow/graph/workflow-graph.types.ts +231 -0
- package/src/visit.ts +14 -2
- package/tsconfig.tsbuildinfo +1 -1
- package/dist/add/add-mcp-tool.d.ts +0 -2
- package/dist/add/add-mcp-tool.js +0 -81
- package/dist/utils/extract-service-metadata.d.ts +0 -19
- package/dist/utils/extract-service-metadata.js +0 -244
- package/dist/utils/write-service-metadata.d.ts +0 -13
- package/dist/utils/write-service-metadata.js +0 -37
- package/src/add/add-mcp-tool.ts +0 -141
- package/src/utils/extract-service-metadata.ts +0 -353
- package/src/utils/write-service-metadata.ts +0 -51
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
import * as ts from 'typescript';
|
|
2
|
-
import {
|
|
2
|
+
import { detectSchemaVendorOrError } from '../utils/detect-schema-vendor.js';
|
|
3
|
+
import { extractFunctionName, funcIdToTypeName, } from '../utils/extract-function-name.js';
|
|
3
4
|
import { extractFunctionNode } from '../utils/extract-function-node.js';
|
|
5
|
+
import { extractUsedWires } from '../utils/extract-services.js';
|
|
6
|
+
import { formatVersionedId } from '@pikku/core';
|
|
4
7
|
import { getPropertyValue, getCommonWireMetaData, } from '../utils/get-property-value.js';
|
|
5
8
|
import { resolveMiddleware } from '../utils/middleware.js';
|
|
9
|
+
import { resolvePermissions } from '../utils/permissions.js';
|
|
10
|
+
import { extractWireNames } from '../utils/post-process.js';
|
|
11
|
+
import { ErrorCode } from '../error-codes.js';
|
|
6
12
|
const isValidVariableName = (name) => {
|
|
7
13
|
const regex = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;
|
|
8
14
|
return regex.test(name);
|
|
@@ -11,7 +17,8 @@ const nullifyTypes = (type) => {
|
|
|
11
17
|
if (type === 'void' ||
|
|
12
18
|
type === 'undefined' ||
|
|
13
19
|
type === 'unknown' ||
|
|
14
|
-
type === 'any'
|
|
20
|
+
type === 'any' ||
|
|
21
|
+
type === 'null') {
|
|
15
22
|
return null;
|
|
16
23
|
}
|
|
17
24
|
return type;
|
|
@@ -109,7 +116,7 @@ const resolveUnionTypes = (checker, type) => {
|
|
|
109
116
|
// Check if it's a union type AND not part of an intersection
|
|
110
117
|
if (type.isUnion() && !(type.flags & ts.TypeFlags.Intersection)) {
|
|
111
118
|
for (const t of type.types) {
|
|
112
|
-
const name = nullifyTypes(checker.typeToString(t));
|
|
119
|
+
const name = nullifyTypes(checker.typeToString(t, undefined, ts.TypeFormatFlags.NoTruncation));
|
|
113
120
|
if (name) {
|
|
114
121
|
types.push(t);
|
|
115
122
|
names.push(name);
|
|
@@ -117,7 +124,7 @@ const resolveUnionTypes = (checker, type) => {
|
|
|
117
124
|
}
|
|
118
125
|
}
|
|
119
126
|
else {
|
|
120
|
-
const name = nullifyTypes(checker.typeToString(type));
|
|
127
|
+
const name = nullifyTypes(checker.typeToString(type, undefined, ts.TypeFormatFlags.NoTruncation));
|
|
121
128
|
if (name) {
|
|
122
129
|
types.push(type);
|
|
123
130
|
names.push(name);
|
|
@@ -129,8 +136,8 @@ const getNamesAndTypes = (checker, typesMap, direction, funcName, type) => {
|
|
|
129
136
|
if (!type) {
|
|
130
137
|
return { names: [], types: [] };
|
|
131
138
|
}
|
|
132
|
-
// 1) Handle an explicit void (or undefined) type up front
|
|
133
|
-
if (type.flags & ts.TypeFlags.VoidLike) {
|
|
139
|
+
// 1) Handle an explicit void (or undefined or null) type up front
|
|
140
|
+
if (type.flags & (ts.TypeFlags.VoidLike | ts.TypeFlags.Null)) {
|
|
134
141
|
return {
|
|
135
142
|
names: [],
|
|
136
143
|
types: [],
|
|
@@ -145,7 +152,7 @@ const getNamesAndTypes = (checker, typesMap, direction, funcName, type) => {
|
|
|
145
152
|
const firstName = rawNames[0];
|
|
146
153
|
if (rawNames.length > 1 || (firstName && !isValidVariableName(firstName))) {
|
|
147
154
|
const aliasType = rawNames.join(' | ');
|
|
148
|
-
const aliasName =
|
|
155
|
+
const aliasName = funcIdToTypeName(funcName) + direction;
|
|
149
156
|
// record the alias in your TypesMap
|
|
150
157
|
const references = rawTypes
|
|
151
158
|
.map((t) => resolveTypeImports(t, typesMap, true, checker))
|
|
@@ -184,7 +191,6 @@ const isPrimitiveType = (type) => {
|
|
|
184
191
|
ts.TypeFlags.Void |
|
|
185
192
|
ts.TypeFlags.Undefined |
|
|
186
193
|
ts.TypeFlags.Null |
|
|
187
|
-
ts.TypeFlags.Any |
|
|
188
194
|
ts.TypeFlags.Unknown |
|
|
189
195
|
ts.TypeFlags.VoidLike;
|
|
190
196
|
return (type.flags & primitiveFlags) !== 0;
|
|
@@ -211,7 +217,7 @@ function unwrapPromise(checker, type) {
|
|
|
211
217
|
* Inspect pikkuFunc calls, extract input/output and first-arg destructuring,
|
|
212
218
|
* then push into state.functions.meta.
|
|
213
219
|
*/
|
|
214
|
-
export const addFunctions = (logger, node, checker, state) => {
|
|
220
|
+
export const addFunctions = (logger, node, checker, state, options) => {
|
|
215
221
|
if (!ts.isCallExpression(node))
|
|
216
222
|
return;
|
|
217
223
|
const { expression, arguments: args, typeArguments } = node;
|
|
@@ -230,28 +236,153 @@ export const addFunctions = (logger, node, checker, state) => {
|
|
|
230
236
|
}
|
|
231
237
|
if (args.length === 0)
|
|
232
238
|
return;
|
|
233
|
-
|
|
239
|
+
let { pikkuFuncId, name, explicitName, exportedName } = extractFunctionName(node, checker, state.rootDir);
|
|
240
|
+
if (!pikkuFuncId || pikkuFuncId.startsWith('__temp_')) {
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
let title;
|
|
234
244
|
let tags;
|
|
235
245
|
let summary;
|
|
236
246
|
let description;
|
|
237
247
|
let errors;
|
|
238
248
|
let expose;
|
|
239
|
-
let
|
|
249
|
+
let remote;
|
|
250
|
+
let mcp;
|
|
251
|
+
let requiresApproval;
|
|
252
|
+
let version;
|
|
240
253
|
let objectNode;
|
|
254
|
+
let nodeDisplayName = null;
|
|
255
|
+
let nodeCategory = null;
|
|
256
|
+
let nodeType = null;
|
|
257
|
+
let nodeErrorOutput = null;
|
|
241
258
|
// Extract the function node using shared utility
|
|
242
259
|
const firstArg = args[0];
|
|
243
260
|
const { funcNode: handlerNode, resolvedFunc, isDirectFunction, } = extractFunctionNode(firstArg, checker);
|
|
261
|
+
// Variables to hold schema references if provided
|
|
262
|
+
let inputSchemaRef = null;
|
|
263
|
+
let outputSchemaRef = null;
|
|
264
|
+
// Helper to resolve schema identifier to its actual source file and detect vendor.
|
|
265
|
+
// Logs a fatal error and returns null if vendor cannot be determined.
|
|
266
|
+
const resolveSchemaRef = (identifier, context) => {
|
|
267
|
+
const symbol = checker.getSymbolAtLocation(identifier);
|
|
268
|
+
if (!symbol)
|
|
269
|
+
return null;
|
|
270
|
+
const decl = symbol.valueDeclaration || symbol.declarations?.[0];
|
|
271
|
+
if (!decl)
|
|
272
|
+
return null;
|
|
273
|
+
let sourceFile;
|
|
274
|
+
// If it's an import specifier, resolve the aliased symbol to get the actual source
|
|
275
|
+
if (ts.isImportSpecifier(decl)) {
|
|
276
|
+
const aliasedSymbol = checker.getAliasedSymbol(symbol);
|
|
277
|
+
if (aliasedSymbol) {
|
|
278
|
+
const aliasedDecl = aliasedSymbol.valueDeclaration || aliasedSymbol.declarations?.[0];
|
|
279
|
+
if (aliasedDecl) {
|
|
280
|
+
sourceFile = aliasedDecl.getSourceFile().fileName;
|
|
281
|
+
}
|
|
282
|
+
else {
|
|
283
|
+
return null;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
else {
|
|
287
|
+
return null;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
else {
|
|
291
|
+
sourceFile = decl.getSourceFile().fileName;
|
|
292
|
+
}
|
|
293
|
+
const vendor = detectSchemaVendorOrError(identifier, checker, logger, context, sourceFile);
|
|
294
|
+
if (!vendor)
|
|
295
|
+
return null;
|
|
296
|
+
return {
|
|
297
|
+
variableName: identifier.text,
|
|
298
|
+
sourceFile,
|
|
299
|
+
vendor,
|
|
300
|
+
};
|
|
301
|
+
};
|
|
244
302
|
// Extract config properties if using object form
|
|
245
303
|
if (ts.isObjectLiteralExpression(firstArg)) {
|
|
246
304
|
objectNode = firstArg;
|
|
247
305
|
const metadata = getCommonWireMetaData(firstArg, 'Function', name, logger);
|
|
306
|
+
if (metadata.disabled)
|
|
307
|
+
return;
|
|
308
|
+
title = metadata.title;
|
|
248
309
|
tags = metadata.tags;
|
|
249
310
|
summary = metadata.summary;
|
|
250
311
|
description = metadata.description;
|
|
251
312
|
errors = metadata.errors;
|
|
252
313
|
expose = getPropertyValue(firstArg, 'expose');
|
|
253
|
-
|
|
314
|
+
remote = getPropertyValue(firstArg, 'remote');
|
|
315
|
+
mcp = getPropertyValue(firstArg, 'mcp');
|
|
316
|
+
requiresApproval = getPropertyValue(firstArg, 'requiresApproval');
|
|
317
|
+
const versionRaw = getPropertyValue(firstArg, 'version');
|
|
318
|
+
if (versionRaw !== null && versionRaw !== undefined) {
|
|
319
|
+
const parsed = Number(versionRaw);
|
|
320
|
+
if (Number.isInteger(parsed) && parsed >= 1) {
|
|
321
|
+
version = parsed;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
// Extract node config from nested object
|
|
325
|
+
for (const prop of firstArg.properties) {
|
|
326
|
+
if (ts.isPropertyAssignment(prop) &&
|
|
327
|
+
ts.isIdentifier(prop.name) &&
|
|
328
|
+
prop.name.text === 'node' &&
|
|
329
|
+
ts.isObjectLiteralExpression(prop.initializer)) {
|
|
330
|
+
const nodeObj = prop.initializer;
|
|
331
|
+
nodeDisplayName = getPropertyValue(nodeObj, 'displayName');
|
|
332
|
+
nodeCategory = getPropertyValue(nodeObj, 'category');
|
|
333
|
+
nodeType = getPropertyValue(nodeObj, 'type');
|
|
334
|
+
nodeErrorOutput = getPropertyValue(nodeObj, 'errorOutput');
|
|
335
|
+
if (!nodeDisplayName) {
|
|
336
|
+
logger.critical(ErrorCode.MISSING_NAME, `Function '${name}' node config is missing the required 'displayName' property.`);
|
|
337
|
+
}
|
|
338
|
+
if (!nodeCategory) {
|
|
339
|
+
logger.critical(ErrorCode.MISSING_NAME, `Function '${name}' node config is missing the required 'category' property.`);
|
|
340
|
+
}
|
|
341
|
+
if (!nodeType) {
|
|
342
|
+
logger.critical(ErrorCode.MISSING_NAME, `Function '${name}' node config is missing the required 'type' property.`);
|
|
343
|
+
}
|
|
344
|
+
else if (!['trigger', 'action', 'end'].includes(nodeType)) {
|
|
345
|
+
logger.critical(ErrorCode.INVALID_VALUE, `Function '${name}' node config has invalid type '${nodeType}'. Must be 'trigger', 'action', or 'end'.`);
|
|
346
|
+
}
|
|
347
|
+
break;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
// Extract schema variable names from input/output properties
|
|
351
|
+
for (const prop of firstArg.properties) {
|
|
352
|
+
if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name)) {
|
|
353
|
+
const propName = prop.name.text;
|
|
354
|
+
if (propName === 'input' || propName === 'output') {
|
|
355
|
+
if (ts.isIdentifier(prop.initializer)) {
|
|
356
|
+
// Good - it's a variable reference, resolve its actual source file and vendor
|
|
357
|
+
const context = `Function '${name}' ${propName}`;
|
|
358
|
+
const ref = resolveSchemaRef(prop.initializer, context);
|
|
359
|
+
if (ref) {
|
|
360
|
+
if (propName === 'input') {
|
|
361
|
+
inputSchemaRef = ref;
|
|
362
|
+
}
|
|
363
|
+
else {
|
|
364
|
+
outputSchemaRef = ref;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
else if (ts.isCallExpression(prop.initializer)) {
|
|
369
|
+
// Bad - it's an inline expression
|
|
370
|
+
const schemaName = `${funcIdToTypeName(name)}${propName.charAt(0).toUpperCase() + propName.slice(1)}`;
|
|
371
|
+
logger.critical(ErrorCode.INLINE_SCHEMA, `Inline schemas are not supported for '${propName}' in '${name}'.\n` +
|
|
372
|
+
` Extract to an exported variable:\n` +
|
|
373
|
+
` export const ${schemaName} = ${prop.initializer.getText()}\n` +
|
|
374
|
+
` Then use: ${propName}: ${schemaName}`);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
if (version !== undefined) {
|
|
381
|
+
const baseName = explicitName || exportedName || pikkuFuncId;
|
|
382
|
+
pikkuFuncId = formatVersionedId(baseName, version);
|
|
254
383
|
}
|
|
384
|
+
const isMCPToolFunc = expression.text === 'pikkuMCPToolFunc';
|
|
385
|
+
const mcpEnabled = mcp || isMCPToolFunc;
|
|
255
386
|
// Pick the handler: use resolvedFunc when it exists and is a function, otherwise fall back to handlerNode
|
|
256
387
|
const handler = resolvedFunc &&
|
|
257
388
|
(ts.isArrowFunction(resolvedFunc) || ts.isFunctionExpression(resolvedFunc))
|
|
@@ -259,10 +390,11 @@ export const addFunctions = (logger, node, checker, state) => {
|
|
|
259
390
|
: handlerNode;
|
|
260
391
|
// Validate that we got a valid function
|
|
261
392
|
if (!ts.isArrowFunction(handler) && !ts.isFunctionExpression(handler)) {
|
|
262
|
-
logger.error(`• No valid 'func' property found for ${
|
|
393
|
+
logger.error(`• No valid 'func' property found for ${pikkuFuncId}.`);
|
|
263
394
|
// Create stub metadata to prevent "function not found" errors in wirings
|
|
264
|
-
state.functions.meta[
|
|
265
|
-
|
|
395
|
+
state.functions.meta[pikkuFuncId] = {
|
|
396
|
+
pikkuFuncId,
|
|
397
|
+
functionType: 'user',
|
|
266
398
|
name,
|
|
267
399
|
services: { optimized: false, services: [] },
|
|
268
400
|
inputSchemaName: null,
|
|
@@ -291,39 +423,51 @@ export const addFunctions = (logger, node, checker, state) => {
|
|
|
291
423
|
}
|
|
292
424
|
}
|
|
293
425
|
}
|
|
294
|
-
else if (ts.isIdentifier(firstParam.name)
|
|
426
|
+
else if (ts.isIdentifier(firstParam.name) &&
|
|
427
|
+
!firstParam.name.text.startsWith('_')) {
|
|
295
428
|
services.optimized = false;
|
|
296
429
|
}
|
|
297
430
|
}
|
|
298
|
-
|
|
299
|
-
const usedWires = [];
|
|
300
|
-
const thirdParam = handler.parameters[2];
|
|
301
|
-
if (thirdParam && ts.isObjectBindingPattern(thirdParam.name)) {
|
|
302
|
-
for (const elem of thirdParam.name.elements) {
|
|
303
|
-
const propertyName = elem.propertyName && ts.isIdentifier(elem.propertyName)
|
|
304
|
-
? elem.propertyName.text
|
|
305
|
-
: ts.isIdentifier(elem.name)
|
|
306
|
-
? elem.name.text
|
|
307
|
-
: undefined;
|
|
308
|
-
if (propertyName) {
|
|
309
|
-
usedWires.push(propertyName);
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
}
|
|
431
|
+
const wires = extractUsedWires(handler, 2);
|
|
313
432
|
// --- Generics → ts.Type[], unwrapped from Promise ---
|
|
314
433
|
const genericTypes = (typeArguments ?? [])
|
|
315
434
|
.map((tn) => checker.getTypeFromTypeNode(tn))
|
|
316
435
|
.map((t) => unwrapPromise(checker, t));
|
|
436
|
+
const capitalizedName = funcIdToTypeName(name);
|
|
317
437
|
// --- Input Extraction ---
|
|
318
|
-
let
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
438
|
+
let inputNames = [];
|
|
439
|
+
let inputTypes = [];
|
|
440
|
+
if (inputSchemaRef) {
|
|
441
|
+
const schemaName = `${capitalizedName}Input`;
|
|
442
|
+
inputNames = [schemaName];
|
|
443
|
+
state.schemaLookup.set(schemaName, inputSchemaRef);
|
|
444
|
+
state.functions.typesMap.addCustomType(schemaName, 'unknown', []);
|
|
445
|
+
}
|
|
446
|
+
else if (genericTypes.length >= 1 && genericTypes[0]) {
|
|
447
|
+
// Fall back to extracting from generic type arguments
|
|
448
|
+
const result = getNamesAndTypes(checker, state.functions.typesMap, 'Input', name, genericTypes[0]);
|
|
449
|
+
inputNames = result.names;
|
|
450
|
+
inputTypes = result.types;
|
|
451
|
+
}
|
|
452
|
+
else {
|
|
453
|
+
// Fall back to extracting from the function's second parameter type
|
|
454
|
+
const secondParam = handler.parameters[1];
|
|
455
|
+
if (secondParam) {
|
|
456
|
+
const paramType = checker.getTypeAtLocation(secondParam);
|
|
457
|
+
const result = getNamesAndTypes(checker, state.functions.typesMap, 'Input', pikkuFuncId, paramType);
|
|
458
|
+
inputNames = result.names;
|
|
459
|
+
inputTypes = result.types;
|
|
460
|
+
}
|
|
461
|
+
}
|
|
324
462
|
// --- Output Extraction ---
|
|
325
463
|
let outputNames = [];
|
|
326
|
-
if (
|
|
464
|
+
if (outputSchemaRef) {
|
|
465
|
+
const schemaName = `${capitalizedName}Output`;
|
|
466
|
+
outputNames = [schemaName];
|
|
467
|
+
state.schemaLookup.set(schemaName, outputSchemaRef);
|
|
468
|
+
state.functions.typesMap.addCustomType(schemaName, 'unknown', []);
|
|
469
|
+
}
|
|
470
|
+
else if (genericTypes.length >= 2) {
|
|
327
471
|
outputNames = getNamesAndTypes(checker, state.functions.typesMap, 'Output', name, genericTypes[1]).names;
|
|
328
472
|
}
|
|
329
473
|
else {
|
|
@@ -331,74 +475,198 @@ export const addFunctions = (logger, node, checker, state) => {
|
|
|
331
475
|
if (sig) {
|
|
332
476
|
const rawRet = checker.getReturnTypeOfSignature(sig);
|
|
333
477
|
const unwrapped = unwrapPromise(checker, rawRet);
|
|
334
|
-
outputNames = getNamesAndTypes(checker, state.functions.typesMap, 'Output',
|
|
478
|
+
outputNames = getNamesAndTypes(checker, state.functions.typesMap, 'Output', pikkuFuncId, unwrapped).names;
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
const mcpOutputTypes = {
|
|
482
|
+
pikkuMCPResourceFunc: 'MCPResourceResponse',
|
|
483
|
+
pikkuMCPToolFunc: 'MCPToolResponse',
|
|
484
|
+
pikkuMCPPromptFunc: 'MCPPromptResponse',
|
|
485
|
+
};
|
|
486
|
+
const mcpOutputType = mcpOutputTypes[expression.text];
|
|
487
|
+
if (mcpOutputType && outputNames[0] !== mcpOutputType) {
|
|
488
|
+
let resolved = false;
|
|
489
|
+
const rawSymbol = checker.getSymbolAtLocation(expression);
|
|
490
|
+
const funcSymbol = rawSymbol && rawSymbol.flags & ts.SymbolFlags.Alias
|
|
491
|
+
? checker.getAliasedSymbol(rawSymbol)
|
|
492
|
+
: rawSymbol;
|
|
493
|
+
const funcDecls = funcSymbol?.getDeclarations() || [];
|
|
494
|
+
for (const funcDecl of funcDecls) {
|
|
495
|
+
if (resolved)
|
|
496
|
+
break;
|
|
497
|
+
const mcpTypeSymbol = checker.resolveName(mcpOutputType, funcDecl, ts.SymbolFlags.Type, false);
|
|
498
|
+
if (mcpTypeSymbol) {
|
|
499
|
+
const aliased = mcpTypeSymbol.flags & ts.SymbolFlags.Alias
|
|
500
|
+
? checker.getAliasedSymbol(mcpTypeSymbol)
|
|
501
|
+
: mcpTypeSymbol;
|
|
502
|
+
const typeDecl = aliased?.getDeclarations()?.[0];
|
|
503
|
+
if (typeDecl) {
|
|
504
|
+
const path = typeDecl.getSourceFile().fileName;
|
|
505
|
+
if (!state.functions.typesMap.exists(mcpOutputType, path)) {
|
|
506
|
+
state.functions.typesMap.addType(mcpOutputType, path);
|
|
507
|
+
}
|
|
508
|
+
resolved = true;
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
if (!resolved) {
|
|
513
|
+
state.functions.typesMap.addCustomType(mcpOutputType, mcpOutputType, []);
|
|
335
514
|
}
|
|
515
|
+
outputNames = [mcpOutputType];
|
|
336
516
|
}
|
|
337
517
|
if (inputNames.length > 1) {
|
|
338
518
|
logger.warn('More than one input type detected, only the first one will be used as a schema.');
|
|
339
519
|
}
|
|
340
520
|
// Store the input type for later use
|
|
341
521
|
if (inputTypes.length > 0) {
|
|
342
|
-
state.typesLookup.set(
|
|
522
|
+
state.typesLookup.set(pikkuFuncId, inputTypes);
|
|
343
523
|
}
|
|
344
524
|
// --- resolve middleware ---
|
|
345
|
-
|
|
525
|
+
let middleware = objectNode
|
|
346
526
|
? resolveMiddleware(state, objectNode, tags, checker)
|
|
347
527
|
: undefined;
|
|
348
|
-
|
|
349
|
-
|
|
528
|
+
// --- resolve permissions ---
|
|
529
|
+
let permissions = objectNode
|
|
530
|
+
? resolvePermissions(state, objectNode, tags, checker)
|
|
531
|
+
: undefined;
|
|
532
|
+
if (options.tags?.length) {
|
|
533
|
+
tags = [...new Set([...(tags || []), ...options.tags])];
|
|
534
|
+
const tagEntries = options.tags.map((tag) => ({
|
|
535
|
+
type: 'tag',
|
|
536
|
+
tag,
|
|
537
|
+
}));
|
|
538
|
+
const existingMiddlewareTags = new Set((middleware || [])
|
|
539
|
+
.filter((m) => m.type === 'tag')
|
|
540
|
+
.map((m) => m.tag));
|
|
541
|
+
const newMiddleware = tagEntries.filter((e) => !existingMiddlewareTags.has(e.tag));
|
|
542
|
+
if (newMiddleware.length > 0) {
|
|
543
|
+
middleware = [...(middleware || []), ...newMiddleware];
|
|
544
|
+
}
|
|
545
|
+
const existingPermissionTags = new Set((permissions || [])
|
|
546
|
+
.filter((p) => p.type === 'tag')
|
|
547
|
+
.map((p) => p.tag));
|
|
548
|
+
const newPermissions = tagEntries.filter((e) => !existingPermissionTags.has(e.tag));
|
|
549
|
+
if (newPermissions.length > 0) {
|
|
550
|
+
permissions = [...(permissions || []), ...newPermissions];
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
const sessionless = expression.text !== 'pikkuFunc';
|
|
554
|
+
state.functions.meta[pikkuFuncId] = {
|
|
555
|
+
pikkuFuncId,
|
|
556
|
+
functionType: 'user',
|
|
557
|
+
funcWrapper: expression.text,
|
|
558
|
+
sessionless,
|
|
350
559
|
name,
|
|
351
560
|
services,
|
|
352
|
-
|
|
561
|
+
wires: wires.wires.length > 0 || !wires.optimized ? wires : undefined,
|
|
353
562
|
inputSchemaName: inputNames[0] ?? null,
|
|
354
563
|
outputSchemaName: outputNames[0] ?? null,
|
|
355
564
|
inputs: inputNames.filter((n) => n !== 'void') ?? null,
|
|
356
565
|
outputs: outputNames.filter((n) => n !== 'void') ?? null,
|
|
357
566
|
expose: expose || undefined,
|
|
358
|
-
|
|
567
|
+
remote: remote || undefined,
|
|
568
|
+
mcp: mcpEnabled || undefined,
|
|
569
|
+
requiresApproval: requiresApproval || undefined,
|
|
570
|
+
version,
|
|
571
|
+
title,
|
|
359
572
|
tags: tags || undefined,
|
|
360
573
|
summary,
|
|
361
574
|
description,
|
|
362
575
|
errors,
|
|
363
576
|
middleware,
|
|
577
|
+
permissions,
|
|
364
578
|
isDirectFunction,
|
|
365
579
|
};
|
|
580
|
+
// Populate node metadata if node config is present
|
|
581
|
+
if (nodeDisplayName && nodeCategory && nodeType) {
|
|
582
|
+
state.nodes.files.add(node.getSourceFile().fileName);
|
|
583
|
+
state.nodes.meta[pikkuFuncId] = {
|
|
584
|
+
name: pikkuFuncId,
|
|
585
|
+
displayName: nodeDisplayName,
|
|
586
|
+
category: nodeCategory,
|
|
587
|
+
type: nodeType,
|
|
588
|
+
rpc: pikkuFuncId,
|
|
589
|
+
description,
|
|
590
|
+
errorOutput: nodeErrorOutput ?? false,
|
|
591
|
+
inputSchemaName: inputNames[0] ?? null,
|
|
592
|
+
outputSchemaName: outputNames[0] ?? null,
|
|
593
|
+
tags,
|
|
594
|
+
};
|
|
595
|
+
}
|
|
596
|
+
if (mcpEnabled) {
|
|
597
|
+
if (!description) {
|
|
598
|
+
logger.critical(ErrorCode.MISSING_DESCRIPTION, `MCP tool '${name}' is missing a description.`);
|
|
599
|
+
return;
|
|
600
|
+
}
|
|
601
|
+
state.mcpEndpoints.files.add(node.getSourceFile().fileName);
|
|
602
|
+
state.mcpEndpoints.toolsMeta[name] = {
|
|
603
|
+
pikkuFuncId,
|
|
604
|
+
name,
|
|
605
|
+
title: title || undefined,
|
|
606
|
+
description,
|
|
607
|
+
summary,
|
|
608
|
+
errors,
|
|
609
|
+
tags,
|
|
610
|
+
inputSchema: inputNames[0] ?? null,
|
|
611
|
+
outputSchema: outputNames[0] ?? null,
|
|
612
|
+
middleware,
|
|
613
|
+
permissions,
|
|
614
|
+
};
|
|
615
|
+
state.serviceAggregation.usedFunctions.add(pikkuFuncId);
|
|
616
|
+
extractWireNames(middleware).forEach((n) => state.serviceAggregation.usedMiddleware.add(n));
|
|
617
|
+
extractWireNames(permissions).forEach((n) => state.serviceAggregation.usedPermissions.add(n));
|
|
618
|
+
}
|
|
619
|
+
// Workflow functions don't get registered as RPC functions,
|
|
620
|
+
// they are their own type handled by add-workflow
|
|
621
|
+
if (expression.text.includes('Workflow')) {
|
|
622
|
+
return;
|
|
623
|
+
}
|
|
624
|
+
// Trigger and channel connect/disconnect functions are not callable via RPC
|
|
625
|
+
const nonRPCPatterns = [
|
|
626
|
+
/Trigger/i,
|
|
627
|
+
/ChannelConnection/i,
|
|
628
|
+
/ChannelDisconnection/i,
|
|
629
|
+
];
|
|
630
|
+
if (nonRPCPatterns.some((pattern) => pattern.test(expression.text))) {
|
|
631
|
+
return;
|
|
632
|
+
}
|
|
366
633
|
// Store function file location for wiring generation
|
|
367
634
|
if (exportedName) {
|
|
368
|
-
state.functions.files.set(
|
|
635
|
+
state.functions.files.set(pikkuFuncId, {
|
|
369
636
|
path: node.getSourceFile().fileName,
|
|
370
637
|
exportedName,
|
|
371
638
|
});
|
|
372
639
|
}
|
|
373
|
-
// Workflow functions don't get registered as RPC functions,
|
|
374
|
-
// they are their own type handled by add-workdflow
|
|
375
|
-
if (expression.text.includes('Workflow')) {
|
|
376
|
-
return;
|
|
377
|
-
}
|
|
378
640
|
if (exportedName || explicitName) {
|
|
379
641
|
if (!exportedName) {
|
|
380
642
|
logger.error(`• Function with explicit name '${name}' is not exported, this is not allowed.`);
|
|
381
643
|
return;
|
|
382
644
|
}
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
state.rpc.invokedFunctions.add(pikkuFuncName);
|
|
645
|
+
if (remote) {
|
|
646
|
+
state.rpc.invokedFunctions.add(pikkuFuncId);
|
|
386
647
|
}
|
|
387
648
|
if (expose) {
|
|
388
|
-
state.rpc.exposedMeta[name] =
|
|
649
|
+
state.rpc.exposedMeta[name] = pikkuFuncId;
|
|
389
650
|
state.rpc.exposedFiles.set(name, {
|
|
390
651
|
path: node.getSourceFile().fileName,
|
|
391
652
|
exportedName,
|
|
392
653
|
});
|
|
393
654
|
// Track exposed RPC function for service aggregation
|
|
394
|
-
state.serviceAggregation.usedFunctions.add(
|
|
655
|
+
state.serviceAggregation.usedFunctions.add(pikkuFuncId);
|
|
395
656
|
}
|
|
396
657
|
// We add it to internal meta to allow autocomplete for everything
|
|
397
|
-
state.rpc.internalMeta[name] =
|
|
658
|
+
state.rpc.internalMeta[name] = pikkuFuncId;
|
|
659
|
+
if (version !== undefined) {
|
|
660
|
+
state.rpc.internalMeta[pikkuFuncId] = pikkuFuncId;
|
|
661
|
+
state.rpc.invokedFunctions.add(pikkuFuncId);
|
|
662
|
+
}
|
|
398
663
|
// But we only import the actual function if it's actually invoked to keep
|
|
399
664
|
// bundle size down
|
|
400
|
-
if (state.rpc.invokedFunctions.has(
|
|
401
|
-
|
|
665
|
+
if (state.rpc.invokedFunctions.has(pikkuFuncId) ||
|
|
666
|
+
expose ||
|
|
667
|
+
remote ||
|
|
668
|
+
mcpEnabled) {
|
|
669
|
+
state.rpc.internalFiles.set(pikkuFuncId, {
|
|
402
670
|
path: node.getSourceFile().fileName,
|
|
403
671
|
exportedName,
|
|
404
672
|
});
|
|
@@ -1,15 +1,24 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as ts from 'typescript';
|
|
2
|
+
import { AddWiring, InspectorState } from '../types.js';
|
|
3
|
+
import type { InspectorLogger } from '../types.js';
|
|
2
4
|
/**
|
|
3
|
-
*
|
|
4
|
-
* query and params. Returns undefined (we only mutate metaTypes).
|
|
5
|
+
* Parameters for registering an HTTP route
|
|
5
6
|
*/
|
|
6
|
-
export
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
export interface RegisterHTTPRouteParams {
|
|
8
|
+
obj: ts.ObjectLiteralExpression;
|
|
9
|
+
state: InspectorState;
|
|
10
|
+
checker: ts.TypeChecker;
|
|
11
|
+
logger: InspectorLogger;
|
|
12
|
+
sourceFile: ts.SourceFile;
|
|
13
|
+
basePath?: string;
|
|
14
|
+
inheritedTags?: string[];
|
|
15
|
+
}
|
|
11
16
|
/**
|
|
12
|
-
*
|
|
13
|
-
*
|
|
17
|
+
* Shared function to register an HTTP route in the inspector state.
|
|
18
|
+
* Used by both wireHTTP and wireHTTPRoutes.
|
|
19
|
+
*/
|
|
20
|
+
export declare function registerHTTPRoute({ obj, state, checker, logger, sourceFile, basePath, inheritedTags, }: RegisterHTTPRouteParams): void;
|
|
21
|
+
/**
|
|
22
|
+
* Process wireHTTP calls
|
|
14
23
|
*/
|
|
15
24
|
export declare const addHTTPRoute: AddWiring;
|