@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
|
@@ -2,14 +2,35 @@ import type {
|
|
|
2
2
|
AIAgentInput,
|
|
3
3
|
AIAgentOutput,
|
|
4
4
|
AIAgentStep,
|
|
5
|
-
|
|
5
|
+
AIAgentMemoryConfig,
|
|
6
|
+
CoreAIAgent,
|
|
6
7
|
PikkuAIMiddlewareHooks,
|
|
8
|
+
AgentRunState,
|
|
7
9
|
} from './ai-agent.types.js'
|
|
8
|
-
import type {
|
|
10
|
+
import type {
|
|
11
|
+
AIAgentStepResult,
|
|
12
|
+
AIAgentRunnerParams,
|
|
13
|
+
AIAgentRunnerService,
|
|
14
|
+
} from '../../services/ai-agent-runner-service.js'
|
|
15
|
+
import type { AIStorageService } from '../../services/ai-storage-service.js'
|
|
16
|
+
import type { AIRunStateService } from '../../services/ai-run-state-service.js'
|
|
9
17
|
|
|
10
|
-
import {
|
|
11
|
-
|
|
12
|
-
|
|
18
|
+
import {
|
|
19
|
+
saveMessages,
|
|
20
|
+
resolveMemoryServices,
|
|
21
|
+
loadContextMessages,
|
|
22
|
+
trimMessages,
|
|
23
|
+
} from './ai-agent-memory.js'
|
|
24
|
+
import {
|
|
25
|
+
prepareAgentRun,
|
|
26
|
+
resolveAgent,
|
|
27
|
+
buildInstructions,
|
|
28
|
+
buildToolDefs,
|
|
29
|
+
type RunAIAgentParams,
|
|
30
|
+
} from './ai-agent-prepare.js'
|
|
31
|
+
import { checkForApprovals, appendStepMessages } from './ai-agent-stream.js'
|
|
32
|
+
import { pikkuState, getSingletonServices } from '../../pikku-state.js'
|
|
33
|
+
import { resolveModelConfig } from './ai-agent-model-config.js'
|
|
13
34
|
import { randomUUID } from 'crypto'
|
|
14
35
|
|
|
15
36
|
export async function runAIAgent(
|
|
@@ -147,35 +168,102 @@ export async function runAIAgent(
|
|
|
147
168
|
|
|
148
169
|
if (stepResult.toolCalls.length === 0) break
|
|
149
170
|
|
|
150
|
-
const
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
171
|
+
const approvalsNeeded = checkForApprovals(
|
|
172
|
+
stepResult,
|
|
173
|
+
runnerParams.tools,
|
|
174
|
+
runId
|
|
175
|
+
)
|
|
176
|
+
if (approvalsNeeded.length > 0) {
|
|
177
|
+
for (const approval of approvalsNeeded) {
|
|
178
|
+
const toolDef = runnerParams.tools.find(
|
|
179
|
+
(t) => t.name === approval.toolName
|
|
180
|
+
)
|
|
181
|
+
if (toolDef?.approvalDescriptionFn && !approval.reason) {
|
|
182
|
+
try {
|
|
183
|
+
approval.reason = await toolDef.approvalDescriptionFn(
|
|
184
|
+
approval.args
|
|
185
|
+
)
|
|
186
|
+
} catch {
|
|
187
|
+
// If description generation fails, continue without it
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const pendingApprovals = approvalsNeeded.map((a) =>
|
|
193
|
+
a.agentRunId
|
|
194
|
+
? {
|
|
195
|
+
type: 'agent-call' as const,
|
|
196
|
+
toolCallId: a.toolCallId,
|
|
197
|
+
agentName: a.toolName,
|
|
198
|
+
agentRunId: a.agentRunId,
|
|
199
|
+
displayToolName: a.displayToolName ?? a.toolName,
|
|
200
|
+
displayArgs: a.displayArgs ?? a.args,
|
|
201
|
+
}
|
|
202
|
+
: {
|
|
203
|
+
type: 'tool-call' as const,
|
|
204
|
+
toolCallId: a.toolCallId,
|
|
205
|
+
toolName: a.toolName,
|
|
206
|
+
args: a.args,
|
|
207
|
+
}
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
const completedStepsForSave = accumulatedSteps.slice(0, -1)
|
|
211
|
+
await saveMessages(
|
|
212
|
+
storage,
|
|
213
|
+
threadId,
|
|
214
|
+
input.resourceId,
|
|
215
|
+
memoryConfig,
|
|
216
|
+
userMessage,
|
|
217
|
+
{ text: '', steps: completedStepsForSave },
|
|
218
|
+
{
|
|
219
|
+
workingMemoryJsonSchema,
|
|
220
|
+
workingMemorySchemaName,
|
|
221
|
+
logger: singletonServices.logger,
|
|
222
|
+
schemaService: singletonServices.schema,
|
|
223
|
+
}
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
if (storage) {
|
|
227
|
+
await storage.saveMessages(threadId, [
|
|
228
|
+
{
|
|
229
|
+
id: randomUUID(),
|
|
230
|
+
role: 'assistant',
|
|
231
|
+
toolCalls: stepResult.toolCalls.map((tc) => ({
|
|
232
|
+
id: tc.toolCallId,
|
|
233
|
+
name: tc.toolName,
|
|
234
|
+
args: tc.args as Record<string, unknown>,
|
|
235
|
+
})),
|
|
236
|
+
createdAt: new Date(),
|
|
237
|
+
},
|
|
238
|
+
])
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
await aiRunState.updateRun(runId, {
|
|
242
|
+
status: 'suspended',
|
|
243
|
+
suspendReason: 'approval',
|
|
244
|
+
pendingApprovals,
|
|
245
|
+
usage: { ...totalUsage, model: agent.model },
|
|
246
|
+
})
|
|
247
|
+
|
|
248
|
+
const suspendedFinalText = lastStepResult?.text ?? ''
|
|
249
|
+
return {
|
|
250
|
+
runId,
|
|
251
|
+
text: suspendedFinalText,
|
|
252
|
+
threadId,
|
|
253
|
+
steps: accumulatedSteps,
|
|
254
|
+
usage: totalUsage,
|
|
255
|
+
status: 'suspended',
|
|
256
|
+
pendingApprovals: approvalsNeeded.map((a) => ({
|
|
257
|
+
toolCallId: a.toolCallId,
|
|
258
|
+
toolName: a.displayToolName ?? a.toolName,
|
|
259
|
+
args: a.displayArgs ?? a.args,
|
|
260
|
+
reason: a.reason,
|
|
261
|
+
runId,
|
|
174
262
|
})),
|
|
175
|
-
createdAt: new Date(),
|
|
176
263
|
}
|
|
177
|
-
runnerParams.messages.push(toolMsg)
|
|
178
264
|
}
|
|
265
|
+
|
|
266
|
+
appendStepMessages(runnerParams, stepResult)
|
|
179
267
|
}
|
|
180
268
|
|
|
181
269
|
const finalText = lastStepResult?.text ?? ''
|
|
@@ -245,6 +333,440 @@ export async function runAIAgent(
|
|
|
245
333
|
}
|
|
246
334
|
await aiRunState.updateRun(runId, {
|
|
247
335
|
status: 'failed',
|
|
336
|
+
errorMessage: error instanceof Error ? error.message : String(error),
|
|
337
|
+
})
|
|
338
|
+
throw error
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
export async function resumeAIAgentSync(
|
|
343
|
+
runId: string,
|
|
344
|
+
approvals: { toolCallId: string; approved: boolean }[],
|
|
345
|
+
params: RunAIAgentParams,
|
|
346
|
+
expectedAgentName?: string
|
|
347
|
+
): Promise<AIAgentOutput> {
|
|
348
|
+
const singletonServices = getSingletonServices()
|
|
349
|
+
const { aiRunState } = singletonServices
|
|
350
|
+
if (!aiRunState) {
|
|
351
|
+
throw new Error('AIRunStateService not available in singletonServices')
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
const run = await aiRunState.getRun(runId)
|
|
355
|
+
if (!run) throw new Error(`No run found for runId ${runId}`)
|
|
356
|
+
if (expectedAgentName && run.agentName !== expectedAgentName) {
|
|
357
|
+
throw new Error(
|
|
358
|
+
`Run ${runId} belongs to agent '${run.agentName}', not '${expectedAgentName}'`
|
|
359
|
+
)
|
|
360
|
+
}
|
|
361
|
+
if (run.status !== 'suspended') {
|
|
362
|
+
throw new Error(`Run ${runId} is not suspended (status: ${run.status})`)
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
const { agent, packageName, resolvedName } = resolveAgent(run.agentName)
|
|
366
|
+
const { storage } = resolveMemoryServices(agent, singletonServices)
|
|
367
|
+
const memoryConfig = agent.memory
|
|
368
|
+
const agentRunner = singletonServices.aiAgentRunner
|
|
369
|
+
if (!agentRunner) {
|
|
370
|
+
throw new Error('AIAgentRunnerService not available')
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
const approvedIds = new Set(
|
|
374
|
+
approvals.filter((a) => a.approved).map((a) => a.toolCallId)
|
|
375
|
+
)
|
|
376
|
+
const rejectedIds = new Set(
|
|
377
|
+
approvals.filter((a) => !a.approved).map((a) => a.toolCallId)
|
|
378
|
+
)
|
|
379
|
+
|
|
380
|
+
const savedPendingApprovals = [...(run.pendingApprovals ?? [])]
|
|
381
|
+
|
|
382
|
+
for (const { toolCallId, approved } of approvals) {
|
|
383
|
+
await aiRunState.resolveApproval(
|
|
384
|
+
toolCallId,
|
|
385
|
+
approved ? 'approved' : 'denied'
|
|
386
|
+
)
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
const { tools } = await buildToolDefs(
|
|
390
|
+
params,
|
|
391
|
+
new Map<string, string>(),
|
|
392
|
+
run.resourceId,
|
|
393
|
+
resolvedName,
|
|
394
|
+
packageName,
|
|
395
|
+
undefined,
|
|
396
|
+
agent.aiMiddleware ?? []
|
|
397
|
+
)
|
|
398
|
+
|
|
399
|
+
const toolCallMessages: {
|
|
400
|
+
toolCallId: string
|
|
401
|
+
toolName: string
|
|
402
|
+
args: any
|
|
403
|
+
result: string
|
|
404
|
+
}[] = []
|
|
405
|
+
|
|
406
|
+
for (const pending of savedPendingApprovals) {
|
|
407
|
+
if (pending.type !== 'tool-call') continue
|
|
408
|
+
|
|
409
|
+
const toolCallId = pending.toolCallId
|
|
410
|
+
let resultStr: string
|
|
411
|
+
|
|
412
|
+
if (rejectedIds.has(toolCallId)) {
|
|
413
|
+
resultStr =
|
|
414
|
+
'The user explicitly declined this action. Inform them that it was declined and do not retry.'
|
|
415
|
+
} else if (approvedIds.has(toolCallId)) {
|
|
416
|
+
const matchingTool = tools.find((t) => t.name === pending.toolName)
|
|
417
|
+
if (!matchingTool) {
|
|
418
|
+
throw new Error(
|
|
419
|
+
`Tool "${pending.toolName}" not found in agent definition`
|
|
420
|
+
)
|
|
421
|
+
}
|
|
422
|
+
const toolArgs =
|
|
423
|
+
typeof pending.args === 'string'
|
|
424
|
+
? JSON.parse(pending.args)
|
|
425
|
+
: pending.args
|
|
426
|
+
try {
|
|
427
|
+
const toolResult = await matchingTool.execute(toolArgs)
|
|
428
|
+
resultStr =
|
|
429
|
+
typeof toolResult === 'string'
|
|
430
|
+
? toolResult
|
|
431
|
+
: JSON.stringify(toolResult)
|
|
432
|
+
} catch (err) {
|
|
433
|
+
resultStr = `Error: ${err instanceof Error ? err.message : String(err)}`
|
|
434
|
+
}
|
|
435
|
+
} else {
|
|
436
|
+
continue
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
toolCallMessages.push({
|
|
440
|
+
toolCallId,
|
|
441
|
+
toolName: pending.toolName,
|
|
442
|
+
args:
|
|
443
|
+
typeof pending.args === 'string'
|
|
444
|
+
? JSON.parse(pending.args)
|
|
445
|
+
: pending.args,
|
|
446
|
+
result: resultStr,
|
|
447
|
+
})
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
if (storage && toolCallMessages.length > 0) {
|
|
451
|
+
await storage.saveMessages(run.threadId, [
|
|
452
|
+
{
|
|
453
|
+
id: randomUUID(),
|
|
454
|
+
role: 'tool',
|
|
455
|
+
toolResults: toolCallMessages.map((tc) => ({
|
|
456
|
+
id: tc.toolCallId,
|
|
457
|
+
name: tc.toolName,
|
|
458
|
+
result: tc.result,
|
|
459
|
+
})),
|
|
460
|
+
createdAt: new Date(),
|
|
461
|
+
},
|
|
462
|
+
])
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
await aiRunState.updateRun(runId, { status: 'running' })
|
|
466
|
+
|
|
467
|
+
return continueAfterToolResultSync(
|
|
468
|
+
run,
|
|
469
|
+
agent,
|
|
470
|
+
packageName,
|
|
471
|
+
resolvedName,
|
|
472
|
+
storage,
|
|
473
|
+
memoryConfig,
|
|
474
|
+
agentRunner,
|
|
475
|
+
params,
|
|
476
|
+
aiRunState
|
|
477
|
+
)
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
async function continueAfterToolResultSync(
|
|
481
|
+
run: AgentRunState,
|
|
482
|
+
agent: CoreAIAgent,
|
|
483
|
+
packageName: string | null,
|
|
484
|
+
resolvedName: string,
|
|
485
|
+
storage: AIStorageService | undefined,
|
|
486
|
+
memoryConfig: AIAgentMemoryConfig | undefined,
|
|
487
|
+
agentRunner: AIAgentRunnerService,
|
|
488
|
+
params: RunAIAgentParams,
|
|
489
|
+
aiRunState: AIRunStateService
|
|
490
|
+
): Promise<AIAgentOutput> {
|
|
491
|
+
const singletonServices = getSingletonServices()
|
|
492
|
+
const agentsMeta = pikkuState(packageName, 'agent', 'agentsMeta')
|
|
493
|
+
const meta = agentsMeta[resolvedName]
|
|
494
|
+
const workingMemorySchemaName = meta?.workingMemorySchema ?? null
|
|
495
|
+
|
|
496
|
+
const messages = storage
|
|
497
|
+
? await storage.getMessages(run.threadId, {
|
|
498
|
+
lastN: memoryConfig?.lastMessages ?? 20,
|
|
499
|
+
})
|
|
500
|
+
: []
|
|
501
|
+
|
|
502
|
+
const workingMemoryJsonSchema = workingMemorySchemaName
|
|
503
|
+
? pikkuState(packageName, 'misc', 'schemas').get(workingMemorySchemaName)
|
|
504
|
+
: undefined
|
|
505
|
+
|
|
506
|
+
const contextMessages = await loadContextMessages(
|
|
507
|
+
memoryConfig,
|
|
508
|
+
storage,
|
|
509
|
+
{ message: '', threadId: run.threadId, resourceId: run.resourceId },
|
|
510
|
+
workingMemoryJsonSchema
|
|
511
|
+
)
|
|
512
|
+
|
|
513
|
+
const allMessages = [...contextMessages, ...messages]
|
|
514
|
+
const trimmedMessages = trimMessages(allMessages)
|
|
515
|
+
|
|
516
|
+
const instructions = await buildInstructions(resolvedName, packageName)
|
|
517
|
+
|
|
518
|
+
const aiMiddlewares: PikkuAIMiddlewareHooks[] = agent.aiMiddleware ?? []
|
|
519
|
+
let modifiedMessages = trimmedMessages
|
|
520
|
+
let modifiedInstructions = instructions
|
|
521
|
+
for (const mw of aiMiddlewares) {
|
|
522
|
+
if (mw.modifyInput) {
|
|
523
|
+
const result = await mw.modifyInput(singletonServices, {
|
|
524
|
+
messages: modifiedMessages,
|
|
525
|
+
instructions: modifiedInstructions,
|
|
526
|
+
})
|
|
527
|
+
modifiedMessages = result.messages
|
|
528
|
+
modifiedInstructions = result.instructions
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
const { tools: resumeTools } = await buildToolDefs(
|
|
533
|
+
params,
|
|
534
|
+
new Map<string, string>(),
|
|
535
|
+
run.resourceId,
|
|
536
|
+
resolvedName,
|
|
537
|
+
packageName,
|
|
538
|
+
undefined,
|
|
539
|
+
aiMiddlewares
|
|
540
|
+
)
|
|
541
|
+
|
|
542
|
+
const resolved = resolveModelConfig(resolvedName, agent)
|
|
543
|
+
const maxSteps = resolved.maxSteps ?? 10
|
|
544
|
+
|
|
545
|
+
const runnerParams: AIAgentRunnerParams = {
|
|
546
|
+
model: resolved.model,
|
|
547
|
+
temperature: resolved.temperature,
|
|
548
|
+
instructions: modifiedInstructions,
|
|
549
|
+
messages: modifiedMessages,
|
|
550
|
+
tools: resumeTools,
|
|
551
|
+
maxSteps: 1,
|
|
552
|
+
toolChoice: (agent.toolChoice ?? 'auto') as 'auto' | 'required' | 'none',
|
|
553
|
+
outputSchema: meta?.outputSchema
|
|
554
|
+
? pikkuState(packageName, 'misc', 'schemas').get(meta.outputSchema)
|
|
555
|
+
: undefined,
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
try {
|
|
559
|
+
const accumulatedSteps: AIAgentStep[] = []
|
|
560
|
+
const totalUsage = { inputTokens: 0, outputTokens: 0 }
|
|
561
|
+
let lastStepResult: AIAgentStepResult | null = null
|
|
562
|
+
|
|
563
|
+
for (let step = 0; step < maxSteps; step++) {
|
|
564
|
+
const stepResult = await agentRunner.run(runnerParams)
|
|
565
|
+
lastStepResult = stepResult
|
|
566
|
+
|
|
567
|
+
totalUsage.inputTokens += stepResult.usage.inputTokens
|
|
568
|
+
totalUsage.outputTokens += stepResult.usage.outputTokens
|
|
569
|
+
|
|
570
|
+
for (const mw of aiMiddlewares) {
|
|
571
|
+
if (mw.afterStep) {
|
|
572
|
+
await mw.afterStep(singletonServices, {
|
|
573
|
+
stepNumber: step,
|
|
574
|
+
text: stepResult.text,
|
|
575
|
+
toolCalls: stepResult.toolCalls,
|
|
576
|
+
toolResults: stepResult.toolResults,
|
|
577
|
+
usage: stepResult.usage,
|
|
578
|
+
finishReason: stepResult.finishReason,
|
|
579
|
+
})
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
accumulatedSteps.push({
|
|
584
|
+
usage: stepResult.usage,
|
|
585
|
+
toolCalls: stepResult.toolCalls.map((tc) => {
|
|
586
|
+
const tr = stepResult.toolResults.find(
|
|
587
|
+
(r) => r.toolCallId === tc.toolCallId
|
|
588
|
+
)
|
|
589
|
+
return {
|
|
590
|
+
name: tc.toolName,
|
|
591
|
+
args: tc.args as Record<string, unknown>,
|
|
592
|
+
result:
|
|
593
|
+
typeof tr?.result === 'string'
|
|
594
|
+
? tr.result
|
|
595
|
+
: JSON.stringify(tr?.result ?? ''),
|
|
596
|
+
}
|
|
597
|
+
}),
|
|
598
|
+
})
|
|
599
|
+
|
|
600
|
+
if (stepResult.toolCalls.length === 0) break
|
|
601
|
+
|
|
602
|
+
const approvalsNeeded = checkForApprovals(
|
|
603
|
+
stepResult,
|
|
604
|
+
runnerParams.tools,
|
|
605
|
+
run.runId
|
|
606
|
+
)
|
|
607
|
+
if (approvalsNeeded.length > 0) {
|
|
608
|
+
for (const approval of approvalsNeeded) {
|
|
609
|
+
const toolDef = runnerParams.tools.find(
|
|
610
|
+
(t) => t.name === approval.toolName
|
|
611
|
+
)
|
|
612
|
+
if (toolDef?.approvalDescriptionFn && !approval.reason) {
|
|
613
|
+
try {
|
|
614
|
+
approval.reason = await toolDef.approvalDescriptionFn(
|
|
615
|
+
approval.args
|
|
616
|
+
)
|
|
617
|
+
} catch {
|
|
618
|
+
// ignore
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
const pendingApprovals = approvalsNeeded.map((a) =>
|
|
624
|
+
a.agentRunId
|
|
625
|
+
? {
|
|
626
|
+
type: 'agent-call' as const,
|
|
627
|
+
toolCallId: a.toolCallId,
|
|
628
|
+
agentName: a.toolName,
|
|
629
|
+
agentRunId: a.agentRunId,
|
|
630
|
+
displayToolName: a.displayToolName ?? a.toolName,
|
|
631
|
+
displayArgs: a.displayArgs ?? a.args,
|
|
632
|
+
}
|
|
633
|
+
: {
|
|
634
|
+
type: 'tool-call' as const,
|
|
635
|
+
toolCallId: a.toolCallId,
|
|
636
|
+
toolName: a.toolName,
|
|
637
|
+
args: a.args,
|
|
638
|
+
}
|
|
639
|
+
)
|
|
640
|
+
|
|
641
|
+
const completedSteps = accumulatedSteps.slice(0, -1)
|
|
642
|
+
if (completedSteps.length > 0) {
|
|
643
|
+
await saveMessages(
|
|
644
|
+
storage,
|
|
645
|
+
run.threadId,
|
|
646
|
+
run.resourceId,
|
|
647
|
+
memoryConfig,
|
|
648
|
+
null,
|
|
649
|
+
{ text: '', steps: completedSteps },
|
|
650
|
+
{
|
|
651
|
+
workingMemoryJsonSchema,
|
|
652
|
+
workingMemorySchemaName,
|
|
653
|
+
logger: singletonServices.logger,
|
|
654
|
+
schemaService: singletonServices.schema,
|
|
655
|
+
}
|
|
656
|
+
)
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
if (storage) {
|
|
660
|
+
await storage.saveMessages(run.threadId, [
|
|
661
|
+
{
|
|
662
|
+
id: randomUUID(),
|
|
663
|
+
role: 'assistant',
|
|
664
|
+
toolCalls: stepResult.toolCalls.map((tc) => ({
|
|
665
|
+
id: tc.toolCallId,
|
|
666
|
+
name: tc.toolName,
|
|
667
|
+
args: tc.args as Record<string, unknown>,
|
|
668
|
+
})),
|
|
669
|
+
createdAt: new Date(),
|
|
670
|
+
},
|
|
671
|
+
])
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
await aiRunState.updateRun(run.runId, {
|
|
675
|
+
status: 'suspended',
|
|
676
|
+
suspendReason: 'approval',
|
|
677
|
+
pendingApprovals,
|
|
678
|
+
usage: { ...totalUsage, model: agent.model },
|
|
679
|
+
})
|
|
680
|
+
|
|
681
|
+
const suspendedText = lastStepResult?.text ?? ''
|
|
682
|
+
return {
|
|
683
|
+
runId: run.runId,
|
|
684
|
+
text: suspendedText,
|
|
685
|
+
threadId: run.threadId,
|
|
686
|
+
steps: accumulatedSteps,
|
|
687
|
+
usage: totalUsage,
|
|
688
|
+
status: 'suspended',
|
|
689
|
+
pendingApprovals: approvalsNeeded.map((a) => ({
|
|
690
|
+
toolCallId: a.toolCallId,
|
|
691
|
+
toolName: a.displayToolName ?? a.toolName,
|
|
692
|
+
args: a.displayArgs ?? a.args,
|
|
693
|
+
reason: a.reason,
|
|
694
|
+
runId: run.runId,
|
|
695
|
+
})),
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
appendStepMessages(runnerParams, stepResult)
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
const finalText = lastStepResult?.text ?? ''
|
|
703
|
+
const finalObject = lastStepResult?.object
|
|
704
|
+
|
|
705
|
+
const result = {
|
|
706
|
+
text: finalText,
|
|
707
|
+
steps: accumulatedSteps,
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
const responseText = await saveMessages(
|
|
711
|
+
storage,
|
|
712
|
+
run.threadId,
|
|
713
|
+
run.resourceId,
|
|
714
|
+
memoryConfig,
|
|
715
|
+
null as any,
|
|
716
|
+
result,
|
|
717
|
+
{
|
|
718
|
+
workingMemoryJsonSchema,
|
|
719
|
+
workingMemorySchemaName,
|
|
720
|
+
logger: singletonServices.logger,
|
|
721
|
+
schemaService: singletonServices.schema,
|
|
722
|
+
}
|
|
723
|
+
)
|
|
724
|
+
|
|
725
|
+
let outputText = responseText
|
|
726
|
+
let outputMessages = runnerParams.messages
|
|
727
|
+
for (let i = aiMiddlewares.length - 1; i >= 0; i--) {
|
|
728
|
+
const mw = aiMiddlewares[i]
|
|
729
|
+
if (mw.modifyOutput) {
|
|
730
|
+
const modResult = await mw.modifyOutput(singletonServices, {
|
|
731
|
+
text: outputText,
|
|
732
|
+
messages: outputMessages,
|
|
733
|
+
usage: totalUsage,
|
|
734
|
+
})
|
|
735
|
+
outputText = modResult.text
|
|
736
|
+
outputMessages = modResult.messages
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
await aiRunState.updateRun(run.runId, {
|
|
741
|
+
status: 'completed',
|
|
742
|
+
usage: { ...totalUsage, model: agent.model },
|
|
743
|
+
})
|
|
744
|
+
|
|
745
|
+
return {
|
|
746
|
+
runId: run.runId,
|
|
747
|
+
text: outputText,
|
|
748
|
+
object: finalObject,
|
|
749
|
+
threadId: run.threadId,
|
|
750
|
+
steps: accumulatedSteps,
|
|
751
|
+
usage: totalUsage,
|
|
752
|
+
}
|
|
753
|
+
} catch (error) {
|
|
754
|
+
for (const mw of aiMiddlewares) {
|
|
755
|
+
if (mw.onError) {
|
|
756
|
+
try {
|
|
757
|
+
await mw.onError(singletonServices, {
|
|
758
|
+
error: error instanceof Error ? error : new Error(String(error)),
|
|
759
|
+
stepNumber: -1,
|
|
760
|
+
messages: runnerParams.messages,
|
|
761
|
+
})
|
|
762
|
+
} catch {
|
|
763
|
+
// ignore
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
await aiRunState.updateRun(run.runId, {
|
|
768
|
+
status: 'failed',
|
|
769
|
+
errorMessage: error instanceof Error ? error.message : String(error),
|
|
248
770
|
})
|
|
249
771
|
throw error
|
|
250
772
|
}
|