@pikku/core 0.12.2 → 0.12.3
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 +18 -0
- package/dist/crypto-utils.d.ts +10 -0
- package/dist/crypto-utils.js +62 -0
- package/dist/function/function-runner.js +4 -4
- package/dist/function/functions.types.d.ts +26 -1
- package/dist/function/functions.types.js +16 -0
- package/dist/handle-error.d.ts +1 -1
- package/dist/handle-error.js +7 -3
- package/dist/index.d.ts +14 -2
- package/dist/index.js +1 -1
- package/dist/pikku-state.js +3 -1
- package/dist/schema.js +1 -1
- package/dist/services/in-memory-workflow-service.d.ts +8 -2
- package/dist/services/in-memory-workflow-service.js +28 -1
- package/dist/services/workflow-service.d.ts +8 -2
- package/dist/testing/index.d.ts +2 -0
- package/dist/testing/index.js +1 -0
- package/dist/testing/service-tests.d.ts +31 -0
- package/dist/testing/service-tests.js +598 -0
- package/dist/types/core.types.d.ts +9 -1
- package/dist/types/state.types.d.ts +4 -2
- package/dist/utils/hash.d.ts +2 -0
- package/dist/utils/hash.js +23 -0
- package/dist/wirings/ai-agent/agent-dynamic-workflow.d.ts +6 -0
- package/dist/wirings/ai-agent/agent-dynamic-workflow.js +348 -0
- package/dist/wirings/ai-agent/ai-agent-memory.d.ts +1 -1
- package/dist/wirings/ai-agent/ai-agent-memory.js +6 -5
- package/dist/wirings/ai-agent/ai-agent-prepare.d.ts +10 -7
- package/dist/wirings/ai-agent/ai-agent-prepare.js +127 -47
- package/dist/wirings/ai-agent/ai-agent-runner.d.ts +4 -0
- package/dist/wirings/ai-agent/ai-agent-runner.js +381 -27
- package/dist/wirings/ai-agent/ai-agent-stream.d.ts +7 -2
- package/dist/wirings/ai-agent/ai-agent-stream.js +109 -46
- package/dist/wirings/ai-agent/ai-agent.types.d.ts +22 -1
- package/dist/wirings/ai-agent/index.d.ts +1 -2
- package/dist/wirings/ai-agent/index.js +1 -2
- package/dist/wirings/channel/channel-handler.js +5 -1
- package/dist/wirings/channel/channel.types.d.ts +1 -7
- package/dist/wirings/channel/local/local-channel-handler.d.ts +1 -1
- package/dist/wirings/channel/local/local-channel-handler.js +5 -3
- package/dist/wirings/channel/local/local-channel-runner.js +14 -4
- package/dist/wirings/channel/serverless/serverless-channel-runner.js +4 -1
- package/dist/wirings/cli/cli-runner.js +10 -2
- package/dist/wirings/cli/cli.types.d.ts +1 -0
- package/dist/wirings/http/http-routes.js +0 -1
- package/dist/wirings/http/http-runner.d.ts +1 -1
- package/dist/wirings/http/http-runner.js +25 -3
- package/dist/wirings/http/http.types.d.ts +3 -11
- package/dist/wirings/http/index.d.ts +2 -1
- package/dist/wirings/http/index.js +1 -0
- package/dist/wirings/http/pikku-fetch-http-request.d.ts +1 -0
- package/dist/wirings/http/pikku-fetch-http-request.js +3 -0
- package/dist/wirings/http/pikku-fetch-http-response.d.ts +1 -0
- package/dist/wirings/http/pikku-fetch-http-response.js +4 -4
- package/dist/wirings/http/web-request.d.ts +12 -0
- package/dist/wirings/http/web-request.js +85 -0
- package/dist/wirings/mcp/mcp-endpoint-registry.js +1 -1
- package/dist/wirings/mcp/mcp-runner.js +13 -1
- package/dist/wirings/rpc/rpc-runner.d.ts +30 -6
- package/dist/wirings/rpc/rpc-runner.js +36 -14
- package/dist/wirings/rpc/rpc-types.d.ts +6 -1
- package/dist/wirings/rpc/wire-addon.d.ts +1 -0
- package/dist/wirings/rpc/wire-addon.js +1 -1
- package/dist/wirings/scheduler/scheduler-runner.js +1 -0
- package/dist/wirings/workflow/graph/graph-validation.d.ts +3 -0
- package/dist/wirings/workflow/graph/graph-validation.js +175 -0
- package/dist/wirings/workflow/graph/index.d.ts +1 -0
- package/dist/wirings/workflow/graph/index.js +1 -0
- package/dist/wirings/workflow/index.d.ts +1 -1
- package/dist/wirings/workflow/pikku-workflow-service.d.ts +8 -2
- package/dist/wirings/workflow/pikku-workflow-service.js +1 -2
- package/dist/wirings/workflow/workflow.types.d.ts +10 -1
- package/package.json +4 -2
- package/src/crypto-utils.test.ts +99 -1
- package/src/crypto-utils.ts +114 -0
- package/src/function/function-runner.ts +5 -5
- package/src/function/functions.types.ts +38 -1
- package/src/handle-error.test.ts +65 -2
- package/src/handle-error.ts +8 -4
- package/src/index.ts +14 -0
- package/src/pikku-state.ts +3 -1
- package/src/schema.ts +1 -1
- package/src/services/in-memory-workflow-service.ts +45 -2
- package/src/services/workflow-service.ts +12 -1
- package/src/testing/index.ts +2 -0
- package/src/testing/service-tests.ts +873 -0
- package/src/types/core.types.ts +9 -1
- package/src/types/state.types.ts +4 -2
- package/src/utils/hash.test.ts +68 -0
- package/src/utils/hash.ts +26 -0
- package/src/wirings/ai-agent/agent-dynamic-workflow.ts +469 -0
- package/src/wirings/ai-agent/ai-agent-memory.ts +7 -6
- package/src/wirings/ai-agent/ai-agent-prepare.ts +175 -61
- package/src/wirings/ai-agent/ai-agent-runner.test.ts +3 -1
- package/src/wirings/ai-agent/ai-agent-runner.ts +553 -31
- package/src/wirings/ai-agent/ai-agent-stream.test.ts +209 -2
- package/src/wirings/ai-agent/ai-agent-stream.ts +182 -89
- package/src/wirings/ai-agent/ai-agent.types.ts +23 -1
- package/src/wirings/ai-agent/index.ts +1 -5
- package/src/wirings/channel/channel-handler.ts +11 -2
- package/src/wirings/channel/channel.types.ts +1 -7
- package/src/wirings/channel/local/local-channel-handler.ts +5 -3
- package/src/wirings/channel/local/local-channel-runner.ts +17 -7
- package/src/wirings/channel/serverless/serverless-channel-runner.ts +4 -1
- package/src/wirings/cli/cli-runner.ts +12 -2
- package/src/wirings/cli/cli.types.ts +1 -0
- package/src/wirings/http/http-routes.ts +0 -1
- package/src/wirings/http/http-runner.ts +35 -10
- package/src/wirings/http/http.types.ts +3 -12
- package/src/wirings/http/index.ts +3 -0
- package/src/wirings/http/pikku-fetch-http-request.ts +4 -0
- package/src/wirings/http/pikku-fetch-http-response.ts +4 -4
- package/src/wirings/http/web-request.test.ts +236 -0
- package/src/wirings/http/web-request.ts +104 -0
- package/src/wirings/mcp/mcp-endpoint-registry.ts +1 -1
- package/src/wirings/mcp/mcp-runner.ts +30 -12
- package/src/wirings/rpc/rpc-runner.ts +45 -24
- package/src/wirings/rpc/rpc-types.ts +6 -1
- package/src/wirings/rpc/wire-addon.ts +2 -1
- package/src/wirings/scheduler/scheduler-runner.ts +1 -0
- package/src/wirings/workflow/graph/graph-validation.test.ts +170 -0
- package/src/wirings/workflow/graph/graph-validation.ts +237 -0
- package/src/wirings/workflow/graph/index.ts +5 -0
- package/src/wirings/workflow/index.ts +1 -0
- package/src/wirings/workflow/pikku-workflow-service.ts +14 -3
- package/src/wirings/workflow/workflow.types.ts +9 -1
- package/tsconfig.tsbuildinfo +1 -1
- package/dist/wirings/ai-agent/ai-agent-assistant-ui.d.ts +0 -5
- package/dist/wirings/ai-agent/ai-agent-assistant-ui.js +0 -168
- package/src/wirings/ai-agent/ai-agent-assistant-ui.test.ts +0 -363
- package/src/wirings/ai-agent/ai-agent-assistant-ui.ts +0 -238
|
@@ -3,7 +3,6 @@ import { combineChannelMiddleware, wrapChannelWithMiddleware, } from '../channel
|
|
|
3
3
|
import { randomUUID } from 'crypto';
|
|
4
4
|
import { parseWorkingMemory, deepMergeWorkingMemory, resolveMemoryServices, loadContextMessages, trimMessages, } from './ai-agent-memory.js';
|
|
5
5
|
import { prepareAgentRun, resolveAgent, buildInstructions, buildToolDefs, createScopedChannel, ToolApprovalRequired, } from './ai-agent-prepare.js';
|
|
6
|
-
import { createAssistantUIChannel, parseAssistantUIInput, } from './ai-agent-assistant-ui.js';
|
|
7
6
|
import { resolveModelConfig } from './ai-agent-model-config.js';
|
|
8
7
|
function createPersistingChannel(parent, storage, threadId) {
|
|
9
8
|
let fullText = '';
|
|
@@ -181,31 +180,52 @@ async function runStreamStepLoop(params) {
|
|
|
181
180
|
}
|
|
182
181
|
if (stepResult.toolCalls.length === 0)
|
|
183
182
|
break;
|
|
184
|
-
const
|
|
185
|
-
if (
|
|
186
|
-
|
|
183
|
+
const approvalsNeeded = checkForApprovals(stepResult, runnerParams.tools, runId);
|
|
184
|
+
if (approvalsNeeded.length > 0) {
|
|
185
|
+
// For each approval, call approvalDescriptionFn if available
|
|
186
|
+
for (const approval of approvalsNeeded) {
|
|
187
|
+
const toolDef = runnerParams.tools.find((t) => t.name === approval.toolName);
|
|
188
|
+
if (toolDef?.approvalDescriptionFn && !approval.reason) {
|
|
189
|
+
try {
|
|
190
|
+
approval.reason = await toolDef.approvalDescriptionFn(approval.args);
|
|
191
|
+
}
|
|
192
|
+
catch {
|
|
193
|
+
// If description generation fails, continue without it
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
return { outcome: 'approval', approvals: approvalsNeeded };
|
|
187
198
|
}
|
|
188
199
|
appendStepMessages(runnerParams, stepResult);
|
|
189
200
|
}
|
|
190
201
|
return { outcome: 'done' };
|
|
191
202
|
}
|
|
192
|
-
function
|
|
203
|
+
export function checkForApprovals(stepResult, tools, runId) {
|
|
204
|
+
const approvals = [];
|
|
193
205
|
for (const tc of stepResult.toolCalls) {
|
|
194
206
|
const toolDef = tools.find((t) => t.name === tc.toolName);
|
|
195
207
|
if (toolDef?.needsApproval) {
|
|
196
|
-
|
|
208
|
+
approvals.push(new ToolApprovalRequired(tc.toolCallId, tc.toolName, tc.args));
|
|
209
|
+
continue;
|
|
197
210
|
}
|
|
198
211
|
const tr = stepResult.toolResults.find((r) => r.toolCallId === tc.toolCallId);
|
|
199
212
|
if (tr?.result &&
|
|
200
213
|
typeof tr.result === 'object' &&
|
|
201
214
|
'__approvalRequired' in tr.result) {
|
|
202
215
|
const r = tr.result;
|
|
203
|
-
|
|
216
|
+
if (r.subApprovals?.length) {
|
|
217
|
+
for (const sub of r.subApprovals) {
|
|
218
|
+
approvals.push(new ToolApprovalRequired(sub.toolCallId, r.toolName, r.args, undefined, sub.toolName, sub.args, r.agentRunId));
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
else {
|
|
222
|
+
approvals.push(new ToolApprovalRequired(tc.toolCallId, r.toolName, r.args, r.reason, r.displayToolName, r.displayArgs, r.agentRunId));
|
|
223
|
+
}
|
|
204
224
|
}
|
|
205
225
|
}
|
|
206
|
-
return
|
|
226
|
+
return approvals;
|
|
207
227
|
}
|
|
208
|
-
function appendStepMessages(runnerParams, stepResult) {
|
|
228
|
+
export function appendStepMessages(runnerParams, stepResult) {
|
|
209
229
|
const assistantMsg = {
|
|
210
230
|
id: randomUUID(),
|
|
211
231
|
role: 'assistant',
|
|
@@ -234,10 +254,10 @@ function appendStepMessages(runnerParams, stepResult) {
|
|
|
234
254
|
runnerParams.messages.push(toolMsg);
|
|
235
255
|
}
|
|
236
256
|
}
|
|
237
|
-
function
|
|
257
|
+
function handleApprovals(approvals, runId, channel, aiRunState, persistingChannel) {
|
|
238
258
|
return (async () => {
|
|
239
259
|
await persistingChannel.flush();
|
|
240
|
-
const
|
|
260
|
+
const pendingApprovals = approvals.map((err) => err.agentRunId
|
|
241
261
|
? {
|
|
242
262
|
type: 'agent-call',
|
|
243
263
|
toolCallId: err.toolCallId,
|
|
@@ -251,40 +271,32 @@ function handleApproval(err, runId, channel, aiRunState, persistingChannel) {
|
|
|
251
271
|
toolCallId: err.toolCallId,
|
|
252
272
|
toolName: err.toolName,
|
|
253
273
|
args: err.args,
|
|
254
|
-
};
|
|
274
|
+
});
|
|
255
275
|
await aiRunState.updateRun(runId, {
|
|
256
276
|
status: 'suspended',
|
|
257
277
|
suspendReason: 'approval',
|
|
258
|
-
pendingApprovals
|
|
278
|
+
pendingApprovals,
|
|
259
279
|
});
|
|
260
|
-
const
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
280
|
+
for (const err of approvals) {
|
|
281
|
+
const approvalEvent = {
|
|
282
|
+
type: 'approval-request',
|
|
283
|
+
toolCallId: err.toolCallId,
|
|
284
|
+
toolName: err.displayToolName ?? err.toolName,
|
|
285
|
+
args: err.displayArgs ?? err.args,
|
|
286
|
+
reason: err.reason,
|
|
287
|
+
runId,
|
|
288
|
+
};
|
|
289
|
+
channel.send(approvalEvent);
|
|
290
|
+
}
|
|
269
291
|
channel.send({ type: 'done' });
|
|
270
292
|
channel.close();
|
|
271
293
|
})();
|
|
272
294
|
}
|
|
273
295
|
export async function streamAIAgent(agentName, input, channel, params, agentSessionMap, options) {
|
|
274
296
|
const sessionMap = agentSessionMap ?? new Map();
|
|
275
|
-
const
|
|
276
|
-
const inputRecord = input;
|
|
277
|
-
const useUIMessageStream = resolvedAgentForProtocol.protocol === 'ui-message-stream' ||
|
|
278
|
-
Array.isArray(inputRecord.messages);
|
|
279
|
-
let normalizedInput;
|
|
280
|
-
if (useUIMessageStream) {
|
|
281
|
-
normalizedInput = parseAssistantUIInput(inputRecord);
|
|
282
|
-
channel = createAssistantUIChannel(channel);
|
|
283
|
-
}
|
|
284
|
-
else {
|
|
285
|
-
normalizedInput = input;
|
|
286
|
-
}
|
|
297
|
+
const normalizedInput = input;
|
|
287
298
|
const streamContext = { channel, options };
|
|
299
|
+
// delegateState is attached after prepareAgentRun resolves the agent config
|
|
288
300
|
const { agent, packageName, resolvedName, agentRunner, storage, memoryConfig, threadId, userMessage, runnerParams, maxSteps, missingRpcs, workingMemorySchemaName, } = await prepareAgentRun(agentName, normalizedInput, params, sessionMap, streamContext);
|
|
289
301
|
const singletonServices = getSingletonServices();
|
|
290
302
|
const { aiRunState } = singletonServices;
|
|
@@ -305,7 +317,7 @@ export async function streamAIAgent(agentName, input, channel, params, agentSess
|
|
|
305
317
|
});
|
|
306
318
|
channel.send({ type: 'suspended', reason: 'rpc-missing', missingRpcs });
|
|
307
319
|
channel.send({ type: 'done' });
|
|
308
|
-
return;
|
|
320
|
+
return '';
|
|
309
321
|
}
|
|
310
322
|
const aiMiddlewares = agent.aiMiddleware ?? [];
|
|
311
323
|
let modifiedMessages = runnerParams.messages;
|
|
@@ -369,7 +381,28 @@ export async function streamAIAgent(agentName, input, channel, params, agentSess
|
|
|
369
381
|
const wrappedChannel = allChannelMiddleware.length > 0
|
|
370
382
|
? wrapChannelWithMiddleware({ channel }, singletonServices, allChannelMiddleware).channel
|
|
371
383
|
: channel;
|
|
372
|
-
|
|
384
|
+
// In delegate mode (default), suppress parent's text from reaching the client
|
|
385
|
+
// AFTER a sub-agent has been called. If the parent responds directly (no delegation),
|
|
386
|
+
// its text goes through normally. Sub-agent text bypasses this path entirely
|
|
387
|
+
// (goes through subChannel → channel directly).
|
|
388
|
+
const isDelegateMode = agent.agentMode !== 'supervise' && meta.agents?.length;
|
|
389
|
+
const delegateState = { delegated: false };
|
|
390
|
+
if (isDelegateMode) {
|
|
391
|
+
streamContext.delegateState = delegateState;
|
|
392
|
+
}
|
|
393
|
+
const outputChannel = isDelegateMode
|
|
394
|
+
? {
|
|
395
|
+
...wrappedChannel,
|
|
396
|
+
send: (event) => {
|
|
397
|
+
if (delegateState.delegated &&
|
|
398
|
+
(event.type === 'text-delta' || event.type === 'reasoning-delta'))
|
|
399
|
+
return;
|
|
400
|
+
wrappedChannel.send(event);
|
|
401
|
+
},
|
|
402
|
+
delegateState,
|
|
403
|
+
}
|
|
404
|
+
: wrappedChannel;
|
|
405
|
+
const persistingChannel = createPersistingChannel(outputChannel, storage, threadId);
|
|
373
406
|
try {
|
|
374
407
|
const loopResult = await runStreamStepLoop({
|
|
375
408
|
agent,
|
|
@@ -382,12 +415,13 @@ export async function streamAIAgent(agentName, input, channel, params, agentSess
|
|
|
382
415
|
aiMiddlewares,
|
|
383
416
|
});
|
|
384
417
|
if (loopResult.outcome === 'approval') {
|
|
385
|
-
await
|
|
386
|
-
return;
|
|
418
|
+
await handleApprovals(loopResult.approvals, runId, channel, aiRunState, persistingChannel);
|
|
419
|
+
return persistingChannel.fullText;
|
|
387
420
|
}
|
|
388
421
|
await postStreamCleanup(persistingChannel, aiMiddlewares, singletonServices, storage, memoryConfig, threadId, workingMemorySchemaName, runnerParams.messages, aiRunState, runId);
|
|
389
422
|
channel.send({ type: 'done' });
|
|
390
423
|
channel.close();
|
|
424
|
+
return persistingChannel.fullText;
|
|
391
425
|
}
|
|
392
426
|
catch (err) {
|
|
393
427
|
for (const mw of aiMiddlewares) {
|
|
@@ -404,13 +438,17 @@ export async function streamAIAgent(agentName, input, channel, params, agentSess
|
|
|
404
438
|
}
|
|
405
439
|
}
|
|
406
440
|
}
|
|
407
|
-
await aiRunState.updateRun(runId, {
|
|
441
|
+
await aiRunState.updateRun(runId, {
|
|
442
|
+
status: 'failed',
|
|
443
|
+
errorMessage: err instanceof Error ? err.message : String(err),
|
|
444
|
+
});
|
|
408
445
|
channel.send({
|
|
409
446
|
type: 'error',
|
|
410
447
|
message: err instanceof Error ? err.message : String(err),
|
|
411
448
|
});
|
|
412
449
|
channel.send({ type: 'done' });
|
|
413
450
|
channel.close();
|
|
451
|
+
return persistingChannel.fullText;
|
|
414
452
|
}
|
|
415
453
|
}
|
|
416
454
|
export async function resumeAIAgent(input, channel, params, options) {
|
|
@@ -439,7 +477,7 @@ export async function resumeAIAgent(input, channel, params, options) {
|
|
|
439
477
|
if (pending.type === 'agent-call') {
|
|
440
478
|
await aiRunState.updateRun(pending.agentRunId, { status: 'failed' });
|
|
441
479
|
}
|
|
442
|
-
const denialResult = '
|
|
480
|
+
const denialResult = 'The user explicitly declined this action. Inform them that it was declined and do not retry.';
|
|
443
481
|
if (storage) {
|
|
444
482
|
await storage.saveMessages(run.threadId, [
|
|
445
483
|
{
|
|
@@ -458,6 +496,15 @@ export async function resumeAIAgent(input, channel, params, options) {
|
|
|
458
496
|
},
|
|
459
497
|
]);
|
|
460
498
|
}
|
|
499
|
+
// Check remaining pending approvals
|
|
500
|
+
const updatedRun = await aiRunState.getRun(run.runId);
|
|
501
|
+
const remaining = updatedRun?.pendingApprovals ?? [];
|
|
502
|
+
if (remaining.length > 0) {
|
|
503
|
+
// Still waiting for more approvals - don't continue step loop
|
|
504
|
+
channel.send({ type: 'done' });
|
|
505
|
+
channel.close();
|
|
506
|
+
return;
|
|
507
|
+
}
|
|
461
508
|
await aiRunState.updateRun(run.runId, { status: 'running' });
|
|
462
509
|
await continueAfterToolResult(run, agent, packageName, resolvedName, storage, memoryConfig, agentRunner, channel, params, aiRunState, options);
|
|
463
510
|
return;
|
|
@@ -467,7 +514,8 @@ export async function resumeAIAgent(input, channel, params, options) {
|
|
|
467
514
|
if (!subRun) {
|
|
468
515
|
throw new Error(`Sub-agent run not found: ${pending.agentRunId}`);
|
|
469
516
|
}
|
|
470
|
-
const subPending = subRun.pendingApprovals?.
|
|
517
|
+
const subPending = subRun.pendingApprovals?.find((p) => p.toolCallId === input.toolCallId) ??
|
|
518
|
+
subRun.pendingApprovals?.[0];
|
|
471
519
|
if (!subPending) {
|
|
472
520
|
throw new Error(`No pending approval on sub-agent run ${pending.agentRunId}`);
|
|
473
521
|
}
|
|
@@ -512,18 +560,20 @@ export async function resumeAIAgent(input, channel, params, options) {
|
|
|
512
560
|
options: { ...options, requiresToolApproval: false },
|
|
513
561
|
};
|
|
514
562
|
const aiMiddlewaresForResume = agent.aiMiddleware ?? [];
|
|
515
|
-
const { tools } = buildToolDefs(params, new Map(), run.resourceId, resolvedName, packageName, streamContext, aiMiddlewaresForResume);
|
|
563
|
+
const { tools } = await buildToolDefs(params, new Map(), run.resourceId, resolvedName, packageName, streamContext, aiMiddlewaresForResume);
|
|
516
564
|
const matchingTool = tools.find((t) => t.name === pending.toolName);
|
|
517
565
|
if (!matchingTool) {
|
|
518
566
|
throw new Error(`Tool "${pending.toolName}" not found in agent definition`);
|
|
519
567
|
}
|
|
520
568
|
const toolArgs = typeof pending.args === 'string' ? JSON.parse(pending.args) : pending.args;
|
|
521
569
|
let toolResult;
|
|
570
|
+
let isError = false;
|
|
522
571
|
try {
|
|
523
572
|
toolResult = await matchingTool.execute(toolArgs);
|
|
524
573
|
}
|
|
525
574
|
catch (execErr) {
|
|
526
575
|
toolResult = `Error: ${execErr instanceof Error ? execErr.message : String(execErr)}`;
|
|
576
|
+
isError = true;
|
|
527
577
|
}
|
|
528
578
|
const resultStr = typeof toolResult === 'string' ? toolResult : JSON.stringify(toolResult);
|
|
529
579
|
if (storage) {
|
|
@@ -547,8 +597,18 @@ export async function resumeAIAgent(input, channel, params, options) {
|
|
|
547
597
|
toolCallId: input.toolCallId,
|
|
548
598
|
toolName: pending.toolName,
|
|
549
599
|
result: toolResult,
|
|
600
|
+
...(isError ? { isError: true } : {}),
|
|
550
601
|
});
|
|
551
602
|
}
|
|
603
|
+
// Check remaining pending approvals after processing this one
|
|
604
|
+
const updatedRun = await aiRunState.getRun(run.runId);
|
|
605
|
+
const remaining = updatedRun?.pendingApprovals ?? [];
|
|
606
|
+
if (remaining.length > 0) {
|
|
607
|
+
// Still waiting for more approvals - don't continue step loop
|
|
608
|
+
channel.send({ type: 'done' });
|
|
609
|
+
channel.close();
|
|
610
|
+
return;
|
|
611
|
+
}
|
|
552
612
|
await aiRunState.updateRun(run.runId, { status: 'running' });
|
|
553
613
|
await continueAfterToolResult(run, agent, packageName, resolvedName, storage, memoryConfig, agentRunner, channel, params, aiRunState, options);
|
|
554
614
|
}
|
|
@@ -568,7 +628,7 @@ async function continueAfterToolResult(run, agent, packageName, resolvedName, st
|
|
|
568
628
|
const contextMessages = await loadContextMessages(memoryConfig, storage, { message: '', threadId: run.threadId, resourceId: run.resourceId }, workingMemoryJsonSchema);
|
|
569
629
|
const allMessages = [...contextMessages, ...messages];
|
|
570
630
|
const trimmedMessages = trimMessages(allMessages);
|
|
571
|
-
const instructions = buildInstructions(resolvedName, packageName);
|
|
631
|
+
const instructions = await buildInstructions(resolvedName, packageName);
|
|
572
632
|
const aiMiddlewares = agent.aiMiddleware ?? [];
|
|
573
633
|
let modifiedMessages = trimmedMessages;
|
|
574
634
|
let modifiedInstructions = instructions;
|
|
@@ -617,7 +677,7 @@ async function continueAfterToolResult(run, agent, packageName, resolvedName, st
|
|
|
617
677
|
: channel;
|
|
618
678
|
const persistingChannel = createPersistingChannel(wrappedChannel, storage, run.threadId);
|
|
619
679
|
const streamContext = { channel, options };
|
|
620
|
-
const resumeTools = buildToolDefs(params, new Map(), run.resourceId, resolvedName, packageName, streamContext, aiMiddlewares).tools;
|
|
680
|
+
const resumeTools = (await buildToolDefs(params, new Map(), run.resourceId, resolvedName, packageName, streamContext, aiMiddlewares)).tools;
|
|
621
681
|
const resolved = resolveModelConfig(resolvedName, agent);
|
|
622
682
|
const maxSteps = resolved.maxSteps ?? 10;
|
|
623
683
|
const runnerParams = {
|
|
@@ -644,7 +704,7 @@ async function continueAfterToolResult(run, agent, packageName, resolvedName, st
|
|
|
644
704
|
aiMiddlewares,
|
|
645
705
|
});
|
|
646
706
|
if (loopResult.outcome === 'approval') {
|
|
647
|
-
await
|
|
707
|
+
await handleApprovals(loopResult.approvals, run.runId, channel, aiRunState, persistingChannel);
|
|
648
708
|
return;
|
|
649
709
|
}
|
|
650
710
|
await postStreamCleanup(persistingChannel, aiMiddlewares, singletonServices, storage, memoryConfig, run.threadId, workingMemorySchemaName, runnerParams.messages, aiRunState, run.runId);
|
|
@@ -666,7 +726,10 @@ async function continueAfterToolResult(run, agent, packageName, resolvedName, st
|
|
|
666
726
|
}
|
|
667
727
|
}
|
|
668
728
|
}
|
|
669
|
-
await aiRunState.updateRun(run.runId, {
|
|
729
|
+
await aiRunState.updateRun(run.runId, {
|
|
730
|
+
status: 'failed',
|
|
731
|
+
errorMessage: err instanceof Error ? err.message : String(err),
|
|
732
|
+
});
|
|
670
733
|
channel.send({
|
|
671
734
|
type: 'error',
|
|
672
735
|
message: err instanceof Error ? err.message : String(err),
|
|
@@ -66,6 +66,8 @@ export interface AIAgentInput {
|
|
|
66
66
|
threadId: string;
|
|
67
67
|
resourceId: string;
|
|
68
68
|
attachments?: AIAgentInputAttachment[];
|
|
69
|
+
model?: string;
|
|
70
|
+
temperature?: number;
|
|
69
71
|
}
|
|
70
72
|
export interface AIAgentOutput {
|
|
71
73
|
runId: string;
|
|
@@ -77,6 +79,14 @@ export interface AIAgentOutput {
|
|
|
77
79
|
inputTokens: number;
|
|
78
80
|
outputTokens: number;
|
|
79
81
|
};
|
|
82
|
+
status?: 'completed' | 'suspended';
|
|
83
|
+
pendingApprovals?: Array<{
|
|
84
|
+
toolCallId: string;
|
|
85
|
+
toolName: string;
|
|
86
|
+
args: unknown;
|
|
87
|
+
reason?: string;
|
|
88
|
+
runId: string;
|
|
89
|
+
}>;
|
|
80
90
|
}
|
|
81
91
|
export interface AIAgentToolDef {
|
|
82
92
|
name: string;
|
|
@@ -84,6 +94,7 @@ export interface AIAgentToolDef {
|
|
|
84
94
|
inputSchema: Record<string, unknown>;
|
|
85
95
|
execute: (input: unknown) => Promise<unknown>;
|
|
86
96
|
needsApproval?: boolean;
|
|
97
|
+
approvalDescriptionFn?: (input: unknown) => Promise<string>;
|
|
87
98
|
}
|
|
88
99
|
export interface PikkuAIMiddlewareHooks<State extends Record<string, unknown> = Record<string, unknown>, Services = any> {
|
|
89
100
|
modifyInput?: (services: Services, ctx: {
|
|
@@ -177,13 +188,14 @@ export type CoreAIAgent<PikkuPermission = CorePikkuPermission<any, any>, PikkuMi
|
|
|
177
188
|
temperature?: number;
|
|
178
189
|
tools?: unknown[];
|
|
179
190
|
agents?: unknown[];
|
|
191
|
+
agentMode?: 'delegate' | 'supervise';
|
|
180
192
|
memory?: AIAgentMemoryConfig;
|
|
181
193
|
maxSteps?: number;
|
|
182
194
|
toolChoice?: 'auto' | 'required' | 'none';
|
|
195
|
+
dynamicWorkflows?: 'read' | 'always' | 'ask';
|
|
183
196
|
input?: unknown;
|
|
184
197
|
output?: unknown;
|
|
185
198
|
tags?: string[];
|
|
186
|
-
protocol?: 'ui-message-stream';
|
|
187
199
|
prepareStep?: (ctx: {
|
|
188
200
|
stepNumber: number;
|
|
189
201
|
messages: AIMessage[];
|
|
@@ -268,6 +280,12 @@ export type AIStreamEvent = {
|
|
|
268
280
|
type: 'audio-done';
|
|
269
281
|
agent?: string;
|
|
270
282
|
session?: string;
|
|
283
|
+
} | {
|
|
284
|
+
type: 'workflow-created';
|
|
285
|
+
workflowName: string;
|
|
286
|
+
graph: any;
|
|
287
|
+
agent?: string;
|
|
288
|
+
session?: string;
|
|
271
289
|
} | {
|
|
272
290
|
type: 'suspended';
|
|
273
291
|
reason: 'rpc-missing';
|
|
@@ -296,6 +314,7 @@ export interface AgentRunState {
|
|
|
296
314
|
threadId: string;
|
|
297
315
|
resourceId: string;
|
|
298
316
|
status: 'running' | 'suspended' | 'completed' | 'failed';
|
|
317
|
+
errorMessage?: string;
|
|
299
318
|
suspendReason?: 'approval' | 'rpc-missing';
|
|
300
319
|
missingRpcs?: string[];
|
|
301
320
|
pendingApprovals?: PendingApproval[];
|
|
@@ -313,6 +332,7 @@ export interface AgentRunRow {
|
|
|
313
332
|
threadId: string;
|
|
314
333
|
resourceId: string;
|
|
315
334
|
status: string;
|
|
335
|
+
errorMessage?: string;
|
|
316
336
|
suspendReason?: string;
|
|
317
337
|
missingRpcs?: string[];
|
|
318
338
|
usageInputTokens: number;
|
|
@@ -343,4 +363,5 @@ export type AIAgentMeta = Record<string, Omit<CoreAIAgent, 'input' | 'output' |
|
|
|
343
363
|
channelMiddleware?: MiddlewareMetadata[];
|
|
344
364
|
aiMiddleware?: MiddlewareMetadata[];
|
|
345
365
|
permissions?: PermissionMetadata[];
|
|
366
|
+
dynamicWorkflows?: 'read' | 'always' | 'ask';
|
|
346
367
|
}>;
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
export { agent, agentStream, agentResume, agentApprove, } from './ai-agent-helpers.js';
|
|
2
|
-
export { runAIAgent } from './ai-agent-runner.js';
|
|
2
|
+
export { runAIAgent, resumeAIAgentSync } from './ai-agent-runner.js';
|
|
3
3
|
export { streamAIAgent, resumeAIAgent } from './ai-agent-stream.js';
|
|
4
|
-
export { createAssistantUIChannel, parseAssistantUIInput, } from './ai-agent-assistant-ui.js';
|
|
5
4
|
export { type RunAIAgentParams, type StreamAIAgentOptions, ToolApprovalRequired, } from './ai-agent-prepare.js';
|
|
6
5
|
export { addAIAgent, approveAIAgent, getAIAgents, getAIAgentsMeta, } from './ai-agent-registry.js';
|
|
7
6
|
export type { AIAgentInput, AIAgentInputAttachment, AIAgentMeta, AIAgentMemoryConfig, AIAgentStep, AIContentPart, AgentRunRow, AgentRunService, AgentRunState, AIMessage, AIStreamChannel, AIStreamEvent, AIThread, CoreAIAgent, PendingApproval, PikkuAIMiddlewareHooks, } from './ai-agent.types.js';
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
export { agent, agentStream, agentResume, agentApprove, } from './ai-agent-helpers.js';
|
|
2
|
-
export { runAIAgent } from './ai-agent-runner.js';
|
|
2
|
+
export { runAIAgent, resumeAIAgentSync } from './ai-agent-runner.js';
|
|
3
3
|
export { streamAIAgent, resumeAIAgent } from './ai-agent-stream.js';
|
|
4
|
-
export { createAssistantUIChannel, parseAssistantUIInput, } from './ai-agent-assistant-ui.js';
|
|
5
4
|
export { ToolApprovalRequired, } from './ai-agent-prepare.js';
|
|
6
5
|
export { addAIAgent, approveAIAgent, getAIAgents, getAIAgentsMeta, } from './ai-agent-registry.js';
|
|
@@ -48,7 +48,9 @@ export const processMessageHandlers = (services, channelConfig, channelHandler,
|
|
|
48
48
|
channelHandler.getChannel().send(`Unauthorized for ${routeMessage}`);
|
|
49
49
|
return;
|
|
50
50
|
}
|
|
51
|
-
const
|
|
51
|
+
const routeMeta = getRouteMeta(channelConfig.name, routingProperty, routerValue);
|
|
52
|
+
const { packageName, middleware: routeInheritedMiddleware, permissions: inheritedPermissions, } = routeMeta;
|
|
53
|
+
const pikkuFuncId = routeMeta.pikkuFuncId;
|
|
52
54
|
// Get wire middleware: channel-level middleware + message-specific middleware
|
|
53
55
|
const channelWireMiddleware = channelConfig.middleware || [];
|
|
54
56
|
// Check if onMessage is a wrapper object vs direct function config:
|
|
@@ -83,12 +85,14 @@ export const processMessageHandlers = (services, channelConfig, channelHandler,
|
|
|
83
85
|
wireChannelMiddleware: wireChannelMiddleware,
|
|
84
86
|
inheritedPermissions,
|
|
85
87
|
wirePermissions,
|
|
88
|
+
coerceDataFromSchema: true,
|
|
86
89
|
tags: channelConfig.tags,
|
|
87
90
|
sessionService: userSession,
|
|
88
91
|
wire: {
|
|
89
92
|
channel: channelHandler.getChannel(),
|
|
90
93
|
...(userSession && createMiddlewareSessionWireProps(userSession)),
|
|
91
94
|
},
|
|
95
|
+
packageName,
|
|
92
96
|
});
|
|
93
97
|
};
|
|
94
98
|
const onMessage = async (rawData) => {
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type { PikkuError } from '../../errors/error-handler.js';
|
|
2
1
|
import type { HTTPFunctionMetaInputTypes, PikkuHTTPRequest, PikkuHTTPResponse } from '../http/http.types.js';
|
|
3
2
|
import type { CorePikkuMiddleware, MiddlewareMetadata, PermissionMetadata } from '../../types/core.types.js';
|
|
4
3
|
export type BinaryData = ArrayBuffer | Uint8Array;
|
|
@@ -19,6 +18,7 @@ export type RunChannelParams<ChannelData> = {
|
|
|
19
18
|
};
|
|
20
19
|
export interface ChannelMessageMeta {
|
|
21
20
|
pikkuFuncId: string;
|
|
21
|
+
packageName?: string;
|
|
22
22
|
summary?: string;
|
|
23
23
|
description?: string;
|
|
24
24
|
errors?: string[];
|
|
@@ -71,12 +71,6 @@ export type CoreChannel<ChannelData, Channel extends string, ChannelConnect = Co
|
|
|
71
71
|
auth?: boolean;
|
|
72
72
|
binary?: boolean | null;
|
|
73
73
|
onBinaryMessage?: (services: any, data: BinaryData, channel: PikkuChannel<ChannelData, any>) => Promise<BinaryData | void> | BinaryData | void;
|
|
74
|
-
docs?: Partial<{
|
|
75
|
-
description: string;
|
|
76
|
-
response: string;
|
|
77
|
-
errors: Array<typeof PikkuError>;
|
|
78
|
-
tags: string[];
|
|
79
|
-
}>;
|
|
80
74
|
tags?: string[];
|
|
81
75
|
};
|
|
82
76
|
export interface PikkuChannel<OpeningData, out Out> {
|
|
@@ -4,7 +4,7 @@ export declare class PikkuLocalChannelHandler<OpeningData = unknown, Out = unkno
|
|
|
4
4
|
private onMessageCallback?;
|
|
5
5
|
private onBinaryMessageCallback?;
|
|
6
6
|
private openCallBack?;
|
|
7
|
-
private
|
|
7
|
+
private closeCallbacks;
|
|
8
8
|
private sendCallback?;
|
|
9
9
|
private sendBinaryCallback?;
|
|
10
10
|
registerOnOpen(callback: () => void): void;
|
|
@@ -3,7 +3,7 @@ export class PikkuLocalChannelHandler extends PikkuAbstractChannelHandler {
|
|
|
3
3
|
onMessageCallback;
|
|
4
4
|
onBinaryMessageCallback;
|
|
5
5
|
openCallBack;
|
|
6
|
-
|
|
6
|
+
closeCallbacks = [];
|
|
7
7
|
sendCallback;
|
|
8
8
|
sendBinaryCallback;
|
|
9
9
|
registerOnOpen(callback) {
|
|
@@ -28,14 +28,16 @@ export class PikkuLocalChannelHandler extends PikkuAbstractChannelHandler {
|
|
|
28
28
|
return this.onBinaryMessageCallback?.(data);
|
|
29
29
|
}
|
|
30
30
|
registerOnClose(callback) {
|
|
31
|
-
this.
|
|
31
|
+
this.closeCallbacks.push(callback);
|
|
32
32
|
}
|
|
33
33
|
close() {
|
|
34
34
|
if (this.getChannel().state === 'closed') {
|
|
35
35
|
return;
|
|
36
36
|
}
|
|
37
37
|
super.close();
|
|
38
|
-
this.
|
|
38
|
+
for (const cb of this.closeCallbacks) {
|
|
39
|
+
cb();
|
|
40
|
+
}
|
|
39
41
|
}
|
|
40
42
|
registerOnSend(send) {
|
|
41
43
|
this.sendCallback = send;
|
|
@@ -75,7 +75,10 @@ export const runLocalChannel = async ({ channelId, request, response, route, ski
|
|
|
75
75
|
}
|
|
76
76
|
catch (e) {
|
|
77
77
|
singletonServices.logger.error(`Error handling onConnect: ${e}`);
|
|
78
|
-
channel.send({
|
|
78
|
+
channel.send({
|
|
79
|
+
error: e.message || 'Unknown error',
|
|
80
|
+
errorName: e.constructor?.name,
|
|
81
|
+
});
|
|
79
82
|
}
|
|
80
83
|
}
|
|
81
84
|
});
|
|
@@ -94,7 +97,10 @@ export const runLocalChannel = async ({ channelId, request, response, route, ski
|
|
|
94
97
|
}
|
|
95
98
|
catch (e) {
|
|
96
99
|
singletonServices.logger.error(`Error handling onDisconnect: ${e}`);
|
|
97
|
-
channel.send({
|
|
100
|
+
channel.send({
|
|
101
|
+
error: e.message || 'Unknown error',
|
|
102
|
+
errorName: e.constructor?.name,
|
|
103
|
+
});
|
|
98
104
|
}
|
|
99
105
|
}
|
|
100
106
|
await closeServices();
|
|
@@ -106,8 +112,12 @@ export const runLocalChannel = async ({ channelId, request, response, route, ski
|
|
|
106
112
|
await channel.send(result);
|
|
107
113
|
}
|
|
108
114
|
catch (e) {
|
|
109
|
-
singletonServices.logger.error(
|
|
110
|
-
channel.send({
|
|
115
|
+
singletonServices.logger.error(e);
|
|
116
|
+
channel.send({
|
|
117
|
+
error: e.message || 'Unknown error',
|
|
118
|
+
errorName: e.constructor?.name,
|
|
119
|
+
});
|
|
120
|
+
setTimeout(() => channel.close(), 200);
|
|
111
121
|
}
|
|
112
122
|
});
|
|
113
123
|
if (onBinaryMessage) {
|
|
@@ -176,7 +176,10 @@ export const runChannelMessage = async ({ channelId, channelStore, channelHandle
|
|
|
176
176
|
}
|
|
177
177
|
catch (e) {
|
|
178
178
|
singletonServices.logger.error(`Error processing message: ${e.message || e}`);
|
|
179
|
-
return {
|
|
179
|
+
return {
|
|
180
|
+
error: e.message || 'Unknown error',
|
|
181
|
+
errorName: e.constructor?.name,
|
|
182
|
+
};
|
|
180
183
|
}
|
|
181
184
|
finally {
|
|
182
185
|
if (wireServices) {
|
|
@@ -130,14 +130,20 @@ function pluckCLIData(mergedData, funcName, availableOptions) {
|
|
|
130
130
|
const funcMeta = pikkuState(null, 'function', 'meta')[funcName];
|
|
131
131
|
const schemaName = funcMeta?.inputSchemaName;
|
|
132
132
|
const schema = schemaName
|
|
133
|
-
? pikkuState(null, 'misc', 'schemas').get(schemaName)
|
|
133
|
+
? pikkuState(funcMeta?.packageName ?? null, 'misc', 'schemas').get(schemaName)
|
|
134
134
|
: null;
|
|
135
135
|
if (schema && schema.properties) {
|
|
136
136
|
// If we have a schema, only include fields that are in the schema
|
|
137
137
|
const result = {};
|
|
138
138
|
for (const key of Object.keys(schema.properties)) {
|
|
139
139
|
if (key in mergedData) {
|
|
140
|
-
|
|
140
|
+
let value = mergedData[key];
|
|
141
|
+
// CLI passes all values as strings — coerce to array when schema expects one
|
|
142
|
+
const propSchema = schema.properties[key];
|
|
143
|
+
if (propSchema?.type === 'array' && !Array.isArray(value)) {
|
|
144
|
+
value = typeof value === 'string' ? value.split(',') : [value];
|
|
145
|
+
}
|
|
146
|
+
result[key] = value;
|
|
141
147
|
}
|
|
142
148
|
else if (availableOptions[key]?.default !== undefined) {
|
|
143
149
|
// Apply default if not provided
|
|
@@ -237,9 +243,11 @@ export async function runCLICommand({ program, commandPath, data, singletonServi
|
|
|
237
243
|
wireMiddleware: allWireMiddleware,
|
|
238
244
|
inheritedPermissions: currentCommand.permissions,
|
|
239
245
|
wirePermissions: undefined,
|
|
246
|
+
coerceDataFromSchema: true,
|
|
240
247
|
tags: programData?.tags,
|
|
241
248
|
wire,
|
|
242
249
|
sessionService: userSession,
|
|
250
|
+
packageName: currentCommand.packageName,
|
|
243
251
|
});
|
|
244
252
|
// Apply renderer one final time with the final output (if renderer exists)
|
|
245
253
|
// Skip if result is undefined (void functions handle their own output)
|
|
@@ -136,4 +136,4 @@ export declare const pikkuFetch: <In, Out>(request: Request | PikkuHTTPRequest,
|
|
|
136
136
|
* @param {RunHTTPWiringOptions} options - Options such as singleton services, session handling, and error configuration.
|
|
137
137
|
* @returns {Promise<Out | void>} The output from the route handler or void if an error occurred.
|
|
138
138
|
*/
|
|
139
|
-
export declare const fetchData: <In, Out>(request: Request | PikkuHTTPRequest, response: PikkuHTTPResponse, { skipUserSession, respondWith404, logWarningsForStatusCodes, coerceDataFromSchema, bubbleErrors, generateRequestId, }?: RunHTTPWiringOptions) => Promise<Out | void>;
|
|
139
|
+
export declare const fetchData: <In, Out>(request: Request | PikkuHTTPRequest, response: PikkuHTTPResponse, { skipUserSession, respondWith404, logWarningsForStatusCodes, coerceDataFromSchema, bubbleErrors, exposeErrors, generateRequestId, }?: RunHTTPWiringOptions) => Promise<Out | void>;
|