@pikku/core 0.12.2 → 0.12.3

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 (131) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/dist/crypto-utils.d.ts +10 -0
  3. package/dist/crypto-utils.js +62 -0
  4. package/dist/function/function-runner.js +4 -4
  5. package/dist/function/functions.types.d.ts +26 -1
  6. package/dist/function/functions.types.js +16 -0
  7. package/dist/handle-error.d.ts +1 -1
  8. package/dist/handle-error.js +7 -3
  9. package/dist/index.d.ts +14 -2
  10. package/dist/index.js +1 -1
  11. package/dist/pikku-state.js +3 -1
  12. package/dist/schema.js +1 -1
  13. package/dist/services/in-memory-workflow-service.d.ts +8 -2
  14. package/dist/services/in-memory-workflow-service.js +28 -1
  15. package/dist/services/workflow-service.d.ts +8 -2
  16. package/dist/testing/index.d.ts +2 -0
  17. package/dist/testing/index.js +1 -0
  18. package/dist/testing/service-tests.d.ts +31 -0
  19. package/dist/testing/service-tests.js +598 -0
  20. package/dist/types/core.types.d.ts +9 -1
  21. package/dist/types/state.types.d.ts +4 -2
  22. package/dist/utils/hash.d.ts +2 -0
  23. package/dist/utils/hash.js +23 -0
  24. package/dist/wirings/ai-agent/agent-dynamic-workflow.d.ts +6 -0
  25. package/dist/wirings/ai-agent/agent-dynamic-workflow.js +348 -0
  26. package/dist/wirings/ai-agent/ai-agent-memory.d.ts +1 -1
  27. package/dist/wirings/ai-agent/ai-agent-memory.js +6 -5
  28. package/dist/wirings/ai-agent/ai-agent-prepare.d.ts +10 -7
  29. package/dist/wirings/ai-agent/ai-agent-prepare.js +127 -47
  30. package/dist/wirings/ai-agent/ai-agent-runner.d.ts +4 -0
  31. package/dist/wirings/ai-agent/ai-agent-runner.js +381 -27
  32. package/dist/wirings/ai-agent/ai-agent-stream.d.ts +7 -2
  33. package/dist/wirings/ai-agent/ai-agent-stream.js +109 -46
  34. package/dist/wirings/ai-agent/ai-agent.types.d.ts +22 -1
  35. package/dist/wirings/ai-agent/index.d.ts +1 -2
  36. package/dist/wirings/ai-agent/index.js +1 -2
  37. package/dist/wirings/channel/channel-handler.js +5 -1
  38. package/dist/wirings/channel/channel.types.d.ts +1 -7
  39. package/dist/wirings/channel/local/local-channel-handler.d.ts +1 -1
  40. package/dist/wirings/channel/local/local-channel-handler.js +5 -3
  41. package/dist/wirings/channel/local/local-channel-runner.js +14 -4
  42. package/dist/wirings/channel/serverless/serverless-channel-runner.js +4 -1
  43. package/dist/wirings/cli/cli-runner.js +10 -2
  44. package/dist/wirings/cli/cli.types.d.ts +1 -0
  45. package/dist/wirings/http/http-routes.js +0 -1
  46. package/dist/wirings/http/http-runner.d.ts +1 -1
  47. package/dist/wirings/http/http-runner.js +25 -3
  48. package/dist/wirings/http/http.types.d.ts +3 -11
  49. package/dist/wirings/http/index.d.ts +2 -1
  50. package/dist/wirings/http/index.js +1 -0
  51. package/dist/wirings/http/pikku-fetch-http-request.d.ts +1 -0
  52. package/dist/wirings/http/pikku-fetch-http-request.js +3 -0
  53. package/dist/wirings/http/pikku-fetch-http-response.d.ts +1 -0
  54. package/dist/wirings/http/pikku-fetch-http-response.js +4 -4
  55. package/dist/wirings/http/web-request.d.ts +12 -0
  56. package/dist/wirings/http/web-request.js +85 -0
  57. package/dist/wirings/mcp/mcp-endpoint-registry.js +1 -1
  58. package/dist/wirings/mcp/mcp-runner.js +13 -1
  59. package/dist/wirings/rpc/rpc-runner.d.ts +30 -6
  60. package/dist/wirings/rpc/rpc-runner.js +36 -14
  61. package/dist/wirings/rpc/rpc-types.d.ts +6 -1
  62. package/dist/wirings/rpc/wire-addon.d.ts +1 -0
  63. package/dist/wirings/rpc/wire-addon.js +1 -1
  64. package/dist/wirings/scheduler/scheduler-runner.js +1 -0
  65. package/dist/wirings/workflow/graph/graph-validation.d.ts +3 -0
  66. package/dist/wirings/workflow/graph/graph-validation.js +175 -0
  67. package/dist/wirings/workflow/graph/index.d.ts +1 -0
  68. package/dist/wirings/workflow/graph/index.js +1 -0
  69. package/dist/wirings/workflow/index.d.ts +1 -1
  70. package/dist/wirings/workflow/pikku-workflow-service.d.ts +8 -2
  71. package/dist/wirings/workflow/pikku-workflow-service.js +1 -2
  72. package/dist/wirings/workflow/workflow.types.d.ts +10 -1
  73. package/package.json +4 -2
  74. package/src/crypto-utils.test.ts +99 -1
  75. package/src/crypto-utils.ts +114 -0
  76. package/src/function/function-runner.ts +5 -5
  77. package/src/function/functions.types.ts +38 -1
  78. package/src/handle-error.test.ts +65 -2
  79. package/src/handle-error.ts +8 -4
  80. package/src/index.ts +14 -0
  81. package/src/pikku-state.ts +3 -1
  82. package/src/schema.ts +1 -1
  83. package/src/services/in-memory-workflow-service.ts +45 -2
  84. package/src/services/workflow-service.ts +12 -1
  85. package/src/testing/index.ts +2 -0
  86. package/src/testing/service-tests.ts +873 -0
  87. package/src/types/core.types.ts +9 -1
  88. package/src/types/state.types.ts +4 -2
  89. package/src/utils/hash.test.ts +68 -0
  90. package/src/utils/hash.ts +26 -0
  91. package/src/wirings/ai-agent/agent-dynamic-workflow.ts +469 -0
  92. package/src/wirings/ai-agent/ai-agent-memory.ts +7 -6
  93. package/src/wirings/ai-agent/ai-agent-prepare.ts +175 -61
  94. package/src/wirings/ai-agent/ai-agent-runner.test.ts +3 -1
  95. package/src/wirings/ai-agent/ai-agent-runner.ts +553 -31
  96. package/src/wirings/ai-agent/ai-agent-stream.test.ts +209 -2
  97. package/src/wirings/ai-agent/ai-agent-stream.ts +182 -89
  98. package/src/wirings/ai-agent/ai-agent.types.ts +23 -1
  99. package/src/wirings/ai-agent/index.ts +1 -5
  100. package/src/wirings/channel/channel-handler.ts +11 -2
  101. package/src/wirings/channel/channel.types.ts +1 -7
  102. package/src/wirings/channel/local/local-channel-handler.ts +5 -3
  103. package/src/wirings/channel/local/local-channel-runner.ts +17 -7
  104. package/src/wirings/channel/serverless/serverless-channel-runner.ts +4 -1
  105. package/src/wirings/cli/cli-runner.ts +12 -2
  106. package/src/wirings/cli/cli.types.ts +1 -0
  107. package/src/wirings/http/http-routes.ts +0 -1
  108. package/src/wirings/http/http-runner.ts +35 -10
  109. package/src/wirings/http/http.types.ts +3 -12
  110. package/src/wirings/http/index.ts +3 -0
  111. package/src/wirings/http/pikku-fetch-http-request.ts +4 -0
  112. package/src/wirings/http/pikku-fetch-http-response.ts +4 -4
  113. package/src/wirings/http/web-request.test.ts +236 -0
  114. package/src/wirings/http/web-request.ts +104 -0
  115. package/src/wirings/mcp/mcp-endpoint-registry.ts +1 -1
  116. package/src/wirings/mcp/mcp-runner.ts +30 -12
  117. package/src/wirings/rpc/rpc-runner.ts +45 -24
  118. package/src/wirings/rpc/rpc-types.ts +6 -1
  119. package/src/wirings/rpc/wire-addon.ts +2 -1
  120. package/src/wirings/scheduler/scheduler-runner.ts +1 -0
  121. package/src/wirings/workflow/graph/graph-validation.test.ts +170 -0
  122. package/src/wirings/workflow/graph/graph-validation.ts +237 -0
  123. package/src/wirings/workflow/graph/index.ts +5 -0
  124. package/src/wirings/workflow/index.ts +1 -0
  125. package/src/wirings/workflow/pikku-workflow-service.ts +14 -3
  126. package/src/wirings/workflow/workflow.types.ts +9 -1
  127. package/tsconfig.tsbuildinfo +1 -1
  128. package/dist/wirings/ai-agent/ai-agent-assistant-ui.d.ts +0 -5
  129. package/dist/wirings/ai-agent/ai-agent-assistant-ui.js +0 -168
  130. package/src/wirings/ai-agent/ai-agent-assistant-ui.test.ts +0 -363
  131. package/src/wirings/ai-agent/ai-agent-assistant-ui.ts +0 -238
@@ -11,16 +11,17 @@ import type {
11
11
  } from './ai-agent.types.js'
12
12
  import type { AIAgentRunnerParams } from '../../services/ai-agent-runner-service.js'
13
13
  import { PikkuError } from '../../errors/error-handler.js'
14
- import {
15
- pikkuState,
16
- getSingletonServices,
17
- getCreateWireServices,
18
- } from '../../pikku-state.js'
19
- import { runPikkuFunc } from '../../function/function-runner.js'
14
+ import { pikkuState, getSingletonServices } from '../../pikku-state.js'
20
15
  import { createMiddlewareSessionWireProps } from '../../services/user-session-service.js'
21
16
  import type { SessionService } from '../../services/user-session-service.js'
22
17
  import { randomUUID } from 'crypto'
23
-
18
+ import { streamAIAgent } from './ai-agent-stream.js'
19
+ import { runAIAgent } from './ai-agent-runner.js'
20
+ import {
21
+ resolveNamespace,
22
+ ContextAwareRPCService,
23
+ } from '../../wirings/rpc/rpc-runner.js'
24
+ import { buildDynamicWorkflowInstructions, buildWorkflowTools } from './agent-dynamic-workflow.js'
24
25
  import {
25
26
  resolveMemoryServices,
26
27
  loadContextMessages,
@@ -40,7 +41,7 @@ export class ToolApprovalRequired extends PikkuError {
40
41
  public readonly toolCallId: string
41
42
  public readonly toolName: string
42
43
  public readonly args: unknown
43
- public readonly reason?: string
44
+ public reason?: string
44
45
  public readonly displayToolName?: string
45
46
  public readonly displayArgs?: unknown
46
47
  public readonly agentRunId?: string
@@ -68,6 +69,7 @@ export class ToolApprovalRequired extends PikkuError {
68
69
  export type StreamContext = {
69
70
  channel: AIStreamChannel
70
71
  options?: StreamAIAgentOptions
72
+ delegateState?: { delegated: boolean }
71
73
  }
72
74
 
73
75
  export const resolveAgent = (
@@ -82,7 +84,7 @@ export const resolveAgent = (
82
84
  if (colonIndex !== -1) {
83
85
  const namespace = agentName.substring(0, colonIndex)
84
86
  const localName = agentName.substring(colonIndex + 1)
85
- const addons = pikkuState(null, 'rpc', 'addons')
87
+ const addons = pikkuState(null, 'addons', 'packages')
86
88
  const pkgConfig = addons.get(namespace)
87
89
  if (pkgConfig) {
88
90
  const extAgent = pikkuState(pkgConfig.package, 'agent', 'agents').get(
@@ -101,31 +103,38 @@ export const resolveAgent = (
101
103
  throw new Error(`AI agent not found: ${agentName}`)
102
104
  }
103
105
 
104
- export function buildInstructions(
106
+ export async function buildInstructions(
105
107
  agentName: string,
106
108
  packageName: string | null
107
- ): string {
109
+ ): Promise<string> {
108
110
  const meta = pikkuState(packageName, 'agent', 'agentsMeta')[agentName]
109
- const instructions = meta?.instructions ?? ''
110
- const baseInstructions = Array.isArray(instructions)
111
- ? instructions.join('\n')
112
- : instructions
113
-
114
- return meta?.agents?.length
115
- ? baseInstructions +
116
- '\n\nWhen calling a sub-agent, provide a short session name that describes the task. ' +
117
- 'Use the same session name to continue a previous conversation with that agent. ' +
118
- 'Use a new session name for a new independent task.'
119
- : baseInstructions
111
+ const rawInstructions = meta?.instructions ?? ''
112
+ let instructions = Array.isArray(rawInstructions)
113
+ ? rawInstructions.join('\n')
114
+ : rawInstructions
115
+
116
+ if (meta?.agents?.length) {
117
+ instructions +=
118
+ '\n\nWhen calling a sub-agent, provide a short session name that describes the task. ' +
119
+ 'Use the same session name to continue a previous conversation with that agent. ' +
120
+ 'Use a new session name for a new independent task. ' +
121
+ 'When a request involves multiple actions for the same domain, combine them into a single sub-agent call rather than making separate calls.'
122
+ }
123
+
124
+ if (meta?.dynamicWorkflows && meta.tools?.length) {
125
+ instructions += buildDynamicWorkflowInstructions(meta.tools, meta.dynamicWorkflows)
126
+ }
127
+
128
+ return instructions
120
129
  }
121
130
 
122
131
  export type ScopedChannel = AIStreamChannel & {
123
- approval: {
132
+ approvals: Array<{
124
133
  toolCallId: string
125
134
  toolName: string
126
135
  args: unknown
127
136
  runId: string
128
- } | null
137
+ }>
129
138
  }
130
139
 
131
140
  export function createScopedChannel(
@@ -133,7 +142,7 @@ export function createScopedChannel(
133
142
  agentName: string,
134
143
  session: string
135
144
  ): ScopedChannel {
136
- let capturedApproval: ScopedChannel['approval'] = null
145
+ const capturedApprovals: ScopedChannel['approvals'] = []
137
146
 
138
147
  return {
139
148
  channelId: `${parent.channelId}:${agentName}:${session}`,
@@ -141,20 +150,20 @@ export function createScopedChannel(
141
150
  get state() {
142
151
  return parent.state
143
152
  },
144
- get approval() {
145
- return capturedApproval
153
+ get approvals() {
154
+ return capturedApprovals
146
155
  },
147
156
  close: () => {},
148
157
  sendBinary: (data) => parent.sendBinary(data),
149
158
  send: (event: AIStreamEvent) => {
150
159
  if (event.type === 'done') return
151
160
  if (event.type === 'approval-request') {
152
- capturedApproval = {
161
+ capturedApprovals.push({
153
162
  toolCallId: event.toolCallId,
154
163
  toolName: event.toolName,
155
164
  args: event.args,
156
165
  runId: (event as any).runId,
157
- }
166
+ })
158
167
  return
159
168
  }
160
169
  if (
@@ -164,7 +173,8 @@ export function createScopedChannel(
164
173
  event.type === 'tool-call' ||
165
174
  event.type === 'tool-result' ||
166
175
  event.type === 'usage' ||
167
- event.type === 'error'
176
+ event.type === 'error' ||
177
+ event.type === 'workflow-created'
168
178
  ) {
169
179
  parent.send({ ...event, agent: agentName, session } as AIStreamEvent)
170
180
  } else {
@@ -174,15 +184,16 @@ export function createScopedChannel(
174
184
  }
175
185
  }
176
186
 
177
- export function buildToolDefs(
187
+ export async function buildToolDefs(
178
188
  params: RunAIAgentParams,
179
189
  agentSessionMap: Map<string, string>,
180
190
  resourceId: string,
181
191
  agentName: string,
182
192
  packageName: string | null,
183
193
  streamContext?: StreamContext,
184
- aiMiddlewares?: PikkuAIMiddlewareHooks[]
185
- ): { tools: AIAgentToolDef[]; missingRpcs: string[] } {
194
+ aiMiddlewares?: PikkuAIMiddlewareHooks[],
195
+ agentMode?: 'delegate' | 'supervise'
196
+ ): Promise<{ tools: AIAgentToolDef[]; missingRpcs: string[] }> {
186
197
  const singletonServices = getSingletonServices()
187
198
  const tools: AIAgentToolDef[] = []
188
199
  const missingRpcs: string[] = []
@@ -196,18 +207,38 @@ export function buildToolDefs(
196
207
  const metaAgents = meta.agents
197
208
 
198
209
  if (metaTools?.length) {
199
- const functionMeta = pikkuState(null, 'function', 'meta')
200
- const schemas = pikkuState(null, 'misc', 'schemas')
201
-
202
210
  for (const toolName of metaTools) {
203
- const rpcMeta = pikkuState(null, 'rpc', 'meta')
204
- const pikkuFuncId = rpcMeta[toolName]
205
- if (!pikkuFuncId) {
211
+ let fnMeta: any
212
+ let resolvedPkg: string | null = null
213
+ let schemas: Map<string, any>
214
+
215
+ const resolved = toolName.includes(':')
216
+ ? resolveNamespace(toolName)
217
+ : null
218
+
219
+ let pikkuFuncId: string | undefined
220
+
221
+ if (resolved) {
222
+ resolvedPkg = resolved.package
223
+ pikkuFuncId = resolved.function
224
+ fnMeta = pikkuState(resolvedPkg, 'function', 'meta')[pikkuFuncId]
225
+ schemas = pikkuState(resolvedPkg, 'misc', 'schemas')
226
+ } else {
227
+ const rpcMeta = pikkuState(null, 'rpc', 'meta')
228
+ pikkuFuncId = rpcMeta[toolName]
229
+ if (!pikkuFuncId) {
230
+ missingRpcs.push(toolName)
231
+ continue
232
+ }
233
+ fnMeta = pikkuState(null, 'function', 'meta')[pikkuFuncId]
234
+ schemas = pikkuState(null, 'misc', 'schemas')
235
+ }
236
+
237
+ if (!fnMeta) {
206
238
  missingRpcs.push(toolName)
207
239
  continue
208
240
  }
209
241
 
210
- const fnMeta = functionMeta[pikkuFuncId]
211
242
  const inputSchemaName = fnMeta?.inputSchemaName
212
243
  let inputSchema = inputSchemaName
213
244
  ? schemas.get(inputSchemaName)
@@ -223,24 +254,52 @@ export function buildToolDefs(
223
254
 
224
255
  const needsApproval =
225
256
  approvalPolicy === 'all' ||
226
- (approvalPolicy === 'explicit' && fnMeta?.requiresApproval)
257
+ (approvalPolicy === 'explicit' && fnMeta?.approvalRequired)
258
+
259
+ // Build approvalDescriptionFn if the function has an approvalDescription configured
260
+ let approvalDescriptionFn:
261
+ | ((input: unknown) => Promise<string>)
262
+ | undefined
263
+ if (needsApproval && pikkuFuncId) {
264
+ const funcConfig = pikkuState(resolvedPkg, 'function', 'functions').get(
265
+ pikkuFuncId
266
+ )
267
+ if (funcConfig?.approvalDescription) {
268
+ const descFn = funcConfig.approvalDescription
269
+ const capturedPkg = resolvedPkg
270
+ approvalDescriptionFn = async (input: unknown) => {
271
+ let services = singletonServices
272
+ if (capturedPkg) {
273
+ const pkgServices = pikkuState(
274
+ capturedPkg,
275
+ 'package',
276
+ 'singletonServices'
277
+ )
278
+ if (pkgServices) {
279
+ services = pkgServices
280
+ }
281
+ }
282
+ return descFn(services, input)
283
+ }
284
+ }
285
+ }
227
286
 
228
287
  tools.push({
229
- name: pikkuFuncId,
288
+ name: toolName.replaceAll(':', '__'),
230
289
  description: fnMeta?.description || fnMeta?.title || toolName,
231
290
  inputSchema,
232
291
  needsApproval: needsApproval || undefined,
292
+ approvalDescriptionFn,
233
293
  execute: async (toolInput: unknown) => {
234
294
  const wire: PikkuWire = params.sessionService
235
295
  ? { ...createMiddlewareSessionWireProps(params.sessionService) }
236
296
  : {}
237
- return runPikkuFunc('agent', `tool:${pikkuFuncId}`, pikkuFuncId, {
297
+ const rpcService = new ContextAwareRPCService(
238
298
  singletonServices,
239
- createWireServices: getCreateWireServices(),
240
- data: () => toolInput,
241
299
  wire,
242
- sessionService: params.sessionService,
243
- })
300
+ { sessionService: params.sessionService }
301
+ )
302
+ return rpcService.rpc(toolName, toolInput)
244
303
  },
245
304
  })
246
305
  }
@@ -280,12 +339,15 @@ export function buildToolDefs(
280
339
  const sessionKey = `${subAgentName}::${session}`
281
340
  let threadId = agentSessionMap.get(sessionKey)
282
341
  if (!threadId) {
283
- threadId = `${subAgentName}-${session}-${Date.now()}`
342
+ threadId = randomUUID()
284
343
  agentSessionMap.set(sessionKey, threadId)
285
344
  }
286
345
 
287
346
  if (streamContext) {
288
- const { streamAIAgent } = await import('./ai-agent-stream.js')
347
+ const isDelegate = agentMode !== 'supervise'
348
+ if (isDelegate && streamContext.delegateState) {
349
+ streamContext.delegateState.delegated = true
350
+ }
289
351
  const { channel } = streamContext
290
352
  channel.send({
291
353
  type: 'agent-call',
@@ -298,34 +360,48 @@ export function buildToolDefs(
298
360
  subAgentName,
299
361
  session
300
362
  )
301
- await streamAIAgent(
363
+ // In supervise mode, suppress sub-agent text from reaching the client.
364
+ // Approvals still flow through normally.
365
+ const effectiveChannel = isDelegate
366
+ ? subChannel
367
+ : {
368
+ ...subChannel,
369
+ send: (event: AIStreamEvent) => {
370
+ if (
371
+ event.type === 'text-delta' ||
372
+ event.type === 'reasoning-delta'
373
+ )
374
+ return
375
+ subChannel.send(event)
376
+ },
377
+ }
378
+ const resultText = await streamAIAgent(
302
379
  subAgentName,
303
380
  { message, threadId, resourceId },
304
- subChannel,
381
+ effectiveChannel,
305
382
  params,
306
383
  agentSessionMap,
307
384
  streamContext.options
308
385
  )
309
- if (subChannel.approval) {
386
+ if (subChannel.approvals.length > 0) {
310
387
  return {
311
388
  __approvalRequired: true,
312
389
  toolName: subAgentName,
313
390
  args: toolInput,
314
- displayToolName: subChannel.approval.toolName,
315
- displayArgs: subChannel.approval.args,
316
- agentRunId: subChannel.approval.runId,
391
+ agentRunId: subChannel.approvals[0].runId,
392
+ subApprovals: subChannel.approvals,
317
393
  }
318
394
  }
319
395
  channel.send({
320
396
  type: 'agent-result',
321
397
  agentName: subAgentName,
322
398
  session,
323
- result: null,
399
+ result: resultText,
324
400
  })
325
- return null
401
+ return resultText
326
402
  }
327
403
 
328
- const { runAIAgent } = await import('./ai-agent-runner.js')
404
+ // No stream context: sub-agent runs non-streaming
329
405
  const result = await runAIAgent(
330
406
  subAgentName,
331
407
  { message, threadId, resourceId },
@@ -338,6 +414,18 @@ export function buildToolDefs(
338
414
  }
339
415
  }
340
416
 
417
+ if (meta.dynamicWorkflows) {
418
+ const workflowTools = buildWorkflowTools(
419
+ agentName,
420
+ packageName,
421
+ meta.tools ?? [],
422
+ meta.dynamicWorkflows,
423
+ streamContext,
424
+ params.sessionService
425
+ )
426
+ tools.push(...workflowTools)
427
+ }
428
+
341
429
  const hasToolHooks = aiMiddlewares?.some(
342
430
  (mw) => mw.beforeToolCall || mw.afterToolCall
343
431
  )
@@ -412,6 +500,19 @@ export async function prepareAgentRun(
412
500
  throw new Error('AIAgentRunnerService not available in singletonServices')
413
501
  }
414
502
 
503
+ if (agent.dynamicWorkflows && singletonServices.workflowService) {
504
+ const persisted =
505
+ await singletonServices.workflowService.getAIGeneratedWorkflows(
506
+ resolvedName
507
+ )
508
+ const allMeta = pikkuState(null, 'workflows', 'meta')
509
+ for (const wf of persisted) {
510
+ if (!allMeta[wf.workflowName]) {
511
+ allMeta[wf.workflowName] = wf.graph
512
+ }
513
+ }
514
+ }
515
+
415
516
  const { storage } = resolveMemoryServices(agent, singletonServices)
416
517
  const memoryConfig = agent.memory
417
518
  const threadId = input.threadId
@@ -478,19 +579,32 @@ export async function prepareAgentRun(
478
579
 
479
580
  const aiMiddlewares: PikkuAIMiddlewareHooks[] = agent.aiMiddleware ?? []
480
581
 
481
- const { tools, missingRpcs } = buildToolDefs(
582
+ const { tools, missingRpcs } = await buildToolDefs(
482
583
  params,
483
584
  agentSessionMap,
484
585
  input.resourceId,
485
586
  resolvedName,
486
587
  packageName,
487
588
  streamContext,
488
- aiMiddlewares
589
+ aiMiddlewares,
590
+ agent.agentMode
489
591
  )
490
592
 
491
- const instructions = buildInstructions(resolvedName, packageName)
593
+ const instructions = await buildInstructions(resolvedName, packageName)
492
594
 
493
595
  const resolved = resolveModelConfig(resolvedName, agent)
596
+
597
+ // Per-request overrides
598
+ if (input.model) {
599
+ resolved.model = resolveModelConfig(resolvedName, {
600
+ ...agent,
601
+ model: input.model,
602
+ }).model
603
+ }
604
+ if (input.temperature !== undefined) {
605
+ resolved.temperature = input.temperature
606
+ }
607
+
494
608
  const maxSteps = resolved.maxSteps ?? 10
495
609
 
496
610
  const runnerParams: AIAgentRunnerParams = {
@@ -84,7 +84,9 @@ describe('runAIAgent', () => {
84
84
  expectedError
85
85
  )
86
86
 
87
- assert.deepEqual(updates, [{ status: 'failed' }])
87
+ assert.deepEqual(updates, [
88
+ { status: 'failed', errorMessage: 'runner failed' },
89
+ ])
88
90
  })
89
91
 
90
92
  test('loops through multiple steps and accumulates usage', async () => {