@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
@@ -2,8 +2,8 @@ import * as ts from 'typescript';
2
2
  import { PermissionMetadata } from '@pikku/core';
3
3
  import { InspectorState } from '../types.js';
4
4
  /**
5
- * Extract permission pikkuFuncNames from an expression (array or object literal)
6
- * Resolves each identifier to its pikkuFuncName using extractFunctionName
5
+ * Extract permission pikkuFuncIds from an expression (array or object literal)
6
+ * Resolves each identifier to its pikkuFuncId using extractFunctionName
7
7
  * Also handles call expressions (like rolePermission({...}))
8
8
  *
9
9
  * Supports both formats:
@@ -1,8 +1,8 @@
1
1
  import * as ts from 'typescript';
2
2
  import { extractFunctionName } from './extract-function-name.js';
3
3
  /**
4
- * Extract permission pikkuFuncNames from an expression (array or object literal)
5
- * Resolves each identifier to its pikkuFuncName using extractFunctionName
4
+ * Extract permission pikkuFuncIds from an expression (array or object literal)
5
+ * Resolves each identifier to its pikkuFuncId using extractFunctionName
6
6
  * Also handles call expressions (like rolePermission({...}))
7
7
  *
8
8
  * Supports both formats:
@@ -14,14 +14,14 @@ export function extractPermissionPikkuNames(node, checker, rootDir) {
14
14
  // Helper to extract from a single element
15
15
  const extractFromElement = (element) => {
16
16
  if (ts.isIdentifier(element)) {
17
- const { pikkuFuncName } = extractFunctionName(element, checker, rootDir);
18
- names.push(pikkuFuncName);
17
+ const { pikkuFuncId } = extractFunctionName(element, checker, rootDir);
18
+ names.push(pikkuFuncId);
19
19
  }
20
20
  else if (ts.isCallExpression(element)) {
21
21
  // Handle call expressions like hasEmailQuota(100) or rolePermission({...})
22
22
  // Extract the function being called (e.g., 'hasEmailQuota' from 'hasEmailQuota(100)')
23
- const { pikkuFuncName } = extractFunctionName(element.expression, checker, rootDir);
24
- names.push(pikkuFuncName);
23
+ const { pikkuFuncId } = extractFunctionName(element.expression, checker, rootDir);
24
+ names.push(pikkuFuncId);
25
25
  }
26
26
  else if (ts.isArrayLiteralExpression(element)) {
27
27
  // Nested array (for Record values that are arrays)
@@ -107,11 +107,11 @@ export function resolveHTTPPermissions(state, route, tags, explicitPermissionsNo
107
107
  if (explicitPermissionsNode) {
108
108
  const permissionNames = extractPermissionPikkuNames(explicitPermissionsNode, checker, state.rootDir);
109
109
  for (const name of permissionNames) {
110
- const meta = state.permissions.meta[name];
110
+ const def = state.permissions.definitions[name];
111
111
  resolved.push({
112
112
  type: 'wire',
113
113
  name,
114
- inline: meta?.exportedName === null,
114
+ inline: def?.exportedName === null,
115
115
  });
116
116
  }
117
117
  }
@@ -140,11 +140,11 @@ function resolveTagAndExplicitPermissions(state, tags, explicitPermissionsNode,
140
140
  if (explicitPermissionsNode) {
141
141
  const permissionNames = extractPermissionPikkuNames(explicitPermissionsNode, checker, state.rootDir);
142
142
  for (const name of permissionNames) {
143
- const meta = state.permissions.meta[name];
143
+ const def = state.permissions.definitions[name];
144
144
  resolved.push({
145
145
  type: 'wire',
146
146
  name,
147
- inline: meta?.exportedName === null,
147
+ inline: def?.exportedName === null,
148
148
  });
149
149
  }
150
150
  }
@@ -1,5 +1,4 @@
1
- import * as ts from 'typescript';
2
- import { InspectorState } from '../types.js';
1
+ import { InspectorState, InspectorLogger, InspectorOptions, InspectorModelConfig, ExternalPackageConfig } from '../types.js';
3
2
  import { MiddlewareMetadata, PermissionMetadata } from '@pikku/core';
4
3
  /**
5
4
  * Helper to extract wire-level middleware/permission names from metadata.
@@ -15,11 +14,11 @@ export declare function extractWireNames(list?: MiddlewareMetadata[] | Permissio
15
14
  * in the add-* methods during AST traversal for efficiency.
16
15
  */
17
16
  export declare function aggregateRequiredServices(state: InspectorState | Omit<InspectorState, 'typesLookup'>): void;
18
- /**
19
- * Extract service interface metadata for all user-defined services.
20
- * This extracts metadata for services in SingletonServices and Services types
21
- * to generate documentation for AI consumption.
22
- *
23
- * Must be called after aggregateRequiredServices() to ensure types are loaded.
24
- */
25
- export declare function extractServiceInterfaceMetadata(state: InspectorState | Omit<InspectorState, 'typesLookup'>, checker: ts.TypeChecker): void;
17
+ export declare function validateSecretOverrides(logger: InspectorLogger, state: InspectorState | Omit<InspectorState, 'typesLookup'>, externalPackages?: Record<string, ExternalPackageConfig>): void;
18
+ export declare function computeResolvedIOTypes(state: InspectorState): void;
19
+ export declare function computeMiddlewareGroupsMeta(state: InspectorState): void;
20
+ export declare function computePermissionsGroupsMeta(state: InspectorState): void;
21
+ export declare function computeRequiredSchemas(state: InspectorState, options: InspectorOptions): void;
22
+ export declare function validateAgentModels(logger: InspectorLogger, state: InspectorState | Omit<InspectorState, 'typesLookup'>, modelConfig?: InspectorModelConfig): void;
23
+ export declare function validateAgentOverrides(logger: InspectorLogger, state: InspectorState | Omit<InspectorState, 'typesLookup'>, modelConfig?: InspectorModelConfig): void;
24
+ export declare function computeDiagnostics(state: InspectorState): void;
@@ -1,5 +1,5 @@
1
1
  import { extractTypeKeys } from './type-utils.js';
2
- import { extractAllServiceMetadata, } from './extract-service-metadata.js';
2
+ import { ErrorCode } from '../error-codes.js';
3
3
  /**
4
4
  * Helper to extract wire-level middleware/permission names from metadata.
5
5
  * Only extracts type:'wire' variants (individual middleware/permissions).
@@ -99,14 +99,14 @@ export function aggregateRequiredServices(state) {
99
99
  });
100
100
  // 2. Services from used middleware (individual + groups)
101
101
  usedMiddleware.forEach((middlewareName) => {
102
- const middlewareMeta = state.middleware.meta[middlewareName];
102
+ const middlewareMeta = state.middleware.definitions[middlewareName];
103
103
  if (middlewareMeta?.services) {
104
104
  addServices(middlewareMeta.services);
105
105
  }
106
106
  });
107
107
  // 3. Services from used permissions (individual + groups)
108
108
  usedPermissions.forEach((permissionName) => {
109
- const permissionMeta = state.permissions.meta[permissionName];
109
+ const permissionMeta = state.permissions.definitions[permissionName];
110
110
  if (permissionMeta?.services) {
111
111
  addServices(permissionMeta.services);
112
112
  }
@@ -161,29 +161,236 @@ export function aggregateRequiredServices(state) {
161
161
  });
162
162
  }
163
163
  }
164
- /**
165
- * Extract service interface metadata for all user-defined services.
166
- * This extracts metadata for services in SingletonServices and Services types
167
- * to generate documentation for AI consumption.
168
- *
169
- * Must be called after aggregateRequiredServices() to ensure types are loaded.
170
- */
171
- export function extractServiceInterfaceMetadata(state, checker) {
172
- if (!('typesLookup' in state)) {
164
+ export function validateSecretOverrides(logger, state, externalPackages) {
165
+ if (!externalPackages)
173
166
  return;
167
+ const secretNames = new Set(state.secrets.definitions.map((d) => d.name));
168
+ for (const [namespace, pkgConfig] of Object.entries(externalPackages)) {
169
+ if (!pkgConfig.secretOverrides)
170
+ continue;
171
+ for (const secretKey of Object.keys(pkgConfig.secretOverrides)) {
172
+ if (!secretNames.has(secretKey)) {
173
+ const availableSecrets = Array.from(secretNames);
174
+ logger.critical(ErrorCode.INVALID_VALUE, `Secret override '${secretKey}' in external package '${namespace}' (${pkgConfig.package}) does not exist. Available secrets: ${availableSecrets.join(', ') || 'none'}`);
175
+ }
176
+ }
174
177
  }
175
- const allMetadata = [];
176
- const singletonServicesTypes = state.typesLookup.get('SingletonServices');
177
- if (singletonServicesTypes && singletonServicesTypes.length > 0) {
178
- const singletonMeta = extractAllServiceMetadata(singletonServicesTypes[0], checker, state.rootDir);
179
- allMetadata.push(...singletonMeta);
178
+ }
179
+ export function computeResolvedIOTypes(state) {
180
+ const { functions } = state;
181
+ for (const [pikkuFuncId, meta] of Object.entries(functions.meta)) {
182
+ const input = meta.inputs?.[0];
183
+ const output = meta.outputs?.[0];
184
+ let inputType = 'null';
185
+ if (input) {
186
+ try {
187
+ inputType = functions.typesMap.getTypeMeta(input).uniqueName;
188
+ }
189
+ catch {
190
+ inputType = input;
191
+ }
192
+ }
193
+ let outputType = 'null';
194
+ if (output) {
195
+ try {
196
+ outputType = functions.typesMap.getTypeMeta(output).uniqueName;
197
+ }
198
+ catch {
199
+ outputType = output;
200
+ }
201
+ }
202
+ state.resolvedIOTypes[pikkuFuncId] = { inputType, outputType };
180
203
  }
181
- const servicesTypes = state.typesLookup.get('Services');
182
- if (servicesTypes && servicesTypes.length > 0) {
183
- const wireServicesMeta = extractAllServiceMetadata(servicesTypes[0], checker, state.rootDir);
184
- const singletonNames = new Set(state.serviceAggregation.allSingletonServices);
185
- const uniqueWireServices = wireServicesMeta.filter((meta) => !singletonNames.has(meta.name));
186
- allMetadata.push(...uniqueWireServices);
204
+ }
205
+ const serializeGroupMap = (groupMap) => {
206
+ const result = {};
207
+ for (const [key, meta] of groupMap.entries()) {
208
+ result[key] = {
209
+ exportName: meta.exportName,
210
+ sourceFile: meta.sourceFile,
211
+ position: meta.position,
212
+ services: meta.services,
213
+ count: meta.count,
214
+ instanceIds: meta.instanceIds,
215
+ isFactory: meta.isFactory,
216
+ };
217
+ }
218
+ return result;
219
+ };
220
+ export function computeMiddlewareGroupsMeta(state) {
221
+ state.middlewareGroupsMeta = {
222
+ definitions: state.middleware.definitions,
223
+ instances: state.middleware.instances,
224
+ httpGroups: serializeGroupMap(state.http.routeMiddleware),
225
+ tagGroups: serializeGroupMap(state.middleware.tagMiddleware),
226
+ channelMiddleware: {
227
+ definitions: state.channelMiddleware.definitions,
228
+ instances: state.channelMiddleware.instances,
229
+ tagGroups: serializeGroupMap(state.channelMiddleware.tagMiddleware),
230
+ },
231
+ };
232
+ }
233
+ export function computePermissionsGroupsMeta(state) {
234
+ const httpGroups = {};
235
+ for (const [pattern, meta] of state.http.routePermissions.entries()) {
236
+ httpGroups[pattern] = {
237
+ exportName: meta.exportName,
238
+ sourceFile: meta.sourceFile,
239
+ position: meta.position,
240
+ services: meta.services,
241
+ count: meta.count,
242
+ instanceIds: meta.instanceIds,
243
+ isFactory: meta.isFactory,
244
+ };
245
+ }
246
+ const tagGroups = {};
247
+ for (const [tag, meta] of state.permissions.tagPermissions.entries()) {
248
+ tagGroups[tag] = {
249
+ exportName: meta.exportName,
250
+ sourceFile: meta.sourceFile,
251
+ position: meta.position,
252
+ services: meta.services,
253
+ count: meta.count,
254
+ instanceIds: meta.instanceIds,
255
+ isFactory: meta.isFactory,
256
+ };
257
+ }
258
+ state.permissionsGroupsMeta = {
259
+ definitions: state.permissions.definitions,
260
+ httpGroups,
261
+ tagGroups,
262
+ };
263
+ }
264
+ const PRIMITIVE_TYPES = new Set([
265
+ 'boolean',
266
+ 'string',
267
+ 'number',
268
+ 'null',
269
+ 'undefined',
270
+ 'void',
271
+ 'any',
272
+ 'unknown',
273
+ 'never',
274
+ ]);
275
+ export function computeRequiredSchemas(state, options) {
276
+ const { functions, schemaLookup } = state;
277
+ const schemasFromTypes = options.schemaConfig?.schemasFromTypes;
278
+ state.requiredSchemas = new Set([
279
+ ...Object.values(functions.meta)
280
+ .map(({ inputs, outputs }) => {
281
+ const types = [];
282
+ if (inputs?.[0]) {
283
+ try {
284
+ types.push(functions.typesMap.getUniqueName(inputs[0]));
285
+ }
286
+ catch {
287
+ types.push(inputs[0]);
288
+ }
289
+ }
290
+ if (outputs?.[0]) {
291
+ try {
292
+ types.push(functions.typesMap.getUniqueName(outputs[0]));
293
+ }
294
+ catch {
295
+ types.push(outputs[0]);
296
+ }
297
+ }
298
+ return types;
299
+ })
300
+ .flat()
301
+ .filter((s) => !!s && !PRIMITIVE_TYPES.has(s)),
302
+ ...functions.typesMap.customTypes.keys(),
303
+ ...(schemasFromTypes || []),
304
+ ...Array.from(schemaLookup.keys()),
305
+ ]);
306
+ }
307
+ export function validateAgentModels(logger, state, modelConfig) {
308
+ const aliases = modelConfig?.models ?? {};
309
+ for (const [, meta] of Object.entries(state.agents.agentsMeta)) {
310
+ const model = meta.model;
311
+ if (!model) {
312
+ logger.critical(ErrorCode.MISSING_MODEL, `AI agent '${meta.name}' is missing the 'model' property.`);
313
+ continue;
314
+ }
315
+ if (model.includes('/'))
316
+ continue;
317
+ if (!aliases[model]) {
318
+ const available = Object.keys(aliases);
319
+ logger.critical(ErrorCode.INVALID_MODEL, `AI agent '${meta.name}' uses model alias '${model}' which is not defined in pikku.config.json models. ` +
320
+ `Available aliases: ${available.join(', ') || 'none'}`);
321
+ }
322
+ }
323
+ }
324
+ export function validateAgentOverrides(logger, state, modelConfig) {
325
+ const overrides = modelConfig?.agentOverrides ?? {};
326
+ const aliases = modelConfig?.models ?? {};
327
+ const agentNames = new Set(Object.values(state.agents.agentsMeta).map((m) => m.name));
328
+ for (const [agentName, override] of Object.entries(overrides)) {
329
+ if (!agentNames.has(agentName)) {
330
+ logger.warn(`agentOverrides references unknown agent '${agentName}'`);
331
+ }
332
+ if (override.model &&
333
+ !override.model.includes('/') &&
334
+ !aliases[override.model]) {
335
+ logger.critical(ErrorCode.INVALID_MODEL, `agentOverrides['${agentName}'].model uses alias '${override.model}' which is not defined in models.`);
336
+ }
337
+ }
338
+ }
339
+ export function computeDiagnostics(state) {
340
+ const diagnostics = [];
341
+ for (const [id, meta] of Object.entries(state.functions.meta)) {
342
+ if (meta.services && !meta.services.optimized) {
343
+ diagnostics.push({
344
+ code: ErrorCode.SERVICES_NOT_DESTRUCTURED,
345
+ message: `Function '${id}' does not destructure its services parameter, preventing tree-shaking optimization.`,
346
+ sourceFile: meta.pikkuFuncId,
347
+ position: 0,
348
+ });
349
+ }
350
+ if (meta.wires && !meta.wires.optimized) {
351
+ diagnostics.push({
352
+ code: ErrorCode.WIRES_NOT_DESTRUCTURED,
353
+ message: `Function '${id}' does not destructure its wires parameter, preventing tree-shaking optimization.`,
354
+ sourceFile: meta.pikkuFuncId,
355
+ position: 0,
356
+ });
357
+ }
358
+ }
359
+ for (const [id, def] of Object.entries(state.middleware.definitions)) {
360
+ if (def.services && !def.services.optimized) {
361
+ diagnostics.push({
362
+ code: ErrorCode.SERVICES_NOT_DESTRUCTURED,
363
+ message: `Middleware '${id}' does not destructure its services parameter, preventing tree-shaking optimization.`,
364
+ sourceFile: def.sourceFile,
365
+ position: def.position,
366
+ });
367
+ }
368
+ if (def.wires && !def.wires.optimized) {
369
+ diagnostics.push({
370
+ code: ErrorCode.WIRES_NOT_DESTRUCTURED,
371
+ message: `Middleware '${id}' does not destructure its wires parameter, preventing tree-shaking optimization.`,
372
+ sourceFile: def.sourceFile,
373
+ position: def.position,
374
+ });
375
+ }
376
+ }
377
+ for (const [id, def] of Object.entries(state.permissions.definitions)) {
378
+ if (def.services && !def.services.optimized) {
379
+ diagnostics.push({
380
+ code: ErrorCode.SERVICES_NOT_DESTRUCTURED,
381
+ message: `Permission '${id}' does not destructure its services parameter, preventing tree-shaking optimization.`,
382
+ sourceFile: def.sourceFile,
383
+ position: def.position,
384
+ });
385
+ }
386
+ if (def.wires && !def.wires.optimized) {
387
+ diagnostics.push({
388
+ code: ErrorCode.WIRES_NOT_DESTRUCTURED,
389
+ message: `Permission '${id}' does not destructure its wires parameter, preventing tree-shaking optimization.`,
390
+ sourceFile: def.sourceFile,
391
+ position: def.position,
392
+ });
393
+ }
187
394
  }
188
- state.serviceMetadata = allMetadata;
395
+ state.diagnostics = diagnostics;
189
396
  }
@@ -0,0 +1,12 @@
1
+ import * as ts from 'typescript';
2
+ import { ExternalPackageConfig } from '../types.js';
3
+ /**
4
+ * Resolve the external package name from an imported identifier.
5
+ * Checks if the identifier's import module specifier matches any
6
+ * configured external 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
+ * external package.
11
+ */
12
+ export declare const resolveExternalPackageName: (identifier: ts.Identifier, checker: ts.TypeChecker, externalPackages?: Record<string, ExternalPackageConfig>) => string | null;
@@ -0,0 +1,34 @@
1
+ import * as ts from 'typescript';
2
+ /**
3
+ * Resolve the external package name from an imported identifier.
4
+ * Checks if the identifier's import module specifier matches any
5
+ * configured external package.
6
+ *
7
+ * This is a general utility — any wire handler that processes a `func`
8
+ * property can use it to detect when the function comes from an
9
+ * external package.
10
+ */
11
+ export const resolveExternalPackageName = (identifier, checker, externalPackages) => {
12
+ if (!externalPackages || Object.keys(externalPackages).length === 0) {
13
+ return null;
14
+ }
15
+ const sym = checker.getSymbolAtLocation(identifier);
16
+ if (!sym)
17
+ return null;
18
+ const decl = sym.declarations?.[0];
19
+ if (!decl || !ts.isImportSpecifier(decl))
20
+ return null;
21
+ // ImportSpecifier -> NamedImports -> ImportClause -> ImportDeclaration
22
+ const importDecl = decl.parent?.parent?.parent;
23
+ if (!importDecl || !ts.isImportDeclaration(importDecl))
24
+ return null;
25
+ if (!ts.isStringLiteral(importDecl.moduleSpecifier))
26
+ return null;
27
+ const moduleSpecifier = importDecl.moduleSpecifier.text;
28
+ for (const config of Object.values(externalPackages)) {
29
+ if (config.package === moduleSpecifier) {
30
+ return config.package;
31
+ }
32
+ }
33
+ return null;
34
+ };
@@ -0,0 +1,6 @@
1
+ import type { TypesMap } from '../types-map.js';
2
+ import type { FunctionsMeta } from '@pikku/core';
3
+ export declare function resolveFunctionIOTypes(pikkuFuncId: string, functionsMeta: FunctionsMeta, typesMap: TypesMap, requiredTypes: Set<string>): {
4
+ inputType: string;
5
+ outputType: string;
6
+ };
@@ -0,0 +1,29 @@
1
+ export function resolveFunctionIOTypes(pikkuFuncId, functionsMeta, typesMap, requiredTypes) {
2
+ const functionMeta = functionsMeta[pikkuFuncId];
3
+ if (!functionMeta) {
4
+ throw new Error(`Function ${pikkuFuncId} not found in functionsMeta. Please check your configuration.`);
5
+ }
6
+ const input = functionMeta.inputs ? functionMeta.inputs[0] : undefined;
7
+ const output = functionMeta.outputs ? functionMeta.outputs[0] : undefined;
8
+ let inputType = 'null';
9
+ if (input) {
10
+ try {
11
+ inputType = typesMap.getTypeMeta(input).uniqueName;
12
+ }
13
+ catch {
14
+ inputType = input;
15
+ }
16
+ }
17
+ let outputType = 'null';
18
+ if (output) {
19
+ try {
20
+ outputType = typesMap.getTypeMeta(output).uniqueName;
21
+ }
22
+ catch {
23
+ outputType = output;
24
+ }
25
+ }
26
+ requiredTypes.add(inputType);
27
+ requiredTypes.add(outputType);
28
+ return { inputType, outputType };
29
+ }
@@ -0,0 +1,10 @@
1
+ import * as ts from 'typescript';
2
+ /**
3
+ * Resolve an identifier to its definition, optionally unwrapping
4
+ * known "define" wrapper functions (e.g. defineHTTPRoutes, defineCLICommands).
5
+ *
6
+ * When the identifier resolves to a variable whose initializer is a call
7
+ * to one of the `unwrapFunctionNames`, the first argument of that call
8
+ * is returned instead.
9
+ */
10
+ export declare function resolveIdentifier(node: ts.Identifier, checker: ts.TypeChecker, unwrapFunctionNames?: string[]): ts.Node | undefined;
@@ -0,0 +1,36 @@
1
+ import * as ts from 'typescript';
2
+ /**
3
+ * Resolve an identifier to its definition, optionally unwrapping
4
+ * known "define" wrapper functions (e.g. defineHTTPRoutes, defineCLICommands).
5
+ *
6
+ * When the identifier resolves to a variable whose initializer is a call
7
+ * to one of the `unwrapFunctionNames`, the first argument of that call
8
+ * is returned instead.
9
+ */
10
+ export function resolveIdentifier(node, checker, unwrapFunctionNames) {
11
+ const symbol = checker.getSymbolAtLocation(node);
12
+ if (!symbol)
13
+ return undefined;
14
+ // Handle aliased symbols (imports)
15
+ let resolvedSymbol = symbol;
16
+ if (resolvedSymbol.flags & ts.SymbolFlags.Alias) {
17
+ resolvedSymbol = checker.getAliasedSymbol(resolvedSymbol) ?? resolvedSymbol;
18
+ }
19
+ const decl = resolvedSymbol.valueDeclaration || resolvedSymbol.declarations?.[0];
20
+ if (!decl)
21
+ return undefined;
22
+ // Follow to the actual value (handles imports, variable declarations)
23
+ if (ts.isVariableDeclaration(decl) && decl.initializer) {
24
+ // Check if it's a call to one of the unwrap functions
25
+ if (ts.isCallExpression(decl.initializer) &&
26
+ unwrapFunctionNames &&
27
+ unwrapFunctionNames.length > 0) {
28
+ const expr = decl.initializer.expression;
29
+ if (ts.isIdentifier(expr) && unwrapFunctionNames.includes(expr.text)) {
30
+ return decl.initializer.arguments[0];
31
+ }
32
+ }
33
+ return decl.initializer;
34
+ }
35
+ return undefined;
36
+ }
@@ -0,0 +1,2 @@
1
+ import type { InspectorState, InspectorLogger } from '../types.js';
2
+ export declare function resolveLatestVersions(state: InspectorState, logger: InspectorLogger): void;
@@ -0,0 +1,78 @@
1
+ import { parseVersionedId, formatVersionedId } from '@pikku/core';
2
+ import { ErrorCode } from '../error-codes.js';
3
+ export function resolveLatestVersions(state, logger) {
4
+ const functionsMeta = state.functions.meta;
5
+ const groups = new Map();
6
+ for (const [id, meta] of Object.entries(functionsMeta)) {
7
+ const parsed = parseVersionedId(id);
8
+ const baseName = parsed.version !== null ? parsed.baseName : id;
9
+ let group = groups.get(baseName);
10
+ if (!group) {
11
+ group = { explicit: [], unversioned: null };
12
+ groups.set(baseName, group);
13
+ }
14
+ if (parsed.version !== null) {
15
+ group.explicit.push({ id, version: parsed.version });
16
+ }
17
+ else if (meta.version !== undefined) {
18
+ group.explicit.push({ id, version: meta.version });
19
+ }
20
+ else {
21
+ group.unversioned = id;
22
+ }
23
+ }
24
+ for (const [baseName, group] of groups) {
25
+ if (group.explicit.length === 0) {
26
+ continue;
27
+ }
28
+ const seen = new Map();
29
+ for (const entry of group.explicit) {
30
+ const existing = seen.get(entry.version);
31
+ if (existing) {
32
+ logger.critical(ErrorCode.DUPLICATE_FUNCTION_VERSION, `Duplicate version ${entry.version} for function '${baseName}': '${existing}' and '${entry.id}'.`);
33
+ }
34
+ seen.set(entry.version, entry.id);
35
+ }
36
+ const maxVersion = Math.max(...group.explicit.map((e) => e.version));
37
+ if (group.unversioned) {
38
+ const implicitVersion = maxVersion + 1;
39
+ const oldId = group.unversioned;
40
+ const newId = formatVersionedId(baseName, implicitVersion);
41
+ const meta = functionsMeta[oldId];
42
+ meta.pikkuFuncId = newId;
43
+ meta.version = implicitVersion;
44
+ delete functionsMeta[oldId];
45
+ functionsMeta[newId] = meta;
46
+ const fileEntry = state.functions.files.get(oldId);
47
+ if (fileEntry) {
48
+ state.functions.files.delete(oldId);
49
+ state.functions.files.set(newId, fileEntry);
50
+ }
51
+ const rpcFileEntry = state.rpc.internalFiles.get(oldId);
52
+ if (rpcFileEntry) {
53
+ state.rpc.internalFiles.delete(oldId);
54
+ state.rpc.internalFiles.set(newId, rpcFileEntry);
55
+ }
56
+ if (state.rpc.invokedFunctions.has(oldId)) {
57
+ state.rpc.invokedFunctions.delete(oldId);
58
+ state.rpc.invokedFunctions.add(newId);
59
+ }
60
+ if (state.serviceAggregation.usedFunctions.has(oldId)) {
61
+ state.serviceAggregation.usedFunctions.delete(oldId);
62
+ state.serviceAggregation.usedFunctions.add(newId);
63
+ }
64
+ state.rpc.internalMeta[baseName] = newId;
65
+ state.rpc.internalMeta[newId] = newId;
66
+ if (state.rpc.exposedMeta[baseName] === oldId) {
67
+ state.rpc.exposedMeta[baseName] = newId;
68
+ }
69
+ }
70
+ else {
71
+ const latest = group.explicit.reduce((a, b) => a.version > b.version ? a : b);
72
+ state.rpc.internalMeta[baseName] = latest.id;
73
+ }
74
+ for (const entry of group.explicit) {
75
+ state.rpc.invokedFunctions.add(entry.id);
76
+ }
77
+ }
78
+ }
@@ -0,0 +1,9 @@
1
+ import { JSONValue } from '@pikku/core';
2
+ import { InspectorLogger, InspectorState } from '../types.js';
3
+ export declare function generateAllSchemas(logger: InspectorLogger, config: {
4
+ tsconfig: string;
5
+ schemasFromTypes?: string[];
6
+ schema?: {
7
+ additionalProperties?: boolean;
8
+ };
9
+ }, state: InspectorState): Promise<Record<string, JSONValue>>;