@pikku/inspector 0.11.2 → 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.
- package/CHANGELOG.md +11 -1
- package/OPTIMIZATION-PLAN.md +195 -0
- package/dist/add/add-ai-agent.d.ts +2 -0
- package/dist/add/add-ai-agent.js +314 -0
- package/dist/add/add-channel.js +69 -61
- package/dist/add/add-cli.js +36 -18
- package/dist/add/add-file-with-factory.js +2 -0
- package/dist/add/add-functions.js +250 -75
- package/dist/add/add-http-route.d.ts +19 -10
- package/dist/add/add-http-route.js +152 -66
- package/dist/add/add-http-routes.d.ts +5 -0
- package/dist/add/add-http-routes.js +159 -0
- package/dist/add/add-keyed-wiring.d.ts +12 -0
- package/dist/add/add-keyed-wiring.js +97 -0
- package/dist/add/add-mcp-prompt.js +14 -9
- package/dist/add/add-mcp-resource.js +14 -9
- package/dist/add/add-middleware.d.ts +1 -4
- package/dist/add/add-middleware.js +364 -79
- package/dist/add/add-permission.d.ts +1 -1
- package/dist/add/add-permission.js +152 -40
- package/dist/add/add-queue-worker.js +18 -12
- package/dist/add/add-rpc-invocations.js +14 -0
- package/dist/add/add-schedule.js +11 -5
- package/dist/add/add-secret.d.ts +3 -0
- package/dist/add/add-secret.js +82 -0
- package/dist/add/add-trigger.d.ts +2 -0
- package/dist/add/add-trigger.js +87 -0
- package/dist/add/add-variable.d.ts +1 -0
- package/dist/add/add-variable.js +8 -0
- package/dist/add/add-workflow-graph.d.ts +3 -2
- package/dist/add/add-workflow-graph.js +143 -406
- package/dist/add/add-workflow.js +6 -4
- package/dist/error-codes.d.ts +14 -1
- package/dist/error-codes.js +19 -1
- package/dist/index.d.ts +9 -8
- package/dist/index.js +5 -4
- package/dist/inspector.d.ts +1 -1
- package/dist/inspector.js +91 -14
- package/dist/schema-generator.d.ts +1 -0
- package/dist/schema-generator.js +1 -0
- package/dist/types-map.js +10 -1
- package/dist/types.d.ts +163 -39
- package/dist/utils/compute-required-schemas.d.ts +4 -0
- package/dist/utils/compute-required-schemas.js +41 -0
- package/dist/utils/contract-hashes.d.ts +35 -0
- package/dist/utils/contract-hashes.js +202 -0
- package/dist/utils/custom-types-generator.d.ts +9 -0
- package/dist/utils/custom-types-generator.js +71 -0
- package/dist/utils/detect-schema-vendor.d.ts +22 -0
- package/dist/utils/detect-schema-vendor.js +76 -0
- package/dist/utils/ensure-function-metadata.d.ts +5 -2
- package/dist/utils/ensure-function-metadata.js +220 -6
- package/dist/utils/extract-function-name.d.ts +5 -16
- package/dist/utils/extract-function-name.js +86 -291
- package/dist/utils/extract-services.d.ts +2 -1
- package/dist/utils/extract-services.js +25 -1
- package/dist/utils/filter-inspector-state.js +107 -23
- package/dist/utils/get-property-value.d.ts +6 -1
- package/dist/utils/get-property-value.js +28 -3
- package/dist/utils/hash.d.ts +2 -0
- package/dist/utils/hash.js +23 -0
- package/dist/utils/middleware.d.ts +7 -30
- package/dist/utils/middleware.js +80 -66
- package/dist/utils/permissions.d.ts +2 -2
- package/dist/utils/permissions.js +10 -10
- package/dist/utils/post-process.d.ts +9 -10
- package/dist/utils/post-process.js +231 -24
- package/dist/utils/resolve-external-package.d.ts +12 -0
- package/dist/utils/resolve-external-package.js +34 -0
- package/dist/utils/resolve-function-types.d.ts +6 -0
- package/dist/utils/resolve-function-types.js +29 -0
- package/dist/utils/resolve-identifier.d.ts +10 -0
- package/dist/utils/resolve-identifier.js +36 -0
- package/dist/utils/resolve-versions.d.ts +2 -0
- package/dist/utils/resolve-versions.js +78 -0
- package/dist/utils/schema-generator.d.ts +9 -0
- package/dist/utils/schema-generator.js +209 -0
- package/dist/utils/serialize-inspector-state.d.ts +59 -22
- package/dist/utils/serialize-inspector-state.js +92 -20
- package/dist/utils/serialize-mcp-json.d.ts +2 -0
- package/dist/utils/serialize-mcp-json.js +99 -0
- package/dist/utils/serialize-middleware-groups-meta.d.ts +12 -0
- package/dist/utils/serialize-middleware-groups-meta.js +28 -0
- package/dist/utils/serialize-openapi-json.d.ts +85 -0
- package/dist/utils/serialize-openapi-json.js +151 -0
- package/dist/utils/serialize-permissions-groups-meta.d.ts +6 -0
- package/dist/utils/serialize-permissions-groups-meta.js +31 -0
- package/dist/utils/workflow/dsl/deserialize-dsl-workflow.js +34 -102
- package/dist/utils/workflow/dsl/extract-dsl-workflow.js +23 -4
- package/dist/utils/workflow/graph/convert-dsl-to-graph.js +12 -10
- package/dist/utils/workflow/graph/finalize-workflow-wires.d.ts +3 -0
- package/dist/utils/workflow/graph/finalize-workflow-wires.js +276 -0
- package/dist/utils/workflow/graph/finalize-workflows.d.ts +2 -0
- package/dist/utils/workflow/graph/finalize-workflows.js +75 -0
- package/dist/utils/workflow/graph/index.d.ts +2 -0
- package/dist/utils/workflow/graph/index.js +2 -0
- package/dist/utils/workflow/graph/serialize-workflow-graph.d.ts +0 -8
- package/dist/utils/workflow/graph/serialize-workflow-graph.js +1 -3
- package/dist/utils/workflow/graph/workflow-graph.types.d.ts +53 -79
- package/dist/utils/workflow/graph/workflow-graph.types.js +1 -1
- package/dist/visit.js +11 -6
- package/package.json +14 -4
- package/src/add/add-ai-agent.ts +468 -0
- package/src/add/add-channel.ts +82 -79
- package/src/add/add-cli.ts +49 -20
- package/src/add/add-file-with-factory.ts +2 -0
- package/src/add/add-functions.ts +330 -86
- package/src/add/add-http-route.ts +245 -88
- package/src/add/add-http-routes.ts +228 -0
- package/src/add/add-keyed-wiring.ts +151 -0
- package/src/add/add-mcp-prompt.ts +26 -15
- package/src/add/add-mcp-resource.ts +27 -15
- package/src/add/add-middleware.ts +482 -80
- package/src/add/add-permission.ts +199 -40
- package/src/add/add-queue-worker.ts +24 -19
- package/src/add/add-rpc-invocations.ts +17 -0
- package/src/add/add-schedule.ts +16 -11
- package/src/add/add-secret.ts +140 -0
- package/src/add/add-trigger.ts +154 -0
- package/src/add/add-variable.ts +9 -0
- package/src/add/add-workflow-graph.ts +180 -522
- package/src/add/add-workflow.ts +5 -4
- package/src/error-codes.ts +24 -1
- package/src/index.ts +22 -13
- package/src/inspector.ts +129 -17
- package/src/schema-generator.ts +1 -0
- package/src/types-map.ts +12 -1
- package/src/types.ts +175 -58
- package/src/utils/compute-required-schemas.ts +49 -0
- package/src/utils/contract-hashes.test.ts +528 -0
- package/src/utils/contract-hashes.ts +290 -0
- package/src/utils/custom-types-generator.ts +88 -0
- package/src/utils/detect-schema-vendor.ts +90 -0
- package/src/utils/ensure-function-metadata.ts +324 -7
- package/src/utils/extract-function-name.ts +101 -351
- package/src/utils/extract-services.ts +35 -2
- package/src/utils/filter-inspector-state.test.ts +34 -20
- package/src/utils/filter-inspector-state.ts +140 -31
- package/src/utils/get-property-value.ts +42 -4
- package/src/utils/hash.ts +26 -0
- package/src/utils/middleware.test.ts +204 -0
- package/src/utils/middleware.ts +129 -67
- package/src/utils/permissions.test.ts +35 -12
- package/src/utils/permissions.ts +10 -10
- package/src/utils/post-process.ts +283 -43
- package/src/utils/resolve-external-package.ts +42 -0
- package/src/utils/resolve-function-types.ts +42 -0
- package/src/utils/resolve-identifier.ts +46 -0
- package/src/utils/resolve-versions.test.ts +249 -0
- package/src/utils/resolve-versions.ts +105 -0
- package/src/utils/schema-generator.ts +329 -0
- package/src/utils/serialize-inspector-state.ts +163 -40
- package/src/utils/serialize-mcp-json.ts +145 -0
- package/src/utils/serialize-middleware-groups-meta.ts +33 -0
- package/src/utils/serialize-openapi-json.ts +277 -0
- package/src/utils/serialize-permissions-groups-meta.ts +35 -0
- package/src/utils/test-data/inspector-state.json +69 -66
- package/src/utils/workflow/dsl/deserialize-dsl-workflow.ts +43 -119
- package/src/utils/workflow/dsl/extract-dsl-workflow.ts +24 -4
- package/src/utils/workflow/graph/convert-dsl-to-graph.ts +17 -10
- package/src/utils/workflow/graph/finalize-workflow-wires.ts +310 -0
- package/src/utils/workflow/graph/finalize-workflows.ts +100 -0
- package/src/utils/workflow/graph/index.ts +5 -0
- package/src/utils/workflow/graph/serialize-workflow-graph.ts +1 -8
- package/src/utils/workflow/graph/workflow-graph.types.ts +29 -78
- package/src/visit.ts +12 -6
- package/tsconfig.tsbuildinfo +1 -1
- package/dist/add/add-forge-credential.d.ts +0 -8
- package/dist/add/add-forge-credential.js +0 -77
- package/dist/add/add-forge-node.d.ts +0 -7
- package/dist/add/add-forge-node.js +0 -77
- package/dist/add/add-mcp-tool.d.ts +0 -2
- package/dist/add/add-mcp-tool.js +0 -81
- package/dist/utils/extract-service-metadata.d.ts +0 -19
- package/dist/utils/extract-service-metadata.js +0 -244
- package/dist/utils/write-service-metadata.d.ts +0 -13
- package/dist/utils/write-service-metadata.js +0 -37
- package/src/add/add-forge-credential.ts +0 -119
- package/src/add/add-forge-node.ts +0 -132
- package/src/add/add-mcp-tool.ts +0 -141
- package/src/utils/extract-service-metadata.ts +0 -353
- package/src/utils/write-service-metadata.ts +0 -51
package/src/add/add-functions.ts
CHANGED
|
@@ -1,16 +1,23 @@
|
|
|
1
1
|
import * as ts from 'typescript'
|
|
2
|
-
import { AddWiring } from '../types.js'
|
|
2
|
+
import { AddWiring, SchemaRef } from '../types.js'
|
|
3
|
+
import { detectSchemaVendorOrError } from '../utils/detect-schema-vendor.js'
|
|
3
4
|
import { TypesMap } from '../types-map.js'
|
|
4
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
extractFunctionName,
|
|
7
|
+
funcIdToTypeName,
|
|
8
|
+
} from '../utils/extract-function-name.js'
|
|
5
9
|
import { extractFunctionNode } from '../utils/extract-function-node.js'
|
|
6
|
-
import {
|
|
10
|
+
import { extractUsedWires } from '../utils/extract-services.js'
|
|
11
|
+
import { FunctionServicesMeta, formatVersionedId } from '@pikku/core'
|
|
7
12
|
import {
|
|
8
13
|
getPropertyValue,
|
|
9
14
|
getCommonWireMetaData,
|
|
10
15
|
} from '../utils/get-property-value.js'
|
|
11
16
|
import { resolveMiddleware } from '../utils/middleware.js'
|
|
12
17
|
import { resolvePermissions } from '../utils/permissions.js'
|
|
18
|
+
import { extractWireNames } from '../utils/post-process.js'
|
|
13
19
|
import { ErrorCode } from '../error-codes.js'
|
|
20
|
+
import type { NodeType } from '@pikku/core/node'
|
|
14
21
|
|
|
15
22
|
const isValidVariableName = (name: string) => {
|
|
16
23
|
const regex = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/
|
|
@@ -22,7 +29,8 @@ const nullifyTypes = (type: string | null) => {
|
|
|
22
29
|
type === 'void' ||
|
|
23
30
|
type === 'undefined' ||
|
|
24
31
|
type === 'unknown' ||
|
|
25
|
-
type === 'any'
|
|
32
|
+
type === 'any' ||
|
|
33
|
+
type === 'null'
|
|
26
34
|
) {
|
|
27
35
|
return null
|
|
28
36
|
}
|
|
@@ -186,8 +194,8 @@ const getNamesAndTypes = (
|
|
|
186
194
|
return { names: [], types: [] }
|
|
187
195
|
}
|
|
188
196
|
|
|
189
|
-
// 1) Handle an explicit void (or undefined) type up front
|
|
190
|
-
if (type.flags & ts.TypeFlags.VoidLike) {
|
|
197
|
+
// 1) Handle an explicit void (or undefined or null) type up front
|
|
198
|
+
if (type.flags & (ts.TypeFlags.VoidLike | ts.TypeFlags.Null)) {
|
|
191
199
|
return {
|
|
192
200
|
names: [],
|
|
193
201
|
types: [],
|
|
@@ -205,8 +213,7 @@ const getNamesAndTypes = (
|
|
|
205
213
|
const firstName = rawNames[0]
|
|
206
214
|
if (rawNames.length > 1 || (firstName && !isValidVariableName(firstName))) {
|
|
207
215
|
const aliasType = rawNames.join(' | ')
|
|
208
|
-
const aliasName =
|
|
209
|
-
funcName.charAt(0).toUpperCase() + funcName.slice(1) + direction
|
|
216
|
+
const aliasName = funcIdToTypeName(funcName) + direction
|
|
210
217
|
|
|
211
218
|
// record the alias in your TypesMap
|
|
212
219
|
const references = rawTypes
|
|
@@ -252,7 +259,6 @@ const isPrimitiveType = (type: ts.Type): boolean => {
|
|
|
252
259
|
ts.TypeFlags.Void |
|
|
253
260
|
ts.TypeFlags.Undefined |
|
|
254
261
|
ts.TypeFlags.Null |
|
|
255
|
-
ts.TypeFlags.Any |
|
|
256
262
|
ts.TypeFlags.Unknown |
|
|
257
263
|
ts.TypeFlags.VoidLike
|
|
258
264
|
|
|
@@ -286,7 +292,13 @@ function unwrapPromise(checker: ts.TypeChecker, type: ts.Type): ts.Type {
|
|
|
286
292
|
* Inspect pikkuFunc calls, extract input/output and first-arg destructuring,
|
|
287
293
|
* then push into state.functions.meta.
|
|
288
294
|
*/
|
|
289
|
-
export const addFunctions: AddWiring = (
|
|
295
|
+
export const addFunctions: AddWiring = (
|
|
296
|
+
logger,
|
|
297
|
+
node,
|
|
298
|
+
checker,
|
|
299
|
+
state,
|
|
300
|
+
options
|
|
301
|
+
) => {
|
|
290
302
|
if (!ts.isCallExpression(node)) return
|
|
291
303
|
|
|
292
304
|
const { expression, arguments: args, typeArguments } = node
|
|
@@ -309,8 +321,15 @@ export const addFunctions: AddWiring = (logger, node, checker, state) => {
|
|
|
309
321
|
|
|
310
322
|
if (args.length === 0) return
|
|
311
323
|
|
|
312
|
-
|
|
313
|
-
|
|
324
|
+
let { pikkuFuncId, name, explicitName, exportedName } = extractFunctionName(
|
|
325
|
+
node,
|
|
326
|
+
checker,
|
|
327
|
+
state.rootDir
|
|
328
|
+
)
|
|
329
|
+
|
|
330
|
+
if (!pikkuFuncId || pikkuFuncId.startsWith('__temp_')) {
|
|
331
|
+
return
|
|
332
|
+
}
|
|
314
333
|
|
|
315
334
|
let title: string | undefined
|
|
316
335
|
let tags: string[] | undefined
|
|
@@ -318,8 +337,15 @@ export const addFunctions: AddWiring = (logger, node, checker, state) => {
|
|
|
318
337
|
let description: string | undefined
|
|
319
338
|
let errors: string[] | undefined
|
|
320
339
|
let expose: boolean | undefined
|
|
321
|
-
let
|
|
340
|
+
let remote: boolean | undefined
|
|
341
|
+
let mcp: boolean | undefined
|
|
342
|
+
let requiresApproval: boolean | undefined
|
|
343
|
+
let version: number | undefined
|
|
322
344
|
let objectNode: ts.ObjectLiteralExpression | undefined
|
|
345
|
+
let nodeDisplayName: string | null = null
|
|
346
|
+
let nodeCategory: string | null = null
|
|
347
|
+
let nodeType: NodeType | null = null
|
|
348
|
+
let nodeErrorOutput: boolean | null = null
|
|
323
349
|
|
|
324
350
|
// Extract the function node using shared utility
|
|
325
351
|
const firstArg = args[0]!
|
|
@@ -329,22 +355,24 @@ export const addFunctions: AddWiring = (logger, node, checker, state) => {
|
|
|
329
355
|
isDirectFunction,
|
|
330
356
|
} = extractFunctionNode(firstArg, checker)
|
|
331
357
|
|
|
332
|
-
// Variables to hold
|
|
333
|
-
let
|
|
334
|
-
|
|
335
|
-
let outputZodSchemaRef: { variableName: string; sourceFile: string } | null =
|
|
336
|
-
null
|
|
358
|
+
// Variables to hold schema references if provided
|
|
359
|
+
let inputSchemaRef: SchemaRef | null = null
|
|
360
|
+
let outputSchemaRef: SchemaRef | null = null
|
|
337
361
|
|
|
338
|
-
// Helper to resolve schema identifier to its actual source file
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
362
|
+
// Helper to resolve schema identifier to its actual source file and detect vendor.
|
|
363
|
+
// Logs a fatal error and returns null if vendor cannot be determined.
|
|
364
|
+
const resolveSchemaRef = (
|
|
365
|
+
identifier: ts.Identifier,
|
|
366
|
+
context: string
|
|
367
|
+
): SchemaRef | null => {
|
|
342
368
|
const symbol = checker.getSymbolAtLocation(identifier)
|
|
343
369
|
if (!symbol) return null
|
|
344
370
|
|
|
345
371
|
const decl = symbol.valueDeclaration || symbol.declarations?.[0]
|
|
346
372
|
if (!decl) return null
|
|
347
373
|
|
|
374
|
+
let sourceFile: string
|
|
375
|
+
|
|
348
376
|
// If it's an import specifier, resolve the aliased symbol to get the actual source
|
|
349
377
|
if (ts.isImportSpecifier(decl)) {
|
|
350
378
|
const aliasedSymbol = checker.getAliasedSymbol(symbol)
|
|
@@ -352,18 +380,30 @@ export const addFunctions: AddWiring = (logger, node, checker, state) => {
|
|
|
352
380
|
const aliasedDecl =
|
|
353
381
|
aliasedSymbol.valueDeclaration || aliasedSymbol.declarations?.[0]
|
|
354
382
|
if (aliasedDecl) {
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
}
|
|
383
|
+
sourceFile = aliasedDecl.getSourceFile().fileName
|
|
384
|
+
} else {
|
|
385
|
+
return null
|
|
359
386
|
}
|
|
387
|
+
} else {
|
|
388
|
+
return null
|
|
360
389
|
}
|
|
390
|
+
} else {
|
|
391
|
+
sourceFile = decl.getSourceFile().fileName
|
|
361
392
|
}
|
|
362
393
|
|
|
363
|
-
|
|
394
|
+
const vendor = detectSchemaVendorOrError(
|
|
395
|
+
identifier,
|
|
396
|
+
checker,
|
|
397
|
+
logger,
|
|
398
|
+
context,
|
|
399
|
+
sourceFile
|
|
400
|
+
)
|
|
401
|
+
if (!vendor) return null
|
|
402
|
+
|
|
364
403
|
return {
|
|
365
404
|
variableName: identifier.text,
|
|
366
|
-
sourceFile
|
|
405
|
+
sourceFile,
|
|
406
|
+
vendor,
|
|
367
407
|
}
|
|
368
408
|
}
|
|
369
409
|
|
|
@@ -371,35 +411,94 @@ export const addFunctions: AddWiring = (logger, node, checker, state) => {
|
|
|
371
411
|
if (ts.isObjectLiteralExpression(firstArg)) {
|
|
372
412
|
objectNode = firstArg
|
|
373
413
|
const metadata = getCommonWireMetaData(firstArg, 'Function', name, logger)
|
|
414
|
+
if (metadata.disabled) return
|
|
374
415
|
title = metadata.title
|
|
375
416
|
tags = metadata.tags
|
|
376
417
|
summary = metadata.summary
|
|
377
418
|
description = metadata.description
|
|
378
419
|
errors = metadata.errors
|
|
379
420
|
expose = getPropertyValue(firstArg, 'expose') as boolean | undefined
|
|
380
|
-
|
|
421
|
+
remote = getPropertyValue(firstArg, 'remote') as boolean | undefined
|
|
422
|
+
mcp = getPropertyValue(firstArg, 'mcp') as boolean | undefined
|
|
423
|
+
requiresApproval = getPropertyValue(firstArg, 'requiresApproval') as
|
|
424
|
+
| boolean
|
|
425
|
+
| undefined
|
|
426
|
+
|
|
427
|
+
const versionRaw = getPropertyValue(firstArg, 'version')
|
|
428
|
+
if (versionRaw !== null && versionRaw !== undefined) {
|
|
429
|
+
const parsed = Number(versionRaw)
|
|
430
|
+
if (Number.isInteger(parsed) && parsed >= 1) {
|
|
431
|
+
version = parsed
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
// Extract node config from nested object
|
|
436
|
+
for (const prop of firstArg.properties) {
|
|
437
|
+
if (
|
|
438
|
+
ts.isPropertyAssignment(prop) &&
|
|
439
|
+
ts.isIdentifier(prop.name) &&
|
|
440
|
+
prop.name.text === 'node' &&
|
|
441
|
+
ts.isObjectLiteralExpression(prop.initializer)
|
|
442
|
+
) {
|
|
443
|
+
const nodeObj = prop.initializer
|
|
444
|
+
nodeDisplayName = getPropertyValue(nodeObj, 'displayName') as
|
|
445
|
+
| string
|
|
446
|
+
| null
|
|
447
|
+
nodeCategory = getPropertyValue(nodeObj, 'category') as string | null
|
|
448
|
+
nodeType = getPropertyValue(nodeObj, 'type') as NodeType | null
|
|
449
|
+
nodeErrorOutput = getPropertyValue(nodeObj, 'errorOutput') as
|
|
450
|
+
| boolean
|
|
451
|
+
| null
|
|
452
|
+
|
|
453
|
+
if (!nodeDisplayName) {
|
|
454
|
+
logger.critical(
|
|
455
|
+
ErrorCode.MISSING_NAME,
|
|
456
|
+
`Function '${name}' node config is missing the required 'displayName' property.`
|
|
457
|
+
)
|
|
458
|
+
}
|
|
459
|
+
if (!nodeCategory) {
|
|
460
|
+
logger.critical(
|
|
461
|
+
ErrorCode.MISSING_NAME,
|
|
462
|
+
`Function '${name}' node config is missing the required 'category' property.`
|
|
463
|
+
)
|
|
464
|
+
}
|
|
465
|
+
if (!nodeType) {
|
|
466
|
+
logger.critical(
|
|
467
|
+
ErrorCode.MISSING_NAME,
|
|
468
|
+
`Function '${name}' node config is missing the required 'type' property.`
|
|
469
|
+
)
|
|
470
|
+
} else if (!['trigger', 'action', 'end'].includes(nodeType)) {
|
|
471
|
+
logger.critical(
|
|
472
|
+
ErrorCode.INVALID_VALUE,
|
|
473
|
+
`Function '${name}' node config has invalid type '${nodeType}'. Must be 'trigger', 'action', or 'end'.`
|
|
474
|
+
)
|
|
475
|
+
}
|
|
476
|
+
break
|
|
477
|
+
}
|
|
478
|
+
}
|
|
381
479
|
|
|
382
|
-
// Extract
|
|
480
|
+
// Extract schema variable names from input/output properties
|
|
383
481
|
for (const prop of firstArg.properties) {
|
|
384
482
|
if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name)) {
|
|
385
483
|
const propName = prop.name.text
|
|
386
484
|
if (propName === 'input' || propName === 'output') {
|
|
387
485
|
if (ts.isIdentifier(prop.initializer)) {
|
|
388
|
-
// Good - it's a variable reference, resolve its actual source file
|
|
389
|
-
const
|
|
486
|
+
// Good - it's a variable reference, resolve its actual source file and vendor
|
|
487
|
+
const context = `Function '${name}' ${propName}`
|
|
488
|
+
const ref = resolveSchemaRef(prop.initializer, context)
|
|
390
489
|
if (ref) {
|
|
391
490
|
if (propName === 'input') {
|
|
392
|
-
|
|
491
|
+
inputSchemaRef = ref
|
|
393
492
|
} else {
|
|
394
|
-
|
|
493
|
+
outputSchemaRef = ref
|
|
395
494
|
}
|
|
396
495
|
}
|
|
397
496
|
} else if (ts.isCallExpression(prop.initializer)) {
|
|
398
497
|
// Bad - it's an inline expression
|
|
399
|
-
const schemaName = `${
|
|
498
|
+
const schemaName = `${funcIdToTypeName(name)}${propName.charAt(0).toUpperCase() + propName.slice(1)}`
|
|
400
499
|
logger.critical(
|
|
401
|
-
ErrorCode.
|
|
402
|
-
`Inline
|
|
500
|
+
ErrorCode.INLINE_SCHEMA,
|
|
501
|
+
`Inline schemas are not supported for '${propName}' in '${name}'.\n` +
|
|
403
502
|
` Extract to an exported variable:\n` +
|
|
404
503
|
` export const ${schemaName} = ${prop.initializer.getText()}\n` +
|
|
405
504
|
` Then use: ${propName}: ${schemaName}`
|
|
@@ -410,6 +509,14 @@ export const addFunctions: AddWiring = (logger, node, checker, state) => {
|
|
|
410
509
|
}
|
|
411
510
|
}
|
|
412
511
|
|
|
512
|
+
if (version !== undefined) {
|
|
513
|
+
const baseName = explicitName || exportedName || pikkuFuncId
|
|
514
|
+
pikkuFuncId = formatVersionedId(baseName, version)
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
const isMCPToolFunc = expression.text === 'pikkuMCPToolFunc'
|
|
518
|
+
const mcpEnabled = mcp || isMCPToolFunc
|
|
519
|
+
|
|
413
520
|
// Pick the handler: use resolvedFunc when it exists and is a function, otherwise fall back to handlerNode
|
|
414
521
|
const handler =
|
|
415
522
|
resolvedFunc &&
|
|
@@ -419,10 +526,11 @@ export const addFunctions: AddWiring = (logger, node, checker, state) => {
|
|
|
419
526
|
|
|
420
527
|
// Validate that we got a valid function
|
|
421
528
|
if (!ts.isArrowFunction(handler) && !ts.isFunctionExpression(handler)) {
|
|
422
|
-
logger.error(`• No valid 'func' property found for ${
|
|
529
|
+
logger.error(`• No valid 'func' property found for ${pikkuFuncId}.`)
|
|
423
530
|
// Create stub metadata to prevent "function not found" errors in wirings
|
|
424
|
-
state.functions.meta[
|
|
425
|
-
|
|
531
|
+
state.functions.meta[pikkuFuncId] = {
|
|
532
|
+
pikkuFuncId,
|
|
533
|
+
functionType: 'user',
|
|
426
534
|
name,
|
|
427
535
|
services: { optimized: false, services: [] },
|
|
428
536
|
inputSchemaName: null,
|
|
@@ -453,43 +561,31 @@ export const addFunctions: AddWiring = (logger, node, checker, state) => {
|
|
|
453
561
|
services.services.push(original)
|
|
454
562
|
}
|
|
455
563
|
}
|
|
456
|
-
} else if (
|
|
564
|
+
} else if (
|
|
565
|
+
ts.isIdentifier(firstParam.name) &&
|
|
566
|
+
!firstParam.name.text.startsWith('_')
|
|
567
|
+
) {
|
|
457
568
|
services.optimized = false
|
|
458
569
|
}
|
|
459
570
|
}
|
|
460
571
|
|
|
461
|
-
|
|
462
|
-
const usedWires: string[] = []
|
|
463
|
-
const thirdParam = handler.parameters[2]
|
|
464
|
-
if (thirdParam && ts.isObjectBindingPattern(thirdParam.name)) {
|
|
465
|
-
for (const elem of thirdParam.name.elements) {
|
|
466
|
-
const propertyName =
|
|
467
|
-
elem.propertyName && ts.isIdentifier(elem.propertyName)
|
|
468
|
-
? elem.propertyName.text
|
|
469
|
-
: ts.isIdentifier(elem.name)
|
|
470
|
-
? elem.name.text
|
|
471
|
-
: undefined
|
|
472
|
-
if (propertyName) {
|
|
473
|
-
usedWires.push(propertyName)
|
|
474
|
-
}
|
|
475
|
-
}
|
|
476
|
-
}
|
|
572
|
+
const wires = extractUsedWires(handler, 2)
|
|
477
573
|
|
|
478
574
|
// --- Generics → ts.Type[], unwrapped from Promise ---
|
|
479
575
|
const genericTypes: ts.Type[] = (typeArguments ?? [])
|
|
480
576
|
.map((tn) => checker.getTypeFromTypeNode(tn))
|
|
481
577
|
.map((t) => unwrapPromise(checker, t))
|
|
482
578
|
|
|
483
|
-
const capitalizedName =
|
|
579
|
+
const capitalizedName = funcIdToTypeName(name)
|
|
484
580
|
|
|
485
581
|
// --- Input Extraction ---
|
|
486
582
|
let inputNames: string[] = []
|
|
487
583
|
let inputTypes: ts.Type[] = []
|
|
488
584
|
|
|
489
|
-
if (
|
|
585
|
+
if (inputSchemaRef) {
|
|
490
586
|
const schemaName = `${capitalizedName}Input`
|
|
491
587
|
inputNames = [schemaName]
|
|
492
|
-
state.
|
|
588
|
+
state.schemaLookup.set(schemaName, inputSchemaRef)
|
|
493
589
|
state.functions.typesMap.addCustomType(schemaName, 'unknown', [])
|
|
494
590
|
} else if (genericTypes.length >= 1 && genericTypes[0]) {
|
|
495
591
|
// Fall back to extracting from generic type arguments
|
|
@@ -511,7 +607,7 @@ export const addFunctions: AddWiring = (logger, node, checker, state) => {
|
|
|
511
607
|
checker,
|
|
512
608
|
state.functions.typesMap,
|
|
513
609
|
'Input',
|
|
514
|
-
|
|
610
|
+
pikkuFuncId,
|
|
515
611
|
paramType
|
|
516
612
|
)
|
|
517
613
|
inputNames = result.names
|
|
@@ -522,10 +618,10 @@ export const addFunctions: AddWiring = (logger, node, checker, state) => {
|
|
|
522
618
|
// --- Output Extraction ---
|
|
523
619
|
let outputNames: string[] = []
|
|
524
620
|
|
|
525
|
-
if (
|
|
621
|
+
if (outputSchemaRef) {
|
|
526
622
|
const schemaName = `${capitalizedName}Output`
|
|
527
623
|
outputNames = [schemaName]
|
|
528
|
-
state.
|
|
624
|
+
state.schemaLookup.set(schemaName, outputSchemaRef)
|
|
529
625
|
state.functions.typesMap.addCustomType(schemaName, 'unknown', [])
|
|
530
626
|
} else if (genericTypes.length >= 2) {
|
|
531
627
|
outputNames = getNamesAndTypes(
|
|
@@ -544,12 +640,55 @@ export const addFunctions: AddWiring = (logger, node, checker, state) => {
|
|
|
544
640
|
checker,
|
|
545
641
|
state.functions.typesMap,
|
|
546
642
|
'Output',
|
|
547
|
-
|
|
643
|
+
pikkuFuncId,
|
|
548
644
|
unwrapped
|
|
549
645
|
).names
|
|
550
646
|
}
|
|
551
647
|
}
|
|
552
648
|
|
|
649
|
+
const mcpOutputTypes: Record<string, string> = {
|
|
650
|
+
pikkuMCPResourceFunc: 'MCPResourceResponse',
|
|
651
|
+
pikkuMCPToolFunc: 'MCPToolResponse',
|
|
652
|
+
pikkuMCPPromptFunc: 'MCPPromptResponse',
|
|
653
|
+
}
|
|
654
|
+
const mcpOutputType = mcpOutputTypes[expression.text]
|
|
655
|
+
if (mcpOutputType && outputNames[0] !== mcpOutputType) {
|
|
656
|
+
let resolved = false
|
|
657
|
+
const rawSymbol = checker.getSymbolAtLocation(expression)
|
|
658
|
+
const funcSymbol =
|
|
659
|
+
rawSymbol && rawSymbol.flags & ts.SymbolFlags.Alias
|
|
660
|
+
? checker.getAliasedSymbol(rawSymbol)
|
|
661
|
+
: rawSymbol
|
|
662
|
+
const funcDecls = funcSymbol?.getDeclarations() || []
|
|
663
|
+
for (const funcDecl of funcDecls) {
|
|
664
|
+
if (resolved) break
|
|
665
|
+
const mcpTypeSymbol = checker.resolveName(
|
|
666
|
+
mcpOutputType,
|
|
667
|
+
funcDecl,
|
|
668
|
+
ts.SymbolFlags.Type,
|
|
669
|
+
false
|
|
670
|
+
)
|
|
671
|
+
if (mcpTypeSymbol) {
|
|
672
|
+
const aliased =
|
|
673
|
+
mcpTypeSymbol.flags & ts.SymbolFlags.Alias
|
|
674
|
+
? checker.getAliasedSymbol(mcpTypeSymbol)
|
|
675
|
+
: mcpTypeSymbol
|
|
676
|
+
const typeDecl = aliased?.getDeclarations()?.[0]
|
|
677
|
+
if (typeDecl) {
|
|
678
|
+
const path = typeDecl.getSourceFile().fileName
|
|
679
|
+
if (!state.functions.typesMap.exists(mcpOutputType, path)) {
|
|
680
|
+
state.functions.typesMap.addType(mcpOutputType, path)
|
|
681
|
+
}
|
|
682
|
+
resolved = true
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
if (!resolved) {
|
|
687
|
+
state.functions.typesMap.addCustomType(mcpOutputType, mcpOutputType, [])
|
|
688
|
+
}
|
|
689
|
+
outputNames = [mcpOutputType]
|
|
690
|
+
}
|
|
691
|
+
|
|
553
692
|
if (inputNames.length > 1) {
|
|
554
693
|
logger.warn(
|
|
555
694
|
'More than one input type detected, only the first one will be used as a schema.'
|
|
@@ -558,30 +697,68 @@ export const addFunctions: AddWiring = (logger, node, checker, state) => {
|
|
|
558
697
|
|
|
559
698
|
// Store the input type for later use
|
|
560
699
|
if (inputTypes.length > 0) {
|
|
561
|
-
state.typesLookup.set(
|
|
700
|
+
state.typesLookup.set(pikkuFuncId, inputTypes)
|
|
562
701
|
}
|
|
563
702
|
|
|
564
703
|
// --- resolve middleware ---
|
|
565
|
-
|
|
704
|
+
let middleware = objectNode
|
|
566
705
|
? resolveMiddleware(state, objectNode, tags, checker)
|
|
567
706
|
: undefined
|
|
568
707
|
|
|
569
708
|
// --- resolve permissions ---
|
|
570
|
-
|
|
709
|
+
let permissions = objectNode
|
|
571
710
|
? resolvePermissions(state, objectNode, tags, checker)
|
|
572
711
|
: undefined
|
|
573
712
|
|
|
574
|
-
|
|
575
|
-
|
|
713
|
+
if (options.tags?.length) {
|
|
714
|
+
tags = [...new Set([...(tags || []), ...options.tags])]
|
|
715
|
+
const tagEntries = options.tags.map((tag) => ({
|
|
716
|
+
type: 'tag' as const,
|
|
717
|
+
tag,
|
|
718
|
+
}))
|
|
719
|
+
const existingMiddlewareTags = new Set(
|
|
720
|
+
(middleware || [])
|
|
721
|
+
.filter((m) => m.type === 'tag')
|
|
722
|
+
.map((m) => (m as any).tag)
|
|
723
|
+
)
|
|
724
|
+
const newMiddleware = tagEntries.filter(
|
|
725
|
+
(e) => !existingMiddlewareTags.has(e.tag)
|
|
726
|
+
)
|
|
727
|
+
if (newMiddleware.length > 0) {
|
|
728
|
+
middleware = [...(middleware || []), ...newMiddleware]
|
|
729
|
+
}
|
|
730
|
+
const existingPermissionTags = new Set(
|
|
731
|
+
(permissions || [])
|
|
732
|
+
.filter((p) => p.type === 'tag')
|
|
733
|
+
.map((p) => (p as any).tag)
|
|
734
|
+
)
|
|
735
|
+
const newPermissions = tagEntries.filter(
|
|
736
|
+
(e) => !existingPermissionTags.has(e.tag)
|
|
737
|
+
)
|
|
738
|
+
if (newPermissions.length > 0) {
|
|
739
|
+
permissions = [...(permissions || []), ...newPermissions]
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
const sessionless = expression.text !== 'pikkuFunc'
|
|
744
|
+
|
|
745
|
+
state.functions.meta[pikkuFuncId] = {
|
|
746
|
+
pikkuFuncId,
|
|
747
|
+
functionType: 'user',
|
|
748
|
+
funcWrapper: expression.text,
|
|
749
|
+
sessionless,
|
|
576
750
|
name,
|
|
577
751
|
services,
|
|
578
|
-
|
|
752
|
+
wires: wires.wires.length > 0 || !wires.optimized ? wires : undefined,
|
|
579
753
|
inputSchemaName: inputNames[0] ?? null,
|
|
580
754
|
outputSchemaName: outputNames[0] ?? null,
|
|
581
755
|
inputs: inputNames.filter((n) => n !== 'void') ?? null,
|
|
582
756
|
outputs: outputNames.filter((n) => n !== 'void') ?? null,
|
|
583
757
|
expose: expose || undefined,
|
|
584
|
-
|
|
758
|
+
remote: remote || undefined,
|
|
759
|
+
mcp: mcpEnabled || undefined,
|
|
760
|
+
requiresApproval: requiresApproval || undefined,
|
|
761
|
+
version,
|
|
585
762
|
title,
|
|
586
763
|
tags: tags || undefined,
|
|
587
764
|
summary,
|
|
@@ -592,20 +769,78 @@ export const addFunctions: AddWiring = (logger, node, checker, state) => {
|
|
|
592
769
|
isDirectFunction,
|
|
593
770
|
}
|
|
594
771
|
|
|
595
|
-
//
|
|
596
|
-
if (
|
|
597
|
-
state.
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
772
|
+
// Populate node metadata if node config is present
|
|
773
|
+
if (nodeDisplayName && nodeCategory && nodeType) {
|
|
774
|
+
state.nodes.files.add(node.getSourceFile().fileName)
|
|
775
|
+
state.nodes.meta[pikkuFuncId] = {
|
|
776
|
+
name: pikkuFuncId,
|
|
777
|
+
displayName: nodeDisplayName,
|
|
778
|
+
category: nodeCategory,
|
|
779
|
+
type: nodeType,
|
|
780
|
+
rpc: pikkuFuncId,
|
|
781
|
+
description,
|
|
782
|
+
errorOutput: nodeErrorOutput ?? false,
|
|
783
|
+
inputSchemaName: inputNames[0] ?? null,
|
|
784
|
+
outputSchemaName: outputNames[0] ?? null,
|
|
785
|
+
tags,
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
if (mcpEnabled) {
|
|
790
|
+
if (!description) {
|
|
791
|
+
logger.critical(
|
|
792
|
+
ErrorCode.MISSING_DESCRIPTION,
|
|
793
|
+
`MCP tool '${name}' is missing a description.`
|
|
794
|
+
)
|
|
795
|
+
return
|
|
796
|
+
}
|
|
797
|
+
state.mcpEndpoints.files.add(node.getSourceFile().fileName)
|
|
798
|
+
state.mcpEndpoints.toolsMeta[name] = {
|
|
799
|
+
pikkuFuncId,
|
|
800
|
+
name,
|
|
801
|
+
title: title || undefined,
|
|
802
|
+
description,
|
|
803
|
+
summary,
|
|
804
|
+
errors,
|
|
805
|
+
tags,
|
|
806
|
+
inputSchema: inputNames[0] ?? null,
|
|
807
|
+
outputSchema: outputNames[0] ?? null,
|
|
808
|
+
middleware,
|
|
809
|
+
permissions,
|
|
810
|
+
}
|
|
811
|
+
state.serviceAggregation.usedFunctions.add(pikkuFuncId)
|
|
812
|
+
extractWireNames(middleware).forEach((n) =>
|
|
813
|
+
state.serviceAggregation.usedMiddleware.add(n)
|
|
814
|
+
)
|
|
815
|
+
extractWireNames(permissions).forEach((n) =>
|
|
816
|
+
state.serviceAggregation.usedPermissions.add(n)
|
|
817
|
+
)
|
|
601
818
|
}
|
|
602
819
|
|
|
603
820
|
// Workflow functions don't get registered as RPC functions,
|
|
604
|
-
// they are their own type handled by add-
|
|
821
|
+
// they are their own type handled by add-workflow
|
|
605
822
|
if (expression.text.includes('Workflow')) {
|
|
606
823
|
return
|
|
607
824
|
}
|
|
608
825
|
|
|
826
|
+
// Trigger and channel connect/disconnect functions are not callable via RPC
|
|
827
|
+
const nonRPCPatterns = [
|
|
828
|
+
/Trigger/i,
|
|
829
|
+
/ChannelConnection/i,
|
|
830
|
+
/ChannelDisconnection/i,
|
|
831
|
+
]
|
|
832
|
+
if (nonRPCPatterns.some((pattern) => pattern.test(expression.text))) {
|
|
833
|
+
return
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
// Store function file location for wiring generation
|
|
837
|
+
if (exportedName) {
|
|
838
|
+
state.functions.files.set(pikkuFuncId, {
|
|
839
|
+
path: node.getSourceFile().fileName,
|
|
840
|
+
exportedName,
|
|
841
|
+
})
|
|
842
|
+
}
|
|
843
|
+
|
|
609
844
|
if (exportedName || explicitName) {
|
|
610
845
|
if (!exportedName) {
|
|
611
846
|
logger.error(
|
|
@@ -614,28 +849,37 @@ export const addFunctions: AddWiring = (logger, node, checker, state) => {
|
|
|
614
849
|
return
|
|
615
850
|
}
|
|
616
851
|
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
state.rpc.invokedFunctions.add(pikkuFuncName)
|
|
852
|
+
if (remote) {
|
|
853
|
+
state.rpc.invokedFunctions.add(pikkuFuncId)
|
|
620
854
|
}
|
|
621
855
|
|
|
622
856
|
if (expose) {
|
|
623
|
-
state.rpc.exposedMeta[name] =
|
|
857
|
+
state.rpc.exposedMeta[name] = pikkuFuncId
|
|
624
858
|
state.rpc.exposedFiles.set(name, {
|
|
625
859
|
path: node.getSourceFile().fileName,
|
|
626
860
|
exportedName,
|
|
627
861
|
})
|
|
628
862
|
// Track exposed RPC function for service aggregation
|
|
629
|
-
state.serviceAggregation.usedFunctions.add(
|
|
863
|
+
state.serviceAggregation.usedFunctions.add(pikkuFuncId)
|
|
630
864
|
}
|
|
631
865
|
|
|
632
866
|
// We add it to internal meta to allow autocomplete for everything
|
|
633
|
-
state.rpc.internalMeta[name] =
|
|
867
|
+
state.rpc.internalMeta[name] = pikkuFuncId
|
|
868
|
+
|
|
869
|
+
if (version !== undefined) {
|
|
870
|
+
state.rpc.internalMeta[pikkuFuncId] = pikkuFuncId
|
|
871
|
+
state.rpc.invokedFunctions.add(pikkuFuncId)
|
|
872
|
+
}
|
|
634
873
|
|
|
635
874
|
// But we only import the actual function if it's actually invoked to keep
|
|
636
875
|
// bundle size down
|
|
637
|
-
if (
|
|
638
|
-
state.rpc.
|
|
876
|
+
if (
|
|
877
|
+
state.rpc.invokedFunctions.has(pikkuFuncId) ||
|
|
878
|
+
expose ||
|
|
879
|
+
remote ||
|
|
880
|
+
mcpEnabled
|
|
881
|
+
) {
|
|
882
|
+
state.rpc.internalFiles.set(pikkuFuncId, {
|
|
639
883
|
path: node.getSourceFile().fileName,
|
|
640
884
|
exportedName,
|
|
641
885
|
})
|