@pikku/core 0.12.1 → 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 (165) hide show
  1. package/CHANGELOG.md +34 -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/middleware-runner.js +1 -0
  12. package/dist/permissions.js +1 -0
  13. package/dist/pikku-state.js +7 -1
  14. package/dist/schema.js +1 -1
  15. package/dist/services/gateway-service.d.ts +19 -0
  16. package/dist/services/gateway-service.js +1 -0
  17. package/dist/services/in-memory-workflow-service.d.ts +8 -2
  18. package/dist/services/in-memory-workflow-service.js +28 -1
  19. package/dist/services/index.d.ts +2 -0
  20. package/dist/services/index.js +1 -0
  21. package/dist/services/local-gateway-service.d.ts +22 -0
  22. package/dist/services/local-gateway-service.js +51 -0
  23. package/dist/services/workflow-service.d.ts +8 -2
  24. package/dist/testing/index.d.ts +2 -0
  25. package/dist/testing/index.js +1 -0
  26. package/dist/testing/service-tests.d.ts +31 -0
  27. package/dist/testing/service-tests.js +598 -0
  28. package/dist/types/core.types.d.ts +12 -2
  29. package/dist/types/state.types.d.ts +9 -2
  30. package/dist/utils/hash.d.ts +2 -0
  31. package/dist/utils/hash.js +23 -0
  32. package/dist/utils.js +1 -1
  33. package/dist/wirings/ai-agent/agent-dynamic-workflow.d.ts +6 -0
  34. package/dist/wirings/ai-agent/agent-dynamic-workflow.js +348 -0
  35. package/dist/wirings/ai-agent/ai-agent-memory.d.ts +1 -1
  36. package/dist/wirings/ai-agent/ai-agent-memory.js +19 -7
  37. package/dist/wirings/ai-agent/ai-agent-prepare.d.ts +10 -7
  38. package/dist/wirings/ai-agent/ai-agent-prepare.js +141 -48
  39. package/dist/wirings/ai-agent/ai-agent-runner.d.ts +4 -0
  40. package/dist/wirings/ai-agent/ai-agent-runner.js +381 -27
  41. package/dist/wirings/ai-agent/ai-agent-stream.d.ts +7 -2
  42. package/dist/wirings/ai-agent/ai-agent-stream.js +126 -48
  43. package/dist/wirings/ai-agent/ai-agent.types.d.ts +57 -3
  44. package/dist/wirings/ai-agent/index.d.ts +2 -3
  45. package/dist/wirings/ai-agent/index.js +1 -2
  46. package/dist/wirings/channel/channel-handler.d.ts +5 -2
  47. package/dist/wirings/channel/channel-handler.js +16 -2
  48. package/dist/wirings/channel/channel-middleware-runner.js +1 -0
  49. package/dist/wirings/channel/channel.types.d.ts +7 -7
  50. package/dist/wirings/channel/index.d.ts +1 -1
  51. package/dist/wirings/channel/local/local-channel-handler.d.ts +8 -1
  52. package/dist/wirings/channel/local/local-channel-handler.js +22 -3
  53. package/dist/wirings/channel/local/local-channel-runner.js +28 -5
  54. package/dist/wirings/channel/pikku-abstract-channel-handler.d.ts +2 -1
  55. package/dist/wirings/channel/pikku-abstract-channel-handler.js +1 -0
  56. package/dist/wirings/channel/serverless/serverless-channel-runner.js +5 -2
  57. package/dist/wirings/cli/cli-runner.js +13 -2
  58. package/dist/wirings/cli/cli.types.d.ts +1 -0
  59. package/dist/wirings/gateway/gateway-runner.d.ts +24 -0
  60. package/dist/wirings/gateway/gateway-runner.js +325 -0
  61. package/dist/wirings/gateway/gateway.types.d.ts +127 -0
  62. package/dist/wirings/gateway/gateway.types.js +1 -0
  63. package/dist/wirings/gateway/index.d.ts +2 -0
  64. package/dist/wirings/gateway/index.js +1 -0
  65. package/dist/wirings/http/http-routes.js +0 -1
  66. package/dist/wirings/http/http-runner.d.ts +1 -1
  67. package/dist/wirings/http/http-runner.js +28 -3
  68. package/dist/wirings/http/http.types.d.ts +3 -11
  69. package/dist/wirings/http/index.d.ts +2 -1
  70. package/dist/wirings/http/index.js +1 -0
  71. package/dist/wirings/http/pikku-fetch-http-request.d.ts +1 -0
  72. package/dist/wirings/http/pikku-fetch-http-request.js +3 -0
  73. package/dist/wirings/http/pikku-fetch-http-response.d.ts +1 -0
  74. package/dist/wirings/http/pikku-fetch-http-response.js +4 -4
  75. package/dist/wirings/http/web-request.d.ts +12 -0
  76. package/dist/wirings/http/web-request.js +85 -0
  77. package/dist/wirings/mcp/mcp-endpoint-registry.js +1 -1
  78. package/dist/wirings/mcp/mcp-runner.js +13 -1
  79. package/dist/wirings/rpc/rpc-runner.d.ts +30 -6
  80. package/dist/wirings/rpc/rpc-runner.js +36 -14
  81. package/dist/wirings/rpc/rpc-types.d.ts +6 -1
  82. package/dist/wirings/rpc/wire-addon.d.ts +1 -0
  83. package/dist/wirings/rpc/wire-addon.js +1 -1
  84. package/dist/wirings/scheduler/scheduler-runner.js +1 -0
  85. package/dist/wirings/workflow/graph/graph-validation.d.ts +3 -0
  86. package/dist/wirings/workflow/graph/graph-validation.js +175 -0
  87. package/dist/wirings/workflow/graph/index.d.ts +1 -0
  88. package/dist/wirings/workflow/graph/index.js +1 -0
  89. package/dist/wirings/workflow/index.d.ts +1 -1
  90. package/dist/wirings/workflow/pikku-workflow-service.d.ts +8 -2
  91. package/dist/wirings/workflow/pikku-workflow-service.js +1 -2
  92. package/dist/wirings/workflow/workflow.types.d.ts +10 -1
  93. package/package.json +6 -3
  94. package/src/crypto-utils.test.ts +99 -1
  95. package/src/crypto-utils.ts +114 -0
  96. package/src/function/function-runner.ts +5 -5
  97. package/src/function/functions.types.ts +38 -1
  98. package/src/handle-error.test.ts +65 -2
  99. package/src/handle-error.ts +8 -4
  100. package/src/index.ts +14 -0
  101. package/src/middleware-runner.ts +1 -0
  102. package/src/permissions.ts +1 -0
  103. package/src/pikku-state.ts +8 -1
  104. package/src/schema.ts +1 -1
  105. package/src/services/gateway-service.ts +20 -0
  106. package/src/services/in-memory-workflow-service.ts +45 -2
  107. package/src/services/index.ts +2 -0
  108. package/src/services/local-gateway-service.ts +62 -0
  109. package/src/services/workflow-service.ts +12 -1
  110. package/src/testing/index.ts +2 -0
  111. package/src/testing/service-tests.ts +873 -0
  112. package/src/types/core.types.ts +12 -1
  113. package/src/types/state.types.ts +12 -2
  114. package/src/utils/hash.test.ts +68 -0
  115. package/src/utils/hash.ts +26 -0
  116. package/src/utils.ts +1 -1
  117. package/src/wirings/ai-agent/agent-dynamic-workflow.ts +469 -0
  118. package/src/wirings/ai-agent/ai-agent-memory.ts +17 -7
  119. package/src/wirings/ai-agent/ai-agent-prepare.ts +194 -62
  120. package/src/wirings/ai-agent/ai-agent-runner.test.ts +3 -1
  121. package/src/wirings/ai-agent/ai-agent-runner.ts +553 -31
  122. package/src/wirings/ai-agent/ai-agent-stream.test.ts +209 -2
  123. package/src/wirings/ai-agent/ai-agent-stream.ts +195 -91
  124. package/src/wirings/ai-agent/ai-agent.types.ts +57 -3
  125. package/src/wirings/ai-agent/index.ts +3 -5
  126. package/src/wirings/channel/channel-handler.ts +41 -4
  127. package/src/wirings/channel/channel-middleware-runner.ts +1 -0
  128. package/src/wirings/channel/channel.types.ts +12 -7
  129. package/src/wirings/channel/index.ts +1 -0
  130. package/src/wirings/channel/local/local-channel-handler.ts +31 -3
  131. package/src/wirings/channel/local/local-channel-runner.ts +32 -8
  132. package/src/wirings/channel/pikku-abstract-channel-handler.test.ts +2 -0
  133. package/src/wirings/channel/pikku-abstract-channel-handler.ts +8 -1
  134. package/src/wirings/channel/serverless/serverless-channel-runner.ts +5 -2
  135. package/src/wirings/cli/cli-runner.ts +15 -2
  136. package/src/wirings/cli/cli.types.ts +1 -0
  137. package/src/wirings/gateway/gateway-runner.test.ts +474 -0
  138. package/src/wirings/gateway/gateway-runner.ts +411 -0
  139. package/src/wirings/gateway/gateway.types.ts +149 -0
  140. package/src/wirings/gateway/index.ts +13 -0
  141. package/src/wirings/http/http-routes.ts +0 -1
  142. package/src/wirings/http/http-runner.ts +38 -10
  143. package/src/wirings/http/http.types.ts +3 -12
  144. package/src/wirings/http/index.ts +3 -0
  145. package/src/wirings/http/pikku-fetch-http-request.ts +4 -0
  146. package/src/wirings/http/pikku-fetch-http-response.ts +4 -4
  147. package/src/wirings/http/web-request.test.ts +236 -0
  148. package/src/wirings/http/web-request.ts +104 -0
  149. package/src/wirings/mcp/mcp-endpoint-registry.ts +1 -1
  150. package/src/wirings/mcp/mcp-runner.ts +30 -12
  151. package/src/wirings/rpc/rpc-runner.ts +45 -24
  152. package/src/wirings/rpc/rpc-types.ts +6 -1
  153. package/src/wirings/rpc/wire-addon.ts +2 -1
  154. package/src/wirings/scheduler/scheduler-runner.ts +1 -0
  155. package/src/wirings/workflow/graph/graph-validation.test.ts +170 -0
  156. package/src/wirings/workflow/graph/graph-validation.ts +237 -0
  157. package/src/wirings/workflow/graph/index.ts +5 -0
  158. package/src/wirings/workflow/index.ts +1 -0
  159. package/src/wirings/workflow/pikku-workflow-service.ts +14 -3
  160. package/src/wirings/workflow/workflow.types.ts +9 -1
  161. package/tsconfig.tsbuildinfo +1 -1
  162. package/dist/wirings/ai-agent/ai-agent-assistant-ui.d.ts +0 -5
  163. package/dist/wirings/ai-agent/ai-agent-assistant-ui.js +0 -146
  164. package/src/wirings/ai-agent/ai-agent-assistant-ui.test.ts +0 -363
  165. package/src/wirings/ai-agent/ai-agent-assistant-ui.ts +0 -214
@@ -39,10 +39,6 @@ import {
39
39
  type StreamAIAgentOptions,
40
40
  type StreamContext,
41
41
  } from './ai-agent-prepare.js'
42
- import {
43
- createAssistantUIChannel,
44
- parseAssistantUIInput,
45
- } from './ai-agent-assistant-ui.js'
46
42
  import { resolveModelConfig } from './ai-agent-model-config.js'
47
43
  import type { AIRunStateService } from '../../services/ai-run-state-service.js'
48
44
  import type { AIAgentRunnerService } from '../../services/ai-agent-runner-service.js'
@@ -114,6 +110,7 @@ function createPersistingChannel(
114
110
  },
115
111
  flush: flushStep,
116
112
  close: () => parent.close(),
113
+ sendBinary: (data) => parent.sendBinary(data),
117
114
  send: (event: AIStreamEvent) => {
118
115
  if (storage) {
119
116
  switch (event.type) {
@@ -237,7 +234,7 @@ type StepLoopParams = {
237
234
 
238
235
  type StepLoopResult =
239
236
  | { outcome: 'done' }
240
- | { outcome: 'approval'; approval: ToolApprovalRequired }
237
+ | { outcome: 'approval'; approvals: ToolApprovalRequired[] }
241
238
 
242
239
  async function runStreamStepLoop(
243
240
  params: StepLoopParams
@@ -290,13 +287,26 @@ async function runStreamStepLoop(
290
287
 
291
288
  if (stepResult.toolCalls.length === 0) break
292
289
 
293
- const approvalNeeded = checkForApproval(
290
+ const approvalsNeeded = checkForApprovals(
294
291
  stepResult,
295
292
  runnerParams.tools,
296
293
  runId
297
294
  )
298
- if (approvalNeeded) {
299
- return { outcome: 'approval', approval: approvalNeeded }
295
+ if (approvalsNeeded.length > 0) {
296
+ // For each approval, call approvalDescriptionFn if available
297
+ for (const approval of approvalsNeeded) {
298
+ const toolDef = runnerParams.tools.find(
299
+ (t) => t.name === approval.toolName
300
+ )
301
+ if (toolDef?.approvalDescriptionFn && !approval.reason) {
302
+ try {
303
+ approval.reason = await toolDef.approvalDescriptionFn(approval.args)
304
+ } catch {
305
+ // If description generation fails, continue without it
306
+ }
307
+ }
308
+ }
309
+ return { outcome: 'approval', approvals: approvalsNeeded }
300
310
  }
301
311
 
302
312
  appendStepMessages(runnerParams, stepResult)
@@ -305,16 +315,20 @@ async function runStreamStepLoop(
305
315
  return { outcome: 'done' }
306
316
  }
307
317
 
308
- function checkForApproval(
318
+ export function checkForApprovals(
309
319
  stepResult: AIAgentStepResult,
310
320
  tools: AIAgentRunnerParams['tools'],
311
321
  runId: string
312
- ): ToolApprovalRequired | null {
322
+ ): ToolApprovalRequired[] {
323
+ const approvals: ToolApprovalRequired[] = []
313
324
  for (const tc of stepResult.toolCalls) {
314
325
  const toolDef = tools.find((t) => t.name === tc.toolName)
315
326
 
316
327
  if (toolDef?.needsApproval) {
317
- return new ToolApprovalRequired(tc.toolCallId, tc.toolName, tc.args)
328
+ approvals.push(
329
+ new ToolApprovalRequired(tc.toolCallId, tc.toolName, tc.args)
330
+ )
331
+ continue
318
332
  }
319
333
 
320
334
  const tr = stepResult.toolResults.find(
@@ -332,22 +346,46 @@ function checkForApproval(
332
346
  displayToolName?: string
333
347
  displayArgs?: unknown
334
348
  agentRunId?: string
349
+ subApprovals?: Array<{
350
+ toolCallId: string
351
+ toolName: string
352
+ args: unknown
353
+ runId: string
354
+ }>
355
+ }
356
+ if (r.subApprovals?.length) {
357
+ for (const sub of r.subApprovals) {
358
+ approvals.push(
359
+ new ToolApprovalRequired(
360
+ sub.toolCallId,
361
+ r.toolName,
362
+ r.args,
363
+ undefined,
364
+ sub.toolName,
365
+ sub.args,
366
+ r.agentRunId
367
+ )
368
+ )
369
+ }
370
+ } else {
371
+ approvals.push(
372
+ new ToolApprovalRequired(
373
+ tc.toolCallId,
374
+ r.toolName,
375
+ r.args,
376
+ r.reason,
377
+ r.displayToolName,
378
+ r.displayArgs,
379
+ r.agentRunId
380
+ )
381
+ )
335
382
  }
336
- return new ToolApprovalRequired(
337
- tc.toolCallId,
338
- r.toolName,
339
- r.args,
340
- r.reason,
341
- r.displayToolName,
342
- r.displayArgs,
343
- r.agentRunId
344
- )
345
383
  }
346
384
  }
347
- return null
385
+ return approvals
348
386
  }
349
387
 
350
- function appendStepMessages(
388
+ export function appendStepMessages(
351
389
  runnerParams: AIAgentRunnerParams,
352
390
  stepResult: AIAgentStepResult
353
391
  ): void {
@@ -383,8 +421,8 @@ function appendStepMessages(
383
421
  }
384
422
  }
385
423
 
386
- function handleApproval(
387
- err: ToolApprovalRequired,
424
+ function handleApprovals(
425
+ approvals: ToolApprovalRequired[],
388
426
  runId: string,
389
427
  channel: AIStreamChannel,
390
428
  aiRunState: AIRunStateService,
@@ -393,37 +431,41 @@ function handleApproval(
393
431
  return (async () => {
394
432
  await persistingChannel.flush()
395
433
 
396
- const pendingApproval = err.agentRunId
397
- ? {
398
- type: 'agent-call' as const,
399
- toolCallId: err.toolCallId,
400
- agentName: err.toolName,
401
- agentRunId: err.agentRunId,
402
- displayToolName: err.displayToolName ?? err.toolName,
403
- displayArgs: err.displayArgs ?? err.args,
404
- }
405
- : {
406
- type: 'tool-call' as const,
407
- toolCallId: err.toolCallId,
408
- toolName: err.toolName,
409
- args: err.args,
410
- }
434
+ const pendingApprovals = approvals.map((err) =>
435
+ err.agentRunId
436
+ ? {
437
+ type: 'agent-call' as const,
438
+ toolCallId: err.toolCallId,
439
+ agentName: err.toolName,
440
+ agentRunId: err.agentRunId,
441
+ displayToolName: err.displayToolName ?? err.toolName,
442
+ displayArgs: err.displayArgs ?? err.args,
443
+ }
444
+ : {
445
+ type: 'tool-call' as const,
446
+ toolCallId: err.toolCallId,
447
+ toolName: err.toolName,
448
+ args: err.args,
449
+ }
450
+ )
411
451
 
412
452
  await aiRunState.updateRun(runId, {
413
453
  status: 'suspended',
414
454
  suspendReason: 'approval',
415
- pendingApprovals: [pendingApproval],
455
+ pendingApprovals,
416
456
  })
417
457
 
418
- const approvalEvent = {
419
- type: 'approval-request' as const,
420
- toolCallId: err.toolCallId,
421
- toolName: err.displayToolName ?? err.toolName,
422
- args: err.displayArgs ?? err.args,
423
- reason: err.reason,
424
- runId,
458
+ for (const err of approvals) {
459
+ const approvalEvent = {
460
+ type: 'approval-request' as const,
461
+ toolCallId: err.toolCallId,
462
+ toolName: err.displayToolName ?? err.toolName,
463
+ args: err.displayArgs ?? err.args,
464
+ reason: err.reason,
465
+ runId,
466
+ }
467
+ channel.send(approvalEvent as any)
425
468
  }
426
- channel.send(approvalEvent as any)
427
469
  channel.send({ type: 'done' })
428
470
  channel.close()
429
471
  })()
@@ -431,34 +473,24 @@ function handleApproval(
431
473
 
432
474
  export async function streamAIAgent(
433
475
  agentName: string,
434
- input:
435
- | { message: string; threadId: string; resourceId: string }
436
- | Record<string, unknown>,
476
+ input: {
477
+ message: string
478
+ threadId: string
479
+ resourceId: string
480
+ model?: string
481
+ temperature?: number
482
+ },
437
483
  channel: AIStreamChannel,
438
484
  params: RunAIAgentParams,
439
485
  agentSessionMap?: Map<string, string>,
440
486
  options?: StreamAIAgentOptions
441
- ): Promise<void> {
487
+ ): Promise<string> {
442
488
  const sessionMap = agentSessionMap ?? new Map<string, string>()
443
489
 
444
- const { agent: resolvedAgentForProtocol } = resolveAgent(agentName)
445
- const inputRecord = input as Record<string, unknown>
446
- const useUIMessageStream =
447
- resolvedAgentForProtocol.protocol === 'ui-message-stream' ||
448
- Array.isArray(inputRecord.messages)
449
- let normalizedInput: { message: string; threadId: string; resourceId: string }
450
- if (useUIMessageStream) {
451
- normalizedInput = parseAssistantUIInput(inputRecord)
452
- channel = createAssistantUIChannel(channel)
453
- } else {
454
- normalizedInput = input as {
455
- message: string
456
- threadId: string
457
- resourceId: string
458
- }
459
- }
490
+ const normalizedInput = input
460
491
 
461
492
  const streamContext: StreamContext = { channel, options }
493
+ // delegateState is attached after prepareAgentRun resolves the agent config
462
494
 
463
495
  const {
464
496
  agent,
@@ -501,7 +533,7 @@ export async function streamAIAgent(
501
533
  })
502
534
  channel.send({ type: 'suspended', reason: 'rpc-missing', missingRpcs })
503
535
  channel.send({ type: 'done' })
504
- return
536
+ return ''
505
537
  }
506
538
 
507
539
  const aiMiddlewares: PikkuAIMiddlewareHooks[] = agent.aiMiddleware ?? []
@@ -547,7 +579,12 @@ export async function streamAIAgent(
547
579
  allEvents,
548
580
  state,
549
581
  })
550
- if (result != null) await next(result)
582
+ if (result == null) return
583
+ if (Array.isArray(result)) {
584
+ for (const r of result) await next(r)
585
+ } else {
586
+ await next(result)
587
+ }
551
588
  }
552
589
  })
553
590
 
@@ -574,8 +611,32 @@ export async function streamAIAgent(
574
611
  ).channel as AIStreamChannel)
575
612
  : channel
576
613
 
614
+ // In delegate mode (default), suppress parent's text from reaching the client
615
+ // AFTER a sub-agent has been called. If the parent responds directly (no delegation),
616
+ // its text goes through normally. Sub-agent text bypasses this path entirely
617
+ // (goes through subChannel → channel directly).
618
+ const isDelegateMode = agent.agentMode !== 'supervise' && meta.agents?.length
619
+ const delegateState = { delegated: false }
620
+ if (isDelegateMode) {
621
+ streamContext.delegateState = delegateState
622
+ }
623
+ const outputChannel = isDelegateMode
624
+ ? {
625
+ ...wrappedChannel,
626
+ send: (event: AIStreamEvent) => {
627
+ if (
628
+ delegateState.delegated &&
629
+ (event.type === 'text-delta' || event.type === 'reasoning-delta')
630
+ )
631
+ return
632
+ wrappedChannel.send(event)
633
+ },
634
+ delegateState,
635
+ }
636
+ : wrappedChannel
637
+
577
638
  const persistingChannel = createPersistingChannel(
578
- wrappedChannel,
639
+ outputChannel,
579
640
  storage,
580
641
  threadId
581
642
  )
@@ -593,14 +654,14 @@ export async function streamAIAgent(
593
654
  })
594
655
 
595
656
  if (loopResult.outcome === 'approval') {
596
- await handleApproval(
597
- loopResult.approval,
657
+ await handleApprovals(
658
+ loopResult.approvals,
598
659
  runId,
599
660
  channel,
600
661
  aiRunState,
601
662
  persistingChannel
602
663
  )
603
- return
664
+ return persistingChannel.fullText
604
665
  }
605
666
 
606
667
  await postStreamCleanup(
@@ -618,6 +679,7 @@ export async function streamAIAgent(
618
679
 
619
680
  channel.send({ type: 'done' })
620
681
  channel.close()
682
+ return persistingChannel.fullText
621
683
  } catch (err) {
622
684
  for (const mw of aiMiddlewares) {
623
685
  if (mw.onError) {
@@ -632,13 +694,17 @@ export async function streamAIAgent(
632
694
  }
633
695
  }
634
696
  }
635
- await aiRunState.updateRun(runId, { status: 'failed' })
697
+ await aiRunState.updateRun(runId, {
698
+ status: 'failed',
699
+ errorMessage: err instanceof Error ? err.message : String(err),
700
+ })
636
701
  channel.send({
637
702
  type: 'error',
638
703
  message: err instanceof Error ? err.message : String(err),
639
704
  })
640
705
  channel.send({ type: 'done' })
641
706
  channel.close()
707
+ return persistingChannel.fullText
642
708
  }
643
709
  }
644
710
 
@@ -690,7 +756,8 @@ export async function resumeAIAgent(
690
756
  await aiRunState.updateRun(pending.agentRunId, { status: 'failed' })
691
757
  }
692
758
 
693
- const denialResult = 'Tool call was not approved by the user'
759
+ const denialResult =
760
+ 'The user explicitly declined this action. Inform them that it was declined and do not retry.'
694
761
 
695
762
  if (storage) {
696
763
  await storage.saveMessages(run.threadId, [
@@ -712,6 +779,17 @@ export async function resumeAIAgent(
712
779
  ])
713
780
  }
714
781
 
782
+ // Check remaining pending approvals
783
+ const updatedRun = await aiRunState.getRun(run.runId)
784
+ const remaining = updatedRun?.pendingApprovals ?? []
785
+
786
+ if (remaining.length > 0) {
787
+ // Still waiting for more approvals - don't continue step loop
788
+ channel.send({ type: 'done' })
789
+ channel.close()
790
+ return
791
+ }
792
+
715
793
  await aiRunState.updateRun(run.runId, { status: 'running' })
716
794
 
717
795
  await continueAfterToolResult(
@@ -735,7 +813,9 @@ export async function resumeAIAgent(
735
813
  if (!subRun) {
736
814
  throw new Error(`Sub-agent run not found: ${pending.agentRunId}`)
737
815
  }
738
- const subPending = subRun.pendingApprovals?.[0]
816
+ const subPending =
817
+ subRun.pendingApprovals?.find((p) => p.toolCallId === input.toolCallId) ??
818
+ subRun.pendingApprovals?.[0]
739
819
  if (!subPending) {
740
820
  throw new Error(
741
821
  `No pending approval on sub-agent run ${pending.agentRunId}`
@@ -791,7 +871,7 @@ export async function resumeAIAgent(
791
871
  }
792
872
  const aiMiddlewaresForResume: PikkuAIMiddlewareHooks[] =
793
873
  agent.aiMiddleware ?? []
794
- const { tools } = buildToolDefs(
874
+ const { tools } = await buildToolDefs(
795
875
  params,
796
876
  new Map<string, string>(),
797
877
  run.resourceId,
@@ -812,10 +892,12 @@ export async function resumeAIAgent(
812
892
  typeof pending.args === 'string' ? JSON.parse(pending.args) : pending.args
813
893
 
814
894
  let toolResult: unknown
895
+ let isError = false
815
896
  try {
816
897
  toolResult = await matchingTool.execute(toolArgs)
817
898
  } catch (execErr) {
818
899
  toolResult = `Error: ${execErr instanceof Error ? execErr.message : String(execErr)}`
900
+ isError = true
819
901
  }
820
902
 
821
903
  const resultStr =
@@ -842,9 +924,21 @@ export async function resumeAIAgent(
842
924
  toolCallId: input.toolCallId,
843
925
  toolName: pending.toolName,
844
926
  result: toolResult,
927
+ ...(isError ? { isError: true } : {}),
845
928
  })
846
929
  }
847
930
 
931
+ // Check remaining pending approvals after processing this one
932
+ const updatedRun = await aiRunState.getRun(run.runId)
933
+ const remaining = updatedRun?.pendingApprovals ?? []
934
+
935
+ if (remaining.length > 0) {
936
+ // Still waiting for more approvals - don't continue step loop
937
+ channel.send({ type: 'done' })
938
+ channel.close()
939
+ return
940
+ }
941
+
848
942
  await aiRunState.updateRun(run.runId, { status: 'running' })
849
943
 
850
944
  await continueAfterToolResult(
@@ -900,7 +994,7 @@ async function continueAfterToolResult(
900
994
  const allMessages = [...contextMessages, ...messages]
901
995
  const trimmedMessages = trimMessages(allMessages)
902
996
 
903
- const instructions = buildInstructions(resolvedName, packageName)
997
+ const instructions = await buildInstructions(resolvedName, packageName)
904
998
 
905
999
  const aiMiddlewares: PikkuAIMiddlewareHooks[] = agent.aiMiddleware ?? []
906
1000
  let modifiedMessages = trimmedMessages
@@ -928,7 +1022,12 @@ async function continueAfterToolResult(
928
1022
  allEvents,
929
1023
  state,
930
1024
  })
931
- if (result != null) await next(result)
1025
+ if (result == null) return
1026
+ if (Array.isArray(result)) {
1027
+ for (const r of result) await next(r)
1028
+ } else {
1029
+ await next(result)
1030
+ }
932
1031
  }
933
1032
  })
934
1033
 
@@ -960,14 +1059,16 @@ async function continueAfterToolResult(
960
1059
  )
961
1060
 
962
1061
  const streamContext: StreamContext = { channel, options }
963
- const resumeTools = buildToolDefs(
964
- params,
965
- new Map<string, string>(),
966
- run.resourceId,
967
- resolvedName,
968
- packageName,
969
- streamContext,
970
- aiMiddlewares
1062
+ const resumeTools = (
1063
+ await buildToolDefs(
1064
+ params,
1065
+ new Map<string, string>(),
1066
+ run.resourceId,
1067
+ resolvedName,
1068
+ packageName,
1069
+ streamContext,
1070
+ aiMiddlewares
1071
+ )
971
1072
  ).tools
972
1073
 
973
1074
  const resolved = resolveModelConfig(resolvedName, agent)
@@ -999,8 +1100,8 @@ async function continueAfterToolResult(
999
1100
  })
1000
1101
 
1001
1102
  if (loopResult.outcome === 'approval') {
1002
- await handleApproval(
1003
- loopResult.approval,
1103
+ await handleApprovals(
1104
+ loopResult.approvals,
1004
1105
  run.runId,
1005
1106
  channel,
1006
1107
  aiRunState,
@@ -1038,7 +1139,10 @@ async function continueAfterToolResult(
1038
1139
  }
1039
1140
  }
1040
1141
  }
1041
- await aiRunState.updateRun(run.runId, { status: 'failed' })
1142
+ await aiRunState.updateRun(run.runId, {
1143
+ status: 'failed',
1144
+ errorMessage: err instanceof Error ? err.message : String(err),
1145
+ })
1042
1146
  channel.send({
1043
1147
  type: 'error',
1044
1148
  message: err instanceof Error ? err.message : String(err),
@@ -19,6 +19,17 @@ export interface AIThread {
19
19
  updatedAt: Date
20
20
  }
21
21
 
22
+ export type AIContentPart =
23
+ | { type: 'text'; text: string }
24
+ | { type: 'image'; data?: string; url?: string; mediaType?: string }
25
+ | {
26
+ type: 'file'
27
+ data?: string
28
+ url?: string
29
+ mediaType: string
30
+ filename?: string
31
+ }
32
+
22
33
  export interface AIToolCall {
23
34
  id: string
24
35
  name: string
@@ -34,7 +45,7 @@ export interface AIToolResult {
34
45
  export interface AIMessage {
35
46
  id: string
36
47
  role: 'system' | 'user' | 'assistant' | 'tool'
37
- content?: string
48
+ content?: string | AIContentPart[]
38
49
  toolCalls?: AIToolCall[]
39
50
  toolResults?: AIToolResult[]
40
51
  createdAt: Date
@@ -45,10 +56,21 @@ export interface AIAgentStep {
45
56
  toolCalls?: { name: string; args: Record<string, unknown>; result: string }[]
46
57
  }
47
58
 
59
+ export interface AIAgentInputAttachment {
60
+ type: 'image' | 'file'
61
+ data?: string
62
+ url?: string
63
+ mediaType?: string
64
+ filename?: string
65
+ }
66
+
48
67
  export interface AIAgentInput {
49
68
  message: string
50
69
  threadId: string
51
70
  resourceId: string
71
+ attachments?: AIAgentInputAttachment[]
72
+ model?: string
73
+ temperature?: number
52
74
  }
53
75
 
54
76
  export interface AIAgentOutput {
@@ -58,6 +80,14 @@ export interface AIAgentOutput {
58
80
  threadId: string
59
81
  steps: AIAgentStep[]
60
82
  usage: { inputTokens: number; outputTokens: number }
83
+ status?: 'completed' | 'suspended'
84
+ pendingApprovals?: Array<{
85
+ toolCallId: string
86
+ toolName: string
87
+ args: unknown
88
+ reason?: string
89
+ runId: string
90
+ }>
61
91
  }
62
92
 
63
93
  export interface AIAgentToolDef {
@@ -66,6 +96,7 @@ export interface AIAgentToolDef {
66
96
  inputSchema: Record<string, unknown>
67
97
  execute: (input: unknown) => Promise<unknown>
68
98
  needsApproval?: boolean
99
+ approvalDescriptionFn?: (input: unknown) => Promise<string>
69
100
  }
70
101
 
71
102
  export interface PikkuAIMiddlewareHooks<
@@ -86,7 +117,11 @@ export interface PikkuAIMiddlewareHooks<
86
117
  allEvents: readonly AIStreamEvent[]
87
118
  state: State
88
119
  }
89
- ) => Promise<AIStreamEvent | null> | AIStreamEvent | null
120
+ ) =>
121
+ | Promise<AIStreamEvent | AIStreamEvent[] | null>
122
+ | AIStreamEvent
123
+ | AIStreamEvent[]
124
+ | null
90
125
 
91
126
  modifyOutput?: (
92
127
  services: Services,
@@ -165,13 +200,14 @@ export type CoreAIAgent<
165
200
  temperature?: number
166
201
  tools?: unknown[]
167
202
  agents?: unknown[]
203
+ agentMode?: 'delegate' | 'supervise'
168
204
  memory?: AIAgentMemoryConfig
169
205
  maxSteps?: number
170
206
  toolChoice?: 'auto' | 'required' | 'none'
207
+ dynamicWorkflows?: 'read' | 'always' | 'ask'
171
208
  input?: unknown
172
209
  output?: unknown
173
210
  tags?: string[]
174
- protocol?: 'ui-message-stream'
175
211
  prepareStep?: (ctx: {
176
212
  stepNumber: number
177
213
  messages: AIMessage[]
@@ -235,6 +271,21 @@ export type AIStreamEvent =
235
271
  session?: string
236
272
  }
237
273
  | { type: 'error'; message: string; agent?: string; session?: string }
274
+ | {
275
+ type: 'audio-delta'
276
+ data: string
277
+ format: string
278
+ agent?: string
279
+ session?: string
280
+ }
281
+ | { type: 'audio-done'; agent?: string; session?: string }
282
+ | {
283
+ type: 'workflow-created'
284
+ workflowName: string
285
+ graph: any
286
+ agent?: string
287
+ session?: string
288
+ }
238
289
  | {
239
290
  type: 'suspended'
240
291
  reason: 'rpc-missing'
@@ -266,6 +317,7 @@ export interface AgentRunState {
266
317
  threadId: string
267
318
  resourceId: string
268
319
  status: 'running' | 'suspended' | 'completed' | 'failed'
320
+ errorMessage?: string
269
321
  suspendReason?: 'approval' | 'rpc-missing'
270
322
  missingRpcs?: string[]
271
323
  pendingApprovals?: PendingApproval[]
@@ -280,6 +332,7 @@ export interface AgentRunRow {
280
332
  threadId: string
281
333
  resourceId: string
282
334
  status: string
335
+ errorMessage?: string
283
336
  suspendReason?: string
284
337
  missingRpcs?: string[]
285
338
  usageInputTokens: number
@@ -324,5 +377,6 @@ export type AIAgentMeta = Record<
324
377
  channelMiddleware?: MiddlewareMetadata[]
325
378
  aiMiddleware?: MiddlewareMetadata[]
326
379
  permissions?: PermissionMetadata[]
380
+ dynamicWorkflows?: 'read' | 'always' | 'ask'
327
381
  }
328
382
  >
@@ -4,12 +4,8 @@ export {
4
4
  agentResume,
5
5
  agentApprove,
6
6
  } from './ai-agent-helpers.js'
7
- export { runAIAgent } from './ai-agent-runner.js'
7
+ export { runAIAgent, resumeAIAgentSync } from './ai-agent-runner.js'
8
8
  export { streamAIAgent, resumeAIAgent } from './ai-agent-stream.js'
9
- export {
10
- createAssistantUIChannel,
11
- parseAssistantUIInput,
12
- } from './ai-agent-assistant-ui.js'
13
9
  export {
14
10
  type RunAIAgentParams,
15
11
  type StreamAIAgentOptions,
@@ -23,9 +19,11 @@ export {
23
19
  } from './ai-agent-registry.js'
24
20
  export type {
25
21
  AIAgentInput,
22
+ AIAgentInputAttachment,
26
23
  AIAgentMeta,
27
24
  AIAgentMemoryConfig,
28
25
  AIAgentStep,
26
+ AIContentPart,
29
27
  AgentRunRow,
30
28
  AgentRunService,
31
29
  AgentRunState,