@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.
- package/CHANGELOG.md +26 -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 +327 -59
- package/dist/add/add-http-route.d.ts +19 -10
- package/dist/add/add-http-route.js +153 -44
- 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.d.ts +3 -0
- package/dist/add/add-rpc-invocations.js +65 -25
- 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 +7 -0
- package/dist/add/add-workflow-graph.js +396 -0
- package/dist/add/add-workflow.js +124 -26
- package/dist/error-codes.d.ts +16 -1
- package/dist/error-codes.js +21 -1
- package/dist/index.d.ts +9 -5
- package/dist/index.js +5 -2
- package/dist/inspector.d.ts +1 -1
- package/dist/inspector.js +106 -13
- 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 +180 -30
- 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 +93 -298
- 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 +8 -2
- package/dist/utils/get-property-value.js +33 -4
- 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 +73 -13
- package/dist/utils/serialize-inspector-state.js +102 -6
- 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.d.ts +24 -0
- package/dist/utils/workflow/dsl/deserialize-dsl-workflow.js +830 -0
- package/dist/{workflow/extract-simple-workflow.d.ts → utils/workflow/dsl/extract-dsl-workflow.d.ts} +4 -2
- package/dist/{workflow/extract-simple-workflow.js → utils/workflow/dsl/extract-dsl-workflow.js} +572 -72
- package/dist/utils/workflow/dsl/index.d.ts +7 -0
- package/dist/utils/workflow/dsl/index.js +7 -0
- package/dist/{workflow → utils/workflow/dsl}/patterns.d.ts +21 -0
- package/dist/{workflow → utils/workflow/dsl}/patterns.js +90 -10
- package/dist/{workflow → utils/workflow/dsl}/validation.d.ts +2 -0
- package/dist/{workflow → utils/workflow/dsl}/validation.js +25 -7
- package/dist/utils/workflow/graph/convert-dsl-to-graph.d.ts +13 -0
- package/dist/utils/workflow/graph/convert-dsl-to-graph.js +318 -0
- 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 +8 -0
- package/dist/utils/workflow/graph/index.js +8 -0
- package/dist/utils/workflow/graph/serialize-workflow-graph.d.ts +35 -0
- package/dist/utils/workflow/graph/serialize-workflow-graph.js +150 -0
- package/dist/utils/workflow/graph/workflow-graph.types.d.ts +203 -0
- package/dist/utils/workflow/graph/workflow-graph.types.js +38 -0
- package/dist/visit.js +13 -2
- package/package.json +26 -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 +429 -71
- package/src/add/add-http-route.ts +246 -65
- 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 +78 -31
- 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 +522 -0
- package/src/add/add-workflow.ts +117 -30
- package/src/error-codes.ts +26 -1
- package/src/index.ts +27 -8
- package/src/inspector.ts +145 -17
- package/src/schema-generator.ts +1 -0
- package/src/types-map.ts +12 -1
- package/src/types.ts +192 -51
- 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 +108 -358
- 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 +50 -5
- 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 +181 -20
- 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 +1104 -0
- package/src/{workflow/extract-simple-workflow.ts → utils/workflow/dsl/extract-dsl-workflow.ts} +678 -85
- package/src/utils/workflow/dsl/index.ts +11 -0
- package/src/{workflow → utils/workflow/dsl}/patterns.ts +108 -11
- package/src/{workflow → utils/workflow/dsl}/validation.ts +34 -7
- package/src/utils/workflow/graph/convert-dsl-to-graph.ts +422 -0
- 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 +11 -0
- package/src/utils/workflow/graph/serialize-workflow-graph.ts +216 -0
- package/src/utils/workflow/graph/workflow-graph.types.ts +231 -0
- package/src/visit.ts +14 -2
- package/tsconfig.tsbuildinfo +1 -1
- 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-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,14 +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'
|
|
17
|
+
import { resolvePermissions } from '../utils/permissions.js'
|
|
18
|
+
import { extractWireNames } from '../utils/post-process.js'
|
|
19
|
+
import { ErrorCode } from '../error-codes.js'
|
|
20
|
+
import type { NodeType } from '@pikku/core/node'
|
|
12
21
|
|
|
13
22
|
const isValidVariableName = (name: string) => {
|
|
14
23
|
const regex = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/
|
|
@@ -20,7 +29,8 @@ const nullifyTypes = (type: string | null) => {
|
|
|
20
29
|
type === 'void' ||
|
|
21
30
|
type === 'undefined' ||
|
|
22
31
|
type === 'unknown' ||
|
|
23
|
-
type === 'any'
|
|
32
|
+
type === 'any' ||
|
|
33
|
+
type === 'null'
|
|
24
34
|
) {
|
|
25
35
|
return null
|
|
26
36
|
}
|
|
@@ -152,14 +162,18 @@ const resolveUnionTypes = (
|
|
|
152
162
|
// Check if it's a union type AND not part of an intersection
|
|
153
163
|
if (type.isUnion() && !(type.flags & ts.TypeFlags.Intersection)) {
|
|
154
164
|
for (const t of type.types) {
|
|
155
|
-
const name = nullifyTypes(
|
|
165
|
+
const name = nullifyTypes(
|
|
166
|
+
checker.typeToString(t, undefined, ts.TypeFormatFlags.NoTruncation)
|
|
167
|
+
)
|
|
156
168
|
if (name) {
|
|
157
169
|
types.push(t)
|
|
158
170
|
names.push(name)
|
|
159
171
|
}
|
|
160
172
|
}
|
|
161
173
|
} else {
|
|
162
|
-
const name = nullifyTypes(
|
|
174
|
+
const name = nullifyTypes(
|
|
175
|
+
checker.typeToString(type, undefined, ts.TypeFormatFlags.NoTruncation)
|
|
176
|
+
)
|
|
163
177
|
if (name) {
|
|
164
178
|
types.push(type)
|
|
165
179
|
names.push(name)
|
|
@@ -180,8 +194,8 @@ const getNamesAndTypes = (
|
|
|
180
194
|
return { names: [], types: [] }
|
|
181
195
|
}
|
|
182
196
|
|
|
183
|
-
// 1) Handle an explicit void (or undefined) type up front
|
|
184
|
-
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)) {
|
|
185
199
|
return {
|
|
186
200
|
names: [],
|
|
187
201
|
types: [],
|
|
@@ -199,8 +213,7 @@ const getNamesAndTypes = (
|
|
|
199
213
|
const firstName = rawNames[0]
|
|
200
214
|
if (rawNames.length > 1 || (firstName && !isValidVariableName(firstName))) {
|
|
201
215
|
const aliasType = rawNames.join(' | ')
|
|
202
|
-
const aliasName =
|
|
203
|
-
funcName.charAt(0).toUpperCase() + funcName.slice(1) + direction
|
|
216
|
+
const aliasName = funcIdToTypeName(funcName) + direction
|
|
204
217
|
|
|
205
218
|
// record the alias in your TypesMap
|
|
206
219
|
const references = rawTypes
|
|
@@ -246,7 +259,6 @@ const isPrimitiveType = (type: ts.Type): boolean => {
|
|
|
246
259
|
ts.TypeFlags.Void |
|
|
247
260
|
ts.TypeFlags.Undefined |
|
|
248
261
|
ts.TypeFlags.Null |
|
|
249
|
-
ts.TypeFlags.Any |
|
|
250
262
|
ts.TypeFlags.Unknown |
|
|
251
263
|
ts.TypeFlags.VoidLike
|
|
252
264
|
|
|
@@ -280,7 +292,13 @@ function unwrapPromise(checker: ts.TypeChecker, type: ts.Type): ts.Type {
|
|
|
280
292
|
* Inspect pikkuFunc calls, extract input/output and first-arg destructuring,
|
|
281
293
|
* then push into state.functions.meta.
|
|
282
294
|
*/
|
|
283
|
-
export const addFunctions: AddWiring = (
|
|
295
|
+
export const addFunctions: AddWiring = (
|
|
296
|
+
logger,
|
|
297
|
+
node,
|
|
298
|
+
checker,
|
|
299
|
+
state,
|
|
300
|
+
options
|
|
301
|
+
) => {
|
|
284
302
|
if (!ts.isCallExpression(node)) return
|
|
285
303
|
|
|
286
304
|
const { expression, arguments: args, typeArguments } = node
|
|
@@ -303,16 +321,31 @@ export const addFunctions: AddWiring = (logger, node, checker, state) => {
|
|
|
303
321
|
|
|
304
322
|
if (args.length === 0) return
|
|
305
323
|
|
|
306
|
-
|
|
307
|
-
|
|
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
|
+
}
|
|
308
333
|
|
|
334
|
+
let title: string | undefined
|
|
309
335
|
let tags: string[] | undefined
|
|
310
336
|
let summary: string | undefined
|
|
311
337
|
let description: string | undefined
|
|
312
338
|
let errors: string[] | undefined
|
|
313
339
|
let expose: boolean | undefined
|
|
314
|
-
let
|
|
340
|
+
let remote: boolean | undefined
|
|
341
|
+
let mcp: boolean | undefined
|
|
342
|
+
let requiresApproval: boolean | undefined
|
|
343
|
+
let version: number | undefined
|
|
315
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
|
|
316
349
|
|
|
317
350
|
// Extract the function node using shared utility
|
|
318
351
|
const firstArg = args[0]!
|
|
@@ -322,18 +355,168 @@ export const addFunctions: AddWiring = (logger, node, checker, state) => {
|
|
|
322
355
|
isDirectFunction,
|
|
323
356
|
} = extractFunctionNode(firstArg, checker)
|
|
324
357
|
|
|
358
|
+
// Variables to hold schema references if provided
|
|
359
|
+
let inputSchemaRef: SchemaRef | null = null
|
|
360
|
+
let outputSchemaRef: SchemaRef | null = null
|
|
361
|
+
|
|
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 => {
|
|
368
|
+
const symbol = checker.getSymbolAtLocation(identifier)
|
|
369
|
+
if (!symbol) return null
|
|
370
|
+
|
|
371
|
+
const decl = symbol.valueDeclaration || symbol.declarations?.[0]
|
|
372
|
+
if (!decl) return null
|
|
373
|
+
|
|
374
|
+
let sourceFile: string
|
|
375
|
+
|
|
376
|
+
// If it's an import specifier, resolve the aliased symbol to get the actual source
|
|
377
|
+
if (ts.isImportSpecifier(decl)) {
|
|
378
|
+
const aliasedSymbol = checker.getAliasedSymbol(symbol)
|
|
379
|
+
if (aliasedSymbol) {
|
|
380
|
+
const aliasedDecl =
|
|
381
|
+
aliasedSymbol.valueDeclaration || aliasedSymbol.declarations?.[0]
|
|
382
|
+
if (aliasedDecl) {
|
|
383
|
+
sourceFile = aliasedDecl.getSourceFile().fileName
|
|
384
|
+
} else {
|
|
385
|
+
return null
|
|
386
|
+
}
|
|
387
|
+
} else {
|
|
388
|
+
return null
|
|
389
|
+
}
|
|
390
|
+
} else {
|
|
391
|
+
sourceFile = decl.getSourceFile().fileName
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
const vendor = detectSchemaVendorOrError(
|
|
395
|
+
identifier,
|
|
396
|
+
checker,
|
|
397
|
+
logger,
|
|
398
|
+
context,
|
|
399
|
+
sourceFile
|
|
400
|
+
)
|
|
401
|
+
if (!vendor) return null
|
|
402
|
+
|
|
403
|
+
return {
|
|
404
|
+
variableName: identifier.text,
|
|
405
|
+
sourceFile,
|
|
406
|
+
vendor,
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
325
410
|
// Extract config properties if using object form
|
|
326
411
|
if (ts.isObjectLiteralExpression(firstArg)) {
|
|
327
412
|
objectNode = firstArg
|
|
328
413
|
const metadata = getCommonWireMetaData(firstArg, 'Function', name, logger)
|
|
414
|
+
if (metadata.disabled) return
|
|
415
|
+
title = metadata.title
|
|
329
416
|
tags = metadata.tags
|
|
330
417
|
summary = metadata.summary
|
|
331
418
|
description = metadata.description
|
|
332
419
|
errors = metadata.errors
|
|
333
420
|
expose = getPropertyValue(firstArg, 'expose') as boolean | undefined
|
|
334
|
-
|
|
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
|
+
}
|
|
479
|
+
|
|
480
|
+
// Extract schema variable names from input/output properties
|
|
481
|
+
for (const prop of firstArg.properties) {
|
|
482
|
+
if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name)) {
|
|
483
|
+
const propName = prop.name.text
|
|
484
|
+
if (propName === 'input' || propName === 'output') {
|
|
485
|
+
if (ts.isIdentifier(prop.initializer)) {
|
|
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)
|
|
489
|
+
if (ref) {
|
|
490
|
+
if (propName === 'input') {
|
|
491
|
+
inputSchemaRef = ref
|
|
492
|
+
} else {
|
|
493
|
+
outputSchemaRef = ref
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
} else if (ts.isCallExpression(prop.initializer)) {
|
|
497
|
+
// Bad - it's an inline expression
|
|
498
|
+
const schemaName = `${funcIdToTypeName(name)}${propName.charAt(0).toUpperCase() + propName.slice(1)}`
|
|
499
|
+
logger.critical(
|
|
500
|
+
ErrorCode.INLINE_SCHEMA,
|
|
501
|
+
`Inline schemas are not supported for '${propName}' in '${name}'.\n` +
|
|
502
|
+
` Extract to an exported variable:\n` +
|
|
503
|
+
` export const ${schemaName} = ${prop.initializer.getText()}\n` +
|
|
504
|
+
` Then use: ${propName}: ${schemaName}`
|
|
505
|
+
)
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
}
|
|
335
510
|
}
|
|
336
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
|
+
|
|
337
520
|
// Pick the handler: use resolvedFunc when it exists and is a function, otherwise fall back to handlerNode
|
|
338
521
|
const handler =
|
|
339
522
|
resolvedFunc &&
|
|
@@ -343,10 +526,11 @@ export const addFunctions: AddWiring = (logger, node, checker, state) => {
|
|
|
343
526
|
|
|
344
527
|
// Validate that we got a valid function
|
|
345
528
|
if (!ts.isArrowFunction(handler) && !ts.isFunctionExpression(handler)) {
|
|
346
|
-
logger.error(`• No valid 'func' property found for ${
|
|
529
|
+
logger.error(`• No valid 'func' property found for ${pikkuFuncId}.`)
|
|
347
530
|
// Create stub metadata to prevent "function not found" errors in wirings
|
|
348
|
-
state.functions.meta[
|
|
349
|
-
|
|
531
|
+
state.functions.meta[pikkuFuncId] = {
|
|
532
|
+
pikkuFuncId,
|
|
533
|
+
functionType: 'user',
|
|
350
534
|
name,
|
|
351
535
|
services: { optimized: false, services: [] },
|
|
352
536
|
inputSchemaName: null,
|
|
@@ -377,50 +561,69 @@ export const addFunctions: AddWiring = (logger, node, checker, state) => {
|
|
|
377
561
|
services.services.push(original)
|
|
378
562
|
}
|
|
379
563
|
}
|
|
380
|
-
} else if (
|
|
564
|
+
} else if (
|
|
565
|
+
ts.isIdentifier(firstParam.name) &&
|
|
566
|
+
!firstParam.name.text.startsWith('_')
|
|
567
|
+
) {
|
|
381
568
|
services.optimized = false
|
|
382
569
|
}
|
|
383
570
|
}
|
|
384
571
|
|
|
385
|
-
|
|
386
|
-
const usedWires: string[] = []
|
|
387
|
-
const thirdParam = handler.parameters[2]
|
|
388
|
-
if (thirdParam && ts.isObjectBindingPattern(thirdParam.name)) {
|
|
389
|
-
for (const elem of thirdParam.name.elements) {
|
|
390
|
-
const propertyName =
|
|
391
|
-
elem.propertyName && ts.isIdentifier(elem.propertyName)
|
|
392
|
-
? elem.propertyName.text
|
|
393
|
-
: ts.isIdentifier(elem.name)
|
|
394
|
-
? elem.name.text
|
|
395
|
-
: undefined
|
|
396
|
-
if (propertyName) {
|
|
397
|
-
usedWires.push(propertyName)
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
}
|
|
572
|
+
const wires = extractUsedWires(handler, 2)
|
|
401
573
|
|
|
402
574
|
// --- Generics → ts.Type[], unwrapped from Promise ---
|
|
403
575
|
const genericTypes: ts.Type[] = (typeArguments ?? [])
|
|
404
576
|
.map((tn) => checker.getTypeFromTypeNode(tn))
|
|
405
577
|
.map((t) => unwrapPromise(checker, t))
|
|
406
578
|
|
|
579
|
+
const capitalizedName = funcIdToTypeName(name)
|
|
580
|
+
|
|
407
581
|
// --- Input Extraction ---
|
|
408
|
-
let
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
582
|
+
let inputNames: string[] = []
|
|
583
|
+
let inputTypes: ts.Type[] = []
|
|
584
|
+
|
|
585
|
+
if (inputSchemaRef) {
|
|
586
|
+
const schemaName = `${capitalizedName}Input`
|
|
587
|
+
inputNames = [schemaName]
|
|
588
|
+
state.schemaLookup.set(schemaName, inputSchemaRef)
|
|
589
|
+
state.functions.typesMap.addCustomType(schemaName, 'unknown', [])
|
|
590
|
+
} else if (genericTypes.length >= 1 && genericTypes[0]) {
|
|
591
|
+
// Fall back to extracting from generic type arguments
|
|
592
|
+
const result = getNamesAndTypes(
|
|
593
|
+
checker,
|
|
594
|
+
state.functions.typesMap,
|
|
595
|
+
'Input',
|
|
596
|
+
name,
|
|
597
|
+
genericTypes[0]
|
|
598
|
+
)
|
|
599
|
+
inputNames = result.names
|
|
600
|
+
inputTypes = result.types
|
|
601
|
+
} else {
|
|
602
|
+
// Fall back to extracting from the function's second parameter type
|
|
603
|
+
const secondParam = handler.parameters[1]
|
|
604
|
+
if (secondParam) {
|
|
605
|
+
const paramType = checker.getTypeAtLocation(secondParam)
|
|
606
|
+
const result = getNamesAndTypes(
|
|
607
|
+
checker,
|
|
608
|
+
state.functions.typesMap,
|
|
609
|
+
'Input',
|
|
610
|
+
pikkuFuncId,
|
|
611
|
+
paramType
|
|
612
|
+
)
|
|
613
|
+
inputNames = result.names
|
|
614
|
+
inputTypes = result.types
|
|
615
|
+
}
|
|
616
|
+
}
|
|
420
617
|
|
|
421
618
|
// --- Output Extraction ---
|
|
422
619
|
let outputNames: string[] = []
|
|
423
|
-
|
|
620
|
+
|
|
621
|
+
if (outputSchemaRef) {
|
|
622
|
+
const schemaName = `${capitalizedName}Output`
|
|
623
|
+
outputNames = [schemaName]
|
|
624
|
+
state.schemaLookup.set(schemaName, outputSchemaRef)
|
|
625
|
+
state.functions.typesMap.addCustomType(schemaName, 'unknown', [])
|
|
626
|
+
} else if (genericTypes.length >= 2) {
|
|
424
627
|
outputNames = getNamesAndTypes(
|
|
425
628
|
checker,
|
|
426
629
|
state.functions.typesMap,
|
|
@@ -437,12 +640,55 @@ export const addFunctions: AddWiring = (logger, node, checker, state) => {
|
|
|
437
640
|
checker,
|
|
438
641
|
state.functions.typesMap,
|
|
439
642
|
'Output',
|
|
440
|
-
|
|
643
|
+
pikkuFuncId,
|
|
441
644
|
unwrapped
|
|
442
645
|
).names
|
|
443
646
|
}
|
|
444
647
|
}
|
|
445
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
|
+
|
|
446
692
|
if (inputNames.length > 1) {
|
|
447
693
|
logger.warn(
|
|
448
694
|
'More than one input type detected, only the first one will be used as a schema.'
|
|
@@ -451,47 +697,150 @@ export const addFunctions: AddWiring = (logger, node, checker, state) => {
|
|
|
451
697
|
|
|
452
698
|
// Store the input type for later use
|
|
453
699
|
if (inputTypes.length > 0) {
|
|
454
|
-
state.typesLookup.set(
|
|
700
|
+
state.typesLookup.set(pikkuFuncId, inputTypes)
|
|
455
701
|
}
|
|
456
702
|
|
|
457
703
|
// --- resolve middleware ---
|
|
458
|
-
|
|
704
|
+
let middleware = objectNode
|
|
459
705
|
? resolveMiddleware(state, objectNode, tags, checker)
|
|
460
706
|
: undefined
|
|
461
707
|
|
|
462
|
-
|
|
463
|
-
|
|
708
|
+
// --- resolve permissions ---
|
|
709
|
+
let permissions = objectNode
|
|
710
|
+
? resolvePermissions(state, objectNode, tags, checker)
|
|
711
|
+
: undefined
|
|
712
|
+
|
|
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,
|
|
464
750
|
name,
|
|
465
751
|
services,
|
|
466
|
-
|
|
752
|
+
wires: wires.wires.length > 0 || !wires.optimized ? wires : undefined,
|
|
467
753
|
inputSchemaName: inputNames[0] ?? null,
|
|
468
754
|
outputSchemaName: outputNames[0] ?? null,
|
|
469
755
|
inputs: inputNames.filter((n) => n !== 'void') ?? null,
|
|
470
756
|
outputs: outputNames.filter((n) => n !== 'void') ?? null,
|
|
471
757
|
expose: expose || undefined,
|
|
472
|
-
|
|
758
|
+
remote: remote || undefined,
|
|
759
|
+
mcp: mcpEnabled || undefined,
|
|
760
|
+
requiresApproval: requiresApproval || undefined,
|
|
761
|
+
version,
|
|
762
|
+
title,
|
|
473
763
|
tags: tags || undefined,
|
|
474
764
|
summary,
|
|
475
765
|
description,
|
|
476
766
|
errors,
|
|
477
767
|
middleware,
|
|
768
|
+
permissions,
|
|
478
769
|
isDirectFunction,
|
|
479
770
|
}
|
|
480
771
|
|
|
481
|
-
//
|
|
482
|
-
if (
|
|
483
|
-
state.
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
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
|
+
)
|
|
487
818
|
}
|
|
488
819
|
|
|
489
820
|
// Workflow functions don't get registered as RPC functions,
|
|
490
|
-
// they are their own type handled by add-
|
|
821
|
+
// they are their own type handled by add-workflow
|
|
491
822
|
if (expression.text.includes('Workflow')) {
|
|
492
823
|
return
|
|
493
824
|
}
|
|
494
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
|
+
|
|
495
844
|
if (exportedName || explicitName) {
|
|
496
845
|
if (!exportedName) {
|
|
497
846
|
logger.error(
|
|
@@ -500,28 +849,37 @@ export const addFunctions: AddWiring = (logger, node, checker, state) => {
|
|
|
500
849
|
return
|
|
501
850
|
}
|
|
502
851
|
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
state.rpc.invokedFunctions.add(pikkuFuncName)
|
|
852
|
+
if (remote) {
|
|
853
|
+
state.rpc.invokedFunctions.add(pikkuFuncId)
|
|
506
854
|
}
|
|
507
855
|
|
|
508
856
|
if (expose) {
|
|
509
|
-
state.rpc.exposedMeta[name] =
|
|
857
|
+
state.rpc.exposedMeta[name] = pikkuFuncId
|
|
510
858
|
state.rpc.exposedFiles.set(name, {
|
|
511
859
|
path: node.getSourceFile().fileName,
|
|
512
860
|
exportedName,
|
|
513
861
|
})
|
|
514
862
|
// Track exposed RPC function for service aggregation
|
|
515
|
-
state.serviceAggregation.usedFunctions.add(
|
|
863
|
+
state.serviceAggregation.usedFunctions.add(pikkuFuncId)
|
|
516
864
|
}
|
|
517
865
|
|
|
518
866
|
// We add it to internal meta to allow autocomplete for everything
|
|
519
|
-
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
|
+
}
|
|
520
873
|
|
|
521
874
|
// But we only import the actual function if it's actually invoked to keep
|
|
522
875
|
// bundle size down
|
|
523
|
-
if (
|
|
524
|
-
state.rpc.
|
|
876
|
+
if (
|
|
877
|
+
state.rpc.invokedFunctions.has(pikkuFuncId) ||
|
|
878
|
+
expose ||
|
|
879
|
+
remote ||
|
|
880
|
+
mcpEnabled
|
|
881
|
+
) {
|
|
882
|
+
state.rpc.internalFiles.set(pikkuFuncId, {
|
|
525
883
|
path: node.getSourceFile().fileName,
|
|
526
884
|
exportedName,
|
|
527
885
|
})
|