@pikku/inspector 0.12.1 → 0.12.3
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 +23 -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 +44 -4
- package/dist/add/add-cli.js +94 -18
- package/dist/add/add-file-with-factory.js +1 -0
- package/dist/add/add-functions.js +22 -3
- package/dist/add/add-gateway.d.ts +2 -0
- package/dist/add/add-gateway.js +62 -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-queue-worker.js +5 -0
- package/dist/add/add-schedule.js +5 -0
- package/dist/add/add-wire-addon.js +7 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/inspector.js +11 -0
- package/dist/types.d.ts +15 -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.js +26 -0
- package/dist/utils/resolve-function-meta.d.ts +11 -0
- package/dist/utils/resolve-function-meta.js +17 -0
- package/dist/utils/serialize-inspector-state.d.ts +6 -0
- package/dist/utils/serialize-inspector-state.js +12 -0
- package/dist/utils/serialize-mcp-json.js +13 -7
- package/dist/visit.js +4 -0
- package/package.json +3 -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 +47 -11
- package/src/add/add-cli.ts +140 -30
- package/src/add/add-file-with-factory.ts +1 -0
- package/src/add/add-functions.ts +28 -3
- package/src/add/add-gateway.ts +101 -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-queue-worker.ts +6 -0
- package/src/add/add-schedule.ts +6 -0
- package/src/add/add-wire-addon.ts +8 -0
- package/src/index.ts +1 -0
- package/src/inspector.ts +16 -0
- package/src/types.ts +16 -0
- package/src/utils/load-addon-functions-meta.ts +94 -0
- package/src/utils/post-process.ts +25 -0
- package/src/utils/resolve-function-meta.ts +25 -0
- package/src/utils/serialize-inspector-state.ts +18 -0
- package/src/utils/serialize-mcp-json.ts +12 -7
- package/src/visit.ts +4 -0
- package/tsconfig.tsbuildinfo +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
## 0.12.0
|
|
2
2
|
|
|
3
|
+
## 0.12.3
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 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
|
|
8
|
+
- 387b2ee: Add approval description inspection, track packageName on wire metadata, and resolve addon package names in channel/RPC wirings
|
|
9
|
+
- Updated dependencies [387b2ee]
|
|
10
|
+
- Updated dependencies [32ed003]
|
|
11
|
+
- Updated dependencies [7d369f3]
|
|
12
|
+
- Updated dependencies [508a796]
|
|
13
|
+
- Updated dependencies [ffe83af]
|
|
14
|
+
- Updated dependencies [c7ff141]
|
|
15
|
+
- @pikku/core@0.12.3
|
|
16
|
+
|
|
17
|
+
## 0.12.2
|
|
18
|
+
|
|
19
|
+
### Patch Changes
|
|
20
|
+
|
|
21
|
+
- 3e04565: chore: update dependencies to latest minor/patch versions
|
|
22
|
+
- Updated dependencies [cc4c9e9]
|
|
23
|
+
- Updated dependencies [3e04565]
|
|
24
|
+
- @pikku/core@0.12.2
|
|
25
|
+
|
|
3
26
|
## 0.12.1
|
|
4
27
|
|
|
5
28
|
### 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
|
}
|
|
@@ -366,6 +382,7 @@ export const addChannel = (logger, node, checker, state, options) => {
|
|
|
366
382
|
if (disabled)
|
|
367
383
|
return;
|
|
368
384
|
const query = getPropertyValue(obj, 'query');
|
|
385
|
+
const binary = getPropertyValue(obj, 'binary');
|
|
369
386
|
const connect = getPropertyAssignmentInitializer(obj, 'onConnect', true, checker);
|
|
370
387
|
const disconnect = getPropertyAssignmentInitializer(obj, 'onDisconnect', true, checker);
|
|
371
388
|
// default onMessage handler
|
|
@@ -381,8 +398,12 @@ export const addChannel = (logger, node, checker, state, options) => {
|
|
|
381
398
|
logger.critical(ErrorCode.FUNCTION_METADATA_NOT_FOUND, `No function metadata found for onMessage handler '${msgFuncId}'`);
|
|
382
399
|
return;
|
|
383
400
|
}
|
|
401
|
+
const msgPackageName = ts.isIdentifier(onMsgProp)
|
|
402
|
+
? resolveAddonName(onMsgProp, checker, state.rpc.wireAddonDeclarations)
|
|
403
|
+
: null;
|
|
384
404
|
message = {
|
|
385
405
|
pikkuFuncId: msgFuncId,
|
|
406
|
+
...(msgPackageName && { packageName: msgPackageName }),
|
|
386
407
|
};
|
|
387
408
|
}
|
|
388
409
|
// nested message-routes
|
|
@@ -393,19 +414,27 @@ export const addChannel = (logger, node, checker, state, options) => {
|
|
|
393
414
|
// --- track used functions/middleware for service aggregation ---
|
|
394
415
|
// Track connect/disconnect/message handlers
|
|
395
416
|
let connectFuncId;
|
|
417
|
+
let connectPackageName = null;
|
|
396
418
|
if (connect) {
|
|
397
419
|
const extracted = extractFunctionName(connect, checker, state.rootDir);
|
|
398
420
|
connectFuncId = extracted.pikkuFuncId.startsWith('__temp_')
|
|
399
421
|
? makeContextBasedId('channel', name, 'connect')
|
|
400
422
|
: extracted.pikkuFuncId;
|
|
423
|
+
connectPackageName = ts.isIdentifier(connect)
|
|
424
|
+
? resolveAddonName(connect, checker, state.rpc.wireAddonDeclarations)
|
|
425
|
+
: null;
|
|
401
426
|
state.serviceAggregation.usedFunctions.add(connectFuncId);
|
|
402
427
|
}
|
|
403
428
|
let disconnectFuncId;
|
|
429
|
+
let disconnectPackageName = null;
|
|
404
430
|
if (disconnect) {
|
|
405
431
|
const extracted = extractFunctionName(disconnect, checker, state.rootDir);
|
|
406
432
|
disconnectFuncId = extracted.pikkuFuncId.startsWith('__temp_')
|
|
407
433
|
? makeContextBasedId('channel', name, 'disconnect')
|
|
408
434
|
: extracted.pikkuFuncId;
|
|
435
|
+
disconnectPackageName = ts.isIdentifier(disconnect)
|
|
436
|
+
? resolveAddonName(disconnect, checker, state.rpc.wireAddonDeclarations)
|
|
437
|
+
: null;
|
|
409
438
|
state.serviceAggregation.usedFunctions.add(disconnectFuncId);
|
|
410
439
|
}
|
|
411
440
|
if (message) {
|
|
@@ -435,10 +464,21 @@ export const addChannel = (logger, node, checker, state, options) => {
|
|
|
435
464
|
input: null,
|
|
436
465
|
params: params.length ? params : undefined,
|
|
437
466
|
query: query?.length ? query : undefined,
|
|
438
|
-
connect: connectFuncId
|
|
439
|
-
|
|
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,
|
|
440
479
|
message,
|
|
441
480
|
messageWirings,
|
|
481
|
+
binary: binary === undefined ? undefined : binary,
|
|
442
482
|
summary,
|
|
443
483
|
description,
|
|
444
484
|
errors,
|
package/dist/add/add-cli.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import ts from 'typescript';
|
|
2
|
-
import { extractFunctionName } from '../utils/extract-function-name.js';
|
|
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();
|
|
@@ -95,10 +97,14 @@ function processCLIConfig(logger, node, sourceFile, typeChecker, inspectorState,
|
|
|
95
97
|
programMeta.options = processOptions(logger, prop.initializer, typeChecker, inspectorState, options);
|
|
96
98
|
}
|
|
97
99
|
break;
|
|
98
|
-
case 'render':
|
|
99
|
-
|
|
100
|
-
|
|
100
|
+
case 'render': {
|
|
101
|
+
let renderFuncId = extractFunctionName(prop.initializer, typeChecker, inspectorState.rootDir).pikkuFuncId;
|
|
102
|
+
if (renderFuncId.startsWith('__temp_')) {
|
|
103
|
+
renderFuncId = makeContextBasedId('cli-render', programName);
|
|
104
|
+
}
|
|
105
|
+
programMeta.defaultRenderName = renderFuncId;
|
|
101
106
|
break;
|
|
107
|
+
}
|
|
102
108
|
}
|
|
103
109
|
}
|
|
104
110
|
return { programName, programMeta };
|
|
@@ -156,8 +162,12 @@ function processCommand(logger, inspectorState, options, name, node, sourceFile,
|
|
|
156
162
|
if (ts.isIdentifier(node) ||
|
|
157
163
|
ts.isArrowFunction(node) ||
|
|
158
164
|
ts.isFunctionExpression(node)) {
|
|
165
|
+
let pikkuFuncId = extractFunctionName(node, typeChecker, inspectorState.rootDir).pikkuFuncId;
|
|
166
|
+
if (pikkuFuncId.startsWith('__temp_')) {
|
|
167
|
+
pikkuFuncId = makeContextBasedId('cli', programName, ...fullPath);
|
|
168
|
+
}
|
|
159
169
|
return {
|
|
160
|
-
pikkuFuncId
|
|
170
|
+
pikkuFuncId,
|
|
161
171
|
positionals: [],
|
|
162
172
|
options: {},
|
|
163
173
|
};
|
|
@@ -194,8 +204,38 @@ function processCommand(logger, inspectorState, options, name, node, sourceFile,
|
|
|
194
204
|
continue;
|
|
195
205
|
const propName = prop.name.text;
|
|
196
206
|
if (propName === 'func') {
|
|
197
|
-
|
|
198
|
-
|
|
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
|
+
}
|
|
238
|
+
}
|
|
199
239
|
}
|
|
200
240
|
else if (propName === 'options' &&
|
|
201
241
|
ts.isObjectLiteralExpression(prop.initializer)) {
|
|
@@ -238,9 +278,14 @@ function processCommand(logger, inspectorState, options, name, node, sourceFile,
|
|
|
238
278
|
case 'func':
|
|
239
279
|
// Already handled in first pass
|
|
240
280
|
break;
|
|
241
|
-
case 'render':
|
|
242
|
-
|
|
281
|
+
case 'render': {
|
|
282
|
+
let renderFuncId = extractFunctionName(prop.initializer, typeChecker, inspectorState.rootDir).pikkuFuncId;
|
|
283
|
+
if (renderFuncId.startsWith('__temp_')) {
|
|
284
|
+
renderFuncId = makeContextBasedId('cli-render', programName, ...fullPath);
|
|
285
|
+
}
|
|
286
|
+
meta.renderName = renderFuncId;
|
|
243
287
|
break;
|
|
288
|
+
}
|
|
244
289
|
case 'options':
|
|
245
290
|
// Process with pikkuFuncId from first pass
|
|
246
291
|
if (optionsNode) {
|
|
@@ -341,17 +386,20 @@ function processOptions(logger, node, typeChecker, inspectorState, inspectorOpti
|
|
|
341
386
|
}
|
|
342
387
|
}
|
|
343
388
|
// Extract enum values from the function input type if available
|
|
344
|
-
// Get the input type if we have a pikkuFuncId
|
|
345
|
-
let inputTypes;
|
|
346
|
-
if (pikkuFuncId) {
|
|
347
|
-
inputTypes = inspectorState.typesLookup.get(pikkuFuncId);
|
|
348
|
-
}
|
|
349
389
|
let derivedChoices = null;
|
|
350
|
-
if (
|
|
351
|
-
|
|
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
|
+
}
|
|
352
400
|
}
|
|
353
|
-
|
|
354
|
-
|
|
401
|
+
// 3. Last resort: try Config type
|
|
402
|
+
if (!derivedChoices) {
|
|
355
403
|
derivedChoices = extractEnumFromConfigType(logger, optionName, typeChecker, inspectorState, inspectorOptions);
|
|
356
404
|
}
|
|
357
405
|
// Validate and set choices
|
|
@@ -472,6 +520,34 @@ function extractEnumFromConfigType(logger, propertyName, typeChecker, inspectorS
|
|
|
472
520
|
// Extract enum from the property
|
|
473
521
|
return extractEnumFromPropertyType(configType, propertyName, typeChecker);
|
|
474
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
|
+
}
|
|
475
551
|
/**
|
|
476
552
|
* Gets the property name from a property assignment
|
|
477
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,
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import * as ts from 'typescript';
|
|
2
|
+
import { getPropertyValue, getCommonWireMetaData, } from '../utils/get-property-value.js';
|
|
3
|
+
import { extractFunctionName, makeContextBasedId, } from '../utils/extract-function-name.js';
|
|
4
|
+
import { getPropertyAssignmentInitializer } from '../utils/type-utils.js';
|
|
5
|
+
import { resolveMiddleware } from '../utils/middleware.js';
|
|
6
|
+
import { extractWireNames } from '../utils/post-process.js';
|
|
7
|
+
import { resolveAddonName } from '../utils/resolve-addon-package.js';
|
|
8
|
+
import { ErrorCode } from '../error-codes.js';
|
|
9
|
+
export const addGateway = (logger, node, checker, state, _options) => {
|
|
10
|
+
if (!ts.isCallExpression(node)) {
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
const args = node.arguments;
|
|
14
|
+
const firstArg = args[0];
|
|
15
|
+
const expression = node.expression;
|
|
16
|
+
if (!ts.isIdentifier(expression) || expression.text !== 'wireGateway') {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
if (!firstArg || !ts.isObjectLiteralExpression(firstArg)) {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
const obj = firstArg;
|
|
23
|
+
const nameValue = getPropertyValue(obj, 'name');
|
|
24
|
+
const typeValue = getPropertyValue(obj, 'type');
|
|
25
|
+
const routeValue = getPropertyValue(obj, 'route');
|
|
26
|
+
const { disabled, tags, summary, description, errors } = getCommonWireMetaData(obj, 'Gateway', nameValue, logger);
|
|
27
|
+
if (disabled)
|
|
28
|
+
return;
|
|
29
|
+
const funcInitializer = getPropertyAssignmentInitializer(obj, 'func', true, checker);
|
|
30
|
+
if (!funcInitializer) {
|
|
31
|
+
logger.critical(ErrorCode.MISSING_FUNC, `No valid 'func' property for gateway '${nameValue}'.`);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
const extracted = extractFunctionName(funcInitializer, checker, state.rootDir);
|
|
35
|
+
let pikkuFuncId = extracted.pikkuFuncId;
|
|
36
|
+
if (pikkuFuncId.startsWith('__temp_') && nameValue) {
|
|
37
|
+
pikkuFuncId = makeContextBasedId('gateway', nameValue);
|
|
38
|
+
}
|
|
39
|
+
const packageName = ts.isIdentifier(funcInitializer)
|
|
40
|
+
? resolveAddonName(funcInitializer, checker, state.rpc.wireAddonDeclarations)
|
|
41
|
+
: null;
|
|
42
|
+
if (!nameValue || !typeValue) {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
const middleware = resolveMiddleware(state, obj, tags, checker);
|
|
46
|
+
state.serviceAggregation.usedFunctions.add(pikkuFuncId);
|
|
47
|
+
extractWireNames(middleware).forEach((name) => state.serviceAggregation.usedMiddleware.add(name));
|
|
48
|
+
state.gateways.files.add(node.getSourceFile().fileName);
|
|
49
|
+
state.gateways.meta[nameValue] = {
|
|
50
|
+
pikkuFuncId,
|
|
51
|
+
...(packageName && { packageName }),
|
|
52
|
+
name: nameValue,
|
|
53
|
+
type: typeValue,
|
|
54
|
+
route: routeValue,
|
|
55
|
+
gateway: true,
|
|
56
|
+
summary,
|
|
57
|
+
description,
|
|
58
|
+
errors,
|
|
59
|
+
tags,
|
|
60
|
+
middleware,
|
|
61
|
+
};
|
|
62
|
+
};
|
|
@@ -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,
|
|
@@ -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,
|
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,
|