@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
@@ -9,13 +9,17 @@ import type {
9
9
  CoreAIAgent,
10
10
  AIAgentMemoryConfig,
11
11
  } from './ai-agent.types.js'
12
- import { pikkuState } from '../../pikku-state.js'
12
+ import { pikkuState, getSingletonServices } from '../../pikku-state.js'
13
13
  import {
14
14
  combineChannelMiddleware,
15
15
  wrapChannelWithMiddleware,
16
16
  } from '../channel/channel-middleware-runner.js'
17
17
  import { randomUUID } from 'crypto'
18
18
  import type { AIStorageService } from '../../services/ai-storage-service.js'
19
+ import type {
20
+ AIAgentRunnerParams,
21
+ AIAgentStepResult,
22
+ } from '../../services/ai-agent-runner-service.js'
19
23
 
20
24
  import {
21
25
  parseWorkingMemory,
@@ -35,6 +39,10 @@ import {
35
39
  type StreamAIAgentOptions,
36
40
  type StreamContext,
37
41
  } from './ai-agent-prepare.js'
42
+ import {
43
+ createAssistantUIChannel,
44
+ parseAssistantUIInput,
45
+ } from './ai-agent-assistant-ui.js'
38
46
  import { resolveModelConfig } from './ai-agent-model-config.js'
39
47
  import type { AIRunStateService } from '../../services/ai-run-state-service.js'
40
48
  import type { AIAgentRunnerService } from '../../services/ai-agent-runner-service.js'
@@ -42,6 +50,7 @@ import type { AIAgentRunnerService } from '../../services/ai-agent-runner-servic
42
50
  type PersistingChannel = AIStreamChannel & {
43
51
  fullText: string
44
52
  flush: () => Promise<void>
53
+ totalUsage: { inputTokens: number; outputTokens: number; model?: string }
45
54
  }
46
55
 
47
56
  function createPersistingChannel(
@@ -53,6 +62,14 @@ function createPersistingChannel(
53
62
  let stepText = ''
54
63
  let stepToolCalls: AIToolCall[] = []
55
64
  let stepToolResults: AIToolResult[] = []
65
+ const totalUsage: {
66
+ inputTokens: number
67
+ outputTokens: number
68
+ model?: string
69
+ } = {
70
+ inputTokens: 0,
71
+ outputTokens: 0,
72
+ }
56
73
 
57
74
  const flushStep = async () => {
58
75
  if (!storage) return
@@ -92,8 +109,12 @@ function createPersistingChannel(
92
109
  get fullText() {
93
110
  return fullText
94
111
  },
112
+ get totalUsage() {
113
+ return totalUsage
114
+ },
95
115
  flush: flushStep,
96
116
  close: () => parent.close(),
117
+ sendBinary: (data) => parent.sendBinary(data),
97
118
  send: (event: AIStreamEvent) => {
98
119
  if (storage) {
99
120
  switch (event.type) {
@@ -119,6 +140,9 @@ function createPersistingChannel(
119
140
  })
120
141
  break
121
142
  case 'usage':
143
+ totalUsage.inputTokens += event.tokens.input
144
+ totalUsage.outputTokens += event.tokens.output
145
+ if (event.model) totalUsage.model = event.model
122
146
  flushStep()
123
147
  break
124
148
  case 'done':
@@ -144,6 +168,7 @@ async function postStreamCleanup(
144
168
  aiRunState: AIRunStateService,
145
169
  runId: string
146
170
  ): Promise<void> {
171
+ const usage = persistingChannel.totalUsage
147
172
  let outputText = persistingChannel.fullText
148
173
  let outputMessages = messages
149
174
  for (let i = aiMiddlewares.length - 1; i >= 0; i--) {
@@ -152,7 +177,10 @@ async function postStreamCleanup(
152
177
  const result = await mw.modifyOutput(singletonServices, {
153
178
  text: outputText,
154
179
  messages: outputMessages,
155
- usage: { inputTokens: 0, outputTokens: 0 },
180
+ usage: {
181
+ inputTokens: usage.inputTokens,
182
+ outputTokens: usage.outputTokens,
183
+ },
156
184
  })
157
185
  outputText = result.text
158
186
  outputMessages = result.messages
@@ -183,18 +211,254 @@ async function postStreamCleanup(
183
211
  }
184
212
  }
185
213
 
186
- await aiRunState.updateRun(runId, { status: 'completed' })
214
+ await aiRunState.updateRun(runId, {
215
+ status: 'completed',
216
+ ...(usage.model
217
+ ? {
218
+ usage: {
219
+ inputTokens: usage.inputTokens,
220
+ outputTokens: usage.outputTokens,
221
+ model: usage.model,
222
+ },
223
+ }
224
+ : {}),
225
+ })
226
+ }
227
+
228
+ type StepLoopParams = {
229
+ agent: CoreAIAgent
230
+ runnerParams: AIAgentRunnerParams
231
+ maxSteps: number
232
+ agentRunner: AIAgentRunnerService
233
+ persistingChannel: PersistingChannel
234
+ channel: AIStreamChannel
235
+ runId: string
236
+ aiMiddlewares: PikkuAIMiddlewareHooks[]
237
+ }
238
+
239
+ type StepLoopResult =
240
+ | { outcome: 'done' }
241
+ | { outcome: 'approval'; approval: ToolApprovalRequired }
242
+
243
+ async function runStreamStepLoop(
244
+ params: StepLoopParams
245
+ ): Promise<StepLoopResult> {
246
+ const {
247
+ agent,
248
+ runnerParams,
249
+ maxSteps,
250
+ agentRunner,
251
+ persistingChannel,
252
+ channel,
253
+ runId,
254
+ aiMiddlewares,
255
+ } = params
256
+
257
+ const singletonServices = getSingletonServices()
258
+
259
+ for (let step = 0; step < maxSteps; step++) {
260
+ if (agent.prepareStep) {
261
+ let stopped = false
262
+ await agent.prepareStep({
263
+ stepNumber: step,
264
+ messages: runnerParams.messages,
265
+ tools: runnerParams.tools,
266
+ toolChoice: runnerParams.toolChoice,
267
+ model: runnerParams.model,
268
+ stop: () => {
269
+ stopped = true
270
+ },
271
+ })
272
+ if (stopped) break
273
+ }
274
+
275
+ channel.send({ type: 'step-start', stepNumber: step })
276
+
277
+ const stepResult = await agentRunner.stream(runnerParams, persistingChannel)
278
+
279
+ for (const mw of aiMiddlewares) {
280
+ if (mw.afterStep) {
281
+ await mw.afterStep(singletonServices, {
282
+ stepNumber: step,
283
+ text: stepResult.text,
284
+ toolCalls: stepResult.toolCalls,
285
+ toolResults: stepResult.toolResults,
286
+ usage: stepResult.usage,
287
+ finishReason: stepResult.finishReason,
288
+ })
289
+ }
290
+ }
291
+
292
+ if (stepResult.toolCalls.length === 0) break
293
+
294
+ const approvalNeeded = checkForApproval(
295
+ stepResult,
296
+ runnerParams.tools,
297
+ runId
298
+ )
299
+ if (approvalNeeded) {
300
+ return { outcome: 'approval', approval: approvalNeeded }
301
+ }
302
+
303
+ appendStepMessages(runnerParams, stepResult)
304
+ }
305
+
306
+ return { outcome: 'done' }
307
+ }
308
+
309
+ function checkForApproval(
310
+ stepResult: AIAgentStepResult,
311
+ tools: AIAgentRunnerParams['tools'],
312
+ runId: string
313
+ ): ToolApprovalRequired | null {
314
+ for (const tc of stepResult.toolCalls) {
315
+ const toolDef = tools.find((t) => t.name === tc.toolName)
316
+
317
+ if (toolDef?.needsApproval) {
318
+ return new ToolApprovalRequired(tc.toolCallId, tc.toolName, tc.args)
319
+ }
320
+
321
+ const tr = stepResult.toolResults.find(
322
+ (r) => r.toolCallId === tc.toolCallId
323
+ )
324
+ if (
325
+ tr?.result &&
326
+ typeof tr.result === 'object' &&
327
+ '__approvalRequired' in (tr.result as object)
328
+ ) {
329
+ const r = tr.result as {
330
+ toolName: string
331
+ args: unknown
332
+ reason?: string
333
+ displayToolName?: string
334
+ displayArgs?: unknown
335
+ agentRunId?: string
336
+ }
337
+ return new ToolApprovalRequired(
338
+ tc.toolCallId,
339
+ r.toolName,
340
+ r.args,
341
+ r.reason,
342
+ r.displayToolName,
343
+ r.displayArgs,
344
+ r.agentRunId
345
+ )
346
+ }
347
+ }
348
+ return null
349
+ }
350
+
351
+ function appendStepMessages(
352
+ runnerParams: AIAgentRunnerParams,
353
+ stepResult: AIAgentStepResult
354
+ ): void {
355
+ const assistantMsg: AIMessage = {
356
+ id: randomUUID(),
357
+ role: 'assistant',
358
+ content: stepResult.text || undefined,
359
+ toolCalls:
360
+ stepResult.toolCalls.length > 0
361
+ ? stepResult.toolCalls.map((tc) => ({
362
+ id: tc.toolCallId,
363
+ name: tc.toolName,
364
+ args: tc.args as Record<string, unknown>,
365
+ }))
366
+ : undefined,
367
+ createdAt: new Date(),
368
+ }
369
+ runnerParams.messages.push(assistantMsg)
370
+
371
+ if (stepResult.toolResults.length > 0) {
372
+ const toolMsg: AIMessage = {
373
+ id: randomUUID(),
374
+ role: 'tool',
375
+ toolResults: stepResult.toolResults.map((tr) => ({
376
+ id: tr.toolCallId,
377
+ name: tr.toolName,
378
+ result:
379
+ typeof tr.result === 'string' ? tr.result : JSON.stringify(tr.result),
380
+ })),
381
+ createdAt: new Date(),
382
+ }
383
+ runnerParams.messages.push(toolMsg)
384
+ }
385
+ }
386
+
387
+ function handleApproval(
388
+ err: ToolApprovalRequired,
389
+ runId: string,
390
+ channel: AIStreamChannel,
391
+ aiRunState: AIRunStateService,
392
+ persistingChannel: PersistingChannel
393
+ ): Promise<void> {
394
+ return (async () => {
395
+ await persistingChannel.flush()
396
+
397
+ const pendingApproval = err.agentRunId
398
+ ? {
399
+ type: 'agent-call' as const,
400
+ toolCallId: err.toolCallId,
401
+ agentName: err.toolName,
402
+ agentRunId: err.agentRunId,
403
+ displayToolName: err.displayToolName ?? err.toolName,
404
+ displayArgs: err.displayArgs ?? err.args,
405
+ }
406
+ : {
407
+ type: 'tool-call' as const,
408
+ toolCallId: err.toolCallId,
409
+ toolName: err.toolName,
410
+ args: err.args,
411
+ }
412
+
413
+ await aiRunState.updateRun(runId, {
414
+ status: 'suspended',
415
+ suspendReason: 'approval',
416
+ pendingApprovals: [pendingApproval],
417
+ })
418
+
419
+ const approvalEvent = {
420
+ type: 'approval-request' as const,
421
+ toolCallId: err.toolCallId,
422
+ toolName: err.displayToolName ?? err.toolName,
423
+ args: err.displayArgs ?? err.args,
424
+ reason: err.reason,
425
+ runId,
426
+ }
427
+ channel.send(approvalEvent as any)
428
+ channel.send({ type: 'done' })
429
+ channel.close()
430
+ })()
187
431
  }
188
432
 
189
433
  export async function streamAIAgent(
190
434
  agentName: string,
191
- input: { message: string; threadId: string; resourceId: string },
435
+ input:
436
+ | { message: string; threadId: string; resourceId: string }
437
+ | Record<string, unknown>,
192
438
  channel: AIStreamChannel,
193
439
  params: RunAIAgentParams,
194
440
  agentSessionMap?: Map<string, string>,
195
441
  options?: StreamAIAgentOptions
196
442
  ): Promise<void> {
197
443
  const sessionMap = agentSessionMap ?? new Map<string, string>()
444
+
445
+ const { agent: resolvedAgentForProtocol } = resolveAgent(agentName)
446
+ const inputRecord = input as Record<string, unknown>
447
+ const useUIMessageStream =
448
+ resolvedAgentForProtocol.protocol === 'ui-message-stream' ||
449
+ Array.isArray(inputRecord.messages)
450
+ let normalizedInput: { message: string; threadId: string; resourceId: string }
451
+ if (useUIMessageStream) {
452
+ normalizedInput = parseAssistantUIInput(inputRecord)
453
+ channel = createAssistantUIChannel(channel)
454
+ } else {
455
+ normalizedInput = input as {
456
+ message: string
457
+ threadId: string
458
+ resourceId: string
459
+ }
460
+ }
461
+
198
462
  const streamContext: StreamContext = { channel, options }
199
463
 
200
464
  const {
@@ -207,11 +471,18 @@ export async function streamAIAgent(
207
471
  threadId,
208
472
  userMessage,
209
473
  runnerParams,
474
+ maxSteps,
210
475
  missingRpcs,
211
476
  workingMemorySchemaName,
212
- } = await prepareAgentRun(agentName, input, params, sessionMap, streamContext)
477
+ } = await prepareAgentRun(
478
+ agentName,
479
+ normalizedInput,
480
+ params,
481
+ sessionMap,
482
+ streamContext
483
+ )
213
484
 
214
- const { singletonServices } = params
485
+ const singletonServices = getSingletonServices()
215
486
  const { aiRunState } = singletonServices
216
487
  if (!aiRunState) {
217
488
  throw new Error('AIRunStateService not available in singletonServices')
@@ -221,7 +492,7 @@ export async function streamAIAgent(
221
492
  await aiRunState.createRun({
222
493
  agentName,
223
494
  threadId,
224
- resourceId: input.resourceId,
495
+ resourceId: normalizedInput.resourceId,
225
496
  status: 'suspended',
226
497
  suspendReason: 'rpc-missing',
227
498
  missingRpcs,
@@ -254,7 +525,7 @@ export async function streamAIAgent(
254
525
  const runId = await aiRunState.createRun({
255
526
  agentName,
256
527
  threadId,
257
- resourceId: input.resourceId,
528
+ resourceId: normalizedInput.resourceId,
258
529
  status: 'running',
259
530
  usage: { inputTokens: 0, outputTokens: 0, model: agent.model },
260
531
  createdAt: new Date(),
@@ -277,7 +548,12 @@ export async function streamAIAgent(
277
548
  allEvents,
278
549
  state,
279
550
  })
280
- if (result != null) await next(result)
551
+ if (result == null) return
552
+ if (Array.isArray(result)) {
553
+ for (const r of result) await next(r)
554
+ } else {
555
+ await next(result)
556
+ }
281
557
  }
282
558
  })
283
559
 
@@ -311,7 +587,27 @@ export async function streamAIAgent(
311
587
  )
312
588
 
313
589
  try {
314
- await agentRunner.stream(runnerParams, persistingChannel)
590
+ const loopResult = await runStreamStepLoop({
591
+ agent,
592
+ runnerParams,
593
+ maxSteps,
594
+ agentRunner,
595
+ persistingChannel,
596
+ channel,
597
+ runId,
598
+ aiMiddlewares,
599
+ })
600
+
601
+ if (loopResult.outcome === 'approval') {
602
+ await handleApproval(
603
+ loopResult.approval,
604
+ runId,
605
+ channel,
606
+ aiRunState,
607
+ persistingChannel
608
+ )
609
+ return
610
+ }
315
611
 
316
612
  await postStreamCleanup(
317
613
  persistingChannel,
@@ -325,46 +621,23 @@ export async function streamAIAgent(
325
621
  aiRunState,
326
622
  runId
327
623
  )
328
- } catch (err) {
329
- if (err instanceof ToolApprovalRequired) {
330
- await persistingChannel.flush()
331
-
332
- const pendingApproval = err.agentRunId
333
- ? {
334
- type: 'agent-call' as const,
335
- toolCallId: err.toolCallId,
336
- agentName: err.toolName,
337
- agentRunId: err.agentRunId,
338
- displayToolName: err.displayToolName ?? err.toolName,
339
- displayArgs: err.displayArgs ?? err.args,
340
- }
341
- : {
342
- type: 'tool-call' as const,
343
- toolCallId: err.toolCallId,
344
- toolName: err.toolName,
345
- args: err.args,
346
- }
347
-
348
- await aiRunState.updateRun(runId, {
349
- status: 'suspended',
350
- suspendReason: 'approval',
351
- pendingApprovals: [pendingApproval],
352
- })
353
624
 
354
- const approvalEvent = {
355
- type: 'approval-request' as const,
356
- toolCallId: err.toolCallId,
357
- toolName: err.displayToolName ?? err.toolName,
358
- args: err.displayArgs ?? err.args,
359
- reason: err.reason,
360
- runId,
625
+ channel.send({ type: 'done' })
626
+ channel.close()
627
+ } catch (err) {
628
+ for (const mw of aiMiddlewares) {
629
+ if (mw.onError) {
630
+ try {
631
+ await mw.onError(singletonServices, {
632
+ error: err instanceof Error ? err : new Error(String(err)),
633
+ stepNumber: -1,
634
+ messages: runnerParams.messages,
635
+ })
636
+ } catch {
637
+ // onError hooks must not affect error flow
638
+ }
361
639
  }
362
- channel.send(approvalEvent as any)
363
- channel.send({ type: 'done' })
364
- channel.close()
365
- return
366
640
  }
367
-
368
641
  await aiRunState.updateRun(runId, { status: 'failed' })
369
642
  channel.send({
370
643
  type: 'error',
@@ -377,6 +650,7 @@ export async function streamAIAgent(
377
650
 
378
651
  export async function resumeAIAgent(
379
652
  input: {
653
+ runId: string
380
654
  toolCallId: string
381
655
  approved: boolean
382
656
  },
@@ -384,18 +658,25 @@ export async function resumeAIAgent(
384
658
  params: RunAIAgentParams,
385
659
  options?: StreamAIAgentOptions
386
660
  ): Promise<void> {
387
- const { singletonServices } = params
661
+ const singletonServices = getSingletonServices()
388
662
  const { aiRunState } = singletonServices
389
663
  if (!aiRunState) {
390
664
  throw new Error('AIRunStateService not available in singletonServices')
391
665
  }
392
666
 
393
- const found = await aiRunState.findRunByToolCallId(input.toolCallId)
394
- if (!found) {
395
- throw new Error(`No suspended run found for toolCallId ${input.toolCallId}`)
667
+ const run = await aiRunState.getRun(input.runId)
668
+ if (!run) {
669
+ throw new Error(`No run found for runId ${input.runId}`)
396
670
  }
397
671
 
398
- const { run, approval: pending } = found
672
+ const pending = run.pendingApprovals?.find(
673
+ (p) => p.toolCallId === input.toolCallId
674
+ )
675
+ if (!pending) {
676
+ throw new Error(
677
+ `No pending approval for toolCallId ${input.toolCallId} on run ${input.runId}`
678
+ )
679
+ }
399
680
 
400
681
  await aiRunState.resolveApproval(
401
682
  input.toolCallId,
@@ -476,7 +757,11 @@ export async function resumeAIAgent(
476
757
  })
477
758
 
478
759
  await resumeAIAgent(
479
- { toolCallId: subPending.toolCallId, approved: true },
760
+ {
761
+ runId: pending.agentRunId,
762
+ toolCallId: subPending.toolCallId,
763
+ approved: true,
764
+ },
480
765
  subChannel,
481
766
  params,
482
767
  options
@@ -510,14 +795,16 @@ export async function resumeAIAgent(
510
795
  channel,
511
796
  options: { ...options, requiresToolApproval: false },
512
797
  }
798
+ const aiMiddlewaresForResume: PikkuAIMiddlewareHooks[] =
799
+ agent.aiMiddleware ?? []
513
800
  const { tools } = buildToolDefs(
514
- singletonServices,
515
801
  params,
516
802
  new Map<string, string>(),
517
803
  run.resourceId,
518
804
  resolvedName,
519
805
  packageName,
520
- streamContext
806
+ streamContext,
807
+ aiMiddlewaresForResume
521
808
  )
522
809
 
523
810
  const matchingTool = tools.find((t) => t.name === pending.toolName)
@@ -529,14 +816,10 @@ export async function resumeAIAgent(
529
816
 
530
817
  const toolArgs =
531
818
  typeof pending.args === 'string' ? JSON.parse(pending.args) : pending.args
532
- const { toolApprovalReason: _ignored, ...cleanArgs } = toolArgs as Record<
533
- string,
534
- unknown
535
- >
536
819
 
537
820
  let toolResult: unknown
538
821
  try {
539
- toolResult = await matchingTool.execute(cleanArgs)
822
+ toolResult = await matchingTool.execute(toolArgs)
540
823
  } catch (execErr) {
541
824
  toolResult = `Error: ${execErr instanceof Error ? execErr.message : String(execErr)}`
542
825
  }
@@ -598,7 +881,7 @@ async function continueAfterToolResult(
598
881
  aiRunState: AIRunStateService,
599
882
  options?: StreamAIAgentOptions
600
883
  ): Promise<void> {
601
- const { singletonServices } = params
884
+ const singletonServices = getSingletonServices()
602
885
  const agentsMeta = pikkuState(packageName, 'agent', 'agentsMeta')
603
886
  const meta = agentsMeta[resolvedName]
604
887
  const workingMemorySchemaName = meta?.workingMemorySchema ?? null
@@ -651,7 +934,12 @@ async function continueAfterToolResult(
651
934
  allEvents,
652
935
  state,
653
936
  })
654
- if (result != null) await next(result)
937
+ if (result == null) return
938
+ if (Array.isArray(result)) {
939
+ for (const r of result) await next(r)
940
+ } else {
941
+ await next(result)
942
+ }
655
943
  }
656
944
  })
657
945
 
@@ -684,24 +972,25 @@ async function continueAfterToolResult(
684
972
 
685
973
  const streamContext: StreamContext = { channel, options }
686
974
  const resumeTools = buildToolDefs(
687
- singletonServices,
688
975
  params,
689
976
  new Map<string, string>(),
690
977
  run.resourceId,
691
978
  resolvedName,
692
979
  packageName,
693
- streamContext
980
+ streamContext,
981
+ aiMiddlewares
694
982
  ).tools
695
983
 
696
984
  const resolved = resolveModelConfig(resolvedName, agent)
985
+ const maxSteps = resolved.maxSteps ?? 10
697
986
 
698
- const runnerParams = {
987
+ const runnerParams: AIAgentRunnerParams = {
699
988
  model: resolved.model,
700
989
  temperature: resolved.temperature,
701
990
  instructions: modifiedInstructions,
702
991
  messages: modifiedMessages,
703
992
  tools: resumeTools,
704
- maxSteps: resolved.maxSteps ?? 10,
993
+ maxSteps: 1,
705
994
  toolChoice: (agent.toolChoice ?? 'auto') as 'auto' | 'required' | 'none',
706
995
  outputSchema: meta?.outputSchema
707
996
  ? pikkuState(packageName, 'misc', 'schemas').get(meta.outputSchema)
@@ -709,7 +998,27 @@ async function continueAfterToolResult(
709
998
  }
710
999
 
711
1000
  try {
712
- await agentRunner.stream(runnerParams, persistingChannel)
1001
+ const loopResult = await runStreamStepLoop({
1002
+ agent,
1003
+ runnerParams,
1004
+ maxSteps,
1005
+ agentRunner,
1006
+ persistingChannel,
1007
+ channel,
1008
+ runId: run.runId,
1009
+ aiMiddlewares,
1010
+ })
1011
+
1012
+ if (loopResult.outcome === 'approval') {
1013
+ await handleApproval(
1014
+ loopResult.approval,
1015
+ run.runId,
1016
+ channel,
1017
+ aiRunState,
1018
+ persistingChannel
1019
+ )
1020
+ return
1021
+ }
713
1022
 
714
1023
  await postStreamCleanup(
715
1024
  persistingChannel,
@@ -723,46 +1032,23 @@ async function continueAfterToolResult(
723
1032
  aiRunState,
724
1033
  run.runId
725
1034
  )
726
- } catch (err) {
727
- if (err instanceof ToolApprovalRequired) {
728
- await persistingChannel.flush()
729
-
730
- const pendingApproval = err.agentRunId
731
- ? {
732
- type: 'agent-call' as const,
733
- toolCallId: err.toolCallId,
734
- agentName: err.toolName,
735
- agentRunId: err.agentRunId,
736
- displayToolName: err.displayToolName ?? err.toolName,
737
- displayArgs: err.displayArgs ?? err.args,
738
- }
739
- : {
740
- type: 'tool-call' as const,
741
- toolCallId: err.toolCallId,
742
- toolName: err.toolName,
743
- args: err.args,
744
- }
745
-
746
- await aiRunState.updateRun(run.runId, {
747
- status: 'suspended',
748
- suspendReason: 'approval',
749
- pendingApprovals: [pendingApproval],
750
- })
751
1035
 
752
- const approvalEvent = {
753
- type: 'approval-request' as const,
754
- toolCallId: err.toolCallId,
755
- toolName: err.displayToolName ?? err.toolName,
756
- args: err.displayArgs ?? err.args,
757
- reason: err.reason,
758
- runId: run.runId,
1036
+ channel.send({ type: 'done' })
1037
+ channel.close()
1038
+ } catch (err) {
1039
+ for (const mw of aiMiddlewares) {
1040
+ if (mw.onError) {
1041
+ try {
1042
+ await mw.onError(singletonServices, {
1043
+ error: err instanceof Error ? err : new Error(String(err)),
1044
+ stepNumber: -1,
1045
+ messages: runnerParams.messages,
1046
+ })
1047
+ } catch {
1048
+ // onError hooks must not affect error flow
1049
+ }
759
1050
  }
760
- channel.send(approvalEvent as any)
761
- channel.send({ type: 'done' })
762
- channel.close()
763
- return
764
1051
  }
765
-
766
1052
  await aiRunState.updateRun(run.runId, { status: 'failed' })
767
1053
  channel.send({
768
1054
  type: 'error',