@pikku/inspector 0.11.2 → 0.12.1

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