@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
@@ -3,14 +3,17 @@ import type { CoreAIAgent, AIAgentMemoryConfig, AIAgentInput, AIMessage } from '
3
3
  import type { AIStorageService } from '../../services/ai-storage-service.js';
4
4
  import type { Logger } from '../../services/logger.js';
5
5
  import type { SchemaService } from '../../services/schema-service.js';
6
+ import type { PikkuAIMiddlewareHooks } from './ai-agent.types.js';
6
7
  export declare function resolveMemoryServices(agent: CoreAIAgent, singletonServices: CoreSingletonServices): {
7
8
  storage: AIStorageService | undefined;
8
9
  };
10
+ export declare function isWorkingMemoryEnabled(memoryConfig: AIAgentMemoryConfig | undefined, storage: AIStorageService | undefined): boolean;
9
11
  export declare function deepMergeWorkingMemory(existing: Record<string, unknown>, updates: Record<string, unknown>): Record<string, unknown>;
10
12
  export declare function buildWorkingMemoryPrompt(currentState: Record<string, unknown> | null, jsonSchema?: Record<string, unknown>): string;
11
13
  export declare function loadContextMessages(memoryConfig: AIAgentMemoryConfig | undefined, storage: AIStorageService | undefined, input: AIAgentInput, workingMemoryJsonSchema?: Record<string, unknown>): Promise<AIMessage[]>;
12
14
  export declare function saveMessages(storage: AIStorageService | undefined, threadId: string, resourceId: string, memoryConfig: AIAgentMemoryConfig | undefined, userMessage: AIMessage | null | undefined, result: {
13
15
  text: string;
16
+ uiSpec?: unknown;
14
17
  steps: {
15
18
  toolCalls?: {
16
19
  name: string;
@@ -18,12 +21,28 @@ export declare function saveMessages(storage: AIStorageService | undefined, thre
18
21
  result: string;
19
22
  }[];
20
23
  }[];
21
- }, options?: {
22
- workingMemoryJsonSchema?: Record<string, unknown>;
23
- workingMemorySchemaName?: string | null;
24
- logger?: Logger;
25
- schemaService?: SchemaService;
26
24
  }): Promise<string>;
27
25
  export declare function trimMessages(messages: AIMessage[], maxTokenBudget?: number): AIMessage[];
28
26
  export declare function parseWorkingMemory(text: string): Record<string, unknown> | null;
29
27
  export declare function stripWorkingMemoryTags(text: string): string;
28
+ export declare function extractWorkingMemory(text: string): {
29
+ workingMemory: Record<string, unknown> | null;
30
+ cleanedText: string;
31
+ };
32
+ export declare function stripWorkingMemoryForStreaming(text: string): string;
33
+ export declare function createWorkingMemoryMiddleware(options: {
34
+ storage?: AIStorageService;
35
+ threadId: string;
36
+ workingMemorySchemaName?: string | null;
37
+ logger?: Logger;
38
+ schemaService?: SchemaService;
39
+ }): PikkuAIMiddlewareHooks<{
40
+ rawText?: string;
41
+ emittedVisibleText?: string;
42
+ }>;
43
+ export declare function getWorkingMemoryMiddleware(memoryConfig: AIAgentMemoryConfig | undefined, storage: AIStorageService | undefined, options: {
44
+ threadId: string;
45
+ workingMemorySchemaName?: string | null;
46
+ logger?: Logger;
47
+ schemaService?: SchemaService;
48
+ }): PikkuAIMiddlewareHooks[];
@@ -6,9 +6,15 @@ export function resolveMemoryServices(agent, singletonServices) {
6
6
  : singletonServices.aiStorage;
7
7
  return { storage };
8
8
  }
9
+ export function isWorkingMemoryEnabled(memoryConfig, storage) {
10
+ return !!memoryConfig?.workingMemory && !!storage;
11
+ }
9
12
  export function deepMergeWorkingMemory(existing, updates) {
10
13
  const result = { ...existing };
11
14
  for (const key of Object.keys(updates)) {
15
+ if (key === '__proto__' || key === 'constructor' || key === 'prototype') {
16
+ continue;
17
+ }
12
18
  const value = updates[key];
13
19
  if (value === null) {
14
20
  delete result[key];
@@ -46,13 +52,18 @@ export function buildWorkingMemoryPrompt(currentState, jsonSchema) {
46
52
  parts.push('Current working memory: (empty)');
47
53
  }
48
54
  parts.push('When you learn new information, output a partial JSON update in <working_memory> tags. ' +
49
- 'Only include changed fields. Set a field to null to delete it.');
55
+ 'Only include durable facts you have actually derived or the user has confirmed. ' +
56
+ 'Do not output templates, placeholders, or narration. ' +
57
+ 'Only include changed fields. Leave unknown fields untouched. Set a field to null to delete it.');
50
58
  return parts.join('\n\n');
51
59
  }
52
60
  export async function loadContextMessages(memoryConfig, storage, input, workingMemoryJsonSchema) {
53
61
  const contextMessages = [];
54
- if (memoryConfig?.workingMemory && storage) {
55
- const workingMem = await storage.getWorkingMemory(input.threadId, 'thread');
62
+ const workingMemoryStorage = isWorkingMemoryEnabled(memoryConfig, storage)
63
+ ? storage
64
+ : undefined;
65
+ if (workingMemoryStorage) {
66
+ const workingMem = await workingMemoryStorage.getWorkingMemory(input.threadId, 'thread');
56
67
  const prompt = buildWorkingMemoryPrompt(workingMem, workingMemoryJsonSchema);
57
68
  contextMessages.push({
58
69
  id: `wm-${randomUUID()}`,
@@ -63,8 +74,10 @@ export async function loadContextMessages(memoryConfig, storage, input, workingM
63
74
  }
64
75
  return contextMessages;
65
76
  }
66
- export async function saveMessages(storage, threadId, resourceId, memoryConfig, userMessage, result, options) {
67
- let responseText = result.text;
77
+ export async function saveMessages(storage, threadId, resourceId, memoryConfig, userMessage, result) {
78
+ const responseText = memoryConfig?.workingMemory
79
+ ? extractWorkingMemory(result.text).cleanedText
80
+ : result.text;
68
81
  if (storage) {
69
82
  const newMessages = userMessage ? [userMessage] : [];
70
83
  for (const step of result.steps) {
@@ -92,30 +105,21 @@ export async function saveMessages(storage, threadId, resourceId, memoryConfig,
92
105
  });
93
106
  }
94
107
  }
108
+ const assistantContent = result.uiSpec != null
109
+ ? [
110
+ ...(responseText
111
+ ? [{ type: 'text', text: responseText }]
112
+ : []),
113
+ { type: 'generative-ui', spec: result.uiSpec },
114
+ ]
115
+ : responseText;
95
116
  newMessages.push({
96
117
  id: randomUUID(),
97
118
  role: 'assistant',
98
- content: responseText,
119
+ content: assistantContent || undefined,
99
120
  createdAt: new Date(),
100
121
  });
101
122
  await storage.saveMessages(threadId, newMessages);
102
- if (memoryConfig?.workingMemory) {
103
- const parsed = parseWorkingMemory(responseText);
104
- if (parsed) {
105
- const existing = (await storage.getWorkingMemory(threadId, 'thread')) ?? {};
106
- const merged = deepMergeWorkingMemory(existing, parsed);
107
- if (options?.schemaService && options?.workingMemorySchemaName) {
108
- try {
109
- await options.schemaService.validateSchema(options.workingMemorySchemaName, merged);
110
- }
111
- catch (err) {
112
- options.logger?.warn(`Working memory validation failed: ${err instanceof Error ? err.message : String(err)}`);
113
- }
114
- }
115
- await storage.saveWorkingMemory(threadId, 'thread', merged);
116
- responseText = stripWorkingMemoryTags(responseText);
117
- }
118
- }
119
123
  }
120
124
  return responseText;
121
125
  }
@@ -209,3 +213,104 @@ export function parseWorkingMemory(text) {
209
213
  export function stripWorkingMemoryTags(text) {
210
214
  return text.replace(/<working_memory>[\s\S]*?<\/working_memory>/g, '').trim();
211
215
  }
216
+ export function extractWorkingMemory(text) {
217
+ const workingMemory = parseWorkingMemory(text);
218
+ return {
219
+ workingMemory,
220
+ cleanedText: workingMemory ? stripWorkingMemoryTags(text) : text,
221
+ };
222
+ }
223
+ function getPendingWorkingMemoryPrefixLength(text) {
224
+ const prefixes = ['<working_memory>', '</working_memory>'];
225
+ const lastLt = text.lastIndexOf('<');
226
+ if (lastLt === -1)
227
+ return 0;
228
+ const suffix = text.slice(lastLt);
229
+ for (const prefix of prefixes) {
230
+ if (prefix.startsWith(suffix)) {
231
+ return suffix.length;
232
+ }
233
+ }
234
+ return 0;
235
+ }
236
+ export function stripWorkingMemoryForStreaming(text) {
237
+ let output = '';
238
+ let cursor = 0;
239
+ while (cursor < text.length) {
240
+ const openIndex = text.indexOf('<working_memory>', cursor);
241
+ if (openIndex === -1) {
242
+ const tail = text.slice(cursor);
243
+ const pendingPrefixLength = getPendingWorkingMemoryPrefixLength(tail);
244
+ output +=
245
+ pendingPrefixLength > 0 ? tail.slice(0, -pendingPrefixLength) : tail;
246
+ return output;
247
+ }
248
+ output += text.slice(cursor, openIndex);
249
+ const closeIndex = text.indexOf('</working_memory>', openIndex);
250
+ if (closeIndex === -1) {
251
+ return output;
252
+ }
253
+ cursor = closeIndex + '</working_memory>'.length;
254
+ }
255
+ return output;
256
+ }
257
+ export function createWorkingMemoryMiddleware(options) {
258
+ return {
259
+ modifyOutputStream: async (_services, { event, state }) => {
260
+ if (event.type !== 'text-delta')
261
+ return event;
262
+ const rawText = `${state.rawText ?? ''}${event.text}`;
263
+ state.rawText = rawText;
264
+ const visibleText = stripWorkingMemoryForStreaming(rawText);
265
+ const emittedVisibleText = state.emittedVisibleText ?? '';
266
+ if (!visibleText.startsWith(emittedVisibleText)) {
267
+ state.emittedVisibleText = visibleText;
268
+ return { ...event, text: visibleText };
269
+ }
270
+ const delta = visibleText.slice(emittedVisibleText.length);
271
+ state.emittedVisibleText = visibleText;
272
+ if (!delta)
273
+ return null;
274
+ return { ...event, text: delta };
275
+ },
276
+ modifyOutput: async (_services, { text, messages, usage }) => {
277
+ const { workingMemory, cleanedText } = extractWorkingMemory(text);
278
+ if (workingMemory && options.storage) {
279
+ const existing = (await options.storage.getWorkingMemory(options.threadId, 'thread')) ?? {};
280
+ const merged = deepMergeWorkingMemory(existing, workingMemory);
281
+ let valid = true;
282
+ if (options.schemaService && options.workingMemorySchemaName) {
283
+ try {
284
+ await options.schemaService.validateSchema(options.workingMemorySchemaName, merged);
285
+ }
286
+ catch (err) {
287
+ valid = false;
288
+ options.logger?.warn(`Working memory validation failed: ${err instanceof Error ? err.message : String(err)}`);
289
+ }
290
+ }
291
+ // Only persist when the merged value passes schema validation —
292
+ // saving invalid data would poison subsequent getWorkingMemory reads.
293
+ if (valid) {
294
+ await options.storage.saveWorkingMemory(options.threadId, 'thread', merged);
295
+ }
296
+ }
297
+ return {
298
+ text: cleanedText,
299
+ messages,
300
+ };
301
+ },
302
+ };
303
+ }
304
+ export function getWorkingMemoryMiddleware(memoryConfig, storage, options) {
305
+ if (!isWorkingMemoryEnabled(memoryConfig, storage))
306
+ return [];
307
+ return [
308
+ createWorkingMemoryMiddleware({
309
+ storage,
310
+ threadId: options.threadId,
311
+ workingMemorySchemaName: options.workingMemorySchemaName,
312
+ logger: options.logger,
313
+ schemaService: options.schemaService,
314
+ }),
315
+ ];
316
+ }
@@ -1,4 +1,13 @@
1
- export declare function resolveModelConfig(agentName: string, agent: {
1
+ /**
2
+ * Resolves the effective model/temperature/maxSteps for an agent at runtime.
3
+ *
4
+ * Models are declared per-agent using the provider-qualified `provider/model`
5
+ * form (e.g. `openai/gpt-5-mini`); there is no config-level alias map. This
6
+ * function remains the single merge point for request-time overrides (see
7
+ * ai-agent-prepare's `input.model`), so callers stay decoupled from how the
8
+ * effective config is assembled.
9
+ */
10
+ export declare function resolveModelConfig(_agentName: string, agent: {
2
11
  model: string;
3
12
  temperature?: number;
4
13
  maxSteps?: number;
@@ -1,36 +1,16 @@
1
- import { pikkuState } from '../../pikku-state.js';
2
- export function resolveModelConfig(agentName, agent) {
3
- let config = {};
4
- try {
5
- config = pikkuState(null, 'models', 'config') ?? {};
6
- }
7
- catch { }
8
- const models = config.models ?? {};
9
- const defaults = config.agentDefaults;
10
- const agentOverride = config.agentOverrides?.[agentName];
11
- let rawModel = agentOverride?.model ?? agent.model;
12
- let aliasTemperature;
13
- let aliasMaxSteps;
14
- if (!rawModel.includes('/')) {
15
- const entry = models[rawModel];
16
- if (!entry)
17
- throw new Error(`Unknown model alias '${rawModel}'.`);
18
- if (typeof entry === 'string') {
19
- rawModel = entry;
20
- }
21
- else {
22
- rawModel = entry.model;
23
- aliasTemperature = entry.temperature;
24
- aliasMaxSteps = entry.maxSteps;
25
- }
26
- }
27
- const temperature = agentOverride?.temperature ??
28
- aliasTemperature ??
29
- defaults?.temperature ??
30
- agent.temperature;
31
- const maxSteps = agentOverride?.maxSteps ??
32
- aliasMaxSteps ??
33
- defaults?.maxSteps ??
34
- agent.maxSteps;
35
- return { model: rawModel, temperature, maxSteps };
1
+ /**
2
+ * Resolves the effective model/temperature/maxSteps for an agent at runtime.
3
+ *
4
+ * Models are declared per-agent using the provider-qualified `provider/model`
5
+ * form (e.g. `openai/gpt-5-mini`); there is no config-level alias map. This
6
+ * function remains the single merge point for request-time overrides (see
7
+ * ai-agent-prepare's `input.model`), so callers stay decoupled from how the
8
+ * effective config is assembled.
9
+ */
10
+ export function resolveModelConfig(_agentName, agent) {
11
+ return {
12
+ model: agent.model,
13
+ temperature: agent.temperature,
14
+ maxSteps: agent.maxSteps,
15
+ };
36
16
  }
@@ -169,6 +169,9 @@ export function createScopedChannel(parent, agentName, session) {
169
169
  parent.send(event);
170
170
  }
171
171
  },
172
+ setState: (s) => parent.setState(s),
173
+ getState: () => parent.getState(),
174
+ clearState: () => parent.clearState(),
172
175
  };
173
176
  }
174
177
  export async function buildToolDefs(params, agentSessionMap, resourceId, agentName, packageName, streamContext, aiMiddlewares, agentMode) {
@@ -254,7 +257,7 @@ export async function buildToolDefs(params, agentSessionMap, resourceId, agentNa
254
257
  }
255
258
  }
256
259
  tools.push({
257
- name: toolName.replaceAll(':', '__'),
260
+ name: toolName.replaceAll('@', '_').replaceAll(':', '__'),
258
261
  description: fnMeta?.description || fnMeta?.title || toolName,
259
262
  inputSchema,
260
263
  needsApproval: needsApproval || undefined,
@@ -1,4 +1,4 @@
1
- import { saveMessages, resolveMemoryServices, loadContextMessages, trimMessages, } from './ai-agent-memory.js';
1
+ import { saveMessages, resolveMemoryServices, loadContextMessages, trimMessages, getWorkingMemoryMiddleware, } from './ai-agent-memory.js';
2
2
  import { prepareAgentRun, resolveAgent, buildInstructions, buildToolDefs, } from './ai-agent-prepare.js';
3
3
  import { checkForApprovals, appendStepMessages } from './ai-agent-stream.js';
4
4
  import { pikkuState, getSingletonServices } from '../../pikku-state.js';
@@ -20,9 +20,19 @@ function stripNulls(obj) {
20
20
  return result;
21
21
  }
22
22
  import { randomUUID } from 'crypto';
23
+ function extractStructuredAssistantOutput(object) {
24
+ if (!object || typeof object !== 'object') {
25
+ return { text: null, uiSpec: null };
26
+ }
27
+ const record = object;
28
+ return {
29
+ text: typeof record.text === 'string' ? record.text : null,
30
+ uiSpec: record.ui ?? null,
31
+ };
32
+ }
23
33
  export async function runAIAgent(agentName, input, params, agentSessionMap) {
24
34
  const sessionMap = agentSessionMap ?? new Map();
25
- const { agent, agentRunner, storage, memoryConfig, threadId, userMessage, runnerParams, maxSteps, missingRpcs, workingMemoryJsonSchema, workingMemorySchemaName, } = await prepareAgentRun(agentName, input, params, sessionMap);
35
+ const { agent, agentRunner, storage, memoryConfig, threadId, userMessage, runnerParams, maxSteps, missingRpcs, workingMemorySchemaName, } = await prepareAgentRun(agentName, input, params, sessionMap);
26
36
  const singletonServices = getSingletonServices();
27
37
  const { aiRunState } = singletonServices;
28
38
  if (!aiRunState) {
@@ -48,7 +58,15 @@ export async function runAIAgent(agentName, input, params, agentSessionMap) {
48
58
  usage: { inputTokens: 0, outputTokens: 0 },
49
59
  };
50
60
  }
51
- const aiMiddlewares = agent.aiMiddleware ?? [];
61
+ const aiMiddlewares = [
62
+ ...getWorkingMemoryMiddleware(memoryConfig, storage, {
63
+ threadId,
64
+ workingMemorySchemaName,
65
+ logger: singletonServices.logger,
66
+ schemaService: singletonServices.schema,
67
+ }),
68
+ ...(agent.aiMiddleware ?? []),
69
+ ];
52
70
  let modifiedMessages = runnerParams.messages;
53
71
  let modifiedInstructions = runnerParams.instructions;
54
72
  for (const mw of aiMiddlewares) {
@@ -152,12 +170,7 @@ export async function runAIAgent(agentName, input, params, agentSessionMap) {
152
170
  args: a.args,
153
171
  });
154
172
  const completedStepsForSave = accumulatedSteps.slice(0, -1);
155
- await saveMessages(storage, threadId, input.resourceId, memoryConfig, userMessage, { text: '', steps: completedStepsForSave }, {
156
- workingMemoryJsonSchema,
157
- workingMemorySchemaName,
158
- logger: singletonServices.logger,
159
- schemaService: singletonServices.schema,
160
- });
173
+ await saveMessages(storage, threadId, input.resourceId, memoryConfig, userMessage, { text: '', steps: completedStepsForSave });
161
174
  if (storage) {
162
175
  await storage.saveMessages(threadId, [
163
176
  {
@@ -197,19 +210,14 @@ export async function runAIAgent(agentName, input, params, agentSessionMap) {
197
210
  }
198
211
  appendStepMessages(runnerParams, stepResult);
199
212
  }
200
- const finalText = lastStepResult?.text ?? '';
201
213
  const finalObject = lastStepResult?.object;
214
+ const structuredOutput = extractStructuredAssistantOutput(finalObject);
215
+ const finalText = structuredOutput.text ?? lastStepResult?.text ?? '';
202
216
  const result = {
203
217
  text: finalText,
204
218
  steps: accumulatedSteps,
205
219
  };
206
- const responseText = await saveMessages(storage, threadId, input.resourceId, memoryConfig, userMessage, result, {
207
- workingMemoryJsonSchema,
208
- workingMemorySchemaName,
209
- logger: singletonServices.logger,
210
- schemaService: singletonServices.schema,
211
- });
212
- let outputText = responseText;
220
+ let outputText = finalText;
213
221
  let outputMessages = runnerParams.messages;
214
222
  for (let i = aiMiddlewares.length - 1; i >= 0; i--) {
215
223
  const mw = aiMiddlewares[i];
@@ -223,6 +231,11 @@ export async function runAIAgent(agentName, input, params, agentSessionMap) {
223
231
  outputMessages = modResult.messages;
224
232
  }
225
233
  }
234
+ await saveMessages(storage, threadId, input.resourceId, memoryConfig, userMessage, {
235
+ ...result,
236
+ text: outputText,
237
+ uiSpec: structuredOutput.uiSpec,
238
+ });
226
239
  await aiRunState.updateRun(runId, {
227
240
  status: 'completed',
228
241
  usage: { ...totalUsage, model: agent.model },
@@ -369,7 +382,15 @@ async function continueAfterToolResultSync(run, agent, packageName, resolvedName
369
382
  const allMessages = [...contextMessages, ...messages];
370
383
  const trimmedMessages = trimMessages(allMessages);
371
384
  const instructions = await buildInstructions(resolvedName, packageName);
372
- const aiMiddlewares = agent.aiMiddleware ?? [];
385
+ const aiMiddlewares = [
386
+ ...getWorkingMemoryMiddleware(memoryConfig, storage, {
387
+ threadId: run.threadId,
388
+ workingMemorySchemaName,
389
+ logger: singletonServices.logger,
390
+ schemaService: singletonServices.schema,
391
+ }),
392
+ ...(agent.aiMiddleware ?? []),
393
+ ];
373
394
  let modifiedMessages = trimmedMessages;
374
395
  let modifiedInstructions = instructions;
375
396
  for (const mw of aiMiddlewares) {
@@ -463,12 +484,7 @@ async function continueAfterToolResultSync(run, agent, packageName, resolvedName
463
484
  });
464
485
  const completedSteps = accumulatedSteps.slice(0, -1);
465
486
  if (completedSteps.length > 0) {
466
- await saveMessages(storage, run.threadId, run.resourceId, memoryConfig, null, { text: '', steps: completedSteps }, {
467
- workingMemoryJsonSchema,
468
- workingMemorySchemaName,
469
- logger: singletonServices.logger,
470
- schemaService: singletonServices.schema,
471
- });
487
+ await saveMessages(storage, run.threadId, run.resourceId, memoryConfig, null, { text: '', steps: completedSteps });
472
488
  }
473
489
  if (storage) {
474
490
  await storage.saveMessages(run.threadId, [
@@ -515,13 +531,7 @@ async function continueAfterToolResultSync(run, agent, packageName, resolvedName
515
531
  text: finalText,
516
532
  steps: accumulatedSteps,
517
533
  };
518
- const responseText = await saveMessages(storage, run.threadId, run.resourceId, memoryConfig, null, result, {
519
- workingMemoryJsonSchema,
520
- workingMemorySchemaName,
521
- logger: singletonServices.logger,
522
- schemaService: singletonServices.schema,
523
- });
524
- let outputText = responseText;
534
+ let outputText = finalText;
525
535
  let outputMessages = runnerParams.messages;
526
536
  for (let i = aiMiddlewares.length - 1; i >= 0; i--) {
527
537
  const mw = aiMiddlewares[i];
@@ -535,6 +545,10 @@ async function continueAfterToolResultSync(run, agent, packageName, resolvedName
535
545
  outputMessages = modResult.messages;
536
546
  }
537
547
  }
548
+ await saveMessages(storage, run.threadId, run.resourceId, memoryConfig, null, {
549
+ ...result,
550
+ text: outputText,
551
+ });
538
552
  await aiRunState.updateRun(run.runId, {
539
553
  status: 'completed',
540
554
  usage: { ...totalUsage, model: agent.model },