@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/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,51 @@
|
|
|
1
1
|
## 0.12.0
|
|
2
2
|
|
|
3
|
+
## 0.12.4
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 5866b66: Add critical error (PKU490) when Zod schemas and wiring calls (wireHTTPRoutes, addPermission, addHTTPMiddleware) coexist in the same file. The CLI uses tsImport to extract Zod schemas at runtime, which executes all top-level code — wiring side-effects crash in this context because pikku state metadata doesn't exist. Schemas and wirings must be in separate files.
|
|
8
|
+
- e412b4d: Optimize CLI codegen performance: 12x faster `pikku all`
|
|
9
|
+
|
|
10
|
+
- Reuse schemas across re-inspections (skip redundant `ts-json-schema-generator` runs)
|
|
11
|
+
- Cache TS schemas to disk (`.pikku/schema-cache.json`) for cross-run reuse
|
|
12
|
+
- Pass `oldProgram` to `ts.createProgram` for incremental TS compilation
|
|
13
|
+
- Cache parsed tsconfig in schema generator between runs
|
|
14
|
+
- Auto-include direct `addPermission`/`addHTTPMiddleware` in bootstrap via side-effect imports
|
|
15
|
+
- Skip `pikkuAuth()` errors when nested inside `addPermission`/`addHTTPPermission`
|
|
16
|
+
|
|
17
|
+
- Updated dependencies [e412b4d]
|
|
18
|
+
- Updated dependencies [53dc8c8]
|
|
19
|
+
- Updated dependencies [0a1cc51]
|
|
20
|
+
- Updated dependencies [0a1cc51]
|
|
21
|
+
- Updated dependencies [0a1cc51]
|
|
22
|
+
- Updated dependencies [0a1cc51]
|
|
23
|
+
- Updated dependencies [0a1cc51]
|
|
24
|
+
- Updated dependencies [0a1cc51]
|
|
25
|
+
- Updated dependencies [0a1cc51]
|
|
26
|
+
- Updated dependencies [0a1cc51]
|
|
27
|
+
- Updated dependencies [0a1cc51]
|
|
28
|
+
- Updated dependencies [8b9b2e9]
|
|
29
|
+
- Updated dependencies [8b9b2e9]
|
|
30
|
+
- Updated dependencies [b973d44]
|
|
31
|
+
- Updated dependencies [8b9b2e9]
|
|
32
|
+
- Updated dependencies [8b9b2e9]
|
|
33
|
+
- @pikku/core@0.12.9
|
|
34
|
+
|
|
35
|
+
## 0.12.3
|
|
36
|
+
|
|
37
|
+
### Patch Changes
|
|
38
|
+
|
|
39
|
+
- 508a796: Fix MCP server not exposing addon tools: resolve namespaced function IDs in MCP runner, load addon schemas after schema generation, and use resolveFunctionMeta for MCP JSON serialization
|
|
40
|
+
- 387b2ee: Add approval description inspection, track packageName on wire metadata, and resolve addon package names in channel/RPC wirings
|
|
41
|
+
- Updated dependencies [387b2ee]
|
|
42
|
+
- Updated dependencies [32ed003]
|
|
43
|
+
- Updated dependencies [7d369f3]
|
|
44
|
+
- Updated dependencies [508a796]
|
|
45
|
+
- Updated dependencies [ffe83af]
|
|
46
|
+
- Updated dependencies [c7ff141]
|
|
47
|
+
- @pikku/core@0.12.3
|
|
48
|
+
|
|
3
49
|
## 0.12.2
|
|
4
50
|
|
|
5
51
|
### Patch Changes
|
package/dist/add/add-ai-agent.js
CHANGED
|
@@ -167,6 +167,7 @@ export const addAIAgent = (logger, node, checker, state, options) => {
|
|
|
167
167
|
const maxStepsValue = getPropertyValue(obj, 'maxSteps');
|
|
168
168
|
const temperatureValue = getPropertyValue(obj, 'temperature');
|
|
169
169
|
const toolChoiceValue = getPropertyValue(obj, 'toolChoice');
|
|
170
|
+
const dynamicWorkflowsValue = getPropertyValue(obj, 'dynamicWorkflows');
|
|
170
171
|
const toolsValue = resolveToolReferences(obj, checker, nameValue || '', logger);
|
|
171
172
|
if (toolsValue) {
|
|
172
173
|
for (const toolName of toolsValue) {
|
|
@@ -301,6 +302,9 @@ export const addAIAgent = (logger, node, checker, state, options) => {
|
|
|
301
302
|
}),
|
|
302
303
|
...(toolsValue !== null && { tools: toolsValue }),
|
|
303
304
|
...(agentsValue !== null && { agents: agentsValue }),
|
|
305
|
+
...(dynamicWorkflowsValue !== null && {
|
|
306
|
+
dynamicWorkflows: dynamicWorkflowsValue,
|
|
307
|
+
}),
|
|
304
308
|
tags,
|
|
305
309
|
inputSchema,
|
|
306
310
|
outputSchema,
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import * as ts from 'typescript';
|
|
2
|
+
import { extractFunctionName } from '../utils/extract-function-name.js';
|
|
3
|
+
import { extractServicesFromFunction, extractUsedWires, } from '../utils/extract-services.js';
|
|
4
|
+
/**
|
|
5
|
+
* Inspect pikkuApprovalDescription() calls and extract metadata
|
|
6
|
+
*/
|
|
7
|
+
export const addApprovalDescription = (logger, node, checker, state) => {
|
|
8
|
+
if (!ts.isCallExpression(node))
|
|
9
|
+
return;
|
|
10
|
+
const { expression, arguments: args } = node;
|
|
11
|
+
if (!ts.isIdentifier(expression))
|
|
12
|
+
return;
|
|
13
|
+
if (expression.text !== 'pikkuApprovalDescription')
|
|
14
|
+
return;
|
|
15
|
+
const arg = args[0];
|
|
16
|
+
if (!arg)
|
|
17
|
+
return;
|
|
18
|
+
let actualHandler;
|
|
19
|
+
if (ts.isArrowFunction(arg) || ts.isFunctionExpression(arg)) {
|
|
20
|
+
actualHandler = arg;
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
logger.error(`• Handler for pikkuApprovalDescription is not a function.`);
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
const services = extractServicesFromFunction(actualHandler);
|
|
27
|
+
const wires = extractUsedWires(actualHandler, 1);
|
|
28
|
+
let { pikkuFuncId, exportedName } = extractFunctionName(node, checker, state.rootDir);
|
|
29
|
+
if (pikkuFuncId.startsWith('__temp_')) {
|
|
30
|
+
if (ts.isVariableDeclaration(node.parent) &&
|
|
31
|
+
ts.isIdentifier(node.parent.name)) {
|
|
32
|
+
pikkuFuncId = node.parent.name.text;
|
|
33
|
+
}
|
|
34
|
+
else if (ts.isPropertyAssignment(node.parent) &&
|
|
35
|
+
ts.isIdentifier(node.parent.name)) {
|
|
36
|
+
pikkuFuncId = node.parent.name.text;
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
logger.error(`• pikkuApprovalDescription() must be assigned to a variable or object property. ` +
|
|
40
|
+
`Extract it to a const: const myApproval = pikkuApprovalDescription(...)`);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
state.functions.approvalDescriptions[pikkuFuncId] = {
|
|
45
|
+
services,
|
|
46
|
+
wires: wires.wires.length > 0 || !wires.optimized ? wires : undefined,
|
|
47
|
+
sourceFile: node.getSourceFile().fileName,
|
|
48
|
+
position: node.getStart(),
|
|
49
|
+
exportedName,
|
|
50
|
+
};
|
|
51
|
+
logger.debug(`• Found approval description '${pikkuFuncId}' with services: ${services.services.join(', ')}`);
|
|
52
|
+
};
|
package/dist/add/add-channel.js
CHANGED
|
@@ -7,6 +7,8 @@ import { getPropertyAssignmentInitializer } from '../utils/type-utils.js';
|
|
|
7
7
|
import { resolveMiddleware, resolveChannelMiddleware, } from '../utils/middleware.js';
|
|
8
8
|
import { extractWireNames } from '../utils/post-process.js';
|
|
9
9
|
import { resolveIdentifier } from '../utils/resolve-identifier.js';
|
|
10
|
+
import { resolveFunctionMeta } from '../utils/resolve-function-meta.js';
|
|
11
|
+
import { resolveAddonName } from '../utils/resolve-addon-package.js';
|
|
10
12
|
import { validateAuthSessionless } from '../utils/validate-auth-sessionless.js';
|
|
11
13
|
/**
|
|
12
14
|
* Safely get the "initializer" expression of a property-like AST node:
|
|
@@ -63,6 +65,13 @@ function getHandlerNameFromExpression(expr, checker, rootDir) {
|
|
|
63
65
|
}
|
|
64
66
|
// Handle call expressions
|
|
65
67
|
if (ts.isCallExpression(expr)) {
|
|
68
|
+
// Handle addon('namespace:funcName') calls
|
|
69
|
+
if (ts.isIdentifier(expr.expression) && expr.expression.text === 'addon') {
|
|
70
|
+
const [firstArg] = expr.arguments;
|
|
71
|
+
if (firstArg && ts.isStringLiteral(firstArg)) {
|
|
72
|
+
return firstArg.text;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
66
75
|
const { pikkuFuncId } = extractFunctionName(expr, checker, rootDir);
|
|
67
76
|
return pikkuFuncId;
|
|
68
77
|
}
|
|
@@ -315,9 +324,9 @@ export function addMessagesRoutes(logger, obj, state, checker) {
|
|
|
315
324
|
logger.error(`Could not resolve handler for message route '${routeKey}'`);
|
|
316
325
|
continue;
|
|
317
326
|
}
|
|
318
|
-
const fnMeta = state
|
|
327
|
+
const fnMeta = resolveFunctionMeta(state, handlerName);
|
|
319
328
|
if (!fnMeta) {
|
|
320
|
-
logger.critical(ErrorCode.FUNCTION_METADATA_NOT_FOUND, `No function metadata found for handler '${handlerName}'
|
|
329
|
+
logger.critical(ErrorCode.FUNCTION_METADATA_NOT_FOUND, `No function metadata found for channel handler '${handlerName}' on route '${routeKey}'. If this is an inline function, it must be exported for the inspector to discover it.`);
|
|
321
330
|
continue;
|
|
322
331
|
}
|
|
323
332
|
// Resolve middleware and permissions for this route
|
|
@@ -328,8 +337,15 @@ export function addMessagesRoutes(logger, obj, state, checker) {
|
|
|
328
337
|
const routeMiddleware = ts.isObjectLiteralExpression(init)
|
|
329
338
|
? resolveMiddleware(state, init, routeTags, checker)
|
|
330
339
|
: undefined;
|
|
340
|
+
// Resolve package name for addon functions (e.g. 'swaggerPetstore:addPet')
|
|
341
|
+
const colonIdx = handlerName.indexOf(':');
|
|
342
|
+
const addonNs = colonIdx !== -1 ? handlerName.substring(0, colonIdx) : null;
|
|
343
|
+
const packageName = addonNs
|
|
344
|
+
? state.rpc.wireAddonDeclarations.get(addonNs)?.package
|
|
345
|
+
: undefined;
|
|
331
346
|
result[channelKey][routeKey] = {
|
|
332
347
|
pikkuFuncId: handlerName,
|
|
348
|
+
packageName,
|
|
333
349
|
middleware: routeMiddleware,
|
|
334
350
|
};
|
|
335
351
|
}
|
|
@@ -382,8 +398,12 @@ export const addChannel = (logger, node, checker, state, options) => {
|
|
|
382
398
|
logger.critical(ErrorCode.FUNCTION_METADATA_NOT_FOUND, `No function metadata found for onMessage handler '${msgFuncId}'`);
|
|
383
399
|
return;
|
|
384
400
|
}
|
|
401
|
+
const msgPackageName = ts.isIdentifier(onMsgProp)
|
|
402
|
+
? resolveAddonName(onMsgProp, checker, state.rpc.wireAddonDeclarations)
|
|
403
|
+
: null;
|
|
385
404
|
message = {
|
|
386
405
|
pikkuFuncId: msgFuncId,
|
|
406
|
+
...(msgPackageName && { packageName: msgPackageName }),
|
|
387
407
|
};
|
|
388
408
|
}
|
|
389
409
|
// nested message-routes
|
|
@@ -394,19 +414,27 @@ export const addChannel = (logger, node, checker, state, options) => {
|
|
|
394
414
|
// --- track used functions/middleware for service aggregation ---
|
|
395
415
|
// Track connect/disconnect/message handlers
|
|
396
416
|
let connectFuncId;
|
|
417
|
+
let connectPackageName = null;
|
|
397
418
|
if (connect) {
|
|
398
419
|
const extracted = extractFunctionName(connect, checker, state.rootDir);
|
|
399
420
|
connectFuncId = extracted.pikkuFuncId.startsWith('__temp_')
|
|
400
421
|
? makeContextBasedId('channel', name, 'connect')
|
|
401
422
|
: extracted.pikkuFuncId;
|
|
423
|
+
connectPackageName = ts.isIdentifier(connect)
|
|
424
|
+
? resolveAddonName(connect, checker, state.rpc.wireAddonDeclarations)
|
|
425
|
+
: null;
|
|
402
426
|
state.serviceAggregation.usedFunctions.add(connectFuncId);
|
|
403
427
|
}
|
|
404
428
|
let disconnectFuncId;
|
|
429
|
+
let disconnectPackageName = null;
|
|
405
430
|
if (disconnect) {
|
|
406
431
|
const extracted = extractFunctionName(disconnect, checker, state.rootDir);
|
|
407
432
|
disconnectFuncId = extracted.pikkuFuncId.startsWith('__temp_')
|
|
408
433
|
? makeContextBasedId('channel', name, 'disconnect')
|
|
409
434
|
: extracted.pikkuFuncId;
|
|
435
|
+
disconnectPackageName = ts.isIdentifier(disconnect)
|
|
436
|
+
? resolveAddonName(disconnect, checker, state.rpc.wireAddonDeclarations)
|
|
437
|
+
: null;
|
|
410
438
|
state.serviceAggregation.usedFunctions.add(disconnectFuncId);
|
|
411
439
|
}
|
|
412
440
|
if (message) {
|
|
@@ -436,8 +464,18 @@ export const addChannel = (logger, node, checker, state, options) => {
|
|
|
436
464
|
input: null,
|
|
437
465
|
params: params.length ? params : undefined,
|
|
438
466
|
query: query?.length ? query : undefined,
|
|
439
|
-
connect: connectFuncId
|
|
440
|
-
|
|
467
|
+
connect: connectFuncId
|
|
468
|
+
? {
|
|
469
|
+
pikkuFuncId: connectFuncId,
|
|
470
|
+
...(connectPackageName && { packageName: connectPackageName }),
|
|
471
|
+
}
|
|
472
|
+
: null,
|
|
473
|
+
disconnect: disconnectFuncId
|
|
474
|
+
? {
|
|
475
|
+
pikkuFuncId: disconnectFuncId,
|
|
476
|
+
...(disconnectPackageName && { packageName: disconnectPackageName }),
|
|
477
|
+
}
|
|
478
|
+
: null,
|
|
441
479
|
message,
|
|
442
480
|
messageWirings,
|
|
443
481
|
binary: binary === undefined ? undefined : binary,
|
package/dist/add/add-cli.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import ts from 'typescript';
|
|
2
2
|
import { extractFunctionName, makeContextBasedId, } from '../utils/extract-function-name.js';
|
|
3
3
|
import { resolveMiddleware } from '../utils/middleware.js';
|
|
4
|
+
import { resolveFunctionMeta } from '../utils/resolve-function-meta.js';
|
|
4
5
|
import { extractWireNames } from '../utils/post-process.js';
|
|
5
6
|
import { getPropertyValue } from '../utils/get-property-value.js';
|
|
6
7
|
import { resolveIdentifier } from '../utils/resolve-identifier.js';
|
|
8
|
+
import { resolveAddonName } from '../utils/resolve-addon-package.js';
|
|
7
9
|
import { validateAuthSessionless } from '../utils/validate-auth-sessionless.js';
|
|
8
10
|
// Track if we've warned about missing Config type to avoid duplicate warnings
|
|
9
11
|
const configTypeWarningShown = new Set();
|
|
@@ -202,11 +204,38 @@ function processCommand(logger, inspectorState, options, name, node, sourceFile,
|
|
|
202
204
|
continue;
|
|
203
205
|
const propName = prop.name.text;
|
|
204
206
|
if (propName === 'func') {
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
207
|
+
if (ts.isCallExpression(prop.initializer) &&
|
|
208
|
+
ts.isIdentifier(prop.initializer.expression) &&
|
|
209
|
+
prop.initializer.expression.text === 'addon') {
|
|
210
|
+
const [firstArg] = prop.initializer.arguments;
|
|
211
|
+
if (!firstArg || !ts.isStringLiteral(firstArg)) {
|
|
212
|
+
throw new Error(`addon() call requires a string literal argument in the form "namespace:funcName"`);
|
|
213
|
+
}
|
|
214
|
+
pikkuFuncId = firstArg.text;
|
|
215
|
+
const addonNamespace = pikkuFuncId.split(':')[0];
|
|
216
|
+
if (!addonNamespace || !pikkuFuncId.includes(':')) {
|
|
217
|
+
throw new Error(`Malformed addon function ID "${pikkuFuncId}": expected "namespace:funcName" format`);
|
|
218
|
+
}
|
|
219
|
+
if (!inspectorState.rpc.wireAddonDeclarations.has(addonNamespace)) {
|
|
220
|
+
throw new Error(`Unknown addon namespace "${addonNamespace}" in "${pikkuFuncId}": no matching wireAddonDeclarations entry found`);
|
|
221
|
+
}
|
|
222
|
+
meta.pikkuFuncId = pikkuFuncId;
|
|
223
|
+
meta.packageName =
|
|
224
|
+
inspectorState.rpc.wireAddonDeclarations.get(addonNamespace).package;
|
|
225
|
+
}
|
|
226
|
+
else {
|
|
227
|
+
pikkuFuncId = extractFunctionName(prop.initializer, typeChecker, inspectorState.rootDir).pikkuFuncId;
|
|
228
|
+
if (pikkuFuncId.startsWith('__temp_')) {
|
|
229
|
+
pikkuFuncId = makeContextBasedId('cli', programName, ...fullPath);
|
|
230
|
+
}
|
|
231
|
+
meta.pikkuFuncId = pikkuFuncId;
|
|
232
|
+
const cliPackageName = ts.isIdentifier(prop.initializer)
|
|
233
|
+
? resolveAddonName(prop.initializer, typeChecker, inspectorState.rpc.wireAddonDeclarations)
|
|
234
|
+
: null;
|
|
235
|
+
if (cliPackageName) {
|
|
236
|
+
meta.packageName = cliPackageName;
|
|
237
|
+
}
|
|
208
238
|
}
|
|
209
|
-
meta.pikkuFuncId = pikkuFuncId;
|
|
210
239
|
}
|
|
211
240
|
else if (propName === 'options' &&
|
|
212
241
|
ts.isObjectLiteralExpression(prop.initializer)) {
|
|
@@ -357,17 +386,20 @@ function processOptions(logger, node, typeChecker, inspectorState, inspectorOpti
|
|
|
357
386
|
}
|
|
358
387
|
}
|
|
359
388
|
// Extract enum values from the function input type if available
|
|
360
|
-
// Get the input type if we have a pikkuFuncId
|
|
361
|
-
let inputTypes;
|
|
362
|
-
if (pikkuFuncId) {
|
|
363
|
-
inputTypes = inspectorState.typesLookup.get(pikkuFuncId);
|
|
364
|
-
}
|
|
365
389
|
let derivedChoices = null;
|
|
366
|
-
if (
|
|
367
|
-
|
|
390
|
+
if (pikkuFuncId) {
|
|
391
|
+
// 1. Try TypeScript types first (most precise — handles unions, TS enums)
|
|
392
|
+
const inputTypes = inspectorState.typesLookup.get(pikkuFuncId);
|
|
393
|
+
if (inputTypes && inputTypes.length > 0) {
|
|
394
|
+
derivedChoices = extractEnumFromPropertyType(inputTypes[0], optionName, typeChecker);
|
|
395
|
+
}
|
|
396
|
+
// 2. Fallback: try JSON schema (works for addon functions)
|
|
397
|
+
if (!derivedChoices) {
|
|
398
|
+
derivedChoices = extractEnumFromJsonSchema(inspectorState, pikkuFuncId, optionName);
|
|
399
|
+
}
|
|
368
400
|
}
|
|
369
|
-
|
|
370
|
-
|
|
401
|
+
// 3. Last resort: try Config type
|
|
402
|
+
if (!derivedChoices) {
|
|
371
403
|
derivedChoices = extractEnumFromConfigType(logger, optionName, typeChecker, inspectorState, inspectorOptions);
|
|
372
404
|
}
|
|
373
405
|
// Validate and set choices
|
|
@@ -488,6 +520,34 @@ function extractEnumFromConfigType(logger, propertyName, typeChecker, inspectorS
|
|
|
488
520
|
// Extract enum from the property
|
|
489
521
|
return extractEnumFromPropertyType(configType, propertyName, typeChecker);
|
|
490
522
|
}
|
|
523
|
+
/**
|
|
524
|
+
* Extracts enum values from the function's JSON schema.
|
|
525
|
+
* Works for addon functions whose schemas are generated from OpenAPI/Zod.
|
|
526
|
+
*/
|
|
527
|
+
function extractEnumFromJsonSchema(inspectorState, pikkuFuncId, propertyName) {
|
|
528
|
+
const fnMeta = resolveFunctionMeta(inspectorState, pikkuFuncId);
|
|
529
|
+
if (!fnMeta?.inputSchemaName)
|
|
530
|
+
return null;
|
|
531
|
+
const schema = inspectorState.schemas[fnMeta.inputSchemaName];
|
|
532
|
+
if (!schema?.properties?.[propertyName])
|
|
533
|
+
return null;
|
|
534
|
+
const prop = schema.properties[propertyName];
|
|
535
|
+
// Direct enum on property
|
|
536
|
+
if (prop.enum && Array.isArray(prop.enum)) {
|
|
537
|
+
const strings = prop.enum.filter((v) => typeof v === 'string');
|
|
538
|
+
if (strings.length > 0)
|
|
539
|
+
return strings;
|
|
540
|
+
}
|
|
541
|
+
// Array with enum items (e.g. z.array(z.enum([...])))
|
|
542
|
+
if (prop.type === 'array' &&
|
|
543
|
+
prop.items?.enum &&
|
|
544
|
+
Array.isArray(prop.items.enum)) {
|
|
545
|
+
const strings = prop.items.enum.filter((v) => typeof v === 'string');
|
|
546
|
+
if (strings.length > 0)
|
|
547
|
+
return strings;
|
|
548
|
+
}
|
|
549
|
+
return null;
|
|
550
|
+
}
|
|
491
551
|
/**
|
|
492
552
|
* Gets the property name from a property assignment
|
|
493
553
|
*/
|
|
@@ -7,6 +7,7 @@ const wrapperFunctionMap = {
|
|
|
7
7
|
pikkuServices: 'CreateSingletonServices',
|
|
8
8
|
pikkuAddonServices: 'CreateSingletonServices',
|
|
9
9
|
pikkuWireServices: 'CreateWireServices',
|
|
10
|
+
pikkuAddonWireServices: 'CreateWireServices',
|
|
10
11
|
};
|
|
11
12
|
export const addFileWithFactory = (node, checker, methods = new Map(), expectedTypeName, state) => {
|
|
12
13
|
if (ts.isVariableDeclaration(node)) {
|
|
@@ -245,7 +245,8 @@ export const addFunctions = (logger, node, checker, state, options) => {
|
|
|
245
245
|
let remote;
|
|
246
246
|
let mcp;
|
|
247
247
|
let readonly_;
|
|
248
|
-
let
|
|
248
|
+
let approvalRequired;
|
|
249
|
+
let approvalDescription;
|
|
249
250
|
let version;
|
|
250
251
|
let objectNode;
|
|
251
252
|
let nodeDisplayName = null;
|
|
@@ -311,7 +312,24 @@ export const addFunctions = (logger, node, checker, state, options) => {
|
|
|
311
312
|
remote = getPropertyValue(firstArg, 'remote');
|
|
312
313
|
mcp = getPropertyValue(firstArg, 'mcp');
|
|
313
314
|
readonly_ = getPropertyValue(firstArg, 'readonly');
|
|
314
|
-
|
|
315
|
+
approvalRequired = getPropertyValue(firstArg, 'approvalRequired');
|
|
316
|
+
// Extract approvalDescription identifier reference
|
|
317
|
+
for (const prop of firstArg.properties) {
|
|
318
|
+
if (ts.isPropertyAssignment(prop) &&
|
|
319
|
+
ts.isIdentifier(prop.name) &&
|
|
320
|
+
prop.name.text === 'approvalDescription' &&
|
|
321
|
+
ts.isIdentifier(prop.initializer)) {
|
|
322
|
+
const { pikkuFuncId: descId } = extractFunctionName(prop.initializer, checker, state.rootDir);
|
|
323
|
+
if (descId && !descId.startsWith('__temp_')) {
|
|
324
|
+
approvalDescription = descId;
|
|
325
|
+
}
|
|
326
|
+
else {
|
|
327
|
+
// Try resolving the identifier directly
|
|
328
|
+
approvalDescription = prop.initializer.text;
|
|
329
|
+
}
|
|
330
|
+
break;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
315
333
|
const versionRaw = getPropertyValue(firstArg, 'version');
|
|
316
334
|
if (versionRaw !== null && versionRaw !== undefined) {
|
|
317
335
|
const parsed = Number(versionRaw);
|
|
@@ -565,7 +583,8 @@ export const addFunctions = (logger, node, checker, state, options) => {
|
|
|
565
583
|
remote: remote || undefined,
|
|
566
584
|
mcp: mcpEnabled || undefined,
|
|
567
585
|
readonly: readonly_ || undefined,
|
|
568
|
-
|
|
586
|
+
approvalRequired: approvalRequired || undefined,
|
|
587
|
+
approvalDescription: approvalDescription || undefined,
|
|
569
588
|
version,
|
|
570
589
|
title,
|
|
571
590
|
tags: tags || undefined,
|
package/dist/add/add-gateway.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 addGateway = (logger, node, checker, state, _options) => {
|
|
9
10
|
if (!ts.isCallExpression(node)) {
|
|
@@ -35,6 +36,9 @@ export const addGateway = (logger, node, checker, state, _options) => {
|
|
|
35
36
|
if (pikkuFuncId.startsWith('__temp_') && nameValue) {
|
|
36
37
|
pikkuFuncId = makeContextBasedId('gateway', nameValue);
|
|
37
38
|
}
|
|
39
|
+
const packageName = ts.isIdentifier(funcInitializer)
|
|
40
|
+
? resolveAddonName(funcInitializer, checker, state.rpc.wireAddonDeclarations)
|
|
41
|
+
: null;
|
|
38
42
|
if (!nameValue || !typeValue) {
|
|
39
43
|
return;
|
|
40
44
|
}
|
|
@@ -44,6 +48,7 @@ export const addGateway = (logger, node, checker, state, _options) => {
|
|
|
44
48
|
state.gateways.files.add(node.getSourceFile().fileName);
|
|
45
49
|
state.gateways.meta[nameValue] = {
|
|
46
50
|
pikkuFuncId,
|
|
51
|
+
...(packageName && { packageName }),
|
|
47
52
|
name: nameValue,
|
|
48
53
|
type: typeValue,
|
|
49
54
|
route: routeValue,
|
|
@@ -10,6 +10,7 @@ import { ensureFunctionMetadata } from '../utils/ensure-function-metadata.js';
|
|
|
10
10
|
import { ErrorCode } from '../error-codes.js';
|
|
11
11
|
import { validateAuthSessionless } from '../utils/validate-auth-sessionless.js';
|
|
12
12
|
import { detectSchemaVendorOrError } from '../utils/detect-schema-vendor.js';
|
|
13
|
+
import { resolveAddonName } from '../utils/resolve-addon-package.js';
|
|
13
14
|
/**
|
|
14
15
|
* Extract header schema reference from headers property
|
|
15
16
|
*/
|
|
@@ -112,6 +113,9 @@ export function registerHTTPRoute({ obj, state, checker, logger, sourceFile, bas
|
|
|
112
113
|
if (funcName.startsWith('__temp_')) {
|
|
113
114
|
funcName = makeContextBasedId('http', method, fullRoute);
|
|
114
115
|
}
|
|
116
|
+
const packageName = ts.isIdentifier(funcInitializer)
|
|
117
|
+
? resolveAddonName(funcInitializer, checker, state.rpc.wireAddonDeclarations)
|
|
118
|
+
: null;
|
|
115
119
|
ensureFunctionMetadata(state, funcName, fullRoute, funcInitializer, checker, extracted.isHelper);
|
|
116
120
|
// Lookup existing function metadata
|
|
117
121
|
const fnMeta = state.functions.meta[funcName];
|
|
@@ -168,6 +172,7 @@ export function registerHTTPRoute({ obj, state, checker, logger, sourceFile, bas
|
|
|
168
172
|
state.http.files.add(sourceFile.fileName);
|
|
169
173
|
state.http.meta[method][fullRoute] = {
|
|
170
174
|
pikkuFuncId: funcName,
|
|
175
|
+
...(packageName && { packageName }),
|
|
171
176
|
route: fullRoute,
|
|
172
177
|
method: method,
|
|
173
178
|
params: params.length > 0 ? params : undefined,
|
|
@@ -6,6 +6,7 @@ import { extractFunctionName, makeContextBasedId, } from '../utils/extract-funct
|
|
|
6
6
|
import { getPropertyAssignmentInitializer } from '../utils/type-utils.js';
|
|
7
7
|
import { resolveMiddleware } from '../utils/middleware.js';
|
|
8
8
|
import { resolvePermissions } from '../utils/permissions.js';
|
|
9
|
+
import { resolveAddonName } from '../utils/resolve-addon-package.js';
|
|
9
10
|
import { ErrorCode } from '../error-codes.js';
|
|
10
11
|
export const addMCPPrompt = (logger, node, checker, state, options) => {
|
|
11
12
|
if (!ts.isCallExpression(node)) {
|
|
@@ -37,6 +38,9 @@ export const addMCPPrompt = (logger, node, checker, state, options) => {
|
|
|
37
38
|
if (pikkuFuncId.startsWith('__temp_') && nameValue) {
|
|
38
39
|
pikkuFuncId = makeContextBasedId('mcp', 'prompt', nameValue);
|
|
39
40
|
}
|
|
41
|
+
const packageName = ts.isIdentifier(funcInitializer)
|
|
42
|
+
? resolveAddonName(funcInitializer, checker, state.rpc.wireAddonDeclarations)
|
|
43
|
+
: null;
|
|
40
44
|
ensureFunctionMetadata(state, pikkuFuncId, nameValue || undefined, funcInitializer, checker, extracted.isHelper);
|
|
41
45
|
if (!nameValue) {
|
|
42
46
|
logger.critical(ErrorCode.MISSING_NAME, "MCP prompt is missing the required 'name' property.");
|
|
@@ -65,6 +69,7 @@ export const addMCPPrompt = (logger, node, checker, state, options) => {
|
|
|
65
69
|
state.mcpEndpoints.files.add(node.getSourceFile().fileName);
|
|
66
70
|
state.mcpEndpoints.promptsMeta[nameValue] = {
|
|
67
71
|
pikkuFuncId,
|
|
72
|
+
...(packageName && { packageName }),
|
|
68
73
|
name: nameValue,
|
|
69
74
|
description,
|
|
70
75
|
summary,
|
|
@@ -6,6 +6,7 @@ import { extractFunctionName, makeContextBasedId, } from '../utils/extract-funct
|
|
|
6
6
|
import { getPropertyAssignmentInitializer } from '../utils/type-utils.js';
|
|
7
7
|
import { resolveMiddleware } from '../utils/middleware.js';
|
|
8
8
|
import { resolvePermissions } from '../utils/permissions.js';
|
|
9
|
+
import { resolveAddonName } from '../utils/resolve-addon-package.js';
|
|
9
10
|
import { ErrorCode } from '../error-codes.js';
|
|
10
11
|
export const addMCPResource = (logger, node, checker, state, options) => {
|
|
11
12
|
if (!ts.isCallExpression(node)) {
|
|
@@ -42,6 +43,9 @@ export const addMCPResource = (logger, node, checker, state, options) => {
|
|
|
42
43
|
if (pikkuFuncId.startsWith('__temp_') && uriValue) {
|
|
43
44
|
pikkuFuncId = makeContextBasedId('mcp', 'resource', uriValue);
|
|
44
45
|
}
|
|
46
|
+
const packageName = ts.isIdentifier(funcInitializer)
|
|
47
|
+
? resolveAddonName(funcInitializer, checker, state.rpc.wireAddonDeclarations)
|
|
48
|
+
: null;
|
|
45
49
|
ensureFunctionMetadata(state, pikkuFuncId, uriValue || undefined, funcInitializer, checker, extracted.isHelper);
|
|
46
50
|
if (!uriValue) {
|
|
47
51
|
logger.critical(ErrorCode.MISSING_URI, "MCP resource is missing the required 'uri' property.");
|
|
@@ -74,6 +78,7 @@ export const addMCPResource = (logger, node, checker, state, options) => {
|
|
|
74
78
|
state.mcpEndpoints.files.add(node.getSourceFile().fileName);
|
|
75
79
|
state.mcpEndpoints.resourcesMeta[uriValue] = {
|
|
76
80
|
pikkuFuncId,
|
|
81
|
+
...(packageName && { packageName }),
|
|
77
82
|
uri: uriValue,
|
|
78
83
|
title: titleValue,
|
|
79
84
|
description,
|
|
@@ -189,12 +189,10 @@ export const addMiddleware = (logger, node, checker, state) => {
|
|
|
189
189
|
return;
|
|
190
190
|
}
|
|
191
191
|
const refs = extractMiddlewareRefs(middlewareArrayArg, checker, state.rootDir);
|
|
192
|
-
if (refs.length === 0) {
|
|
193
|
-
logger.warn(`• addMiddleware('${tag}', ...) has empty middleware array`);
|
|
194
|
-
return;
|
|
195
|
-
}
|
|
196
192
|
const definitionIds = refs.map((r) => r.definitionId);
|
|
197
|
-
|
|
193
|
+
if (definitionIds.length > 0) {
|
|
194
|
+
renameTempDefinitions(state, definitionIds, 'tag', tag);
|
|
195
|
+
}
|
|
198
196
|
const sourceFile = node.getSourceFile().fileName;
|
|
199
197
|
const instanceIds = [];
|
|
200
198
|
for (let i = 0; i < refs.length; i++) {
|
|
@@ -273,12 +271,10 @@ export const addMiddleware = (logger, node, checker, state) => {
|
|
|
273
271
|
return;
|
|
274
272
|
}
|
|
275
273
|
const refs = extractMiddlewareRefs(middlewareArrayArg, checker, state.rootDir);
|
|
276
|
-
if (refs.length === 0) {
|
|
277
|
-
logger.warn(`• addHTTPMiddleware('${pattern}', ...) has empty middleware array`);
|
|
278
|
-
return;
|
|
279
|
-
}
|
|
280
274
|
const definitionIds = refs.map((r) => r.definitionId);
|
|
281
|
-
|
|
275
|
+
if (definitionIds.length > 0) {
|
|
276
|
+
renameTempDefinitions(state, definitionIds, 'http', pattern);
|
|
277
|
+
}
|
|
282
278
|
const sourceFile = node.getSourceFile().fileName;
|
|
283
279
|
const instanceIds = [];
|
|
284
280
|
for (let i = 0; i < refs.length; i++) {
|
|
@@ -21,12 +21,14 @@ function renameTempDefinitions(state, definitionIds, groupType, groupKey) {
|
|
|
21
21
|
definitionIds[idx] = newId;
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
|
-
function
|
|
24
|
+
function isInsidePermissionContainer(node) {
|
|
25
25
|
let current = node.parent;
|
|
26
26
|
while (current) {
|
|
27
27
|
if (ts.isCallExpression(current) &&
|
|
28
28
|
ts.isIdentifier(current.expression) &&
|
|
29
|
-
current.expression.text === 'pikkuPermissionFactory'
|
|
29
|
+
(current.expression.text === 'pikkuPermissionFactory' ||
|
|
30
|
+
current.expression.text === 'addPermission' ||
|
|
31
|
+
current.expression.text === 'addHTTPPermission')) {
|
|
30
32
|
return true;
|
|
31
33
|
}
|
|
32
34
|
current = current.parent;
|
|
@@ -47,7 +49,7 @@ export const addPermission = (logger, node, checker, state) => {
|
|
|
47
49
|
// Handle pikkuPermission(...) - individual permission function definition
|
|
48
50
|
if (expression.text === 'pikkuPermission') {
|
|
49
51
|
// Skip if nested inside pikkuPermissionFactory — the factory handler extracts services itself
|
|
50
|
-
if (
|
|
52
|
+
if (isInsidePermissionContainer(node))
|
|
51
53
|
return;
|
|
52
54
|
const arg = args[0];
|
|
53
55
|
if (!arg)
|
|
@@ -110,7 +112,7 @@ export const addPermission = (logger, node, checker, state) => {
|
|
|
110
112
|
return;
|
|
111
113
|
}
|
|
112
114
|
if (expression.text === 'pikkuAuth') {
|
|
113
|
-
if (
|
|
115
|
+
if (isInsidePermissionContainer(node))
|
|
114
116
|
return;
|
|
115
117
|
const arg = args[0];
|
|
116
118
|
if (!arg)
|
|
@@ -269,11 +271,9 @@ export const addPermission = (logger, node, checker, state) => {
|
|
|
269
271
|
}
|
|
270
272
|
// Extract permission pikkuFuncIds from array
|
|
271
273
|
const permissionNames = extractPermissionPikkuNames(permissionsArrayArg, checker, state.rootDir);
|
|
272
|
-
if (permissionNames.length
|
|
273
|
-
|
|
274
|
-
return;
|
|
274
|
+
if (permissionNames.length > 0) {
|
|
275
|
+
renameTempDefinitions(state, permissionNames, 'tag', tag);
|
|
275
276
|
}
|
|
276
|
-
renameTempDefinitions(state, permissionNames, 'tag', tag);
|
|
277
277
|
const allServices = new Set();
|
|
278
278
|
for (const permissionName of permissionNames) {
|
|
279
279
|
const permissionMeta = state.permissions.definitions[permissionName];
|
|
@@ -348,11 +348,9 @@ export const addPermission = (logger, node, checker, state) => {
|
|
|
348
348
|
}
|
|
349
349
|
// Extract permission pikkuFuncIds from array
|
|
350
350
|
const permissionNames = extractPermissionPikkuNames(permissionsArrayArg, checker, state.rootDir);
|
|
351
|
-
if (permissionNames.length
|
|
352
|
-
|
|
353
|
-
return;
|
|
351
|
+
if (permissionNames.length > 0) {
|
|
352
|
+
renameTempDefinitions(state, permissionNames, 'http', pattern);
|
|
354
353
|
}
|
|
355
|
-
renameTempDefinitions(state, permissionNames, 'http', pattern);
|
|
356
354
|
const allServices = new Set();
|
|
357
355
|
for (const permissionName of permissionNames) {
|
|
358
356
|
const permissionMeta = state.permissions.definitions[permissionName];
|
|
@@ -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 addQueueWorker = (logger, node, checker, state) => {
|
|
9
10
|
if (!ts.isCallExpression(node)) {
|
|
@@ -36,6 +37,9 @@ export const addQueueWorker = (logger, node, checker, state) => {
|
|
|
36
37
|
if (pikkuFuncId.startsWith('__temp_') && name) {
|
|
37
38
|
pikkuFuncId = makeContextBasedId('queue', name);
|
|
38
39
|
}
|
|
40
|
+
const packageName = ts.isIdentifier(funcInitializer)
|
|
41
|
+
? resolveAddonName(funcInitializer, checker, state.rpc.wireAddonDeclarations)
|
|
42
|
+
: null;
|
|
39
43
|
if (!name) {
|
|
40
44
|
logger.critical(ErrorCode.MISSING_QUEUE_NAME, `No 'name' provided for queue processor function '${pikkuFuncId}'.`);
|
|
41
45
|
return;
|
|
@@ -48,6 +52,7 @@ export const addQueueWorker = (logger, node, checker, state) => {
|
|
|
48
52
|
state.queueWorkers.files.add(node.getSourceFile().fileName);
|
|
49
53
|
state.queueWorkers.meta[name] = {
|
|
50
54
|
pikkuFuncId,
|
|
55
|
+
...(packageName && { packageName }),
|
|
51
56
|
name,
|
|
52
57
|
summary,
|
|
53
58
|
description,
|