@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
package/run-tests.sh CHANGED
@@ -1,5 +1,8 @@
1
1
  #!/bin/bash
2
2
 
3
+ script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
4
+ cd "$script_dir"
5
+
3
6
  # Enable nullglob to handle cases where no files match the pattern
4
7
  shopt -s nullglob
5
8
 
@@ -34,7 +37,7 @@ files=($(find src -type f -name "*.test.ts"))
34
37
  # Check if any files matched the pattern
35
38
  if [ ${#files[@]} -eq 0 ]; then
36
39
  echo "No test files found matching pattern: $pattern"
37
- if [ "${STRICT_TEST_DISCOVERY}" = "1" ] || [ "${CI}" = "true" ]; then
40
+ if [ "${STRICT_TEST_DISCOVERY}" = "1" ]; then
38
41
  exit 1
39
42
  fi
40
43
  exit 0
@@ -1,5 +1,12 @@
1
- import { describe, test, beforeEach, afterEach } from 'node:test'
1
+ import {
2
+ describe,
3
+ test,
4
+ beforeEach,
5
+ afterEach,
6
+ type TestContext,
7
+ } from 'node:test'
2
8
  import assert from 'node:assert/strict'
9
+ import { watch } from 'node:fs'
3
10
  import { mkdtemp, writeFile, rm, mkdir } from 'node:fs/promises'
4
11
  import { join } from 'node:path'
5
12
  import { tmpdir } from 'node:os'
@@ -21,6 +28,32 @@ import {
21
28
 
22
29
  const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))
23
30
 
31
+ const ensureRecursiveWatchAvailable = async (
32
+ t: TestContext,
33
+ dir: string
34
+ ): Promise<boolean> => {
35
+ if (process.platform === 'darwin') {
36
+ t.skip('recursive fs.watch is unreliable on darwin')
37
+ return false
38
+ }
39
+
40
+ try {
41
+ const watcher = watch(dir, { recursive: true }, () => {})
42
+ watcher.close()
43
+ return true
44
+ } catch (error: any) {
45
+ if (
46
+ error?.code === 'EMFILE' ||
47
+ error?.code === 'ERR_FEATURE_UNAVAILABLE_ON_PLATFORM' ||
48
+ error?.code === 'ERR_INVALID_ARG_VALUE'
49
+ ) {
50
+ t.skip(`recursive fs.watch unavailable: ${error.code}`)
51
+ return false
52
+ }
53
+ throw error
54
+ }
55
+ }
56
+
24
57
  const createMockLogger = () => {
25
58
  const logs: Array<{ level: string; message: string }> = []
26
59
  return {
@@ -47,7 +80,7 @@ const writeFunctionModule = async (
47
80
  await writeFile(join(dir, filename), `// ts trigger ${Date.now()}`)
48
81
  }
49
82
 
50
- describe('pikkuDevReloader', () => {
83
+ describe('pikkuDevReloader', { concurrency: false }, () => {
51
84
  let tmpDir: string
52
85
  let reloader: { close: () => void } | undefined
53
86
  let mockLogger: ReturnType<typeof createMockLogger>
@@ -76,7 +109,9 @@ describe('pikkuDevReloader', () => {
76
109
  await rm(tmpDir, { recursive: true, force: true })
77
110
  })
78
111
 
79
- test('should hot-reload a function and pick up new return value', async () => {
112
+ test('should hot-reload a function and pick up new return value', async (t) => {
113
+ if (!(await ensureRecursiveWatchAvailable(t, tmpDir))) return
114
+
80
115
  addFunction('myFunc', {
81
116
  func: async () => ({ version: 1 }),
82
117
  })
@@ -112,7 +147,9 @@ describe('pikkuDevReloader', () => {
112
147
  assert.ok(reloadLog, 'Should log hot-reload message')
113
148
  })
114
149
 
115
- test('should not replace a function that is not registered', async () => {
150
+ test('should not replace a function that is not registered', async (t) => {
151
+ if (!(await ensureRecursiveWatchAvailable(t, tmpDir))) return
152
+
116
153
  addFunction('registeredFunc', {
117
154
  func: async () => ({ name: 'registered' }),
118
155
  })
@@ -135,7 +172,9 @@ describe('pikkuDevReloader', () => {
135
172
  )
136
173
  })
137
174
 
138
- test('should keep old code when JS import fails', async () => {
175
+ test('should keep old code when JS import fails', async (t) => {
176
+ if (!(await ensureRecursiveWatchAvailable(t, tmpDir))) return
177
+
139
178
  addFunction('badFunc', {
140
179
  func: async () => ({ working: true }),
141
180
  })
@@ -166,7 +205,9 @@ describe('pikkuDevReloader', () => {
166
205
  })
167
206
  })
168
207
 
169
- test('should ignore non-ts files, test files, and gen files', async () => {
208
+ test('should ignore non-ts files, test files, and gen files', async (t) => {
209
+ if (!(await ensureRecursiveWatchAvailable(t, tmpDir))) return
210
+
170
211
  addFunction('someFunc', {
171
212
  func: async () => ({ original: true }),
172
213
  })
@@ -190,7 +231,9 @@ describe('pikkuDevReloader', () => {
190
231
  assert.equal(reloadLogs.length, 0)
191
232
  })
192
233
 
193
- test('should hot-reload function used via HTTP wire', async () => {
234
+ test('should hot-reload function used via HTTP wire', async (t) => {
235
+ if (!(await ensureRecursiveWatchAvailable(t, tmpDir))) return
236
+
194
237
  const sessionMiddleware = async (_services: any, wire: any, next: any) => {
195
238
  wire.setSession?.({ userId: 'test' } as any)
196
239
  await next()
@@ -251,7 +294,9 @@ describe('pikkuDevReloader', () => {
251
294
  })
252
295
  })
253
296
 
254
- test('should hot-reload function used via scheduler wire', async () => {
297
+ test('should hot-reload function used via scheduler wire', async (t) => {
298
+ if (!(await ensureRecursiveWatchAvailable(t, tmpDir))) return
299
+
255
300
  const taskResult = { ref: 'initial' }
256
301
 
257
302
  pikkuState(null, 'scheduler', 'meta')['hotTask'] = {
@@ -371,7 +416,9 @@ describe('pikkuDevReloader', () => {
371
416
  assert.deepEqual(resultV2, { result: 'v2' })
372
417
  })
373
418
 
374
- test('should debounce rapid file changes', async () => {
419
+ test('should debounce rapid file changes', async (t) => {
420
+ if (!(await ensureRecursiveWatchAvailable(t, tmpDir))) return
421
+
375
422
  addFunction('debounceFunc', {
376
423
  func: async () => ({ count: 0 }),
377
424
  })
@@ -396,7 +443,9 @@ describe('pikkuDevReloader', () => {
396
443
  assert.equal(result.count, 5)
397
444
  })
398
445
 
399
- test('should watch subdirectories', async () => {
446
+ test('should watch subdirectories', async (t) => {
447
+ if (!(await ensureRecursiveWatchAvailable(t, tmpDir))) return
448
+
400
449
  const subDir = join(tmpDir, 'functions')
401
450
  await mkdir(subDir)
402
451
 
@@ -426,7 +475,9 @@ describe('pikkuDevReloader', () => {
426
475
  })
427
476
  })
428
477
 
429
- test('should properly clean up on close', async () => {
478
+ test('should properly clean up on close', async (t) => {
479
+ if (!(await ensureRecursiveWatchAvailable(t, tmpDir))) return
480
+
430
481
  addFunction('cleanupFunc', {
431
482
  func: async () => ({ v: 1 }),
432
483
  })
@@ -1,6 +1,9 @@
1
1
  import { watch, type FSWatcher } from 'node:fs'
2
2
  import { stat, readFile } from 'node:fs/promises'
3
3
  import { join, resolve, relative } from 'node:path'
4
+ import { pathToFileURL } from 'node:url'
5
+
6
+ import { register } from 'tsx/esm/api'
4
7
 
5
8
  import { pikkuState } from '../pikku-state.js'
6
9
  import { addFunction } from '../function/function-runner.js'
@@ -54,10 +57,26 @@ const findCompiledFile = async (
54
57
  // Use data: URLs to import modules. This bypasses TypeScript loaders
55
58
  // (e.g. tsx) that intercept file:// imports and break dynamic ESM loading.
56
59
  // Each import gets unique content so there's no module cache to worry about.
60
+ let tsxRegistered = false
61
+
62
+ const ensureTsxRegistered = () => {
63
+ if (tsxRegistered) return
64
+ register()
65
+ tsxRegistered = true
66
+ }
67
+
57
68
  const reimportModule = async (
58
- filePath: string
69
+ filePath: string,
70
+ useTsx = false
59
71
  ): Promise<Record<string, unknown> | null> => {
60
72
  try {
73
+ if (useTsx) {
74
+ ensureTsxRegistered()
75
+ return await import(
76
+ `${pathToFileURL(resolve(filePath)).href}?t=${Date.now()}`
77
+ )
78
+ }
79
+
61
80
  const content = await readFile(resolve(filePath), 'utf-8')
62
81
  const dataUrl =
63
82
  'data:text/javascript;base64,' + Buffer.from(content).toString('base64')
@@ -98,17 +117,19 @@ export async function pikkuDevReloader(
98
117
  srcDir,
99
118
  absPikkuDir
100
119
  )
101
- if (!compiledFile) {
102
- logger.warn(
103
- `Could not find compiled JS for: ${relative(process.cwd(), changedTsFile)}`
120
+ const importPath = compiledFile ?? changedTsFile
121
+ const usedTsxFallback = !compiledFile
122
+
123
+ if (usedTsxFallback) {
124
+ logger.debug(
125
+ `Hot-reload using tsx fallback for: ${relative(process.cwd(), changedTsFile)}`
104
126
  )
105
- return
106
127
  }
107
128
 
108
- const mod = await reimportModule(compiledFile)
129
+ const mod = await reimportModule(importPath, usedTsxFallback)
109
130
  if (!mod) {
110
131
  logger.error(
111
- `Failed to import: ${relative(process.cwd(), compiledFile)} (keeping old code)`
132
+ `Failed to import: ${relative(process.cwd(), importPath)} (keeping old code)`
112
133
  )
113
134
  return
114
135
  }
@@ -29,8 +29,14 @@ export interface ErrorDetails {
29
29
  * @param error - The error to add.
30
30
  * @param details - The details of the error.
31
31
  */
32
- export const addError = (error: any, { status, message }: ErrorDetails) => {
33
- pikkuState(null, 'misc', 'errors').set(error, { status, message })
32
+ export const addError = (
33
+ error: any,
34
+ { status, message, mcpCode }: ErrorDetails
35
+ ) => {
36
+ pikkuState(null, 'misc', 'errors').set(
37
+ error,
38
+ mcpCode === undefined ? { status, message } : { status, message, mcpCode }
39
+ )
34
40
  }
35
41
 
36
42
  /**
@@ -77,6 +77,7 @@ describe('getErrorResponse', () => {
77
77
  const response = getErrorResponse(error)
78
78
  assert.deepStrictEqual(response, {
79
79
  status: 400,
80
+ mcpCode: -32600,
80
81
  message:
81
82
  'The server cannot or will not process the request due to client error (e.g., malformed request syntax).',
82
83
  })
@@ -87,6 +88,7 @@ describe('getErrorResponse', () => {
87
88
  const response = getErrorResponse(error)
88
89
  assert.deepStrictEqual(response, {
89
90
  status: 404,
91
+ mcpCode: -32601,
90
92
  message: 'The server cannot find the requested resource.',
91
93
  })
92
94
  })