@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.
- package/CHANGELOG.md +77 -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/function-runner.js +53 -3
- package/dist/function/functions.types.d.ts +3 -2
- package/dist/index.d.ts +3 -3
- 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/content-service.d.ts +66 -37
- 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/index.d.ts +1 -1
- package/dist/services/local-content.d.ts +10 -12
- package/dist/services/local-content.js +54 -38
- 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.d.ts +3 -2
- package/dist/wirings/rpc/rpc-runner.js +45 -11
- 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/function-runner.ts +68 -3
- package/src/function/functions.types.ts +3 -2
- package/src/index.ts +22 -3
- 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/content-service.ts +79 -51
- 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/index.ts +10 -1
- package/src/services/local-content.test.ts +54 -0
- package/src/services/local-content.ts +80 -53
- 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 +60 -21
- 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
|
@@ -2,12 +2,13 @@ import { pikkuState, getSingletonServices } from '../../pikku-state.js';
|
|
|
2
2
|
import { AIProviderNotConfiguredError } from '../../errors/errors.js';
|
|
3
3
|
import { combineChannelMiddleware, wrapChannelWithMiddleware, } from '../channel/channel-middleware-runner.js';
|
|
4
4
|
import { randomUUID } from 'crypto';
|
|
5
|
-
import {
|
|
5
|
+
import { resolveMemoryServices, loadContextMessages, trimMessages, getWorkingMemoryMiddleware, } from './ai-agent-memory.js';
|
|
6
6
|
import { prepareAgentRun, resolveAgent, buildInstructions, buildToolDefs, createScopedChannel, ToolApprovalRequired, ToolCredentialRequired, } from './ai-agent-prepare.js';
|
|
7
7
|
import { resolveModelConfig } from './ai-agent-model-config.js';
|
|
8
8
|
function createPersistingChannel(parent, storage, threadId) {
|
|
9
9
|
let fullText = '';
|
|
10
10
|
let stepText = '';
|
|
11
|
+
let stepGenerativeUI = null;
|
|
11
12
|
let stepToolCalls = [];
|
|
12
13
|
let stepToolResults = [];
|
|
13
14
|
const totalUsage = {
|
|
@@ -18,16 +19,23 @@ function createPersistingChannel(parent, storage, threadId) {
|
|
|
18
19
|
if (!storage)
|
|
19
20
|
return;
|
|
20
21
|
const text = stepText;
|
|
22
|
+
const generativeUI = stepGenerativeUI;
|
|
21
23
|
const calls = stepToolCalls;
|
|
22
24
|
const results = stepToolResults;
|
|
23
25
|
stepText = '';
|
|
26
|
+
stepGenerativeUI = null;
|
|
24
27
|
stepToolCalls = [];
|
|
25
28
|
stepToolResults = [];
|
|
26
|
-
if (text || calls.length > 0) {
|
|
29
|
+
if (text || generativeUI != null || calls.length > 0) {
|
|
27
30
|
const assistantMsg = {
|
|
28
31
|
id: randomUUID(),
|
|
29
32
|
role: 'assistant',
|
|
30
|
-
content:
|
|
33
|
+
content: generativeUI != null
|
|
34
|
+
? [
|
|
35
|
+
...(text ? [{ type: 'text', text }] : []),
|
|
36
|
+
{ type: 'generative-ui', spec: generativeUI },
|
|
37
|
+
]
|
|
38
|
+
: text || undefined,
|
|
31
39
|
toolCalls: calls.length > 0 ? calls : undefined,
|
|
32
40
|
createdAt: new Date(),
|
|
33
41
|
};
|
|
@@ -81,6 +89,9 @@ function createPersistingChannel(parent, storage, threadId) {
|
|
|
81
89
|
: JSON.stringify(event.result),
|
|
82
90
|
});
|
|
83
91
|
break;
|
|
92
|
+
case 'generative-ui':
|
|
93
|
+
stepGenerativeUI = event.spec;
|
|
94
|
+
break;
|
|
84
95
|
case 'usage':
|
|
85
96
|
totalUsage.inputTokens += event.tokens.input;
|
|
86
97
|
totalUsage.outputTokens += event.tokens.output;
|
|
@@ -95,10 +106,13 @@ function createPersistingChannel(parent, storage, threadId) {
|
|
|
95
106
|
}
|
|
96
107
|
parent.send(event);
|
|
97
108
|
},
|
|
109
|
+
setState: (s) => parent.setState(s),
|
|
110
|
+
getState: () => parent.getState(),
|
|
111
|
+
clearState: () => parent.clearState(),
|
|
98
112
|
};
|
|
99
113
|
return channel;
|
|
100
114
|
}
|
|
101
|
-
async function postStreamCleanup(persistingChannel, aiMiddlewares, singletonServices,
|
|
115
|
+
async function postStreamCleanup(persistingChannel, aiMiddlewares, singletonServices, messages, aiRunState, runId) {
|
|
102
116
|
const usage = persistingChannel.totalUsage;
|
|
103
117
|
let outputText = persistingChannel.fullText;
|
|
104
118
|
let outputMessages = messages;
|
|
@@ -117,22 +131,6 @@ async function postStreamCleanup(persistingChannel, aiMiddlewares, singletonServ
|
|
|
117
131
|
outputMessages = result.messages;
|
|
118
132
|
}
|
|
119
133
|
}
|
|
120
|
-
if (storage && memoryConfig?.workingMemory && outputText) {
|
|
121
|
-
const parsed = parseWorkingMemory(outputText);
|
|
122
|
-
if (parsed) {
|
|
123
|
-
const existing = (await storage.getWorkingMemory(threadId, 'thread')) ?? {};
|
|
124
|
-
const merged = deepMergeWorkingMemory(existing, parsed);
|
|
125
|
-
if (singletonServices.schema && workingMemorySchemaName) {
|
|
126
|
-
try {
|
|
127
|
-
await singletonServices.schema.validateSchema(workingMemorySchemaName, merged);
|
|
128
|
-
}
|
|
129
|
-
catch (err) {
|
|
130
|
-
singletonServices.logger.warn(`Working memory validation failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
await storage.saveWorkingMemory(threadId, 'thread', merged);
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
134
|
await aiRunState.updateRun(runId, {
|
|
137
135
|
status: 'completed',
|
|
138
136
|
...(usage.model
|
|
@@ -147,7 +145,7 @@ async function postStreamCleanup(persistingChannel, aiMiddlewares, singletonServ
|
|
|
147
145
|
});
|
|
148
146
|
}
|
|
149
147
|
async function runStreamStepLoop(params) {
|
|
150
|
-
const { agent, runnerParams, maxSteps, agentRunner,
|
|
148
|
+
const { agent, runnerParams, maxSteps, agentRunner, streamChannel, channel, runId, aiMiddlewares, } = params;
|
|
151
149
|
const singletonServices = getSingletonServices();
|
|
152
150
|
for (let step = 0; step < maxSteps; step++) {
|
|
153
151
|
if (agent.prepareStep) {
|
|
@@ -166,7 +164,7 @@ async function runStreamStepLoop(params) {
|
|
|
166
164
|
break;
|
|
167
165
|
}
|
|
168
166
|
channel.send({ type: 'step-start', stepNumber: step });
|
|
169
|
-
const stepResult = await agentRunner.stream(runnerParams,
|
|
167
|
+
const stepResult = await agentRunner.stream(runnerParams, streamChannel);
|
|
170
168
|
for (const mw of aiMiddlewares) {
|
|
171
169
|
if (mw.afterStep) {
|
|
172
170
|
await mw.afterStep(singletonServices, {
|
|
@@ -246,10 +244,21 @@ export function checkForCredentialRequests(stepResult, runId) {
|
|
|
246
244
|
return requests;
|
|
247
245
|
}
|
|
248
246
|
export function appendStepMessages(runnerParams, stepResult) {
|
|
247
|
+
const structuredOutput = stepResult.object && typeof stepResult.object === 'object'
|
|
248
|
+
? stepResult.object
|
|
249
|
+
: null;
|
|
250
|
+
const assistantContent = structuredOutput?.ui != null
|
|
251
|
+
? [
|
|
252
|
+
...(stepResult.text
|
|
253
|
+
? [{ type: 'text', text: stepResult.text }]
|
|
254
|
+
: []),
|
|
255
|
+
{ type: 'generative-ui', spec: structuredOutput.ui },
|
|
256
|
+
]
|
|
257
|
+
: stepResult.text || undefined;
|
|
249
258
|
const assistantMsg = {
|
|
250
259
|
id: randomUUID(),
|
|
251
260
|
role: 'assistant',
|
|
252
|
-
content:
|
|
261
|
+
content: assistantContent,
|
|
253
262
|
toolCalls: stepResult.toolCalls.length > 0
|
|
254
263
|
? stepResult.toolCalls.map((tc) => ({
|
|
255
264
|
id: tc.toolCallId,
|
|
@@ -257,6 +266,9 @@ export function appendStepMessages(runnerParams, stepResult) {
|
|
|
257
266
|
args: tc.args,
|
|
258
267
|
}))
|
|
259
268
|
: undefined,
|
|
269
|
+
...(stepResult.reasoningContent
|
|
270
|
+
? { reasoningContent: stepResult.reasoningContent }
|
|
271
|
+
: {}),
|
|
260
272
|
createdAt: new Date(),
|
|
261
273
|
};
|
|
262
274
|
runnerParams.messages.push(assistantMsg);
|
|
@@ -363,7 +375,15 @@ export async function streamAIAgent(agentName, input, channel, params, agentSess
|
|
|
363
375
|
channel.send({ type: 'done' });
|
|
364
376
|
return '';
|
|
365
377
|
}
|
|
366
|
-
const aiMiddlewares =
|
|
378
|
+
const aiMiddlewares = [
|
|
379
|
+
...getWorkingMemoryMiddleware(memoryConfig, storage, {
|
|
380
|
+
threadId,
|
|
381
|
+
workingMemorySchemaName,
|
|
382
|
+
logger: singletonServices.logger,
|
|
383
|
+
schemaService: singletonServices.schema,
|
|
384
|
+
}),
|
|
385
|
+
...(agent.aiMiddleware ?? []),
|
|
386
|
+
];
|
|
367
387
|
let modifiedMessages = runnerParams.messages;
|
|
368
388
|
let modifiedInstructions = runnerParams.instructions;
|
|
369
389
|
for (const mw of aiMiddlewares) {
|
|
@@ -422,9 +442,10 @@ export async function streamAIAgent(agentName, input, channel, params, agentSess
|
|
|
422
442
|
...streamMiddleware,
|
|
423
443
|
],
|
|
424
444
|
});
|
|
445
|
+
const persistingChannel = createPersistingChannel(channel, storage, threadId);
|
|
425
446
|
const wrappedChannel = allChannelMiddleware.length > 0
|
|
426
|
-
? wrapChannelWithMiddleware({ channel }, singletonServices, allChannelMiddleware).channel
|
|
427
|
-
:
|
|
447
|
+
? wrapChannelWithMiddleware({ channel: persistingChannel }, singletonServices, allChannelMiddleware).channel
|
|
448
|
+
: persistingChannel;
|
|
428
449
|
// In delegate mode (default), suppress parent's text from reaching the client
|
|
429
450
|
// AFTER a sub-agent has been called. If the parent responds directly (no delegation),
|
|
430
451
|
// its text goes through normally. Sub-agent text bypasses this path entirely
|
|
@@ -446,13 +467,13 @@ export async function streamAIAgent(agentName, input, channel, params, agentSess
|
|
|
446
467
|
delegateState,
|
|
447
468
|
}
|
|
448
469
|
: wrappedChannel;
|
|
449
|
-
const persistingChannel = createPersistingChannel(outputChannel, storage, threadId);
|
|
450
470
|
try {
|
|
451
471
|
const loopResult = await runStreamStepLoop({
|
|
452
472
|
agent,
|
|
453
473
|
runnerParams,
|
|
454
474
|
maxSteps,
|
|
455
475
|
agentRunner,
|
|
476
|
+
streamChannel: outputChannel,
|
|
456
477
|
persistingChannel,
|
|
457
478
|
channel,
|
|
458
479
|
runId,
|
|
@@ -466,7 +487,7 @@ export async function streamAIAgent(agentName, input, channel, params, agentSess
|
|
|
466
487
|
await handleCredentialRequests(loopResult.credentialRequests, runId, channel, aiRunState, persistingChannel);
|
|
467
488
|
return persistingChannel.fullText;
|
|
468
489
|
}
|
|
469
|
-
await postStreamCleanup(persistingChannel, aiMiddlewares, singletonServices,
|
|
490
|
+
await postStreamCleanup(persistingChannel, aiMiddlewares, singletonServices, runnerParams.messages, aiRunState, runId);
|
|
470
491
|
channel.send({ type: 'done' });
|
|
471
492
|
channel.close();
|
|
472
493
|
return persistingChannel.fullText;
|
|
@@ -683,7 +704,15 @@ async function continueAfterToolResult(run, agent, packageName, resolvedName, st
|
|
|
683
704
|
const allMessages = [...contextMessages, ...messages];
|
|
684
705
|
const trimmedMessages = trimMessages(allMessages);
|
|
685
706
|
const instructions = await buildInstructions(resolvedName, packageName);
|
|
686
|
-
const aiMiddlewares =
|
|
707
|
+
const aiMiddlewares = [
|
|
708
|
+
...getWorkingMemoryMiddleware(memoryConfig, storage, {
|
|
709
|
+
threadId: run.threadId,
|
|
710
|
+
workingMemorySchemaName,
|
|
711
|
+
logger: singletonServices.logger,
|
|
712
|
+
schemaService: singletonServices.schema,
|
|
713
|
+
}),
|
|
714
|
+
...(agent.aiMiddleware ?? []),
|
|
715
|
+
];
|
|
687
716
|
let modifiedMessages = trimmedMessages;
|
|
688
717
|
let modifiedInstructions = instructions;
|
|
689
718
|
for (const mw of aiMiddlewares) {
|
|
@@ -726,14 +755,13 @@ async function continueAfterToolResult(run, agent, packageName, resolvedName, st
|
|
|
726
755
|
...streamMiddleware,
|
|
727
756
|
],
|
|
728
757
|
});
|
|
758
|
+
const persistingChannel = createPersistingChannel(channel, storage, run.threadId);
|
|
729
759
|
const wrappedChannel = allChannelMiddleware.length > 0
|
|
730
|
-
? wrapChannelWithMiddleware({ channel }, singletonServices, allChannelMiddleware).channel
|
|
731
|
-
:
|
|
732
|
-
const persistingChannel = createPersistingChannel(wrappedChannel, storage, run.threadId);
|
|
760
|
+
? wrapChannelWithMiddleware({ channel: persistingChannel }, singletonServices, allChannelMiddleware).channel
|
|
761
|
+
: persistingChannel;
|
|
733
762
|
const streamContext = { channel, options };
|
|
734
763
|
const resumeTools = (await buildToolDefs(params, new Map(), run.resourceId, resolvedName, packageName, streamContext, aiMiddlewares)).tools;
|
|
735
764
|
const resolved = resolveModelConfig(resolvedName, agent);
|
|
736
|
-
console.log('[DEBUG resume] resolvedName:', resolvedName, 'agent.model:', agent.model, 'resolved.model:', resolved.model);
|
|
737
765
|
const maxSteps = resolved.maxSteps ?? 10;
|
|
738
766
|
const runnerParams = {
|
|
739
767
|
model: resolved.model,
|
|
@@ -753,6 +781,7 @@ async function continueAfterToolResult(run, agent, packageName, resolvedName, st
|
|
|
753
781
|
runnerParams,
|
|
754
782
|
maxSteps,
|
|
755
783
|
agentRunner,
|
|
784
|
+
streamChannel: wrappedChannel,
|
|
756
785
|
persistingChannel,
|
|
757
786
|
channel,
|
|
758
787
|
runId: run.runId,
|
|
@@ -766,7 +795,7 @@ async function continueAfterToolResult(run, agent, packageName, resolvedName, st
|
|
|
766
795
|
await handleCredentialRequests(loopResult.credentialRequests, run.runId, channel, aiRunState, persistingChannel);
|
|
767
796
|
return;
|
|
768
797
|
}
|
|
769
|
-
await postStreamCleanup(persistingChannel, aiMiddlewares, singletonServices,
|
|
798
|
+
await postStreamCleanup(persistingChannel, aiMiddlewares, singletonServices, runnerParams.messages, aiRunState, run.runId);
|
|
770
799
|
channel.send({ type: 'done' });
|
|
771
800
|
channel.close();
|
|
772
801
|
}
|
|
@@ -24,6 +24,13 @@ export type AIContentPart = {
|
|
|
24
24
|
url?: string;
|
|
25
25
|
mediaType: string;
|
|
26
26
|
filename?: string;
|
|
27
|
+
} | {
|
|
28
|
+
type: 'data';
|
|
29
|
+
name: string;
|
|
30
|
+
data: unknown;
|
|
31
|
+
} | {
|
|
32
|
+
type: 'generative-ui';
|
|
33
|
+
spec: unknown;
|
|
27
34
|
};
|
|
28
35
|
export interface AIToolCall {
|
|
29
36
|
id: string;
|
|
@@ -41,6 +48,7 @@ export interface AIMessage {
|
|
|
41
48
|
content?: string | AIContentPart[];
|
|
42
49
|
toolCalls?: AIToolCall[];
|
|
43
50
|
toolResults?: AIToolResult[];
|
|
51
|
+
reasoningContent?: string;
|
|
44
52
|
createdAt: Date;
|
|
45
53
|
}
|
|
46
54
|
export interface AIAgentStep {
|
|
@@ -298,6 +306,17 @@ export type AIStreamEvent = {
|
|
|
298
306
|
graph: any;
|
|
299
307
|
agent?: string;
|
|
300
308
|
session?: string;
|
|
309
|
+
} | {
|
|
310
|
+
type: 'data';
|
|
311
|
+
name: string;
|
|
312
|
+
data: unknown;
|
|
313
|
+
agent?: string;
|
|
314
|
+
session?: string;
|
|
315
|
+
} | {
|
|
316
|
+
type: 'generative-ui';
|
|
317
|
+
spec: unknown;
|
|
318
|
+
agent?: string;
|
|
319
|
+
session?: string;
|
|
301
320
|
} | {
|
|
302
321
|
type: 'suspended';
|
|
303
322
|
reason: 'rpc-missing';
|
|
@@ -364,6 +383,7 @@ export interface AgentRunRow {
|
|
|
364
383
|
export interface AgentRunService {
|
|
365
384
|
listThreads(options?: {
|
|
366
385
|
agentName?: string;
|
|
386
|
+
resourceId?: string;
|
|
367
387
|
limit?: number;
|
|
368
388
|
offset?: number;
|
|
369
389
|
}): Promise<AIThread[]>;
|
|
@@ -43,7 +43,7 @@ export const processMessageHandlers = (services, channelConfig, channelHandler,
|
|
|
43
43
|
const routeMessage = routingProperty
|
|
44
44
|
? `route '${routingProperty}:${routerValue}'`
|
|
45
45
|
: 'the default message route';
|
|
46
|
-
logger.error(`Channel ${channelConfig.name} with id ${channelHandler.getChannel().channelId} requires a session for ${routeMessage}
|
|
46
|
+
logger.error(`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.`);
|
|
47
47
|
// TODO: Send error message back breaks typescript, but should be implemented somehow
|
|
48
48
|
channelHandler.getChannel().send(`Unauthorized for ${routeMessage}`);
|
|
49
49
|
return;
|
|
@@ -13,4 +13,17 @@ export declare abstract class ChannelStore<ChannelType = unknown, OpeningData =
|
|
|
13
13
|
}> | (TypedChannel & {
|
|
14
14
|
pikkuUserId?: string;
|
|
15
15
|
});
|
|
16
|
+
/**
|
|
17
|
+
* Persist per-socket scratch state scoped to a single channel (keyed by
|
|
18
|
+
* channelId). This is intentionally separate from `SessionStore` (which is
|
|
19
|
+
* keyed by `pikkuUserId` and holds the user session — shared across HTTP
|
|
20
|
+
* and channel transports).
|
|
21
|
+
*
|
|
22
|
+
* Use this for ephemeral, channel-local data: a per-socket subscription
|
|
23
|
+
* filter, a step in a connection-bound state machine, the last command sent
|
|
24
|
+
* on this socket, etc. It is cleared when the channel is removed.
|
|
25
|
+
*/
|
|
26
|
+
abstract setState(channelId: string, state: unknown): Promise<void> | void;
|
|
27
|
+
abstract getState(channelId: string): Promise<unknown | undefined> | unknown | undefined;
|
|
28
|
+
abstract clearState(channelId: string): Promise<void> | void;
|
|
16
29
|
}
|
|
@@ -80,6 +80,9 @@ export interface PikkuChannel<OpeningData, out Out> {
|
|
|
80
80
|
sendBinary(data: BinaryData): Promise<void> | void;
|
|
81
81
|
close(): Promise<void> | void;
|
|
82
82
|
state: 'initial' | 'open' | 'closed';
|
|
83
|
+
setState<T>(state: T): Promise<void> | void;
|
|
84
|
+
getState<T>(): Promise<T | undefined> | T | undefined;
|
|
85
|
+
clearState(): Promise<void> | void;
|
|
83
86
|
}
|
|
84
87
|
export interface PikkuChannelHandler<OpeningData = unknown, Out = unknown> {
|
|
85
88
|
send(message: Out, isBinary?: boolean): Promise<void> | void;
|
|
@@ -7,8 +7,26 @@ import { isProduction } from '../../../env.js';
|
|
|
7
7
|
import { getErrorResponse } from '../../../errors/error-handler.js';
|
|
8
8
|
import { handleHTTPError } from '../../../handle-error.js';
|
|
9
9
|
import { PikkuSessionService, createMiddlewareSessionWireProps, } from '../../../services/user-session-service.js';
|
|
10
|
+
import { combineMiddleware, runMiddleware } from '../../../middleware-runner.js';
|
|
10
11
|
import { runChannelLifecycleWithMiddleware } from '../channel-common.js';
|
|
11
|
-
import { getSingletonServices, getCreateWireServices, } from '../../../pikku-state.js';
|
|
12
|
+
import { getSingletonServices, getCreateWireServices, pikkuState, } from '../../../pikku-state.js';
|
|
13
|
+
const runUpgradeMiddleware = async (singletonServices, request, response, userSession) => {
|
|
14
|
+
const http = createHTTPWire(request, response);
|
|
15
|
+
const httpGroups = pikkuState(null, 'middleware', 'httpGroup');
|
|
16
|
+
const wireInheritedMiddleware = httpGroups['*']
|
|
17
|
+
? [{ type: 'http', route: '*' }]
|
|
18
|
+
: undefined;
|
|
19
|
+
const middleware = combineMiddleware('channel', `upgrade:${request.path()}`, {
|
|
20
|
+
wireInheritedMiddleware,
|
|
21
|
+
});
|
|
22
|
+
if (middleware.length > 0) {
|
|
23
|
+
await runMiddleware(singletonServices, {
|
|
24
|
+
http,
|
|
25
|
+
...createMiddlewareSessionWireProps(userSession),
|
|
26
|
+
}, middleware);
|
|
27
|
+
}
|
|
28
|
+
return http;
|
|
29
|
+
};
|
|
12
30
|
export const runLocalChannel = async ({ channelId, request, response, route, skipUserSession = false, respondWith404 = true, coerceDataFromSchema = true, logWarningsForStatusCodes = [], bubbleErrors = false, }) => {
|
|
13
31
|
const singletonServices = getSingletonServices();
|
|
14
32
|
const createWireServices = getCreateWireServices();
|
|
@@ -17,12 +35,12 @@ export const runLocalChannel = async ({ channelId, request, response, route, ski
|
|
|
17
35
|
let channelHandler;
|
|
18
36
|
const userSession = new PikkuSessionService(singletonServices.sessionStore);
|
|
19
37
|
let http;
|
|
20
|
-
if (request) {
|
|
21
|
-
http = createHTTPWire(request, response);
|
|
22
|
-
route = http?.request?.path();
|
|
23
|
-
}
|
|
24
38
|
let openingData, channelConfig, meta;
|
|
25
39
|
try {
|
|
40
|
+
if (request) {
|
|
41
|
+
http = await runUpgradeMiddleware(singletonServices, request, response, userSession);
|
|
42
|
+
route = request.path();
|
|
43
|
+
}
|
|
26
44
|
;
|
|
27
45
|
({ openingData, channelConfig, meta } = await openChannel({
|
|
28
46
|
channelId,
|
|
@@ -10,6 +10,7 @@ export class PikkuAbstractChannelHandler {
|
|
|
10
10
|
}
|
|
11
11
|
getChannel() {
|
|
12
12
|
if (!this.channel) {
|
|
13
|
+
let channelState;
|
|
13
14
|
this.channel = {
|
|
14
15
|
channelId: this.channelId,
|
|
15
16
|
openingData: this.openingData,
|
|
@@ -17,6 +18,13 @@ export class PikkuAbstractChannelHandler {
|
|
|
17
18
|
sendBinary: this.sendBinary.bind(this),
|
|
18
19
|
close: this.close.bind(this),
|
|
19
20
|
state: 'initial',
|
|
21
|
+
setState: (s) => {
|
|
22
|
+
channelState = s;
|
|
23
|
+
},
|
|
24
|
+
getState: () => channelState,
|
|
25
|
+
clearState: () => {
|
|
26
|
+
channelState = undefined;
|
|
27
|
+
},
|
|
20
28
|
};
|
|
21
29
|
}
|
|
22
30
|
return this.channel;
|
|
@@ -54,6 +54,9 @@ export const runChannelConnect = async ({ channelId, channelObject, request, res
|
|
|
54
54
|
channelHandlerFactory,
|
|
55
55
|
channelName: channelConfig.name,
|
|
56
56
|
});
|
|
57
|
+
channel.setState = (state) => channelStore.setState(channelId, state);
|
|
58
|
+
channel.getState = () => channelStore.getState(channelId);
|
|
59
|
+
channel.clearState = () => channelStore.clearState(channelId);
|
|
57
60
|
const wire = {
|
|
58
61
|
channel,
|
|
59
62
|
...createMiddlewareSessionWireProps(userSession),
|
|
@@ -116,6 +119,9 @@ export const runChannelDisconnect = async ({ channelId, channelStore, channelHan
|
|
|
116
119
|
openingData,
|
|
117
120
|
channelName,
|
|
118
121
|
});
|
|
122
|
+
channel.setState = (state) => channelStore.setState(channelId, state);
|
|
123
|
+
channel.getState = () => channelStore.getState(channelId);
|
|
124
|
+
channel.clearState = () => channelStore.clearState(channelId);
|
|
119
125
|
const userSession = new PikkuSessionService(singletonServices.sessionStore);
|
|
120
126
|
if (pikkuUserId) {
|
|
121
127
|
userSession.setPikkuUserId(pikkuUserId);
|
|
@@ -167,6 +173,9 @@ export const runChannelMessage = async ({ channelId, channelStore, channelHandle
|
|
|
167
173
|
openingData,
|
|
168
174
|
channelName,
|
|
169
175
|
});
|
|
176
|
+
channel.setState = (state) => channelStore.setState(channelId, state);
|
|
177
|
+
channel.getState = () => channelStore.getState(channelId);
|
|
178
|
+
channel.clearState = () => channelStore.clearState(channelId);
|
|
170
179
|
const userSession = new PikkuSessionService(singletonServices.sessionStore);
|
|
171
180
|
if (pikkuUserId) {
|
|
172
181
|
userSession.setPikkuUserId(pikkuUserId);
|
|
@@ -208,6 +208,7 @@ export async function runCLICommand({ program, commandPath, data, singletonServi
|
|
|
208
208
|
// Get the renderer
|
|
209
209
|
const renderer = programData?.renderers[commandId] || programData?.defaultRenderer;
|
|
210
210
|
// Create a CLI channel for progressive output
|
|
211
|
+
let cliState;
|
|
211
212
|
const channel = {
|
|
212
213
|
channelId: `cli:${program}:${commandId}`,
|
|
213
214
|
openingData: pluckedData,
|
|
@@ -225,6 +226,13 @@ export async function runCLICommand({ program, commandPath, data, singletonServi
|
|
|
225
226
|
}
|
|
226
227
|
},
|
|
227
228
|
state: 'open',
|
|
229
|
+
setState: (s) => {
|
|
230
|
+
cliState = s;
|
|
231
|
+
},
|
|
232
|
+
getState: () => cliState,
|
|
233
|
+
clearState: () => {
|
|
234
|
+
cliState = undefined;
|
|
235
|
+
},
|
|
228
236
|
};
|
|
229
237
|
const userSession = new PikkuSessionService(singletonServices.sessionStore);
|
|
230
238
|
const wire = {
|
|
@@ -252,10 +260,20 @@ export async function runCLICommand({ program, commandPath, data, singletonServi
|
|
|
252
260
|
sessionService: userSession,
|
|
253
261
|
packageName: currentCommand.packageName,
|
|
254
262
|
});
|
|
255
|
-
//
|
|
256
|
-
//
|
|
257
|
-
|
|
258
|
-
|
|
263
|
+
// Render the final result. `--json` / `--output json` routes the result
|
|
264
|
+
// through the global JSON renderer, but only for commands that opted into
|
|
265
|
+
// result-rendering by declaring a `render` — inline-printing commands (no
|
|
266
|
+
// per-command renderer) keep emitting their own output untouched.
|
|
267
|
+
// Skip if result is undefined (void functions handle their own output).
|
|
268
|
+
if (result !== undefined) {
|
|
269
|
+
const commandRenderer = programData?.renderers[commandId];
|
|
270
|
+
const jsonMode = data.json === true || data.output === 'json';
|
|
271
|
+
const finalRenderer = jsonMode && commandRenderer !== undefined
|
|
272
|
+
? defaultJSONRenderer
|
|
273
|
+
: (commandRenderer ?? programData?.defaultRenderer);
|
|
274
|
+
if (finalRenderer !== undefined) {
|
|
275
|
+
await Promise.resolve(finalRenderer(singletonServices, result, userSession.get()));
|
|
276
|
+
}
|
|
259
277
|
}
|
|
260
278
|
return result;
|
|
261
279
|
}
|
|
@@ -305,7 +323,8 @@ export async function executeCLI({ programName, args, createConfig, createSingle
|
|
|
305
323
|
if (parsed.errors.length > 0) {
|
|
306
324
|
// Check if any error is about an unknown command
|
|
307
325
|
const hasUnknownCommand = parsed.errors.some((error) => error.startsWith('Unknown command:') ||
|
|
308
|
-
error.startsWith('Command not found:')
|
|
326
|
+
error.startsWith('Command not found:') ||
|
|
327
|
+
error.startsWith('Missing subcommand:'));
|
|
309
328
|
if (hasUnknownCommand) {
|
|
310
329
|
// Show help instead of error for unknown commands
|
|
311
330
|
const helpText = generateCommandHelp(programName, allCLIMeta, parsed.commandPath);
|
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
function toCamelCase(str) {
|
|
3
3
|
return str.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
4
4
|
}
|
|
5
|
+
/** Convert camelCase to kebab-case for display: "autoApply" → "auto-apply".
|
|
6
|
+
* Flags are stored camelCase (matching the function input field) but shown
|
|
7
|
+
* kebab; the parser accepts both forms via toCamelCase. */
|
|
8
|
+
function toKebabCase(str) {
|
|
9
|
+
return str.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase();
|
|
10
|
+
}
|
|
5
11
|
/**
|
|
6
12
|
* Parses raw CLI arguments into structured data for a specific program
|
|
7
13
|
*/
|
|
@@ -77,6 +83,15 @@ export function parseCLIArguments(args, programName, allMeta) {
|
|
|
77
83
|
}
|
|
78
84
|
return result;
|
|
79
85
|
}
|
|
86
|
+
// A group/parent command — has subcommands but no runnable function of its
|
|
87
|
+
// own — cannot be executed directly. Surface a routable error so the runner
|
|
88
|
+
// shows its subcommand help instead of attempting to run a missing function.
|
|
89
|
+
if (!commandMeta.pikkuFuncId &&
|
|
90
|
+
commandMeta.subcommands &&
|
|
91
|
+
Object.keys(commandMeta.subcommands).length > 0) {
|
|
92
|
+
result.errors.push(`Missing subcommand: ${result.commandPath.join(' ')}`);
|
|
93
|
+
return result;
|
|
94
|
+
}
|
|
80
95
|
// Collect all available options (global + inherited)
|
|
81
96
|
const availableOptions = collectAvailableOptions(meta, result.commandPath);
|
|
82
97
|
// Parse remaining arguments as positionals and options
|
|
@@ -289,7 +304,7 @@ function applyOptionDefaults(optionDefs, options, result) {
|
|
|
289
304
|
}
|
|
290
305
|
// Check required
|
|
291
306
|
if (def.required && !(name in options)) {
|
|
292
|
-
result.errors.push(`Missing required option: --${name}`);
|
|
307
|
+
result.errors.push(`Missing required option: --${toKebabCase(name)}`);
|
|
293
308
|
}
|
|
294
309
|
// Validate choices
|
|
295
310
|
if (def.choices && name in options) {
|
|
@@ -297,12 +312,12 @@ function applyOptionDefaults(optionDefs, options, result) {
|
|
|
297
312
|
if (Array.isArray(value)) {
|
|
298
313
|
for (const v of value) {
|
|
299
314
|
if (!def.choices.includes(v)) {
|
|
300
|
-
result.errors.push(`Invalid value for --${name}: ${v}. Valid choices: ${def.choices.join(', ')}`);
|
|
315
|
+
result.errors.push(`Invalid value for --${toKebabCase(name)}: ${v}. Valid choices: ${def.choices.join(', ')}`);
|
|
301
316
|
}
|
|
302
317
|
}
|
|
303
318
|
}
|
|
304
319
|
else if (!def.choices.includes(value)) {
|
|
305
|
-
result.errors.push(`Invalid value for --${name}: ${value}. Valid choices: ${def.choices.join(', ')}`);
|
|
320
|
+
result.errors.push(`Invalid value for --${toKebabCase(name)}: ${value}. Valid choices: ${def.choices.join(', ')}`);
|
|
306
321
|
}
|
|
307
322
|
}
|
|
308
323
|
}
|
|
@@ -390,7 +405,7 @@ function formatOptions(options, lines) {
|
|
|
390
405
|
else {
|
|
391
406
|
line += ' ';
|
|
392
407
|
}
|
|
393
|
-
line += `--${name}`;
|
|
408
|
+
line += `--${toKebabCase(name)}`;
|
|
394
409
|
if (opt.default !== undefined && typeof opt.default !== 'boolean') {
|
|
395
410
|
line += ` <value>`;
|
|
396
411
|
}
|