@pikku/core 0.12.13 → 0.12.15
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 +46 -0
- package/dist/errors/errors.d.ts +18 -0
- package/dist/errors/errors.js +33 -0
- package/dist/function/function-runner.d.ts +1 -1
- package/dist/function/function-runner.js +10 -5
- package/dist/function/functions.types.js +1 -0
- package/dist/function/index.d.ts +2 -1
- package/dist/function/index.js +1 -0
- package/dist/handle-error.d.ts +2 -2
- package/dist/handle-error.js +8 -8
- package/dist/index.d.ts +1 -2
- package/dist/index.js +1 -2
- package/dist/middleware/index.d.ts +2 -0
- package/dist/middleware/index.js +2 -0
- package/dist/middleware/remote-auth.js +5 -2
- package/dist/permissions.d.ts +13 -1
- package/dist/permissions.js +51 -0
- package/dist/pikku-state.d.ts +0 -1
- package/dist/pikku-state.js +2 -4
- package/dist/remote.d.ts +10 -0
- package/dist/remote.js +32 -0
- package/dist/schema.js +2 -0
- package/dist/services/credential-service.d.ts +11 -0
- package/dist/services/credential-wire-service.d.ts +14 -3
- package/dist/services/credential-wire-service.js +49 -6
- package/dist/services/deployment-service.d.ts +12 -1
- package/dist/services/in-memory-queue-service.d.ts +7 -0
- package/dist/services/in-memory-queue-service.js +28 -0
- package/dist/services/index.d.ts +4 -1
- package/dist/services/index.js +2 -1
- package/dist/services/local-credential-service.d.ts +2 -0
- package/dist/services/local-credential-service.js +20 -0
- package/dist/services/logger-console.d.ts +26 -40
- package/dist/services/logger-console.js +102 -46
- package/dist/services/logger.d.ts +5 -0
- package/dist/services/meta-service.d.ts +175 -0
- package/dist/services/meta-service.js +310 -0
- package/dist/services/pikku-user-id.d.ts +3 -0
- package/dist/services/pikku-user-id.js +16 -0
- package/dist/services/typed-credential-service.d.ts +2 -0
- package/dist/services/typed-credential-service.js +6 -0
- package/dist/services/workflow-service.d.ts +6 -1
- package/dist/testing/service-tests.js +0 -8
- package/dist/types/core.types.d.ts +14 -2
- package/dist/types/state.types.d.ts +9 -2
- package/dist/wirings/ai-agent/ai-agent-prepare.d.ts +21 -0
- package/dist/wirings/ai-agent/ai-agent-prepare.js +90 -21
- package/dist/wirings/ai-agent/ai-agent-registry.js +2 -2
- package/dist/wirings/ai-agent/ai-agent-runner.js +24 -2
- package/dist/wirings/ai-agent/ai-agent-stream.d.ts +2 -1
- package/dist/wirings/ai-agent/ai-agent-stream.js +61 -3
- package/dist/wirings/ai-agent/ai-agent.types.d.ts +25 -4
- package/dist/wirings/ai-agent/index.d.ts +1 -1
- package/dist/wirings/ai-agent/index.js +1 -1
- package/dist/wirings/channel/channel-runner.js +3 -3
- package/dist/wirings/cli/cli-runner.js +4 -3
- package/dist/wirings/cli/command-parser.js +7 -3
- package/dist/wirings/http/http-runner.d.ts +1 -1
- package/dist/wirings/http/http-runner.js +23 -15
- package/dist/wirings/http/http.types.d.ts +2 -0
- package/dist/wirings/mcp/mcp-runner.js +5 -3
- package/dist/wirings/queue/queue-runner.d.ts +3 -1
- package/dist/wirings/queue/queue-runner.js +8 -3
- package/dist/wirings/queue/queue.types.d.ts +6 -0
- package/dist/wirings/rpc/rpc-runner.js +57 -90
- package/dist/wirings/scheduler/scheduler-runner.d.ts +3 -1
- package/dist/wirings/scheduler/scheduler-runner.js +9 -5
- package/dist/wirings/trigger/trigger-runner.js +4 -2
- package/dist/wirings/workflow/dsl/workflow-dsl.types.d.ts +2 -0
- package/dist/wirings/workflow/dsl/workflow-runner.d.ts +1 -1
- package/dist/wirings/workflow/dsl/workflow-runner.js +6 -9
- package/dist/wirings/workflow/graph/graph-runner.d.ts +1 -1
- package/dist/wirings/workflow/graph/graph-runner.js +18 -4
- package/dist/wirings/workflow/index.d.ts +4 -2
- package/dist/wirings/workflow/index.js +4 -2
- package/dist/wirings/workflow/pikku-workflow-service.d.ts +16 -4
- package/dist/wirings/workflow/pikku-workflow-service.js +218 -24
- package/dist/wirings/workflow/workflow-queue-workers.d.ts +40 -0
- package/dist/wirings/workflow/workflow-queue-workers.js +41 -0
- package/dist/wirings/workflow/workflow.types.d.ts +19 -1
- package/package.json +4 -1
- package/src/errors/errors.ts +44 -0
- package/src/function/function-runner.ts +24 -13
- package/src/function/functions.types.ts +1 -0
- package/src/function/index.ts +10 -0
- package/src/handle-error.test.ts +2 -2
- package/src/handle-error.ts +8 -8
- package/src/index.ts +1 -2
- package/src/middleware/index.ts +2 -0
- package/src/middleware/remote-auth.test.ts +4 -4
- package/src/middleware/remote-auth.ts +7 -2
- package/src/permissions.ts +70 -0
- package/src/pikku-state.test.ts +0 -26
- package/src/pikku-state.ts +2 -5
- package/src/remote.ts +46 -0
- package/src/schema.ts +1 -0
- package/src/services/credential-service.ts +13 -0
- package/src/services/credential-wire-service.test.ts +174 -0
- package/src/services/credential-wire-service.ts +52 -8
- package/src/services/deployment-service.ts +17 -1
- package/src/services/in-memory-queue-service.ts +46 -0
- package/src/services/index.ts +21 -1
- package/src/services/local-credential-service.ts +22 -0
- package/src/services/logger-console.ts +127 -47
- package/src/services/logger.ts +6 -0
- package/src/services/meta-service.ts +523 -0
- package/src/services/pikku-user-id.test.ts +62 -0
- package/src/services/pikku-user-id.ts +17 -0
- package/src/services/typed-credential-service.ts +8 -0
- package/src/services/workflow-service.ts +8 -0
- package/src/testing/service-tests.ts +0 -10
- package/src/types/core.types.ts +16 -2
- package/src/types/state.types.ts +7 -2
- package/src/wirings/ai-agent/ai-agent-prepare.ts +122 -41
- package/src/wirings/ai-agent/ai-agent-registry.ts +3 -3
- package/src/wirings/ai-agent/ai-agent-runner.ts +22 -3
- package/src/wirings/ai-agent/ai-agent-stream.ts +115 -3
- package/src/wirings/ai-agent/ai-agent.types.ts +27 -4
- package/src/wirings/ai-agent/index.ts +1 -0
- package/src/wirings/channel/channel-handler.ts +0 -1
- package/src/wirings/channel/channel-runner.ts +4 -5
- package/src/wirings/cli/cli-runner.test.ts +8 -7
- package/src/wirings/cli/cli-runner.ts +5 -4
- package/src/wirings/cli/command-parser.ts +8 -3
- package/src/wirings/http/http-runner.ts +27 -18
- package/src/wirings/http/http.types.ts +2 -0
- package/src/wirings/mcp/mcp-runner.ts +7 -10
- package/src/wirings/queue/queue-runner.test.ts +5 -11
- package/src/wirings/queue/queue-runner.ts +12 -4
- package/src/wirings/queue/queue.types.ts +6 -0
- package/src/wirings/rpc/rpc-runner.ts +91 -118
- package/src/wirings/scheduler/scheduler-runner.test.ts +5 -11
- package/src/wirings/scheduler/scheduler-runner.ts +13 -5
- package/src/wirings/trigger/trigger-runner.test.ts +10 -11
- package/src/wirings/trigger/trigger-runner.ts +6 -4
- package/src/wirings/workflow/dsl/workflow-dsl.types.ts +2 -0
- package/src/wirings/workflow/dsl/workflow-runner.ts +11 -10
- package/src/wirings/workflow/graph/graph-runner.ts +19 -4
- package/src/wirings/workflow/index.ts +17 -6
- package/src/wirings/workflow/pikku-workflow-service.ts +286 -24
- package/src/wirings/workflow/workflow-queue-workers.ts +85 -0
- package/src/wirings/workflow/workflow.types.ts +18 -1
- package/tsconfig.tsbuildinfo +1 -1
- package/dist/wirings/ai-agent/agent-dynamic-workflow.d.ts +0 -6
- package/dist/wirings/ai-agent/agent-dynamic-workflow.js +0 -468
- package/dist/wirings/workflow/workflow-helpers.d.ts +0 -36
- package/dist/wirings/workflow/workflow-helpers.js +0 -38
- package/src/wirings/ai-agent/agent-dynamic-workflow.ts +0 -610
- package/src/wirings/workflow/workflow-helpers.test.ts +0 -129
- package/src/wirings/workflow/workflow-helpers.ts +0 -79
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { PikkuError } from '../../errors/error-handler.js';
|
|
2
|
+
import { checkAuthPermissions } from '../../permissions.js';
|
|
2
3
|
import { AIProviderNotConfiguredError } from '../../errors/errors.js';
|
|
3
4
|
import { pikkuState, getSingletonServices } from '../../pikku-state.js';
|
|
4
5
|
import { createMiddlewareSessionWireProps } from '../../services/user-session-service.js';
|
|
@@ -6,7 +7,6 @@ import { randomUUID } from 'crypto';
|
|
|
6
7
|
import { streamAIAgent } from './ai-agent-stream.js';
|
|
7
8
|
import { runAIAgent } from './ai-agent-runner.js';
|
|
8
9
|
import { resolveNamespace, ContextAwareRPCService, } from '../../wirings/rpc/rpc-runner.js';
|
|
9
|
-
import { buildDynamicWorkflowInstructions, buildWorkflowTools, } from './agent-dynamic-workflow.js';
|
|
10
10
|
import { resolveMemoryServices, loadContextMessages, trimMessages, } from './ai-agent-memory.js';
|
|
11
11
|
import { resolveModelConfig } from './ai-agent-model-config.js';
|
|
12
12
|
export class ToolApprovalRequired extends PikkuError {
|
|
@@ -28,7 +28,57 @@ export class ToolApprovalRequired extends PikkuError {
|
|
|
28
28
|
this.agentRunId = agentRunId;
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
|
+
export class ToolCredentialRequired extends PikkuError {
|
|
32
|
+
toolCallId;
|
|
33
|
+
toolName;
|
|
34
|
+
args;
|
|
35
|
+
credentialName;
|
|
36
|
+
credentialType;
|
|
37
|
+
connectUrl;
|
|
38
|
+
constructor(toolCallId, toolName, args, credentialName, credentialType, connectUrl) {
|
|
39
|
+
super(`Tool '${toolName}' requires credential '${credentialName}'`);
|
|
40
|
+
this.toolCallId = toolCallId;
|
|
41
|
+
this.toolName = toolName;
|
|
42
|
+
this.args = args;
|
|
43
|
+
this.credentialName = credentialName;
|
|
44
|
+
this.credentialType = credentialType;
|
|
45
|
+
this.connectUrl = connectUrl;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Given a list of tool names (e.g. ["oauth-api:getProfile"]),
|
|
50
|
+
* returns the wire OAuth credentials required by their addons.
|
|
51
|
+
*/
|
|
52
|
+
export function getAddonCredentialRequirements(toolNames) {
|
|
53
|
+
const requirements = new Map();
|
|
54
|
+
for (const toolName of toolNames) {
|
|
55
|
+
if (!toolName.includes(':'))
|
|
56
|
+
continue;
|
|
57
|
+
const resolved = resolveNamespace(toolName);
|
|
58
|
+
if (!resolved)
|
|
59
|
+
continue;
|
|
60
|
+
const credsMeta = pikkuState(resolved.package, 'package', 'credentialsMeta');
|
|
61
|
+
if (!credsMeta)
|
|
62
|
+
continue;
|
|
63
|
+
for (const [name, meta] of Object.entries(credsMeta)) {
|
|
64
|
+
if (meta.type === 'wire' && meta.oauth2 && !requirements.has(name)) {
|
|
65
|
+
requirements.set(name, {
|
|
66
|
+
credentialName: name,
|
|
67
|
+
displayName: meta.displayName ?? name,
|
|
68
|
+
addonNamespace: toolName.split(':')[0],
|
|
69
|
+
type: 'wire',
|
|
70
|
+
oauth2: true,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return [...requirements.values()];
|
|
76
|
+
}
|
|
31
77
|
export const resolveAgent = (agentName) => {
|
|
78
|
+
if (!agentName) {
|
|
79
|
+
console.error('[resolveAgent] agentName is undefined/null! Stack:', new Error().stack);
|
|
80
|
+
throw new Error('resolveAgent called with undefined agentName');
|
|
81
|
+
}
|
|
32
82
|
const mainAgent = pikkuState(null, 'agent', 'agents').get(agentName);
|
|
33
83
|
if (mainAgent) {
|
|
34
84
|
return { agent: mainAgent, packageName: null, resolvedName: agentName };
|
|
@@ -54,10 +104,23 @@ export const resolveAgent = (agentName) => {
|
|
|
54
104
|
};
|
|
55
105
|
export async function buildInstructions(agentName, packageName) {
|
|
56
106
|
const meta = pikkuState(packageName, 'agent', 'agentsMeta')[agentName];
|
|
57
|
-
const
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
107
|
+
const parts = [];
|
|
108
|
+
if (meta?.role)
|
|
109
|
+
parts.push(meta.role);
|
|
110
|
+
if (meta?.personality)
|
|
111
|
+
parts.push(meta.personality);
|
|
112
|
+
if (meta?.goal)
|
|
113
|
+
parts.push(meta.goal);
|
|
114
|
+
let instructions = parts.join('\n\n');
|
|
115
|
+
if (meta?.tools?.length) {
|
|
116
|
+
instructions +=
|
|
117
|
+
'\n\nTool usage rules:\n' +
|
|
118
|
+
'- Act immediately with the information given. Do NOT ask clarifying questions unless a required field is truly missing.\n' +
|
|
119
|
+
'- Only use fields defined in your tool schemas. Never mention or ask for fields that do not exist.\n' +
|
|
120
|
+
'- Never fill optional fields with placeholder or zero values. Omit them entirely unless the user provides a real value.\n' +
|
|
121
|
+
'- Never stuff unrelated information into the wrong field.\n' +
|
|
122
|
+
'- Keep responses concise.';
|
|
123
|
+
}
|
|
61
124
|
if (meta?.agents?.length) {
|
|
62
125
|
instructions +=
|
|
63
126
|
'\n\nWhen calling a sub-agent, provide a short session name that describes the task. ' +
|
|
@@ -65,9 +128,6 @@ export async function buildInstructions(agentName, packageName) {
|
|
|
65
128
|
'Use a new session name for a new independent task. ' +
|
|
66
129
|
'When a request involves multiple actions for the same domain, combine them into a single sub-agent call rather than making separate calls.';
|
|
67
130
|
}
|
|
68
|
-
if (meta?.dynamicWorkflows && meta.tools?.length) {
|
|
69
|
-
instructions += buildDynamicWorkflowInstructions(meta.tools, meta.dynamicWorkflows);
|
|
70
|
-
}
|
|
71
131
|
return instructions;
|
|
72
132
|
}
|
|
73
133
|
export function createScopedChannel(parent, agentName, session) {
|
|
@@ -119,6 +179,10 @@ export async function buildToolDefs(params, agentSessionMap, resourceId, agentNa
|
|
|
119
179
|
const meta = pikkuState(packageName, 'agent', 'agentsMeta')[agentName];
|
|
120
180
|
if (!meta)
|
|
121
181
|
return { tools, missingRpcs };
|
|
182
|
+
// Get session for permission filtering
|
|
183
|
+
const session = params.sessionService
|
|
184
|
+
? await params.sessionService.get()
|
|
185
|
+
: null;
|
|
122
186
|
const metaTools = meta.tools;
|
|
123
187
|
const metaAgents = meta.agents;
|
|
124
188
|
if (metaTools?.length) {
|
|
@@ -150,6 +214,14 @@ export async function buildToolDefs(params, agentSessionMap, resourceId, agentNa
|
|
|
150
214
|
missingRpcs.push(toolName);
|
|
151
215
|
continue;
|
|
152
216
|
}
|
|
217
|
+
// Filter out tools the user doesn't have auth for
|
|
218
|
+
if (fnMeta.permissions?.length) {
|
|
219
|
+
if (!session)
|
|
220
|
+
continue;
|
|
221
|
+
const allowed = await checkAuthPermissions(fnMeta.permissions, session, singletonServices, resolvedPkg);
|
|
222
|
+
if (!allowed)
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
153
225
|
const inputSchemaName = fnMeta?.inputSchemaName;
|
|
154
226
|
let inputSchema = inputSchemaName
|
|
155
227
|
? schemas.get(inputSchemaName)
|
|
@@ -205,6 +277,14 @@ export async function buildToolDefs(params, agentSessionMap, resourceId, agentNa
|
|
|
205
277
|
singletonServices.logger.warn(`Sub-agent '${subAgentName}' not found in agent registry`);
|
|
206
278
|
continue;
|
|
207
279
|
}
|
|
280
|
+
// Filter out sub-agents the user doesn't have auth for
|
|
281
|
+
if (subMeta.permissions?.length) {
|
|
282
|
+
if (!session)
|
|
283
|
+
continue;
|
|
284
|
+
const allowed = await checkAuthPermissions(subMeta.permissions, session, singletonServices);
|
|
285
|
+
if (!allowed)
|
|
286
|
+
continue;
|
|
287
|
+
}
|
|
208
288
|
tools.push({
|
|
209
289
|
name: subAgentName,
|
|
210
290
|
description: subMeta.description,
|
|
@@ -293,10 +373,6 @@ export async function buildToolDefs(params, agentSessionMap, resourceId, agentNa
|
|
|
293
373
|
});
|
|
294
374
|
}
|
|
295
375
|
}
|
|
296
|
-
if (meta.dynamicWorkflows) {
|
|
297
|
-
const workflowTools = buildWorkflowTools(agentName, packageName, meta.tools ?? [], meta.dynamicWorkflows, streamContext, params.sessionService);
|
|
298
|
-
tools.push(...workflowTools);
|
|
299
|
-
}
|
|
300
376
|
const hasToolHooks = aiMiddlewares?.some((mw) => mw.beforeToolCall || mw.afterToolCall);
|
|
301
377
|
if (hasToolHooks) {
|
|
302
378
|
for (const tool of tools) {
|
|
@@ -324,6 +400,8 @@ export async function buildToolDefs(params, agentSessionMap, resourceId, agentNa
|
|
|
324
400
|
}
|
|
325
401
|
catch (err) {
|
|
326
402
|
execError = err;
|
|
403
|
+
if (err?.payload?.error === 'missing_credential')
|
|
404
|
+
throw err;
|
|
327
405
|
result = err instanceof Error ? err.message : String(err);
|
|
328
406
|
}
|
|
329
407
|
const durationMs = Date.now() - startTime;
|
|
@@ -357,15 +435,6 @@ export async function prepareAgentRun(agentName, input, params, agentSessionMap,
|
|
|
357
435
|
if (!agentRunner) {
|
|
358
436
|
throw new AIProviderNotConfiguredError();
|
|
359
437
|
}
|
|
360
|
-
if (agent.dynamicWorkflows && singletonServices.workflowService) {
|
|
361
|
-
const persisted = await singletonServices.workflowService.getAIGeneratedWorkflows(resolvedName);
|
|
362
|
-
const allMeta = pikkuState(null, 'workflows', 'meta');
|
|
363
|
-
for (const wf of persisted) {
|
|
364
|
-
if (!allMeta[wf.workflowName]) {
|
|
365
|
-
allMeta[wf.workflowName] = wf.graph;
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
438
|
const { storage } = resolveMemoryServices(agent, singletonServices);
|
|
370
439
|
const memoryConfig = agent.memory;
|
|
371
440
|
const threadId = input.threadId;
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { pikkuState } from '../../pikku-state.js';
|
|
2
|
-
import { PikkuMissingMetaError } from '../../errors/errors.js';
|
|
3
2
|
export const addAIAgent = (agentName, agent, packageName = null) => {
|
|
4
3
|
const agentsMeta = pikkuState(packageName, 'agent', 'agentsMeta');
|
|
5
4
|
const agentMeta = agentsMeta[agentName];
|
|
6
5
|
if (!agentMeta) {
|
|
7
|
-
|
|
6
|
+
console.warn(`[pikku] Skipping AI agent '${agentName}' — metadata not found. Consider moving this wiring to its own file.`);
|
|
7
|
+
return;
|
|
8
8
|
}
|
|
9
9
|
const agents = pikkuState(packageName, 'agent', 'agents');
|
|
10
10
|
if (agents.has(agentName)) {
|
|
@@ -4,6 +4,21 @@ import { checkForApprovals, appendStepMessages } from './ai-agent-stream.js';
|
|
|
4
4
|
import { pikkuState, getSingletonServices } from '../../pikku-state.js';
|
|
5
5
|
import { resolveModelConfig } from './ai-agent-model-config.js';
|
|
6
6
|
import { AIProviderNotConfiguredError } from '../../errors/errors.js';
|
|
7
|
+
function stripNulls(obj) {
|
|
8
|
+
if (obj === null)
|
|
9
|
+
return undefined;
|
|
10
|
+
if (Array.isArray(obj))
|
|
11
|
+
return obj.map(stripNulls);
|
|
12
|
+
if (typeof obj !== 'object')
|
|
13
|
+
return obj;
|
|
14
|
+
const result = {};
|
|
15
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
16
|
+
if (value !== null) {
|
|
17
|
+
result[key] = stripNulls(value);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return result;
|
|
21
|
+
}
|
|
7
22
|
import { randomUUID } from 'crypto';
|
|
8
23
|
export async function runAIAgent(agentName, input, params, agentSessionMap) {
|
|
9
24
|
const sessionMap = agentSessionMap ?? new Map();
|
|
@@ -287,9 +302,11 @@ export async function resumeAIAgentSync(runId, approvals, params, expectedAgentN
|
|
|
287
302
|
if (!matchingTool) {
|
|
288
303
|
throw new Error(`Tool "${pending.toolName}" not found in agent definition`);
|
|
289
304
|
}
|
|
290
|
-
const
|
|
305
|
+
const rawArgs = typeof pending.args === 'string'
|
|
291
306
|
? JSON.parse(pending.args)
|
|
292
307
|
: pending.args;
|
|
308
|
+
// Strip null values recursively — LLMs send null for optional fields but Zod expects undefined
|
|
309
|
+
const toolArgs = stripNulls(rawArgs) ?? {};
|
|
293
310
|
try {
|
|
294
311
|
const toolResult = await matchingTool.execute(toolArgs);
|
|
295
312
|
resultStr =
|
|
@@ -298,7 +315,12 @@ export async function resumeAIAgentSync(runId, approvals, params, expectedAgentN
|
|
|
298
315
|
: JSON.stringify(toolResult);
|
|
299
316
|
}
|
|
300
317
|
catch (err) {
|
|
301
|
-
|
|
318
|
+
if (err?.payload?.error === 'missing_credential') {
|
|
319
|
+
resultStr = JSON.stringify(err.payload);
|
|
320
|
+
}
|
|
321
|
+
else {
|
|
322
|
+
resultStr = `Error: ${err instanceof Error ? err.message : String(err)}`;
|
|
323
|
+
}
|
|
302
324
|
}
|
|
303
325
|
}
|
|
304
326
|
else {
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { AIStreamChannel } from './ai-agent.types.js';
|
|
2
2
|
import type { AIAgentRunnerParams, AIAgentStepResult } from '../../services/ai-agent-runner-service.js';
|
|
3
|
-
import { ToolApprovalRequired, type RunAIAgentParams, type StreamAIAgentOptions } from './ai-agent-prepare.js';
|
|
3
|
+
import { ToolApprovalRequired, ToolCredentialRequired, type RunAIAgentParams, type StreamAIAgentOptions } from './ai-agent-prepare.js';
|
|
4
4
|
export declare function checkForApprovals(stepResult: AIAgentStepResult, tools: AIAgentRunnerParams['tools'], runId: string): ToolApprovalRequired[];
|
|
5
|
+
export declare function checkForCredentialRequests(stepResult: AIAgentStepResult, runId: string): ToolCredentialRequired[];
|
|
5
6
|
export declare function appendStepMessages(runnerParams: AIAgentRunnerParams, stepResult: AIAgentStepResult): void;
|
|
6
7
|
export declare function streamAIAgent(agentName: string, input: {
|
|
7
8
|
message: string;
|
|
@@ -3,7 +3,7 @@ import { AIProviderNotConfiguredError } from '../../errors/errors.js';
|
|
|
3
3
|
import { combineChannelMiddleware, wrapChannelWithMiddleware, } from '../channel/channel-middleware-runner.js';
|
|
4
4
|
import { randomUUID } from 'crypto';
|
|
5
5
|
import { parseWorkingMemory, deepMergeWorkingMemory, resolveMemoryServices, loadContextMessages, trimMessages, } from './ai-agent-memory.js';
|
|
6
|
-
import { prepareAgentRun, resolveAgent, buildInstructions, buildToolDefs, createScopedChannel, ToolApprovalRequired, } from './ai-agent-prepare.js';
|
|
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 = '';
|
|
@@ -197,6 +197,12 @@ async function runStreamStepLoop(params) {
|
|
|
197
197
|
}
|
|
198
198
|
return { outcome: 'approval', approvals: approvalsNeeded };
|
|
199
199
|
}
|
|
200
|
+
const credentialRequests = checkForCredentialRequests(stepResult, runId);
|
|
201
|
+
if (credentialRequests.length > 0) {
|
|
202
|
+
// Append step messages so the tool result is preserved for resume
|
|
203
|
+
appendStepMessages(runnerParams, stepResult);
|
|
204
|
+
return { outcome: 'credential', credentialRequests };
|
|
205
|
+
}
|
|
200
206
|
appendStepMessages(runnerParams, stepResult);
|
|
201
207
|
}
|
|
202
208
|
return { outcome: 'done' };
|
|
@@ -226,6 +232,19 @@ export function checkForApprovals(stepResult, tools, runId) {
|
|
|
226
232
|
}
|
|
227
233
|
return approvals;
|
|
228
234
|
}
|
|
235
|
+
export function checkForCredentialRequests(stepResult, runId) {
|
|
236
|
+
const requests = [];
|
|
237
|
+
for (const tr of stepResult.toolResults) {
|
|
238
|
+
if (tr.result &&
|
|
239
|
+
typeof tr.result === 'object' &&
|
|
240
|
+
'__credentialRequired' in tr.result) {
|
|
241
|
+
const r = tr.result;
|
|
242
|
+
const tc = stepResult.toolCalls.find((t) => t.toolCallId === tr.toolCallId);
|
|
243
|
+
requests.push(new ToolCredentialRequired(tr.toolCallId, tc?.toolName ?? 'unknown', tc?.args ?? {}, r.credentialName, r.credentialType, r.connectUrl));
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
return requests;
|
|
247
|
+
}
|
|
229
248
|
export function appendStepMessages(runnerParams, stepResult) {
|
|
230
249
|
const assistantMsg = {
|
|
231
250
|
id: randomUUID(),
|
|
@@ -293,6 +312,30 @@ function handleApprovals(approvals, runId, channel, aiRunState, persistingChanne
|
|
|
293
312
|
channel.close();
|
|
294
313
|
})();
|
|
295
314
|
}
|
|
315
|
+
function handleCredentialRequests(requests, runId, channel, aiRunState, persistingChannel) {
|
|
316
|
+
return (async () => {
|
|
317
|
+
await persistingChannel.flush();
|
|
318
|
+
const pendingApprovals = requests.map((req) => ({
|
|
319
|
+
type: 'credential-request',
|
|
320
|
+
toolCallId: req.toolCallId,
|
|
321
|
+
toolName: req.toolName,
|
|
322
|
+
args: req.args,
|
|
323
|
+
credentialName: req.credentialName,
|
|
324
|
+
credentialType: req.credentialType,
|
|
325
|
+
connectUrl: req.connectUrl,
|
|
326
|
+
}));
|
|
327
|
+
await aiRunState.updateRun(runId, {
|
|
328
|
+
status: 'suspended',
|
|
329
|
+
suspendReason: 'credential',
|
|
330
|
+
pendingApprovals,
|
|
331
|
+
});
|
|
332
|
+
// Don't send credential-request SSE events — the tool result with
|
|
333
|
+
// __credentialRequired was already streamed. The frontend detects it
|
|
334
|
+
// from the tool result and shows Connect/Ignore buttons.
|
|
335
|
+
channel.send({ type: 'done' });
|
|
336
|
+
channel.close();
|
|
337
|
+
})();
|
|
338
|
+
}
|
|
296
339
|
export async function streamAIAgent(agentName, input, channel, params, agentSessionMap, options) {
|
|
297
340
|
const sessionMap = agentSessionMap ?? new Map();
|
|
298
341
|
const normalizedInput = input;
|
|
@@ -419,6 +462,10 @@ export async function streamAIAgent(agentName, input, channel, params, agentSess
|
|
|
419
462
|
await handleApprovals(loopResult.approvals, runId, channel, aiRunState, persistingChannel);
|
|
420
463
|
return persistingChannel.fullText;
|
|
421
464
|
}
|
|
465
|
+
if (loopResult.outcome === 'credential') {
|
|
466
|
+
await handleCredentialRequests(loopResult.credentialRequests, runId, channel, aiRunState, persistingChannel);
|
|
467
|
+
return persistingChannel.fullText;
|
|
468
|
+
}
|
|
422
469
|
await postStreamCleanup(persistingChannel, aiMiddlewares, singletonServices, storage, memoryConfig, threadId, workingMemorySchemaName, runnerParams.messages, aiRunState, runId);
|
|
423
470
|
channel.send({ type: 'done' });
|
|
424
471
|
channel.close();
|
|
@@ -487,7 +534,8 @@ export async function resumeAIAgent(input, channel, params, options) {
|
|
|
487
534
|
toolResults: [
|
|
488
535
|
{
|
|
489
536
|
id: input.toolCallId,
|
|
490
|
-
name: pending.type === 'tool-call'
|
|
537
|
+
name: pending.type === 'tool-call' ||
|
|
538
|
+
pending.type === 'credential-request'
|
|
491
539
|
? pending.toolName
|
|
492
540
|
: pending.agentName,
|
|
493
541
|
result: denialResult,
|
|
@@ -573,7 +621,12 @@ export async function resumeAIAgent(input, channel, params, options) {
|
|
|
573
621
|
toolResult = await matchingTool.execute(toolArgs);
|
|
574
622
|
}
|
|
575
623
|
catch (execErr) {
|
|
576
|
-
|
|
624
|
+
if (execErr?.payload?.error === 'missing_credential') {
|
|
625
|
+
toolResult = execErr.payload;
|
|
626
|
+
}
|
|
627
|
+
else {
|
|
628
|
+
toolResult = `Error: ${execErr instanceof Error ? execErr.message : String(execErr)}`;
|
|
629
|
+
}
|
|
577
630
|
isError = true;
|
|
578
631
|
}
|
|
579
632
|
const resultStr = typeof toolResult === 'string' ? toolResult : JSON.stringify(toolResult);
|
|
@@ -680,6 +733,7 @@ async function continueAfterToolResult(run, agent, packageName, resolvedName, st
|
|
|
680
733
|
const streamContext = { channel, options };
|
|
681
734
|
const resumeTools = (await buildToolDefs(params, new Map(), run.resourceId, resolvedName, packageName, streamContext, aiMiddlewares)).tools;
|
|
682
735
|
const resolved = resolveModelConfig(resolvedName, agent);
|
|
736
|
+
console.log('[DEBUG resume] resolvedName:', resolvedName, 'agent.model:', agent.model, 'resolved.model:', resolved.model);
|
|
683
737
|
const maxSteps = resolved.maxSteps ?? 10;
|
|
684
738
|
const runnerParams = {
|
|
685
739
|
model: resolved.model,
|
|
@@ -708,6 +762,10 @@ async function continueAfterToolResult(run, agent, packageName, resolvedName, st
|
|
|
708
762
|
await handleApprovals(loopResult.approvals, run.runId, channel, aiRunState, persistingChannel);
|
|
709
763
|
return;
|
|
710
764
|
}
|
|
765
|
+
if (loopResult.outcome === 'credential') {
|
|
766
|
+
await handleCredentialRequests(loopResult.credentialRequests, run.runId, channel, aiRunState, persistingChannel);
|
|
767
|
+
return;
|
|
768
|
+
}
|
|
711
769
|
await postStreamCleanup(persistingChannel, aiMiddlewares, singletonServices, storage, memoryConfig, run.threadId, workingMemorySchemaName, runnerParams.messages, aiRunState, run.runId);
|
|
712
770
|
channel.send({ type: 'done' });
|
|
713
771
|
channel.close();
|
|
@@ -183,7 +183,9 @@ export type CoreAIAgent<PikkuPermission = CorePikkuPermission<any, any>, PikkuMi
|
|
|
183
183
|
description: string;
|
|
184
184
|
summary?: string;
|
|
185
185
|
errors?: string[];
|
|
186
|
-
|
|
186
|
+
role?: string;
|
|
187
|
+
personality?: string;
|
|
188
|
+
goal: string;
|
|
187
189
|
model: string;
|
|
188
190
|
temperature?: number;
|
|
189
191
|
tools?: unknown[];
|
|
@@ -192,7 +194,6 @@ export type CoreAIAgent<PikkuPermission = CorePikkuPermission<any, any>, PikkuMi
|
|
|
192
194
|
memory?: AIAgentMemoryConfig;
|
|
193
195
|
maxSteps?: number;
|
|
194
196
|
toolChoice?: 'auto' | 'required' | 'none';
|
|
195
|
-
dynamicWorkflows?: 'read' | 'always' | 'ask';
|
|
196
197
|
input?: unknown;
|
|
197
198
|
output?: unknown;
|
|
198
199
|
tags?: string[];
|
|
@@ -256,6 +257,17 @@ export type AIStreamEvent = {
|
|
|
256
257
|
reason?: string;
|
|
257
258
|
agent?: string;
|
|
258
259
|
session?: string;
|
|
260
|
+
} | {
|
|
261
|
+
type: 'credential-request';
|
|
262
|
+
toolCallId: string;
|
|
263
|
+
toolName: string;
|
|
264
|
+
args: unknown;
|
|
265
|
+
credentialName: string;
|
|
266
|
+
credentialType: 'oauth2' | 'apikey';
|
|
267
|
+
connectUrl?: string;
|
|
268
|
+
runId: string;
|
|
269
|
+
agent?: string;
|
|
270
|
+
session?: string;
|
|
259
271
|
} | {
|
|
260
272
|
type: 'usage';
|
|
261
273
|
tokens: {
|
|
@@ -307,6 +319,14 @@ export type PendingApproval = {
|
|
|
307
319
|
agentRunId: string;
|
|
308
320
|
displayToolName: string;
|
|
309
321
|
displayArgs: unknown;
|
|
322
|
+
} | {
|
|
323
|
+
type: 'credential-request';
|
|
324
|
+
toolCallId: string;
|
|
325
|
+
toolName: string;
|
|
326
|
+
args: unknown;
|
|
327
|
+
credentialName: string;
|
|
328
|
+
credentialType: 'oauth2' | 'apikey';
|
|
329
|
+
connectUrl?: string;
|
|
310
330
|
};
|
|
311
331
|
export interface AgentRunState {
|
|
312
332
|
runId: string;
|
|
@@ -315,7 +335,7 @@ export interface AgentRunState {
|
|
|
315
335
|
resourceId: string;
|
|
316
336
|
status: 'running' | 'suspended' | 'completed' | 'failed';
|
|
317
337
|
errorMessage?: string;
|
|
318
|
-
suspendReason?: 'approval' | 'rpc-missing';
|
|
338
|
+
suspendReason?: 'approval' | 'credential' | 'rpc-missing';
|
|
319
339
|
missingRpcs?: string[];
|
|
320
340
|
pendingApprovals?: PendingApproval[];
|
|
321
341
|
usage: {
|
|
@@ -363,5 +383,6 @@ export type AIAgentMeta = Record<string, Omit<CoreAIAgent, 'input' | 'output' |
|
|
|
363
383
|
channelMiddleware?: MiddlewareMetadata[];
|
|
364
384
|
aiMiddleware?: MiddlewareMetadata[];
|
|
365
385
|
permissions?: PermissionMetadata[];
|
|
366
|
-
|
|
386
|
+
sourceFile?: string;
|
|
387
|
+
exportedName?: string;
|
|
367
388
|
}>;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { agent, agentStream, agentResume, agentApprove, } from './ai-agent-helpers.js';
|
|
2
2
|
export { runAIAgent, resumeAIAgentSync } from './ai-agent-runner.js';
|
|
3
3
|
export { streamAIAgent, resumeAIAgent } from './ai-agent-stream.js';
|
|
4
|
-
export { type RunAIAgentParams, type StreamAIAgentOptions, ToolApprovalRequired, } from './ai-agent-prepare.js';
|
|
4
|
+
export { type RunAIAgentParams, type StreamAIAgentOptions, ToolApprovalRequired, ToolCredentialRequired, } from './ai-agent-prepare.js';
|
|
5
5
|
export { addAIAgent, approveAIAgent, getAIAgents, getAIAgentsMeta, } from './ai-agent-registry.js';
|
|
6
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,5 +1,5 @@
|
|
|
1
1
|
export { agent, agentStream, agentResume, agentApprove, } from './ai-agent-helpers.js';
|
|
2
2
|
export { runAIAgent, resumeAIAgentSync } from './ai-agent-runner.js';
|
|
3
3
|
export { streamAIAgent, resumeAIAgent } from './ai-agent-stream.js';
|
|
4
|
-
export { ToolApprovalRequired, } from './ai-agent-prepare.js';
|
|
4
|
+
export { ToolApprovalRequired, ToolCredentialRequired, } from './ai-agent-prepare.js';
|
|
5
5
|
export { addAIAgent, approveAIAgent, getAIAgents, getAIAgentsMeta, } from './ai-agent-registry.js';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { NotFoundError
|
|
1
|
+
import { NotFoundError } from '../../errors/errors.js';
|
|
2
2
|
import { addFunction } from '../../function/function-runner.js';
|
|
3
3
|
import { pikkuState, getSingletonServices } from '../../pikku-state.js';
|
|
4
4
|
import { coerceTopLevelDataFromSchema, validateSchema } from '../../schema.js';
|
|
@@ -12,9 +12,9 @@ export const wireChannel = (channel) => {
|
|
|
12
12
|
const channelsMeta = pikkuState(null, 'channel', 'meta');
|
|
13
13
|
const channelMeta = channelsMeta[channel.name];
|
|
14
14
|
if (!channelMeta) {
|
|
15
|
-
|
|
15
|
+
console.warn(`[pikku] Skipping channel '${channel.name}' — metadata not found. Consider moving this wiring to its own file.`);
|
|
16
|
+
return;
|
|
16
17
|
}
|
|
17
|
-
pikkuState(null, 'channel', 'channels').set(channel.name, channel);
|
|
18
18
|
// Register onConnect function if provided
|
|
19
19
|
if (channel.onConnect && channelMeta.connect) {
|
|
20
20
|
addFunction(channelMeta.connect.pikkuFuncId, channel.onConnect);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { NotFoundError
|
|
1
|
+
import { NotFoundError } from '../../errors/errors.js';
|
|
2
2
|
import { addFunction, runPikkuFunc } from '../../function/function-runner.js';
|
|
3
3
|
import { pikkuState } from '../../pikku-state.js';
|
|
4
4
|
import { PikkuSessionService, createMiddlewareSessionWireProps, } from '../../services/user-session-service.js';
|
|
@@ -29,7 +29,8 @@ export const wireCLI = (cli) => {
|
|
|
29
29
|
// Get the existing metadata that was generated during inspection
|
|
30
30
|
const cliMeta = pikkuState(null, 'cli', 'meta') || {};
|
|
31
31
|
if (!cliMeta.programs?.[cli.program]) {
|
|
32
|
-
|
|
32
|
+
console.warn(`[pikku] Skipping CLI program '${cli.program}' — metadata not found. Consider moving this wiring to its own file.`);
|
|
33
|
+
return;
|
|
33
34
|
}
|
|
34
35
|
// Get existing programs state and add this program
|
|
35
36
|
const programs = pikkuState(null, 'cli', 'programs') || {};
|
|
@@ -110,7 +111,7 @@ function registerCLICommands(commands, path = [], inheritedOptions = {}, program
|
|
|
110
111
|
programs[program].commandMiddleware[commandId] = command.middleware;
|
|
111
112
|
}
|
|
112
113
|
}
|
|
113
|
-
addFunction(funcName, unwrapFunc(command));
|
|
114
|
+
addFunction(funcName, unwrapFunc(command), currentMeta?.packageName);
|
|
114
115
|
// Register renderer if provided
|
|
115
116
|
if (typeof command === 'object' && command.render) {
|
|
116
117
|
if (programs[program]) {
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
/** Convert kebab-case to camelCase: "from-plan" → "fromPlan" */
|
|
2
|
+
function toCamelCase(str) {
|
|
3
|
+
return str.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
4
|
+
}
|
|
1
5
|
/**
|
|
2
6
|
* Parses raw CLI arguments into structured data for a specific program
|
|
3
7
|
*/
|
|
@@ -81,11 +85,11 @@ export function parseCLIArguments(args, programName, allMeta) {
|
|
|
81
85
|
while (currentIndex < args.length) {
|
|
82
86
|
const arg = args[currentIndex];
|
|
83
87
|
if (arg.startsWith('--')) {
|
|
84
|
-
// Long option
|
|
88
|
+
// Long option (--from-plan → fromPlan)
|
|
85
89
|
const equalIndex = arg.indexOf('=');
|
|
86
90
|
if (equalIndex > 0) {
|
|
87
91
|
// --option=value format
|
|
88
|
-
const key = arg.slice(2, equalIndex);
|
|
92
|
+
const key = toCamelCase(arg.slice(2, equalIndex));
|
|
89
93
|
const optionDef = availableOptions[key];
|
|
90
94
|
// Unknown options are allowed for forward compatibility
|
|
91
95
|
const value = arg.slice(equalIndex + 1);
|
|
@@ -93,7 +97,7 @@ export function parseCLIArguments(args, programName, allMeta) {
|
|
|
93
97
|
}
|
|
94
98
|
else {
|
|
95
99
|
// --option value format
|
|
96
|
-
const key = arg.slice(2);
|
|
100
|
+
const key = toCamelCase(arg.slice(2));
|
|
97
101
|
const optionDef = availableOptions[key];
|
|
98
102
|
// Unknown options are allowed for forward compatibility
|
|
99
103
|
if (optionDef && optionDef.array) {
|
|
@@ -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, exposeErrors, generateRequestId, }?: RunHTTPWiringOptions) => Promise<Out | void>;
|
|
139
|
+
export declare const fetchData: <In, Out>(request: Request | PikkuHTTPRequest, response: PikkuHTTPResponse, { skipUserSession, respondWith404, logWarningsForStatusCodes, coerceDataFromSchema, bubbleErrors, exposeErrors, generateRequestId, traceId: externalTraceId, }?: RunHTTPWiringOptions) => Promise<Out | void>;
|