@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,11 +1,11 @@
1
1
  import * as ts from 'typescript'
2
- import { PermissionMetadata } from '@pikku/core'
2
+ import type { PermissionMetadata } from '@pikku/core'
3
3
  import { extractFunctionName } from './extract-function-name.js'
4
- import { InspectorState } from '../types.js'
4
+ import type { InspectorState } from '../types.js'
5
5
 
6
6
  /**
7
- * Extract permission pikkuFuncNames from an expression (array or object literal)
8
- * Resolves each identifier to its pikkuFuncName using extractFunctionName
7
+ * Extract permission pikkuFuncIds from an expression (array or object literal)
8
+ * Resolves each identifier to its pikkuFuncId using extractFunctionName
9
9
  * Also handles call expressions (like rolePermission({...}))
10
10
  *
11
11
  * Supports both formats:
@@ -22,17 +22,17 @@ export function extractPermissionPikkuNames(
22
22
  // Helper to extract from a single element
23
23
  const extractFromElement = (element: ts.Expression) => {
24
24
  if (ts.isIdentifier(element)) {
25
- const { pikkuFuncName } = extractFunctionName(element, checker, rootDir)
26
- names.push(pikkuFuncName)
25
+ const { pikkuFuncId } = extractFunctionName(element, checker, rootDir)
26
+ names.push(pikkuFuncId)
27
27
  } else if (ts.isCallExpression(element)) {
28
28
  // Handle call expressions like hasEmailQuota(100) or rolePermission({...})
29
29
  // Extract the function being called (e.g., 'hasEmailQuota' from 'hasEmailQuota(100)')
30
- const { pikkuFuncName } = extractFunctionName(
30
+ const { pikkuFuncId } = extractFunctionName(
31
31
  element.expression,
32
32
  checker,
33
33
  rootDir
34
34
  )
35
- names.push(pikkuFuncName)
35
+ names.push(pikkuFuncId)
36
36
  } else if (ts.isArrayLiteralExpression(element)) {
37
37
  // Nested array (for Record values that are arrays)
38
38
  for (const nestedElement of element.elements) {
@@ -141,11 +141,11 @@ export function resolveHTTPPermissions(
141
141
  state.rootDir
142
142
  )
143
143
  for (const name of permissionNames) {
144
- const meta = state.permissions.meta[name]
144
+ const def = state.permissions.definitions[name]
145
145
  resolved.push({
146
146
  type: 'wire',
147
147
  name,
148
- inline: meta?.exportedName === null,
148
+ inline: def?.exportedName === null,
149
149
  })
150
150
  }
151
151
  }
@@ -187,11 +187,11 @@ function resolveTagAndExplicitPermissions(
187
187
  state.rootDir
188
188
  )
189
189
  for (const name of permissionNames) {
190
- const meta = state.permissions.meta[name]
190
+ const def = state.permissions.definitions[name]
191
191
  resolved.push({
192
192
  type: 'wire',
193
193
  name,
194
- inline: meta?.exportedName === null,
194
+ inline: def?.exportedName === null,
195
195
  })
196
196
  }
197
197
  }
@@ -1,15 +1,18 @@
1
- import * as ts from 'typescript'
2
- import { InspectorState } from '../types.js'
3
- import {
1
+ import type {
2
+ InspectorState,
3
+ InspectorLogger,
4
+ InspectorOptions,
5
+ InspectorModelConfig,
6
+ MiddlewareGroupMeta,
7
+ InspectorDiagnostic,
8
+ } from '../types.js'
9
+ import type {
4
10
  FunctionServicesMeta,
5
11
  MiddlewareMetadata,
6
12
  PermissionMetadata,
7
13
  } from '@pikku/core'
8
14
  import { extractTypeKeys } from './type-utils.js'
9
- import {
10
- extractAllServiceMetadata,
11
- ServiceMetadata,
12
- } from './extract-service-metadata.js'
15
+ import { ErrorCode } from '../error-codes.js'
13
16
 
14
17
  /**
15
18
  * Helper to extract wire-level middleware/permission names from metadata.
@@ -133,7 +136,7 @@ export function aggregateRequiredServices(
133
136
 
134
137
  // 2. Services from used middleware (individual + groups)
135
138
  usedMiddleware.forEach((middlewareName) => {
136
- const middlewareMeta = state.middleware.meta[middlewareName]
139
+ const middlewareMeta = state.middleware.definitions[middlewareName]
137
140
  if (middlewareMeta?.services) {
138
141
  addServices(middlewareMeta.services)
139
142
  }
@@ -141,7 +144,7 @@ export function aggregateRequiredServices(
141
144
 
142
145
  // 3. Services from used permissions (individual + groups)
143
146
  usedPermissions.forEach((permissionName) => {
144
- const permissionMeta = state.permissions.meta[permissionName]
147
+ const permissionMeta = state.permissions.definitions[permissionName]
145
148
  if (permissionMeta?.services) {
146
149
  addServices(permissionMeta.services)
147
150
  }
@@ -220,50 +223,309 @@ export function aggregateRequiredServices(
220
223
  }
221
224
  }
222
225
 
223
- /**
224
- * Extract service interface metadata for all user-defined services.
225
- * This extracts metadata for services in SingletonServices and Services types
226
- * to generate documentation for AI consumption.
227
- *
228
- * Must be called after aggregateRequiredServices() to ensure types are loaded.
229
- */
230
- export function extractServiceInterfaceMetadata(
226
+ export function validateSecretOverrides(
227
+ logger: InspectorLogger,
228
+ state: InspectorState | Omit<InspectorState, 'typesLookup'>
229
+ ): void {
230
+ const { wireAddonDeclarations } = state.rpc
231
+ if (!wireAddonDeclarations || wireAddonDeclarations.size === 0) return
232
+
233
+ const secretNames = new Set(state.secrets.definitions.map((d) => d.name))
234
+
235
+ for (const [namespace, addonDecl] of wireAddonDeclarations.entries()) {
236
+ if (!addonDecl.secretOverrides) continue
237
+
238
+ for (const secretKey of Object.keys(addonDecl.secretOverrides)) {
239
+ if (!secretNames.has(secretKey)) {
240
+ const availableSecrets = Array.from(secretNames)
241
+ logger.critical(
242
+ ErrorCode.INVALID_VALUE,
243
+ `Secret override '${secretKey}' in addon '${namespace}' (${addonDecl.package}) does not exist. Available secrets: ${availableSecrets.join(', ') || 'none'}`
244
+ )
245
+ }
246
+ }
247
+ }
248
+ }
249
+
250
+ export function validateVariableOverrides(
251
+ logger: InspectorLogger,
252
+ state: InspectorState | Omit<InspectorState, 'typesLookup'>
253
+ ): void {
254
+ const { wireAddonDeclarations } = state.rpc
255
+ if (!wireAddonDeclarations || wireAddonDeclarations.size === 0) return
256
+
257
+ const variableNames = new Set(state.variables.definitions.map((d) => d.name))
258
+
259
+ for (const [namespace, addonDecl] of wireAddonDeclarations.entries()) {
260
+ if (!addonDecl.variableOverrides) continue
261
+
262
+ for (const variableKey of Object.keys(addonDecl.variableOverrides)) {
263
+ if (!variableNames.has(variableKey)) {
264
+ const availableVariables = Array.from(variableNames)
265
+ logger.critical(
266
+ ErrorCode.INVALID_VALUE,
267
+ `Variable override '${variableKey}' in addon '${namespace}' (${addonDecl.package}) does not exist. Available variables: ${availableVariables.join(', ') || 'none'}`
268
+ )
269
+ }
270
+ }
271
+ }
272
+ }
273
+
274
+ export function computeResolvedIOTypes(state: InspectorState): void {
275
+ const { functions } = state
276
+ for (const [pikkuFuncId, meta] of Object.entries(functions.meta)) {
277
+ const input = meta.inputs?.[0]
278
+ const output = meta.outputs?.[0]
279
+
280
+ let inputType = 'null'
281
+ if (input) {
282
+ try {
283
+ inputType = functions.typesMap.getTypeMeta(input).uniqueName
284
+ } catch {
285
+ inputType = input
286
+ }
287
+ }
288
+
289
+ let outputType = 'null'
290
+ if (output) {
291
+ try {
292
+ outputType = functions.typesMap.getTypeMeta(output).uniqueName
293
+ } catch {
294
+ outputType = output
295
+ }
296
+ }
297
+
298
+ state.resolvedIOTypes[pikkuFuncId] = { inputType, outputType }
299
+ }
300
+ }
301
+
302
+ const serializeGroupMap = (
303
+ groupMap: Map<string, MiddlewareGroupMeta>
304
+ ): Record<string, MiddlewareGroupMeta> => {
305
+ const result: Record<string, MiddlewareGroupMeta> = {}
306
+ for (const [key, meta] of groupMap.entries()) {
307
+ result[key] = {
308
+ exportName: meta.exportName,
309
+ sourceFile: meta.sourceFile,
310
+ position: meta.position,
311
+ services: meta.services,
312
+ count: meta.count,
313
+ instanceIds: meta.instanceIds,
314
+ isFactory: meta.isFactory,
315
+ }
316
+ }
317
+ return result
318
+ }
319
+
320
+ export function computeMiddlewareGroupsMeta(state: InspectorState): void {
321
+ state.middlewareGroupsMeta = {
322
+ definitions: state.middleware.definitions,
323
+ instances: state.middleware.instances,
324
+ httpGroups: serializeGroupMap(state.http.routeMiddleware),
325
+ tagGroups: serializeGroupMap(state.middleware.tagMiddleware),
326
+ channelMiddleware: {
327
+ definitions: state.channelMiddleware.definitions,
328
+ instances: state.channelMiddleware.instances,
329
+ tagGroups: serializeGroupMap(state.channelMiddleware.tagMiddleware),
330
+ },
331
+ }
332
+ }
333
+
334
+ export function computePermissionsGroupsMeta(state: InspectorState): void {
335
+ const httpGroups: Record<string, any> = {}
336
+ for (const [pattern, meta] of state.http.routePermissions.entries()) {
337
+ httpGroups[pattern] = {
338
+ exportName: meta.exportName,
339
+ sourceFile: meta.sourceFile,
340
+ position: meta.position,
341
+ services: meta.services,
342
+ count: meta.count,
343
+ instanceIds: meta.instanceIds,
344
+ isFactory: meta.isFactory,
345
+ }
346
+ }
347
+
348
+ const tagGroups: Record<string, any> = {}
349
+ for (const [tag, meta] of state.permissions.tagPermissions.entries()) {
350
+ tagGroups[tag] = {
351
+ exportName: meta.exportName,
352
+ sourceFile: meta.sourceFile,
353
+ position: meta.position,
354
+ services: meta.services,
355
+ count: meta.count,
356
+ instanceIds: meta.instanceIds,
357
+ isFactory: meta.isFactory,
358
+ }
359
+ }
360
+
361
+ state.permissionsGroupsMeta = {
362
+ definitions: state.permissions.definitions,
363
+ httpGroups,
364
+ tagGroups,
365
+ }
366
+ }
367
+
368
+ const PRIMITIVE_TYPES = new Set([
369
+ 'boolean',
370
+ 'string',
371
+ 'number',
372
+ 'null',
373
+ 'undefined',
374
+ 'void',
375
+ 'any',
376
+ 'unknown',
377
+ 'never',
378
+ ])
379
+
380
+ export function computeRequiredSchemas(
381
+ state: InspectorState,
382
+ options: InspectorOptions
383
+ ): void {
384
+ const { functions, schemaLookup } = state
385
+ const schemasFromTypes = options.schemaConfig?.schemasFromTypes
386
+
387
+ state.requiredSchemas = new Set<string>([
388
+ ...Object.values(functions.meta)
389
+ .flatMap(({ inputs, outputs }) => {
390
+ const types: (string | undefined)[] = []
391
+ if (inputs?.[0]) {
392
+ try {
393
+ types.push(functions.typesMap.getUniqueName(inputs[0]))
394
+ } catch {
395
+ types.push(inputs[0])
396
+ }
397
+ }
398
+ if (outputs?.[0]) {
399
+ try {
400
+ types.push(functions.typesMap.getUniqueName(outputs[0]))
401
+ } catch {
402
+ types.push(outputs[0])
403
+ }
404
+ }
405
+ return types
406
+ })
407
+ .filter((s): s is string => !!s && !PRIMITIVE_TYPES.has(s)),
408
+ ...functions.typesMap.customTypes.keys(),
409
+ ...(schemasFromTypes || []),
410
+ ...Array.from(schemaLookup.keys()),
411
+ ])
412
+ }
413
+
414
+ export function validateAgentModels(
415
+ logger: InspectorLogger,
231
416
  state: InspectorState | Omit<InspectorState, 'typesLookup'>,
232
- checker: ts.TypeChecker
417
+ modelConfig?: InspectorModelConfig
233
418
  ): void {
234
- if (!('typesLookup' in state)) {
235
- return
419
+ const aliases = modelConfig?.models ?? {}
420
+
421
+ for (const [, meta] of Object.entries(state.agents.agentsMeta)) {
422
+ const model = meta.model
423
+ if (!model) {
424
+ logger.critical(
425
+ ErrorCode.MISSING_MODEL,
426
+ `AI agent '${meta.name}' is missing the 'model' property.`
427
+ )
428
+ continue
429
+ }
430
+ if (model.includes('/')) continue
431
+ if (!aliases[model]) {
432
+ const available = Object.keys(aliases)
433
+ logger.critical(
434
+ ErrorCode.INVALID_MODEL,
435
+ `AI agent '${meta.name}' uses model alias '${model}' which is not defined in pikku.config.json models. ` +
436
+ `Available aliases: ${available.join(', ') || 'none'}`
437
+ )
438
+ }
236
439
  }
440
+ }
237
441
 
238
- const allMetadata: ServiceMetadata[] = []
442
+ export function validateAgentOverrides(
443
+ logger: InspectorLogger,
444
+ state: InspectorState | Omit<InspectorState, 'typesLookup'>,
445
+ modelConfig?: InspectorModelConfig
446
+ ): void {
447
+ const overrides = modelConfig?.agentOverrides ?? {}
448
+ const aliases = modelConfig?.models ?? {}
449
+ const agentNames = new Set(
450
+ Object.values(state.agents.agentsMeta).map((m) => m.name)
451
+ )
239
452
 
240
- const singletonServicesTypes = state.typesLookup.get('SingletonServices')
241
- if (singletonServicesTypes && singletonServicesTypes.length > 0) {
242
- const singletonMeta = extractAllServiceMetadata(
243
- singletonServicesTypes[0],
244
- checker,
245
- state.rootDir
246
- )
247
- allMetadata.push(...singletonMeta)
453
+ for (const [agentName, override] of Object.entries(overrides)) {
454
+ if (!agentNames.has(agentName)) {
455
+ logger.warn(`agentOverrides references unknown agent '${agentName}'`)
456
+ }
457
+ if (
458
+ override.model &&
459
+ !override.model.includes('/') &&
460
+ !aliases[override.model]
461
+ ) {
462
+ logger.critical(
463
+ ErrorCode.INVALID_MODEL,
464
+ `agentOverrides['${agentName}'].model uses alias '${override.model}' which is not defined in models.`
465
+ )
466
+ }
248
467
  }
468
+ }
249
469
 
250
- const servicesTypes = state.typesLookup.get('Services')
251
- if (servicesTypes && servicesTypes.length > 0) {
252
- const wireServicesMeta = extractAllServiceMetadata(
253
- servicesTypes[0],
254
- checker,
255
- state.rootDir
256
- )
470
+ export function computeDiagnostics(state: InspectorState): void {
471
+ const diagnostics: InspectorDiagnostic[] = []
257
472
 
258
- const singletonNames = new Set(
259
- state.serviceAggregation.allSingletonServices
260
- )
261
- const uniqueWireServices = wireServicesMeta.filter(
262
- (meta) => !singletonNames.has(meta.name)
263
- )
473
+ for (const [id, meta] of Object.entries(state.functions.meta)) {
474
+ if (meta.services && !meta.services.optimized) {
475
+ diagnostics.push({
476
+ code: ErrorCode.SERVICES_NOT_DESTRUCTURED,
477
+ message: `Function '${id}' does not destructure its services parameter, preventing tree-shaking optimization.`,
478
+ sourceFile: meta.pikkuFuncId,
479
+ position: 0,
480
+ })
481
+ }
482
+ if (meta.wires && !meta.wires.optimized) {
483
+ diagnostics.push({
484
+ code: ErrorCode.WIRES_NOT_DESTRUCTURED,
485
+ message: `Function '${id}' does not destructure its wires parameter, preventing tree-shaking optimization.`,
486
+ sourceFile: meta.pikkuFuncId,
487
+ position: 0,
488
+ })
489
+ }
490
+ }
491
+
492
+ for (const [id, def] of Object.entries(state.middleware.definitions)) {
493
+ if (def.services && !def.services.optimized) {
494
+ diagnostics.push({
495
+ code: ErrorCode.SERVICES_NOT_DESTRUCTURED,
496
+ message: `Middleware '${id}' does not destructure its services parameter, preventing tree-shaking optimization.`,
497
+ sourceFile: def.sourceFile,
498
+ position: def.position,
499
+ })
500
+ }
501
+ if (def.wires && !def.wires.optimized) {
502
+ diagnostics.push({
503
+ code: ErrorCode.WIRES_NOT_DESTRUCTURED,
504
+ message: `Middleware '${id}' does not destructure its wires parameter, preventing tree-shaking optimization.`,
505
+ sourceFile: def.sourceFile,
506
+ position: def.position,
507
+ })
508
+ }
509
+ }
264
510
 
265
- allMetadata.push(...uniqueWireServices)
511
+ for (const [id, def] of Object.entries(state.permissions.definitions)) {
512
+ if (def.services && !def.services.optimized) {
513
+ diagnostics.push({
514
+ code: ErrorCode.SERVICES_NOT_DESTRUCTURED,
515
+ message: `Permission '${id}' does not destructure its services parameter, preventing tree-shaking optimization.`,
516
+ sourceFile: def.sourceFile,
517
+ position: def.position,
518
+ })
519
+ }
520
+ if (def.wires && !def.wires.optimized) {
521
+ diagnostics.push({
522
+ code: ErrorCode.WIRES_NOT_DESTRUCTURED,
523
+ message: `Permission '${id}' does not destructure its wires parameter, preventing tree-shaking optimization.`,
524
+ sourceFile: def.sourceFile,
525
+ position: def.position,
526
+ })
527
+ }
266
528
  }
267
529
 
268
- state.serviceMetadata = allMetadata
530
+ state.diagnostics = diagnostics
269
531
  }
@@ -0,0 +1,49 @@
1
+ import * as ts from 'typescript'
2
+
3
+ /**
4
+ * Resolve the addon package name from an imported identifier.
5
+ * Checks if the identifier's import module specifier matches any
6
+ * configured addon package.
7
+ *
8
+ * This is a general utility — any wire handler that processes a `func`
9
+ * property can use it to detect when the function comes from an
10
+ * addon package.
11
+ */
12
+ export const resolveAddonName = (
13
+ identifier: ts.Identifier,
14
+ checker: ts.TypeChecker,
15
+ wireAddonDeclarations?: Map<
16
+ string,
17
+ {
18
+ package: string
19
+ rpcEndpoint?: string
20
+ secretOverrides?: Record<string, string>
21
+ variableOverrides?: Record<string, string>
22
+ }
23
+ >
24
+ ): string | null => {
25
+ if (!wireAddonDeclarations || wireAddonDeclarations.size === 0) {
26
+ return null
27
+ }
28
+
29
+ const sym = checker.getSymbolAtLocation(identifier)
30
+ if (!sym) return null
31
+
32
+ const decl = sym.declarations?.[0]
33
+ if (!decl || !ts.isImportSpecifier(decl)) return null
34
+
35
+ // ImportSpecifier -> NamedImports -> ImportClause -> ImportDeclaration
36
+ const importDecl = decl.parent?.parent?.parent
37
+ if (!importDecl || !ts.isImportDeclaration(importDecl)) return null
38
+ if (!ts.isStringLiteral(importDecl.moduleSpecifier)) return null
39
+
40
+ const moduleSpecifier = importDecl.moduleSpecifier.text
41
+
42
+ for (const addonDecl of wireAddonDeclarations.values()) {
43
+ if (addonDecl.package === moduleSpecifier) {
44
+ return addonDecl.package
45
+ }
46
+ }
47
+
48
+ return null
49
+ }
@@ -0,0 +1,42 @@
1
+ import type { TypesMap } from '../types-map.js'
2
+ import type { FunctionsMeta } from '@pikku/core'
3
+
4
+ export function resolveFunctionIOTypes(
5
+ pikkuFuncId: string,
6
+ functionsMeta: FunctionsMeta,
7
+ typesMap: TypesMap,
8
+ requiredTypes: Set<string>
9
+ ): { inputType: string; outputType: string } {
10
+ const functionMeta = functionsMeta[pikkuFuncId]
11
+ if (!functionMeta) {
12
+ throw new Error(
13
+ `Function ${pikkuFuncId} not found in functionsMeta. Please check your configuration.`
14
+ )
15
+ }
16
+
17
+ const input = functionMeta.inputs ? functionMeta.inputs[0] : undefined
18
+ const output = functionMeta.outputs ? functionMeta.outputs[0] : undefined
19
+
20
+ let inputType = 'null'
21
+ if (input) {
22
+ try {
23
+ inputType = typesMap.getTypeMeta(input).uniqueName
24
+ } catch {
25
+ inputType = input
26
+ }
27
+ }
28
+
29
+ let outputType = 'null'
30
+ if (output) {
31
+ try {
32
+ outputType = typesMap.getTypeMeta(output).uniqueName
33
+ } catch {
34
+ outputType = output
35
+ }
36
+ }
37
+
38
+ requiredTypes.add(inputType)
39
+ requiredTypes.add(outputType)
40
+
41
+ return { inputType, outputType }
42
+ }
@@ -0,0 +1,46 @@
1
+ import * as ts from 'typescript'
2
+
3
+ /**
4
+ * Resolve an identifier to its definition, optionally unwrapping
5
+ * known "define" wrapper functions (e.g. defineHTTPRoutes, defineCLICommands).
6
+ *
7
+ * When the identifier resolves to a variable whose initializer is a call
8
+ * to one of the `unwrapFunctionNames`, the first argument of that call
9
+ * is returned instead.
10
+ */
11
+ export function resolveIdentifier(
12
+ node: ts.Identifier,
13
+ checker: ts.TypeChecker,
14
+ unwrapFunctionNames?: string[]
15
+ ): ts.Node | undefined {
16
+ const symbol = checker.getSymbolAtLocation(node)
17
+ if (!symbol) return undefined
18
+
19
+ // Handle aliased symbols (imports)
20
+ let resolvedSymbol = symbol
21
+ if (resolvedSymbol.flags & ts.SymbolFlags.Alias) {
22
+ resolvedSymbol = checker.getAliasedSymbol(resolvedSymbol) ?? resolvedSymbol
23
+ }
24
+
25
+ const decl =
26
+ resolvedSymbol.valueDeclaration || resolvedSymbol.declarations?.[0]
27
+ if (!decl) return undefined
28
+
29
+ // Follow to the actual value (handles imports, variable declarations)
30
+ if (ts.isVariableDeclaration(decl) && decl.initializer) {
31
+ // Check if it's a call to one of the unwrap functions
32
+ if (
33
+ ts.isCallExpression(decl.initializer) &&
34
+ unwrapFunctionNames &&
35
+ unwrapFunctionNames.length > 0
36
+ ) {
37
+ const expr = decl.initializer.expression
38
+ if (ts.isIdentifier(expr) && unwrapFunctionNames.includes(expr.text)) {
39
+ return decl.initializer.arguments[0]
40
+ }
41
+ }
42
+ return decl.initializer
43
+ }
44
+
45
+ return undefined
46
+ }