@pikku/inspector 0.12.2 → 0.12.3
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.
- package/CHANGELOG.md +14 -0
- package/dist/add/add-ai-agent.js +4 -0
- package/dist/add/add-approval-description.d.ts +5 -0
- package/dist/add/add-approval-description.js +52 -0
- package/dist/add/add-channel.js +42 -4
- package/dist/add/add-cli.js +73 -13
- package/dist/add/add-file-with-factory.js +1 -0
- package/dist/add/add-functions.js +22 -3
- package/dist/add/add-gateway.js +5 -0
- package/dist/add/add-http-route.js +5 -0
- package/dist/add/add-mcp-prompt.js +5 -0
- package/dist/add/add-mcp-resource.js +5 -0
- package/dist/add/add-queue-worker.js +5 -0
- package/dist/add/add-schedule.js +5 -0
- package/dist/add/add-wire-addon.js +7 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/inspector.js +7 -0
- package/dist/types.d.ts +10 -0
- package/dist/utils/load-addon-functions-meta.d.ts +12 -0
- package/dist/utils/load-addon-functions-meta.js +76 -0
- package/dist/utils/post-process.js +26 -0
- package/dist/utils/resolve-function-meta.d.ts +11 -0
- package/dist/utils/resolve-function-meta.js +17 -0
- package/dist/utils/serialize-inspector-state.d.ts +2 -0
- package/dist/utils/serialize-inspector-state.js +4 -0
- package/dist/utils/serialize-mcp-json.js +13 -7
- package/dist/visit.js +2 -0
- package/package.json +2 -2
- package/src/add/add-ai-agent.ts +6 -0
- package/src/add/add-approval-description.ts +76 -0
- package/src/add/add-channel.ts +44 -4
- package/src/add/add-cli.ts +108 -21
- package/src/add/add-file-with-factory.ts +1 -0
- package/src/add/add-functions.ts +28 -3
- package/src/add/add-gateway.ts +6 -0
- package/src/add/add-http-route.ts +6 -0
- package/src/add/add-mcp-prompt.ts +6 -0
- package/src/add/add-mcp-resource.ts +6 -0
- package/src/add/add-queue-worker.ts +6 -0
- package/src/add/add-schedule.ts +6 -0
- package/src/add/add-wire-addon.ts +8 -0
- package/src/index.ts +1 -0
- package/src/inspector.ts +12 -0
- package/src/types.ts +11 -0
- package/src/utils/load-addon-functions-meta.ts +94 -0
- package/src/utils/post-process.ts +25 -0
- package/src/utils/resolve-function-meta.ts +25 -0
- package/src/utils/serialize-inspector-state.ts +6 -0
- package/src/utils/serialize-mcp-json.ts +12 -7
- package/src/visit.ts +2 -0
- package/tsconfig.tsbuildinfo +1 -1
package/src/add/add-cli.ts
CHANGED
|
@@ -12,9 +12,11 @@ import {
|
|
|
12
12
|
makeContextBasedId,
|
|
13
13
|
} from '../utils/extract-function-name.js'
|
|
14
14
|
import { resolveMiddleware } from '../utils/middleware.js'
|
|
15
|
+
import { resolveFunctionMeta } from '../utils/resolve-function-meta.js'
|
|
15
16
|
import { extractWireNames } from '../utils/post-process.js'
|
|
16
17
|
import { getPropertyValue } from '../utils/get-property-value.js'
|
|
17
18
|
import { resolveIdentifier } from '../utils/resolve-identifier.js'
|
|
19
|
+
import { resolveAddonName } from '../utils/resolve-addon-package.js'
|
|
18
20
|
import { validateAuthSessionless } from '../utils/validate-auth-sessionless.js'
|
|
19
21
|
|
|
20
22
|
// Track if we've warned about missing Config type to avoid duplicate warnings
|
|
@@ -344,15 +346,53 @@ function processCommand(
|
|
|
344
346
|
const propName = prop.name.text
|
|
345
347
|
|
|
346
348
|
if (propName === 'func') {
|
|
347
|
-
|
|
348
|
-
prop.initializer
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
)
|
|
352
|
-
|
|
353
|
-
|
|
349
|
+
if (
|
|
350
|
+
ts.isCallExpression(prop.initializer) &&
|
|
351
|
+
ts.isIdentifier(prop.initializer.expression) &&
|
|
352
|
+
prop.initializer.expression.text === 'addon'
|
|
353
|
+
) {
|
|
354
|
+
const [firstArg] = prop.initializer.arguments
|
|
355
|
+
if (!firstArg || !ts.isStringLiteral(firstArg)) {
|
|
356
|
+
throw new Error(
|
|
357
|
+
`addon() call requires a string literal argument in the form "namespace:funcName"`
|
|
358
|
+
)
|
|
359
|
+
}
|
|
360
|
+
pikkuFuncId = firstArg.text
|
|
361
|
+
const addonNamespace = pikkuFuncId.split(':')[0]
|
|
362
|
+
if (!addonNamespace || !pikkuFuncId.includes(':')) {
|
|
363
|
+
throw new Error(
|
|
364
|
+
`Malformed addon function ID "${pikkuFuncId}": expected "namespace:funcName" format`
|
|
365
|
+
)
|
|
366
|
+
}
|
|
367
|
+
if (!inspectorState.rpc.wireAddonDeclarations.has(addonNamespace)) {
|
|
368
|
+
throw new Error(
|
|
369
|
+
`Unknown addon namespace "${addonNamespace}" in "${pikkuFuncId}": no matching wireAddonDeclarations entry found`
|
|
370
|
+
)
|
|
371
|
+
}
|
|
372
|
+
meta.pikkuFuncId = pikkuFuncId
|
|
373
|
+
meta.packageName =
|
|
374
|
+
inspectorState.rpc.wireAddonDeclarations.get(addonNamespace)!.package
|
|
375
|
+
} else {
|
|
376
|
+
pikkuFuncId = extractFunctionName(
|
|
377
|
+
prop.initializer,
|
|
378
|
+
typeChecker,
|
|
379
|
+
inspectorState.rootDir
|
|
380
|
+
).pikkuFuncId
|
|
381
|
+
if (pikkuFuncId.startsWith('__temp_')) {
|
|
382
|
+
pikkuFuncId = makeContextBasedId('cli', programName, ...fullPath)
|
|
383
|
+
}
|
|
384
|
+
meta.pikkuFuncId = pikkuFuncId
|
|
385
|
+
const cliPackageName = ts.isIdentifier(prop.initializer)
|
|
386
|
+
? resolveAddonName(
|
|
387
|
+
prop.initializer,
|
|
388
|
+
typeChecker,
|
|
389
|
+
inspectorState.rpc.wireAddonDeclarations
|
|
390
|
+
)
|
|
391
|
+
: null
|
|
392
|
+
if (cliPackageName) {
|
|
393
|
+
meta.packageName = cliPackageName
|
|
394
|
+
}
|
|
354
395
|
}
|
|
355
|
-
meta.pikkuFuncId = pikkuFuncId
|
|
356
396
|
} else if (
|
|
357
397
|
propName === 'options' &&
|
|
358
398
|
ts.isObjectLiteralExpression(prop.initializer)
|
|
@@ -573,22 +613,31 @@ function processOptions(
|
|
|
573
613
|
}
|
|
574
614
|
|
|
575
615
|
// Extract enum values from the function input type if available
|
|
576
|
-
|
|
577
|
-
|
|
616
|
+
let derivedChoices: string[] | null = null
|
|
617
|
+
|
|
578
618
|
if (pikkuFuncId) {
|
|
579
|
-
|
|
580
|
-
|
|
619
|
+
// 1. Try TypeScript types first (most precise — handles unions, TS enums)
|
|
620
|
+
const inputTypes = inspectorState.typesLookup.get(pikkuFuncId)
|
|
621
|
+
if (inputTypes && inputTypes.length > 0) {
|
|
622
|
+
derivedChoices = extractEnumFromPropertyType(
|
|
623
|
+
inputTypes[0]!,
|
|
624
|
+
optionName,
|
|
625
|
+
typeChecker
|
|
626
|
+
)
|
|
627
|
+
}
|
|
581
628
|
|
|
582
|
-
|
|
629
|
+
// 2. Fallback: try JSON schema (works for addon functions)
|
|
630
|
+
if (!derivedChoices) {
|
|
631
|
+
derivedChoices = extractEnumFromJsonSchema(
|
|
632
|
+
inspectorState,
|
|
633
|
+
pikkuFuncId,
|
|
634
|
+
optionName
|
|
635
|
+
)
|
|
636
|
+
}
|
|
637
|
+
}
|
|
583
638
|
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
inputTypes[0]!,
|
|
587
|
-
optionName,
|
|
588
|
-
typeChecker
|
|
589
|
-
)
|
|
590
|
-
} else {
|
|
591
|
-
// Fallback: try to extract from Config type
|
|
639
|
+
// 3. Last resort: try Config type
|
|
640
|
+
if (!derivedChoices) {
|
|
592
641
|
derivedChoices = extractEnumFromConfigType(
|
|
593
642
|
logger,
|
|
594
643
|
optionName,
|
|
@@ -750,6 +799,44 @@ function extractEnumFromConfigType(
|
|
|
750
799
|
return extractEnumFromPropertyType(configType, propertyName, typeChecker)
|
|
751
800
|
}
|
|
752
801
|
|
|
802
|
+
/**
|
|
803
|
+
* Extracts enum values from the function's JSON schema.
|
|
804
|
+
* Works for addon functions whose schemas are generated from OpenAPI/Zod.
|
|
805
|
+
*/
|
|
806
|
+
function extractEnumFromJsonSchema(
|
|
807
|
+
inspectorState: InspectorState,
|
|
808
|
+
pikkuFuncId: string,
|
|
809
|
+
propertyName: string
|
|
810
|
+
): string[] | null {
|
|
811
|
+
const fnMeta = resolveFunctionMeta(inspectorState, pikkuFuncId)
|
|
812
|
+
if (!fnMeta?.inputSchemaName) return null
|
|
813
|
+
|
|
814
|
+
const schema = inspectorState.schemas[fnMeta.inputSchemaName] as any
|
|
815
|
+
if (!schema?.properties?.[propertyName]) return null
|
|
816
|
+
|
|
817
|
+
const prop = schema.properties[propertyName]
|
|
818
|
+
|
|
819
|
+
// Direct enum on property
|
|
820
|
+
if (prop.enum && Array.isArray(prop.enum)) {
|
|
821
|
+
const strings = prop.enum.filter((v: unknown) => typeof v === 'string')
|
|
822
|
+
if (strings.length > 0) return strings
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
// Array with enum items (e.g. z.array(z.enum([...])))
|
|
826
|
+
if (
|
|
827
|
+
prop.type === 'array' &&
|
|
828
|
+
prop.items?.enum &&
|
|
829
|
+
Array.isArray(prop.items.enum)
|
|
830
|
+
) {
|
|
831
|
+
const strings = prop.items.enum.filter(
|
|
832
|
+
(v: unknown) => typeof v === 'string'
|
|
833
|
+
)
|
|
834
|
+
if (strings.length > 0) return strings
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
return null
|
|
838
|
+
}
|
|
839
|
+
|
|
753
840
|
/**
|
|
754
841
|
* Gets the property name from a property assignment
|
|
755
842
|
*/
|
|
@@ -9,6 +9,7 @@ const wrapperFunctionMap: Record<string, string> = {
|
|
|
9
9
|
pikkuServices: 'CreateSingletonServices',
|
|
10
10
|
pikkuAddonServices: 'CreateSingletonServices',
|
|
11
11
|
pikkuWireServices: 'CreateWireServices',
|
|
12
|
+
pikkuAddonWireServices: 'CreateWireServices',
|
|
12
13
|
}
|
|
13
14
|
|
|
14
15
|
export const addFileWithFactory = (
|
package/src/add/add-functions.ts
CHANGED
|
@@ -339,7 +339,8 @@ export const addFunctions: AddWiring = (
|
|
|
339
339
|
let remote: boolean | undefined
|
|
340
340
|
let mcp: boolean | undefined
|
|
341
341
|
let readonly_: boolean | undefined
|
|
342
|
-
let
|
|
342
|
+
let approvalRequired: boolean | undefined
|
|
343
|
+
let approvalDescription: string | undefined
|
|
343
344
|
let version: number | undefined
|
|
344
345
|
let objectNode: ts.ObjectLiteralExpression | undefined
|
|
345
346
|
let nodeDisplayName: string | null = null
|
|
@@ -421,10 +422,33 @@ export const addFunctions: AddWiring = (
|
|
|
421
422
|
remote = getPropertyValue(firstArg, 'remote') as boolean | undefined
|
|
422
423
|
mcp = getPropertyValue(firstArg, 'mcp') as boolean | undefined
|
|
423
424
|
readonly_ = getPropertyValue(firstArg, 'readonly') as boolean | undefined
|
|
424
|
-
|
|
425
|
+
approvalRequired = getPropertyValue(firstArg, 'approvalRequired') as
|
|
425
426
|
| boolean
|
|
426
427
|
| undefined
|
|
427
428
|
|
|
429
|
+
// Extract approvalDescription identifier reference
|
|
430
|
+
for (const prop of firstArg.properties) {
|
|
431
|
+
if (
|
|
432
|
+
ts.isPropertyAssignment(prop) &&
|
|
433
|
+
ts.isIdentifier(prop.name) &&
|
|
434
|
+
prop.name.text === 'approvalDescription' &&
|
|
435
|
+
ts.isIdentifier(prop.initializer)
|
|
436
|
+
) {
|
|
437
|
+
const { pikkuFuncId: descId } = extractFunctionName(
|
|
438
|
+
prop.initializer,
|
|
439
|
+
checker,
|
|
440
|
+
state.rootDir
|
|
441
|
+
)
|
|
442
|
+
if (descId && !descId.startsWith('__temp_')) {
|
|
443
|
+
approvalDescription = descId
|
|
444
|
+
} else {
|
|
445
|
+
// Try resolving the identifier directly
|
|
446
|
+
approvalDescription = prop.initializer.text
|
|
447
|
+
}
|
|
448
|
+
break
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
|
|
428
452
|
const versionRaw = getPropertyValue(firstArg, 'version')
|
|
429
453
|
if (versionRaw !== null && versionRaw !== undefined) {
|
|
430
454
|
const parsed = Number(versionRaw)
|
|
@@ -759,7 +783,8 @@ export const addFunctions: AddWiring = (
|
|
|
759
783
|
remote: remote || undefined,
|
|
760
784
|
mcp: mcpEnabled || undefined,
|
|
761
785
|
readonly: readonly_ || undefined,
|
|
762
|
-
|
|
786
|
+
approvalRequired: approvalRequired || undefined,
|
|
787
|
+
approvalDescription: approvalDescription || undefined,
|
|
763
788
|
version,
|
|
764
789
|
title,
|
|
765
790
|
tags: tags || undefined,
|
package/src/add/add-gateway.ts
CHANGED
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
import { getPropertyAssignmentInitializer } from '../utils/type-utils.js'
|
|
12
12
|
import { resolveMiddleware } from '../utils/middleware.js'
|
|
13
13
|
import { extractWireNames } from '../utils/post-process.js'
|
|
14
|
+
import { resolveAddonName } from '../utils/resolve-addon-package.js'
|
|
14
15
|
import type { GatewayTransportType } from '@pikku/core/gateway'
|
|
15
16
|
|
|
16
17
|
import { ErrorCode } from '../error-codes.js'
|
|
@@ -68,6 +69,10 @@ export const addGateway: AddWiring = (
|
|
|
68
69
|
pikkuFuncId = makeContextBasedId('gateway', nameValue)
|
|
69
70
|
}
|
|
70
71
|
|
|
72
|
+
const packageName = ts.isIdentifier(funcInitializer)
|
|
73
|
+
? resolveAddonName(funcInitializer, checker, state.rpc.wireAddonDeclarations)
|
|
74
|
+
: null
|
|
75
|
+
|
|
71
76
|
if (!nameValue || !typeValue) {
|
|
72
77
|
return
|
|
73
78
|
}
|
|
@@ -82,6 +87,7 @@ export const addGateway: AddWiring = (
|
|
|
82
87
|
state.gateways.files.add(node.getSourceFile().fileName)
|
|
83
88
|
state.gateways.meta[nameValue] = {
|
|
84
89
|
pikkuFuncId,
|
|
90
|
+
...(packageName && { packageName }),
|
|
85
91
|
name: nameValue,
|
|
86
92
|
type: typeValue,
|
|
87
93
|
route: routeValue,
|
|
@@ -21,6 +21,7 @@ import { ensureFunctionMetadata } from '../utils/ensure-function-metadata.js'
|
|
|
21
21
|
import { ErrorCode } from '../error-codes.js'
|
|
22
22
|
import { validateAuthSessionless } from '../utils/validate-auth-sessionless.js'
|
|
23
23
|
import { detectSchemaVendorOrError } from '../utils/detect-schema-vendor.js'
|
|
24
|
+
import { resolveAddonName } from '../utils/resolve-addon-package.js'
|
|
24
25
|
|
|
25
26
|
import type { InspectorLogger } from '../types.js'
|
|
26
27
|
|
|
@@ -203,6 +204,10 @@ export function registerHTTPRoute({
|
|
|
203
204
|
funcName = makeContextBasedId('http', method, fullRoute)
|
|
204
205
|
}
|
|
205
206
|
|
|
207
|
+
const packageName = ts.isIdentifier(funcInitializer)
|
|
208
|
+
? resolveAddonName(funcInitializer, checker, state.rpc.wireAddonDeclarations)
|
|
209
|
+
: null
|
|
210
|
+
|
|
206
211
|
ensureFunctionMetadata(
|
|
207
212
|
state,
|
|
208
213
|
funcName,
|
|
@@ -320,6 +325,7 @@ export function registerHTTPRoute({
|
|
|
320
325
|
state.http.files.add(sourceFile.fileName)
|
|
321
326
|
state.http.meta[method][fullRoute] = {
|
|
322
327
|
pikkuFuncId: funcName,
|
|
328
|
+
...(packageName && { packageName }),
|
|
323
329
|
route: fullRoute,
|
|
324
330
|
method: method as HTTPMethod,
|
|
325
331
|
params: params.length > 0 ? params : undefined,
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
import { getPropertyAssignmentInitializer } from '../utils/type-utils.js'
|
|
14
14
|
import { resolveMiddleware } from '../utils/middleware.js'
|
|
15
15
|
import { resolvePermissions } from '../utils/permissions.js'
|
|
16
|
+
import { resolveAddonName } from '../utils/resolve-addon-package.js'
|
|
16
17
|
import { ErrorCode } from '../error-codes.js'
|
|
17
18
|
|
|
18
19
|
export const addMCPPrompt: AddWiring = (
|
|
@@ -72,6 +73,10 @@ export const addMCPPrompt: AddWiring = (
|
|
|
72
73
|
pikkuFuncId = makeContextBasedId('mcp', 'prompt', nameValue)
|
|
73
74
|
}
|
|
74
75
|
|
|
76
|
+
const packageName = ts.isIdentifier(funcInitializer)
|
|
77
|
+
? resolveAddonName(funcInitializer, checker, state.rpc.wireAddonDeclarations)
|
|
78
|
+
: null
|
|
79
|
+
|
|
75
80
|
ensureFunctionMetadata(
|
|
76
81
|
state,
|
|
77
82
|
pikkuFuncId,
|
|
@@ -128,6 +133,7 @@ export const addMCPPrompt: AddWiring = (
|
|
|
128
133
|
|
|
129
134
|
state.mcpEndpoints.promptsMeta[nameValue] = {
|
|
130
135
|
pikkuFuncId,
|
|
136
|
+
...(packageName && { packageName }),
|
|
131
137
|
name: nameValue,
|
|
132
138
|
description,
|
|
133
139
|
summary,
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
import { getPropertyAssignmentInitializer } from '../utils/type-utils.js'
|
|
14
14
|
import { resolveMiddleware } from '../utils/middleware.js'
|
|
15
15
|
import { resolvePermissions } from '../utils/permissions.js'
|
|
16
|
+
import { resolveAddonName } from '../utils/resolve-addon-package.js'
|
|
16
17
|
import { ErrorCode } from '../error-codes.js'
|
|
17
18
|
|
|
18
19
|
export const addMCPResource: AddWiring = (
|
|
@@ -81,6 +82,10 @@ export const addMCPResource: AddWiring = (
|
|
|
81
82
|
pikkuFuncId = makeContextBasedId('mcp', 'resource', uriValue)
|
|
82
83
|
}
|
|
83
84
|
|
|
85
|
+
const packageName = ts.isIdentifier(funcInitializer)
|
|
86
|
+
? resolveAddonName(funcInitializer, checker, state.rpc.wireAddonDeclarations)
|
|
87
|
+
: null
|
|
88
|
+
|
|
84
89
|
ensureFunctionMetadata(
|
|
85
90
|
state,
|
|
86
91
|
pikkuFuncId,
|
|
@@ -145,6 +150,7 @@ export const addMCPResource: AddWiring = (
|
|
|
145
150
|
|
|
146
151
|
state.mcpEndpoints.resourcesMeta[uriValue] = {
|
|
147
152
|
pikkuFuncId,
|
|
153
|
+
...(packageName && { packageName }),
|
|
148
154
|
uri: uriValue,
|
|
149
155
|
title: titleValue,
|
|
150
156
|
description,
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
import { getPropertyAssignmentInitializer } from '../utils/type-utils.js'
|
|
12
12
|
import { resolveMiddleware } from '../utils/middleware.js'
|
|
13
13
|
import { extractWireNames } from '../utils/post-process.js'
|
|
14
|
+
import { resolveAddonName } from '../utils/resolve-addon-package.js'
|
|
14
15
|
import { ErrorCode } from '../error-codes.js'
|
|
15
16
|
|
|
16
17
|
export const addQueueWorker: AddWiring = (logger, node, checker, state) => {
|
|
@@ -65,6 +66,10 @@ export const addQueueWorker: AddWiring = (logger, node, checker, state) => {
|
|
|
65
66
|
pikkuFuncId = makeContextBasedId('queue', name)
|
|
66
67
|
}
|
|
67
68
|
|
|
69
|
+
const packageName = ts.isIdentifier(funcInitializer)
|
|
70
|
+
? resolveAddonName(funcInitializer, checker, state.rpc.wireAddonDeclarations)
|
|
71
|
+
: null
|
|
72
|
+
|
|
68
73
|
if (!name) {
|
|
69
74
|
logger.critical(
|
|
70
75
|
ErrorCode.MISSING_QUEUE_NAME,
|
|
@@ -85,6 +90,7 @@ export const addQueueWorker: AddWiring = (logger, node, checker, state) => {
|
|
|
85
90
|
state.queueWorkers.files.add(node.getSourceFile().fileName)
|
|
86
91
|
state.queueWorkers.meta[name] = {
|
|
87
92
|
pikkuFuncId,
|
|
93
|
+
...(packageName && { packageName }),
|
|
88
94
|
name,
|
|
89
95
|
summary,
|
|
90
96
|
description,
|
package/src/add/add-schedule.ts
CHANGED
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
import { getPropertyAssignmentInitializer } from '../utils/type-utils.js'
|
|
12
12
|
import { resolveMiddleware } from '../utils/middleware.js'
|
|
13
13
|
import { extractWireNames } from '../utils/post-process.js'
|
|
14
|
+
import { resolveAddonName } from '../utils/resolve-addon-package.js'
|
|
14
15
|
|
|
15
16
|
import { ErrorCode } from '../error-codes.js'
|
|
16
17
|
export const addSchedule: AddWiring = (
|
|
@@ -71,6 +72,10 @@ export const addSchedule: AddWiring = (
|
|
|
71
72
|
pikkuFuncId = makeContextBasedId('scheduler', nameValue)
|
|
72
73
|
}
|
|
73
74
|
|
|
75
|
+
const packageName = ts.isIdentifier(funcInitializer)
|
|
76
|
+
? resolveAddonName(funcInitializer, checker, state.rpc.wireAddonDeclarations)
|
|
77
|
+
: null
|
|
78
|
+
|
|
74
79
|
if (!nameValue || !scheduleValue) {
|
|
75
80
|
return
|
|
76
81
|
}
|
|
@@ -87,6 +92,7 @@ export const addSchedule: AddWiring = (
|
|
|
87
92
|
state.scheduledTasks.files.add(node.getSourceFile().fileName)
|
|
88
93
|
state.scheduledTasks.meta[nameValue] = {
|
|
89
94
|
pikkuFuncId,
|
|
95
|
+
...(packageName && { packageName }),
|
|
90
96
|
name: nameValue,
|
|
91
97
|
schedule: scheduleValue,
|
|
92
98
|
summary,
|
|
@@ -40,6 +40,7 @@ export function addWireAddon(
|
|
|
40
40
|
let name: string | undefined
|
|
41
41
|
let pkg: string | undefined
|
|
42
42
|
let rpcEndpoint: string | undefined
|
|
43
|
+
let mcp: boolean | undefined
|
|
43
44
|
let secretOverrides: Record<string, string> | undefined
|
|
44
45
|
let variableOverrides: Record<string, string> | undefined
|
|
45
46
|
|
|
@@ -53,6 +54,12 @@ export function addWireAddon(
|
|
|
53
54
|
pkg = prop.initializer.text
|
|
54
55
|
} else if (key === 'rpcEndpoint' && ts.isStringLiteral(prop.initializer)) {
|
|
55
56
|
rpcEndpoint = prop.initializer.text
|
|
57
|
+
} else if (
|
|
58
|
+
key === 'mcp' &&
|
|
59
|
+
(prop.initializer.kind === ts.SyntaxKind.TrueKeyword ||
|
|
60
|
+
prop.initializer.kind === ts.SyntaxKind.FalseKeyword)
|
|
61
|
+
) {
|
|
62
|
+
mcp = prop.initializer.kind === ts.SyntaxKind.TrueKeyword
|
|
56
63
|
} else if (
|
|
57
64
|
key === 'secretOverrides' &&
|
|
58
65
|
ts.isObjectLiteralExpression(prop.initializer)
|
|
@@ -72,6 +79,7 @@ export function addWireAddon(
|
|
|
72
79
|
state.rpc.wireAddonDeclarations.set(name, {
|
|
73
80
|
package: pkg,
|
|
74
81
|
rpcEndpoint,
|
|
82
|
+
mcp,
|
|
75
83
|
secretOverrides,
|
|
76
84
|
variableOverrides,
|
|
77
85
|
})
|
package/src/index.ts
CHANGED
|
@@ -32,6 +32,7 @@ export {
|
|
|
32
32
|
deserializeAllDslWorkflows,
|
|
33
33
|
} from './utils/workflow/dsl/index.js'
|
|
34
34
|
export { getFilesAndMethods } from './utils/get-files-and-methods.js'
|
|
35
|
+
export { resolveFunctionMeta } from './utils/resolve-function-meta.js'
|
|
35
36
|
export type {
|
|
36
37
|
SerializedWorkflowGraph,
|
|
37
38
|
SerializedWorkflowGraphs,
|
package/src/inspector.ts
CHANGED
|
@@ -30,6 +30,10 @@ import {
|
|
|
30
30
|
finalizeWorkflowWires,
|
|
31
31
|
} from './utils/workflow/graph/finalize-workflow-wires.js'
|
|
32
32
|
import { generateAllSchemas } from './utils/schema-generator.js'
|
|
33
|
+
import {
|
|
34
|
+
loadAddonFunctionsMeta,
|
|
35
|
+
loadAddonSchemas,
|
|
36
|
+
} from './utils/load-addon-functions-meta.js'
|
|
33
37
|
import {
|
|
34
38
|
computeContractHashes,
|
|
35
39
|
extractContractsFromMeta,
|
|
@@ -63,6 +67,7 @@ export function getInitialInspectorState(rootDir: string): InspectorState {
|
|
|
63
67
|
typesMap: new TypesMap(),
|
|
64
68
|
meta: {},
|
|
65
69
|
files: new Map(),
|
|
70
|
+
approvalDescriptions: {},
|
|
66
71
|
},
|
|
67
72
|
http: {
|
|
68
73
|
metaInputTypes: new Map(),
|
|
@@ -197,6 +202,7 @@ export function getInitialInspectorState(rootDir: string): InspectorState {
|
|
|
197
202
|
requiredSchemas: new Set(),
|
|
198
203
|
openAPISpec: null,
|
|
199
204
|
diagnostics: [],
|
|
205
|
+
addonFunctions: {},
|
|
200
206
|
}
|
|
201
207
|
}
|
|
202
208
|
|
|
@@ -252,6 +258,9 @@ export const inspect = async (
|
|
|
252
258
|
`Visit setup phase completed in ${(performance.now() - startSetup).toFixed(2)}ms`
|
|
253
259
|
)
|
|
254
260
|
|
|
261
|
+
// Load addon function metadata so wirings can reference addon functions
|
|
262
|
+
await loadAddonFunctionsMeta(logger, state)
|
|
263
|
+
|
|
255
264
|
if (!options.setupOnly) {
|
|
256
265
|
// Second sweep: add all transports
|
|
257
266
|
const startRoutes = performance.now()
|
|
@@ -280,6 +289,9 @@ export const inspect = async (
|
|
|
280
289
|
computeRequiredSchemas(state, options)
|
|
281
290
|
}
|
|
282
291
|
|
|
292
|
+
// Re-load addon schemas (generateAllSchemas replaces state.schemas)
|
|
293
|
+
await loadAddonSchemas(logger, state)
|
|
294
|
+
|
|
283
295
|
state.manifest.initial = options.manifest ?? null
|
|
284
296
|
const contracts = extractContractsFromMeta(state.functions.meta)
|
|
285
297
|
const baseManifest = state.manifest.initial ?? createEmptyManifest()
|
package/src/types.ts
CHANGED
|
@@ -98,6 +98,7 @@ export interface InspectorFunctionState {
|
|
|
98
98
|
typesMap: TypesMap
|
|
99
99
|
meta: FunctionsMeta
|
|
100
100
|
files: Map<string, { path: string; exportedName: string }>
|
|
101
|
+
approvalDescriptions: Record<string, InspectorApprovalDescriptionDefinition>
|
|
101
102
|
}
|
|
102
103
|
|
|
103
104
|
export interface InspectorChannelState {
|
|
@@ -140,6 +141,14 @@ export interface InspectorAIMiddlewareState {
|
|
|
140
141
|
definitions: Record<string, InspectorMiddlewareDefinition>
|
|
141
142
|
}
|
|
142
143
|
|
|
144
|
+
export interface InspectorApprovalDescriptionDefinition {
|
|
145
|
+
services: FunctionServicesMeta
|
|
146
|
+
wires?: FunctionWiresMeta
|
|
147
|
+
sourceFile: string
|
|
148
|
+
position: number
|
|
149
|
+
exportedName: string | null
|
|
150
|
+
}
|
|
151
|
+
|
|
143
152
|
export interface InspectorPermissionDefinition {
|
|
144
153
|
services: FunctionServicesMeta
|
|
145
154
|
wires?: FunctionWiresMeta
|
|
@@ -340,6 +349,7 @@ export interface InspectorState {
|
|
|
340
349
|
{
|
|
341
350
|
package: string
|
|
342
351
|
rpcEndpoint?: string
|
|
352
|
+
mcp?: boolean
|
|
343
353
|
secretOverrides?: Record<string, string>
|
|
344
354
|
variableOverrides?: Record<string, string>
|
|
345
355
|
}
|
|
@@ -409,4 +419,5 @@ export interface InspectorState {
|
|
|
409
419
|
requiredSchemas: Set<string>
|
|
410
420
|
openAPISpec: Record<string, any> | null
|
|
411
421
|
diagnostics: InspectorDiagnostic[]
|
|
422
|
+
addonFunctions: Record<string, FunctionsMeta> // namespace -> addon's function metadata
|
|
412
423
|
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { readFile, readdir } from 'fs/promises'
|
|
2
|
+
import { createRequire } from 'module'
|
|
3
|
+
import { join, dirname } from 'path'
|
|
4
|
+
import type { InspectorState, InspectorLogger } from '../types.js'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* After the setup sweep discovers wireAddon() declarations, load each addon
|
|
8
|
+
* package's function metadata so that wiring handlers (channels, HTTP routes,
|
|
9
|
+
* schedules, etc.) can look up addon function types during the routes sweep.
|
|
10
|
+
*/
|
|
11
|
+
export async function loadAddonFunctionsMeta(
|
|
12
|
+
logger: InspectorLogger,
|
|
13
|
+
state: InspectorState
|
|
14
|
+
): Promise<void> {
|
|
15
|
+
const { wireAddonDeclarations } = state.rpc
|
|
16
|
+
if (wireAddonDeclarations.size === 0) return
|
|
17
|
+
|
|
18
|
+
const require = createRequire(join(state.rootDir, 'package.json'))
|
|
19
|
+
|
|
20
|
+
for (const [namespace, decl] of wireAddonDeclarations) {
|
|
21
|
+
try {
|
|
22
|
+
const metaPath = require.resolve(
|
|
23
|
+
`${decl.package}/.pikku/function/pikku-functions-meta.gen.json`
|
|
24
|
+
)
|
|
25
|
+
const raw = await readFile(metaPath, 'utf-8')
|
|
26
|
+
const meta = JSON.parse(raw)
|
|
27
|
+
state.addonFunctions[namespace] = meta
|
|
28
|
+
logger.debug(
|
|
29
|
+
`Loaded ${Object.keys(meta).length} addon functions for '${namespace}' from ${decl.package}`
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
// If wireAddon has mcp: true, expose addon functions with mcp: true as MCP tools
|
|
33
|
+
if (decl.mcp) {
|
|
34
|
+
for (const [funcName, funcMeta] of Object.entries<any>(meta)) {
|
|
35
|
+
if (funcMeta.mcp) {
|
|
36
|
+
const toolName = `${namespace}:${funcName}`
|
|
37
|
+
state.mcpEndpoints.toolsMeta[toolName] = {
|
|
38
|
+
pikkuFuncId: `${namespace}:${funcName}`,
|
|
39
|
+
name: toolName,
|
|
40
|
+
description: funcMeta.description || funcMeta.title || funcName,
|
|
41
|
+
inputSchema: funcMeta.inputSchemaName ?? null,
|
|
42
|
+
outputSchema: funcMeta.outputSchemaName ?? null,
|
|
43
|
+
tags: funcMeta.tags,
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
} catch (error: any) {
|
|
49
|
+
logger.warn(
|
|
50
|
+
`Failed to load addon function metadata for '${namespace}' (${decl.package}): ${error.message}`
|
|
51
|
+
)
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Load addon schemas into state.schemas. Called after generateAllSchemas
|
|
58
|
+
* to ensure addon schemas aren't overwritten.
|
|
59
|
+
*/
|
|
60
|
+
export async function loadAddonSchemas(
|
|
61
|
+
logger: InspectorLogger,
|
|
62
|
+
state: InspectorState
|
|
63
|
+
): Promise<void> {
|
|
64
|
+
const { wireAddonDeclarations } = state.rpc
|
|
65
|
+
if (wireAddonDeclarations.size === 0) return
|
|
66
|
+
|
|
67
|
+
const require = createRequire(join(state.rootDir, 'package.json'))
|
|
68
|
+
|
|
69
|
+
for (const [namespace, decl] of wireAddonDeclarations) {
|
|
70
|
+
try {
|
|
71
|
+
const metaPath = require.resolve(
|
|
72
|
+
`${decl.package}/.pikku/function/pikku-functions-meta.gen.json`
|
|
73
|
+
)
|
|
74
|
+
const schemasDir = join(dirname(metaPath), '..', 'schemas', 'schemas')
|
|
75
|
+
try {
|
|
76
|
+
const schemaFiles = await readdir(schemasDir)
|
|
77
|
+
for (const file of schemaFiles) {
|
|
78
|
+
if (!file.endsWith('.schema.json')) continue
|
|
79
|
+
const schemaName = file.replace('.schema.json', '')
|
|
80
|
+
if (!state.schemas[schemaName]) {
|
|
81
|
+
const schemaRaw = await readFile(join(schemasDir, file), 'utf-8')
|
|
82
|
+
state.schemas[schemaName] = JSON.parse(schemaRaw)
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
} catch {
|
|
86
|
+
// No schemas directory — that's fine
|
|
87
|
+
}
|
|
88
|
+
} catch (error: any) {
|
|
89
|
+
logger.warn(
|
|
90
|
+
`Failed to load addon schemas for '${namespace}' (${decl.package}): ${error.message}`
|
|
91
|
+
)
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
@@ -296,6 +296,31 @@ export function computeResolvedIOTypes(state: InspectorState): void {
|
|
|
296
296
|
}
|
|
297
297
|
|
|
298
298
|
state.resolvedIOTypes[pikkuFuncId] = { inputType, outputType }
|
|
299
|
+
|
|
300
|
+
if (meta.inputSchemaName && inputType !== 'null') {
|
|
301
|
+
meta.inputSchemaName = inputType
|
|
302
|
+
}
|
|
303
|
+
if (meta.outputSchemaName && outputType !== 'null') {
|
|
304
|
+
meta.outputSchemaName = outputType
|
|
305
|
+
}
|
|
306
|
+
if (meta.inputs) {
|
|
307
|
+
meta.inputs = meta.inputs.map((name) => {
|
|
308
|
+
try {
|
|
309
|
+
return functions.typesMap.getTypeMeta(name).uniqueName
|
|
310
|
+
} catch {
|
|
311
|
+
return name
|
|
312
|
+
}
|
|
313
|
+
})
|
|
314
|
+
}
|
|
315
|
+
if (meta.outputs) {
|
|
316
|
+
meta.outputs = meta.outputs.map((name) => {
|
|
317
|
+
try {
|
|
318
|
+
return functions.typesMap.getTypeMeta(name).uniqueName
|
|
319
|
+
} catch {
|
|
320
|
+
return name
|
|
321
|
+
}
|
|
322
|
+
})
|
|
323
|
+
}
|
|
299
324
|
}
|
|
300
325
|
}
|
|
301
326
|
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { FunctionMeta, FunctionsMeta } from '@pikku/core'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Look up function metadata by pikkuFuncId, checking both local functions
|
|
5
|
+
* and addon functions. Addon functions use namespaced IDs like 'namespace:funcName'.
|
|
6
|
+
*/
|
|
7
|
+
export function resolveFunctionMeta(
|
|
8
|
+
state: {
|
|
9
|
+
functions: { meta: FunctionsMeta }
|
|
10
|
+
addonFunctions: Record<string, FunctionsMeta>
|
|
11
|
+
},
|
|
12
|
+
pikkuFuncId: string
|
|
13
|
+
): FunctionMeta | undefined {
|
|
14
|
+
// Check local functions first
|
|
15
|
+
const local = state.functions.meta[pikkuFuncId]
|
|
16
|
+
if (local) return local
|
|
17
|
+
|
|
18
|
+
// Check addon functions (namespaced like 'swaggerPetstore:addPet')
|
|
19
|
+
const colonIndex = pikkuFuncId.indexOf(':')
|
|
20
|
+
if (colonIndex === -1) return undefined
|
|
21
|
+
|
|
22
|
+
const namespace = pikkuFuncId.substring(0, colonIndex)
|
|
23
|
+
const funcName = pikkuFuncId.substring(colonIndex + 1)
|
|
24
|
+
return state.addonFunctions[namespace]?.[funcName]
|
|
25
|
+
}
|