@pikku/inspector 0.11.2 → 0.12.0

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