@pikku/core 0.12.19 → 0.12.21

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 (146) hide show
  1. package/CHANGELOG.md +77 -0
  2. package/dist/dev/hot-reload.js +20 -6
  3. package/dist/errors/error-handler.d.ts +1 -1
  4. package/dist/errors/error-handler.js +2 -2
  5. package/dist/function/function-runner.js +53 -3
  6. package/dist/function/functions.types.d.ts +3 -2
  7. package/dist/index.d.ts +3 -3
  8. package/dist/index.js +2 -2
  9. package/dist/middleware/index.d.ts +2 -2
  10. package/dist/middleware/index.js +2 -2
  11. package/dist/middleware/telemetry.d.ts +2 -2
  12. package/dist/middleware/telemetry.js +2 -2
  13. package/dist/middleware-runner.d.ts +15 -27
  14. package/dist/middleware-runner.js +25 -30
  15. package/dist/permissions.d.ts +15 -22
  16. package/dist/permissions.js +30 -25
  17. package/dist/pikku-request.js +1 -1
  18. package/dist/pikku-state.js +2 -3
  19. package/dist/services/ai-agent-runner-service.d.ts +1 -0
  20. package/dist/services/content-service.d.ts +66 -37
  21. package/dist/services/in-memory-queue-service.js +1 -1
  22. package/dist/services/in-memory-workflow-service.d.ts +15 -13
  23. package/dist/services/in-memory-workflow-service.js +31 -13
  24. package/dist/services/index.d.ts +1 -1
  25. package/dist/services/local-content.d.ts +10 -12
  26. package/dist/services/local-content.js +54 -38
  27. package/dist/services/user-session-service.d.ts +1 -1
  28. package/dist/testing/service-tests.js +24 -0
  29. package/dist/types/core.types.d.ts +4 -0
  30. package/dist/types/state.types.d.ts +2 -18
  31. package/dist/wirings/ai-agent/ai-agent-memory.d.ts +24 -5
  32. package/dist/wirings/ai-agent/ai-agent-memory.js +128 -23
  33. package/dist/wirings/ai-agent/ai-agent-model-config.d.ts +10 -1
  34. package/dist/wirings/ai-agent/ai-agent-model-config.js +15 -35
  35. package/dist/wirings/ai-agent/ai-agent-prepare.js +4 -1
  36. package/dist/wirings/ai-agent/ai-agent-runner.js +45 -31
  37. package/dist/wirings/ai-agent/ai-agent-stream.js +63 -34
  38. package/dist/wirings/ai-agent/ai-agent.types.d.ts +20 -0
  39. package/dist/wirings/channel/channel-handler.js +1 -1
  40. package/dist/wirings/channel/channel-store.d.ts +13 -0
  41. package/dist/wirings/channel/channel.types.d.ts +3 -0
  42. package/dist/wirings/channel/local/local-channel-runner.js +23 -5
  43. package/dist/wirings/channel/pikku-abstract-channel-handler.js +8 -0
  44. package/dist/wirings/channel/serverless/serverless-channel-runner.js +9 -0
  45. package/dist/wirings/cli/cli-runner.js +24 -5
  46. package/dist/wirings/cli/command-parser.js +19 -4
  47. package/dist/wirings/http/http-runner.js +72 -36
  48. package/dist/wirings/http/http.types.d.ts +1 -0
  49. package/dist/wirings/http/pikku-fetch-http-request.js +3 -1
  50. package/dist/wirings/http/web-request.js +32 -0
  51. package/dist/wirings/rpc/rpc-runner.d.ts +3 -2
  52. package/dist/wirings/rpc/rpc-runner.js +45 -11
  53. package/dist/wirings/workflow/graph/graph-node.d.ts +8 -0
  54. package/dist/wirings/workflow/graph/graph-node.js +4 -0
  55. package/dist/wirings/workflow/graph/graph-runner.js +12 -10
  56. package/dist/wirings/workflow/graph/workflow-graph.types.d.ts +4 -0
  57. package/dist/wirings/workflow/index.d.ts +1 -1
  58. package/dist/wirings/workflow/pikku-workflow-service.d.ts +81 -16
  59. package/dist/wirings/workflow/pikku-workflow-service.js +266 -45
  60. package/dist/wirings/workflow/workflow.types.d.ts +34 -0
  61. package/package.json +1 -1
  62. package/run-tests.sh +4 -1
  63. package/src/dev/hot-reload.test.ts +62 -11
  64. package/src/dev/hot-reload.ts +28 -7
  65. package/src/errors/error-handler.ts +8 -2
  66. package/src/errors/error.test.ts +2 -0
  67. package/src/function/function-runner.test.ts +500 -10
  68. package/src/function/function-runner.ts +68 -3
  69. package/src/function/functions.types.ts +3 -2
  70. package/src/index.ts +22 -3
  71. package/src/middleware/index.ts +11 -2
  72. package/src/middleware/telemetry.ts +2 -2
  73. package/src/middleware-runner.test.ts +16 -16
  74. package/src/middleware-runner.ts +42 -30
  75. package/src/permissions.test.ts +27 -24
  76. package/src/permissions.ts +41 -25
  77. package/src/pikku-request.test.ts +35 -0
  78. package/src/pikku-request.ts +1 -1
  79. package/src/pikku-state.ts +2 -3
  80. package/src/run-tests-script.test.ts +18 -0
  81. package/src/services/ai-agent-runner-service.ts +1 -0
  82. package/src/services/content-service.ts +79 -51
  83. package/src/services/in-memory-queue-service.ts +1 -1
  84. package/src/services/in-memory-session-store.ts +1 -2
  85. package/src/services/in-memory-workflow-service.test.ts +33 -11
  86. package/src/services/in-memory-workflow-service.ts +49 -13
  87. package/src/services/index.ts +10 -1
  88. package/src/services/local-content.test.ts +54 -0
  89. package/src/services/local-content.ts +80 -53
  90. package/src/services/typed-credential-service.ts +3 -3
  91. package/src/services/typed-secret-service.ts +3 -3
  92. package/src/services/typed-variables-service.ts +3 -3
  93. package/src/services/user-session-service.ts +4 -4
  94. package/src/testing/service-tests.ts +30 -0
  95. package/src/types/core.types.ts +6 -2
  96. package/src/types/state.types.ts +2 -13
  97. package/src/wirings/ai-agent/ai-agent-memory.test.ts +324 -0
  98. package/src/wirings/ai-agent/ai-agent-memory.ts +187 -36
  99. package/src/wirings/ai-agent/ai-agent-model-config.test.ts +12 -90
  100. package/src/wirings/ai-agent/ai-agent-model-config.ts +14 -38
  101. package/src/wirings/ai-agent/ai-agent-prepare.test.ts +292 -0
  102. package/src/wirings/ai-agent/ai-agent-prepare.ts +4 -1
  103. package/src/wirings/ai-agent/ai-agent-registry.test.ts +230 -3
  104. package/src/wirings/ai-agent/ai-agent-runner.test.ts +625 -6
  105. package/src/wirings/ai-agent/ai-agent-runner.ts +65 -50
  106. package/src/wirings/ai-agent/ai-agent-stream.test.ts +544 -5
  107. package/src/wirings/ai-agent/ai-agent-stream.ts +71 -69
  108. package/src/wirings/ai-agent/ai-agent.types.ts +24 -0
  109. package/src/wirings/channel/channel-handler.test.ts +272 -0
  110. package/src/wirings/channel/channel-handler.ts +1 -1
  111. package/src/wirings/channel/channel-middleware-runner.test.ts +163 -0
  112. package/src/wirings/channel/channel-store.ts +19 -0
  113. package/src/wirings/channel/channel.types.ts +4 -0
  114. package/src/wirings/channel/local/local-channel-runner.test.ts +63 -0
  115. package/src/wirings/channel/local/local-channel-runner.ts +41 -5
  116. package/src/wirings/channel/local/local-eventhub-service.ts +3 -3
  117. package/src/wirings/channel/pikku-abstract-channel-handler.ts +9 -2
  118. package/src/wirings/channel/serverless/serverless-channel-runner.ts +23 -5
  119. package/src/wirings/cli/channel/cli-raw-channel-runner.ts +7 -2
  120. package/src/wirings/cli/cli-runner.test.ts +255 -2
  121. package/src/wirings/cli/cli-runner.ts +31 -10
  122. package/src/wirings/cli/cli.types.ts +4 -8
  123. package/src/wirings/cli/command-parser.test.ts +83 -0
  124. package/src/wirings/cli/command-parser.ts +23 -4
  125. package/src/wirings/http/http-runner.test.ts +296 -1
  126. package/src/wirings/http/http-runner.ts +87 -57
  127. package/src/wirings/http/http.types.ts +11 -26
  128. package/src/wirings/http/pikku-fetch-http-request.ts +8 -4
  129. package/src/wirings/http/pikku-fetch-http-response.test.ts +41 -0
  130. package/src/wirings/http/web-request.test.ts +115 -0
  131. package/src/wirings/http/web-request.ts +43 -5
  132. package/src/wirings/mcp/mcp-runner.test.ts +367 -0
  133. package/src/wirings/queue/queue.types.ts +2 -5
  134. package/src/wirings/rpc/rpc-runner.test.ts +511 -0
  135. package/src/wirings/rpc/rpc-runner.ts +60 -21
  136. package/src/wirings/rpc/wire-addon.test.ts +57 -0
  137. package/src/wirings/workflow/graph/graph-node.ts +12 -0
  138. package/src/wirings/workflow/graph/graph-runner.test.ts +98 -0
  139. package/src/wirings/workflow/graph/graph-runner.ts +28 -10
  140. package/src/wirings/workflow/graph/workflow-graph.types.ts +4 -0
  141. package/src/wirings/workflow/index.ts +1 -0
  142. package/src/wirings/workflow/pikku-workflow-service.test.ts +19 -2
  143. package/src/wirings/workflow/pikku-workflow-service.ts +370 -71
  144. package/src/wirings/workflow/workflow-queue-workers.test.ts +131 -0
  145. package/src/wirings/workflow/workflow.types.ts +68 -5
  146. package/tsconfig.tsbuildinfo +1 -1
@@ -0,0 +1,57 @@
1
+ import { beforeEach, describe, test } from 'node:test'
2
+ import assert from 'node:assert/strict'
3
+
4
+ import { resetPikkuState, pikkuState } from '../../pikku-state.js'
5
+ import { wireAddon } from './wire-addon.js'
6
+
7
+ beforeEach(() => {
8
+ resetPikkuState()
9
+ })
10
+
11
+ describe('wireAddon', () => {
12
+ test('registers addon package metadata for namespace resolution', () => {
13
+ wireAddon({
14
+ name: 'stripe',
15
+ package: '@addon/stripe',
16
+ rpcEndpoint: 'https://rpc.example.com',
17
+ auth: true,
18
+ mcp: true,
19
+ tags: ['payments', 'billing'],
20
+ secretOverrides: { apiKey: 'secretName' },
21
+ variableOverrides: { region: 'eu-west-1' },
22
+ credentialOverrides: { oauth: 'credentialName' },
23
+ })
24
+
25
+ assert.deepEqual(pikkuState(null, 'addons', 'packages').get('stripe'), {
26
+ package: '@addon/stripe',
27
+ rpcEndpoint: 'https://rpc.example.com',
28
+ auth: true,
29
+ tags: ['payments', 'billing'],
30
+ })
31
+ })
32
+
33
+ test('overwrites existing addon config for the same namespace', () => {
34
+ wireAddon({
35
+ name: 'stripe',
36
+ package: '@addon/stripe-v1',
37
+ rpcEndpoint: 'https://rpc-v1.example.com',
38
+ auth: false,
39
+ tags: ['old'],
40
+ })
41
+
42
+ wireAddon({
43
+ name: 'stripe',
44
+ package: '@addon/stripe-v2',
45
+ rpcEndpoint: 'https://rpc-v2.example.com',
46
+ auth: true,
47
+ tags: ['new'],
48
+ })
49
+
50
+ assert.deepEqual(pikkuState(null, 'addons', 'packages').get('stripe'), {
51
+ package: '@addon/stripe-v2',
52
+ rpcEndpoint: 'https://rpc-v2.example.com',
53
+ auth: true,
54
+ tags: ['new'],
55
+ })
56
+ })
57
+ })
@@ -45,6 +45,10 @@ export interface GraphNodeDef<
45
45
  next?: NextConfig<NodeIds>
46
46
  /** Error handling - node(s) to execute on error */
47
47
  onError?: NodeIds | NodeIds[]
48
+ /** Maximum retry attempts allowed (excludes the initial attempt) */
49
+ retries?: number
50
+ /** Delay between retries — milliseconds, duration string, or 'exponential' */
51
+ retryDelay?: string | number
48
52
  }
49
53
 
50
54
  /**
@@ -146,6 +150,8 @@ export function createGraph<RPCMap extends Record<string, RPCHandler>>() {
146
150
  input: def?.input as any,
147
151
  next: def?.next,
148
152
  onError: def?.onError,
153
+ retries: def?.retries,
154
+ retryDelay: def?.retryDelay,
149
155
  }
150
156
  }
151
157
 
@@ -172,6 +178,8 @@ type GraphNodeConfigMap<
172
178
  ) => TypedRef<ComputeNodeOutputs<FuncMap, RPCMap>[N][P]>
173
179
  ) => InputWithRefs<ComputeNodeInputs<FuncMap, RPCMap>[K]>
174
180
  onError?: Extract<keyof FuncMap, string> | Extract<keyof FuncMap, string>[]
181
+ retries?: number
182
+ retryDelay?: string | number
175
183
  }
176
184
  }
177
185
 
@@ -204,6 +212,8 @@ export function graph<NodeIds extends string = string>(
204
212
  ref: (nodeId: NodeIds, path: string) => RefValue
205
213
  ) => Record<string, unknown>
206
214
  onError?: NodeIds | NodeIds[]
215
+ retries?: number
216
+ retryDelay?: string | number
207
217
  }
208
218
  >
209
219
  ): Record<NodeIds, GraphNodeConfig<NodeIds>> {
@@ -215,6 +225,8 @@ export function graph<NodeIds extends string = string>(
215
225
  input: def.input as any,
216
226
  next: def.next,
217
227
  onError: def.onError,
228
+ retries: def.retries,
229
+ retryDelay: def.retryDelay,
218
230
  }
219
231
  }
220
232
 
@@ -526,4 +526,102 @@ describe('graph-runner bugs', () => {
526
526
 
527
527
  delete metaState['testInlineMetaGraph']
528
528
  })
529
+
530
+ test('queueGraphNode forwards node retries to queue attempts and backoff', async () => {
531
+ const ws = new InMemoryWorkflowService()
532
+ const enqueued: Array<{ queueName: string; data: any; options: any }> = []
533
+
534
+ pikkuState(null, 'package', 'singletonServices', {
535
+ queueService: {
536
+ add: async (queueName: string, data: any, options: any) => {
537
+ enqueued.push({ queueName, data, options })
538
+ },
539
+ },
540
+ } as any)
541
+
542
+ const meta: WorkflowRuntimeMeta = {
543
+ name: 'testRetries',
544
+ pikkuFuncId: 'testRetries',
545
+ source: 'graph',
546
+ entryNodeIds: ['a'],
547
+ graphHash: 'retries-hash',
548
+ nodes: {
549
+ a: {
550
+ nodeId: 'a',
551
+ rpcName: 'doA',
552
+ retries: 3,
553
+ retryDelay: 250,
554
+ },
555
+ b: {
556
+ nodeId: 'b',
557
+ rpcName: 'doB',
558
+ retries: 2,
559
+ retryDelay: 'exponential',
560
+ },
561
+ c: {
562
+ nodeId: 'c',
563
+ rpcName: 'doC',
564
+ },
565
+ },
566
+ }
567
+
568
+ const { runId } = await runWorkflowGraph(
569
+ ws,
570
+ 'testRetries',
571
+ {},
572
+ { rpcWithWire: async () => ({}) },
573
+ false,
574
+ undefined,
575
+ undefined,
576
+ meta
577
+ )
578
+
579
+ const aJob = enqueued.find((e) => e.data?.stepName === 'a')
580
+ assert.ok(aJob, 'node a should have been enqueued')
581
+ assert.equal(aJob!.options?.attempts, 4, 'retries=3 → attempts=4')
582
+ assert.deepEqual(aJob!.options?.backoff, { type: 'fixed', delay: 250 })
583
+
584
+ const stepStateA = await ws.getStepState(runId, 'a')
585
+ assert.equal(
586
+ stepStateA.retries,
587
+ 3,
588
+ 'insertStepState should record node retries'
589
+ )
590
+ assert.equal(stepStateA.retryDelay, 250)
591
+
592
+ enqueued.length = 0
593
+ await runWorkflowGraph(
594
+ ws,
595
+ 'testRetries',
596
+ {},
597
+ { rpcWithWire: async () => ({}) },
598
+ false,
599
+ 'b',
600
+ undefined,
601
+ meta
602
+ )
603
+ const bJob = enqueued.find((e) => e.data?.stepName === 'b')
604
+ assert.ok(bJob, 'node b should have been enqueued')
605
+ assert.equal(bJob!.options?.attempts, 3, 'retries=2 → attempts=3')
606
+ assert.equal(bJob!.options?.backoff, 'exponential')
607
+
608
+ enqueued.length = 0
609
+ await runWorkflowGraph(
610
+ ws,
611
+ 'testRetries',
612
+ {},
613
+ { rpcWithWire: async () => ({}) },
614
+ false,
615
+ 'c',
616
+ undefined,
617
+ meta
618
+ )
619
+ const cJob = enqueued.find((e) => e.data?.stepName === 'c')
620
+ assert.ok(cJob, 'node c should have been enqueued')
621
+ assert.equal(
622
+ cJob!.options,
623
+ undefined,
624
+ 'no retries → no queue options (preserves prior queue defaults)'
625
+ )
626
+ })
529
627
  })
@@ -324,12 +324,27 @@ async function queueGraphNode(
324
324
  _graphName: string,
325
325
  nodeId: string,
326
326
  rpcName: string,
327
- input: any
327
+ input: any,
328
+ nodeConfig?: { retries?: number; retryDelay?: string | number }
328
329
  ): Promise<void> {
329
- await workflowService.insertStepState(runId, nodeId, rpcName, input, {
330
- retries: 0,
331
- })
332
- await workflowService.queueStepWorker(runId, nodeId, rpcName, input)
330
+ const stepOptions = {
331
+ retries: nodeConfig?.retries ?? 0,
332
+ retryDelay: nodeConfig?.retryDelay,
333
+ }
334
+ await workflowService.insertStepState(
335
+ runId,
336
+ nodeId,
337
+ rpcName,
338
+ input,
339
+ stepOptions
340
+ )
341
+ await workflowService.queueStepWorker(
342
+ runId,
343
+ nodeId,
344
+ rpcName,
345
+ input,
346
+ stepOptions
347
+ )
333
348
  }
334
349
 
335
350
  export async function continueGraph(
@@ -442,7 +457,8 @@ export async function continueGraph(
442
457
  graphName,
443
458
  nodeId,
444
459
  node.rpcName,
445
- resolvedInput
460
+ resolvedInput,
461
+ node
446
462
  )
447
463
  }
448
464
  }
@@ -553,7 +569,8 @@ export async function executeGraphStep(
553
569
  graphName,
554
570
  errorNodeId,
555
571
  errorNode.rpcName,
556
- { error: { message: (error as Error).message } }
572
+ { error: { message: (error as Error).message } },
573
+ errorNode
557
574
  )
558
575
  }
559
576
  }
@@ -600,7 +617,7 @@ async function executeGraphNodeInline(
600
617
  nodeId,
601
618
  rpcName,
602
619
  input,
603
- { retries: 3 }
620
+ { retries: node.retries ?? 0, retryDelay: node.retryDelay }
604
621
  )
605
622
 
606
623
  await workflowService.setStepRunning(stepState.stepId)
@@ -677,7 +694,7 @@ async function executeGraphNodeInline(
677
694
  message: `RPC '${rpcName}' not found. Deploy the missing function and resume.`,
678
695
  code: 'RPC_NOT_FOUND',
679
696
  })
680
- return
697
+ throw new WorkflowSuspendedException(runId, 'RPC_NOT_FOUND')
681
698
  }
682
699
  await workflowService.setStepError(stepState.stepId, error as Error)
683
700
 
@@ -932,7 +949,8 @@ export async function runWorkflowGraph(
932
949
  graphName,
933
950
  nodeId,
934
951
  node.rpcName,
935
- resolvedInput
952
+ resolvedInput,
953
+ node
936
954
  )
937
955
  }
938
956
  if (inline) {
@@ -65,6 +65,10 @@ export interface GraphNodeConfig<NodeIds extends string = string> {
65
65
  next?: NextConfig<NodeIds>
66
66
  /** Error routing - node(s) to execute on error */
67
67
  onError?: NodeIds | NodeIds[]
68
+ /** Maximum retry attempts allowed (excludes the initial attempt) */
69
+ retries?: number
70
+ /** Delay between retries — milliseconds, duration string, or 'exponential' */
71
+ retryDelay?: string | number
68
72
  }
69
73
 
70
74
  /**
@@ -51,6 +51,7 @@ export type {
51
51
  WorkflowRunStatus,
52
52
  StepState,
53
53
  WorkflowRunService,
54
+ WorkflowRunMirror,
54
55
  CoreWorkflow,
55
56
  PikkuWorkflow,
56
57
  ContextVariable,
@@ -2,13 +2,30 @@ import { describe, test } from 'node:test'
2
2
  import assert from 'node:assert/strict'
3
3
 
4
4
  import { InMemoryWorkflowService } from '../../services/in-memory-workflow-service.js'
5
- import { pikkuState } from '../../pikku-state.js'
5
+ import { pikkuState, resetPikkuState } from '../../pikku-state.js'
6
6
  import { addWorkflow } from './dsl/workflow-runner.js'
7
7
  import {
8
8
  WorkflowSuspendedException,
9
9
  type PikkuWorkflowWire,
10
10
  } from './pikku-workflow-service.js'
11
11
 
12
+ describe('pikku-workflow-service worker registration', () => {
13
+ test('registers sleeper function metadata', () => {
14
+ resetPikkuState()
15
+
16
+ new InMemoryWorkflowService()
17
+
18
+ const functionMeta = pikkuState(null, 'function', 'meta')
19
+ assert.deepEqual(functionMeta.pikkuWorkflowSleeper, {
20
+ pikkuFuncId: 'pikkuWorkflowSleeper',
21
+ sessionless: true,
22
+ functionType: 'helper',
23
+ inputSchemaName: null,
24
+ outputSchemaName: null,
25
+ })
26
+ })
27
+ })
28
+
12
29
  describe('pikku-workflow-service inline meta flag', () => {
13
30
  test('workflow with inline: true in meta should create inline run and not queue', async () => {
14
31
  const ws = new InMemoryWorkflowService()
@@ -53,7 +70,7 @@ describe('pikku-workflow-service inline meta flag', () => {
53
70
  },
54
71
  })
55
72
 
56
- const { runId } = await ws.startWorkflow(workflowName, {}, {})
73
+ const { runId } = await ws.startWorkflow(workflowName, {}, {}, {})
57
74
 
58
75
  // The run should be created as inline
59
76
  const run = await ws.getRun(runId)