@pikku/inspector 0.11.1 → 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 (189) hide show
  1. package/CHANGELOG.md +26 -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 +327 -59
  9. package/dist/add/add-http-route.d.ts +19 -10
  10. package/dist/add/add-http-route.js +153 -44
  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.d.ts +3 -0
  23. package/dist/add/add-rpc-invocations.js +65 -25
  24. package/dist/add/add-schedule.js +11 -5
  25. package/dist/add/add-secret.d.ts +3 -0
  26. package/dist/add/add-secret.js +82 -0
  27. package/dist/add/add-trigger.d.ts +2 -0
  28. package/dist/add/add-trigger.js +87 -0
  29. package/dist/add/add-variable.d.ts +1 -0
  30. package/dist/add/add-variable.js +8 -0
  31. package/dist/add/add-workflow-graph.d.ts +7 -0
  32. package/dist/add/add-workflow-graph.js +396 -0
  33. package/dist/add/add-workflow.js +124 -26
  34. package/dist/error-codes.d.ts +16 -1
  35. package/dist/error-codes.js +21 -1
  36. package/dist/index.d.ts +9 -5
  37. package/dist/index.js +5 -2
  38. package/dist/inspector.d.ts +1 -1
  39. package/dist/inspector.js +106 -13
  40. package/dist/schema-generator.d.ts +1 -0
  41. package/dist/schema-generator.js +1 -0
  42. package/dist/types-map.js +10 -1
  43. package/dist/types.d.ts +180 -30
  44. package/dist/utils/compute-required-schemas.d.ts +4 -0
  45. package/dist/utils/compute-required-schemas.js +41 -0
  46. package/dist/utils/contract-hashes.d.ts +35 -0
  47. package/dist/utils/contract-hashes.js +202 -0
  48. package/dist/utils/custom-types-generator.d.ts +9 -0
  49. package/dist/utils/custom-types-generator.js +71 -0
  50. package/dist/utils/detect-schema-vendor.d.ts +22 -0
  51. package/dist/utils/detect-schema-vendor.js +76 -0
  52. package/dist/utils/ensure-function-metadata.d.ts +5 -2
  53. package/dist/utils/ensure-function-metadata.js +220 -6
  54. package/dist/utils/extract-function-name.d.ts +5 -16
  55. package/dist/utils/extract-function-name.js +93 -298
  56. package/dist/utils/extract-services.d.ts +2 -1
  57. package/dist/utils/extract-services.js +25 -1
  58. package/dist/utils/filter-inspector-state.js +107 -23
  59. package/dist/utils/get-property-value.d.ts +8 -2
  60. package/dist/utils/get-property-value.js +33 -4
  61. package/dist/utils/hash.d.ts +2 -0
  62. package/dist/utils/hash.js +23 -0
  63. package/dist/utils/middleware.d.ts +7 -30
  64. package/dist/utils/middleware.js +80 -66
  65. package/dist/utils/permissions.d.ts +2 -2
  66. package/dist/utils/permissions.js +10 -10
  67. package/dist/utils/post-process.d.ts +9 -10
  68. package/dist/utils/post-process.js +231 -24
  69. package/dist/utils/resolve-external-package.d.ts +12 -0
  70. package/dist/utils/resolve-external-package.js +34 -0
  71. package/dist/utils/resolve-function-types.d.ts +6 -0
  72. package/dist/utils/resolve-function-types.js +29 -0
  73. package/dist/utils/resolve-identifier.d.ts +10 -0
  74. package/dist/utils/resolve-identifier.js +36 -0
  75. package/dist/utils/resolve-versions.d.ts +2 -0
  76. package/dist/utils/resolve-versions.js +78 -0
  77. package/dist/utils/schema-generator.d.ts +9 -0
  78. package/dist/utils/schema-generator.js +209 -0
  79. package/dist/utils/serialize-inspector-state.d.ts +73 -13
  80. package/dist/utils/serialize-inspector-state.js +102 -6
  81. package/dist/utils/serialize-mcp-json.d.ts +2 -0
  82. package/dist/utils/serialize-mcp-json.js +99 -0
  83. package/dist/utils/serialize-middleware-groups-meta.d.ts +12 -0
  84. package/dist/utils/serialize-middleware-groups-meta.js +28 -0
  85. package/dist/utils/serialize-openapi-json.d.ts +85 -0
  86. package/dist/utils/serialize-openapi-json.js +151 -0
  87. package/dist/utils/serialize-permissions-groups-meta.d.ts +6 -0
  88. package/dist/utils/serialize-permissions-groups-meta.js +31 -0
  89. package/dist/utils/workflow/dsl/deserialize-dsl-workflow.d.ts +24 -0
  90. package/dist/utils/workflow/dsl/deserialize-dsl-workflow.js +830 -0
  91. package/dist/{workflow/extract-simple-workflow.d.ts → utils/workflow/dsl/extract-dsl-workflow.d.ts} +4 -2
  92. package/dist/{workflow/extract-simple-workflow.js → utils/workflow/dsl/extract-dsl-workflow.js} +572 -72
  93. package/dist/utils/workflow/dsl/index.d.ts +7 -0
  94. package/dist/utils/workflow/dsl/index.js +7 -0
  95. package/dist/{workflow → utils/workflow/dsl}/patterns.d.ts +21 -0
  96. package/dist/{workflow → utils/workflow/dsl}/patterns.js +90 -10
  97. package/dist/{workflow → utils/workflow/dsl}/validation.d.ts +2 -0
  98. package/dist/{workflow → utils/workflow/dsl}/validation.js +25 -7
  99. package/dist/utils/workflow/graph/convert-dsl-to-graph.d.ts +13 -0
  100. package/dist/utils/workflow/graph/convert-dsl-to-graph.js +318 -0
  101. package/dist/utils/workflow/graph/finalize-workflow-wires.d.ts +3 -0
  102. package/dist/utils/workflow/graph/finalize-workflow-wires.js +276 -0
  103. package/dist/utils/workflow/graph/finalize-workflows.d.ts +2 -0
  104. package/dist/utils/workflow/graph/finalize-workflows.js +75 -0
  105. package/dist/utils/workflow/graph/index.d.ts +8 -0
  106. package/dist/utils/workflow/graph/index.js +8 -0
  107. package/dist/utils/workflow/graph/serialize-workflow-graph.d.ts +35 -0
  108. package/dist/utils/workflow/graph/serialize-workflow-graph.js +150 -0
  109. package/dist/utils/workflow/graph/workflow-graph.types.d.ts +203 -0
  110. package/dist/utils/workflow/graph/workflow-graph.types.js +38 -0
  111. package/dist/visit.js +13 -2
  112. package/package.json +26 -4
  113. package/src/add/add-ai-agent.ts +468 -0
  114. package/src/add/add-channel.ts +82 -79
  115. package/src/add/add-cli.ts +49 -20
  116. package/src/add/add-file-with-factory.ts +2 -0
  117. package/src/add/add-functions.ts +429 -71
  118. package/src/add/add-http-route.ts +246 -65
  119. package/src/add/add-http-routes.ts +228 -0
  120. package/src/add/add-keyed-wiring.ts +151 -0
  121. package/src/add/add-mcp-prompt.ts +26 -15
  122. package/src/add/add-mcp-resource.ts +27 -15
  123. package/src/add/add-middleware.ts +482 -80
  124. package/src/add/add-permission.ts +199 -40
  125. package/src/add/add-queue-worker.ts +24 -19
  126. package/src/add/add-rpc-invocations.ts +78 -31
  127. package/src/add/add-schedule.ts +16 -11
  128. package/src/add/add-secret.ts +140 -0
  129. package/src/add/add-trigger.ts +154 -0
  130. package/src/add/add-variable.ts +9 -0
  131. package/src/add/add-workflow-graph.ts +522 -0
  132. package/src/add/add-workflow.ts +117 -30
  133. package/src/error-codes.ts +26 -1
  134. package/src/index.ts +27 -8
  135. package/src/inspector.ts +145 -17
  136. package/src/schema-generator.ts +1 -0
  137. package/src/types-map.ts +12 -1
  138. package/src/types.ts +192 -51
  139. package/src/utils/compute-required-schemas.ts +49 -0
  140. package/src/utils/contract-hashes.test.ts +528 -0
  141. package/src/utils/contract-hashes.ts +290 -0
  142. package/src/utils/custom-types-generator.ts +88 -0
  143. package/src/utils/detect-schema-vendor.ts +90 -0
  144. package/src/utils/ensure-function-metadata.ts +324 -7
  145. package/src/utils/extract-function-name.ts +108 -358
  146. package/src/utils/extract-services.ts +35 -2
  147. package/src/utils/filter-inspector-state.test.ts +34 -20
  148. package/src/utils/filter-inspector-state.ts +140 -31
  149. package/src/utils/get-property-value.ts +50 -5
  150. package/src/utils/hash.ts +26 -0
  151. package/src/utils/middleware.test.ts +204 -0
  152. package/src/utils/middleware.ts +129 -67
  153. package/src/utils/permissions.test.ts +35 -12
  154. package/src/utils/permissions.ts +10 -10
  155. package/src/utils/post-process.ts +283 -43
  156. package/src/utils/resolve-external-package.ts +42 -0
  157. package/src/utils/resolve-function-types.ts +42 -0
  158. package/src/utils/resolve-identifier.ts +46 -0
  159. package/src/utils/resolve-versions.test.ts +249 -0
  160. package/src/utils/resolve-versions.ts +105 -0
  161. package/src/utils/schema-generator.ts +329 -0
  162. package/src/utils/serialize-inspector-state.ts +181 -20
  163. package/src/utils/serialize-mcp-json.ts +145 -0
  164. package/src/utils/serialize-middleware-groups-meta.ts +33 -0
  165. package/src/utils/serialize-openapi-json.ts +277 -0
  166. package/src/utils/serialize-permissions-groups-meta.ts +35 -0
  167. package/src/utils/test-data/inspector-state.json +69 -66
  168. package/src/utils/workflow/dsl/deserialize-dsl-workflow.ts +1104 -0
  169. package/src/{workflow/extract-simple-workflow.ts → utils/workflow/dsl/extract-dsl-workflow.ts} +678 -85
  170. package/src/utils/workflow/dsl/index.ts +11 -0
  171. package/src/{workflow → utils/workflow/dsl}/patterns.ts +108 -11
  172. package/src/{workflow → utils/workflow/dsl}/validation.ts +34 -7
  173. package/src/utils/workflow/graph/convert-dsl-to-graph.ts +422 -0
  174. package/src/utils/workflow/graph/finalize-workflow-wires.ts +310 -0
  175. package/src/utils/workflow/graph/finalize-workflows.ts +100 -0
  176. package/src/utils/workflow/graph/index.ts +11 -0
  177. package/src/utils/workflow/graph/serialize-workflow-graph.ts +216 -0
  178. package/src/utils/workflow/graph/workflow-graph.types.ts +231 -0
  179. package/src/visit.ts +14 -2
  180. package/tsconfig.tsbuildinfo +1 -1
  181. package/dist/add/add-mcp-tool.d.ts +0 -2
  182. package/dist/add/add-mcp-tool.js +0 -81
  183. package/dist/utils/extract-service-metadata.d.ts +0 -19
  184. package/dist/utils/extract-service-metadata.js +0 -244
  185. package/dist/utils/write-service-metadata.d.ts +0 -13
  186. package/dist/utils/write-service-metadata.js +0 -37
  187. package/src/add/add-mcp-tool.ts +0 -141
  188. package/src/utils/extract-service-metadata.ts +0 -353
  189. package/src/utils/write-service-metadata.ts +0 -51
@@ -4,8 +4,8 @@ import { extractFunctionName } from './extract-function-name.js'
4
4
  import { 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,19 @@
1
- import * as ts from 'typescript'
2
- import { InspectorState } from '../types.js'
1
+ import {
2
+ InspectorState,
3
+ InspectorLogger,
4
+ InspectorOptions,
5
+ InspectorModelConfig,
6
+ ExternalPackageConfig,
7
+ MiddlewareGroupMeta,
8
+ InspectorDiagnostic,
9
+ } from '../types.js'
3
10
  import {
4
11
  FunctionServicesMeta,
5
12
  MiddlewareMetadata,
6
13
  PermissionMetadata,
7
14
  } from '@pikku/core'
8
15
  import { extractTypeKeys } from './type-utils.js'
9
- import {
10
- extractAllServiceMetadata,
11
- ServiceMetadata,
12
- } from './extract-service-metadata.js'
16
+ import { ErrorCode } from '../error-codes.js'
13
17
 
14
18
  /**
15
19
  * Helper to extract wire-level middleware/permission names from metadata.
@@ -133,7 +137,7 @@ export function aggregateRequiredServices(
133
137
 
134
138
  // 2. Services from used middleware (individual + groups)
135
139
  usedMiddleware.forEach((middlewareName) => {
136
- const middlewareMeta = state.middleware.meta[middlewareName]
140
+ const middlewareMeta = state.middleware.definitions[middlewareName]
137
141
  if (middlewareMeta?.services) {
138
142
  addServices(middlewareMeta.services)
139
143
  }
@@ -141,7 +145,7 @@ export function aggregateRequiredServices(
141
145
 
142
146
  // 3. Services from used permissions (individual + groups)
143
147
  usedPermissions.forEach((permissionName) => {
144
- const permissionMeta = state.permissions.meta[permissionName]
148
+ const permissionMeta = state.permissions.definitions[permissionName]
145
149
  if (permissionMeta?.services) {
146
150
  addServices(permissionMeta.services)
147
151
  }
@@ -220,50 +224,286 @@ export function aggregateRequiredServices(
220
224
  }
221
225
  }
222
226
 
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(
227
+ export function validateSecretOverrides(
228
+ logger: InspectorLogger,
231
229
  state: InspectorState | Omit<InspectorState, 'typesLookup'>,
232
- checker: ts.TypeChecker
230
+ externalPackages?: Record<string, ExternalPackageConfig>
233
231
  ): void {
234
- if (!('typesLookup' in state)) {
235
- return
232
+ if (!externalPackages) return
233
+
234
+ const secretNames = new Set(state.secrets.definitions.map((d) => d.name))
235
+
236
+ for (const [namespace, pkgConfig] of Object.entries(externalPackages)) {
237
+ if (!pkgConfig.secretOverrides) continue
238
+
239
+ for (const secretKey of Object.keys(pkgConfig.secretOverrides)) {
240
+ if (!secretNames.has(secretKey)) {
241
+ const availableSecrets = Array.from(secretNames)
242
+ logger.critical(
243
+ ErrorCode.INVALID_VALUE,
244
+ `Secret override '${secretKey}' in external package '${namespace}' (${pkgConfig.package}) does not exist. Available secrets: ${availableSecrets.join(', ') || 'none'}`
245
+ )
246
+ }
247
+ }
236
248
  }
249
+ }
237
250
 
238
- const allMetadata: ServiceMetadata[] = []
251
+ export function computeResolvedIOTypes(state: InspectorState): void {
252
+ const { functions } = state
253
+ for (const [pikkuFuncId, meta] of Object.entries(functions.meta)) {
254
+ const input = meta.inputs?.[0]
255
+ const output = meta.outputs?.[0]
239
256
 
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)
257
+ let inputType = 'null'
258
+ if (input) {
259
+ try {
260
+ inputType = functions.typesMap.getTypeMeta(input).uniqueName
261
+ } catch {
262
+ inputType = input
263
+ }
264
+ }
265
+
266
+ let outputType = 'null'
267
+ if (output) {
268
+ try {
269
+ outputType = functions.typesMap.getTypeMeta(output).uniqueName
270
+ } catch {
271
+ outputType = output
272
+ }
273
+ }
274
+
275
+ state.resolvedIOTypes[pikkuFuncId] = { inputType, outputType }
248
276
  }
277
+ }
249
278
 
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
- )
279
+ const serializeGroupMap = (
280
+ groupMap: Map<string, MiddlewareGroupMeta>
281
+ ): Record<string, MiddlewareGroupMeta> => {
282
+ const result: Record<string, MiddlewareGroupMeta> = {}
283
+ for (const [key, meta] of groupMap.entries()) {
284
+ result[key] = {
285
+ exportName: meta.exportName,
286
+ sourceFile: meta.sourceFile,
287
+ position: meta.position,
288
+ services: meta.services,
289
+ count: meta.count,
290
+ instanceIds: meta.instanceIds,
291
+ isFactory: meta.isFactory,
292
+ }
293
+ }
294
+ return result
295
+ }
257
296
 
258
- const singletonNames = new Set(
259
- state.serviceAggregation.allSingletonServices
260
- )
261
- const uniqueWireServices = wireServicesMeta.filter(
262
- (meta) => !singletonNames.has(meta.name)
263
- )
297
+ export function computeMiddlewareGroupsMeta(state: InspectorState): void {
298
+ state.middlewareGroupsMeta = {
299
+ definitions: state.middleware.definitions,
300
+ instances: state.middleware.instances,
301
+ httpGroups: serializeGroupMap(state.http.routeMiddleware),
302
+ tagGroups: serializeGroupMap(state.middleware.tagMiddleware),
303
+ channelMiddleware: {
304
+ definitions: state.channelMiddleware.definitions,
305
+ instances: state.channelMiddleware.instances,
306
+ tagGroups: serializeGroupMap(state.channelMiddleware.tagMiddleware),
307
+ },
308
+ }
309
+ }
310
+
311
+ export function computePermissionsGroupsMeta(state: InspectorState): void {
312
+ const httpGroups: Record<string, any> = {}
313
+ for (const [pattern, meta] of state.http.routePermissions.entries()) {
314
+ httpGroups[pattern] = {
315
+ exportName: meta.exportName,
316
+ sourceFile: meta.sourceFile,
317
+ position: meta.position,
318
+ services: meta.services,
319
+ count: meta.count,
320
+ instanceIds: meta.instanceIds,
321
+ isFactory: meta.isFactory,
322
+ }
323
+ }
324
+
325
+ const tagGroups: Record<string, any> = {}
326
+ for (const [tag, meta] of state.permissions.tagPermissions.entries()) {
327
+ tagGroups[tag] = {
328
+ exportName: meta.exportName,
329
+ sourceFile: meta.sourceFile,
330
+ position: meta.position,
331
+ services: meta.services,
332
+ count: meta.count,
333
+ instanceIds: meta.instanceIds,
334
+ isFactory: meta.isFactory,
335
+ }
336
+ }
264
337
 
265
- allMetadata.push(...uniqueWireServices)
338
+ state.permissionsGroupsMeta = {
339
+ definitions: state.permissions.definitions,
340
+ httpGroups,
341
+ tagGroups,
342
+ }
343
+ }
344
+
345
+ const PRIMITIVE_TYPES = new Set([
346
+ 'boolean',
347
+ 'string',
348
+ 'number',
349
+ 'null',
350
+ 'undefined',
351
+ 'void',
352
+ 'any',
353
+ 'unknown',
354
+ 'never',
355
+ ])
356
+
357
+ export function computeRequiredSchemas(
358
+ state: InspectorState,
359
+ options: InspectorOptions
360
+ ): void {
361
+ const { functions, schemaLookup } = state
362
+ const schemasFromTypes = options.schemaConfig?.schemasFromTypes
363
+
364
+ state.requiredSchemas = new Set<string>([
365
+ ...Object.values(functions.meta)
366
+ .map(({ inputs, outputs }) => {
367
+ const types: (string | undefined)[] = []
368
+ if (inputs?.[0]) {
369
+ try {
370
+ types.push(functions.typesMap.getUniqueName(inputs[0]))
371
+ } catch {
372
+ types.push(inputs[0])
373
+ }
374
+ }
375
+ if (outputs?.[0]) {
376
+ try {
377
+ types.push(functions.typesMap.getUniqueName(outputs[0]))
378
+ } catch {
379
+ types.push(outputs[0])
380
+ }
381
+ }
382
+ return types
383
+ })
384
+ .flat()
385
+ .filter((s): s is string => !!s && !PRIMITIVE_TYPES.has(s)),
386
+ ...functions.typesMap.customTypes.keys(),
387
+ ...(schemasFromTypes || []),
388
+ ...Array.from(schemaLookup.keys()),
389
+ ])
390
+ }
391
+
392
+ export function validateAgentModels(
393
+ logger: InspectorLogger,
394
+ state: InspectorState | Omit<InspectorState, 'typesLookup'>,
395
+ modelConfig?: InspectorModelConfig
396
+ ): void {
397
+ const aliases = modelConfig?.models ?? {}
398
+
399
+ for (const [, meta] of Object.entries(state.agents.agentsMeta)) {
400
+ const model = meta.model
401
+ if (!model) {
402
+ logger.critical(
403
+ ErrorCode.MISSING_MODEL,
404
+ `AI agent '${meta.name}' is missing the 'model' property.`
405
+ )
406
+ continue
407
+ }
408
+ if (model.includes('/')) continue
409
+ if (!aliases[model]) {
410
+ const available = Object.keys(aliases)
411
+ logger.critical(
412
+ ErrorCode.INVALID_MODEL,
413
+ `AI agent '${meta.name}' uses model alias '${model}' which is not defined in pikku.config.json models. ` +
414
+ `Available aliases: ${available.join(', ') || 'none'}`
415
+ )
416
+ }
417
+ }
418
+ }
419
+
420
+ export function validateAgentOverrides(
421
+ logger: InspectorLogger,
422
+ state: InspectorState | Omit<InspectorState, 'typesLookup'>,
423
+ modelConfig?: InspectorModelConfig
424
+ ): void {
425
+ const overrides = modelConfig?.agentOverrides ?? {}
426
+ const aliases = modelConfig?.models ?? {}
427
+ const agentNames = new Set(
428
+ Object.values(state.agents.agentsMeta).map((m) => m.name)
429
+ )
430
+
431
+ for (const [agentName, override] of Object.entries(overrides)) {
432
+ if (!agentNames.has(agentName)) {
433
+ logger.warn(`agentOverrides references unknown agent '${agentName}'`)
434
+ }
435
+ if (
436
+ override.model &&
437
+ !override.model.includes('/') &&
438
+ !aliases[override.model]
439
+ ) {
440
+ logger.critical(
441
+ ErrorCode.INVALID_MODEL,
442
+ `agentOverrides['${agentName}'].model uses alias '${override.model}' which is not defined in models.`
443
+ )
444
+ }
445
+ }
446
+ }
447
+
448
+ export function computeDiagnostics(state: InspectorState): void {
449
+ const diagnostics: InspectorDiagnostic[] = []
450
+
451
+ for (const [id, meta] of Object.entries(state.functions.meta)) {
452
+ if (meta.services && !meta.services.optimized) {
453
+ diagnostics.push({
454
+ code: ErrorCode.SERVICES_NOT_DESTRUCTURED,
455
+ message: `Function '${id}' does not destructure its services parameter, preventing tree-shaking optimization.`,
456
+ sourceFile: meta.pikkuFuncId,
457
+ position: 0,
458
+ })
459
+ }
460
+ if (meta.wires && !meta.wires.optimized) {
461
+ diagnostics.push({
462
+ code: ErrorCode.WIRES_NOT_DESTRUCTURED,
463
+ message: `Function '${id}' does not destructure its wires parameter, preventing tree-shaking optimization.`,
464
+ sourceFile: meta.pikkuFuncId,
465
+ position: 0,
466
+ })
467
+ }
468
+ }
469
+
470
+ for (const [id, def] of Object.entries(state.middleware.definitions)) {
471
+ if (def.services && !def.services.optimized) {
472
+ diagnostics.push({
473
+ code: ErrorCode.SERVICES_NOT_DESTRUCTURED,
474
+ message: `Middleware '${id}' does not destructure its services parameter, preventing tree-shaking optimization.`,
475
+ sourceFile: def.sourceFile,
476
+ position: def.position,
477
+ })
478
+ }
479
+ if (def.wires && !def.wires.optimized) {
480
+ diagnostics.push({
481
+ code: ErrorCode.WIRES_NOT_DESTRUCTURED,
482
+ message: `Middleware '${id}' does not destructure its wires parameter, preventing tree-shaking optimization.`,
483
+ sourceFile: def.sourceFile,
484
+ position: def.position,
485
+ })
486
+ }
487
+ }
488
+
489
+ for (const [id, def] of Object.entries(state.permissions.definitions)) {
490
+ if (def.services && !def.services.optimized) {
491
+ diagnostics.push({
492
+ code: ErrorCode.SERVICES_NOT_DESTRUCTURED,
493
+ message: `Permission '${id}' does not destructure its services parameter, preventing tree-shaking optimization.`,
494
+ sourceFile: def.sourceFile,
495
+ position: def.position,
496
+ })
497
+ }
498
+ if (def.wires && !def.wires.optimized) {
499
+ diagnostics.push({
500
+ code: ErrorCode.WIRES_NOT_DESTRUCTURED,
501
+ message: `Permission '${id}' does not destructure its wires parameter, preventing tree-shaking optimization.`,
502
+ sourceFile: def.sourceFile,
503
+ position: def.position,
504
+ })
505
+ }
266
506
  }
267
507
 
268
- state.serviceMetadata = allMetadata
508
+ state.diagnostics = diagnostics
269
509
  }
@@ -0,0 +1,42 @@
1
+ import * as ts from 'typescript'
2
+ import { ExternalPackageConfig } from '../types.js'
3
+
4
+ /**
5
+ * Resolve the external package name from an imported identifier.
6
+ * Checks if the identifier's import module specifier matches any
7
+ * configured external package.
8
+ *
9
+ * This is a general utility — any wire handler that processes a `func`
10
+ * property can use it to detect when the function comes from an
11
+ * external package.
12
+ */
13
+ export const resolveExternalPackageName = (
14
+ identifier: ts.Identifier,
15
+ checker: ts.TypeChecker,
16
+ externalPackages?: Record<string, ExternalPackageConfig>
17
+ ): string | null => {
18
+ if (!externalPackages || Object.keys(externalPackages).length === 0) {
19
+ return null
20
+ }
21
+
22
+ const sym = checker.getSymbolAtLocation(identifier)
23
+ if (!sym) return null
24
+
25
+ const decl = sym.declarations?.[0]
26
+ if (!decl || !ts.isImportSpecifier(decl)) return null
27
+
28
+ // ImportSpecifier -> NamedImports -> ImportClause -> ImportDeclaration
29
+ const importDecl = decl.parent?.parent?.parent
30
+ if (!importDecl || !ts.isImportDeclaration(importDecl)) return null
31
+ if (!ts.isStringLiteral(importDecl.moduleSpecifier)) return null
32
+
33
+ const moduleSpecifier = importDecl.moduleSpecifier.text
34
+
35
+ for (const config of Object.values(externalPackages)) {
36
+ if (config.package === moduleSpecifier) {
37
+ return config.package
38
+ }
39
+ }
40
+
41
+ return null
42
+ }
@@ -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
+ }