@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
@@ -1,9 +1,11 @@
1
1
  import { saveMessages } from './ai-agent-memory.js';
2
2
  import { prepareAgentRun } from './ai-agent-prepare.js';
3
+ import { getSingletonServices } from '../../pikku-state.js';
4
+ import { randomUUID } from 'crypto';
3
5
  export async function runAIAgent(agentName, input, params, agentSessionMap) {
4
6
  const sessionMap = agentSessionMap ?? new Map();
5
- const { agent, agentRunner, storage, memoryConfig, threadId, userMessage, runnerParams, missingRpcs, workingMemoryJsonSchema, workingMemorySchemaName, } = await prepareAgentRun(agentName, input, params, sessionMap);
6
- const { singletonServices } = params;
7
+ const { agent, agentRunner, storage, memoryConfig, threadId, userMessage, runnerParams, maxSteps, missingRpcs, workingMemoryJsonSchema, workingMemorySchemaName, } = await prepareAgentRun(agentName, input, params, sessionMap);
8
+ const singletonServices = getSingletonServices();
7
9
  const { aiRunState } = singletonServices;
8
10
  if (!aiRunState) {
9
11
  throw new Error('AIRunStateService not available in singletonServices');
@@ -53,7 +55,90 @@ export async function runAIAgent(agentName, input, params, agentSessionMap) {
53
55
  updatedAt: new Date(),
54
56
  });
55
57
  try {
56
- const result = await agentRunner.run(runnerParams);
58
+ const accumulatedSteps = [];
59
+ const totalUsage = { inputTokens: 0, outputTokens: 0 };
60
+ let lastStepResult = null;
61
+ for (let step = 0; step < maxSteps; step++) {
62
+ if (agent.prepareStep) {
63
+ let stopped = false;
64
+ await agent.prepareStep({
65
+ stepNumber: step,
66
+ messages: runnerParams.messages,
67
+ tools: runnerParams.tools,
68
+ toolChoice: runnerParams.toolChoice,
69
+ model: runnerParams.model,
70
+ stop: () => {
71
+ stopped = true;
72
+ },
73
+ });
74
+ if (stopped)
75
+ break;
76
+ }
77
+ const stepResult = await agentRunner.run(runnerParams);
78
+ lastStepResult = stepResult;
79
+ totalUsage.inputTokens += stepResult.usage.inputTokens;
80
+ totalUsage.outputTokens += stepResult.usage.outputTokens;
81
+ for (const mw of aiMiddlewares) {
82
+ if (mw.afterStep) {
83
+ await mw.afterStep(singletonServices, {
84
+ stepNumber: step,
85
+ text: stepResult.text,
86
+ toolCalls: stepResult.toolCalls,
87
+ toolResults: stepResult.toolResults,
88
+ usage: stepResult.usage,
89
+ finishReason: stepResult.finishReason,
90
+ });
91
+ }
92
+ }
93
+ accumulatedSteps.push({
94
+ usage: stepResult.usage,
95
+ toolCalls: stepResult.toolCalls.map((tc) => {
96
+ const tr = stepResult.toolResults.find((r) => r.toolCallId === tc.toolCallId);
97
+ return {
98
+ name: tc.toolName,
99
+ args: tc.args,
100
+ result: typeof tr?.result === 'string'
101
+ ? tr.result
102
+ : JSON.stringify(tr?.result ?? ''),
103
+ };
104
+ }),
105
+ });
106
+ if (stepResult.toolCalls.length === 0)
107
+ break;
108
+ const assistantMsg = {
109
+ id: randomUUID(),
110
+ role: 'assistant',
111
+ content: stepResult.text || undefined,
112
+ toolCalls: stepResult.toolCalls.map((tc) => ({
113
+ id: tc.toolCallId,
114
+ name: tc.toolName,
115
+ args: tc.args,
116
+ })),
117
+ createdAt: new Date(),
118
+ };
119
+ runnerParams.messages.push(assistantMsg);
120
+ if (stepResult.toolResults.length > 0) {
121
+ const toolMsg = {
122
+ id: randomUUID(),
123
+ role: 'tool',
124
+ toolResults: stepResult.toolResults.map((tr) => ({
125
+ id: tr.toolCallId,
126
+ name: tr.toolName,
127
+ result: typeof tr.result === 'string'
128
+ ? tr.result
129
+ : JSON.stringify(tr.result),
130
+ })),
131
+ createdAt: new Date(),
132
+ };
133
+ runnerParams.messages.push(toolMsg);
134
+ }
135
+ }
136
+ const finalText = lastStepResult?.text ?? '';
137
+ const finalObject = lastStepResult?.object;
138
+ const result = {
139
+ text: finalText,
140
+ steps: accumulatedSteps,
141
+ };
57
142
  const responseText = await saveMessages(storage, threadId, input.resourceId, memoryConfig, userMessage, result, {
58
143
  workingMemoryJsonSchema,
59
144
  workingMemorySchemaName,
@@ -68,7 +153,7 @@ export async function runAIAgent(agentName, input, params, agentSessionMap) {
68
153
  const modResult = await mw.modifyOutput(singletonServices, {
69
154
  text: outputText,
70
155
  messages: outputMessages,
71
- usage: result.usage,
156
+ usage: totalUsage,
72
157
  });
73
158
  outputText = modResult.text;
74
159
  outputMessages = modResult.messages;
@@ -76,18 +161,32 @@ export async function runAIAgent(agentName, input, params, agentSessionMap) {
76
161
  }
77
162
  await aiRunState.updateRun(runId, {
78
163
  status: 'completed',
79
- usage: { ...result.usage, model: agent.model },
164
+ usage: { ...totalUsage, model: agent.model },
80
165
  });
81
166
  return {
82
167
  runId,
83
168
  text: outputText,
84
- object: result.object,
169
+ object: finalObject,
85
170
  threadId,
86
- steps: result.steps,
87
- usage: result.usage,
171
+ steps: accumulatedSteps,
172
+ usage: totalUsage,
88
173
  };
89
174
  }
90
175
  catch (error) {
176
+ for (const mw of aiMiddlewares) {
177
+ if (mw.onError) {
178
+ try {
179
+ await mw.onError(singletonServices, {
180
+ error: error instanceof Error ? error : new Error(String(error)),
181
+ stepNumber: -1,
182
+ messages: runnerParams.messages,
183
+ });
184
+ }
185
+ catch {
186
+ // onError hooks must not affect error flow
187
+ }
188
+ }
189
+ }
91
190
  await aiRunState.updateRun(runId, {
92
191
  status: 'failed',
93
192
  });
@@ -4,8 +4,9 @@ export declare function streamAIAgent(agentName: string, input: {
4
4
  message: string;
5
5
  threadId: string;
6
6
  resourceId: string;
7
- }, channel: AIStreamChannel, params: RunAIAgentParams, agentSessionMap?: Map<string, string>, options?: StreamAIAgentOptions): Promise<void>;
7
+ } | Record<string, unknown>, channel: AIStreamChannel, params: RunAIAgentParams, agentSessionMap?: Map<string, string>, options?: StreamAIAgentOptions): Promise<void>;
8
8
  export declare function resumeAIAgent(input: {
9
+ runId: string;
9
10
  toolCallId: string;
10
11
  approved: boolean;
11
12
  }, channel: AIStreamChannel, params: RunAIAgentParams, options?: StreamAIAgentOptions): Promise<void>;
@@ -1,14 +1,19 @@
1
- import { pikkuState } from '../../pikku-state.js';
1
+ import { pikkuState, getSingletonServices } from '../../pikku-state.js';
2
2
  import { combineChannelMiddleware, wrapChannelWithMiddleware, } from '../channel/channel-middleware-runner.js';
3
3
  import { randomUUID } from 'crypto';
4
4
  import { parseWorkingMemory, deepMergeWorkingMemory, resolveMemoryServices, loadContextMessages, trimMessages, } from './ai-agent-memory.js';
5
5
  import { prepareAgentRun, resolveAgent, buildInstructions, buildToolDefs, createScopedChannel, ToolApprovalRequired, } from './ai-agent-prepare.js';
6
+ import { createAssistantUIChannel, parseAssistantUIInput, } from './ai-agent-assistant-ui.js';
6
7
  import { resolveModelConfig } from './ai-agent-model-config.js';
7
8
  function createPersistingChannel(parent, storage, threadId) {
8
9
  let fullText = '';
9
10
  let stepText = '';
10
11
  let stepToolCalls = [];
11
12
  let stepToolResults = [];
13
+ const totalUsage = {
14
+ inputTokens: 0,
15
+ outputTokens: 0,
16
+ };
12
17
  const flushStep = async () => {
13
18
  if (!storage)
14
19
  return;
@@ -47,6 +52,9 @@ function createPersistingChannel(parent, storage, threadId) {
47
52
  get fullText() {
48
53
  return fullText;
49
54
  },
55
+ get totalUsage() {
56
+ return totalUsage;
57
+ },
50
58
  flush: flushStep,
51
59
  close: () => parent.close(),
52
60
  send: (event) => {
@@ -73,6 +81,10 @@ function createPersistingChannel(parent, storage, threadId) {
73
81
  });
74
82
  break;
75
83
  case 'usage':
84
+ totalUsage.inputTokens += event.tokens.input;
85
+ totalUsage.outputTokens += event.tokens.output;
86
+ if (event.model)
87
+ totalUsage.model = event.model;
76
88
  flushStep();
77
89
  break;
78
90
  case 'done':
@@ -86,6 +98,7 @@ function createPersistingChannel(parent, storage, threadId) {
86
98
  return channel;
87
99
  }
88
100
  async function postStreamCleanup(persistingChannel, aiMiddlewares, singletonServices, storage, memoryConfig, threadId, workingMemorySchemaName, messages, aiRunState, runId) {
101
+ const usage = persistingChannel.totalUsage;
89
102
  let outputText = persistingChannel.fullText;
90
103
  let outputMessages = messages;
91
104
  for (let i = aiMiddlewares.length - 1; i >= 0; i--) {
@@ -94,7 +107,10 @@ async function postStreamCleanup(persistingChannel, aiMiddlewares, singletonServ
94
107
  const result = await mw.modifyOutput(singletonServices, {
95
108
  text: outputText,
96
109
  messages: outputMessages,
97
- usage: { inputTokens: 0, outputTokens: 0 },
110
+ usage: {
111
+ inputTokens: usage.inputTokens,
112
+ outputTokens: usage.outputTokens,
113
+ },
98
114
  });
99
115
  outputText = result.text;
100
116
  outputMessages = result.messages;
@@ -116,13 +132,160 @@ async function postStreamCleanup(persistingChannel, aiMiddlewares, singletonServ
116
132
  await storage.saveWorkingMemory(threadId, 'thread', merged);
117
133
  }
118
134
  }
119
- await aiRunState.updateRun(runId, { status: 'completed' });
135
+ await aiRunState.updateRun(runId, {
136
+ status: 'completed',
137
+ ...(usage.model
138
+ ? {
139
+ usage: {
140
+ inputTokens: usage.inputTokens,
141
+ outputTokens: usage.outputTokens,
142
+ model: usage.model,
143
+ },
144
+ }
145
+ : {}),
146
+ });
147
+ }
148
+ async function runStreamStepLoop(params) {
149
+ const { agent, runnerParams, maxSteps, agentRunner, persistingChannel, channel, runId, aiMiddlewares, } = params;
150
+ const singletonServices = getSingletonServices();
151
+ for (let step = 0; step < maxSteps; step++) {
152
+ if (agent.prepareStep) {
153
+ let stopped = false;
154
+ await agent.prepareStep({
155
+ stepNumber: step,
156
+ messages: runnerParams.messages,
157
+ tools: runnerParams.tools,
158
+ toolChoice: runnerParams.toolChoice,
159
+ model: runnerParams.model,
160
+ stop: () => {
161
+ stopped = true;
162
+ },
163
+ });
164
+ if (stopped)
165
+ break;
166
+ }
167
+ channel.send({ type: 'step-start', stepNumber: step });
168
+ const stepResult = await agentRunner.stream(runnerParams, persistingChannel);
169
+ for (const mw of aiMiddlewares) {
170
+ if (mw.afterStep) {
171
+ await mw.afterStep(singletonServices, {
172
+ stepNumber: step,
173
+ text: stepResult.text,
174
+ toolCalls: stepResult.toolCalls,
175
+ toolResults: stepResult.toolResults,
176
+ usage: stepResult.usage,
177
+ finishReason: stepResult.finishReason,
178
+ });
179
+ }
180
+ }
181
+ if (stepResult.toolCalls.length === 0)
182
+ break;
183
+ const approvalNeeded = checkForApproval(stepResult, runnerParams.tools, runId);
184
+ if (approvalNeeded) {
185
+ return { outcome: 'approval', approval: approvalNeeded };
186
+ }
187
+ appendStepMessages(runnerParams, stepResult);
188
+ }
189
+ return { outcome: 'done' };
190
+ }
191
+ function checkForApproval(stepResult, tools, runId) {
192
+ for (const tc of stepResult.toolCalls) {
193
+ const toolDef = tools.find((t) => t.name === tc.toolName);
194
+ if (toolDef?.needsApproval) {
195
+ return new ToolApprovalRequired(tc.toolCallId, tc.toolName, tc.args);
196
+ }
197
+ const tr = stepResult.toolResults.find((r) => r.toolCallId === tc.toolCallId);
198
+ if (tr?.result &&
199
+ typeof tr.result === 'object' &&
200
+ '__approvalRequired' in tr.result) {
201
+ const r = tr.result;
202
+ return new ToolApprovalRequired(tc.toolCallId, r.toolName, r.args, r.reason, r.displayToolName, r.displayArgs, r.agentRunId);
203
+ }
204
+ }
205
+ return null;
206
+ }
207
+ function appendStepMessages(runnerParams, stepResult) {
208
+ const assistantMsg = {
209
+ id: randomUUID(),
210
+ role: 'assistant',
211
+ content: stepResult.text || undefined,
212
+ toolCalls: stepResult.toolCalls.length > 0
213
+ ? stepResult.toolCalls.map((tc) => ({
214
+ id: tc.toolCallId,
215
+ name: tc.toolName,
216
+ args: tc.args,
217
+ }))
218
+ : undefined,
219
+ createdAt: new Date(),
220
+ };
221
+ runnerParams.messages.push(assistantMsg);
222
+ if (stepResult.toolResults.length > 0) {
223
+ const toolMsg = {
224
+ id: randomUUID(),
225
+ role: 'tool',
226
+ toolResults: stepResult.toolResults.map((tr) => ({
227
+ id: tr.toolCallId,
228
+ name: tr.toolName,
229
+ result: typeof tr.result === 'string' ? tr.result : JSON.stringify(tr.result),
230
+ })),
231
+ createdAt: new Date(),
232
+ };
233
+ runnerParams.messages.push(toolMsg);
234
+ }
235
+ }
236
+ function handleApproval(err, runId, channel, aiRunState, persistingChannel) {
237
+ return (async () => {
238
+ await persistingChannel.flush();
239
+ const pendingApproval = err.agentRunId
240
+ ? {
241
+ type: 'agent-call',
242
+ toolCallId: err.toolCallId,
243
+ agentName: err.toolName,
244
+ agentRunId: err.agentRunId,
245
+ displayToolName: err.displayToolName ?? err.toolName,
246
+ displayArgs: err.displayArgs ?? err.args,
247
+ }
248
+ : {
249
+ type: 'tool-call',
250
+ toolCallId: err.toolCallId,
251
+ toolName: err.toolName,
252
+ args: err.args,
253
+ };
254
+ await aiRunState.updateRun(runId, {
255
+ status: 'suspended',
256
+ suspendReason: 'approval',
257
+ pendingApprovals: [pendingApproval],
258
+ });
259
+ const approvalEvent = {
260
+ type: 'approval-request',
261
+ toolCallId: err.toolCallId,
262
+ toolName: err.displayToolName ?? err.toolName,
263
+ args: err.displayArgs ?? err.args,
264
+ reason: err.reason,
265
+ runId,
266
+ };
267
+ channel.send(approvalEvent);
268
+ channel.send({ type: 'done' });
269
+ channel.close();
270
+ })();
120
271
  }
121
272
  export async function streamAIAgent(agentName, input, channel, params, agentSessionMap, options) {
122
273
  const sessionMap = agentSessionMap ?? new Map();
274
+ const { agent: resolvedAgentForProtocol } = resolveAgent(agentName);
275
+ const inputRecord = input;
276
+ const useUIMessageStream = resolvedAgentForProtocol.protocol === 'ui-message-stream' ||
277
+ Array.isArray(inputRecord.messages);
278
+ let normalizedInput;
279
+ if (useUIMessageStream) {
280
+ normalizedInput = parseAssistantUIInput(inputRecord);
281
+ channel = createAssistantUIChannel(channel);
282
+ }
283
+ else {
284
+ normalizedInput = input;
285
+ }
123
286
  const streamContext = { channel, options };
124
- const { agent, packageName, resolvedName, agentRunner, storage, memoryConfig, threadId, userMessage, runnerParams, missingRpcs, workingMemorySchemaName, } = await prepareAgentRun(agentName, input, params, sessionMap, streamContext);
125
- const { singletonServices } = params;
287
+ const { agent, packageName, resolvedName, agentRunner, storage, memoryConfig, threadId, userMessage, runnerParams, maxSteps, missingRpcs, workingMemorySchemaName, } = await prepareAgentRun(agentName, normalizedInput, params, sessionMap, streamContext);
288
+ const singletonServices = getSingletonServices();
126
289
  const { aiRunState } = singletonServices;
127
290
  if (!aiRunState) {
128
291
  throw new Error('AIRunStateService not available in singletonServices');
@@ -131,7 +294,7 @@ export async function streamAIAgent(agentName, input, channel, params, agentSess
131
294
  await aiRunState.createRun({
132
295
  agentName,
133
296
  threadId,
134
- resourceId: input.resourceId,
297
+ resourceId: normalizedInput.resourceId,
135
298
  status: 'suspended',
136
299
  suspendReason: 'rpc-missing',
137
300
  missingRpcs,
@@ -161,7 +324,7 @@ export async function streamAIAgent(agentName, input, channel, params, agentSess
161
324
  const runId = await aiRunState.createRun({
162
325
  agentName,
163
326
  threadId,
164
- resourceId: input.resourceId,
327
+ resourceId: normalizedInput.resourceId,
165
328
  status: 'running',
166
329
  usage: { inputTokens: 0, outputTokens: 0, model: agent.model },
167
330
  createdAt: new Date(),
@@ -200,44 +363,38 @@ export async function streamAIAgent(agentName, input, channel, params, agentSess
200
363
  : channel;
201
364
  const persistingChannel = createPersistingChannel(wrappedChannel, storage, threadId);
202
365
  try {
203
- await agentRunner.stream(runnerParams, persistingChannel);
366
+ const loopResult = await runStreamStepLoop({
367
+ agent,
368
+ runnerParams,
369
+ maxSteps,
370
+ agentRunner,
371
+ persistingChannel,
372
+ channel,
373
+ runId,
374
+ aiMiddlewares,
375
+ });
376
+ if (loopResult.outcome === 'approval') {
377
+ await handleApproval(loopResult.approval, runId, channel, aiRunState, persistingChannel);
378
+ return;
379
+ }
204
380
  await postStreamCleanup(persistingChannel, aiMiddlewares, singletonServices, storage, memoryConfig, threadId, workingMemorySchemaName, runnerParams.messages, aiRunState, runId);
381
+ channel.send({ type: 'done' });
382
+ channel.close();
205
383
  }
206
384
  catch (err) {
207
- if (err instanceof ToolApprovalRequired) {
208
- await persistingChannel.flush();
209
- const pendingApproval = err.agentRunId
210
- ? {
211
- type: 'agent-call',
212
- toolCallId: err.toolCallId,
213
- agentName: err.toolName,
214
- agentRunId: err.agentRunId,
215
- displayToolName: err.displayToolName ?? err.toolName,
216
- displayArgs: err.displayArgs ?? err.args,
385
+ for (const mw of aiMiddlewares) {
386
+ if (mw.onError) {
387
+ try {
388
+ await mw.onError(singletonServices, {
389
+ error: err instanceof Error ? err : new Error(String(err)),
390
+ stepNumber: -1,
391
+ messages: runnerParams.messages,
392
+ });
217
393
  }
218
- : {
219
- type: 'tool-call',
220
- toolCallId: err.toolCallId,
221
- toolName: err.toolName,
222
- args: err.args,
223
- };
224
- await aiRunState.updateRun(runId, {
225
- status: 'suspended',
226
- suspendReason: 'approval',
227
- pendingApprovals: [pendingApproval],
228
- });
229
- const approvalEvent = {
230
- type: 'approval-request',
231
- toolCallId: err.toolCallId,
232
- toolName: err.displayToolName ?? err.toolName,
233
- args: err.displayArgs ?? err.args,
234
- reason: err.reason,
235
- runId,
236
- };
237
- channel.send(approvalEvent);
238
- channel.send({ type: 'done' });
239
- channel.close();
240
- return;
394
+ catch {
395
+ // onError hooks must not affect error flow
396
+ }
397
+ }
241
398
  }
242
399
  await aiRunState.updateRun(runId, { status: 'failed' });
243
400
  channel.send({
@@ -249,16 +406,19 @@ export async function streamAIAgent(agentName, input, channel, params, agentSess
249
406
  }
250
407
  }
251
408
  export async function resumeAIAgent(input, channel, params, options) {
252
- const { singletonServices } = params;
409
+ const singletonServices = getSingletonServices();
253
410
  const { aiRunState } = singletonServices;
254
411
  if (!aiRunState) {
255
412
  throw new Error('AIRunStateService not available in singletonServices');
256
413
  }
257
- const found = await aiRunState.findRunByToolCallId(input.toolCallId);
258
- if (!found) {
259
- throw new Error(`No suspended run found for toolCallId ${input.toolCallId}`);
414
+ const run = await aiRunState.getRun(input.runId);
415
+ if (!run) {
416
+ throw new Error(`No run found for runId ${input.runId}`);
417
+ }
418
+ const pending = run.pendingApprovals?.find((p) => p.toolCallId === input.toolCallId);
419
+ if (!pending) {
420
+ throw new Error(`No pending approval for toolCallId ${input.toolCallId} on run ${input.runId}`);
260
421
  }
261
- const { run, approval: pending } = found;
262
422
  await aiRunState.resolveApproval(input.toolCallId, input.approved ? 'approved' : 'denied');
263
423
  const { agent, packageName, resolvedName } = resolveAgent(run.agentName);
264
424
  const { storage } = resolveMemoryServices(agent, singletonServices);
@@ -310,7 +470,11 @@ export async function resumeAIAgent(input, channel, params, options) {
310
470
  session: 'resume',
311
471
  input: null,
312
472
  });
313
- await resumeAIAgent({ toolCallId: subPending.toolCallId, approved: true }, subChannel, params, options);
473
+ await resumeAIAgent({
474
+ runId: pending.agentRunId,
475
+ toolCallId: subPending.toolCallId,
476
+ approved: true,
477
+ }, subChannel, params, options);
314
478
  channel.send({
315
479
  type: 'agent-result',
316
480
  agentName: subRun.agentName,
@@ -339,16 +503,16 @@ export async function resumeAIAgent(input, channel, params, options) {
339
503
  channel,
340
504
  options: { ...options, requiresToolApproval: false },
341
505
  };
342
- const { tools } = buildToolDefs(singletonServices, params, new Map(), run.resourceId, resolvedName, packageName, streamContext);
506
+ const aiMiddlewaresForResume = agent.aiMiddleware ?? [];
507
+ const { tools } = buildToolDefs(params, new Map(), run.resourceId, resolvedName, packageName, streamContext, aiMiddlewaresForResume);
343
508
  const matchingTool = tools.find((t) => t.name === pending.toolName);
344
509
  if (!matchingTool) {
345
510
  throw new Error(`Tool "${pending.toolName}" not found in agent definition`);
346
511
  }
347
512
  const toolArgs = typeof pending.args === 'string' ? JSON.parse(pending.args) : pending.args;
348
- const { toolApprovalReason: _ignored, ...cleanArgs } = toolArgs;
349
513
  let toolResult;
350
514
  try {
351
- toolResult = await matchingTool.execute(cleanArgs);
515
+ toolResult = await matchingTool.execute(toolArgs);
352
516
  }
353
517
  catch (execErr) {
354
518
  toolResult = `Error: ${execErr instanceof Error ? execErr.message : String(execErr)}`;
@@ -381,7 +545,7 @@ export async function resumeAIAgent(input, channel, params, options) {
381
545
  await continueAfterToolResult(run, agent, packageName, resolvedName, storage, memoryConfig, agentRunner, channel, params, aiRunState, options);
382
546
  }
383
547
  async function continueAfterToolResult(run, agent, packageName, resolvedName, storage, memoryConfig, agentRunner, channel, params, aiRunState, options) {
384
- const { singletonServices } = params;
548
+ const singletonServices = getSingletonServices();
385
549
  const agentsMeta = pikkuState(packageName, 'agent', 'agentsMeta');
386
550
  const meta = agentsMeta[resolvedName];
387
551
  const workingMemorySchemaName = meta?.workingMemorySchema ?? null;
@@ -438,59 +602,54 @@ async function continueAfterToolResult(run, agent, packageName, resolvedName, st
438
602
  : channel;
439
603
  const persistingChannel = createPersistingChannel(wrappedChannel, storage, run.threadId);
440
604
  const streamContext = { channel, options };
441
- const resumeTools = buildToolDefs(singletonServices, params, new Map(), run.resourceId, resolvedName, packageName, streamContext).tools;
605
+ const resumeTools = buildToolDefs(params, new Map(), run.resourceId, resolvedName, packageName, streamContext, aiMiddlewares).tools;
442
606
  const resolved = resolveModelConfig(resolvedName, agent);
607
+ const maxSteps = resolved.maxSteps ?? 10;
443
608
  const runnerParams = {
444
609
  model: resolved.model,
445
610
  temperature: resolved.temperature,
446
611
  instructions: modifiedInstructions,
447
612
  messages: modifiedMessages,
448
613
  tools: resumeTools,
449
- maxSteps: resolved.maxSteps ?? 10,
614
+ maxSteps: 1,
450
615
  toolChoice: (agent.toolChoice ?? 'auto'),
451
616
  outputSchema: meta?.outputSchema
452
617
  ? pikkuState(packageName, 'misc', 'schemas').get(meta.outputSchema)
453
618
  : undefined,
454
619
  };
455
620
  try {
456
- await agentRunner.stream(runnerParams, persistingChannel);
621
+ const loopResult = await runStreamStepLoop({
622
+ agent,
623
+ runnerParams,
624
+ maxSteps,
625
+ agentRunner,
626
+ persistingChannel,
627
+ channel,
628
+ runId: run.runId,
629
+ aiMiddlewares,
630
+ });
631
+ if (loopResult.outcome === 'approval') {
632
+ await handleApproval(loopResult.approval, run.runId, channel, aiRunState, persistingChannel);
633
+ return;
634
+ }
457
635
  await postStreamCleanup(persistingChannel, aiMiddlewares, singletonServices, storage, memoryConfig, run.threadId, workingMemorySchemaName, runnerParams.messages, aiRunState, run.runId);
636
+ channel.send({ type: 'done' });
637
+ channel.close();
458
638
  }
459
639
  catch (err) {
460
- if (err instanceof ToolApprovalRequired) {
461
- await persistingChannel.flush();
462
- const pendingApproval = err.agentRunId
463
- ? {
464
- type: 'agent-call',
465
- toolCallId: err.toolCallId,
466
- agentName: err.toolName,
467
- agentRunId: err.agentRunId,
468
- displayToolName: err.displayToolName ?? err.toolName,
469
- displayArgs: err.displayArgs ?? err.args,
640
+ for (const mw of aiMiddlewares) {
641
+ if (mw.onError) {
642
+ try {
643
+ await mw.onError(singletonServices, {
644
+ error: err instanceof Error ? err : new Error(String(err)),
645
+ stepNumber: -1,
646
+ messages: runnerParams.messages,
647
+ });
470
648
  }
471
- : {
472
- type: 'tool-call',
473
- toolCallId: err.toolCallId,
474
- toolName: err.toolName,
475
- args: err.args,
476
- };
477
- await aiRunState.updateRun(run.runId, {
478
- status: 'suspended',
479
- suspendReason: 'approval',
480
- pendingApprovals: [pendingApproval],
481
- });
482
- const approvalEvent = {
483
- type: 'approval-request',
484
- toolCallId: err.toolCallId,
485
- toolName: err.displayToolName ?? err.toolName,
486
- args: err.displayArgs ?? err.args,
487
- reason: err.reason,
488
- runId: run.runId,
489
- };
490
- channel.send(approvalEvent);
491
- channel.send({ type: 'done' });
492
- channel.close();
493
- return;
649
+ catch {
650
+ // onError hooks must not affect error flow
651
+ }
652
+ }
494
653
  }
495
654
  await aiRunState.updateRun(run.runId, { status: 'failed' });
496
655
  channel.send({