@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.
Files changed (53) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/dist/add/add-ai-agent.js +4 -0
  3. package/dist/add/add-approval-description.d.ts +5 -0
  4. package/dist/add/add-approval-description.js +52 -0
  5. package/dist/add/add-channel.js +44 -4
  6. package/dist/add/add-cli.js +94 -18
  7. package/dist/add/add-file-with-factory.js +1 -0
  8. package/dist/add/add-functions.js +22 -3
  9. package/dist/add/add-gateway.d.ts +2 -0
  10. package/dist/add/add-gateway.js +62 -0
  11. package/dist/add/add-http-route.js +5 -0
  12. package/dist/add/add-mcp-prompt.js +5 -0
  13. package/dist/add/add-mcp-resource.js +5 -0
  14. package/dist/add/add-queue-worker.js +5 -0
  15. package/dist/add/add-schedule.js +5 -0
  16. package/dist/add/add-wire-addon.js +7 -0
  17. package/dist/index.d.ts +1 -0
  18. package/dist/index.js +1 -0
  19. package/dist/inspector.js +11 -0
  20. package/dist/types.d.ts +15 -0
  21. package/dist/utils/load-addon-functions-meta.d.ts +12 -0
  22. package/dist/utils/load-addon-functions-meta.js +76 -0
  23. package/dist/utils/post-process.js +26 -0
  24. package/dist/utils/resolve-function-meta.d.ts +11 -0
  25. package/dist/utils/resolve-function-meta.js +17 -0
  26. package/dist/utils/serialize-inspector-state.d.ts +6 -0
  27. package/dist/utils/serialize-inspector-state.js +12 -0
  28. package/dist/utils/serialize-mcp-json.js +13 -7
  29. package/dist/visit.js +4 -0
  30. package/package.json +3 -3
  31. package/src/add/add-ai-agent.ts +6 -0
  32. package/src/add/add-approval-description.ts +76 -0
  33. package/src/add/add-channel.ts +47 -11
  34. package/src/add/add-cli.ts +140 -30
  35. package/src/add/add-file-with-factory.ts +1 -0
  36. package/src/add/add-functions.ts +28 -3
  37. package/src/add/add-gateway.ts +101 -0
  38. package/src/add/add-http-route.ts +6 -0
  39. package/src/add/add-mcp-prompt.ts +6 -0
  40. package/src/add/add-mcp-resource.ts +6 -0
  41. package/src/add/add-queue-worker.ts +6 -0
  42. package/src/add/add-schedule.ts +6 -0
  43. package/src/add/add-wire-addon.ts +8 -0
  44. package/src/index.ts +1 -0
  45. package/src/inspector.ts +16 -0
  46. package/src/types.ts +16 -0
  47. package/src/utils/load-addon-functions-meta.ts +94 -0
  48. package/src/utils/post-process.ts +25 -0
  49. package/src/utils/resolve-function-meta.ts +25 -0
  50. package/src/utils/serialize-inspector-state.ts +18 -0
  51. package/src/utils/serialize-mcp-json.ts +12 -7
  52. package/src/visit.ts +4 -0
  53. package/tsconfig.tsbuildinfo +1 -1
@@ -32,6 +32,7 @@ export function addWireAddon(node, state, logger) {
32
32
  let name;
33
33
  let pkg;
34
34
  let rpcEndpoint;
35
+ let mcp;
35
36
  let secretOverrides;
36
37
  let variableOverrides;
37
38
  for (const prop of firstArg.properties) {
@@ -47,6 +48,11 @@ export function addWireAddon(node, state, logger) {
47
48
  else if (key === 'rpcEndpoint' && ts.isStringLiteral(prop.initializer)) {
48
49
  rpcEndpoint = prop.initializer.text;
49
50
  }
51
+ else if (key === 'mcp' &&
52
+ (prop.initializer.kind === ts.SyntaxKind.TrueKeyword ||
53
+ prop.initializer.kind === ts.SyntaxKind.FalseKeyword)) {
54
+ mcp = prop.initializer.kind === ts.SyntaxKind.TrueKeyword;
55
+ }
50
56
  else if (key === 'secretOverrides' &&
51
57
  ts.isObjectLiteralExpression(prop.initializer)) {
52
58
  secretOverrides = parseStringRecord(prop.initializer);
@@ -62,6 +68,7 @@ export function addWireAddon(node, state, logger) {
62
68
  state.rpc.wireAddonDeclarations.set(name, {
63
69
  package: pkg,
64
70
  rpcEndpoint,
71
+ mcp,
65
72
  secretOverrides,
66
73
  variableOverrides,
67
74
  });
package/dist/index.d.ts CHANGED
@@ -13,4 +13,5 @@ export { serializeMCPJson } from './utils/serialize-mcp-json.js';
13
13
  export type { OpenAPISpecInfo } from './utils/serialize-openapi-json.js';
14
14
  export { deserializeDslWorkflow, deserializeGraphWorkflow, deserializeAllDslWorkflows, } from './utils/workflow/dsl/index.js';
15
15
  export { getFilesAndMethods } from './utils/get-files-and-methods.js';
16
+ export { resolveFunctionMeta } from './utils/resolve-function-meta.js';
16
17
  export type { SerializedWorkflowGraph, SerializedWorkflowGraphs, } from './utils/workflow/graph/index.js';
package/dist/index.js CHANGED
@@ -7,3 +7,4 @@ export { createEmptyManifest, serializeManifest, } from './utils/contract-hashes
7
7
  export { serializeMCPJson } from './utils/serialize-mcp-json.js';
8
8
  export { deserializeDslWorkflow, deserializeGraphWorkflow, deserializeAllDslWorkflows, } from './utils/workflow/dsl/index.js';
9
9
  export { getFilesAndMethods } from './utils/get-files-and-methods.js';
10
+ export { resolveFunctionMeta } from './utils/resolve-function-meta.js';
package/dist/inspector.js CHANGED
@@ -11,6 +11,7 @@ import { resolveLatestVersions } from './utils/resolve-versions.js';
11
11
  import { finalizeWorkflows } from './utils/workflow/graph/finalize-workflows.js';
12
12
  import { finalizeWorkflowHelperTypes, finalizeWorkflowWires, } from './utils/workflow/graph/finalize-workflow-wires.js';
13
13
  import { generateAllSchemas } from './utils/schema-generator.js';
14
+ import { loadAddonFunctionsMeta, loadAddonSchemas, } from './utils/load-addon-functions-meta.js';
14
15
  import { computeContractHashes, extractContractsFromMeta, updateManifest, createEmptyManifest, validateContracts, } from './utils/contract-hashes.js';
15
16
  /**
16
17
  * Creates an initial/empty inspector state with all required properties initialized
@@ -37,6 +38,7 @@ export function getInitialInspectorState(rootDir) {
37
38
  typesMap: new TypesMap(),
38
39
  meta: {},
39
40
  files: new Map(),
41
+ approvalDescriptions: {},
40
42
  },
41
43
  http: {
42
44
  metaInputTypes: new Map(),
@@ -57,6 +59,10 @@ export function getInitialInspectorState(rootDir) {
57
59
  files: new Set(),
58
60
  meta: {},
59
61
  },
62
+ gateways: {
63
+ meta: {},
64
+ files: new Set(),
65
+ },
60
66
  triggers: {
61
67
  meta: {},
62
68
  sourceMeta: {},
@@ -167,6 +173,7 @@ export function getInitialInspectorState(rootDir) {
167
173
  requiredSchemas: new Set(),
168
174
  openAPISpec: null,
169
175
  diagnostics: [],
176
+ addonFunctions: {},
170
177
  };
171
178
  }
172
179
  export const inspect = async (logger, routeFiles, options = {}) => {
@@ -201,6 +208,8 @@ export const inspect = async (logger, routeFiles, options = {}) => {
201
208
  ts.forEachChild(sourceFile, (child) => visitSetup(logger, checker, child, state, options));
202
209
  }
203
210
  logger.debug(`Visit setup phase completed in ${(performance.now() - startSetup).toFixed(2)}ms`);
211
+ // Load addon function metadata so wirings can reference addon functions
212
+ await loadAddonFunctionsMeta(logger, state);
204
213
  if (!options.setupOnly) {
205
214
  // Second sweep: add all transports
206
215
  const startRoutes = performance.now();
@@ -214,6 +223,8 @@ export const inspect = async (logger, routeFiles, options = {}) => {
214
223
  computeContractHashes(state.schemas, state.functions.typesMap, state.functions.meta);
215
224
  computeRequiredSchemas(state, options);
216
225
  }
226
+ // Re-load addon schemas (generateAllSchemas replaces state.schemas)
227
+ await loadAddonSchemas(logger, state);
217
228
  state.manifest.initial = options.manifest ?? null;
218
229
  const contracts = extractContractsFromMeta(state.functions.meta);
219
230
  const baseManifest = state.manifest.initial ?? createEmptyManifest();
package/dist/types.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import type * as ts from 'typescript';
2
2
  import type { ChannelsMeta } from '@pikku/core/channel';
3
+ import type { GatewaysMeta } from '@pikku/core/gateway';
3
4
  import type { HTTPWiringsMeta } from '@pikku/core/http';
4
5
  import type { ScheduledTasksMeta } from '@pikku/core/scheduler';
5
6
  import type { TriggerMeta, TriggerSourceMeta } from '@pikku/core/trigger';
@@ -72,6 +73,7 @@ export interface InspectorFunctionState {
72
73
  path: string;
73
74
  exportedName: string;
74
75
  }>;
76
+ approvalDescriptions: Record<string, InspectorApprovalDescriptionDefinition>;
75
77
  }
76
78
  export interface InspectorChannelState {
77
79
  meta: ChannelsMeta;
@@ -107,6 +109,13 @@ export interface InspectorChannelMiddlewareState {
107
109
  export interface InspectorAIMiddlewareState {
108
110
  definitions: Record<string, InspectorMiddlewareDefinition>;
109
111
  }
112
+ export interface InspectorApprovalDescriptionDefinition {
113
+ services: FunctionServicesMeta;
114
+ wires?: FunctionWiresMeta;
115
+ sourceFile: string;
116
+ position: number;
117
+ exportedName: string | null;
118
+ }
110
119
  export interface InspectorPermissionDefinition {
111
120
  services: FunctionServicesMeta;
112
121
  wires?: FunctionWiresMeta;
@@ -262,6 +271,10 @@ export interface InspectorState {
262
271
  http: InspectorHTTPState;
263
272
  functions: InspectorFunctionState;
264
273
  channels: InspectorChannelState;
274
+ gateways: {
275
+ meta: GatewaysMeta;
276
+ files: Set<string>;
277
+ };
265
278
  triggers: {
266
279
  meta: TriggerMeta;
267
280
  sourceMeta: TriggerSourceMeta;
@@ -304,6 +317,7 @@ export interface InspectorState {
304
317
  wireAddonDeclarations: Map<string, {
305
318
  package: string;
306
319
  rpcEndpoint?: string;
320
+ mcp?: boolean;
307
321
  secretOverrides?: Record<string, string>;
308
322
  variableOverrides?: Record<string, string>;
309
323
  }>;
@@ -378,4 +392,5 @@ export interface InspectorState {
378
392
  requiredSchemas: Set<string>;
379
393
  openAPISpec: Record<string, any> | null;
380
394
  diagnostics: InspectorDiagnostic[];
395
+ addonFunctions: Record<string, FunctionsMeta>;
381
396
  }
@@ -0,0 +1,12 @@
1
+ import type { InspectorState, InspectorLogger } from '../types.js';
2
+ /**
3
+ * After the setup sweep discovers wireAddon() declarations, load each addon
4
+ * package's function metadata so that wiring handlers (channels, HTTP routes,
5
+ * schedules, etc.) can look up addon function types during the routes sweep.
6
+ */
7
+ export declare function loadAddonFunctionsMeta(logger: InspectorLogger, state: InspectorState): Promise<void>;
8
+ /**
9
+ * Load addon schemas into state.schemas. Called after generateAllSchemas
10
+ * to ensure addon schemas aren't overwritten.
11
+ */
12
+ export declare function loadAddonSchemas(logger: InspectorLogger, state: InspectorState): Promise<void>;
@@ -0,0 +1,76 @@
1
+ import { readFile, readdir } from 'fs/promises';
2
+ import { createRequire } from 'module';
3
+ import { join, dirname } from 'path';
4
+ /**
5
+ * After the setup sweep discovers wireAddon() declarations, load each addon
6
+ * package's function metadata so that wiring handlers (channels, HTTP routes,
7
+ * schedules, etc.) can look up addon function types during the routes sweep.
8
+ */
9
+ export async function loadAddonFunctionsMeta(logger, state) {
10
+ const { wireAddonDeclarations } = state.rpc;
11
+ if (wireAddonDeclarations.size === 0)
12
+ return;
13
+ const require = createRequire(join(state.rootDir, 'package.json'));
14
+ for (const [namespace, decl] of wireAddonDeclarations) {
15
+ try {
16
+ const metaPath = require.resolve(`${decl.package}/.pikku/function/pikku-functions-meta.gen.json`);
17
+ const raw = await readFile(metaPath, 'utf-8');
18
+ const meta = JSON.parse(raw);
19
+ state.addonFunctions[namespace] = meta;
20
+ logger.debug(`Loaded ${Object.keys(meta).length} addon functions for '${namespace}' from ${decl.package}`);
21
+ // If wireAddon has mcp: true, expose addon functions with mcp: true as MCP tools
22
+ if (decl.mcp) {
23
+ for (const [funcName, funcMeta] of Object.entries(meta)) {
24
+ if (funcMeta.mcp) {
25
+ const toolName = `${namespace}:${funcName}`;
26
+ state.mcpEndpoints.toolsMeta[toolName] = {
27
+ pikkuFuncId: `${namespace}:${funcName}`,
28
+ name: toolName,
29
+ description: funcMeta.description || funcMeta.title || funcName,
30
+ inputSchema: funcMeta.inputSchemaName ?? null,
31
+ outputSchema: funcMeta.outputSchemaName ?? null,
32
+ tags: funcMeta.tags,
33
+ };
34
+ }
35
+ }
36
+ }
37
+ }
38
+ catch (error) {
39
+ logger.warn(`Failed to load addon function metadata for '${namespace}' (${decl.package}): ${error.message}`);
40
+ }
41
+ }
42
+ }
43
+ /**
44
+ * Load addon schemas into state.schemas. Called after generateAllSchemas
45
+ * to ensure addon schemas aren't overwritten.
46
+ */
47
+ export async function loadAddonSchemas(logger, state) {
48
+ const { wireAddonDeclarations } = state.rpc;
49
+ if (wireAddonDeclarations.size === 0)
50
+ return;
51
+ const require = createRequire(join(state.rootDir, 'package.json'));
52
+ for (const [namespace, decl] of wireAddonDeclarations) {
53
+ try {
54
+ const metaPath = require.resolve(`${decl.package}/.pikku/function/pikku-functions-meta.gen.json`);
55
+ const schemasDir = join(dirname(metaPath), '..', 'schemas', 'schemas');
56
+ try {
57
+ const schemaFiles = await readdir(schemasDir);
58
+ for (const file of schemaFiles) {
59
+ if (!file.endsWith('.schema.json'))
60
+ continue;
61
+ const schemaName = file.replace('.schema.json', '');
62
+ if (!state.schemas[schemaName]) {
63
+ const schemaRaw = await readFile(join(schemasDir, file), 'utf-8');
64
+ state.schemas[schemaName] = JSON.parse(schemaRaw);
65
+ }
66
+ }
67
+ }
68
+ catch {
69
+ // No schemas directory — that's fine
70
+ }
71
+ }
72
+ catch (error) {
73
+ logger.warn(`Failed to load addon schemas for '${namespace}' (${decl.package}): ${error.message}`);
74
+ }
75
+ }
76
+ }
@@ -217,6 +217,32 @@ export function computeResolvedIOTypes(state) {
217
217
  }
218
218
  }
219
219
  state.resolvedIOTypes[pikkuFuncId] = { inputType, outputType };
220
+ if (meta.inputSchemaName && inputType !== 'null') {
221
+ meta.inputSchemaName = inputType;
222
+ }
223
+ if (meta.outputSchemaName && outputType !== 'null') {
224
+ meta.outputSchemaName = outputType;
225
+ }
226
+ if (meta.inputs) {
227
+ meta.inputs = meta.inputs.map((name) => {
228
+ try {
229
+ return functions.typesMap.getTypeMeta(name).uniqueName;
230
+ }
231
+ catch {
232
+ return name;
233
+ }
234
+ });
235
+ }
236
+ if (meta.outputs) {
237
+ meta.outputs = meta.outputs.map((name) => {
238
+ try {
239
+ return functions.typesMap.getTypeMeta(name).uniqueName;
240
+ }
241
+ catch {
242
+ return name;
243
+ }
244
+ });
245
+ }
220
246
  }
221
247
  }
222
248
  const serializeGroupMap = (groupMap) => {
@@ -0,0 +1,11 @@
1
+ import type { FunctionMeta, FunctionsMeta } from '@pikku/core';
2
+ /**
3
+ * Look up function metadata by pikkuFuncId, checking both local functions
4
+ * and addon functions. Addon functions use namespaced IDs like 'namespace:funcName'.
5
+ */
6
+ export declare function resolveFunctionMeta(state: {
7
+ functions: {
8
+ meta: FunctionsMeta;
9
+ };
10
+ addonFunctions: Record<string, FunctionsMeta>;
11
+ }, pikkuFuncId: string): FunctionMeta | undefined;
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Look up function metadata by pikkuFuncId, checking both local functions
3
+ * and addon functions. Addon functions use namespaced IDs like 'namespace:funcName'.
4
+ */
5
+ export function resolveFunctionMeta(state, pikkuFuncId) {
6
+ // Check local functions first
7
+ const local = state.functions.meta[pikkuFuncId];
8
+ if (local)
9
+ return local;
10
+ // Check addon functions (namespaced like 'swaggerPetstore:addPet')
11
+ const colonIndex = pikkuFuncId.indexOf(':');
12
+ if (colonIndex === -1)
13
+ return undefined;
14
+ const namespace = pikkuFuncId.substring(0, colonIndex);
15
+ const funcName = pikkuFuncId.substring(colonIndex + 1);
16
+ return state.addonFunctions[namespace]?.[funcName];
17
+ }
@@ -100,6 +100,7 @@ export interface SerializableInspectorState {
100
100
  path: string;
101
101
  exportedName: string;
102
102
  }]>;
103
+ approvalDescriptions: InspectorState['functions']['approvalDescriptions'];
103
104
  };
104
105
  http: {
105
106
  metaInputTypes: Array<[
@@ -125,6 +126,10 @@ export interface SerializableInspectorState {
125
126
  files: string[];
126
127
  meta: InspectorState['channels']['meta'];
127
128
  };
129
+ gateways: {
130
+ meta: InspectorState['gateways']['meta'];
131
+ files: string[];
132
+ };
128
133
  triggers: {
129
134
  meta: InspectorState['triggers']['meta'];
130
135
  sourceMeta: InspectorState['triggers']['sourceMeta'];
@@ -249,6 +254,7 @@ export interface SerializableInspectorState {
249
254
  requiredSchemas: string[];
250
255
  openAPISpec: Record<string, any> | null;
251
256
  diagnostics: InspectorDiagnostic[];
257
+ addonFunctions: InspectorState['addonFunctions'];
252
258
  }
253
259
  /**
254
260
  * Serializes InspectorState to a JSON-compatible format
@@ -32,6 +32,7 @@ export function serializeInspectorState(state) {
32
32
  typesMap: serializeTypesMap(state.functions.typesMap),
33
33
  meta: state.functions.meta,
34
34
  files: Array.from(state.functions.files.entries()),
35
+ approvalDescriptions: state.functions.approvalDescriptions,
35
36
  },
36
37
  http: {
37
38
  metaInputTypes: Array.from(state.http.metaInputTypes.entries()),
@@ -44,6 +45,10 @@ export function serializeInspectorState(state) {
44
45
  files: Array.from(state.channels.files),
45
46
  meta: state.channels.meta,
46
47
  },
48
+ gateways: {
49
+ meta: state.gateways.meta,
50
+ files: Array.from(state.gateways.files),
51
+ },
47
52
  triggers: {
48
53
  meta: state.triggers.meta,
49
54
  sourceMeta: state.triggers.sourceMeta,
@@ -133,6 +138,7 @@ export function serializeInspectorState(state) {
133
138
  requiredSchemas: Array.from(state.requiredSchemas),
134
139
  openAPISpec: state.openAPISpec,
135
140
  diagnostics: state.diagnostics,
141
+ addonFunctions: state.addonFunctions,
136
142
  };
137
143
  }
138
144
  /**
@@ -169,6 +175,7 @@ export function deserializeInspectorState(data) {
169
175
  typesMap: deserializeTypesMap(data.functions.typesMap),
170
176
  meta: data.functions.meta,
171
177
  files: new Map(data.functions.files),
178
+ approvalDescriptions: data.functions.approvalDescriptions || {},
172
179
  },
173
180
  http: {
174
181
  metaInputTypes: new Map(data.http.metaInputTypes),
@@ -181,6 +188,10 @@ export function deserializeInspectorState(data) {
181
188
  files: new Set(data.channels.files),
182
189
  meta: data.channels.meta,
183
190
  },
191
+ gateways: {
192
+ meta: data.gateways?.meta ?? {},
193
+ files: new Set(data.gateways?.files ?? []),
194
+ },
184
195
  triggers: {
185
196
  meta: data.triggers?.meta ?? {},
186
197
  sourceMeta: data.triggers?.sourceMeta ?? {},
@@ -280,5 +291,6 @@ export function deserializeInspectorState(data) {
280
291
  requiredSchemas: new Set(data.requiredSchemas || []),
281
292
  openAPISpec: data.openAPISpec || null,
282
293
  diagnostics: data.diagnostics || [],
294
+ addonFunctions: data.addonFunctions || {},
283
295
  };
284
296
  }
@@ -1,6 +1,7 @@
1
+ import { resolveFunctionMeta } from './resolve-function-meta.js';
1
2
  export const serializeMCPJson = (logger, state) => {
2
3
  const { mcpEndpoints, functions, schemas } = state;
3
- const { meta: functionsMeta, typesMap } = functions;
4
+ const { typesMap } = functions;
4
5
  const { resourcesMeta, toolsMeta, promptsMeta } = mcpEndpoints;
5
6
  const tools = [];
6
7
  const resources = [];
@@ -19,9 +20,14 @@ export const serializeMCPJson = (logger, state) => {
19
20
  ].includes(typeName)) {
20
21
  return undefined;
21
22
  }
22
- const uniqueName = typesMap.getUniqueName(typeName);
23
- if (!uniqueName) {
24
- return undefined;
23
+ // Try local typesMap first, fall back to direct schema lookup (for addon types)
24
+ let uniqueName;
25
+ try {
26
+ uniqueName = typesMap.getUniqueName(typeName);
27
+ }
28
+ catch {
29
+ // Type not in local typesMap — try direct schema lookup (addon schemas)
30
+ uniqueName = typeName;
25
31
  }
26
32
  const schema = schemas[uniqueName];
27
33
  if (!schema) {
@@ -31,7 +37,7 @@ export const serializeMCPJson = (logger, state) => {
31
37
  return schema;
32
38
  };
33
39
  for (const [name, endpointMeta] of Object.entries(resourcesMeta)) {
34
- const functionMeta = functionsMeta[endpointMeta.pikkuFuncId];
40
+ const functionMeta = resolveFunctionMeta(state, endpointMeta.pikkuFuncId);
35
41
  if (!functionMeta) {
36
42
  logger.warn(`Function ${endpointMeta.pikkuFuncId} not found in functionsMeta. Skipping resource ${name}.`);
37
43
  continue;
@@ -50,7 +56,7 @@ export const serializeMCPJson = (logger, state) => {
50
56
  });
51
57
  }
52
58
  for (const [name, endpointMeta] of Object.entries(toolsMeta)) {
53
- const functionMeta = functionsMeta[endpointMeta.pikkuFuncId];
59
+ const functionMeta = resolveFunctionMeta(state, endpointMeta.pikkuFuncId);
54
60
  if (!functionMeta) {
55
61
  logger.warn(`Function ${endpointMeta.pikkuFuncId} not found in functionsMeta. Skipping tool ${name}.`);
56
62
  continue;
@@ -68,7 +74,7 @@ export const serializeMCPJson = (logger, state) => {
68
74
  });
69
75
  }
70
76
  for (const [name, endpointMeta] of Object.entries(promptsMeta)) {
71
- const functionMeta = functionsMeta[endpointMeta.pikkuFuncId];
77
+ const functionMeta = resolveFunctionMeta(state, endpointMeta.pikkuFuncId);
72
78
  if (!functionMeta) {
73
79
  logger.warn(`Function ${endpointMeta.pikkuFuncId} not found in functionsMeta. Skipping prompt ${name}.`);
74
80
  continue;
package/dist/visit.js CHANGED
@@ -11,6 +11,7 @@ import { addMCPResource } from './add/add-mcp-resource.js';
11
11
  import { addMCPPrompt } from './add/add-mcp-prompt.js';
12
12
  import { addFunctions } from './add/add-functions.js';
13
13
  import { addChannel } from './add/add-channel.js';
14
+ import { addGateway } from './add/add-gateway.js';
14
15
  import { addRPCInvocations } from './add/add-rpc-invocations.js';
15
16
  import { addWireAddon } from './add/add-wire-addon.js';
16
17
  import { addMiddleware } from './add/add-middleware.js';
@@ -20,6 +21,7 @@ import { addSecret, addOAuth2Credential } from './add/add-secret.js';
20
21
  import { addVariable } from './add/add-variable.js';
21
22
  import { addWorkflowGraph } from './add/add-workflow-graph.js';
22
23
  import { addAIAgent } from './add/add-ai-agent.js';
24
+ import { addApprovalDescription } from './add/add-approval-description.js';
23
25
  export const visitSetup = (logger, checker, node, state, options) => {
24
26
  addFileExtendsCoreType(node, checker, state.singletonServicesTypeImportMap, 'CoreSingletonServices', state);
25
27
  addFileExtendsCoreType(node, checker, state.wireServicesTypeImportMap, 'CoreServices', state);
@@ -32,6 +34,7 @@ export const visitSetup = (logger, checker, node, state, options) => {
32
34
  addWireAddon(node, state, logger);
33
35
  addMiddleware(logger, node, checker, state, options);
34
36
  addPermission(logger, node, checker, state, options);
37
+ addApprovalDescription(logger, node, checker, state, options);
35
38
  addWorkflow(logger, node, checker, state, options);
36
39
  ts.forEachChild(node, (child) => visitSetup(logger, checker, child, state, options));
37
40
  };
@@ -46,6 +49,7 @@ export const visitRoutes = (logger, checker, node, state, options) => {
46
49
  addTrigger(logger, node, checker, state, options);
47
50
  addQueueWorker(logger, node, checker, state, options);
48
51
  addChannel(logger, node, checker, state, options);
52
+ addGateway(logger, node, checker, state, options);
49
53
  addCLI(logger, node, checker, state, options);
50
54
  addCLIRenderers(logger, node, checker, state, options);
51
55
  addMCPResource(logger, node, checker, state, options);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pikku/inspector",
3
- "version": "0.12.1",
3
+ "version": "0.12.3",
4
4
  "author": "yasser.fadl@gmail.com",
5
5
  "license": "BUSL-1.1",
6
6
  "type": "module",
@@ -34,7 +34,7 @@
34
34
  },
35
35
  "dependencies": {
36
36
  "@openapi-contrib/json-schema-to-openapi-schema": "^4.3.1",
37
- "@pikku/core": "^0.12.1",
37
+ "@pikku/core": "^0.12.3",
38
38
  "path-to-regexp": "^8.3.0",
39
39
  "ts-json-schema-generator": "^2.5.0",
40
40
  "tsx": "^4.21.0",
@@ -43,7 +43,7 @@
43
43
  "zod-to-ts": "^2.0.0"
44
44
  },
45
45
  "devDependencies": {
46
- "@types/node": "^24.10.12"
46
+ "@types/node": "^24.11.0"
47
47
  },
48
48
  "engines": {
49
49
  "node": ">=18"
@@ -268,6 +268,9 @@ export const addAIAgent: AddWiring = (
268
268
  | number
269
269
  | null
270
270
  const toolChoiceValue = getPropertyValue(obj, 'toolChoice') as string | null
271
+ const dynamicWorkflowsValue = getPropertyValue(obj, 'dynamicWorkflows') as
272
+ | string
273
+ | null
271
274
  const toolsValue = resolveToolReferences(
272
275
  obj,
273
276
  checker,
@@ -455,6 +458,9 @@ export const addAIAgent: AddWiring = (
455
458
  }),
456
459
  ...(toolsValue !== null && { tools: toolsValue }),
457
460
  ...(agentsValue !== null && { agents: agentsValue }),
461
+ ...(dynamicWorkflowsValue !== null && {
462
+ dynamicWorkflows: dynamicWorkflowsValue as 'read' | 'always' | 'ask',
463
+ }),
458
464
  tags,
459
465
  inputSchema,
460
466
  outputSchema,
@@ -0,0 +1,76 @@
1
+ import * as ts from 'typescript'
2
+ import type { AddWiring } from '../types.js'
3
+ import { extractFunctionName } from '../utils/extract-function-name.js'
4
+ import {
5
+ extractServicesFromFunction,
6
+ extractUsedWires,
7
+ } from '../utils/extract-services.js'
8
+
9
+ /**
10
+ * Inspect pikkuApprovalDescription() calls and extract metadata
11
+ */
12
+ export const addApprovalDescription: AddWiring = (
13
+ logger,
14
+ node,
15
+ checker,
16
+ state
17
+ ) => {
18
+ if (!ts.isCallExpression(node)) return
19
+
20
+ const { expression, arguments: args } = node
21
+
22
+ if (!ts.isIdentifier(expression)) return
23
+ if (expression.text !== 'pikkuApprovalDescription') return
24
+
25
+ const arg = args[0]
26
+ if (!arg) return
27
+
28
+ let actualHandler: ts.ArrowFunction | ts.FunctionExpression
29
+
30
+ if (ts.isArrowFunction(arg) || ts.isFunctionExpression(arg)) {
31
+ actualHandler = arg
32
+ } else {
33
+ logger.error(`• Handler for pikkuApprovalDescription is not a function.`)
34
+ return
35
+ }
36
+
37
+ const services = extractServicesFromFunction(actualHandler)
38
+ const wires = extractUsedWires(actualHandler, 1)
39
+ let { pikkuFuncId, exportedName } = extractFunctionName(
40
+ node,
41
+ checker,
42
+ state.rootDir
43
+ )
44
+
45
+ if (pikkuFuncId.startsWith('__temp_')) {
46
+ if (
47
+ ts.isVariableDeclaration(node.parent) &&
48
+ ts.isIdentifier(node.parent.name)
49
+ ) {
50
+ pikkuFuncId = node.parent.name.text
51
+ } else if (
52
+ ts.isPropertyAssignment(node.parent) &&
53
+ ts.isIdentifier(node.parent.name)
54
+ ) {
55
+ pikkuFuncId = node.parent.name.text
56
+ } else {
57
+ logger.error(
58
+ `• pikkuApprovalDescription() must be assigned to a variable or object property. ` +
59
+ `Extract it to a const: const myApproval = pikkuApprovalDescription(...)`
60
+ )
61
+ return
62
+ }
63
+ }
64
+
65
+ state.functions.approvalDescriptions[pikkuFuncId] = {
66
+ services,
67
+ wires: wires.wires.length > 0 || !wires.optimized ? wires : undefined,
68
+ sourceFile: node.getSourceFile().fileName,
69
+ position: node.getStart(),
70
+ exportedName,
71
+ }
72
+
73
+ logger.debug(
74
+ `• Found approval description '${pikkuFuncId}' with services: ${services.services.join(', ')}`
75
+ )
76
+ }