@pikku/core 0.12.3 → 0.12.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (218) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/dist/services/in-memory-workflow-service.d.ts +15 -2
  3. package/dist/services/in-memory-workflow-service.js +49 -0
  4. package/lcov.info +18891 -0
  5. package/package.json +1 -1
  6. package/src/crypto-utils.test.ts +0 -214
  7. package/src/crypto-utils.ts +0 -213
  8. package/src/errors/error-handler.ts +0 -62
  9. package/src/errors/error.test.ts +0 -195
  10. package/src/errors/errors.ts +0 -374
  11. package/src/errors/index.ts +0 -2
  12. package/src/factory-functions.test.ts +0 -109
  13. package/src/function/function-runner.test.ts +0 -536
  14. package/src/function/function-runner.ts +0 -365
  15. package/src/function/functions.types.ts +0 -304
  16. package/src/function/index.ts +0 -5
  17. package/src/handle-error.test.ts +0 -424
  18. package/src/handle-error.ts +0 -69
  19. package/src/index.ts +0 -118
  20. package/src/internal.ts +0 -6
  21. package/src/middleware/auth-apikey.test.ts +0 -363
  22. package/src/middleware/auth-apikey.ts +0 -47
  23. package/src/middleware/auth-bearer.test.ts +0 -450
  24. package/src/middleware/auth-bearer.ts +0 -72
  25. package/src/middleware/auth-cookie.test.ts +0 -528
  26. package/src/middleware/auth-cookie.ts +0 -80
  27. package/src/middleware/cors.test.ts +0 -424
  28. package/src/middleware/cors.ts +0 -104
  29. package/src/middleware/index.ts +0 -5
  30. package/src/middleware/remote-auth.test.ts +0 -488
  31. package/src/middleware/remote-auth.ts +0 -68
  32. package/src/middleware/timeout.ts +0 -15
  33. package/src/middleware-runner.test.ts +0 -418
  34. package/src/middleware-runner.ts +0 -234
  35. package/src/permissions.test.ts +0 -434
  36. package/src/permissions.ts +0 -319
  37. package/src/pikku-request.ts +0 -23
  38. package/src/pikku-response.ts +0 -5
  39. package/src/pikku-state.test.ts +0 -224
  40. package/src/pikku-state.ts +0 -216
  41. package/src/run-tests-script.test.ts +0 -49
  42. package/src/schema.test.ts +0 -249
  43. package/src/schema.ts +0 -151
  44. package/src/services/ai-agent-runner-service.ts +0 -41
  45. package/src/services/ai-run-state-service.ts +0 -20
  46. package/src/services/ai-storage-service.ts +0 -39
  47. package/src/services/content-service.ts +0 -76
  48. package/src/services/deployment-service.ts +0 -22
  49. package/src/services/gateway-service.ts +0 -20
  50. package/src/services/gopass-secrets.ts +0 -98
  51. package/src/services/in-memory-ai-run-state-service.ts +0 -73
  52. package/src/services/in-memory-trigger-service.ts +0 -64
  53. package/src/services/in-memory-workflow-service.test.ts +0 -351
  54. package/src/services/in-memory-workflow-service.ts +0 -438
  55. package/src/services/index.ts +0 -56
  56. package/src/services/jwt-service.ts +0 -30
  57. package/src/services/local-content.ts +0 -120
  58. package/src/services/local-gateway-service.ts +0 -62
  59. package/src/services/local-secrets.test.ts +0 -80
  60. package/src/services/local-secrets.ts +0 -94
  61. package/src/services/local-variables.test.ts +0 -61
  62. package/src/services/local-variables.ts +0 -39
  63. package/src/services/logger-console.test.ts +0 -118
  64. package/src/services/logger-console.ts +0 -98
  65. package/src/services/logger.ts +0 -57
  66. package/src/services/scheduler-service.ts +0 -86
  67. package/src/services/schema-service.ts +0 -33
  68. package/src/services/scoped-secret-service.test.ts +0 -74
  69. package/src/services/scoped-secret-service.ts +0 -41
  70. package/src/services/secret-service.ts +0 -38
  71. package/src/services/trigger-service.ts +0 -17
  72. package/src/services/typed-secret-service.test.ts +0 -93
  73. package/src/services/typed-secret-service.ts +0 -70
  74. package/src/services/typed-variables-service.test.ts +0 -73
  75. package/src/services/typed-variables-service.ts +0 -81
  76. package/src/services/user-session-service.test.ts +0 -113
  77. package/src/services/user-session-service.ts +0 -86
  78. package/src/services/variables-service.ts +0 -11
  79. package/src/services/workflow-service.ts +0 -95
  80. package/src/testing/index.ts +0 -2
  81. package/src/testing/service-tests.ts +0 -873
  82. package/src/time-utils.test.ts +0 -56
  83. package/src/time-utils.ts +0 -107
  84. package/src/types/core.types.ts +0 -478
  85. package/src/types/state.types.ts +0 -188
  86. package/src/utils/hash.test.ts +0 -68
  87. package/src/utils/hash.ts +0 -26
  88. package/src/utils.test.ts +0 -350
  89. package/src/utils.ts +0 -129
  90. package/src/version.test.ts +0 -80
  91. package/src/version.ts +0 -25
  92. package/src/wirings/ai-agent/agent-dynamic-workflow.ts +0 -469
  93. package/src/wirings/ai-agent/ai-agent-helpers.test.ts +0 -152
  94. package/src/wirings/ai-agent/ai-agent-helpers.ts +0 -76
  95. package/src/wirings/ai-agent/ai-agent-memory.ts +0 -310
  96. package/src/wirings/ai-agent/ai-agent-model-config.test.ts +0 -115
  97. package/src/wirings/ai-agent/ai-agent-model-config.ts +0 -43
  98. package/src/wirings/ai-agent/ai-agent-prepare.ts +0 -636
  99. package/src/wirings/ai-agent/ai-agent-registry.test.ts +0 -37
  100. package/src/wirings/ai-agent/ai-agent-registry.ts +0 -82
  101. package/src/wirings/ai-agent/ai-agent-runner.test.ts +0 -319
  102. package/src/wirings/ai-agent/ai-agent-runner.ts +0 -773
  103. package/src/wirings/ai-agent/ai-agent-stream.test.ts +0 -859
  104. package/src/wirings/ai-agent/ai-agent-stream.ts +0 -1153
  105. package/src/wirings/ai-agent/ai-agent.types.ts +0 -382
  106. package/src/wirings/ai-agent/index.ts +0 -37
  107. package/src/wirings/channel/channel-common.ts +0 -90
  108. package/src/wirings/channel/channel-handler.ts +0 -250
  109. package/src/wirings/channel/channel-middleware-runner.ts +0 -120
  110. package/src/wirings/channel/channel-runner.ts +0 -166
  111. package/src/wirings/channel/channel-store.ts +0 -29
  112. package/src/wirings/channel/channel.types.ts +0 -172
  113. package/src/wirings/channel/define-channel-routes.ts +0 -25
  114. package/src/wirings/channel/eventhub-service.ts +0 -34
  115. package/src/wirings/channel/eventhub-store.ts +0 -13
  116. package/src/wirings/channel/index.ts +0 -24
  117. package/src/wirings/channel/local/index.ts +0 -3
  118. package/src/wirings/channel/local/local-channel-handler.ts +0 -81
  119. package/src/wirings/channel/local/local-channel-runner.test.ts +0 -215
  120. package/src/wirings/channel/local/local-channel-runner.ts +0 -210
  121. package/src/wirings/channel/local/local-eventhub-service.test.ts +0 -95
  122. package/src/wirings/channel/local/local-eventhub-service.ts +0 -101
  123. package/src/wirings/channel/log-channels.ts +0 -20
  124. package/src/wirings/channel/pikku-abstract-channel-handler.test.ts +0 -54
  125. package/src/wirings/channel/pikku-abstract-channel-handler.ts +0 -45
  126. package/src/wirings/channel/serverless/index.ts +0 -5
  127. package/src/wirings/channel/serverless/serverless-channel-runner.ts +0 -289
  128. package/src/wirings/cli/channel/cli-channel-runner.ts +0 -136
  129. package/src/wirings/cli/channel/index.ts +0 -1
  130. package/src/wirings/cli/cli-runner.test.ts +0 -403
  131. package/src/wirings/cli/cli-runner.ts +0 -529
  132. package/src/wirings/cli/cli.types.ts +0 -358
  133. package/src/wirings/cli/command-parser.test.ts +0 -445
  134. package/src/wirings/cli/command-parser.ts +0 -504
  135. package/src/wirings/cli/define-cli-commands.ts +0 -24
  136. package/src/wirings/cli/index.ts +0 -19
  137. package/src/wirings/gateway/gateway-runner.test.ts +0 -474
  138. package/src/wirings/gateway/gateway-runner.ts +0 -411
  139. package/src/wirings/gateway/gateway.types.ts +0 -149
  140. package/src/wirings/gateway/index.ts +0 -13
  141. package/src/wirings/http/http-routes.test.ts +0 -322
  142. package/src/wirings/http/http-routes.ts +0 -197
  143. package/src/wirings/http/http-runner.test.ts +0 -144
  144. package/src/wirings/http/http-runner.ts +0 -588
  145. package/src/wirings/http/http.types.ts +0 -369
  146. package/src/wirings/http/index.ts +0 -28
  147. package/src/wirings/http/log-http-routes.ts +0 -22
  148. package/src/wirings/http/pikku-fetch-http-request.test.ts +0 -237
  149. package/src/wirings/http/pikku-fetch-http-request.ts +0 -170
  150. package/src/wirings/http/pikku-fetch-http-response.test.ts +0 -82
  151. package/src/wirings/http/pikku-fetch-http-response.ts +0 -147
  152. package/src/wirings/http/routers/http-router.ts +0 -13
  153. package/src/wirings/http/routers/path-to-regex.test.ts +0 -319
  154. package/src/wirings/http/routers/path-to-regex.ts +0 -126
  155. package/src/wirings/http/web-request.test.ts +0 -236
  156. package/src/wirings/http/web-request.ts +0 -104
  157. package/src/wirings/mcp/index.ts +0 -27
  158. package/src/wirings/mcp/mcp-endpoint-registry.test.ts +0 -389
  159. package/src/wirings/mcp/mcp-endpoint-registry.ts +0 -159
  160. package/src/wirings/mcp/mcp-runner.ts +0 -323
  161. package/src/wirings/mcp/mcp.types.ts +0 -216
  162. package/src/wirings/node/index.ts +0 -2
  163. package/src/wirings/node/node.types.ts +0 -23
  164. package/src/wirings/oauth2/index.ts +0 -3
  165. package/src/wirings/oauth2/oauth2-client.test.ts +0 -929
  166. package/src/wirings/oauth2/oauth2-client.ts +0 -335
  167. package/src/wirings/oauth2/oauth2.types.ts +0 -69
  168. package/src/wirings/oauth2/wire-oauth2-credential.ts +0 -23
  169. package/src/wirings/queue/index.ts +0 -31
  170. package/src/wirings/queue/queue-runner.test.ts +0 -710
  171. package/src/wirings/queue/queue-runner.ts +0 -192
  172. package/src/wirings/queue/queue.types.ts +0 -189
  173. package/src/wirings/queue/register-queue-helper.ts +0 -60
  174. package/src/wirings/queue/validate-worker-config.test.ts +0 -108
  175. package/src/wirings/queue/validate-worker-config.ts +0 -116
  176. package/src/wirings/rpc/index.ts +0 -4
  177. package/src/wirings/rpc/rpc-runner.ts +0 -460
  178. package/src/wirings/rpc/rpc-types.ts +0 -56
  179. package/src/wirings/rpc/wire-addon.ts +0 -21
  180. package/src/wirings/scheduler/index.ts +0 -11
  181. package/src/wirings/scheduler/log-schedulers.ts +0 -20
  182. package/src/wirings/scheduler/scheduler-runner.test.ts +0 -660
  183. package/src/wirings/scheduler/scheduler-runner.ts +0 -133
  184. package/src/wirings/scheduler/scheduler.types.ts +0 -53
  185. package/src/wirings/secret/index.ts +0 -9
  186. package/src/wirings/secret/secret.types.ts +0 -32
  187. package/src/wirings/secret/validate-secret-definitions.test.ts +0 -140
  188. package/src/wirings/secret/validate-secret-definitions.ts +0 -82
  189. package/src/wirings/trigger/index.ts +0 -10
  190. package/src/wirings/trigger/pikku-trigger-service.ts +0 -112
  191. package/src/wirings/trigger/trigger-runner.test.ts +0 -79
  192. package/src/wirings/trigger/trigger-runner.ts +0 -135
  193. package/src/wirings/trigger/trigger.types.ts +0 -178
  194. package/src/wirings/variable/index.ts +0 -8
  195. package/src/wirings/variable/validate-variable-definitions.test.ts +0 -91
  196. package/src/wirings/variable/validate-variable-definitions.ts +0 -69
  197. package/src/wirings/variable/variable.types.ts +0 -22
  198. package/src/wirings/workflow/dsl/index.ts +0 -31
  199. package/src/wirings/workflow/dsl/workflow-dsl.types.ts +0 -320
  200. package/src/wirings/workflow/dsl/workflow-runner.ts +0 -28
  201. package/src/wirings/workflow/graph/graph-node.ts +0 -222
  202. package/src/wirings/workflow/graph/graph-runner.test.ts +0 -487
  203. package/src/wirings/workflow/graph/graph-runner.ts +0 -816
  204. package/src/wirings/workflow/graph/graph-validation.test.ts +0 -170
  205. package/src/wirings/workflow/graph/graph-validation.ts +0 -237
  206. package/src/wirings/workflow/graph/index.ts +0 -19
  207. package/src/wirings/workflow/graph/template.test.ts +0 -49
  208. package/src/wirings/workflow/graph/template.ts +0 -42
  209. package/src/wirings/workflow/graph/wire-workflow-graph.ts +0 -29
  210. package/src/wirings/workflow/graph/workflow-graph.types.ts +0 -104
  211. package/src/wirings/workflow/index.ts +0 -82
  212. package/src/wirings/workflow/pikku-workflow-service.test.ts +0 -200
  213. package/src/wirings/workflow/pikku-workflow-service.ts +0 -1176
  214. package/src/wirings/workflow/workflow-helpers.test.ts +0 -129
  215. package/src/wirings/workflow/workflow-helpers.ts +0 -79
  216. package/src/wirings/workflow/workflow.types.ts +0 -274
  217. package/tsconfig.json +0 -14
  218. package/tsconfig.tsbuildinfo +0 -1
package/src/utils.ts DELETED
@@ -1,129 +0,0 @@
1
- import type { Logger } from './services/logger.js'
2
- import type { WireServices } from './types/core.types.js'
3
- import { getSingletonServices, getAllPackageStates } from './pikku-state.js'
4
-
5
- export const closeWireServices = async (
6
- logger: Logger,
7
- wireServices: WireServices
8
- ) => {
9
- await Promise.all(
10
- Object.values(wireServices).map(async (service: any) => {
11
- if (service?.close) {
12
- try {
13
- await service.close()
14
- } catch (e: any) {
15
- logger.error(e)
16
- }
17
- }
18
- })
19
- )
20
- }
21
-
22
- const _uidPrefix = Date.now().toString(36)
23
- let _uidCounter = 0
24
- export const createWeakUID = () => `${_uidPrefix}-${++_uidCounter}`
25
-
26
- export const isSerializable = (data: any): boolean => {
27
- return !(
28
- typeof data === 'string' ||
29
- data instanceof ArrayBuffer ||
30
- data instanceof Uint8Array ||
31
- data instanceof Int8Array ||
32
- data instanceof Uint16Array ||
33
- data instanceof Int16Array ||
34
- data instanceof Uint32Array ||
35
- data instanceof Int32Array ||
36
- data instanceof Float32Array ||
37
- data instanceof Float64Array
38
- )
39
- }
40
-
41
- export const getTagGroups = <T>(
42
- tagGroups: Record<string, T>,
43
- tag: string
44
- ): T[] => {
45
- const results: T[] = []
46
- const exact = tagGroups[tag]
47
- if (exact) results.push(exact)
48
- let colonIdx = tag.lastIndexOf(':')
49
- while (colonIdx !== -1) {
50
- const parent = tag.slice(0, colonIdx)
51
- const group = tagGroups[parent]
52
- if (group) results.push(group)
53
- colonIdx = parent.lastIndexOf(':')
54
- }
55
- return results
56
- }
57
-
58
- const EMPTY_ARRAY = Object.freeze([])
59
-
60
- export const freezeDedupe = <T>(
61
- arr?: readonly T[] | T[] | undefined
62
- ): readonly T[] => {
63
- if (!arr || arr.length === 0) return EMPTY_ARRAY
64
- if (arr.length === 1) return Object.freeze([arr[0]!])
65
- const seen = new Set<T>()
66
- const out: T[] = []
67
- for (let i = 0; i < arr.length; i++) {
68
- const fn = arr[i]!
69
- if (!seen.has(fn)) {
70
- seen.add(fn)
71
- out.push(fn)
72
- }
73
- }
74
- return Object.freeze(out)
75
- }
76
-
77
- /**
78
- * Stop a single service by calling its stop method if it exists
79
- */
80
- const stopService = async (
81
- logger: Logger,
82
- name: string,
83
- service: any
84
- ): Promise<void> => {
85
- const stop = service?.stop
86
- if (stop) {
87
- logger.info(`Stopping singleton service: ${name}`)
88
- try {
89
- await stop.call(service)
90
- } catch (e: any) {
91
- logger.error(`Error stopping service ${name}:`, e)
92
- }
93
- }
94
- }
95
-
96
- /**
97
- * Stop all singleton services, including addon package services.
98
- * Addon package services are stopped first, then the parent services.
99
- *
100
- * @param singletonServices - The parent singleton services to stop
101
- */
102
- export const stopSingletonServices = async (): Promise<void> => {
103
- const singletonServices = getSingletonServices()
104
- const logger = singletonServices.logger
105
-
106
- // Stop all addon package singleton services
107
- const stateMap = getAllPackageStates()
108
- if (stateMap.size > 0) {
109
- for (const [packageName, packageState] of stateMap) {
110
- // Skip main package - we handle it separately
111
- if (packageName === '__main__') continue
112
-
113
- const packageServices = packageState.package?.singletonServices
114
- if (packageServices) {
115
- logger.info(`Stopping singleton services for package: ${packageName}`)
116
- for (const [name, service] of Object.entries(packageServices)) {
117
- await stopService(logger, `${packageName}/${name}`, service)
118
- }
119
- // Clear the cached services
120
- packageState.package.singletonServices = null
121
- }
122
- }
123
- }
124
-
125
- // Then stop the parent singleton services
126
- for (const [name, service] of Object.entries(singletonServices)) {
127
- await stopService(logger, name, service)
128
- }
129
- }
@@ -1,80 +0,0 @@
1
- import { strict as assert } from 'assert'
2
- import { describe, test } from 'node:test'
3
- import {
4
- formatVersionedId,
5
- parseVersionedId,
6
- isVersionedId,
7
- } from './version.js'
8
-
9
- describe('formatVersionedId', () => {
10
- test('formats base name with version', () => {
11
- assert.strictEqual(formatVersionedId('createUser', 1), 'createUser@v1')
12
- assert.strictEqual(formatVersionedId('createUser', 2), 'createUser@v2')
13
- assert.strictEqual(formatVersionedId('createUser', 10), 'createUser@v10')
14
- })
15
-
16
- test('handles namespaced base names', () => {
17
- assert.strictEqual(
18
- formatVersionedId('pkg:createUser', 3),
19
- 'pkg:createUser@v3'
20
- )
21
- })
22
- })
23
-
24
- describe('parseVersionedId', () => {
25
- test('parses versioned IDs', () => {
26
- assert.deepStrictEqual(parseVersionedId('createUser@v1'), {
27
- baseName: 'createUser',
28
- version: 1,
29
- })
30
- assert.deepStrictEqual(parseVersionedId('createUser@v2'), {
31
- baseName: 'createUser',
32
- version: 2,
33
- })
34
- assert.deepStrictEqual(parseVersionedId('createUser@v10'), {
35
- baseName: 'createUser',
36
- version: 10,
37
- })
38
- })
39
-
40
- test('returns null version for unversioned IDs', () => {
41
- assert.deepStrictEqual(parseVersionedId('createUser'), {
42
- baseName: 'createUser',
43
- version: null,
44
- })
45
- })
46
-
47
- test('returns null version for invalid version suffix', () => {
48
- assert.deepStrictEqual(parseVersionedId('createUser@vx'), {
49
- baseName: 'createUser@vx',
50
- version: null,
51
- })
52
- assert.deepStrictEqual(parseVersionedId('createUser@v0'), {
53
- baseName: 'createUser@v0',
54
- version: null,
55
- })
56
- assert.deepStrictEqual(parseVersionedId('createUser@v-1'), {
57
- baseName: 'createUser@v-1',
58
- version: null,
59
- })
60
- })
61
-
62
- test('handles namespaced versioned IDs', () => {
63
- assert.deepStrictEqual(parseVersionedId('pkg:createUser@v3'), {
64
- baseName: 'pkg:createUser',
65
- version: 3,
66
- })
67
- })
68
- })
69
-
70
- describe('isVersionedId', () => {
71
- test('returns true for versioned IDs', () => {
72
- assert.strictEqual(isVersionedId('createUser@v1'), true)
73
- assert.strictEqual(isVersionedId('createUser@v99'), true)
74
- })
75
-
76
- test('returns false for unversioned IDs', () => {
77
- assert.strictEqual(isVersionedId('createUser'), false)
78
- assert.strictEqual(isVersionedId('createUser@vx'), false)
79
- })
80
- })
package/src/version.ts DELETED
@@ -1,25 +0,0 @@
1
- const VERSION_SEPARATOR = '@v'
2
-
3
- export function formatVersionedId(baseName: string, version: number): string {
4
- return `${baseName}${VERSION_SEPARATOR}${version}`
5
- }
6
-
7
- export function parseVersionedId(id: string): {
8
- baseName: string
9
- version: number | null
10
- } {
11
- const idx = id.lastIndexOf(VERSION_SEPARATOR)
12
- if (idx === -1) {
13
- return { baseName: id, version: null }
14
- }
15
- const versionStr = id.slice(idx + VERSION_SEPARATOR.length)
16
- const version = Number(versionStr)
17
- if (!Number.isInteger(version) || version < 1) {
18
- return { baseName: id, version: null }
19
- }
20
- return { baseName: id.slice(0, idx), version }
21
- }
22
-
23
- export function isVersionedId(id: string): boolean {
24
- return parseVersionedId(id).version !== null
25
- }
@@ -1,469 +0,0 @@
1
- import type { AIAgentToolDef } from './ai-agent.types.js'
2
- import { pikkuState, getSingletonServices } from '../../pikku-state.js'
3
- import { canonicalJSON, hashString } from '../../utils/hash.js'
4
- import type { WorkflowRuntimeMeta } from '../workflow/workflow.types.js'
5
- import { runWorkflowGraph } from '../workflow/graph/graph-runner.js'
6
- import type { StreamContext } from './ai-agent-prepare.js'
7
- import type { PikkuWire, CoreUserSession } from '../../types/core.types.js'
8
- import { ContextAwareRPCService, resolveNamespace } from '../rpc/rpc-runner.js'
9
- import { createMiddlewareSessionWireProps } from '../../services/user-session-service.js'
10
- import type { SessionService } from '../../services/user-session-service.js'
11
- import {
12
- validateWorkflowWiring,
13
- computeEntryNodeIds,
14
- } from '../workflow/graph/graph-validation.js'
15
-
16
- export function buildDynamicWorkflowInstructions(tools: string[], mode: 'read' | 'always' | 'ask'): string {
17
- if (mode === 'read') {
18
- return (
19
- '\n\n## Existing Workflows\n\n' +
20
- 'You can list and execute previously saved workflows using listAgentWorkflows and executeAgentWorkflow.\n' +
21
- 'Use listAgentWorkflows to discover what\'s available, then executeAgentWorkflow to run them.'
22
- )
23
- }
24
-
25
- const modeGuidance = mode === 'always'
26
- ? 'When a user requests a complex multi-step task, prefer creating a workflow over making sequential tool calls.\n' +
27
- 'Check if a suitable workflow already exists with listAgentWorkflows first.\n' +
28
- 'If none exists, create one with createAgentWorkflow, save with saveAgentWorkflow, then execute.\n\n'
29
- : 'When you receive a request that would benefit from a reusable multi-step workflow,\n' +
30
- 'suggest creating one to the user. Explain the benefits (reusability, reliability,\n' +
31
- 'can run in background) and wait for confirmation before creating.\n' +
32
- 'Do NOT create workflows automatically — always propose and get user approval first.\n' +
33
- 'For one-off tasks, just use the tools directly.\n\n'
34
-
35
- const toolSchemaLines: string[] = []
36
-
37
- for (const toolName of tools) {
38
- let fnMeta: any
39
- let schemas: Map<string, any>
40
-
41
- const resolved = toolName.includes(':') ? resolveNamespace(toolName) : null
42
-
43
- if (resolved) {
44
- fnMeta = pikkuState(resolved.package, 'function', 'meta')[
45
- resolved.function
46
- ]
47
- schemas = pikkuState(resolved.package, 'misc', 'schemas')
48
- } else {
49
- const rpcMeta = pikkuState(null, 'rpc', 'meta')
50
- const pikkuFuncId = rpcMeta[toolName]
51
- if (!pikkuFuncId) continue
52
- fnMeta = pikkuState(null, 'function', 'meta')[pikkuFuncId]
53
- schemas = pikkuState(null, 'misc', 'schemas')
54
- }
55
-
56
- if (!fnMeta) continue
57
-
58
- const inputSchema = fnMeta.inputSchemaName
59
- ? schemas.get(fnMeta.inputSchemaName)
60
- : null
61
- const outputSchema = fnMeta.outputSchemaName
62
- ? schemas.get(fnMeta.outputSchemaName)
63
- : null
64
-
65
- const toolDescription = fnMeta.description || fnMeta.title || ''
66
- const inputProps = inputSchema?.properties
67
- ? Object.entries(inputSchema.properties)
68
- .map(
69
- ([k, v]: [string, any]) =>
70
- `${k}${inputSchema.required?.includes(k) ? '' : '?'}: ${v.type || 'any'}`
71
- )
72
- .join(', ')
73
- : ''
74
- const outputProps = outputSchema?.properties
75
- ? Object.entries(outputSchema.properties)
76
- .map(([k, v]: [string, any]) => `${k}: ${v.type || 'any'}`)
77
- .join(', ')
78
- : 'any'
79
-
80
- toolSchemaLines.push(
81
- `- ${toolName}(input: {${inputProps}}) → {${outputProps}}${toolDescription ? ` — ${toolDescription}` : ''}`
82
- )
83
- }
84
-
85
- return (
86
- '\n\n## Workflow Creation\n\n' +
87
- modeGuidance +
88
- 'You can create workflows that chain your tools together. Use createAgentWorkflow to validate and preview a workflow, then saveAgentWorkflow to save it, then executeAgentWorkflow to run it.\n\n' +
89
- '### Tool Schemas:\n' +
90
- toolSchemaLines.join('\n') +
91
- '\n\n### Workflow Format:\n' +
92
- '- Each node has: rpcName (tool name), input (with $ref to wire outputs), next (flow control), onError\n' +
93
- '- Use {"$ref": "nodeId", "path": "fieldName"} to wire a previous node\'s output field to this node\'s input\n' +
94
- '- Use {"$ref": "trigger", "path": "fieldName"} to extract a field from the workflow\'s trigger input. IMPORTANT: Always include "path" — never pass {"$ref": "trigger"} without "path", or the entire trigger object will be used as the value.\n' +
95
- '- Use {"$ref": "nodeId", "path": "fieldName"} to extract a field from a previous node\'s output. Always include "path" to select the specific field.\n' +
96
- '- next can be a string (single next node), array (parallel), or object {branchKey: nextNode} for branching\n' +
97
- '\n### Example (add todo from trigger title, then list):\n' +
98
- '{"add":{"rpcName":"todos:addTodo","input":{"title":{"$ref":"trigger","path":"title"}},"next":"list"},"list":{"rpcName":"todos:listTodos","input":{}}}\n' +
99
- '\n### Example (add todo, sleep, complete using addTodo result id):\n' +
100
- '{"add":{"rpcName":"todos:addTodo","input":{"title":{"$ref":"trigger","path":"title"}},"next":"wait"},"wait":{"rpcName":"sleep","input":{"seconds":5},"next":"complete"},"complete":{"rpcName":"todos:completeTodo","input":{"id":{"$ref":"add","path":"id"}}}}'
101
- )
102
- }
103
-
104
- export function buildWorkflowTools(
105
- agentName: string,
106
- packageName: string | null,
107
- toolNames: string[],
108
- mode: 'read' | 'always' | 'ask',
109
- streamContext?: StreamContext,
110
- sessionService?: SessionService<CoreUserSession>
111
- ): AIAgentToolDef[] {
112
- const tools: AIAgentToolDef[] = []
113
-
114
- if (mode !== 'read') {
115
- tools.push({
116
- name: 'createAgentWorkflow',
117
- description:
118
- 'Validate and create a draft workflow graph that chains tools together. Call saveAgentWorkflow to activate it after the user approves.',
119
- inputSchema: {
120
- type: 'object',
121
- properties: {
122
- name: {
123
- type: 'string',
124
- description:
125
- 'Short name for the workflow (will be prefixed with ai:{agentName}:)',
126
- },
127
- description: {
128
- type: 'string',
129
- description: 'What this workflow does',
130
- },
131
- nodes: {
132
- type: 'string',
133
- description:
134
- 'JSON string of a map of nodeId to node config. Each node has: rpcName (tool name), input (with $ref to wire outputs), next (string|string[]|{branchKey: string}), onError (string|string[]). Example: {"list":{"rpcName":"todos:listTodos","input":{}}}',
135
- },
136
- },
137
- required: ['name', 'nodes'],
138
- },
139
- execute: async (input: unknown) => {
140
- const raw = input as {
141
- name: string
142
- description?: string
143
- nodes: string | Record<string, any>
144
- }
145
- const name = raw.name.replace(/[^a-zA-Z0-9_-]/g, '-')
146
- let nodes: Record<string, any>
147
- if (typeof raw.nodes === 'string') {
148
- try {
149
- nodes = JSON.parse(raw.nodes)
150
- } catch {
151
- return { error: 'Invalid JSON in nodes field' }
152
- }
153
- } else {
154
- nodes = raw.nodes
155
- }
156
-
157
- if (Object.keys(nodes).length < 2) {
158
- return {
159
- error:
160
- 'A workflow must have at least 2 nodes. A single node is just a tool call — use the tool directly instead.',
161
- }
162
- }
163
-
164
- const validationErrors = validateWorkflowWiring(nodes, toolNames)
165
- if (validationErrors.length > 0) {
166
- return {
167
- error: 'Workflow validation failed',
168
- errors: validationErrors,
169
- }
170
- }
171
-
172
- const entryNodeIds = computeEntryNodeIds(nodes)
173
- if (entryNodeIds.length === 0) {
174
- return {
175
- error:
176
- 'No entry nodes found. Every node is referenced by another node, creating a cycle.',
177
- }
178
- }
179
-
180
- const fullName = `ai:${agentName}:${name}`
181
-
182
- const graphHash = hashString(canonicalJSON({ nodes, entryNodeIds }))
183
- const graph: WorkflowRuntimeMeta = {
184
- name: fullName,
185
- pikkuFuncId: fullName,
186
- source: 'ai-agent',
187
- description: raw.description,
188
- nodes,
189
- entryNodeIds,
190
- graphHash,
191
- }
192
-
193
- const singletonServices = getSingletonServices()
194
- if (!singletonServices?.workflowService) {
195
- return { error: 'Workflow service not available' }
196
- }
197
-
198
- await singletonServices.workflowService.upsertWorkflowVersion(
199
- fullName,
200
- graphHash,
201
- graph,
202
- 'ai-agent',
203
- 'draft'
204
- )
205
-
206
- return {
207
- valid: true,
208
- workflowName: fullName,
209
- graphHash,
210
- entryNodes: entryNodeIds,
211
- nodeCount: Object.keys(nodes).length,
212
- message: `Workflow '${fullName}' validated and saved as draft. Present this to the user and call saveAgentWorkflow to activate it.`,
213
- }
214
- },
215
- })
216
-
217
- tools.push({
218
- name: 'saveAgentWorkflow',
219
- description:
220
- 'Activate a previously created draft workflow so it can be executed. Requires user approval.',
221
- inputSchema: {
222
- type: 'object',
223
- properties: {
224
- name: {
225
- type: 'string',
226
- description:
227
- 'Full workflow name returned by createAgentWorkflow (e.g. ai:agentName:myWorkflow)',
228
- },
229
- graphHash: {
230
- type: 'string',
231
- description: 'Graph hash returned by createAgentWorkflow',
232
- },
233
- },
234
- required: ['name', 'graphHash'],
235
- },
236
- needsApproval: true,
237
- approvalDescriptionFn: async (input: unknown) => {
238
- const raw = input as { name: string; graphHash: string }
239
- const fullName = raw.name.startsWith(`ai:${agentName}:`)
240
- ? raw.name
241
- : `ai:${agentName}:${raw.name}`
242
-
243
- const singletonServices = getSingletonServices()
244
- if (!singletonServices?.workflowService) {
245
- return `Activate workflow '${fullName}'`
246
- }
247
-
248
- const version =
249
- await singletonServices.workflowService.getWorkflowVersion(
250
- fullName,
251
- raw.graphHash
252
- )
253
- if (version) {
254
- const graph = version.graph as WorkflowRuntimeMeta
255
- if (graph.nodes) {
256
- const desc = graph.description ? `\n${graph.description}` : ''
257
- return `Activate workflow '${fullName}' (${Object.keys(graph.nodes).length} nodes)${desc}`
258
- }
259
- }
260
- return `Activate workflow '${fullName}'`
261
- },
262
- execute: async (input: unknown) => {
263
- const raw = input as { name: string; graphHash: string }
264
- const fullName = raw.name.startsWith(`ai:${agentName}:`)
265
- ? raw.name
266
- : `ai:${agentName}:${raw.name}`
267
-
268
- const singletonServices = getSingletonServices()
269
- if (!singletonServices?.workflowService) {
270
- return { error: 'Workflow service not available' }
271
- }
272
-
273
- const version =
274
- await singletonServices.workflowService.getWorkflowVersion(
275
- fullName,
276
- raw.graphHash
277
- )
278
- if (!version) {
279
- return {
280
- error: `Workflow '${fullName}' with hash '${raw.graphHash}' not found. Use createAgentWorkflow first.`,
281
- }
282
- }
283
-
284
- await singletonServices.workflowService.updateWorkflowVersionStatus(
285
- fullName,
286
- raw.graphHash,
287
- 'active'
288
- )
289
-
290
- const graph = version.graph as WorkflowRuntimeMeta
291
- const allMeta = pikkuState(null, 'workflows', 'meta')
292
- allMeta[fullName] = graph
293
-
294
- if (streamContext) {
295
- streamContext.channel.send({
296
- type: 'workflow-created',
297
- workflowName: fullName,
298
- graph,
299
- })
300
- }
301
-
302
- return {
303
- success: true,
304
- workflowName: fullName,
305
- message: `Workflow '${fullName}' activated and ready to execute.`,
306
- }
307
- },
308
- })
309
- } // end if (mode !== 'read')
310
-
311
- tools.push({
312
- name: 'listAgentWorkflows',
313
- description:
314
- 'List previously saved workflows for this agent that can be executed.',
315
- inputSchema: {
316
- type: 'object',
317
- properties: {},
318
- },
319
- execute: async () => {
320
- const singletonServices = getSingletonServices()
321
- if (!singletonServices?.workflowService) {
322
- return { error: 'Workflow service not available' }
323
- }
324
-
325
- const results: Array<{
326
- name: string
327
- description?: string
328
- graphHash?: string
329
- }> = []
330
-
331
- const allMeta = pikkuState(null, 'workflows', 'meta')
332
- const prefix = `ai:${agentName}:`
333
- for (const [name, meta] of Object.entries(allMeta)) {
334
- if (name.startsWith(prefix) && meta.source === 'ai-agent') {
335
- results.push({
336
- name,
337
- description: meta.description,
338
- graphHash: meta.graphHash,
339
- })
340
- }
341
- }
342
-
343
- if (results.length === 0) {
344
- const persisted =
345
- await singletonServices.workflowService.getAIGeneratedWorkflows(
346
- agentName
347
- )
348
- for (const wf of persisted) {
349
- results.push({
350
- name: wf.workflowName,
351
- graphHash: wf.graphHash,
352
- })
353
- }
354
- }
355
-
356
- return { workflows: results }
357
- },
358
- })
359
-
360
- tools.push({
361
- name: 'executeAgentWorkflow',
362
- description: 'Execute a previously saved workflow with the given input.',
363
- inputSchema: {
364
- type: 'object',
365
- properties: {
366
- name: {
367
- type: 'string',
368
- description:
369
- 'Workflow name (will be auto-prefixed with ai:{agentName}: if needed)',
370
- },
371
- input: {
372
- type: 'object',
373
- description: 'Input data for the workflow trigger',
374
- },
375
- },
376
- required: ['name'],
377
- },
378
- needsApproval: true,
379
- approvalDescriptionFn: async (input: unknown) => {
380
- const { name, input: workflowInput } = input as {
381
- name: string
382
- input?: Record<string, any>
383
- }
384
- const fullName = name.startsWith(`ai:${agentName}:`)
385
- ? name
386
- : `ai:${agentName}:${name}`
387
- const inputStr = workflowInput
388
- ? `\nInput: ${JSON.stringify(workflowInput)}`
389
- : ''
390
- return `Execute workflow '${fullName}'${inputStr}`
391
- },
392
- execute: async (toolInput: unknown) => {
393
- const { name, input: workflowInput } = toolInput as {
394
- name: string
395
- input?: Record<string, any>
396
- }
397
-
398
- const fullName = name.startsWith(`ai:${agentName}:`)
399
- ? name
400
- : `ai:${agentName}:${name}`
401
-
402
- const singletonServices = getSingletonServices()
403
- if (!singletonServices?.workflowService) {
404
- return { error: 'Workflow service not available' }
405
- }
406
-
407
- const allMeta = pikkuState(null, 'workflows', 'meta')
408
- if (!allMeta[fullName]) {
409
- return {
410
- error: `Workflow '${fullName}' not found. Use listAgentWorkflows to see available workflows, or createAgentWorkflow to make a new one.`,
411
- }
412
- }
413
-
414
- const workflowService = singletonServices.workflowService as any
415
-
416
- const wire: PikkuWire = sessionService
417
- ? { ...createMiddlewareSessionWireProps(sessionService) }
418
- : {}
419
- const rpcService = new ContextAwareRPCService(singletonServices, wire, {
420
- sessionService,
421
- })
422
-
423
- const { runId } = await runWorkflowGraph(
424
- workflowService,
425
- fullName,
426
- workflowInput ?? {},
427
- rpcService,
428
- true,
429
- undefined,
430
- { type: 'ai-agent' }
431
- )
432
-
433
- const maxWait = 45000
434
- const startTime = Date.now()
435
- let pollInterval = 100
436
- while (Date.now() - startTime < maxWait) {
437
- const run = await workflowService.getRun(runId)
438
- if (!run) {
439
- return { error: `Workflow run '${runId}' not found` }
440
- }
441
- if (
442
- run.status === 'completed' ||
443
- run.status === 'failed' ||
444
- run.status === 'cancelled' ||
445
- run.status === 'suspended'
446
- ) {
447
- if (run.status === 'failed') {
448
- return {
449
- error: `Workflow failed: ${run.error?.message || 'Unknown error'}`,
450
- runId,
451
- }
452
- }
453
- return { result: run.output, runId, status: run.status }
454
- }
455
- await new Promise((resolve) => setTimeout(resolve, pollInterval))
456
- pollInterval = Math.min(pollInterval * 1.5, 2000)
457
- }
458
-
459
- return {
460
- runId,
461
- status: 'running',
462
- message:
463
- 'Workflow is still running after timeout. It will continue in the background.',
464
- }
465
- },
466
- })
467
-
468
- return tools
469
- }