@pikku/inspector 0.12.11 → 0.12.12

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 (61) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/dist/add/add-cli.js +10 -3
  3. package/dist/add/add-credential.js +2 -1
  4. package/dist/add/add-functions.js +28 -1
  5. package/dist/add/add-http-route.js +24 -5
  6. package/dist/add/add-keyed-wiring.js +3 -1
  7. package/dist/add/add-middleware.js +33 -4
  8. package/dist/add/add-permission.js +7 -7
  9. package/dist/add/add-workflow-graph.js +20 -1
  10. package/dist/error-codes.d.ts +1 -0
  11. package/dist/error-codes.js +1 -0
  12. package/dist/index.d.ts +1 -0
  13. package/dist/index.js +1 -0
  14. package/dist/inspector.js +2 -5
  15. package/dist/types.d.ts +10 -19
  16. package/dist/utils/extract-function-name.js +6 -0
  17. package/dist/utils/filter-inspector-state.js +187 -59
  18. package/dist/utils/filter-utils.js +13 -5
  19. package/dist/utils/get-property-value.d.ts +10 -0
  20. package/dist/utils/get-property-value.js +30 -0
  21. package/dist/utils/post-process.d.ts +2 -3
  22. package/dist/utils/post-process.js +3 -23
  23. package/dist/utils/resolve-addon-package.d.ts +4 -5
  24. package/dist/utils/resolve-addon-package.js +64 -16
  25. package/dist/utils/resolve-deploy-target.d.ts +28 -0
  26. package/dist/utils/resolve-deploy-target.js +56 -0
  27. package/dist/utils/resolve-versions.js +79 -0
  28. package/dist/utils/schema-generator.js +31 -12
  29. package/package.json +2 -2
  30. package/src/add/add-cli.ts +10 -3
  31. package/src/add/add-credential.ts +3 -0
  32. package/src/add/add-functions.test.ts +149 -0
  33. package/src/add/add-functions.ts +37 -1
  34. package/src/add/add-gateway.ts +5 -1
  35. package/src/add/add-http-route.ts +26 -6
  36. package/src/add/add-keyed-wiring.ts +7 -1
  37. package/src/add/add-mcp-prompt.ts +5 -1
  38. package/src/add/add-mcp-resource.ts +5 -1
  39. package/src/add/add-middleware.ts +42 -4
  40. package/src/add/add-permission.ts +7 -7
  41. package/src/add/add-schedule.ts +5 -1
  42. package/src/add/add-workflow-graph.ts +19 -1
  43. package/src/add/wire-name-literal.test.ts +114 -0
  44. package/src/error-codes.ts +1 -0
  45. package/src/index.ts +1 -0
  46. package/src/inspector.ts +1 -5
  47. package/src/types.ts +19 -15
  48. package/src/utils/extract-function-name.ts +8 -0
  49. package/src/utils/filter-inspector-state.test.ts +168 -64
  50. package/src/utils/filter-inspector-state.ts +290 -64
  51. package/src/utils/filter-utils.test.ts +30 -15
  52. package/src/utils/filter-utils.ts +14 -5
  53. package/src/utils/get-property-value.ts +40 -0
  54. package/src/utils/post-process.ts +3 -38
  55. package/src/utils/resolve-addon-package.ts +65 -14
  56. package/src/utils/resolve-deploy-target.test.ts +105 -0
  57. package/src/utils/resolve-deploy-target.ts +63 -0
  58. package/src/utils/resolve-versions.test.ts +108 -0
  59. package/src/utils/resolve-versions.ts +86 -0
  60. package/src/utils/schema-generator.ts +37 -13
  61. package/tsconfig.tsbuildinfo +1 -1
@@ -70,7 +70,11 @@ export const addGateway: AddWiring = (
70
70
  }
71
71
 
72
72
  const packageName = ts.isIdentifier(funcInitializer)
73
- ? resolveAddonName(funcInitializer, checker, state.rpc.wireAddonDeclarations)
73
+ ? resolveAddonName(
74
+ funcInitializer,
75
+ checker,
76
+ state.rpc.wireAddonDeclarations
77
+ )
74
78
  : null
75
79
 
76
80
  if (!nameValue || !typeValue) {
@@ -273,12 +273,32 @@ export function registerHTTPRoute({
273
273
 
274
274
  const input = fnMeta.inputs?.[0] || null
275
275
 
276
- // Validate that route params and query params exist in function input type
277
- if (params.length > 0 || query.length > 0) {
278
- const inputTypes = state.typesLookup.get(funcName)
276
+ const getRouteInputKeys = (): string[] | null => {
277
+ const targetFuncName = refAddonTarget ?? funcName
278
+ const inputTypes = state.typesLookup.get(targetFuncName)
279
279
  if (inputTypes && inputTypes.length > 0) {
280
- const inputKeys = extractTypeKeys(inputTypes[0])
280
+ return extractTypeKeys(inputTypes[0])
281
+ }
282
+
283
+ const targetMeta = resolveFunctionMeta(state, targetFuncName)
284
+ if (targetMeta?.inputSchemaName) {
285
+ const schema = state.schemas[targetMeta.inputSchemaName] as any
286
+ const properties = schema?.properties
287
+ if (properties && typeof properties === 'object') {
288
+ return Object.keys(properties)
289
+ }
290
+ }
281
291
 
292
+ return null
293
+ }
294
+
295
+ // Validate that route params and query params exist in function input type
296
+ if (params.length > 0 || query.length > 0) {
297
+ const inputKeys = getRouteInputKeys()
298
+ if (!inputKeys) {
299
+ // Input shape isn't inspectable at this phase (e.g. addon ref or opaque handler).
300
+ // Skip param/query validation rather than emitting a false positive.
301
+ } else {
282
302
  // Check path params
283
303
  if (params.length > 0) {
284
304
  const missingParams = params.filter((p) => !inputKeys.includes(p))
@@ -286,7 +306,7 @@ export function registerHTTPRoute({
286
306
  logger.critical(
287
307
  ErrorCode.ROUTE_PARAM_MISMATCH,
288
308
  `Route '${fullRoute}' has path parameter(s) [${missingParams.join(', ')}] ` +
289
- `not found in function '${funcName}' input type. ` +
309
+ `not found in function '${refAddonTarget ?? funcName}' input type. ` +
290
310
  `Input type has: [${inputKeys.join(', ')}]`
291
311
  )
292
312
  return
@@ -300,7 +320,7 @@ export function registerHTTPRoute({
300
320
  logger.critical(
301
321
  ErrorCode.ROUTE_QUERY_MISMATCH,
302
322
  `Route '${fullRoute}' has query parameter(s) [${missingQuery.join(', ')}] ` +
303
- `not found in function '${funcName}' input type. ` +
323
+ `not found in function '${refAddonTarget ?? funcName}' input type. ` +
304
324
  `Input type has: [${inputKeys.join(', ')}]`
305
325
  )
306
326
  return
@@ -1,5 +1,8 @@
1
1
  import * as ts from 'typescript'
2
- import { getPropertyValue } from '../utils/get-property-value.js'
2
+ import {
3
+ getPropertyValue,
4
+ assertStringLiteralProperty,
5
+ } from '../utils/get-property-value.js'
3
6
  import type { AddWiring, InspectorState } from '../types.js'
4
7
  import { ErrorCode } from '../error-codes.js'
5
8
  import { detectSchemaVendorOrError } from '../utils/detect-schema-vendor.js'
@@ -39,6 +42,9 @@ export const createAddKeyedWiring = (config: KeyedWiringConfig): AddWiring => {
39
42
  if (ts.isObjectLiteralExpression(firstArg)) {
40
43
  const obj = firstArg
41
44
 
45
+ assertStringLiteralProperty(obj, 'name', config.label, logger)
46
+ assertStringLiteralProperty(obj, config.idField, config.label, logger)
47
+
42
48
  const nameValue = getPropertyValue(obj, 'name') as string | null
43
49
  const displayNameValue = getPropertyValue(obj, 'displayName') as
44
50
  | string
@@ -74,7 +74,11 @@ export const addMCPPrompt: AddWiring = (
74
74
  }
75
75
 
76
76
  const packageName = ts.isIdentifier(funcInitializer)
77
- ? resolveAddonName(funcInitializer, checker, state.rpc.wireAddonDeclarations)
77
+ ? resolveAddonName(
78
+ funcInitializer,
79
+ checker,
80
+ state.rpc.wireAddonDeclarations
81
+ )
78
82
  : null
79
83
 
80
84
  ensureFunctionMetadata(
@@ -83,7 +83,11 @@ export const addMCPResource: AddWiring = (
83
83
  }
84
84
 
85
85
  const packageName = ts.isIdentifier(funcInitializer)
86
- ? resolveAddonName(funcInitializer, checker, state.rpc.wireAddonDeclarations)
86
+ ? resolveAddonName(
87
+ funcInitializer,
88
+ checker,
89
+ state.rpc.wireAddonDeclarations
90
+ )
87
91
  : null
88
92
 
89
93
  ensureFunctionMetadata(
@@ -245,7 +245,7 @@ export const addMiddleware: AddWiring = (logger, node, checker, state) => {
245
245
  return
246
246
  }
247
247
 
248
- if (expression.text === 'addMiddleware') {
248
+ if (expression.text === 'addTagMiddleware') {
249
249
  const tagArg = args[0]
250
250
  const middlewareArrayArg = args[1]
251
251
 
@@ -257,13 +257,13 @@ export const addMiddleware: AddWiring = (logger, node, checker, state) => {
257
257
  }
258
258
 
259
259
  if (!tag) {
260
- logger.warn(`• addMiddleware call without valid tag string`)
260
+ logger.warn(`• addTagMiddleware call without valid tag string`)
261
261
  return
262
262
  }
263
263
 
264
264
  if (!ts.isArrayLiteralExpression(middlewareArrayArg)) {
265
265
  logger.error(
266
- `• addMiddleware('${tag}', ...) must have a literal array as second argument`
266
+ `• addTagMiddleware('${tag}', ...) must have a literal array as second argument`
267
267
  )
268
268
  return
269
269
  }
@@ -329,7 +329,7 @@ export const addMiddleware: AddWiring = (logger, node, checker, state) => {
329
329
  if (!isFactory && exportedName) {
330
330
  logger.warn(
331
331
  `• Middleware group '${exportedName}' for tag '${tag}' is not wrapped in a factory function. ` +
332
- `For tree-shaking, use: export const ${exportedName} = () => addMiddleware('${tag}', [...])`
332
+ `For tree-shaking, use: export const ${exportedName} = () => addTagMiddleware('${tag}', [...])`
333
333
  )
334
334
  }
335
335
 
@@ -352,6 +352,43 @@ export const addMiddleware: AddWiring = (logger, node, checker, state) => {
352
352
  return
353
353
  }
354
354
 
355
+ if (expression.text === 'addGlobalMiddleware') {
356
+ const middlewareArrayArg = args[0]
357
+ if (
358
+ !middlewareArrayArg ||
359
+ !ts.isArrayLiteralExpression(middlewareArrayArg)
360
+ ) {
361
+ logger.error(
362
+ `• addGlobalMiddleware(...) must have a literal array as its only argument`
363
+ )
364
+ return
365
+ }
366
+ const refs = extractMiddlewareRefs(
367
+ middlewareArrayArg,
368
+ checker,
369
+ state.rootDir
370
+ )
371
+ const definitionIds = refs.map((r) => r.definitionId)
372
+ if (definitionIds.length > 0) {
373
+ renameTempDefinitions(state, definitionIds, 'global', 'middleware')
374
+ }
375
+ const sourceFile = node.getSourceFile().fileName
376
+ for (let i = 0; i < refs.length; i++) {
377
+ const instanceId = makeContextBasedId('global', 'middleware', String(i))
378
+ state.middleware.instances[instanceId] = {
379
+ definitionId: definitionIds[i],
380
+ sourceFile,
381
+ position: node.getStart(),
382
+ isFactoryCall: refs[i].isFactoryCall,
383
+ }
384
+ }
385
+ // Without this, bootstrap codegen's "import every file with a wire-call"
386
+ // pass skips middleware-only files and the registration never runs.
387
+ state.http.files.add(sourceFile)
388
+ logger.debug(`• Found global middleware group with ${refs.length} entries`)
389
+ return
390
+ }
391
+
355
392
  if (expression.text === 'addHTTPMiddleware') {
356
393
  const patternArg = args[0]
357
394
  const middlewareArrayArg = args[1]
@@ -452,6 +489,7 @@ export const addMiddleware: AddWiring = (logger, node, checker, state) => {
452
489
  instanceIds,
453
490
  isFactory,
454
491
  })
492
+ state.http.files.add(sourceFile)
455
493
 
456
494
  logger.debug(
457
495
  `• Found HTTP route middleware group: ${pattern} -> [${instanceIds.join(', ')}] (${isFactory ? 'factory' : 'direct'})`
@@ -45,7 +45,7 @@ function isInsidePermissionContainer(node: ts.Node): boolean {
45
45
  ts.isCallExpression(current) &&
46
46
  ts.isIdentifier(current.expression) &&
47
47
  (current.expression.text === 'pikkuPermissionFactory' ||
48
- current.expression.text === 'addPermission' ||
48
+ current.expression.text === 'addTagPermission' ||
49
49
  current.expression.text === 'addHTTPPermission')
50
50
  ) {
51
51
  return true
@@ -338,9 +338,9 @@ export const addPermission: AddWiring = (logger, node, checker, state) => {
338
338
 
339
339
  // Handle addPermission('tag', [permission1, permission2])
340
340
  // Supports two patterns:
341
- // 1. export const x = () => addPermission('tag', [...]) (factory - tree-shakeable)
342
- // 2. export const x = addPermission('tag', [...]) (direct - no tree-shaking)
343
- if (expression.text === 'addPermission') {
341
+ // 1. export const x = () => addTagPermission('tag', [...]) (factory - tree-shakeable)
342
+ // 2. export const x = addTagPermission('tag', [...]) (direct - no tree-shaking)
343
+ if (expression.text === 'addTagPermission') {
344
344
  const tagArg = args[0]
345
345
  const permissionsArrayArg = args[1]
346
346
 
@@ -353,7 +353,7 @@ export const addPermission: AddWiring = (logger, node, checker, state) => {
353
353
  }
354
354
 
355
355
  if (!tag) {
356
- logger.warn(`• addPermission call without valid tag string`)
356
+ logger.warn(`• addTagPermission call without valid tag string`)
357
357
  return
358
358
  }
359
359
 
@@ -363,7 +363,7 @@ export const addPermission: AddWiring = (logger, node, checker, state) => {
363
363
  !ts.isObjectLiteralExpression(permissionsArrayArg)
364
364
  ) {
365
365
  logger.error(
366
- `• addPermission('${tag}', ...) must have a literal array or object as second argument`
366
+ `• addTagPermission('${tag}', ...) must have a literal array or object as second argument`
367
367
  )
368
368
  return
369
369
  }
@@ -416,7 +416,7 @@ export const addPermission: AddWiring = (logger, node, checker, state) => {
416
416
  if (!isFactory && exportedName) {
417
417
  logger.warn(
418
418
  `• Permission group '${exportedName}' for tag '${tag}' is not wrapped in a factory function. ` +
419
- `For tree-shaking, use: export const ${exportedName} = () => addPermission('${tag}', [...])`
419
+ `For tree-shaking, use: export const ${exportedName} = () => addTagPermission('${tag}', [...])`
420
420
  )
421
421
  }
422
422
 
@@ -73,7 +73,11 @@ export const addSchedule: AddWiring = (
73
73
  }
74
74
 
75
75
  const packageName = ts.isIdentifier(funcInitializer)
76
- ? resolveAddonName(funcInitializer, checker, state.rpc.wireAddonDeclarations)
76
+ ? resolveAddonName(
77
+ funcInitializer,
78
+ checker,
79
+ state.rpc.wireAddonDeclarations
80
+ )
77
81
  : null
78
82
 
79
83
  if (!nameValue || !scheduleValue) {
@@ -357,6 +357,8 @@ function extractGraphFromNewFormat(
357
357
  input: {},
358
358
  next: undefined,
359
359
  onError: undefined,
360
+ retries: undefined,
361
+ retryDelay: undefined,
360
362
  }
361
363
  }
362
364
 
@@ -382,6 +384,8 @@ function extractGraphFromNewFormat(
382
384
  nodes[nodeId].next = nodeConfig.next
383
385
  nodes[nodeId].onError = nodeConfig.onError
384
386
  nodes[nodeId].input = nodeConfig.input
387
+ nodes[nodeId].retries = nodeConfig.retries
388
+ nodes[nodeId].retryDelay = nodeConfig.retryDelay
385
389
  }
386
390
  }
387
391
  }
@@ -400,10 +404,14 @@ function extractNodeConfigFromObject(
400
404
  next: any
401
405
  onError: any
402
406
  input: Record<string, any>
407
+ retries: number | undefined
408
+ retryDelay: string | number | undefined
403
409
  } {
404
410
  let next: any = undefined
405
411
  let onError: any = undefined
406
412
  let input: Record<string, any> = {}
413
+ let retries: number | undefined = undefined
414
+ let retryDelay: string | number | undefined = undefined
407
415
 
408
416
  for (const prop of obj.properties) {
409
417
  if (!ts.isPropertyAssignment(prop) || !ts.isIdentifier(prop.name)) continue
@@ -416,10 +424,20 @@ function extractNodeConfigFromObject(
416
424
  onError = extractNextConfig(prop.initializer, checker)
417
425
  } else if (propName === 'input') {
418
426
  input = extractInputMapping(prop.initializer, checker)
427
+ } else if (propName === 'retries') {
428
+ if (ts.isNumericLiteral(prop.initializer)) {
429
+ retries = Number(prop.initializer.text)
430
+ }
431
+ } else if (propName === 'retryDelay') {
432
+ if (ts.isNumericLiteral(prop.initializer)) {
433
+ retryDelay = Number(prop.initializer.text)
434
+ } else if (ts.isStringLiteral(prop.initializer)) {
435
+ retryDelay = prop.initializer.text
436
+ }
419
437
  }
420
438
  }
421
439
 
422
- return { next, onError, input }
440
+ return { next, onError, input, retries, retryDelay }
423
441
  }
424
442
 
425
443
  /**
@@ -0,0 +1,114 @@
1
+ import { strict as assert } from 'assert'
2
+ import { describe, test } from 'node:test'
3
+ import { mkdtemp, rm, writeFile } from 'node:fs/promises'
4
+ import { tmpdir } from 'node:os'
5
+ import { join } from 'node:path'
6
+ import { inspect } from '../inspector.js'
7
+ import { ErrorCode } from '../error-codes.js'
8
+ import type { InspectorLogger } from '../types.js'
9
+
10
+ const makeLogger = (criticals: Array<{ code: ErrorCode; message: string }>) =>
11
+ ({
12
+ debug: () => {},
13
+ info: () => {},
14
+ warn: () => {},
15
+ error: () => {},
16
+ critical: (code: ErrorCode, message: string) => {
17
+ criticals.push({ code, message })
18
+ },
19
+ hasCriticalErrors: () => criticals.length > 0,
20
+ }) satisfies InspectorLogger
21
+
22
+ describe('wiring name must be a string literal', () => {
23
+ test('logs a critical error when a queue worker name is a const reference', async () => {
24
+ const rootDir = await mkdtemp(join(tmpdir(), 'pikku-nonliteral-name-'))
25
+ const file = join(rootDir, 'queue.ts')
26
+
27
+ await writeFile(
28
+ file,
29
+ [
30
+ "import { pikkuSessionlessFunc, wireQueueWorker } from '@pikku/core'",
31
+ 'const QUEUE_NAME = "stripe-webhook-event"',
32
+ 'export const handler = pikkuSessionlessFunc({',
33
+ ' func: async () => ({ ok: true })',
34
+ '})',
35
+ 'wireQueueWorker({',
36
+ ' name: QUEUE_NAME,',
37
+ ' func: handler,',
38
+ '})',
39
+ ].join('\n')
40
+ )
41
+
42
+ const criticals: Array<{ code: ErrorCode; message: string }> = []
43
+ try {
44
+ await inspect(makeLogger(criticals), [file], { rootDir })
45
+ const hit = criticals.find(
46
+ (entry) => entry.code === ErrorCode.NON_LITERAL_WIRE_NAME
47
+ )
48
+ assert.ok(hit, 'expected NON_LITERAL_WIRE_NAME critical')
49
+ assert.match(hit!.message, /QUEUE_NAME/)
50
+ } finally {
51
+ await rm(rootDir, { recursive: true, force: true })
52
+ }
53
+ })
54
+
55
+ test('logs a critical error when a secret id is a const reference', async () => {
56
+ const rootDir = await mkdtemp(join(tmpdir(), 'pikku-nonliteral-secret-'))
57
+ const file = join(rootDir, 'secret.ts')
58
+
59
+ await writeFile(
60
+ file,
61
+ [
62
+ "import { wireSecret } from '@pikku/core'",
63
+ 'const SECRET_ID = "STRIPE_SECRET_KEY"',
64
+ 'wireSecret({',
65
+ ' secretId: SECRET_ID,',
66
+ " name: 'Stripe secret key',",
67
+ " displayName: 'Stripe secret key',",
68
+ '})',
69
+ ].join('\n')
70
+ )
71
+
72
+ const criticals: Array<{ code: ErrorCode; message: string }> = []
73
+ try {
74
+ await inspect(makeLogger(criticals), [file], { rootDir })
75
+ const hit = criticals.find(
76
+ (entry) => entry.code === ErrorCode.NON_LITERAL_WIRE_NAME
77
+ )
78
+ assert.ok(hit, 'expected NON_LITERAL_WIRE_NAME critical')
79
+ assert.match(hit!.message, /SECRET_ID/)
80
+ } finally {
81
+ await rm(rootDir, { recursive: true, force: true })
82
+ }
83
+ })
84
+
85
+ test('does not flag a queue worker whose name is an inline literal', async () => {
86
+ const rootDir = await mkdtemp(join(tmpdir(), 'pikku-literal-name-'))
87
+ const file = join(rootDir, 'queue.ts')
88
+
89
+ await writeFile(
90
+ file,
91
+ [
92
+ "import { pikkuSessionlessFunc, wireQueueWorker } from '@pikku/core'",
93
+ 'export const handler = pikkuSessionlessFunc({',
94
+ ' func: async () => ({ ok: true })',
95
+ '})',
96
+ 'wireQueueWorker({',
97
+ " name: 'stripe-webhook-event',",
98
+ ' func: handler,',
99
+ '})',
100
+ ].join('\n')
101
+ )
102
+
103
+ const criticals: Array<{ code: ErrorCode; message: string }> = []
104
+ try {
105
+ await inspect(makeLogger(criticals), [file], { rootDir })
106
+ const hit = criticals.find(
107
+ (entry) => entry.code === ErrorCode.NON_LITERAL_WIRE_NAME
108
+ )
109
+ assert.equal(hit, undefined)
110
+ } finally {
111
+ await rm(rootDir, { recursive: true, force: true })
112
+ }
113
+ })
114
+ })
@@ -10,6 +10,7 @@
10
10
  export enum ErrorCode {
11
11
  // Validation errors
12
12
  MISSING_NAME = 'PKU111',
13
+ NON_LITERAL_WIRE_NAME = 'PKU118',
13
14
  MISSING_DESCRIPTION = 'PKU123',
14
15
  INVALID_VALUE = 'PKU124',
15
16
  MISSING_URI = 'PKU220',
package/src/index.ts CHANGED
@@ -9,6 +9,7 @@ export {
9
9
  } from './utils/serialize-inspector-state.js'
10
10
  export type { SerializableInspectorState } from './utils/serialize-inspector-state.js'
11
11
  export { filterInspectorState } from './utils/filter-inspector-state.js'
12
+ export { resolveDeployTarget } from './utils/resolve-deploy-target.js'
12
13
  export {
13
14
  generateCustomTypes,
14
15
  sanitizeTypeName,
package/src/inspector.ts CHANGED
@@ -12,7 +12,6 @@ import { findCommonAncestor } from './utils/find-root-dir.js'
12
12
  import {
13
13
  aggregateRequiredServices,
14
14
  validateAgentModels,
15
- validateAgentOverrides,
16
15
  validateSecretOverrides,
17
16
  validateVariableOverrides,
18
17
  validateCredentialOverrides,
@@ -250,8 +249,6 @@ export const inspect = async (
250
249
  const rootDir = options.rootDir || findCommonAncestor(routeFiles)
251
250
 
252
251
  const startSourceFiles = performance.now()
253
- // Filter source files to only include files within the project rootDir
254
- // This prevents picking up types from external packages (including workspace symlinks)
255
252
  const sourceFiles = program
256
253
  .getSourceFiles()
257
254
  .filter((sf) => sf.fileName.startsWith(rootDir))
@@ -354,8 +351,7 @@ export const inspect = async (
354
351
  )
355
352
  }
356
353
 
357
- validateAgentModels(logger, state, options.modelConfig)
358
- validateAgentOverrides(logger, state, options.modelConfig)
354
+ validateAgentModels(logger, state)
359
355
  validateSecretOverrides(logger, state)
360
356
  validateVariableOverrides(logger, state)
361
357
  validateCredentialOverrides(logger, state)
package/src/types.ts CHANGED
@@ -179,10 +179,28 @@ export interface InspectorPermissionState {
179
179
  export type InspectorFilters = {
180
180
  names?: string[] // Wildcard support: "email-*" matches "email-worker", "email-sender"
181
181
  tags?: string[]
182
- types?: string[]
182
+ wires?: string[]
183
183
  directories?: string[]
184
184
  httpRoutes?: string[] // HTTP route patterns: "/api/*", "/webhooks/*"
185
185
  httpMethods?: string[] // HTTP methods: "GET", "POST", "DELETE", etc.
186
+
187
+ excludeNames?: string[]
188
+ excludeTags?: string[]
189
+ excludeWires?: string[]
190
+ excludeDirectories?: string[]
191
+ excludeHttpRoutes?: string[]
192
+ excludeHttpMethods?: string[]
193
+
194
+ // Keep only functions whose effective deploy target is in this list.
195
+ // A function's effective target is its explicit `deploy` field, or
196
+ // 'server' if any of its services are listed in `serverlessIncompatible`,
197
+ // otherwise 'serverless'.
198
+ target?: Array<'serverless' | 'server'>
199
+ excludeTarget?: Array<'serverless' | 'server'>
200
+ // Service names that, when consumed by a function, force its target
201
+ // to 'server'. Sourced from `pikku.config.json` →
202
+ // `deploy.serverlessIncompatible`. Used only when deploy filters are set.
203
+ serverlessIncompatible?: string[]
186
204
  }
187
205
 
188
206
  export type AddonConfig = {
@@ -192,19 +210,6 @@ export type AddonConfig = {
192
210
  forceInclude?: boolean
193
211
  }
194
212
 
195
- export type ModelConfigEntry =
196
- | string
197
- | { model: string; temperature?: number; maxSteps?: number }
198
-
199
- export type InspectorModelConfig = {
200
- models?: Record<string, ModelConfigEntry>
201
- agentDefaults?: { temperature?: number; maxSteps?: number }
202
- agentOverrides?: Record<
203
- string,
204
- { model?: string; temperature?: number; maxSteps?: number }
205
- >
206
- }
207
-
208
213
  export type InspectorOptions = Partial<{
209
214
  setupOnly: boolean
210
215
  rootDir: string
@@ -225,7 +230,6 @@ export type InspectorOptions = Partial<{
225
230
  }
226
231
  tags: string[]
227
232
  manifest: VersionManifest
228
- modelConfig: InspectorModelConfig
229
233
  oldProgram: ts.Program | undefined
230
234
  }>
231
235
 
@@ -444,6 +444,14 @@ export function extractFunctionName(
444
444
  }
445
445
 
446
446
  if (result.version !== null) {
447
+ // Strip trailing VN suffix if it matches the version (e.g. createCardV1 + version:1 → createCard@v1)
448
+ const vSuffix = `V${result.version}`
449
+ if (
450
+ result.pikkuFuncId.endsWith(vSuffix) &&
451
+ result.pikkuFuncId.length > vSuffix.length
452
+ ) {
453
+ result.pikkuFuncId = result.pikkuFuncId.slice(0, -vSuffix.length)
454
+ }
447
455
  result.pikkuFuncId = formatVersionedId(result.pikkuFuncId, result.version)
448
456
  }
449
457