@pikku/core 0.12.14 → 0.12.16

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 (136) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/dist/errors/errors.d.ts +4 -0
  3. package/dist/errors/errors.js +12 -0
  4. package/dist/function/function-runner.js +2 -1
  5. package/dist/function/functions.types.js +1 -0
  6. package/dist/function/index.d.ts +2 -1
  7. package/dist/function/index.js +1 -0
  8. package/dist/handle-error.d.ts +2 -2
  9. package/dist/handle-error.js +8 -8
  10. package/dist/index.d.ts +2 -3
  11. package/dist/index.js +1 -2
  12. package/dist/middleware/index.d.ts +3 -0
  13. package/dist/middleware/index.js +3 -0
  14. package/dist/middleware/remote-auth.js +5 -2
  15. package/dist/middleware/telemetry.d.ts +47 -0
  16. package/dist/middleware/telemetry.js +106 -0
  17. package/dist/middleware-runner.js +31 -4
  18. package/dist/permissions.d.ts +13 -1
  19. package/dist/permissions.js +51 -0
  20. package/dist/pikku-state.d.ts +0 -1
  21. package/dist/pikku-state.js +1 -4
  22. package/dist/remote.d.ts +10 -0
  23. package/dist/remote.js +32 -0
  24. package/dist/schema.js +2 -0
  25. package/dist/services/deployment-service.d.ts +12 -1
  26. package/dist/services/in-memory-queue-service.d.ts +7 -0
  27. package/dist/services/in-memory-queue-service.js +28 -0
  28. package/dist/services/index.d.ts +4 -1
  29. package/dist/services/index.js +2 -1
  30. package/dist/services/logger-console.d.ts +26 -40
  31. package/dist/services/logger-console.js +102 -46
  32. package/dist/services/logger.d.ts +5 -0
  33. package/dist/services/meta-service.d.ts +175 -0
  34. package/dist/services/meta-service.js +310 -0
  35. package/dist/services/workflow-service.d.ts +6 -1
  36. package/dist/testing/service-tests.js +0 -8
  37. package/dist/types/core.types.d.ts +21 -0
  38. package/dist/types/core.types.js +7 -1
  39. package/dist/types/state.types.d.ts +2 -2
  40. package/dist/wirings/ai-agent/ai-agent-prepare.js +42 -21
  41. package/dist/wirings/ai-agent/ai-agent-registry.js +2 -2
  42. package/dist/wirings/ai-agent/ai-agent-runner.js +18 -1
  43. package/dist/wirings/ai-agent/ai-agent-stream.js +1 -0
  44. package/dist/wirings/ai-agent/ai-agent.types.d.ts +5 -3
  45. package/dist/wirings/channel/channel-runner.js +3 -3
  46. package/dist/wirings/cli/cli-runner.js +5 -3
  47. package/dist/wirings/cli/command-parser.js +7 -3
  48. package/dist/wirings/http/http-runner.d.ts +1 -1
  49. package/dist/wirings/http/http-runner.js +23 -11
  50. package/dist/wirings/http/http.types.d.ts +3 -0
  51. package/dist/wirings/http/pikku-fetch-http-response.d.ts +1 -0
  52. package/dist/wirings/http/pikku-fetch-http-response.js +3 -0
  53. package/dist/wirings/mcp/mcp-runner.js +5 -3
  54. package/dist/wirings/mcp/mcp.types.d.ts +1 -1
  55. package/dist/wirings/queue/queue-runner.d.ts +3 -1
  56. package/dist/wirings/queue/queue-runner.js +7 -3
  57. package/dist/wirings/rpc/rpc-runner.js +56 -90
  58. package/dist/wirings/scheduler/scheduler-runner.d.ts +3 -1
  59. package/dist/wirings/scheduler/scheduler-runner.js +9 -5
  60. package/dist/wirings/trigger/trigger-runner.js +4 -2
  61. package/dist/wirings/workflow/dsl/workflow-runner.d.ts +1 -1
  62. package/dist/wirings/workflow/dsl/workflow-runner.js +6 -9
  63. package/dist/wirings/workflow/graph/graph-runner.d.ts +1 -1
  64. package/dist/wirings/workflow/graph/graph-runner.js +18 -4
  65. package/dist/wirings/workflow/index.d.ts +4 -2
  66. package/dist/wirings/workflow/index.js +4 -2
  67. package/dist/wirings/workflow/pikku-workflow-service.d.ts +16 -4
  68. package/dist/wirings/workflow/pikku-workflow-service.js +216 -24
  69. package/dist/wirings/workflow/workflow-queue-workers.d.ts +40 -0
  70. package/dist/wirings/workflow/workflow-queue-workers.js +41 -0
  71. package/dist/wirings/workflow/workflow.types.d.ts +17 -1
  72. package/package.json +4 -1
  73. package/src/errors/errors.ts +12 -0
  74. package/src/function/function-runner.ts +5 -4
  75. package/src/function/functions.types.ts +1 -0
  76. package/src/function/index.ts +10 -0
  77. package/src/handle-error.test.ts +2 -2
  78. package/src/handle-error.ts +8 -8
  79. package/src/index.ts +2 -2
  80. package/src/middleware/index.ts +3 -0
  81. package/src/middleware/remote-auth.test.ts +4 -4
  82. package/src/middleware/remote-auth.ts +7 -2
  83. package/src/middleware/telemetry.ts +110 -0
  84. package/src/middleware-runner.test.ts +149 -1
  85. package/src/middleware-runner.ts +48 -4
  86. package/src/permissions.ts +70 -0
  87. package/src/pikku-state.test.ts +0 -26
  88. package/src/pikku-state.ts +1 -5
  89. package/src/remote.ts +46 -0
  90. package/src/schema.ts +1 -0
  91. package/src/services/deployment-service.ts +17 -1
  92. package/src/services/in-memory-queue-service.ts +46 -0
  93. package/src/services/index.ts +21 -1
  94. package/src/services/logger-console.ts +127 -47
  95. package/src/services/logger.ts +6 -0
  96. package/src/services/meta-service.ts +523 -0
  97. package/src/services/workflow-service.ts +8 -0
  98. package/src/testing/service-tests.ts +0 -10
  99. package/src/types/core.types.ts +36 -1
  100. package/src/types/state.types.ts +2 -2
  101. package/src/wirings/ai-agent/ai-agent-prepare.ts +51 -40
  102. package/src/wirings/ai-agent/ai-agent-registry.ts +3 -3
  103. package/src/wirings/ai-agent/ai-agent-runner.ts +16 -1
  104. package/src/wirings/ai-agent/ai-agent-stream.ts +8 -0
  105. package/src/wirings/ai-agent/ai-agent.types.ts +5 -3
  106. package/src/wirings/channel/channel-runner.ts +4 -5
  107. package/src/wirings/channel/local/local-channel-runner.test.ts +5 -1
  108. package/src/wirings/cli/cli-runner.test.ts +8 -7
  109. package/src/wirings/cli/cli-runner.ts +6 -4
  110. package/src/wirings/cli/command-parser.ts +8 -3
  111. package/src/wirings/http/http-runner.ts +27 -11
  112. package/src/wirings/http/http.types.ts +3 -0
  113. package/src/wirings/http/pikku-fetch-http-response.ts +4 -0
  114. package/src/wirings/mcp/mcp-runner.ts +7 -9
  115. package/src/wirings/mcp/mcp.types.ts +1 -1
  116. package/src/wirings/queue/queue-runner.test.ts +5 -11
  117. package/src/wirings/queue/queue-runner.ts +11 -3
  118. package/src/wirings/rpc/rpc-runner.ts +82 -115
  119. package/src/wirings/scheduler/scheduler-runner.test.ts +5 -11
  120. package/src/wirings/scheduler/scheduler-runner.ts +13 -5
  121. package/src/wirings/trigger/trigger-runner.test.ts +10 -11
  122. package/src/wirings/trigger/trigger-runner.ts +6 -4
  123. package/src/wirings/workflow/dsl/workflow-runner.ts +11 -10
  124. package/src/wirings/workflow/graph/graph-runner.ts +19 -4
  125. package/src/wirings/workflow/index.ts +17 -6
  126. package/src/wirings/workflow/pikku-workflow-service.ts +284 -24
  127. package/src/wirings/workflow/workflow-queue-workers.ts +85 -0
  128. package/src/wirings/workflow/workflow.types.ts +16 -1
  129. package/tsconfig.tsbuildinfo +1 -1
  130. package/dist/wirings/ai-agent/agent-dynamic-workflow.d.ts +0 -6
  131. package/dist/wirings/ai-agent/agent-dynamic-workflow.js +0 -468
  132. package/dist/wirings/workflow/workflow-helpers.d.ts +0 -36
  133. package/dist/wirings/workflow/workflow-helpers.js +0 -38
  134. package/src/wirings/ai-agent/agent-dynamic-workflow.ts +0 -610
  135. package/src/wirings/workflow/workflow-helpers.test.ts +0 -129
  136. package/src/wirings/workflow/workflow-helpers.ts +0 -79
@@ -0,0 +1,310 @@
1
+ import { readFile, readdir } from 'node:fs/promises';
2
+ import { join } from 'node:path';
3
+ /**
4
+ * Node.js filesystem implementation of MetaService.
5
+ * Reads .gen.json files from a local .pikku directory.
6
+ */
7
+ export class LocalMetaService {
8
+ basePath;
9
+ httpMetaCache = null;
10
+ channelsMetaCache = null;
11
+ schedulerMetaCache = null;
12
+ queueMetaCache = null;
13
+ cliMetaCache = null;
14
+ mcpMetaCache = null;
15
+ rpcMetaCache = null;
16
+ workflowMetaCache = null;
17
+ triggerMetaCache = null;
18
+ triggerSourceMetaCache = null;
19
+ functionsMetaCache = null;
20
+ servicesMetaCache = null;
21
+ secretsMetaCache = null;
22
+ credentialsMetaCache = null;
23
+ variablesMetaCache = null;
24
+ middlewareGroupsMetaCache = null;
25
+ permissionsGroupsMetaCache = null;
26
+ agentsMetaCache = null;
27
+ schemaCache = new Map();
28
+ constructor(basePath) {
29
+ this.basePath = basePath;
30
+ }
31
+ async readFile(relativePath) {
32
+ try {
33
+ return await readFile(join(this.basePath, relativePath), 'utf-8');
34
+ }
35
+ catch {
36
+ return null;
37
+ }
38
+ }
39
+ async readDir(relativePath) {
40
+ try {
41
+ return await readdir(join(this.basePath, relativePath));
42
+ }
43
+ catch {
44
+ return [];
45
+ }
46
+ }
47
+ clearCache() {
48
+ this.httpMetaCache = null;
49
+ this.channelsMetaCache = null;
50
+ this.schedulerMetaCache = null;
51
+ this.queueMetaCache = null;
52
+ this.cliMetaCache = null;
53
+ this.mcpMetaCache = null;
54
+ this.rpcMetaCache = null;
55
+ this.workflowMetaCache = null;
56
+ this.triggerMetaCache = null;
57
+ this.triggerSourceMetaCache = null;
58
+ this.functionsMetaCache = null;
59
+ this.servicesMetaCache = null;
60
+ this.secretsMetaCache = null;
61
+ this.credentialsMetaCache = null;
62
+ this.variablesMetaCache = null;
63
+ this.middlewareGroupsMetaCache = null;
64
+ this.permissionsGroupsMetaCache = null;
65
+ this.agentsMetaCache = null;
66
+ this.schemaCache.clear();
67
+ }
68
+ // -- Private helpers --
69
+ async readMetaJson(dir, baseName) {
70
+ const verbose = await this.readFile(`${dir}/${baseName}-verbose.gen.json`);
71
+ if (verbose)
72
+ return verbose;
73
+ const minimal = await this.readFile(`${dir}/${baseName}.gen.json`);
74
+ if (minimal)
75
+ return minimal;
76
+ return null;
77
+ }
78
+ // -- Typed metadata accessors --
79
+ async getHttpMeta() {
80
+ if (this.httpMetaCache)
81
+ return this.httpMetaCache;
82
+ const content = await this.readMetaJson('http', 'pikku-http-wirings-meta');
83
+ this.httpMetaCache = content
84
+ ? JSON.parse(content)
85
+ : {
86
+ get: {},
87
+ post: {},
88
+ put: {},
89
+ delete: {},
90
+ patch: {},
91
+ head: {},
92
+ options: {},
93
+ };
94
+ return this.httpMetaCache;
95
+ }
96
+ async getChannelsMeta() {
97
+ if (this.channelsMetaCache)
98
+ return this.channelsMetaCache;
99
+ const content = await this.readMetaJson('channel', 'pikku-channels-meta');
100
+ this.channelsMetaCache = content ? JSON.parse(content) : {};
101
+ return this.channelsMetaCache;
102
+ }
103
+ async getSchedulerMeta() {
104
+ if (this.schedulerMetaCache)
105
+ return this.schedulerMetaCache;
106
+ const content = await this.readMetaJson('scheduler', 'pikku-schedulers-wirings-meta');
107
+ this.schedulerMetaCache = content ? JSON.parse(content) : {};
108
+ return this.schedulerMetaCache;
109
+ }
110
+ async getQueueMeta() {
111
+ if (this.queueMetaCache)
112
+ return this.queueMetaCache;
113
+ const content = await this.readMetaJson('queue', 'pikku-queue-workers-wirings-meta');
114
+ this.queueMetaCache = content ? JSON.parse(content) : {};
115
+ return this.queueMetaCache;
116
+ }
117
+ async getCliMeta() {
118
+ if (this.cliMetaCache)
119
+ return this.cliMetaCache;
120
+ const content = await this.readMetaJson('cli', 'pikku-cli-wirings-meta');
121
+ this.cliMetaCache = content
122
+ ? JSON.parse(content)
123
+ : { programs: {}, renderers: {} };
124
+ return this.cliMetaCache;
125
+ }
126
+ async getMcpMeta() {
127
+ if (this.mcpMetaCache)
128
+ return this.mcpMetaCache;
129
+ const content = await this.readMetaJson('mcp', 'pikku-mcp-wirings-meta');
130
+ if (content) {
131
+ const mcpData = JSON.parse(content);
132
+ this.mcpMetaCache = {
133
+ resources: mcpData.resourcesMeta || {},
134
+ tools: mcpData.toolsMeta || {},
135
+ prompts: mcpData.promptsMeta || {},
136
+ };
137
+ }
138
+ else {
139
+ this.mcpMetaCache = { resources: {}, tools: {}, prompts: {} };
140
+ }
141
+ return this.mcpMetaCache;
142
+ }
143
+ async getRpcMeta() {
144
+ if (this.rpcMetaCache)
145
+ return this.rpcMetaCache;
146
+ try {
147
+ const content = await this.readFile('rpc/pikku-rpc-wirings-meta.gen.json');
148
+ this.rpcMetaCache = content ? JSON.parse(content) : {};
149
+ return this.rpcMetaCache;
150
+ }
151
+ catch (error) {
152
+ console.error('Error reading RPC wirings metadata:', error);
153
+ this.rpcMetaCache = {};
154
+ return this.rpcMetaCache;
155
+ }
156
+ }
157
+ async getWorkflowMeta() {
158
+ if (this.workflowMetaCache)
159
+ return this.workflowMetaCache;
160
+ try {
161
+ const files = await this.readDir('workflow/meta');
162
+ const jsonFiles = files.filter((f) => f.endsWith('.gen.json'));
163
+ const verboseFiles = jsonFiles.filter((f) => f.includes('-verbose'));
164
+ const minimalFiles = jsonFiles.filter((f) => !f.includes('-verbose'));
165
+ const verboseNames = new Set(verboseFiles.map((f) => f.replace('-verbose.gen.json', '')));
166
+ const filesToRead = [
167
+ ...verboseFiles,
168
+ ...minimalFiles.filter((f) => !verboseNames.has(f.replace('.gen.json', ''))),
169
+ ];
170
+ const result = {};
171
+ await Promise.all(filesToRead.map(async (file) => {
172
+ const content = await this.readFile(`workflow/meta/${file}`);
173
+ if (content) {
174
+ const meta = JSON.parse(content);
175
+ result[meta.name] = meta;
176
+ }
177
+ }));
178
+ this.workflowMetaCache = result;
179
+ return this.workflowMetaCache;
180
+ }
181
+ catch (error) {
182
+ console.error('Error reading Workflow wirings metadata:', error);
183
+ this.workflowMetaCache = {};
184
+ return this.workflowMetaCache;
185
+ }
186
+ }
187
+ async getTriggerMeta() {
188
+ if (this.triggerMetaCache)
189
+ return this.triggerMetaCache;
190
+ const content = await this.readMetaJson('trigger', 'pikku-trigger-wirings-meta');
191
+ this.triggerMetaCache = content ? JSON.parse(content) : {};
192
+ return this.triggerMetaCache;
193
+ }
194
+ async getTriggerSourceMeta() {
195
+ if (this.triggerSourceMetaCache)
196
+ return this.triggerSourceMetaCache;
197
+ const content = await this.readMetaJson('trigger', 'pikku-trigger-sources-meta');
198
+ this.triggerSourceMetaCache = content ? JSON.parse(content) : {};
199
+ return this.triggerSourceMetaCache;
200
+ }
201
+ async getFunctionsMeta() {
202
+ if (this.functionsMetaCache)
203
+ return this.functionsMetaCache;
204
+ const content = await this.readMetaJson('function', 'pikku-functions-meta');
205
+ this.functionsMetaCache = content ? JSON.parse(content) : {};
206
+ return this.functionsMetaCache;
207
+ }
208
+ async getMiddlewareGroupsMeta() {
209
+ if (this.middlewareGroupsMetaCache)
210
+ return this.middlewareGroupsMetaCache;
211
+ const content = await this.readMetaJson('middleware', 'pikku-middleware-groups-meta');
212
+ this.middlewareGroupsMetaCache = content
213
+ ? JSON.parse(content)
214
+ : { definitions: {}, instances: {}, httpGroups: {}, tagGroups: {} };
215
+ return this.middlewareGroupsMetaCache;
216
+ }
217
+ async getPermissionsGroupsMeta() {
218
+ if (this.permissionsGroupsMetaCache)
219
+ return this.permissionsGroupsMetaCache;
220
+ const content = await this.readMetaJson('permissions', 'pikku-permissions-groups-meta');
221
+ this.permissionsGroupsMetaCache = content
222
+ ? JSON.parse(content)
223
+ : { definitions: {}, httpGroups: {}, tagGroups: {} };
224
+ return this.permissionsGroupsMetaCache;
225
+ }
226
+ async getAgentsMeta() {
227
+ if (this.agentsMetaCache)
228
+ return this.agentsMetaCache;
229
+ const content = await this.readMetaJson('agent', 'pikku-agent-wirings-meta');
230
+ if (content) {
231
+ const parsed = JSON.parse(content);
232
+ this.agentsMetaCache = parsed.agentsMeta || parsed;
233
+ }
234
+ else {
235
+ this.agentsMetaCache = {};
236
+ }
237
+ return this.agentsMetaCache;
238
+ }
239
+ async getSecretsMeta() {
240
+ if (this.secretsMetaCache)
241
+ return this.secretsMetaCache;
242
+ const content = await this.readFile('secrets/pikku-secrets-meta.gen.json');
243
+ this.secretsMetaCache = content ? JSON.parse(content) : {};
244
+ return this.secretsMetaCache;
245
+ }
246
+ async getCredentialsMeta() {
247
+ if (this.credentialsMetaCache)
248
+ return this.credentialsMetaCache;
249
+ const content = await this.readFile('credentials/pikku-credentials-meta.gen.json');
250
+ this.credentialsMetaCache = content ? JSON.parse(content) : {};
251
+ return this.credentialsMetaCache;
252
+ }
253
+ async getVariablesMeta() {
254
+ if (this.variablesMetaCache)
255
+ return this.variablesMetaCache;
256
+ const content = await this.readFile('variables/pikku-variables-meta.gen.json');
257
+ this.variablesMetaCache = content ? JSON.parse(content) : {};
258
+ return this.variablesMetaCache;
259
+ }
260
+ async getServicesMeta() {
261
+ if (this.servicesMetaCache)
262
+ return this.servicesMetaCache;
263
+ try {
264
+ const files = await this.readDir('services');
265
+ const jsonFiles = files.filter((f) => f.endsWith('.gen.json'));
266
+ const result = {};
267
+ await Promise.all(jsonFiles.map(async (file) => {
268
+ const content = await this.readFile(`services/${file}`);
269
+ if (content) {
270
+ const meta = JSON.parse(content);
271
+ result[meta.name] = meta;
272
+ }
273
+ }));
274
+ this.servicesMetaCache = result;
275
+ return this.servicesMetaCache;
276
+ }
277
+ catch (error) {
278
+ console.error('Error reading Services metadata:', error);
279
+ this.servicesMetaCache = {};
280
+ return this.servicesMetaCache;
281
+ }
282
+ }
283
+ async getSchema(schemaName) {
284
+ if (this.schemaCache.has(schemaName)) {
285
+ return this.schemaCache.get(schemaName);
286
+ }
287
+ if (!/^[a-zA-Z0-9_\-\.]+$/.test(schemaName)) {
288
+ return null;
289
+ }
290
+ try {
291
+ const content = await this.readFile(`schemas/schemas/${schemaName}.schema.json`);
292
+ if (!content)
293
+ return null;
294
+ const schema = JSON.parse(content);
295
+ this.schemaCache.set(schemaName, schema);
296
+ return schema;
297
+ }
298
+ catch (error) {
299
+ console.error(`Error reading schema ${schemaName}:`, error);
300
+ return null;
301
+ }
302
+ }
303
+ async getSchemas(schemaNames) {
304
+ const results = {};
305
+ await Promise.all(schemaNames.map(async (name) => {
306
+ results[name] = await this.getSchema(name);
307
+ }));
308
+ return results;
309
+ }
310
+ }
@@ -1,5 +1,5 @@
1
1
  import type { SerializedError } from '../types/core.types.js';
2
- import type { WorkflowRun, WorkflowRunWire, StepState, WorkflowStatus, WorkflowVersionStatus } from '../wirings/workflow/workflow.types.js';
2
+ import type { WorkflowRun, WorkflowRunWire, WorkflowRunStatus, StepState, WorkflowStatus, WorkflowVersionStatus } from '../wirings/workflow/workflow.types.js';
3
3
  /**
4
4
  * Interface for workflow orchestration
5
5
  * Handles workflow execution, replay, orchestration logic, and run-level state
@@ -7,6 +7,7 @@ import type { WorkflowRun, WorkflowRunWire, StepState, WorkflowStatus, WorkflowV
7
7
  export interface WorkflowService {
8
8
  createRun(workflowName: string, input: any, inline: boolean, graphHash: string, wire: WorkflowRunWire): Promise<string>;
9
9
  getRun(id: string): Promise<WorkflowRun | null>;
10
+ getRunStatus(id: string): Promise<WorkflowRunStatus | null>;
10
11
  getRunHistory(runId: string): Promise<Array<StepState & {
11
12
  stepName: string;
12
13
  }>>;
@@ -20,6 +21,10 @@ export interface WorkflowService {
20
21
  }): Promise<{
21
22
  runId: string;
22
23
  }>;
24
+ runToCompletion<I>(name: string, input: I, rpcService: any, options?: {
25
+ pollIntervalMs?: number;
26
+ wire?: WorkflowRunWire;
27
+ }): Promise<unknown>;
23
28
  runWorkflowJob(runId: string, rpcService: any): Promise<void>;
24
29
  orchestrateWorkflow(runId: string, rpcService: any): Promise<void>;
25
30
  executeWorkflowSleepCompleted(runId: string, stepId: string): Promise<void>;
@@ -482,14 +482,6 @@ export function defineServiceTests(config) {
482
482
  endpoint: 'http://localhost:3000',
483
483
  functions: ['funcA', 'funcB'],
484
484
  });
485
- const infos = await service.findFunction('funcA');
486
- assert.ok(infos.length >= 1);
487
- assert.equal(infos[0].deploymentId, 'deploy-1');
488
- assert.equal(infos[0].endpoint, 'http://localhost:3000');
489
- });
490
- test('findFunction returns empty for unknown function', async () => {
491
- const infos = await service.findFunction('unknown-func');
492
- assert.deepEqual(infos, []);
493
485
  });
494
486
  });
495
487
  }
@@ -24,6 +24,7 @@ import type { AgentRunService } from '../wirings/ai-agent/ai-agent.types.js';
24
24
  import type { PikkuAIMiddlewareHooks } from '../wirings/ai-agent/ai-agent.types.js';
25
25
  import type { WorkflowRunService } from '../wirings/workflow/workflow.types.js';
26
26
  import type { CredentialService } from '../services/credential-service.js';
27
+ import type { MetaService } from '../services/meta-service.js';
27
28
  export type PikkuWiringTypes = 'http' | 'scheduler' | 'trigger' | 'channel' | 'rpc' | 'queue' | 'mcp' | 'cli' | 'workflow' | 'agent' | 'gateway';
28
29
  export interface FunctionServicesMeta {
29
30
  optimized: boolean;
@@ -75,6 +76,7 @@ export type FunctionRuntimeMeta = {
75
76
  remote?: boolean;
76
77
  mcp?: boolean;
77
78
  readonly?: boolean;
79
+ deploy?: 'serverless' | 'server' | 'auto';
78
80
  sessionless?: boolean;
79
81
  version?: number;
80
82
  approvalRequired?: boolean;
@@ -94,6 +96,8 @@ export type FunctionMeta = FunctionRuntimeMeta & Partial<{
94
96
  middleware: MiddlewareMetadata[];
95
97
  permissions: PermissionMetadata[];
96
98
  isDirectFunction: boolean;
99
+ sourceFile: string;
100
+ exportedName: string;
97
101
  } & CommonWireMeta>;
98
102
  export type FunctionsRuntimeMeta = Record<string, FunctionRuntimeMeta>;
99
103
  export type FunctionsMeta = Record<string, FunctionMeta>;
@@ -175,6 +179,8 @@ export interface CoreSingletonServices<Config extends CoreConfig = CoreConfig> {
175
179
  workflowRunService?: WorkflowRunService;
176
180
  /** Credential service for dynamic/managed credentials (OAuth tokens, per-user API keys) */
177
181
  credentialService?: CredentialService;
182
+ /** Meta service for reading .pikku metadata files (filesystem on Node, R2/KV on CF) */
183
+ metaService?: MetaService;
178
184
  }
179
185
  /**
180
186
  * Represents different forms of wire within Pikku and the outside world.
@@ -182,6 +188,8 @@ export interface CoreSingletonServices<Config extends CoreConfig = CoreConfig> {
182
188
  export type PikkuWire<In = unknown, Out = unknown, HasInitialSession extends boolean = false, UserSession extends CoreUserSession = CoreUserSession, TypedRPC extends PikkuRPC = PikkuRPC, IsChannel extends true | null = null, MCPTools extends string | never = never, TypedWorkflow extends PikkuWorkflowWire | never = PikkuWorkflowWire, TriggerOutput = unknown> = Partial<{
183
189
  wireType: PikkuWiringTypes;
184
190
  wireId: string;
191
+ /** Trace ID for distributed tracing — propagated across remote RPC calls via x-trace-id header */
192
+ traceId: string;
185
193
  http: PikkuHTTP<In>;
186
194
  mcp: PikkuMCP<MCPTools>;
187
195
  rpc: TypedRPC;
@@ -216,6 +224,17 @@ export type PikkuWire<In = unknown, Out = unknown, HasInitialSession extends boo
216
224
  * A function that can wrap an wire and be called before or after
217
225
  */
218
226
  export type CorePikkuMiddleware<SingletonServices extends CoreSingletonServices = CoreSingletonServices, UserSession extends CoreUserSession = CoreUserSession> = (services: SingletonServices, wires: PikkuWire<unknown, unknown, false, UserSession>, next: () => Promise<void>) => Promise<void>;
227
+ /**
228
+ * Priority levels for middleware execution order.
229
+ * Lower priority runs first (outermost in the onion model).
230
+ *
231
+ * - `highest` — Runs first (outermost). Use for telemetry, request tracing.
232
+ * - `high` — Runs early. Use for CORS, rate limiting.
233
+ * - `medium` — Default. Use for auth, most user middleware.
234
+ * - `low` — Runs late. Use for post-auth processing.
235
+ * - `lowest` — Runs last (innermost, closest to function). Use for inner telemetry.
236
+ */
237
+ export type MiddlewarePriority = 'highest' | 'high' | 'medium' | 'low' | 'lowest';
219
238
  /**
220
239
  * Configuration object for creating middleware with metadata
221
240
  *
@@ -229,6 +248,8 @@ export type CorePikkuMiddlewareConfig<SingletonServices extends CoreSingletonSer
229
248
  name?: string;
230
249
  /** Optional description of what the middleware does */
231
250
  description?: string;
251
+ /** Execution priority. Lower runs first (outermost). Defaults to 'medium'. */
252
+ priority?: MiddlewarePriority;
232
253
  };
233
254
  /**
234
255
  * A factory function that takes input and returns middleware
@@ -24,7 +24,13 @@
24
24
  * ```
25
25
  */
26
26
  export const pikkuMiddleware = (middleware) => {
27
- return typeof middleware === 'function' ? middleware : middleware.func;
27
+ if (typeof middleware === 'function')
28
+ return middleware;
29
+ const func = middleware.func;
30
+ if (middleware.priority) {
31
+ func.__priority = middleware.priority;
32
+ }
33
+ return func;
28
34
  };
29
35
  /**
30
36
  * Factory function for creating middleware factories
@@ -130,8 +130,6 @@ export interface PikkuPackageState {
130
130
  } | null;
131
131
  /** Cached singleton services for this package */
132
132
  singletonServices: CoreSingletonServices | null;
133
- /** Absolute path to this package's .pikku directory */
134
- metaDir: string | null;
135
133
  /** Credential metadata for this addon package */
136
134
  credentialsMeta: Record<string, {
137
135
  name: string;
@@ -139,5 +137,7 @@ export interface PikkuPackageState {
139
137
  type: string;
140
138
  oauth2?: boolean;
141
139
  }> | null;
140
+ /** Services this addon needs from the parent project */
141
+ requiredParentServices: string[] | null;
142
142
  };
143
143
  }
@@ -1,4 +1,5 @@
1
1
  import { PikkuError } from '../../errors/error-handler.js';
2
+ import { checkAuthPermissions } from '../../permissions.js';
2
3
  import { AIProviderNotConfiguredError } from '../../errors/errors.js';
3
4
  import { pikkuState, getSingletonServices } from '../../pikku-state.js';
4
5
  import { createMiddlewareSessionWireProps } from '../../services/user-session-service.js';
@@ -6,7 +7,6 @@ import { randomUUID } from 'crypto';
6
7
  import { streamAIAgent } from './ai-agent-stream.js';
7
8
  import { runAIAgent } from './ai-agent-runner.js';
8
9
  import { resolveNamespace, ContextAwareRPCService, } from '../../wirings/rpc/rpc-runner.js';
9
- import { buildDynamicWorkflowInstructions, buildWorkflowTools, } from './agent-dynamic-workflow.js';
10
10
  import { resolveMemoryServices, loadContextMessages, trimMessages, } from './ai-agent-memory.js';
11
11
  import { resolveModelConfig } from './ai-agent-model-config.js';
12
12
  export class ToolApprovalRequired extends PikkuError {
@@ -75,6 +75,10 @@ export function getAddonCredentialRequirements(toolNames) {
75
75
  return [...requirements.values()];
76
76
  }
77
77
  export const resolveAgent = (agentName) => {
78
+ if (!agentName) {
79
+ console.error('[resolveAgent] agentName is undefined/null! Stack:', new Error().stack);
80
+ throw new Error('resolveAgent called with undefined agentName');
81
+ }
78
82
  const mainAgent = pikkuState(null, 'agent', 'agents').get(agentName);
79
83
  if (mainAgent) {
80
84
  return { agent: mainAgent, packageName: null, resolvedName: agentName };
@@ -100,10 +104,23 @@ export const resolveAgent = (agentName) => {
100
104
  };
101
105
  export async function buildInstructions(agentName, packageName) {
102
106
  const meta = pikkuState(packageName, 'agent', 'agentsMeta')[agentName];
103
- const rawInstructions = meta?.instructions ?? '';
104
- let instructions = Array.isArray(rawInstructions)
105
- ? rawInstructions.join('\n')
106
- : rawInstructions;
107
+ const parts = [];
108
+ if (meta?.role)
109
+ parts.push(meta.role);
110
+ if (meta?.personality)
111
+ parts.push(meta.personality);
112
+ if (meta?.goal)
113
+ parts.push(meta.goal);
114
+ let instructions = parts.join('\n\n');
115
+ if (meta?.tools?.length) {
116
+ instructions +=
117
+ '\n\nTool usage rules:\n' +
118
+ '- Act immediately with the information given. Do NOT ask clarifying questions unless a required field is truly missing.\n' +
119
+ '- Only use fields defined in your tool schemas. Never mention or ask for fields that do not exist.\n' +
120
+ '- Never fill optional fields with placeholder or zero values. Omit them entirely unless the user provides a real value.\n' +
121
+ '- Never stuff unrelated information into the wrong field.\n' +
122
+ '- Keep responses concise.';
123
+ }
107
124
  if (meta?.agents?.length) {
108
125
  instructions +=
109
126
  '\n\nWhen calling a sub-agent, provide a short session name that describes the task. ' +
@@ -111,9 +128,6 @@ export async function buildInstructions(agentName, packageName) {
111
128
  'Use a new session name for a new independent task. ' +
112
129
  'When a request involves multiple actions for the same domain, combine them into a single sub-agent call rather than making separate calls.';
113
130
  }
114
- if (meta?.dynamicWorkflows && meta.tools?.length) {
115
- instructions += buildDynamicWorkflowInstructions(meta.tools, meta.dynamicWorkflows);
116
- }
117
131
  return instructions;
118
132
  }
119
133
  export function createScopedChannel(parent, agentName, session) {
@@ -165,6 +179,10 @@ export async function buildToolDefs(params, agentSessionMap, resourceId, agentNa
165
179
  const meta = pikkuState(packageName, 'agent', 'agentsMeta')[agentName];
166
180
  if (!meta)
167
181
  return { tools, missingRpcs };
182
+ // Get session for permission filtering
183
+ const session = params.sessionService
184
+ ? await params.sessionService.get()
185
+ : null;
168
186
  const metaTools = meta.tools;
169
187
  const metaAgents = meta.agents;
170
188
  if (metaTools?.length) {
@@ -196,6 +214,14 @@ export async function buildToolDefs(params, agentSessionMap, resourceId, agentNa
196
214
  missingRpcs.push(toolName);
197
215
  continue;
198
216
  }
217
+ // Filter out tools the user doesn't have auth for
218
+ if (fnMeta.permissions?.length) {
219
+ if (!session)
220
+ continue;
221
+ const allowed = await checkAuthPermissions(fnMeta.permissions, session, singletonServices, resolvedPkg);
222
+ if (!allowed)
223
+ continue;
224
+ }
199
225
  const inputSchemaName = fnMeta?.inputSchemaName;
200
226
  let inputSchema = inputSchemaName
201
227
  ? schemas.get(inputSchemaName)
@@ -251,6 +277,14 @@ export async function buildToolDefs(params, agentSessionMap, resourceId, agentNa
251
277
  singletonServices.logger.warn(`Sub-agent '${subAgentName}' not found in agent registry`);
252
278
  continue;
253
279
  }
280
+ // Filter out sub-agents the user doesn't have auth for
281
+ if (subMeta.permissions?.length) {
282
+ if (!session)
283
+ continue;
284
+ const allowed = await checkAuthPermissions(subMeta.permissions, session, singletonServices);
285
+ if (!allowed)
286
+ continue;
287
+ }
254
288
  tools.push({
255
289
  name: subAgentName,
256
290
  description: subMeta.description,
@@ -339,10 +373,6 @@ export async function buildToolDefs(params, agentSessionMap, resourceId, agentNa
339
373
  });
340
374
  }
341
375
  }
342
- if (meta.dynamicWorkflows) {
343
- const workflowTools = buildWorkflowTools(agentName, packageName, meta.tools ?? [], meta.dynamicWorkflows, streamContext, params.sessionService);
344
- tools.push(...workflowTools);
345
- }
346
376
  const hasToolHooks = aiMiddlewares?.some((mw) => mw.beforeToolCall || mw.afterToolCall);
347
377
  if (hasToolHooks) {
348
378
  for (const tool of tools) {
@@ -405,15 +435,6 @@ export async function prepareAgentRun(agentName, input, params, agentSessionMap,
405
435
  if (!agentRunner) {
406
436
  throw new AIProviderNotConfiguredError();
407
437
  }
408
- if (agent.dynamicWorkflows && singletonServices.workflowService) {
409
- const persisted = await singletonServices.workflowService.getAIGeneratedWorkflows(resolvedName);
410
- const allMeta = pikkuState(null, 'workflows', 'meta');
411
- for (const wf of persisted) {
412
- if (!allMeta[wf.workflowName]) {
413
- allMeta[wf.workflowName] = wf.graph;
414
- }
415
- }
416
- }
417
438
  const { storage } = resolveMemoryServices(agent, singletonServices);
418
439
  const memoryConfig = agent.memory;
419
440
  const threadId = input.threadId;
@@ -1,10 +1,10 @@
1
1
  import { pikkuState } from '../../pikku-state.js';
2
- import { PikkuMissingMetaError } from '../../errors/errors.js';
3
2
  export const addAIAgent = (agentName, agent, packageName = null) => {
4
3
  const agentsMeta = pikkuState(packageName, 'agent', 'agentsMeta');
5
4
  const agentMeta = agentsMeta[agentName];
6
5
  if (!agentMeta) {
7
- throw new PikkuMissingMetaError(`Missing generated metadata for AI agent '${agentName}'`);
6
+ console.warn(`[pikku] Skipping AI agent '${agentName}' — metadata not found. Consider moving this wiring to its own file.`);
7
+ return;
8
8
  }
9
9
  const agents = pikkuState(packageName, 'agent', 'agents');
10
10
  if (agents.has(agentName)) {
@@ -4,6 +4,21 @@ import { checkForApprovals, appendStepMessages } from './ai-agent-stream.js';
4
4
  import { pikkuState, getSingletonServices } from '../../pikku-state.js';
5
5
  import { resolveModelConfig } from './ai-agent-model-config.js';
6
6
  import { AIProviderNotConfiguredError } from '../../errors/errors.js';
7
+ function stripNulls(obj) {
8
+ if (obj === null)
9
+ return undefined;
10
+ if (Array.isArray(obj))
11
+ return obj.map(stripNulls);
12
+ if (typeof obj !== 'object')
13
+ return obj;
14
+ const result = {};
15
+ for (const [key, value] of Object.entries(obj)) {
16
+ if (value !== null) {
17
+ result[key] = stripNulls(value);
18
+ }
19
+ }
20
+ return result;
21
+ }
7
22
  import { randomUUID } from 'crypto';
8
23
  export async function runAIAgent(agentName, input, params, agentSessionMap) {
9
24
  const sessionMap = agentSessionMap ?? new Map();
@@ -287,9 +302,11 @@ export async function resumeAIAgentSync(runId, approvals, params, expectedAgentN
287
302
  if (!matchingTool) {
288
303
  throw new Error(`Tool "${pending.toolName}" not found in agent definition`);
289
304
  }
290
- const toolArgs = typeof pending.args === 'string'
305
+ const rawArgs = typeof pending.args === 'string'
291
306
  ? JSON.parse(pending.args)
292
307
  : pending.args;
308
+ // Strip null values recursively — LLMs send null for optional fields but Zod expects undefined
309
+ const toolArgs = stripNulls(rawArgs) ?? {};
293
310
  try {
294
311
  const toolResult = await matchingTool.execute(toolArgs);
295
312
  resultStr =
@@ -733,6 +733,7 @@ async function continueAfterToolResult(run, agent, packageName, resolvedName, st
733
733
  const streamContext = { channel, options };
734
734
  const resumeTools = (await buildToolDefs(params, new Map(), run.resourceId, resolvedName, packageName, streamContext, aiMiddlewares)).tools;
735
735
  const resolved = resolveModelConfig(resolvedName, agent);
736
+ console.log('[DEBUG resume] resolvedName:', resolvedName, 'agent.model:', agent.model, 'resolved.model:', resolved.model);
736
737
  const maxSteps = resolved.maxSteps ?? 10;
737
738
  const runnerParams = {
738
739
  model: resolved.model,
@@ -183,7 +183,9 @@ export type CoreAIAgent<PikkuPermission = CorePikkuPermission<any, any>, PikkuMi
183
183
  description: string;
184
184
  summary?: string;
185
185
  errors?: string[];
186
- instructions: string | string[];
186
+ role?: string;
187
+ personality?: string;
188
+ goal: string;
187
189
  model: string;
188
190
  temperature?: number;
189
191
  tools?: unknown[];
@@ -192,7 +194,6 @@ export type CoreAIAgent<PikkuPermission = CorePikkuPermission<any, any>, PikkuMi
192
194
  memory?: AIAgentMemoryConfig;
193
195
  maxSteps?: number;
194
196
  toolChoice?: 'auto' | 'required' | 'none';
195
- dynamicWorkflows?: 'read' | 'always' | 'ask';
196
197
  input?: unknown;
197
198
  output?: unknown;
198
199
  tags?: string[];
@@ -382,5 +383,6 @@ export type AIAgentMeta = Record<string, Omit<CoreAIAgent, 'input' | 'output' |
382
383
  channelMiddleware?: MiddlewareMetadata[];
383
384
  aiMiddleware?: MiddlewareMetadata[];
384
385
  permissions?: PermissionMetadata[];
385
- dynamicWorkflows?: 'read' | 'always' | 'ask';
386
+ sourceFile?: string;
387
+ exportedName?: string;
386
388
  }>;