@pikku/core 0.12.20 → 0.12.22
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.
- package/CHANGELOG.md +17 -0
- package/dist/dev/hot-reload.js +20 -6
- package/dist/errors/error-handler.d.ts +1 -1
- package/dist/errors/error-handler.js +2 -2
- package/dist/function/functions.types.d.ts +3 -2
- package/dist/handle-error.js +3 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/middleware/index.d.ts +2 -2
- package/dist/middleware/index.js +2 -2
- package/dist/middleware/telemetry.d.ts +2 -2
- package/dist/middleware/telemetry.js +2 -2
- package/dist/middleware-runner.d.ts +15 -27
- package/dist/middleware-runner.js +25 -30
- package/dist/permissions.d.ts +15 -22
- package/dist/permissions.js +30 -25
- package/dist/pikku-request.js +1 -1
- package/dist/pikku-state.js +2 -3
- package/dist/services/ai-agent-runner-service.d.ts +1 -0
- package/dist/services/in-memory-queue-service.js +1 -1
- package/dist/services/in-memory-workflow-service.d.ts +15 -13
- package/dist/services/in-memory-workflow-service.js +31 -13
- package/dist/services/local-content.js +8 -1
- package/dist/services/user-session-service.d.ts +1 -1
- package/dist/testing/service-tests.js +24 -0
- package/dist/types/core.types.d.ts +4 -0
- package/dist/types/state.types.d.ts +2 -18
- package/dist/wirings/ai-agent/ai-agent-memory.d.ts +24 -5
- package/dist/wirings/ai-agent/ai-agent-memory.js +128 -23
- package/dist/wirings/ai-agent/ai-agent-model-config.d.ts +10 -1
- package/dist/wirings/ai-agent/ai-agent-model-config.js +15 -35
- package/dist/wirings/ai-agent/ai-agent-prepare.js +4 -1
- package/dist/wirings/ai-agent/ai-agent-runner.js +45 -31
- package/dist/wirings/ai-agent/ai-agent-stream.js +63 -34
- package/dist/wirings/ai-agent/ai-agent.types.d.ts +20 -0
- package/dist/wirings/channel/channel-handler.js +1 -1
- package/dist/wirings/channel/channel-store.d.ts +13 -0
- package/dist/wirings/channel/channel.types.d.ts +3 -0
- package/dist/wirings/channel/local/local-channel-runner.js +23 -5
- package/dist/wirings/channel/pikku-abstract-channel-handler.js +8 -0
- package/dist/wirings/channel/serverless/serverless-channel-runner.js +9 -0
- package/dist/wirings/cli/cli-runner.js +24 -5
- package/dist/wirings/cli/command-parser.js +19 -4
- package/dist/wirings/http/http-runner.js +72 -36
- package/dist/wirings/http/http.types.d.ts +1 -0
- package/dist/wirings/http/pikku-fetch-http-request.js +3 -1
- package/dist/wirings/http/web-request.js +32 -0
- package/dist/wirings/rpc/rpc-runner.js +13 -3
- package/dist/wirings/workflow/graph/graph-node.d.ts +8 -0
- package/dist/wirings/workflow/graph/graph-node.js +4 -0
- package/dist/wirings/workflow/graph/graph-runner.js +12 -10
- package/dist/wirings/workflow/graph/workflow-graph.types.d.ts +4 -0
- package/dist/wirings/workflow/index.d.ts +1 -1
- package/dist/wirings/workflow/pikku-workflow-service.d.ts +81 -16
- package/dist/wirings/workflow/pikku-workflow-service.js +266 -45
- package/dist/wirings/workflow/workflow.types.d.ts +34 -0
- package/package.json +1 -1
- package/run-tests.sh +4 -1
- package/src/dev/hot-reload.test.ts +62 -11
- package/src/dev/hot-reload.ts +28 -7
- package/src/errors/error-handler.ts +8 -2
- package/src/errors/error.test.ts +2 -0
- package/src/function/function-runner.test.ts +500 -10
- package/src/function/functions.types.ts +3 -2
- package/src/handle-error.test.ts +1 -1
- package/src/handle-error.ts +4 -1
- package/src/index.ts +12 -2
- package/src/middleware/index.ts +11 -2
- package/src/middleware/telemetry.ts +2 -2
- package/src/middleware-runner.test.ts +16 -16
- package/src/middleware-runner.ts +42 -30
- package/src/permissions.test.ts +27 -24
- package/src/permissions.ts +41 -25
- package/src/pikku-request.test.ts +35 -0
- package/src/pikku-request.ts +1 -1
- package/src/pikku-state.ts +2 -3
- package/src/run-tests-script.test.ts +18 -0
- package/src/services/ai-agent-runner-service.ts +1 -0
- package/src/services/in-memory-queue-service.ts +1 -1
- package/src/services/in-memory-session-store.ts +1 -2
- package/src/services/in-memory-workflow-service.test.ts +33 -11
- package/src/services/in-memory-workflow-service.ts +49 -13
- package/src/services/local-content.test.ts +54 -0
- package/src/services/local-content.ts +12 -1
- package/src/services/typed-credential-service.ts +3 -3
- package/src/services/typed-secret-service.ts +3 -3
- package/src/services/typed-variables-service.ts +3 -3
- package/src/services/user-session-service.ts +4 -4
- package/src/testing/service-tests.ts +30 -0
- package/src/types/core.types.ts +6 -2
- package/src/types/state.types.ts +2 -13
- package/src/wirings/ai-agent/ai-agent-memory.test.ts +324 -0
- package/src/wirings/ai-agent/ai-agent-memory.ts +187 -36
- package/src/wirings/ai-agent/ai-agent-model-config.test.ts +12 -90
- package/src/wirings/ai-agent/ai-agent-model-config.ts +14 -38
- package/src/wirings/ai-agent/ai-agent-prepare.test.ts +292 -0
- package/src/wirings/ai-agent/ai-agent-prepare.ts +4 -1
- package/src/wirings/ai-agent/ai-agent-registry.test.ts +230 -3
- package/src/wirings/ai-agent/ai-agent-runner.test.ts +625 -6
- package/src/wirings/ai-agent/ai-agent-runner.ts +65 -50
- package/src/wirings/ai-agent/ai-agent-stream.test.ts +544 -5
- package/src/wirings/ai-agent/ai-agent-stream.ts +71 -69
- package/src/wirings/ai-agent/ai-agent.types.ts +24 -0
- package/src/wirings/channel/channel-handler.test.ts +272 -0
- package/src/wirings/channel/channel-handler.ts +1 -1
- package/src/wirings/channel/channel-middleware-runner.test.ts +163 -0
- package/src/wirings/channel/channel-store.ts +19 -0
- package/src/wirings/channel/channel.types.ts +4 -0
- package/src/wirings/channel/local/local-channel-runner.test.ts +63 -0
- package/src/wirings/channel/local/local-channel-runner.ts +41 -5
- package/src/wirings/channel/local/local-eventhub-service.ts +3 -3
- package/src/wirings/channel/pikku-abstract-channel-handler.ts +9 -2
- package/src/wirings/channel/serverless/serverless-channel-runner.ts +23 -5
- package/src/wirings/cli/channel/cli-raw-channel-runner.ts +7 -2
- package/src/wirings/cli/cli-runner.test.ts +255 -2
- package/src/wirings/cli/cli-runner.ts +31 -10
- package/src/wirings/cli/cli.types.ts +4 -8
- package/src/wirings/cli/command-parser.test.ts +83 -0
- package/src/wirings/cli/command-parser.ts +23 -4
- package/src/wirings/http/http-runner.test.ts +296 -1
- package/src/wirings/http/http-runner.ts +87 -57
- package/src/wirings/http/http.types.ts +11 -26
- package/src/wirings/http/pikku-fetch-http-request.ts +8 -4
- package/src/wirings/http/pikku-fetch-http-response.test.ts +41 -0
- package/src/wirings/http/web-request.test.ts +115 -0
- package/src/wirings/http/web-request.ts +43 -5
- package/src/wirings/mcp/mcp-runner.test.ts +367 -0
- package/src/wirings/queue/queue.types.ts +2 -5
- package/src/wirings/rpc/rpc-runner.test.ts +511 -0
- package/src/wirings/rpc/rpc-runner.ts +15 -3
- package/src/wirings/rpc/wire-addon.test.ts +57 -0
- package/src/wirings/workflow/graph/graph-node.ts +12 -0
- package/src/wirings/workflow/graph/graph-runner.test.ts +98 -0
- package/src/wirings/workflow/graph/graph-runner.ts +28 -10
- package/src/wirings/workflow/graph/workflow-graph.types.ts +4 -0
- package/src/wirings/workflow/index.ts +1 -0
- package/src/wirings/workflow/pikku-workflow-service.test.ts +19 -2
- package/src/wirings/workflow/pikku-workflow-service.ts +370 -71
- package/src/wirings/workflow/workflow-queue-workers.test.ts +131 -0
- package/src/wirings/workflow/workflow.types.ts +68 -5
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -23,11 +23,10 @@ import type {
|
|
|
23
23
|
} from '../../services/ai-agent-runner-service.js'
|
|
24
24
|
|
|
25
25
|
import {
|
|
26
|
-
parseWorkingMemory,
|
|
27
|
-
deepMergeWorkingMemory,
|
|
28
26
|
resolveMemoryServices,
|
|
29
27
|
loadContextMessages,
|
|
30
28
|
trimMessages,
|
|
29
|
+
getWorkingMemoryMiddleware,
|
|
31
30
|
} from './ai-agent-memory.js'
|
|
32
31
|
import {
|
|
33
32
|
prepareAgentRun,
|
|
@@ -58,6 +57,7 @@ function createPersistingChannel(
|
|
|
58
57
|
): PersistingChannel {
|
|
59
58
|
let fullText = ''
|
|
60
59
|
let stepText = ''
|
|
60
|
+
let stepGenerativeUI: unknown | null = null
|
|
61
61
|
let stepToolCalls: AIToolCall[] = []
|
|
62
62
|
let stepToolResults: AIToolResult[] = []
|
|
63
63
|
const totalUsage: {
|
|
@@ -72,16 +72,24 @@ function createPersistingChannel(
|
|
|
72
72
|
const flushStep = async () => {
|
|
73
73
|
if (!storage) return
|
|
74
74
|
const text = stepText
|
|
75
|
+
const generativeUI = stepGenerativeUI
|
|
75
76
|
const calls = stepToolCalls
|
|
76
77
|
const results = stepToolResults
|
|
77
78
|
stepText = ''
|
|
79
|
+
stepGenerativeUI = null
|
|
78
80
|
stepToolCalls = []
|
|
79
81
|
stepToolResults = []
|
|
80
|
-
if (text || calls.length > 0) {
|
|
82
|
+
if (text || generativeUI != null || calls.length > 0) {
|
|
81
83
|
const assistantMsg: AIMessage = {
|
|
82
84
|
id: randomUUID(),
|
|
83
85
|
role: 'assistant',
|
|
84
|
-
content:
|
|
86
|
+
content:
|
|
87
|
+
generativeUI != null
|
|
88
|
+
? [
|
|
89
|
+
...(text ? [{ type: 'text' as const, text }] : []),
|
|
90
|
+
{ type: 'generative-ui' as const, spec: generativeUI },
|
|
91
|
+
]
|
|
92
|
+
: text || undefined,
|
|
85
93
|
toolCalls: calls.length > 0 ? calls : undefined,
|
|
86
94
|
createdAt: new Date(),
|
|
87
95
|
}
|
|
@@ -137,6 +145,9 @@ function createPersistingChannel(
|
|
|
137
145
|
: JSON.stringify(event.result),
|
|
138
146
|
})
|
|
139
147
|
break
|
|
148
|
+
case 'generative-ui':
|
|
149
|
+
stepGenerativeUI = event.spec
|
|
150
|
+
break
|
|
140
151
|
case 'usage':
|
|
141
152
|
totalUsage.inputTokens += event.tokens.input
|
|
142
153
|
totalUsage.outputTokens += event.tokens.output
|
|
@@ -150,6 +161,9 @@ function createPersistingChannel(
|
|
|
150
161
|
}
|
|
151
162
|
parent.send(event)
|
|
152
163
|
},
|
|
164
|
+
setState: (s) => parent.setState(s),
|
|
165
|
+
getState: () => parent.getState(),
|
|
166
|
+
clearState: () => parent.clearState(),
|
|
153
167
|
}
|
|
154
168
|
return channel
|
|
155
169
|
}
|
|
@@ -158,10 +172,6 @@ async function postStreamCleanup(
|
|
|
158
172
|
persistingChannel: PersistingChannel,
|
|
159
173
|
aiMiddlewares: PikkuAIMiddlewareHooks[],
|
|
160
174
|
singletonServices: any,
|
|
161
|
-
storage: AIStorageService | undefined,
|
|
162
|
-
memoryConfig: any,
|
|
163
|
-
threadId: string,
|
|
164
|
-
workingMemorySchemaName: string | null,
|
|
165
175
|
messages: AIMessage[],
|
|
166
176
|
aiRunState: AIRunStateService,
|
|
167
177
|
runId: string
|
|
@@ -185,30 +195,6 @@ async function postStreamCleanup(
|
|
|
185
195
|
}
|
|
186
196
|
}
|
|
187
197
|
|
|
188
|
-
if (storage && memoryConfig?.workingMemory && outputText) {
|
|
189
|
-
const parsed = parseWorkingMemory(outputText)
|
|
190
|
-
if (parsed) {
|
|
191
|
-
const existing =
|
|
192
|
-
(await storage.getWorkingMemory(threadId, 'thread')) ?? {}
|
|
193
|
-
const merged = deepMergeWorkingMemory(existing, parsed)
|
|
194
|
-
|
|
195
|
-
if (singletonServices.schema && workingMemorySchemaName) {
|
|
196
|
-
try {
|
|
197
|
-
await singletonServices.schema.validateSchema(
|
|
198
|
-
workingMemorySchemaName,
|
|
199
|
-
merged
|
|
200
|
-
)
|
|
201
|
-
} catch (err) {
|
|
202
|
-
singletonServices.logger.warn(
|
|
203
|
-
`Working memory validation failed: ${err instanceof Error ? err.message : String(err)}`
|
|
204
|
-
)
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
await storage.saveWorkingMemory(threadId, 'thread', merged)
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
|
|
212
198
|
await aiRunState.updateRun(runId, {
|
|
213
199
|
status: 'completed',
|
|
214
200
|
...(usage.model
|
|
@@ -228,6 +214,7 @@ type StepLoopParams = {
|
|
|
228
214
|
runnerParams: AIAgentRunnerParams
|
|
229
215
|
maxSteps: number
|
|
230
216
|
agentRunner: AIAgentRunnerService
|
|
217
|
+
streamChannel: AIStreamChannel
|
|
231
218
|
persistingChannel: PersistingChannel
|
|
232
219
|
channel: AIStreamChannel
|
|
233
220
|
runId: string
|
|
@@ -247,7 +234,7 @@ async function runStreamStepLoop(
|
|
|
247
234
|
runnerParams,
|
|
248
235
|
maxSteps,
|
|
249
236
|
agentRunner,
|
|
250
|
-
|
|
237
|
+
streamChannel,
|
|
251
238
|
channel,
|
|
252
239
|
runId,
|
|
253
240
|
aiMiddlewares,
|
|
@@ -273,7 +260,7 @@ async function runStreamStepLoop(
|
|
|
273
260
|
|
|
274
261
|
channel.send({ type: 'step-start', stepNumber: step })
|
|
275
262
|
|
|
276
|
-
const stepResult = await agentRunner.stream(runnerParams,
|
|
263
|
+
const stepResult = await agentRunner.stream(runnerParams, streamChannel)
|
|
277
264
|
|
|
278
265
|
for (const mw of aiMiddlewares) {
|
|
279
266
|
if (mw.afterStep) {
|
|
@@ -433,10 +420,24 @@ export function appendStepMessages(
|
|
|
433
420
|
runnerParams: AIAgentRunnerParams,
|
|
434
421
|
stepResult: AIAgentStepResult
|
|
435
422
|
): void {
|
|
423
|
+
const structuredOutput =
|
|
424
|
+
stepResult.object && typeof stepResult.object === 'object'
|
|
425
|
+
? (stepResult.object as Record<string, unknown>)
|
|
426
|
+
: null
|
|
427
|
+
const assistantContent =
|
|
428
|
+
structuredOutput?.ui != null
|
|
429
|
+
? [
|
|
430
|
+
...(stepResult.text
|
|
431
|
+
? [{ type: 'text' as const, text: stepResult.text }]
|
|
432
|
+
: []),
|
|
433
|
+
{ type: 'generative-ui' as const, spec: structuredOutput.ui },
|
|
434
|
+
]
|
|
435
|
+
: stepResult.text || undefined
|
|
436
|
+
|
|
436
437
|
const assistantMsg: AIMessage = {
|
|
437
438
|
id: randomUUID(),
|
|
438
439
|
role: 'assistant',
|
|
439
|
-
content:
|
|
440
|
+
content: assistantContent,
|
|
440
441
|
toolCalls:
|
|
441
442
|
stepResult.toolCalls.length > 0
|
|
442
443
|
? stepResult.toolCalls.map((tc) => ({
|
|
@@ -445,6 +446,9 @@ export function appendStepMessages(
|
|
|
445
446
|
args: tc.args as Record<string, unknown>,
|
|
446
447
|
}))
|
|
447
448
|
: undefined,
|
|
449
|
+
...(stepResult.reasoningContent
|
|
450
|
+
? { reasoningContent: stepResult.reasoningContent }
|
|
451
|
+
: {}),
|
|
448
452
|
createdAt: new Date(),
|
|
449
453
|
}
|
|
450
454
|
runnerParams.messages.push(assistantMsg)
|
|
@@ -614,7 +618,15 @@ export async function streamAIAgent(
|
|
|
614
618
|
return ''
|
|
615
619
|
}
|
|
616
620
|
|
|
617
|
-
const aiMiddlewares: PikkuAIMiddlewareHooks[] =
|
|
621
|
+
const aiMiddlewares: PikkuAIMiddlewareHooks[] = [
|
|
622
|
+
...getWorkingMemoryMiddleware(memoryConfig, storage, {
|
|
623
|
+
threadId,
|
|
624
|
+
workingMemorySchemaName,
|
|
625
|
+
logger: singletonServices.logger,
|
|
626
|
+
schemaService: singletonServices.schema,
|
|
627
|
+
}),
|
|
628
|
+
...(agent.aiMiddleware ?? []),
|
|
629
|
+
]
|
|
618
630
|
|
|
619
631
|
let modifiedMessages = runnerParams.messages
|
|
620
632
|
let modifiedInstructions = runnerParams.instructions
|
|
@@ -680,14 +692,16 @@ export async function streamAIAgent(
|
|
|
680
692
|
}
|
|
681
693
|
)
|
|
682
694
|
|
|
695
|
+
const persistingChannel = createPersistingChannel(channel, storage, threadId)
|
|
696
|
+
|
|
683
697
|
const wrappedChannel =
|
|
684
698
|
allChannelMiddleware.length > 0
|
|
685
699
|
? (wrapChannelWithMiddleware(
|
|
686
|
-
{ channel },
|
|
700
|
+
{ channel: persistingChannel },
|
|
687
701
|
singletonServices,
|
|
688
702
|
allChannelMiddleware
|
|
689
703
|
).channel as AIStreamChannel)
|
|
690
|
-
:
|
|
704
|
+
: persistingChannel
|
|
691
705
|
|
|
692
706
|
// In delegate mode (default), suppress parent's text from reaching the client
|
|
693
707
|
// AFTER a sub-agent has been called. If the parent responds directly (no delegation),
|
|
@@ -713,18 +727,13 @@ export async function streamAIAgent(
|
|
|
713
727
|
}
|
|
714
728
|
: wrappedChannel
|
|
715
729
|
|
|
716
|
-
const persistingChannel = createPersistingChannel(
|
|
717
|
-
outputChannel,
|
|
718
|
-
storage,
|
|
719
|
-
threadId
|
|
720
|
-
)
|
|
721
|
-
|
|
722
730
|
try {
|
|
723
731
|
const loopResult = await runStreamStepLoop({
|
|
724
732
|
agent,
|
|
725
733
|
runnerParams,
|
|
726
734
|
maxSteps,
|
|
727
735
|
agentRunner,
|
|
736
|
+
streamChannel: outputChannel,
|
|
728
737
|
persistingChannel,
|
|
729
738
|
channel,
|
|
730
739
|
runId,
|
|
@@ -757,10 +766,6 @@ export async function streamAIAgent(
|
|
|
757
766
|
persistingChannel,
|
|
758
767
|
aiMiddlewares,
|
|
759
768
|
singletonServices,
|
|
760
|
-
storage,
|
|
761
|
-
memoryConfig,
|
|
762
|
-
threadId,
|
|
763
|
-
workingMemorySchemaName,
|
|
764
769
|
runnerParams.messages,
|
|
765
770
|
aiRunState,
|
|
766
771
|
runId
|
|
@@ -1090,7 +1095,15 @@ async function continueAfterToolResult(
|
|
|
1090
1095
|
|
|
1091
1096
|
const instructions = await buildInstructions(resolvedName, packageName)
|
|
1092
1097
|
|
|
1093
|
-
const aiMiddlewares: PikkuAIMiddlewareHooks[] =
|
|
1098
|
+
const aiMiddlewares: PikkuAIMiddlewareHooks[] = [
|
|
1099
|
+
...getWorkingMemoryMiddleware(memoryConfig, storage, {
|
|
1100
|
+
threadId: run.threadId,
|
|
1101
|
+
workingMemorySchemaName,
|
|
1102
|
+
logger: singletonServices.logger,
|
|
1103
|
+
schemaService: singletonServices.schema,
|
|
1104
|
+
}),
|
|
1105
|
+
...(agent.aiMiddleware ?? []),
|
|
1106
|
+
]
|
|
1094
1107
|
let modifiedMessages = trimmedMessages
|
|
1095
1108
|
let modifiedInstructions = instructions
|
|
1096
1109
|
for (const mw of aiMiddlewares) {
|
|
@@ -1137,20 +1150,20 @@ async function continueAfterToolResult(
|
|
|
1137
1150
|
}
|
|
1138
1151
|
)
|
|
1139
1152
|
|
|
1153
|
+
const persistingChannel = createPersistingChannel(
|
|
1154
|
+
channel,
|
|
1155
|
+
storage,
|
|
1156
|
+
run.threadId
|
|
1157
|
+
)
|
|
1158
|
+
|
|
1140
1159
|
const wrappedChannel =
|
|
1141
1160
|
allChannelMiddleware.length > 0
|
|
1142
1161
|
? (wrapChannelWithMiddleware(
|
|
1143
|
-
{ channel },
|
|
1162
|
+
{ channel: persistingChannel },
|
|
1144
1163
|
singletonServices,
|
|
1145
1164
|
allChannelMiddleware
|
|
1146
1165
|
).channel as AIStreamChannel)
|
|
1147
|
-
:
|
|
1148
|
-
|
|
1149
|
-
const persistingChannel = createPersistingChannel(
|
|
1150
|
-
wrappedChannel,
|
|
1151
|
-
storage,
|
|
1152
|
-
run.threadId
|
|
1153
|
-
)
|
|
1166
|
+
: persistingChannel
|
|
1154
1167
|
|
|
1155
1168
|
const streamContext: StreamContext = { channel, options }
|
|
1156
1169
|
const resumeTools = (
|
|
@@ -1166,14 +1179,6 @@ async function continueAfterToolResult(
|
|
|
1166
1179
|
).tools
|
|
1167
1180
|
|
|
1168
1181
|
const resolved = resolveModelConfig(resolvedName, agent)
|
|
1169
|
-
console.log(
|
|
1170
|
-
'[DEBUG resume] resolvedName:',
|
|
1171
|
-
resolvedName,
|
|
1172
|
-
'agent.model:',
|
|
1173
|
-
agent.model,
|
|
1174
|
-
'resolved.model:',
|
|
1175
|
-
resolved.model
|
|
1176
|
-
)
|
|
1177
1182
|
const maxSteps = resolved.maxSteps ?? 10
|
|
1178
1183
|
|
|
1179
1184
|
const runnerParams: AIAgentRunnerParams = {
|
|
@@ -1195,6 +1200,7 @@ async function continueAfterToolResult(
|
|
|
1195
1200
|
runnerParams,
|
|
1196
1201
|
maxSteps,
|
|
1197
1202
|
agentRunner,
|
|
1203
|
+
streamChannel: wrappedChannel,
|
|
1198
1204
|
persistingChannel,
|
|
1199
1205
|
channel,
|
|
1200
1206
|
runId: run.runId,
|
|
@@ -1227,10 +1233,6 @@ async function continueAfterToolResult(
|
|
|
1227
1233
|
persistingChannel,
|
|
1228
1234
|
aiMiddlewares,
|
|
1229
1235
|
singletonServices,
|
|
1230
|
-
storage,
|
|
1231
|
-
memoryConfig,
|
|
1232
|
-
run.threadId,
|
|
1233
|
-
workingMemorySchemaName,
|
|
1234
1236
|
runnerParams.messages,
|
|
1235
1237
|
aiRunState,
|
|
1236
1238
|
run.runId
|
|
@@ -29,6 +29,15 @@ export type AIContentPart =
|
|
|
29
29
|
mediaType: string
|
|
30
30
|
filename?: string
|
|
31
31
|
}
|
|
32
|
+
| {
|
|
33
|
+
type: 'data'
|
|
34
|
+
name: string
|
|
35
|
+
data: unknown
|
|
36
|
+
}
|
|
37
|
+
| {
|
|
38
|
+
type: 'generative-ui'
|
|
39
|
+
spec: unknown
|
|
40
|
+
}
|
|
32
41
|
|
|
33
42
|
export interface AIToolCall {
|
|
34
43
|
id: string
|
|
@@ -48,6 +57,7 @@ export interface AIMessage {
|
|
|
48
57
|
content?: string | AIContentPart[]
|
|
49
58
|
toolCalls?: AIToolCall[]
|
|
50
59
|
toolResults?: AIToolResult[]
|
|
60
|
+
reasoningContent?: string
|
|
51
61
|
createdAt: Date
|
|
52
62
|
}
|
|
53
63
|
|
|
@@ -299,6 +309,19 @@ export type AIStreamEvent =
|
|
|
299
309
|
agent?: string
|
|
300
310
|
session?: string
|
|
301
311
|
}
|
|
312
|
+
| {
|
|
313
|
+
type: 'data'
|
|
314
|
+
name: string
|
|
315
|
+
data: unknown
|
|
316
|
+
agent?: string
|
|
317
|
+
session?: string
|
|
318
|
+
}
|
|
319
|
+
| {
|
|
320
|
+
type: 'generative-ui'
|
|
321
|
+
spec: unknown
|
|
322
|
+
agent?: string
|
|
323
|
+
session?: string
|
|
324
|
+
}
|
|
302
325
|
| {
|
|
303
326
|
type: 'suspended'
|
|
304
327
|
reason: 'rpc-missing'
|
|
@@ -367,6 +390,7 @@ export interface AgentRunRow {
|
|
|
367
390
|
export interface AgentRunService {
|
|
368
391
|
listThreads(options?: {
|
|
369
392
|
agentName?: string
|
|
393
|
+
resourceId?: string
|
|
370
394
|
limit?: number
|
|
371
395
|
offset?: number
|
|
372
396
|
}): Promise<AIThread[]>
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
import { beforeEach, describe, test } from 'node:test'
|
|
2
|
+
import assert from 'node:assert/strict'
|
|
3
|
+
|
|
4
|
+
import { addFunction } from '../../function/function-runner.js'
|
|
5
|
+
import { pikkuState, resetPikkuState } from '../../pikku-state.js'
|
|
6
|
+
import { processMessageHandlers } from './channel-handler.js'
|
|
7
|
+
|
|
8
|
+
const createLogger = () => {
|
|
9
|
+
const errors: string[] = []
|
|
10
|
+
return {
|
|
11
|
+
errors,
|
|
12
|
+
logger: {
|
|
13
|
+
debug: () => {},
|
|
14
|
+
info: () => {},
|
|
15
|
+
warn: () => {},
|
|
16
|
+
error: (...args: unknown[]) => {
|
|
17
|
+
errors.push(args.map((arg) => String(arg)).join(' '))
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const createChannelHandler = () => {
|
|
24
|
+
const sent: unknown[] = []
|
|
25
|
+
const channel = {
|
|
26
|
+
channelId: 'channel-1',
|
|
27
|
+
openingData: {},
|
|
28
|
+
state: 'open' as const,
|
|
29
|
+
send: (message: unknown) => {
|
|
30
|
+
sent.push(message)
|
|
31
|
+
},
|
|
32
|
+
sendBinary: () => {},
|
|
33
|
+
close: () => {},
|
|
34
|
+
setState: () => {},
|
|
35
|
+
getState: () => undefined,
|
|
36
|
+
clearState: () => {},
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return {
|
|
40
|
+
sent,
|
|
41
|
+
handler: {
|
|
42
|
+
send: (message: unknown) => channel.send(message),
|
|
43
|
+
sendBinary: () => {},
|
|
44
|
+
getChannel: () => channel,
|
|
45
|
+
},
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const registerFunction = (
|
|
50
|
+
funcName: string,
|
|
51
|
+
func: (services: any, data: any, wire: any) => Promise<unknown> | unknown
|
|
52
|
+
) => {
|
|
53
|
+
addFunction(funcName, { func } as never)
|
|
54
|
+
pikkuState(null, 'function', 'meta')[funcName] = {
|
|
55
|
+
name: funcName,
|
|
56
|
+
sessionless: true,
|
|
57
|
+
permissions: [],
|
|
58
|
+
} as never
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
beforeEach(() => {
|
|
62
|
+
resetPikkuState()
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
describe('processMessageHandlers', () => {
|
|
66
|
+
test('routes json messages using onMessageWiring and appends the routing key to the result', async () => {
|
|
67
|
+
const { logger } = createLogger()
|
|
68
|
+
const { handler } = createChannelHandler()
|
|
69
|
+
|
|
70
|
+
pikkuState(null, 'channel', 'meta').chat = {
|
|
71
|
+
name: 'chat',
|
|
72
|
+
route: '/chat',
|
|
73
|
+
input: null,
|
|
74
|
+
connect: null,
|
|
75
|
+
disconnect: null,
|
|
76
|
+
message: null,
|
|
77
|
+
messageWirings: {
|
|
78
|
+
kind: {
|
|
79
|
+
ping: {
|
|
80
|
+
pikkuFuncId: 'pingFunc',
|
|
81
|
+
packageName: null,
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
} as never
|
|
86
|
+
registerFunction('pingFunc', async (_services, data) => ({
|
|
87
|
+
reply: `pong:${data.value}`,
|
|
88
|
+
}))
|
|
89
|
+
|
|
90
|
+
const { onMessage } = processMessageHandlers(
|
|
91
|
+
{ logger } as never,
|
|
92
|
+
{
|
|
93
|
+
name: 'chat',
|
|
94
|
+
route: '/chat',
|
|
95
|
+
auth: false,
|
|
96
|
+
onMessageWiring: {
|
|
97
|
+
kind: {
|
|
98
|
+
ping: {
|
|
99
|
+
func: { func: async () => ({ ok: true }) } as never,
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
} as never,
|
|
104
|
+
handler as never
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
const result = await onMessage('{"kind":"ping","value":"hello"}')
|
|
108
|
+
|
|
109
|
+
assert.deepEqual(result, {
|
|
110
|
+
reply: 'pong:hello',
|
|
111
|
+
kind: 'ping',
|
|
112
|
+
})
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
test('falls back to the default handler for parsed json with no matching route', async () => {
|
|
116
|
+
const { logger } = createLogger()
|
|
117
|
+
const { handler } = createChannelHandler()
|
|
118
|
+
|
|
119
|
+
pikkuState(null, 'channel', 'meta').chat = {
|
|
120
|
+
name: 'chat',
|
|
121
|
+
route: '/chat',
|
|
122
|
+
input: null,
|
|
123
|
+
connect: null,
|
|
124
|
+
disconnect: null,
|
|
125
|
+
message: {
|
|
126
|
+
pikkuFuncId: 'defaultFunc',
|
|
127
|
+
packageName: null,
|
|
128
|
+
},
|
|
129
|
+
messageWirings: {
|
|
130
|
+
kind: {
|
|
131
|
+
ping: {
|
|
132
|
+
pikkuFuncId: 'pingFunc',
|
|
133
|
+
packageName: null,
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
} as never
|
|
138
|
+
registerFunction('defaultFunc', async (_services, data) => ({
|
|
139
|
+
seen: data,
|
|
140
|
+
}))
|
|
141
|
+
|
|
142
|
+
const { onMessage } = processMessageHandlers(
|
|
143
|
+
{ logger } as never,
|
|
144
|
+
{
|
|
145
|
+
name: 'chat',
|
|
146
|
+
route: '/chat',
|
|
147
|
+
auth: false,
|
|
148
|
+
onMessage: { func: async () => ({ ok: true }) } as never,
|
|
149
|
+
onMessageWiring: {
|
|
150
|
+
kind: {
|
|
151
|
+
ping: {
|
|
152
|
+
func: { func: async () => ({ ok: true }) } as never,
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
},
|
|
156
|
+
} as never,
|
|
157
|
+
handler as never
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
const result = await onMessage('{"kind":"unknown","value":"hello"}')
|
|
161
|
+
|
|
162
|
+
assert.deepEqual(result, {
|
|
163
|
+
seen: { kind: 'unknown', value: 'hello' },
|
|
164
|
+
})
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
test('uses wrapper auth for default messages and sends unauthorized errors without a session', async () => {
|
|
168
|
+
const { errors, logger } = createLogger()
|
|
169
|
+
const { sent, handler } = createChannelHandler()
|
|
170
|
+
|
|
171
|
+
pikkuState(null, 'channel', 'meta').chat = {
|
|
172
|
+
name: 'chat',
|
|
173
|
+
route: '/chat',
|
|
174
|
+
input: null,
|
|
175
|
+
connect: null,
|
|
176
|
+
disconnect: null,
|
|
177
|
+
message: {
|
|
178
|
+
pikkuFuncId: 'defaultFunc',
|
|
179
|
+
packageName: null,
|
|
180
|
+
},
|
|
181
|
+
messageWirings: {},
|
|
182
|
+
} as never
|
|
183
|
+
registerFunction('defaultFunc', async () => ({ ok: true }))
|
|
184
|
+
|
|
185
|
+
const { onMessage } = processMessageHandlers(
|
|
186
|
+
{ logger } as never,
|
|
187
|
+
{
|
|
188
|
+
name: 'chat',
|
|
189
|
+
route: '/chat',
|
|
190
|
+
auth: false,
|
|
191
|
+
onMessage: {
|
|
192
|
+
auth: true,
|
|
193
|
+
func: { func: async () => ({ ok: true }) } as never,
|
|
194
|
+
},
|
|
195
|
+
} as never,
|
|
196
|
+
handler as never
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
const result = await onMessage('hello')
|
|
200
|
+
|
|
201
|
+
assert.equal(result, undefined)
|
|
202
|
+
assert.deepEqual(sent, ['Unauthorized for the default message route'])
|
|
203
|
+
assert.match(
|
|
204
|
+
errors[0] || '',
|
|
205
|
+
/requires a session for the default message route/
|
|
206
|
+
)
|
|
207
|
+
})
|
|
208
|
+
|
|
209
|
+
test('logs when no message handler can process the payload', async () => {
|
|
210
|
+
const { errors, logger } = createLogger()
|
|
211
|
+
const { handler } = createChannelHandler()
|
|
212
|
+
|
|
213
|
+
pikkuState(null, 'channel', 'meta').chat = {
|
|
214
|
+
name: 'chat',
|
|
215
|
+
route: '/chat',
|
|
216
|
+
input: null,
|
|
217
|
+
connect: null,
|
|
218
|
+
disconnect: null,
|
|
219
|
+
message: null,
|
|
220
|
+
messageWirings: {},
|
|
221
|
+
} as never
|
|
222
|
+
|
|
223
|
+
const { onMessage } = processMessageHandlers(
|
|
224
|
+
{ logger } as never,
|
|
225
|
+
{
|
|
226
|
+
name: 'chat',
|
|
227
|
+
route: '/chat',
|
|
228
|
+
auth: false,
|
|
229
|
+
} as never,
|
|
230
|
+
handler as never
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
const result = await onMessage({ raw: true })
|
|
234
|
+
|
|
235
|
+
assert.equal(result, undefined)
|
|
236
|
+
assert.match(
|
|
237
|
+
errors[0] || '',
|
|
238
|
+
/No handler found for message in channel chat/
|
|
239
|
+
)
|
|
240
|
+
assert.match(errors[1] || '', /Channel config name: chat/)
|
|
241
|
+
})
|
|
242
|
+
|
|
243
|
+
test('blocks binary messages when a session is required and missing', async () => {
|
|
244
|
+
const { errors, logger } = createLogger()
|
|
245
|
+
const { handler } = createChannelHandler()
|
|
246
|
+
|
|
247
|
+
pikkuState(null, 'channel', 'meta').chat = {
|
|
248
|
+
name: 'chat',
|
|
249
|
+
route: '/chat',
|
|
250
|
+
input: null,
|
|
251
|
+
connect: null,
|
|
252
|
+
disconnect: null,
|
|
253
|
+
message: null,
|
|
254
|
+
messageWirings: {},
|
|
255
|
+
} as never
|
|
256
|
+
|
|
257
|
+
const { onBinaryMessage } = processMessageHandlers(
|
|
258
|
+
{ logger } as never,
|
|
259
|
+
{
|
|
260
|
+
name: 'chat',
|
|
261
|
+
route: '/chat',
|
|
262
|
+
onBinaryMessage: async () => new Uint8Array([1, 2, 3]),
|
|
263
|
+
} as never,
|
|
264
|
+
handler as never
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
const result = await onBinaryMessage?.(new Uint8Array([1, 2, 3]))
|
|
268
|
+
|
|
269
|
+
assert.equal(result, undefined)
|
|
270
|
+
assert.match(errors[0] || '', /requires a session for binary message/)
|
|
271
|
+
})
|
|
272
|
+
})
|
|
@@ -90,7 +90,7 @@ export const processMessageHandlers = (
|
|
|
90
90
|
? `route '${routingProperty}:${routerValue}'`
|
|
91
91
|
: 'the default message route'
|
|
92
92
|
logger.error(
|
|
93
|
-
`Channel ${channelConfig.name} with id ${channelHandler.getChannel().channelId} requires a session for ${routeMessage}
|
|
93
|
+
`Channel ${channelConfig.name} with id ${channelHandler.getChannel().channelId} requires a session for ${routeMessage}. No session is attached to this websocket connection. Ensure auth middleware establishes a session during websocket upgrade, and configure sessionStore when the runtime needs persisted sessions.`
|
|
94
94
|
)
|
|
95
95
|
// TODO: Send error message back breaks typescript, but should be implemented somehow
|
|
96
96
|
channelHandler.getChannel().send(`Unauthorized for ${routeMessage}`)
|