@pikku/core 0.12.0 → 0.12.1

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 (262) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/dist/errors/errors.d.ts +6 -0
  3. package/dist/errors/errors.js +10 -0
  4. package/dist/errors/index.d.ts +1 -0
  5. package/dist/errors/index.js +1 -0
  6. package/dist/function/function-runner.d.ts +3 -3
  7. package/dist/function/function-runner.js +66 -44
  8. package/dist/function/functions.types.d.ts +1 -0
  9. package/dist/handle-error.d.ts +2 -2
  10. package/dist/handle-error.js +2 -2
  11. package/dist/index.d.ts +5 -3
  12. package/dist/index.js +2 -1
  13. package/dist/internal.d.ts +3 -0
  14. package/dist/internal.js +2 -0
  15. package/dist/middleware/auth-bearer.d.ts +1 -1
  16. package/dist/middleware/auth-bearer.js +1 -1
  17. package/dist/middleware/auth-cookie.d.ts +2 -2
  18. package/dist/middleware/auth-cookie.js +1 -1
  19. package/dist/middleware/cors.d.ts +2 -2
  20. package/dist/middleware/cors.js +45 -37
  21. package/dist/middleware-runner.d.ts +1 -1
  22. package/dist/permissions.d.ts +2 -2
  23. package/dist/pikku-state.d.ts +9 -8
  24. package/dist/pikku-state.js +30 -17
  25. package/dist/schema.d.ts +4 -4
  26. package/dist/schema.js +9 -5
  27. package/dist/services/ai-agent-runner-service.d.ts +22 -3
  28. package/dist/services/ai-run-state-service.d.ts +1 -1
  29. package/dist/services/ai-storage-service.d.ts +1 -1
  30. package/dist/services/content-service.d.ts +6 -0
  31. package/dist/services/gopass-secrets.d.ts +1 -1
  32. package/dist/services/gopass-secrets.js +3 -0
  33. package/dist/services/in-memory-ai-run-state-service.d.ts +15 -0
  34. package/dist/services/in-memory-ai-run-state-service.js +44 -0
  35. package/dist/services/in-memory-trigger-service.d.ts +0 -1
  36. package/dist/services/in-memory-trigger-service.js +6 -8
  37. package/dist/services/index.d.ts +3 -2
  38. package/dist/services/index.js +1 -0
  39. package/dist/services/local-content.d.ts +2 -1
  40. package/dist/services/local-content.js +6 -1
  41. package/dist/services/local-secrets.d.ts +2 -2
  42. package/dist/services/local-variables.d.ts +1 -1
  43. package/dist/services/logger-console.d.ts +2 -1
  44. package/dist/services/scheduler-service.d.ts +0 -12
  45. package/dist/services/scheduler-service.js +0 -6
  46. package/dist/services/scoped-secret-service.d.ts +1 -1
  47. package/dist/services/trigger-service.d.ts +0 -7
  48. package/dist/services/user-session-service.d.ts +3 -3
  49. package/dist/services/workflow-service.d.ts +2 -3
  50. package/dist/types/core.types.d.ts +24 -18
  51. package/dist/types/state.types.d.ts +17 -15
  52. package/dist/utils.d.ts +5 -5
  53. package/dist/utils.js +12 -9
  54. package/dist/wirings/ai-agent/ai-agent-assistant-ui.d.ts +5 -0
  55. package/dist/wirings/ai-agent/ai-agent-assistant-ui.js +146 -0
  56. package/dist/wirings/ai-agent/ai-agent-helpers.d.ts +28 -0
  57. package/dist/wirings/ai-agent/ai-agent-helpers.js +40 -0
  58. package/dist/wirings/ai-agent/ai-agent-prepare.d.ts +4 -5
  59. package/dist/wirings/ai-agent/ai-agent-prepare.js +66 -30
  60. package/dist/wirings/ai-agent/ai-agent-registry.d.ts +1 -1
  61. package/dist/wirings/ai-agent/ai-agent-runner.js +107 -8
  62. package/dist/wirings/ai-agent/ai-agent-stream.d.ts +2 -1
  63. package/dist/wirings/ai-agent/ai-agent-stream.js +246 -87
  64. package/dist/wirings/ai-agent/ai-agent.types.d.ts +61 -2
  65. package/dist/wirings/ai-agent/index.d.ts +3 -1
  66. package/dist/wirings/ai-agent/index.js +2 -0
  67. package/dist/wirings/channel/channel-common.d.ts +2 -2
  68. package/dist/wirings/channel/channel-handler.d.ts +3 -3
  69. package/dist/wirings/channel/channel-handler.js +4 -4
  70. package/dist/wirings/channel/channel-runner.d.ts +5 -5
  71. package/dist/wirings/channel/channel-runner.js +3 -2
  72. package/dist/wirings/channel/channel-store.d.ts +1 -1
  73. package/dist/wirings/channel/channel.types.d.ts +4 -6
  74. package/dist/wirings/channel/local/local-channel-runner.d.ts +7 -2
  75. package/dist/wirings/channel/local/local-channel-runner.js +4 -3
  76. package/dist/wirings/channel/local/local-eventhub-service.d.ts +2 -2
  77. package/dist/wirings/channel/log-channels.d.ts +1 -1
  78. package/dist/wirings/channel/pikku-abstract-channel-handler.d.ts +1 -1
  79. package/dist/wirings/channel/serverless/serverless-channel-runner.d.ts +4 -4
  80. package/dist/wirings/channel/serverless/serverless-channel-runner.js +25 -19
  81. package/dist/wirings/cli/channel/cli-channel-runner.d.ts +1 -1
  82. package/dist/wirings/cli/cli-runner.d.ts +2 -2
  83. package/dist/wirings/cli/cli.types.d.ts +3 -3
  84. package/dist/wirings/cli/command-parser.d.ts +1 -1
  85. package/dist/wirings/http/http-runner.d.ts +9 -9
  86. package/dist/wirings/http/http-runner.js +33 -14
  87. package/dist/wirings/http/http.types.d.ts +2 -6
  88. package/dist/wirings/http/log-http-routes.d.ts +1 -1
  89. package/dist/wirings/http/pikku-fetch-http-request.d.ts +1 -1
  90. package/dist/wirings/http/pikku-fetch-http-response.d.ts +2 -2
  91. package/dist/wirings/http/pikku-fetch-http-response.js +6 -1
  92. package/dist/wirings/http/routers/http-router.d.ts +1 -1
  93. package/dist/wirings/http/routers/path-to-regex.d.ts +2 -2
  94. package/dist/wirings/mcp/mcp-endpoint-registry.d.ts +1 -1
  95. package/dist/wirings/mcp/mcp-runner.d.ts +0 -3
  96. package/dist/wirings/mcp/mcp-runner.js +4 -2
  97. package/dist/wirings/mcp/mcp.types.d.ts +2 -2
  98. package/dist/wirings/oauth2/oauth2-client.d.ts +3 -3
  99. package/dist/wirings/oauth2/wire-oauth2-credential.d.ts +1 -1
  100. package/dist/wirings/queue/index.d.ts +1 -1
  101. package/dist/wirings/queue/index.js +1 -1
  102. package/dist/wirings/queue/queue-runner.d.ts +1 -12
  103. package/dist/wirings/queue/queue-runner.js +4 -12
  104. package/dist/wirings/queue/queue.types.d.ts +3 -9
  105. package/dist/wirings/queue/register-queue-helper.d.ts +2 -2
  106. package/dist/wirings/queue/register-queue-helper.js +1 -1
  107. package/dist/wirings/queue/validate-worker-config.d.ts +1 -1
  108. package/dist/wirings/rpc/index.d.ts +2 -0
  109. package/dist/wirings/rpc/index.js +1 -0
  110. package/dist/wirings/rpc/rpc-runner.d.ts +36 -14
  111. package/dist/wirings/rpc/rpc-runner.js +56 -28
  112. package/dist/wirings/rpc/rpc-types.d.ts +14 -2
  113. package/dist/wirings/rpc/wire-addon.d.ts +10 -0
  114. package/dist/wirings/rpc/wire-addon.js +9 -0
  115. package/dist/wirings/scheduler/index.d.ts +1 -1
  116. package/dist/wirings/scheduler/index.js +1 -1
  117. package/dist/wirings/scheduler/log-schedulers.d.ts +1 -1
  118. package/dist/wirings/scheduler/scheduler-runner.d.ts +3 -10
  119. package/dist/wirings/scheduler/scheduler-runner.js +4 -25
  120. package/dist/wirings/scheduler/scheduler.types.d.ts +2 -2
  121. package/dist/wirings/trigger/pikku-trigger-service.d.ts +2 -6
  122. package/dist/wirings/trigger/pikku-trigger-service.js +11 -16
  123. package/dist/wirings/trigger/trigger-runner.d.ts +1 -5
  124. package/dist/wirings/trigger/trigger-runner.js +4 -3
  125. package/dist/wirings/workflow/pikku-workflow-service.d.ts +2 -6
  126. package/dist/wirings/workflow/pikku-workflow-service.js +20 -29
  127. package/dist/wirings/workflow/workflow.types.d.ts +2 -2
  128. package/package.json +2 -1
  129. package/src/crypto-utils.test.ts +116 -0
  130. package/src/errors/error.test.ts +143 -2
  131. package/src/errors/errors.ts +10 -0
  132. package/src/errors/index.ts +1 -0
  133. package/src/function/function-runner.test.ts +2 -2
  134. package/src/function/function-runner.ts +72 -49
  135. package/src/function/functions.types.ts +1 -0
  136. package/src/handle-error.test.ts +361 -0
  137. package/src/handle-error.ts +4 -4
  138. package/src/index.ts +3 -10
  139. package/src/internal.ts +6 -0
  140. package/src/middleware/auth-apikey.test.ts +1 -1
  141. package/src/middleware/auth-bearer.test.ts +1 -1
  142. package/src/middleware/auth-bearer.ts +2 -5
  143. package/src/middleware/auth-cookie.test.ts +1 -1
  144. package/src/middleware/auth-cookie.ts +3 -5
  145. package/src/middleware/cors.test.ts +424 -0
  146. package/src/middleware/cors.ts +14 -6
  147. package/src/middleware/remote-auth.test.ts +488 -0
  148. package/src/middleware-runner.test.ts +1 -1
  149. package/src/middleware-runner.ts +1 -1
  150. package/src/permissions.test.ts +2 -2
  151. package/src/permissions.ts +2 -2
  152. package/src/pikku-state.test.ts +224 -0
  153. package/src/pikku-state.ts +40 -28
  154. package/src/schema.test.ts +198 -6
  155. package/src/schema.ts +11 -7
  156. package/src/services/ai-agent-runner-service.ts +15 -3
  157. package/src/services/ai-run-state-service.ts +1 -1
  158. package/src/services/ai-storage-service.ts +1 -1
  159. package/src/services/content-service.ts +7 -0
  160. package/src/services/gopass-secrets.ts +4 -1
  161. package/src/services/in-memory-ai-run-state-service.ts +73 -0
  162. package/src/services/in-memory-trigger-service.ts +6 -10
  163. package/src/services/in-memory-workflow-service.test.ts +351 -0
  164. package/src/services/index.ts +2 -1
  165. package/src/services/local-content.ts +9 -3
  166. package/src/services/local-secrets.test.ts +80 -0
  167. package/src/services/local-secrets.ts +2 -2
  168. package/src/services/local-variables.test.ts +61 -0
  169. package/src/services/local-variables.ts +1 -1
  170. package/src/services/logger-console.test.ts +118 -0
  171. package/src/services/logger-console.ts +2 -1
  172. package/src/services/scheduler-service.ts +0 -18
  173. package/src/services/scoped-secret-service.test.ts +74 -0
  174. package/src/services/scoped-secret-service.ts +1 -1
  175. package/src/services/trigger-service.ts +0 -21
  176. package/src/services/typed-secret-service.test.ts +93 -0
  177. package/src/services/typed-variables-service.test.ts +73 -0
  178. package/src/services/user-session-service.test.ts +113 -0
  179. package/src/services/user-session-service.ts +3 -3
  180. package/src/services/workflow-service.ts +2 -12
  181. package/src/time-utils.test.ts +1 -1
  182. package/src/types/core.types.ts +25 -19
  183. package/src/types/state.types.ts +24 -15
  184. package/src/utils.test.ts +350 -0
  185. package/src/utils.ts +14 -13
  186. package/src/wirings/ai-agent/ai-agent-assistant-ui.test.ts +363 -0
  187. package/src/wirings/ai-agent/ai-agent-assistant-ui.ts +214 -0
  188. package/src/wirings/ai-agent/ai-agent-helpers.test.ts +152 -0
  189. package/src/wirings/ai-agent/ai-agent-helpers.ts +76 -0
  190. package/src/wirings/ai-agent/ai-agent-model-config.test.ts +115 -0
  191. package/src/wirings/ai-agent/ai-agent-prepare.ts +79 -45
  192. package/src/wirings/ai-agent/ai-agent-registry.ts +1 -1
  193. package/src/wirings/ai-agent/ai-agent-runner.test.ts +245 -3
  194. package/src/wirings/ai-agent/ai-agent-runner.ts +121 -7
  195. package/src/wirings/ai-agent/ai-agent-stream.test.ts +430 -24
  196. package/src/wirings/ai-agent/ai-agent-stream.ts +377 -102
  197. package/src/wirings/ai-agent/ai-agent.types.ts +57 -2
  198. package/src/wirings/ai-agent/index.ts +11 -0
  199. package/src/wirings/channel/channel-common.ts +2 -2
  200. package/src/wirings/channel/channel-handler.ts +13 -9
  201. package/src/wirings/channel/channel-runner.ts +6 -6
  202. package/src/wirings/channel/channel-store.ts +1 -1
  203. package/src/wirings/channel/channel.types.ts +4 -8
  204. package/src/wirings/channel/local/local-channel-runner.test.ts +19 -12
  205. package/src/wirings/channel/local/local-channel-runner.ts +18 -13
  206. package/src/wirings/channel/local/local-eventhub-service.test.ts +2 -2
  207. package/src/wirings/channel/local/local-eventhub-service.ts +2 -2
  208. package/src/wirings/channel/log-channels.ts +1 -1
  209. package/src/wirings/channel/pikku-abstract-channel-handler.ts +1 -1
  210. package/src/wirings/channel/serverless/serverless-channel-runner.ts +37 -32
  211. package/src/wirings/cli/channel/cli-channel-runner.ts +1 -1
  212. package/src/wirings/cli/cli-runner.test.ts +1 -1
  213. package/src/wirings/cli/cli-runner.ts +11 -4
  214. package/src/wirings/cli/cli.types.ts +3 -3
  215. package/src/wirings/cli/command-parser.test.ts +1 -1
  216. package/src/wirings/cli/command-parser.ts +1 -1
  217. package/src/wirings/http/http-routes.test.ts +1 -1
  218. package/src/wirings/http/http-runner.test.ts +9 -17
  219. package/src/wirings/http/http-runner.ts +43 -27
  220. package/src/wirings/http/http.types.ts +1 -14
  221. package/src/wirings/http/log-http-routes.ts +1 -1
  222. package/src/wirings/http/pikku-fetch-http-request.ts +1 -1
  223. package/src/wirings/http/pikku-fetch-http-response.ts +12 -5
  224. package/src/wirings/http/routers/http-router.ts +1 -1
  225. package/src/wirings/http/routers/path-to-regex.test.ts +1 -1
  226. package/src/wirings/http/routers/path-to-regex.ts +4 -3
  227. package/src/wirings/mcp/mcp-endpoint-registry.ts +5 -1
  228. package/src/wirings/mcp/mcp-runner.ts +9 -15
  229. package/src/wirings/mcp/mcp.types.ts +2 -2
  230. package/src/wirings/oauth2/oauth2-client.ts +3 -3
  231. package/src/wirings/oauth2/wire-oauth2-credential.ts +1 -1
  232. package/src/wirings/queue/index.ts +0 -1
  233. package/src/wirings/queue/queue-runner.test.ts +52 -25
  234. package/src/wirings/queue/queue-runner.ts +8 -30
  235. package/src/wirings/queue/queue.types.ts +3 -13
  236. package/src/wirings/queue/register-queue-helper.ts +3 -5
  237. package/src/wirings/queue/validate-worker-config.test.ts +108 -0
  238. package/src/wirings/queue/validate-worker-config.ts +4 -1
  239. package/src/wirings/rpc/index.ts +2 -0
  240. package/src/wirings/rpc/rpc-runner.ts +92 -49
  241. package/src/wirings/rpc/rpc-types.ts +17 -2
  242. package/src/wirings/rpc/wire-addon.ts +20 -0
  243. package/src/wirings/scheduler/index.ts +0 -1
  244. package/src/wirings/scheduler/log-schedulers.ts +1 -1
  245. package/src/wirings/scheduler/scheduler-runner.test.ts +49 -60
  246. package/src/wirings/scheduler/scheduler-runner.ts +9 -56
  247. package/src/wirings/scheduler/scheduler.types.ts +2 -2
  248. package/src/wirings/secret/validate-secret-definitions.test.ts +140 -0
  249. package/src/wirings/trigger/pikku-trigger-service.ts +17 -39
  250. package/src/wirings/trigger/trigger-runner.test.ts +79 -0
  251. package/src/wirings/trigger/trigger-runner.ts +8 -11
  252. package/src/wirings/variable/validate-variable-definitions.test.ts +91 -0
  253. package/src/wirings/workflow/graph/graph-runner.test.ts +14 -22
  254. package/src/wirings/workflow/graph/template.test.ts +49 -0
  255. package/src/wirings/workflow/pikku-workflow-service.test.ts +15 -27
  256. package/src/wirings/workflow/pikku-workflow-service.ts +29 -45
  257. package/src/wirings/workflow/workflow-helpers.test.ts +129 -0
  258. package/src/wirings/workflow/workflow.types.ts +2 -2
  259. package/tsconfig.tsbuildinfo +1 -1
  260. package/dist/wirings/workflow/workflow-utils.d.ts +0 -1
  261. package/dist/wirings/workflow/workflow-utils.js +0 -1
  262. package/src/wirings/workflow/workflow-utils.ts +0 -0
@@ -0,0 +1,152 @@
1
+ import { describe, test } from 'node:test'
2
+ import assert from 'node:assert'
3
+ import {
4
+ agent,
5
+ agentStream,
6
+ agentResume,
7
+ agentApprove,
8
+ } from './ai-agent-helpers.js'
9
+
10
+ describe('agent helpers', () => {
11
+ describe('agent', () => {
12
+ test('should return an object with func', () => {
13
+ const a = agent('myAgent')
14
+ assert.strictEqual(typeof a.func, 'function')
15
+ })
16
+
17
+ test('func should call rpc.agent.run', async () => {
18
+ const a = agent('myAgent')
19
+ let calledWith: any
20
+ const rpc = {
21
+ agent: {
22
+ run: async (...args: any[]) => {
23
+ calledWith = args
24
+ return {
25
+ runId: 'run-1',
26
+ result: 'done',
27
+ usage: { inputTokens: 10, outputTokens: 20 },
28
+ }
29
+ },
30
+ },
31
+ }
32
+ const input = { message: 'hello', threadId: 't1', resourceId: 'r1' }
33
+ const result = await a.func({}, input, { rpc })
34
+ assert.strictEqual(calledWith[0], 'myAgent')
35
+ assert.deepStrictEqual(calledWith[1], input)
36
+ assert.strictEqual(result.runId, 'run-1')
37
+ })
38
+ })
39
+
40
+ describe('agentStream', () => {
41
+ test('should return an object with func', () => {
42
+ const a = agentStream('myAgent')
43
+ assert.strictEqual(typeof a.func, 'function')
44
+ })
45
+
46
+ test('func should call rpc.agent.stream with bound name', async () => {
47
+ const a = agentStream('myAgent')
48
+ let calledWith: any
49
+ const rpc = {
50
+ agent: {
51
+ stream: async (...args: any[]) => {
52
+ calledWith = args
53
+ },
54
+ },
55
+ }
56
+ const input = { message: 'hello', threadId: 't1', resourceId: 'r1' }
57
+ await a.func({}, input, { rpc })
58
+ assert.strictEqual(calledWith[0], 'myAgent')
59
+ assert.deepStrictEqual(calledWith[1], input)
60
+ })
61
+
62
+ test('func should extract agentName from data when no name bound', async () => {
63
+ const a = agentStream()
64
+ let calledWith: any
65
+ const rpc = {
66
+ agent: {
67
+ stream: async (...args: any[]) => {
68
+ calledWith = args
69
+ },
70
+ },
71
+ }
72
+ const input = {
73
+ agentName: 'dynamicAgent',
74
+ message: 'hello',
75
+ threadId: 't1',
76
+ resourceId: 'r1',
77
+ }
78
+ await a.func({}, input, { rpc })
79
+ assert.strictEqual(calledWith[0], 'dynamicAgent')
80
+ assert.deepStrictEqual(calledWith[1], {
81
+ message: 'hello',
82
+ threadId: 't1',
83
+ resourceId: 'r1',
84
+ })
85
+ })
86
+
87
+ test('func should throw when no name bound and no agentName in data', async () => {
88
+ const a = agentStream()
89
+ const rpc = { agent: { stream: async () => {} } }
90
+ await assert.rejects(() => a.func({}, { message: 'hello' }, { rpc }), {
91
+ message:
92
+ 'agentStream requires an agentName either as a parameter or in the input data',
93
+ })
94
+ })
95
+ })
96
+
97
+ describe('agentResume', () => {
98
+ test('should return an object with func', () => {
99
+ const a = agentResume()
100
+ assert.strictEqual(typeof a.func, 'function')
101
+ })
102
+
103
+ test('func should call rpc.agent.resume', async () => {
104
+ const a = agentResume()
105
+ let calledWith: any
106
+ const rpc = {
107
+ agent: {
108
+ resume: async (...args: any[]) => {
109
+ calledWith = args
110
+ },
111
+ },
112
+ }
113
+ const data = { runId: 'run-1', toolCallId: 'tc1', approved: true }
114
+ await a.func({}, data, { rpc })
115
+ assert.strictEqual(calledWith[0], 'run-1')
116
+ assert.deepStrictEqual(calledWith[1], {
117
+ toolCallId: 'tc1',
118
+ approved: true,
119
+ })
120
+ })
121
+ })
122
+
123
+ describe('agentApprove', () => {
124
+ test('should return an object with func', () => {
125
+ const a = agentApprove('myAgent')
126
+ assert.strictEqual(typeof a.func, 'function')
127
+ })
128
+
129
+ test('func should call rpc.agent.approve', async () => {
130
+ const a = agentApprove('myAgent')
131
+ let calledWith: any
132
+ const rpc = {
133
+ agent: {
134
+ approve: async (...args: any[]) => {
135
+ calledWith = args
136
+ return { approved: true }
137
+ },
138
+ },
139
+ }
140
+ const data = {
141
+ runId: 'run-1',
142
+ approvals: [{ toolCallId: 'tc1', approved: true }],
143
+ }
144
+ const result = await a.func({}, data, { rpc })
145
+ assert.strictEqual(calledWith[0], 'run-1')
146
+ assert.deepStrictEqual(calledWith[1], [
147
+ { toolCallId: 'tc1', approved: true },
148
+ ])
149
+ assert.deepStrictEqual(result, { approved: true })
150
+ })
151
+ })
152
+ })
@@ -0,0 +1,76 @@
1
+ export function agent<TAgentMap extends Record<string, { output: any }>>(
2
+ agentName: string & keyof TAgentMap
3
+ ): { func: (services: any, data: any, wire: any) => Promise<any> } {
4
+ return {
5
+ func: async (_services: any, data: any, { rpc }: any) => {
6
+ return rpc.agent.run(agentName, data)
7
+ },
8
+ }
9
+ }
10
+
11
+ export function agentStream<TAgentMap extends Record<string, { output: any }>>(
12
+ agentName?: string & keyof TAgentMap
13
+ ): { func: (services: any, data: any, wire: any) => Promise<void> } {
14
+ return {
15
+ func: async (_services: any, data: any, { rpc }: any) => {
16
+ if (agentName) {
17
+ await rpc.agent.stream(agentName, data)
18
+ } else {
19
+ const { agentName: name, ...rest } = data
20
+ if (!name) {
21
+ throw new Error(
22
+ 'agentStream requires an agentName either as a parameter or in the input data'
23
+ )
24
+ }
25
+ await rpc.agent.stream(name, rest)
26
+ }
27
+ },
28
+ }
29
+ }
30
+
31
+ export function agentResume(): {
32
+ func: (
33
+ services: any,
34
+ data: { runId: string; toolCallId: string; approved: boolean },
35
+ wire: any
36
+ ) => Promise<void>
37
+ } {
38
+ return {
39
+ func: async (
40
+ _services: any,
41
+ data: { runId: string; toolCallId: string; approved: boolean },
42
+ { rpc }: any
43
+ ) => {
44
+ await rpc.agent.resume(data.runId, {
45
+ toolCallId: data.toolCallId,
46
+ approved: data.approved,
47
+ })
48
+ },
49
+ }
50
+ }
51
+
52
+ export function agentApprove<TAgentMap extends Record<string, { output: any }>>(
53
+ _agentName: string & keyof TAgentMap
54
+ ): {
55
+ func: (
56
+ services: any,
57
+ data: {
58
+ runId: string
59
+ approvals: { toolCallId: string; approved: boolean }[]
60
+ },
61
+ wire: any
62
+ ) => Promise<any>
63
+ } {
64
+ return {
65
+ func: async (
66
+ _services: any,
67
+ data: {
68
+ runId: string
69
+ approvals: { toolCallId: string; approved: boolean }[]
70
+ },
71
+ { rpc }: any
72
+ ) => {
73
+ return rpc.agent.approve(data.runId, data.approvals)
74
+ },
75
+ }
76
+ }
@@ -0,0 +1,115 @@
1
+ import { describe, test, beforeEach } from 'node:test'
2
+ import assert from 'node:assert'
3
+ import { resolveModelConfig } from './ai-agent-model-config.js'
4
+ import { resetPikkuState, pikkuState } from '../../pikku-state.js'
5
+
6
+ beforeEach(() => {
7
+ resetPikkuState()
8
+ })
9
+
10
+ describe('resolveModelConfig', () => {
11
+ test('should return agent model when no config', () => {
12
+ const result = resolveModelConfig('testAgent', {
13
+ model: 'anthropic/claude-3',
14
+ temperature: 0.7,
15
+ })
16
+ assert.strictEqual(result.model, 'anthropic/claude-3')
17
+ assert.strictEqual(result.temperature, 0.7)
18
+ })
19
+
20
+ test('should resolve model alias to string', () => {
21
+ pikkuState(null, 'models', 'config', {
22
+ models: { fast: 'anthropic/claude-haiku' },
23
+ } as any)
24
+
25
+ const result = resolveModelConfig('testAgent', { model: 'fast' })
26
+ assert.strictEqual(result.model, 'anthropic/claude-haiku')
27
+ })
28
+
29
+ test('should resolve model alias with temperature and maxSteps', () => {
30
+ pikkuState(null, 'models', 'config', {
31
+ models: {
32
+ smart: {
33
+ model: 'anthropic/claude-opus',
34
+ temperature: 0.3,
35
+ maxSteps: 10,
36
+ },
37
+ },
38
+ } as any)
39
+
40
+ const result = resolveModelConfig('testAgent', { model: 'smart' })
41
+ assert.strictEqual(result.model, 'anthropic/claude-opus')
42
+ assert.strictEqual(result.temperature, 0.3)
43
+ assert.strictEqual(result.maxSteps, 10)
44
+ })
45
+
46
+ test('should throw for unknown model alias', () => {
47
+ pikkuState(null, 'models', 'config', {
48
+ models: {},
49
+ } as any)
50
+
51
+ assert.throws(() => resolveModelConfig('testAgent', { model: 'unknown' }), {
52
+ message: "Unknown model alias 'unknown'.",
53
+ })
54
+ })
55
+
56
+ test('should pass through model with slash (not an alias)', () => {
57
+ pikkuState(null, 'models', 'config', {
58
+ models: {},
59
+ } as any)
60
+
61
+ const result = resolveModelConfig('testAgent', { model: 'openai/gpt-4' })
62
+ assert.strictEqual(result.model, 'openai/gpt-4')
63
+ })
64
+
65
+ test('should use agent override model', () => {
66
+ pikkuState(null, 'models', 'config', {
67
+ models: { fast: 'anthropic/haiku' },
68
+ agentOverrides: { testAgent: { model: 'fast' } },
69
+ } as any)
70
+
71
+ const result = resolveModelConfig('testAgent', {
72
+ model: 'anthropic/default',
73
+ })
74
+ assert.strictEqual(result.model, 'anthropic/haiku')
75
+ })
76
+
77
+ test('should use agent override temperature over alias', () => {
78
+ pikkuState(null, 'models', 'config', {
79
+ models: { smart: { model: 'anthropic/opus', temperature: 0.5 } },
80
+ agentOverrides: { testAgent: { model: 'smart', temperature: 0.1 } },
81
+ } as any)
82
+
83
+ const result = resolveModelConfig('testAgent', {
84
+ model: 'anthropic/default',
85
+ })
86
+ assert.strictEqual(result.temperature, 0.1)
87
+ })
88
+
89
+ test('should use agentDefaults as fallback', () => {
90
+ pikkuState(null, 'models', 'config', {
91
+ models: {},
92
+ agentDefaults: { temperature: 0.5, maxSteps: 20 },
93
+ } as any)
94
+
95
+ const result = resolveModelConfig('testAgent', {
96
+ model: 'anthropic/claude-3',
97
+ })
98
+ assert.strictEqual(result.temperature, 0.5)
99
+ assert.strictEqual(result.maxSteps, 20)
100
+ })
101
+
102
+ test('should prioritize: override > alias > defaults > agent', () => {
103
+ pikkuState(null, 'models', 'config', {
104
+ models: { fast: { model: 'anthropic/haiku', temperature: 0.3 } },
105
+ agentDefaults: { temperature: 0.5 },
106
+ agentOverrides: { testAgent: { temperature: 0.1 } },
107
+ } as any)
108
+
109
+ const result = resolveModelConfig('testAgent', {
110
+ model: 'fast',
111
+ temperature: 0.9,
112
+ })
113
+ assert.strictEqual(result.temperature, 0.1)
114
+ })
115
+ })
@@ -1,10 +1,4 @@
1
- import {
2
- type CoreSingletonServices,
3
- type CoreServices,
4
- type CoreUserSession,
5
- type CreateWireServices,
6
- PikkuWire,
7
- } from '../../types/core.types.js'
1
+ import type { PikkuWire, CoreUserSession } from '../../types/core.types.js'
8
2
  import type {
9
3
  CoreAIAgent,
10
4
  AIAgentInput,
@@ -12,10 +6,15 @@ import type {
12
6
  AIMessage,
13
7
  AIStreamChannel,
14
8
  AIStreamEvent,
9
+ PikkuAIMiddlewareHooks,
15
10
  } from './ai-agent.types.js'
16
11
  import type { AIAgentRunnerParams } from '../../services/ai-agent-runner-service.js'
17
12
  import { PikkuError } from '../../errors/error-handler.js'
18
- import { pikkuState } from '../../pikku-state.js'
13
+ import {
14
+ pikkuState,
15
+ getSingletonServices,
16
+ getCreateWireServices,
17
+ } from '../../pikku-state.js'
19
18
  import { runPikkuFunc } from '../../function/function-runner.js'
20
19
  import { createMiddlewareSessionWireProps } from '../../services/user-session-service.js'
21
20
  import type { SessionService } from '../../services/user-session-service.js'
@@ -29,13 +28,7 @@ import {
29
28
  import { resolveModelConfig } from './ai-agent-model-config.js'
30
29
 
31
30
  export type RunAIAgentParams = {
32
- singletonServices: CoreSingletonServices
33
31
  sessionService?: SessionService<CoreUserSession>
34
- createWireServices?: CreateWireServices<
35
- CoreSingletonServices,
36
- CoreServices<CoreSingletonServices>,
37
- CoreUserSession
38
- >
39
32
  }
40
33
 
41
34
  export type StreamAIAgentOptions = {
@@ -88,8 +81,8 @@ export const resolveAgent = (
88
81
  if (colonIndex !== -1) {
89
82
  const namespace = agentName.substring(0, colonIndex)
90
83
  const localName = agentName.substring(colonIndex + 1)
91
- const externalPackages = pikkuState(null, 'rpc', 'externalPackages')
92
- const pkgConfig = externalPackages.get(namespace)
84
+ const addons = pikkuState(null, 'rpc', 'addons')
85
+ const pkgConfig = addons.get(namespace)
93
86
  if (pkgConfig) {
94
87
  const extAgent = pikkuState(pkgConfig.package, 'agent', 'agents').get(
95
88
  localName
@@ -163,6 +156,7 @@ export function createScopedChannel(
163
156
  return
164
157
  }
165
158
  if (
159
+ event.type === 'step-start' ||
166
160
  event.type === 'text-delta' ||
167
161
  event.type === 'reasoning-delta' ||
168
162
  event.type === 'tool-call' ||
@@ -179,14 +173,15 @@ export function createScopedChannel(
179
173
  }
180
174
 
181
175
  export function buildToolDefs(
182
- singletonServices: CoreSingletonServices,
183
176
  params: RunAIAgentParams,
184
177
  agentSessionMap: Map<string, string>,
185
178
  resourceId: string,
186
179
  agentName: string,
187
180
  packageName: string | null,
188
- streamContext?: StreamContext
181
+ streamContext?: StreamContext,
182
+ aiMiddlewares?: PikkuAIMiddlewareHooks[]
189
183
  ): { tools: AIAgentToolDef[]; missingRpcs: string[] } {
184
+ const singletonServices = getSingletonServices()
190
185
  const tools: AIAgentToolDef[] = []
191
186
  const missingRpcs: string[] = []
192
187
  const approvalPolicy =
@@ -228,39 +223,18 @@ export function buildToolDefs(
228
223
  approvalPolicy === 'all' ||
229
224
  (approvalPolicy === 'explicit' && fnMeta?.requiresApproval)
230
225
 
231
- if (needsApproval) {
232
- inputSchema = {
233
- ...inputSchema,
234
- properties: {
235
- ...((inputSchema.properties as Record<string, unknown>) || {}),
236
- toolApprovalReason: {
237
- type: 'string',
238
- description: 'Brief explanation of why this action is needed',
239
- },
240
- },
241
- }
242
- }
243
-
244
226
  tools.push({
245
227
  name: pikkuFuncId,
246
228
  description: fnMeta?.description || fnMeta?.title || toolName,
247
229
  inputSchema,
230
+ needsApproval: needsApproval || undefined,
248
231
  execute: async (toolInput: unknown) => {
249
- if (needsApproval) {
250
- const { toolApprovalReason, ...cleanArgs } = toolInput as any
251
- return {
252
- __approvalRequired: true,
253
- toolName: pikkuFuncId,
254
- args: cleanArgs,
255
- reason: toolApprovalReason,
256
- }
257
- }
258
232
  const wire: PikkuWire = params.sessionService
259
233
  ? { ...createMiddlewareSessionWireProps(params.sessionService) }
260
234
  : {}
261
235
  return runPikkuFunc('agent', `tool:${pikkuFuncId}`, pikkuFuncId, {
262
236
  singletonServices,
263
- createWireServices: params.createWireServices,
237
+ createWireServices: getCreateWireServices(),
264
238
  data: () => toolInput,
265
239
  wire,
266
240
  sessionService: params.sessionService,
@@ -362,6 +336,62 @@ export function buildToolDefs(
362
336
  }
363
337
  }
364
338
 
339
+ const hasToolHooks = aiMiddlewares?.some(
340
+ (mw) => mw.beforeToolCall || mw.afterToolCall
341
+ )
342
+ if (hasToolHooks) {
343
+ for (const tool of tools) {
344
+ const originalExecute = tool.execute
345
+ tool.execute = async (toolInput: unknown) => {
346
+ const toolCallId = randomUUID()
347
+ let args = (toolInput ?? {}) as Record<string, unknown>
348
+
349
+ for (const mw of aiMiddlewares!) {
350
+ if (mw.beforeToolCall) {
351
+ const beforeResult = await mw.beforeToolCall(singletonServices, {
352
+ toolName: tool.name,
353
+ toolCallId,
354
+ args,
355
+ })
356
+ if (beforeResult && 'args' in beforeResult) {
357
+ args = beforeResult.args
358
+ }
359
+ }
360
+ }
361
+
362
+ const startTime = Date.now()
363
+ let result: unknown
364
+ let execError: unknown
365
+ try {
366
+ result = await originalExecute(args)
367
+ } catch (err) {
368
+ execError = err
369
+ result = err instanceof Error ? err.message : String(err)
370
+ }
371
+ const durationMs = Date.now() - startTime
372
+
373
+ for (let i = aiMiddlewares!.length - 1; i >= 0; i--) {
374
+ const mw = aiMiddlewares![i]
375
+ if (mw.afterToolCall) {
376
+ const afterResult = await mw.afterToolCall(singletonServices, {
377
+ toolName: tool.name,
378
+ toolCallId,
379
+ args,
380
+ result,
381
+ durationMs,
382
+ })
383
+ if (afterResult && 'result' in afterResult) {
384
+ result = afterResult.result
385
+ }
386
+ }
387
+ }
388
+
389
+ if (execError) throw execError
390
+ return result
391
+ }
392
+ }
393
+ }
394
+
365
395
  return { tools, missingRpcs }
366
396
  }
367
397
 
@@ -372,7 +402,7 @@ export async function prepareAgentRun(
372
402
  agentSessionMap: Map<string, string>,
373
403
  streamContext?: StreamContext
374
404
  ) {
375
- const { singletonServices } = params
405
+ const singletonServices = getSingletonServices()
376
406
  const { agent, packageName, resolvedName } = resolveAgent(agentName)
377
407
 
378
408
  const agentRunner = singletonServices.aiAgentRunner
@@ -428,19 +458,22 @@ export async function prepareAgentRun(
428
458
  const allMessages = [...contextMessages, ...messages, userMessage]
429
459
  const trimmedMessages = trimMessages(allMessages)
430
460
 
461
+ const aiMiddlewares: PikkuAIMiddlewareHooks[] = agent.aiMiddleware ?? []
462
+
431
463
  const { tools, missingRpcs } = buildToolDefs(
432
- singletonServices,
433
464
  params,
434
465
  agentSessionMap,
435
466
  input.resourceId,
436
467
  resolvedName,
437
468
  packageName,
438
- streamContext
469
+ streamContext,
470
+ aiMiddlewares
439
471
  )
440
472
 
441
473
  const instructions = buildInstructions(resolvedName, packageName)
442
474
 
443
475
  const resolved = resolveModelConfig(resolvedName, agent)
476
+ const maxSteps = resolved.maxSteps ?? 10
444
477
 
445
478
  const runnerParams: AIAgentRunnerParams = {
446
479
  model: resolved.model,
@@ -448,7 +481,7 @@ export async function prepareAgentRun(
448
481
  instructions,
449
482
  messages: trimmedMessages,
450
483
  tools,
451
- maxSteps: resolved.maxSteps ?? 10,
484
+ maxSteps: 1,
452
485
  toolChoice: agent.toolChoice ?? 'auto',
453
486
  outputSchema,
454
487
  }
@@ -463,6 +496,7 @@ export async function prepareAgentRun(
463
496
  threadId,
464
497
  userMessage,
465
498
  runnerParams,
499
+ maxSteps,
466
500
  missingRpcs,
467
501
  workingMemoryJsonSchema,
468
502
  workingMemorySchemaName,
@@ -1,6 +1,6 @@
1
1
  import type { CoreAIAgent, AgentRunState } from './ai-agent.types.js'
2
2
  import { pikkuState } from '../../pikku-state.js'
3
- import { AIRunStateService } from '../../services/ai-run-state-service.js'
3
+ import type { AIRunStateService } from '../../services/ai-run-state-service.js'
4
4
  import { PikkuMissingMetaError } from '../../errors/errors.js'
5
5
 
6
6
  export const addAIAgent = (