@pikku/core 0.12.0 → 0.12.2

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 (290) hide show
  1. package/CHANGELOG.md +40 -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/middleware-runner.js +1 -0
  23. package/dist/permissions.d.ts +2 -2
  24. package/dist/permissions.js +1 -0
  25. package/dist/pikku-state.d.ts +9 -8
  26. package/dist/pikku-state.js +34 -17
  27. package/dist/schema.d.ts +4 -4
  28. package/dist/schema.js +9 -5
  29. package/dist/services/ai-agent-runner-service.d.ts +22 -3
  30. package/dist/services/ai-run-state-service.d.ts +1 -1
  31. package/dist/services/ai-storage-service.d.ts +1 -1
  32. package/dist/services/content-service.d.ts +6 -0
  33. package/dist/services/gateway-service.d.ts +19 -0
  34. package/dist/services/gopass-secrets.d.ts +1 -1
  35. package/dist/services/gopass-secrets.js +3 -0
  36. package/dist/services/in-memory-ai-run-state-service.d.ts +15 -0
  37. package/dist/services/in-memory-ai-run-state-service.js +44 -0
  38. package/dist/services/in-memory-trigger-service.d.ts +0 -1
  39. package/dist/services/in-memory-trigger-service.js +6 -8
  40. package/dist/services/index.d.ts +5 -2
  41. package/dist/services/index.js +2 -0
  42. package/dist/services/local-content.d.ts +2 -1
  43. package/dist/services/local-content.js +6 -1
  44. package/dist/services/local-gateway-service.d.ts +22 -0
  45. package/dist/services/local-gateway-service.js +51 -0
  46. package/dist/services/local-secrets.d.ts +2 -2
  47. package/dist/services/local-variables.d.ts +1 -1
  48. package/dist/services/logger-console.d.ts +2 -1
  49. package/dist/services/scheduler-service.d.ts +0 -12
  50. package/dist/services/scheduler-service.js +0 -6
  51. package/dist/services/scoped-secret-service.d.ts +1 -1
  52. package/dist/services/trigger-service.d.ts +0 -7
  53. package/dist/services/user-session-service.d.ts +3 -3
  54. package/dist/services/workflow-service.d.ts +2 -3
  55. package/dist/types/core.types.d.ts +27 -19
  56. package/dist/types/state.types.d.ts +22 -15
  57. package/dist/utils.d.ts +5 -5
  58. package/dist/utils.js +12 -9
  59. package/dist/wirings/ai-agent/ai-agent-assistant-ui.d.ts +5 -0
  60. package/dist/wirings/ai-agent/ai-agent-assistant-ui.js +168 -0
  61. package/dist/wirings/ai-agent/ai-agent-helpers.d.ts +28 -0
  62. package/dist/wirings/ai-agent/ai-agent-helpers.js +40 -0
  63. package/dist/wirings/ai-agent/ai-agent-memory.js +13 -2
  64. package/dist/wirings/ai-agent/ai-agent-prepare.d.ts +4 -5
  65. package/dist/wirings/ai-agent/ai-agent-prepare.js +80 -31
  66. package/dist/wirings/ai-agent/ai-agent-registry.d.ts +1 -1
  67. package/dist/wirings/ai-agent/ai-agent-runner.js +107 -8
  68. package/dist/wirings/ai-agent/ai-agent-stream.d.ts +2 -1
  69. package/dist/wirings/ai-agent/ai-agent-stream.js +263 -89
  70. package/dist/wirings/ai-agent/ai-agent.types.d.ts +96 -4
  71. package/dist/wirings/ai-agent/index.d.ts +3 -1
  72. package/dist/wirings/ai-agent/index.js +2 -0
  73. package/dist/wirings/channel/channel-common.d.ts +2 -2
  74. package/dist/wirings/channel/channel-handler.d.ts +7 -4
  75. package/dist/wirings/channel/channel-handler.js +15 -5
  76. package/dist/wirings/channel/channel-middleware-runner.js +1 -0
  77. package/dist/wirings/channel/channel-runner.d.ts +5 -5
  78. package/dist/wirings/channel/channel-runner.js +3 -2
  79. package/dist/wirings/channel/channel-store.d.ts +1 -1
  80. package/dist/wirings/channel/channel.types.d.ts +10 -6
  81. package/dist/wirings/channel/index.d.ts +1 -1
  82. package/dist/wirings/channel/local/local-channel-handler.d.ts +7 -0
  83. package/dist/wirings/channel/local/local-channel-handler.js +17 -0
  84. package/dist/wirings/channel/local/local-channel-runner.d.ts +7 -2
  85. package/dist/wirings/channel/local/local-channel-runner.js +18 -4
  86. package/dist/wirings/channel/local/local-eventhub-service.d.ts +2 -2
  87. package/dist/wirings/channel/log-channels.d.ts +1 -1
  88. package/dist/wirings/channel/pikku-abstract-channel-handler.d.ts +2 -1
  89. package/dist/wirings/channel/pikku-abstract-channel-handler.js +1 -0
  90. package/dist/wirings/channel/serverless/serverless-channel-runner.d.ts +4 -4
  91. package/dist/wirings/channel/serverless/serverless-channel-runner.js +26 -20
  92. package/dist/wirings/cli/channel/cli-channel-runner.d.ts +1 -1
  93. package/dist/wirings/cli/cli-runner.d.ts +2 -2
  94. package/dist/wirings/cli/cli-runner.js +3 -0
  95. package/dist/wirings/cli/cli.types.d.ts +3 -3
  96. package/dist/wirings/cli/command-parser.d.ts +1 -1
  97. package/dist/wirings/gateway/gateway-runner.d.ts +24 -0
  98. package/dist/wirings/gateway/gateway-runner.js +325 -0
  99. package/dist/wirings/gateway/gateway.types.d.ts +127 -0
  100. package/dist/wirings/gateway/index.d.ts +2 -0
  101. package/dist/wirings/gateway/index.js +1 -0
  102. package/dist/wirings/http/http-runner.d.ts +9 -9
  103. package/dist/wirings/http/http-runner.js +36 -14
  104. package/dist/wirings/http/http.types.d.ts +2 -6
  105. package/dist/wirings/http/log-http-routes.d.ts +1 -1
  106. package/dist/wirings/http/pikku-fetch-http-request.d.ts +1 -1
  107. package/dist/wirings/http/pikku-fetch-http-response.d.ts +2 -2
  108. package/dist/wirings/http/pikku-fetch-http-response.js +6 -1
  109. package/dist/wirings/http/routers/http-router.d.ts +1 -1
  110. package/dist/wirings/http/routers/path-to-regex.d.ts +2 -2
  111. package/dist/wirings/mcp/mcp-endpoint-registry.d.ts +1 -1
  112. package/dist/wirings/mcp/mcp-runner.d.ts +0 -3
  113. package/dist/wirings/mcp/mcp-runner.js +4 -2
  114. package/dist/wirings/mcp/mcp.types.d.ts +2 -2
  115. package/dist/wirings/oauth2/oauth2-client.d.ts +3 -3
  116. package/dist/wirings/oauth2/wire-oauth2-credential.d.ts +1 -1
  117. package/dist/wirings/queue/index.d.ts +1 -1
  118. package/dist/wirings/queue/index.js +1 -1
  119. package/dist/wirings/queue/queue-runner.d.ts +1 -12
  120. package/dist/wirings/queue/queue-runner.js +4 -12
  121. package/dist/wirings/queue/queue.types.d.ts +3 -9
  122. package/dist/wirings/queue/register-queue-helper.d.ts +2 -2
  123. package/dist/wirings/queue/register-queue-helper.js +1 -1
  124. package/dist/wirings/queue/validate-worker-config.d.ts +1 -1
  125. package/dist/wirings/rpc/index.d.ts +2 -0
  126. package/dist/wirings/rpc/index.js +1 -0
  127. package/dist/wirings/rpc/rpc-runner.d.ts +36 -14
  128. package/dist/wirings/rpc/rpc-runner.js +56 -28
  129. package/dist/wirings/rpc/rpc-types.d.ts +14 -2
  130. package/dist/wirings/rpc/wire-addon.d.ts +10 -0
  131. package/dist/wirings/rpc/wire-addon.js +9 -0
  132. package/dist/wirings/scheduler/index.d.ts +1 -1
  133. package/dist/wirings/scheduler/index.js +1 -1
  134. package/dist/wirings/scheduler/log-schedulers.d.ts +1 -1
  135. package/dist/wirings/scheduler/scheduler-runner.d.ts +3 -10
  136. package/dist/wirings/scheduler/scheduler-runner.js +4 -25
  137. package/dist/wirings/scheduler/scheduler.types.d.ts +2 -2
  138. package/dist/wirings/trigger/pikku-trigger-service.d.ts +2 -6
  139. package/dist/wirings/trigger/pikku-trigger-service.js +11 -16
  140. package/dist/wirings/trigger/trigger-runner.d.ts +1 -5
  141. package/dist/wirings/trigger/trigger-runner.js +4 -3
  142. package/dist/wirings/workflow/pikku-workflow-service.d.ts +2 -6
  143. package/dist/wirings/workflow/pikku-workflow-service.js +20 -29
  144. package/dist/wirings/workflow/workflow.types.d.ts +2 -2
  145. package/package.json +4 -2
  146. package/src/crypto-utils.test.ts +116 -0
  147. package/src/errors/error.test.ts +143 -2
  148. package/src/errors/errors.ts +10 -0
  149. package/src/errors/index.ts +1 -0
  150. package/src/function/function-runner.test.ts +2 -2
  151. package/src/function/function-runner.ts +72 -49
  152. package/src/function/functions.types.ts +1 -0
  153. package/src/handle-error.test.ts +361 -0
  154. package/src/handle-error.ts +4 -4
  155. package/src/index.ts +3 -10
  156. package/src/internal.ts +6 -0
  157. package/src/middleware/auth-apikey.test.ts +1 -1
  158. package/src/middleware/auth-bearer.test.ts +1 -1
  159. package/src/middleware/auth-bearer.ts +2 -5
  160. package/src/middleware/auth-cookie.test.ts +1 -1
  161. package/src/middleware/auth-cookie.ts +3 -5
  162. package/src/middleware/cors.test.ts +424 -0
  163. package/src/middleware/cors.ts +14 -6
  164. package/src/middleware/remote-auth.test.ts +488 -0
  165. package/src/middleware-runner.test.ts +1 -1
  166. package/src/middleware-runner.ts +2 -1
  167. package/src/permissions.test.ts +2 -2
  168. package/src/permissions.ts +3 -2
  169. package/src/pikku-state.test.ts +224 -0
  170. package/src/pikku-state.ts +45 -28
  171. package/src/schema.test.ts +198 -6
  172. package/src/schema.ts +11 -7
  173. package/src/services/ai-agent-runner-service.ts +15 -3
  174. package/src/services/ai-run-state-service.ts +1 -1
  175. package/src/services/ai-storage-service.ts +1 -1
  176. package/src/services/content-service.ts +7 -0
  177. package/src/services/gateway-service.ts +20 -0
  178. package/src/services/gopass-secrets.ts +4 -1
  179. package/src/services/in-memory-ai-run-state-service.ts +73 -0
  180. package/src/services/in-memory-trigger-service.ts +6 -10
  181. package/src/services/in-memory-workflow-service.test.ts +351 -0
  182. package/src/services/index.ts +4 -1
  183. package/src/services/local-content.ts +9 -3
  184. package/src/services/local-gateway-service.ts +62 -0
  185. package/src/services/local-secrets.test.ts +80 -0
  186. package/src/services/local-secrets.ts +2 -2
  187. package/src/services/local-variables.test.ts +61 -0
  188. package/src/services/local-variables.ts +1 -1
  189. package/src/services/logger-console.test.ts +118 -0
  190. package/src/services/logger-console.ts +2 -1
  191. package/src/services/scheduler-service.ts +0 -18
  192. package/src/services/scoped-secret-service.test.ts +74 -0
  193. package/src/services/scoped-secret-service.ts +1 -1
  194. package/src/services/trigger-service.ts +0 -21
  195. package/src/services/typed-secret-service.test.ts +93 -0
  196. package/src/services/typed-variables-service.test.ts +73 -0
  197. package/src/services/user-session-service.test.ts +113 -0
  198. package/src/services/user-session-service.ts +3 -3
  199. package/src/services/workflow-service.ts +2 -12
  200. package/src/time-utils.test.ts +1 -1
  201. package/src/types/core.types.ts +28 -19
  202. package/src/types/state.types.ts +32 -15
  203. package/src/utils.test.ts +350 -0
  204. package/src/utils.ts +14 -13
  205. package/src/wirings/ai-agent/ai-agent-assistant-ui.test.ts +363 -0
  206. package/src/wirings/ai-agent/ai-agent-assistant-ui.ts +238 -0
  207. package/src/wirings/ai-agent/ai-agent-helpers.test.ts +152 -0
  208. package/src/wirings/ai-agent/ai-agent-helpers.ts +76 -0
  209. package/src/wirings/ai-agent/ai-agent-memory.ts +10 -1
  210. package/src/wirings/ai-agent/ai-agent-model-config.test.ts +115 -0
  211. package/src/wirings/ai-agent/ai-agent-prepare.ts +98 -46
  212. package/src/wirings/ai-agent/ai-agent-registry.ts +1 -1
  213. package/src/wirings/ai-agent/ai-agent-runner.test.ts +245 -3
  214. package/src/wirings/ai-agent/ai-agent-runner.ts +121 -7
  215. package/src/wirings/ai-agent/ai-agent-stream.test.ts +430 -24
  216. package/src/wirings/ai-agent/ai-agent-stream.ts +390 -104
  217. package/src/wirings/ai-agent/ai-agent.types.ts +91 -4
  218. package/src/wirings/ai-agent/index.ts +13 -0
  219. package/src/wirings/channel/channel-common.ts +2 -2
  220. package/src/wirings/channel/channel-handler.ts +43 -11
  221. package/src/wirings/channel/channel-middleware-runner.ts +1 -0
  222. package/src/wirings/channel/channel-runner.ts +6 -6
  223. package/src/wirings/channel/channel-store.ts +1 -1
  224. package/src/wirings/channel/channel.types.ts +15 -8
  225. package/src/wirings/channel/index.ts +1 -0
  226. package/src/wirings/channel/local/local-channel-handler.ts +26 -0
  227. package/src/wirings/channel/local/local-channel-runner.test.ts +19 -12
  228. package/src/wirings/channel/local/local-channel-runner.ts +33 -14
  229. package/src/wirings/channel/local/local-eventhub-service.test.ts +2 -2
  230. package/src/wirings/channel/local/local-eventhub-service.ts +2 -2
  231. package/src/wirings/channel/log-channels.ts +1 -1
  232. package/src/wirings/channel/pikku-abstract-channel-handler.test.ts +2 -0
  233. package/src/wirings/channel/pikku-abstract-channel-handler.ts +8 -1
  234. package/src/wirings/channel/serverless/serverless-channel-runner.ts +38 -33
  235. package/src/wirings/cli/channel/cli-channel-runner.ts +1 -1
  236. package/src/wirings/cli/cli-runner.test.ts +1 -1
  237. package/src/wirings/cli/cli-runner.ts +14 -4
  238. package/src/wirings/cli/cli.types.ts +3 -3
  239. package/src/wirings/cli/command-parser.test.ts +1 -1
  240. package/src/wirings/cli/command-parser.ts +1 -1
  241. package/src/wirings/gateway/gateway-runner.test.ts +474 -0
  242. package/src/wirings/gateway/gateway-runner.ts +411 -0
  243. package/src/wirings/gateway/gateway.types.ts +149 -0
  244. package/src/wirings/gateway/index.ts +13 -0
  245. package/src/wirings/http/http-routes.test.ts +1 -1
  246. package/src/wirings/http/http-runner.test.ts +9 -17
  247. package/src/wirings/http/http-runner.ts +46 -27
  248. package/src/wirings/http/http.types.ts +1 -14
  249. package/src/wirings/http/log-http-routes.ts +1 -1
  250. package/src/wirings/http/pikku-fetch-http-request.ts +1 -1
  251. package/src/wirings/http/pikku-fetch-http-response.ts +12 -5
  252. package/src/wirings/http/routers/http-router.ts +1 -1
  253. package/src/wirings/http/routers/path-to-regex.test.ts +1 -1
  254. package/src/wirings/http/routers/path-to-regex.ts +4 -3
  255. package/src/wirings/mcp/mcp-endpoint-registry.ts +5 -1
  256. package/src/wirings/mcp/mcp-runner.ts +9 -15
  257. package/src/wirings/mcp/mcp.types.ts +2 -2
  258. package/src/wirings/oauth2/oauth2-client.ts +3 -3
  259. package/src/wirings/oauth2/wire-oauth2-credential.ts +1 -1
  260. package/src/wirings/queue/index.ts +0 -1
  261. package/src/wirings/queue/queue-runner.test.ts +52 -25
  262. package/src/wirings/queue/queue-runner.ts +8 -30
  263. package/src/wirings/queue/queue.types.ts +3 -13
  264. package/src/wirings/queue/register-queue-helper.ts +3 -5
  265. package/src/wirings/queue/validate-worker-config.test.ts +108 -0
  266. package/src/wirings/queue/validate-worker-config.ts +4 -1
  267. package/src/wirings/rpc/index.ts +2 -0
  268. package/src/wirings/rpc/rpc-runner.ts +92 -49
  269. package/src/wirings/rpc/rpc-types.ts +17 -2
  270. package/src/wirings/rpc/wire-addon.ts +20 -0
  271. package/src/wirings/scheduler/index.ts +0 -1
  272. package/src/wirings/scheduler/log-schedulers.ts +1 -1
  273. package/src/wirings/scheduler/scheduler-runner.test.ts +49 -60
  274. package/src/wirings/scheduler/scheduler-runner.ts +9 -56
  275. package/src/wirings/scheduler/scheduler.types.ts +2 -2
  276. package/src/wirings/secret/validate-secret-definitions.test.ts +140 -0
  277. package/src/wirings/trigger/pikku-trigger-service.ts +17 -39
  278. package/src/wirings/trigger/trigger-runner.test.ts +79 -0
  279. package/src/wirings/trigger/trigger-runner.ts +8 -11
  280. package/src/wirings/variable/validate-variable-definitions.test.ts +91 -0
  281. package/src/wirings/workflow/graph/graph-runner.test.ts +14 -22
  282. package/src/wirings/workflow/graph/template.test.ts +49 -0
  283. package/src/wirings/workflow/pikku-workflow-service.test.ts +15 -27
  284. package/src/wirings/workflow/pikku-workflow-service.ts +29 -45
  285. package/src/wirings/workflow/workflow-helpers.test.ts +129 -0
  286. package/src/wirings/workflow/workflow.types.ts +2 -2
  287. package/tsconfig.tsbuildinfo +1 -1
  288. package/src/wirings/workflow/workflow-utils.ts +0 -0
  289. /package/dist/{wirings/workflow/workflow-utils.d.ts → services/gateway-service.js} +0 -0
  290. /package/dist/wirings/{workflow/workflow-utils.js → gateway/gateway.types.js} +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
+ }
@@ -277,7 +277,16 @@ function sanitizeToolMessages(messages: AIMessage[]): AIMessage[] {
277
277
 
278
278
  function estimateTokens(msg: AIMessage): number {
279
279
  let chars = 0
280
- if (msg.content) chars += msg.content.length
280
+ if (msg.content) {
281
+ if (typeof msg.content === 'string') {
282
+ chars += msg.content.length
283
+ } else {
284
+ for (const part of msg.content) {
285
+ if (part.type === 'text') chars += part.text.length
286
+ else chars += 1000
287
+ }
288
+ }
289
+ }
281
290
  if (msg.toolCalls) chars += JSON.stringify(msg.toolCalls).length
282
291
  if (msg.toolResults) chars += JSON.stringify(msg.toolResults).length
283
292
  return Math.ceil(chars / 4)
@@ -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,21 +1,21 @@
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,
11
5
  AIAgentToolDef,
6
+ AIContentPart,
12
7
  AIMessage,
13
8
  AIStreamChannel,
14
9
  AIStreamEvent,
10
+ PikkuAIMiddlewareHooks,
15
11
  } from './ai-agent.types.js'
16
12
  import type { AIAgentRunnerParams } from '../../services/ai-agent-runner-service.js'
17
13
  import { PikkuError } from '../../errors/error-handler.js'
18
- import { pikkuState } from '../../pikku-state.js'
14
+ import {
15
+ pikkuState,
16
+ getSingletonServices,
17
+ getCreateWireServices,
18
+ } from '../../pikku-state.js'
19
19
  import { runPikkuFunc } from '../../function/function-runner.js'
20
20
  import { createMiddlewareSessionWireProps } from '../../services/user-session-service.js'
21
21
  import type { SessionService } from '../../services/user-session-service.js'
@@ -29,13 +29,7 @@ import {
29
29
  import { resolveModelConfig } from './ai-agent-model-config.js'
30
30
 
31
31
  export type RunAIAgentParams = {
32
- singletonServices: CoreSingletonServices
33
32
  sessionService?: SessionService<CoreUserSession>
34
- createWireServices?: CreateWireServices<
35
- CoreSingletonServices,
36
- CoreServices<CoreSingletonServices>,
37
- CoreUserSession
38
- >
39
33
  }
40
34
 
41
35
  export type StreamAIAgentOptions = {
@@ -88,8 +82,8 @@ export const resolveAgent = (
88
82
  if (colonIndex !== -1) {
89
83
  const namespace = agentName.substring(0, colonIndex)
90
84
  const localName = agentName.substring(colonIndex + 1)
91
- const externalPackages = pikkuState(null, 'rpc', 'externalPackages')
92
- const pkgConfig = externalPackages.get(namespace)
85
+ const addons = pikkuState(null, 'rpc', 'addons')
86
+ const pkgConfig = addons.get(namespace)
93
87
  if (pkgConfig) {
94
88
  const extAgent = pikkuState(pkgConfig.package, 'agent', 'agents').get(
95
89
  localName
@@ -151,6 +145,7 @@ export function createScopedChannel(
151
145
  return capturedApproval
152
146
  },
153
147
  close: () => {},
148
+ sendBinary: (data) => parent.sendBinary(data),
154
149
  send: (event: AIStreamEvent) => {
155
150
  if (event.type === 'done') return
156
151
  if (event.type === 'approval-request') {
@@ -163,6 +158,7 @@ export function createScopedChannel(
163
158
  return
164
159
  }
165
160
  if (
161
+ event.type === 'step-start' ||
166
162
  event.type === 'text-delta' ||
167
163
  event.type === 'reasoning-delta' ||
168
164
  event.type === 'tool-call' ||
@@ -179,14 +175,15 @@ export function createScopedChannel(
179
175
  }
180
176
 
181
177
  export function buildToolDefs(
182
- singletonServices: CoreSingletonServices,
183
178
  params: RunAIAgentParams,
184
179
  agentSessionMap: Map<string, string>,
185
180
  resourceId: string,
186
181
  agentName: string,
187
182
  packageName: string | null,
188
- streamContext?: StreamContext
183
+ streamContext?: StreamContext,
184
+ aiMiddlewares?: PikkuAIMiddlewareHooks[]
189
185
  ): { tools: AIAgentToolDef[]; missingRpcs: string[] } {
186
+ const singletonServices = getSingletonServices()
190
187
  const tools: AIAgentToolDef[] = []
191
188
  const missingRpcs: string[] = []
192
189
  const approvalPolicy =
@@ -228,39 +225,18 @@ export function buildToolDefs(
228
225
  approvalPolicy === 'all' ||
229
226
  (approvalPolicy === 'explicit' && fnMeta?.requiresApproval)
230
227
 
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
228
  tools.push({
245
229
  name: pikkuFuncId,
246
230
  description: fnMeta?.description || fnMeta?.title || toolName,
247
231
  inputSchema,
232
+ needsApproval: needsApproval || undefined,
248
233
  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
234
  const wire: PikkuWire = params.sessionService
259
235
  ? { ...createMiddlewareSessionWireProps(params.sessionService) }
260
236
  : {}
261
237
  return runPikkuFunc('agent', `tool:${pikkuFuncId}`, pikkuFuncId, {
262
238
  singletonServices,
263
- createWireServices: params.createWireServices,
239
+ createWireServices: getCreateWireServices(),
264
240
  data: () => toolInput,
265
241
  wire,
266
242
  sessionService: params.sessionService,
@@ -362,6 +338,62 @@ export function buildToolDefs(
362
338
  }
363
339
  }
364
340
 
341
+ const hasToolHooks = aiMiddlewares?.some(
342
+ (mw) => mw.beforeToolCall || mw.afterToolCall
343
+ )
344
+ if (hasToolHooks) {
345
+ for (const tool of tools) {
346
+ const originalExecute = tool.execute
347
+ tool.execute = async (toolInput: unknown) => {
348
+ const toolCallId = randomUUID()
349
+ let args = (toolInput ?? {}) as Record<string, unknown>
350
+
351
+ for (const mw of aiMiddlewares!) {
352
+ if (mw.beforeToolCall) {
353
+ const beforeResult = await mw.beforeToolCall(singletonServices, {
354
+ toolName: tool.name,
355
+ toolCallId,
356
+ args,
357
+ })
358
+ if (beforeResult && 'args' in beforeResult) {
359
+ args = beforeResult.args
360
+ }
361
+ }
362
+ }
363
+
364
+ const startTime = Date.now()
365
+ let result: unknown
366
+ let execError: unknown
367
+ try {
368
+ result = await originalExecute(args)
369
+ } catch (err) {
370
+ execError = err
371
+ result = err instanceof Error ? err.message : String(err)
372
+ }
373
+ const durationMs = Date.now() - startTime
374
+
375
+ for (let i = aiMiddlewares!.length - 1; i >= 0; i--) {
376
+ const mw = aiMiddlewares![i]
377
+ if (mw.afterToolCall) {
378
+ const afterResult = await mw.afterToolCall(singletonServices, {
379
+ toolName: tool.name,
380
+ toolCallId,
381
+ args,
382
+ result,
383
+ durationMs,
384
+ })
385
+ if (afterResult && 'result' in afterResult) {
386
+ result = afterResult.result
387
+ }
388
+ }
389
+ }
390
+
391
+ if (execError) throw execError
392
+ return result
393
+ }
394
+ }
395
+ }
396
+
365
397
  return { tools, missingRpcs }
366
398
  }
367
399
 
@@ -372,7 +404,7 @@ export async function prepareAgentRun(
372
404
  agentSessionMap: Map<string, string>,
373
405
  streamContext?: StreamContext
374
406
  ) {
375
- const { singletonServices } = params
407
+ const singletonServices = getSingletonServices()
376
408
  const { agent, packageName, resolvedName } = resolveAgent(agentName)
377
409
 
378
410
  const agentRunner = singletonServices.aiAgentRunner
@@ -418,29 +450,48 @@ export async function prepareAgentRun(
418
450
  workingMemoryJsonSchema
419
451
  )
420
452
 
453
+ const userContent: AIMessage['content'] = input.attachments?.length
454
+ ? [
455
+ { type: 'text' as const, text: input.message },
456
+ ...input.attachments.map(
457
+ (a) =>
458
+ ({
459
+ type: a.type,
460
+ data: a.data,
461
+ url: a.url,
462
+ mediaType: a.mediaType,
463
+ ...(a.filename ? { filename: a.filename } : {}),
464
+ }) as AIContentPart
465
+ ),
466
+ ]
467
+ : input.message
468
+
421
469
  const userMessage: AIMessage = {
422
470
  id: randomUUID(),
423
471
  role: 'user',
424
- content: input.message,
472
+ content: userContent,
425
473
  createdAt: new Date(),
426
474
  }
427
475
 
428
476
  const allMessages = [...contextMessages, ...messages, userMessage]
429
477
  const trimmedMessages = trimMessages(allMessages)
430
478
 
479
+ const aiMiddlewares: PikkuAIMiddlewareHooks[] = agent.aiMiddleware ?? []
480
+
431
481
  const { tools, missingRpcs } = buildToolDefs(
432
- singletonServices,
433
482
  params,
434
483
  agentSessionMap,
435
484
  input.resourceId,
436
485
  resolvedName,
437
486
  packageName,
438
- streamContext
487
+ streamContext,
488
+ aiMiddlewares
439
489
  )
440
490
 
441
491
  const instructions = buildInstructions(resolvedName, packageName)
442
492
 
443
493
  const resolved = resolveModelConfig(resolvedName, agent)
494
+ const maxSteps = resolved.maxSteps ?? 10
444
495
 
445
496
  const runnerParams: AIAgentRunnerParams = {
446
497
  model: resolved.model,
@@ -448,7 +499,7 @@ export async function prepareAgentRun(
448
499
  instructions,
449
500
  messages: trimmedMessages,
450
501
  tools,
451
- maxSteps: resolved.maxSteps ?? 10,
502
+ maxSteps: 1,
452
503
  toolChoice: agent.toolChoice ?? 'auto',
453
504
  outputSchema,
454
505
  }
@@ -463,6 +514,7 @@ export async function prepareAgentRun(
463
514
  threadId,
464
515
  userMessage,
465
516
  runnerParams,
517
+ maxSteps,
466
518
  missingRpcs,
467
519
  workingMemoryJsonSchema,
468
520
  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 = (