@pikku/core 0.12.79 → 0.12.80
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 +161 -0
- package/dist/dev/hot-reload.js +1 -6
- package/dist/ecosystem.d.ts +27 -0
- package/dist/ecosystem.js +26 -0
- package/dist/errors/error-handler.js +4 -2
- package/dist/function/function-runner.js +20 -42
- package/dist/function/functions.types.d.ts +9 -5
- package/dist/index.d.ts +4 -9
- package/dist/index.js +3 -8
- package/dist/middleware/auth-cookie.d.ts +0 -4
- package/dist/middleware/auth-cookie.js +38 -2
- package/dist/middleware/cors.js +1 -0
- package/dist/middleware/index.d.ts +1 -1
- package/dist/middleware/index.js +1 -1
- package/dist/middleware/remote-auth.js +14 -3
- package/dist/permissions.d.ts +8 -10
- package/dist/permissions.js +0 -11
- package/dist/pikku-state.js +12 -5
- package/dist/schema.js +35 -1
- package/dist/services/ai-run-state-service.d.ts +7 -1
- package/dist/services/in-memory-ai-run-state-service.d.ts +1 -1
- package/dist/services/in-memory-ai-run-state-service.js +2 -1
- package/dist/services/in-memory-workflow-service.js +2 -0
- package/dist/services/local-content-request-handler.d.ts +21 -0
- package/dist/services/local-content-request-handler.js +72 -53
- package/dist/services/local-content.d.ts +6 -0
- package/dist/services/local-content.js +14 -1
- package/dist/services/workflow-service.d.ts +4 -2
- package/dist/testing/service-tests/agent-run-service-tests.d.ts +10 -0
- package/dist/testing/service-tests/agent-run-service-tests.js +72 -0
- package/dist/testing/service-tests/ai-storage-service-tests.d.ts +3 -0
- package/dist/testing/service-tests/ai-storage-service-tests.js +226 -0
- package/dist/testing/service-tests/channel-store-tests.d.ts +3 -0
- package/dist/testing/service-tests/channel-store-tests.js +72 -0
- package/dist/testing/service-tests/credential-service-tests.d.ts +3 -0
- package/dist/testing/service-tests/credential-service-tests.js +109 -0
- package/dist/testing/service-tests/deployment-service-tests.d.ts +3 -0
- package/dist/testing/service-tests/deployment-service-tests.js +21 -0
- package/dist/testing/service-tests/event-hub-store-tests.d.ts +3 -0
- package/dist/testing/service-tests/event-hub-store-tests.js +34 -0
- package/dist/testing/service-tests/secret-service-tests.d.ts +3 -0
- package/dist/testing/service-tests/secret-service-tests.js +80 -0
- package/dist/testing/service-tests/session-store-tests.d.ts +3 -0
- package/dist/testing/service-tests/session-store-tests.js +43 -0
- package/dist/testing/service-tests/workflow-run-service-tests.d.ts +3 -0
- package/dist/testing/service-tests/workflow-run-service-tests.js +42 -0
- package/dist/testing/service-tests/workflow-service-tests.d.ts +3 -0
- package/dist/testing/service-tests/workflow-service-tests.js +150 -0
- package/dist/testing/service-tests.d.ts +6 -0
- package/dist/testing/service-tests.js +26 -791
- package/dist/types/core.types.d.ts +11 -1
- package/dist/types/state.types.d.ts +1 -1
- package/dist/wirings/actor-flow/run-conversation.js +1 -6
- package/dist/wirings/ai-agent/agent-rpc.d.ts +15 -0
- package/dist/wirings/ai-agent/agent-rpc.js +53 -0
- package/dist/wirings/ai-agent/ai-agent-agui.js +1 -5
- package/dist/wirings/ai-agent/ai-agent-memory.js +3 -1
- package/dist/wirings/ai-agent/ai-agent-prepare.js +3 -9
- package/dist/wirings/ai-agent/ai-agent-runner.js +28 -107
- package/dist/wirings/ai-agent/ai-agent-stream.js +20 -61
- package/dist/wirings/ai-agent/ai-agent-turn.d.ts +56 -0
- package/dist/wirings/ai-agent/ai-agent-turn.js +81 -0
- package/dist/wirings/ai-agent/ai-agent.types.d.ts +1 -1
- package/dist/wirings/ai-agent/voice-input.js +1 -6
- package/dist/wirings/ai-agent/voice-output.js +2 -12
- package/dist/wirings/channel/channel-common.js +1 -0
- package/dist/wirings/channel/channel-handler.js +3 -5
- package/dist/wirings/channel/channel-rpc-service.d.ts +0 -6
- package/dist/wirings/channel/channel-rpc-service.js +0 -8
- package/dist/wirings/channel/channel-rpc.types.d.ts +6 -0
- package/dist/wirings/channel/channel-rpc.types.js +8 -0
- package/dist/wirings/channel/channel-runner.d.ts +1 -3
- package/dist/wirings/channel/channel-runner.js +16 -8
- package/dist/wirings/channel/channel.types.d.ts +2 -0
- package/dist/wirings/channel/pikku-abstract-channel-handler.js +1 -0
- package/dist/wirings/channel/serverless/serverless-channel-runner.js +3 -0
- package/dist/wirings/cli/channel/cli-channel-runner.js +2 -0
- package/dist/wirings/cli/cli-runner.js +4 -2
- package/dist/wirings/cli/cli.types.d.ts +0 -8
- package/dist/wirings/cli/command-parser.js +13 -0
- package/dist/wirings/gateway/gateway-runner.js +9 -2
- package/dist/wirings/http/http-routes.js +2 -0
- package/dist/wirings/http/http-runner.d.ts +0 -10
- package/dist/wirings/http/http-runner.js +6 -13
- package/dist/wirings/http/http.types.d.ts +0 -10
- package/dist/wirings/http/index.d.ts +1 -1
- package/dist/wirings/http/index.js +1 -1
- package/dist/wirings/mcp/mcp-runner.d.ts +0 -7
- package/dist/wirings/mcp/mcp-runner.js +0 -6
- package/dist/wirings/rpc/rpc-runner.d.ts +8 -51
- package/dist/wirings/rpc/rpc-runner.js +53 -86
- package/dist/wirings/rpc/rpc-types.d.ts +3 -0
- package/dist/wirings/secret/validate-secret-definitions.js +2 -2
- package/dist/wirings/trigger/pikku-trigger-service.d.ts +0 -4
- package/dist/wirings/trigger/trigger-runner.js +1 -0
- package/dist/wirings/virtual-user/run-virtual-user.js +11 -11
- package/dist/wirings/virtual-user/virtual-user-derive.js +4 -25
- package/dist/wirings/virtual-user/virtual-user-dispositions.js +1 -4
- package/dist/wirings/workflow/dsl/workflow-dsl.types.d.ts +29 -2
- package/dist/wirings/workflow/feature.js +7 -4
- package/dist/wirings/workflow/graph/graph-runner.d.ts +1 -2
- package/dist/wirings/workflow/graph/graph-runner.js +8 -7
- package/dist/wirings/workflow/graph/workflow-graph.types.d.ts +0 -4
- package/dist/wirings/workflow/index.d.ts +7 -2
- package/dist/wirings/workflow/index.js +5 -1
- package/dist/wirings/workflow/pikku-scenario-service.d.ts +17 -3
- package/dist/wirings/workflow/pikku-scenario-service.js +38 -10
- package/dist/wirings/workflow/pikku-workflow-service.d.ts +28 -147
- package/dist/wirings/workflow/pikku-workflow-service.js +71 -493
- package/dist/wirings/workflow/workflow-approval.d.ts +39 -0
- package/dist/wirings/workflow/workflow-approval.js +114 -0
- package/dist/wirings/workflow/workflow-constants.d.ts +26 -0
- package/dist/wirings/workflow/workflow-constants.js +35 -0
- package/dist/wirings/workflow/workflow-errors.d.ts +58 -0
- package/dist/wirings/workflow/workflow-errors.js +112 -0
- package/dist/wirings/workflow/workflow-meta-resolver.d.ts +11 -0
- package/dist/wirings/workflow/workflow-meta-resolver.js +31 -0
- package/dist/wirings/workflow/workflow-queue-routing.d.ts +8 -0
- package/dist/wirings/workflow/workflow-queue-routing.js +38 -0
- package/dist/wirings/workflow/workflow-queue-wiring.d.ts +20 -0
- package/dist/wirings/workflow/workflow-queue-wiring.js +79 -0
- package/dist/wirings/workflow/workflow-recovery.d.ts +68 -0
- package/dist/wirings/workflow/workflow-recovery.js +101 -0
- package/dist/wirings/workflow/workflow-run-engine.types.d.ts +54 -0
- package/dist/wirings/workflow/workflow-run-engine.types.js +1 -0
- package/dist/wirings/workflow/workflow-run-ownership.d.ts +16 -0
- package/dist/wirings/workflow/workflow-run-ownership.js +29 -0
- package/dist/wirings/workflow/workflow-suspend.d.ts +12 -0
- package/dist/wirings/workflow/workflow-suspend.js +33 -0
- package/dist/wirings/workflow/workflow.types.d.ts +7 -0
- package/knowledge/decisions/internals/a-non-streaming-agent-run-registers-with-airunstate-too.md +22 -0
- package/knowledge/decisions/internals/a-resumed-agent-turn-is-as-interruptible-as-the-first.md +20 -0
- package/knowledge/decisions/internals/a-scenario-step-template-is-offered-unfilled.md +21 -0
- package/knowledge/decisions/internals/a-virtual-user-decides-whether-to-trust-memory-once-per-turn.md +21 -0
- package/knowledge/decisions/internals/a-wall-clock-threshold-is-a-load-test-in-disguise.md +46 -0
- package/knowledge/decisions/internals/a-workflow-wire-is-built-from-the-run-not-from-the-rpc-service.md +45 -0
- package/knowledge/decisions/internals/agent-context-waits-for-a-tool-result-still-being-written.md +21 -0
- package/knowledge/decisions/internals/agent-speech-travels-as-a-custom-agui-event.md +22 -0
- package/knowledge/decisions/internals/an-agent-interrupt-is-not-a-failure.md +23 -0
- package/knowledge/decisions/internals/an-agent-run-owned-by-another-instance-says-so.md +25 -0
- package/knowledge/decisions/internals/an-agent-stream-send-must-return-the-inner-sends-promise.md +22 -0
- package/knowledge/decisions/internals/an-empty-text-part-is-omitted-from-an-agent-message.md +20 -0
- package/knowledge/decisions/internals/an-empty-transcript-is-not-recorded.md +22 -0
- package/knowledge/decisions/internals/an-unref-d-timer-cannot-be-awaited-under-node-test.md +66 -0
- package/knowledge/decisions/internals/channel-state-accessors-are-unsound-generics-that-every-implementation-asserts.md +33 -0
- package/knowledge/decisions/internals/gateway-listener-middleware-runs-without-an-rpc-on-the-wire.md +43 -0
- package/knowledge/decisions/internals/hot-reload-writes-into-the-function-map-captured-at-startup.md +26 -0
- package/knowledge/decisions/internals/index.md +38 -26
- package/knowledge/decisions/internals/only-exposed-functions-enter-a-virtual-user-catalogue.md +21 -0
- package/knowledge/decisions/internals/scenario-given-and-when-are-sugar-but-then-is-not.md +24 -0
- package/knowledge/decisions/internals/side-effects-are-an-allowlist-not-a-boolean.md +34 -0
- package/knowledge/decisions/internals/speech-synthesis-picks-a-voice-per-sentence-and-warns-once.md +24 -0
- package/knowledge/decisions/internals/the-actor-prompt-says-json-because-of-json-object-mode.md +22 -0
- package/knowledge/decisions/internals/the-agent-done-event-goes-through-the-middleware-and-is-awaited.md +26 -0
- package/knowledge/decisions/internals/the-api-report-pins-members-not-just-names.md +43 -0
- package/knowledge/decisions/internals/the-ecosystem-entry-point-carries-the-adapter-surface.md +58 -0
- package/knowledge/decisions/internals/the-middleware-resolution-cache-is-deliberately-unbounded.md +40 -0
- package/knowledge/decisions/internals/the-per-invocation-rpc-view-is-a-class.md +40 -0
- package/knowledge/decisions/internals/the-persona-runtime-is-exported-from-the-persona-entry-point.md +28 -0
- package/knowledge/decisions/internals/the-transcript-event-is-sent-ahead-of-the-run.md +25 -0
- package/knowledge/decisions/internals/the-virtual-user-catalogue-is-the-only-gate-on-what-may-be-called.md +21 -0
- package/knowledge/decisions/internals/the-worker-disposition-is-the-one-that-is-not-testing.md +22 -0
- package/knowledge/decisions/internals/thread-history-records-the-transcript-not-the-audio.md +25 -0
- package/knowledge/decisions/internals/virtual-user-step-order-comes-from-insertion-order.md +21 -0
- package/knowledge/decisions/internals/voice-output-speaks-unless-voice-input-explicitly-says-otherwise.md +24 -0
- package/knowledge/decisions/internals/wiring-registries-erase-the-generics-their-wire-functions-capture.md +35 -0
- package/knowledge/decisions/security/a-dropped-audit-write-is-always-logged.md +4 -2
- package/knowledge/decisions/security/a-graph-run-starts-at-an-entry-node-the-graph-declared.md +33 -0
- package/knowledge/decisions/security/a-permission-gets-a-wire-it-cannot-reply-on.md +33 -0
- package/knowledge/decisions/security/a-step-runs-the-function-the-workflow-dispatched-it-with.md +37 -0
- package/knowledge/decisions/security/a-virtual-user-is-never-offered-a-step-that-would-forge-its-own-oracle.md +28 -0
- package/knowledge/decisions/security/a-workflow-run-is-read-and-approved-by-its-owner.md +34 -0
- package/knowledge/decisions/security/an-agent-approval-is-claimed-before-the-tool-runs.md +33 -0
- package/knowledge/decisions/security/an-upload-is-counted-as-it-arrives-not-buffered-then-measured.md +24 -0
- package/knowledge/decisions/security/index.md +7 -0
- package/knowledge/questions/channel-middleware-accepts-bare-factories-that-nothing-resolves.md +35 -0
- package/knowledge/questions/index.md +2 -1
- package/knowledge/questions/unauthorized-channel-replies-escape-the-declared-out-type.md +44 -0
- package/package.json +13 -3
- package/scripts/generate-api-report.d.mts +1 -0
- package/scripts/generate-api-report.mjs +89 -0
- package/scripts/generate-api-report.mts +218 -0
- package/src/api-report.test.ts +69 -0
- package/src/column-form.ts +1 -4
- package/src/crypto-utils.test.ts +15 -2
- package/src/dev/hot-reload.ts +2 -7
- package/src/ecosystem.ts +40 -0
- package/src/errors/error-handler.ts +5 -3
- package/src/errors/error.test.ts +4 -1
- package/src/function/function-runner.test.ts +1 -1
- package/src/function/function-runner.ts +28 -39
- package/src/function/functions.types.ts +15 -9
- package/src/handle-error.ts +1 -1
- package/src/index.ts +3 -22
- package/src/middleware/auth-cookie.test.ts +50 -0
- package/src/middleware/auth-cookie.ts +38 -2
- package/src/middleware/cors.ts +2 -1
- package/src/middleware/index.ts +1 -1
- package/src/middleware/remote-auth.test.ts +43 -0
- package/src/middleware/remote-auth.ts +17 -3
- package/src/middleware-runner.ts +4 -2
- package/src/no-any-casts.test.ts +57 -0
- package/src/permissions.test.ts +3 -1
- package/src/permissions.ts +22 -24
- package/src/pikku-state.ts +17 -6
- package/src/public-surface.json +578 -0
- package/src/public-surface.json.README +25 -0
- package/src/public-surface.test.ts +105 -0
- package/src/removed-legacy-exports.test.ts +62 -0
- package/src/schema.test.ts +78 -0
- package/src/schema.ts +36 -1
- package/src/services/ai-run-state-service.ts +7 -1
- package/src/services/audit-service.ts +2 -2
- package/src/services/in-memory-ai-run-state-service.ts +3 -2
- package/src/services/in-memory-workflow-service.ts +2 -0
- package/src/services/index.ts +1 -4
- package/src/services/local-content-request-handler.test.ts +43 -5
- package/src/services/local-content-request-handler.ts +103 -74
- package/src/services/local-content.ts +15 -1
- package/src/services/local-email-service.ts +5 -1
- package/src/services/system-role-guard.test.ts +4 -1
- package/src/services/workflow-service.ts +9 -2
- package/src/side-effects-are-declared.test.ts +84 -0
- package/src/source-files-stay-composable.test.ts +41 -0
- package/src/testing/service-tests/agent-run-service-tests.ts +98 -0
- package/src/testing/service-tests/ai-storage-service-tests.ts +286 -0
- package/src/testing/service-tests/channel-store-tests.ts +98 -0
- package/src/testing/service-tests/credential-service-tests.ts +143 -0
- package/src/testing/service-tests/deployment-service-tests.ts +33 -0
- package/src/testing/service-tests/event-hub-store-tests.ts +48 -0
- package/src/testing/service-tests/secret-service-tests.ts +105 -0
- package/src/testing/service-tests/session-store-tests.ts +62 -0
- package/src/testing/service-tests/workflow-run-service-tests.ts +59 -0
- package/src/testing/service-tests/workflow-service-tests.ts +308 -0
- package/src/testing/service-tests.ts +31 -1111
- package/src/types/core.types.ts +15 -1
- package/src/types/state.types.ts +1 -1
- package/src/wirings/actor-flow/run-conversation.ts +1 -6
- package/src/wirings/ai-agent/agent-rpc.ts +120 -0
- package/src/wirings/ai-agent/ai-agent-agui.ts +1 -5
- package/src/wirings/ai-agent/ai-agent-interrupt.test.ts +2 -1
- package/src/wirings/ai-agent/ai-agent-memory.ts +8 -1
- package/src/wirings/ai-agent/ai-agent-prepare.ts +10 -12
- package/src/wirings/ai-agent/ai-agent-resume-authorization.test.ts +1 -0
- package/src/wirings/ai-agent/ai-agent-runner.test.ts +82 -2
- package/src/wirings/ai-agent/ai-agent-runner.ts +49 -120
- package/src/wirings/ai-agent/ai-agent-stream.test.ts +2 -0
- package/src/wirings/ai-agent/ai-agent-stream.ts +36 -63
- package/src/wirings/ai-agent/ai-agent-thread-ownership.test.ts +1 -1
- package/src/wirings/ai-agent/ai-agent-turn.ts +121 -0
- package/src/wirings/ai-agent/ai-agent.types.ts +1 -1
- package/src/wirings/ai-agent/voice-input.ts +1 -6
- package/src/wirings/ai-agent/voice-output.ts +2 -12
- package/src/wirings/channel/channel-common.ts +5 -2
- package/src/wirings/channel/channel-handler-shapes.test.ts +72 -0
- package/src/wirings/channel/channel-handler.ts +7 -7
- package/src/wirings/channel/channel-rpc-service.ts +0 -14
- package/src/wirings/channel/channel-rpc.test.ts +25 -4
- package/src/wirings/channel/channel-rpc.types.ts +14 -0
- package/src/wirings/channel/channel-runner.ts +33 -20
- package/src/wirings/channel/channel.types.ts +2 -0
- package/src/wirings/channel/local/local-channel-runner.ts +1 -1
- package/src/wirings/channel/pikku-abstract-channel-handler.ts +2 -1
- package/src/wirings/channel/serverless/serverless-channel-runner.ts +6 -3
- package/src/wirings/cli/channel/cli-channel-runner.ts +3 -1
- package/src/wirings/cli/channel/cli-raw-client-runner.ts +4 -1
- package/src/wirings/cli/cli-runner.ts +7 -4
- package/src/wirings/cli/cli.types.ts +0 -39
- package/src/wirings/cli/command-parser.test.ts +19 -0
- package/src/wirings/cli/command-parser.ts +17 -1
- package/src/wirings/gateway/gateway-channel-meta.test.ts +44 -0
- package/src/wirings/gateway/gateway-runner.ts +27 -19
- package/src/wirings/http/http-routes.ts +4 -1
- package/src/wirings/http/http-runner.ts +11 -26
- package/src/wirings/http/http.types.ts +0 -15
- package/src/wirings/http/index.ts +1 -7
- package/src/wirings/http/pikku-fetch-http-request.ts +2 -2
- package/src/wirings/http/web-request.ts +1 -1
- package/src/wirings/mcp/mcp-runner.ts +2 -16
- package/src/wirings/persona/persona-environments.test.ts +14 -3
- package/src/wirings/persona/persona.test.ts +13 -3
- package/src/wirings/persona/validate-personas.ts +5 -1
- package/src/wirings/queue/queue-runner.ts +1 -1
- package/src/wirings/rpc/addon-auth-tags.test.ts +1 -5
- package/src/wirings/rpc/rpc-runner.ts +58 -136
- package/src/wirings/rpc/rpc-types.ts +7 -0
- package/src/wirings/rpc/wire-addon.ts +3 -1
- package/src/wirings/secret/validate-secret-definitions.test.ts +22 -0
- package/src/wirings/secret/validate-secret-definitions.ts +2 -2
- package/src/wirings/trigger/pikku-trigger-service.ts +0 -5
- package/src/wirings/trigger/trigger-runner.ts +7 -5
- package/src/wirings/virtual-user/run-virtual-user.test.ts +18 -9
- package/src/wirings/virtual-user/run-virtual-user.ts +28 -15
- package/src/wirings/virtual-user/virtual-user-agents.test.ts +4 -1
- package/src/wirings/virtual-user/virtual-user-derive.ts +4 -25
- package/src/wirings/virtual-user/virtual-user-dispositions.test.ts +7 -2
- package/src/wirings/virtual-user/virtual-user-dispositions.ts +1 -4
- package/src/wirings/virtual-user/virtual-user-intents.test.ts +13 -3
- package/src/wirings/workflow/dsl/workflow-dsl.types.ts +35 -2
- package/src/wirings/workflow/feature.ts +7 -4
- package/src/wirings/workflow/graph/graph-node.ts +1 -1
- package/src/wirings/workflow/graph/graph-runner.test.ts +4 -2
- package/src/wirings/workflow/graph/graph-runner.ts +13 -16
- package/src/wirings/workflow/graph/workflow-graph.types.ts +0 -5
- package/src/wirings/workflow/index.ts +12 -4
- package/src/wirings/workflow/pikku-scenario-service.ts +54 -25
- package/src/wirings/workflow/pikku-workflow-service.test.ts +2 -2
- package/src/wirings/workflow/pikku-workflow-service.ts +190 -743
- package/src/wirings/workflow/scenario-hooks.test.ts +51 -0
- package/src/wirings/workflow/workflow-approval.ts +185 -0
- package/src/wirings/workflow/workflow-child-run-session.test.ts +89 -0
- package/src/wirings/workflow/workflow-constants.ts +47 -0
- package/src/wirings/workflow/workflow-dispatch-durability.test.ts +1 -1
- package/src/wirings/workflow/workflow-errors.ts +128 -0
- package/src/wirings/workflow/workflow-inline-authority.test.ts +12 -6
- package/src/wirings/workflow/workflow-meta-resolver.ts +45 -0
- package/src/wirings/workflow/workflow-missing-meta.test.ts +92 -0
- package/src/wirings/workflow/workflow-queue-routing.ts +85 -0
- package/src/wirings/workflow/workflow-queue-wiring.ts +149 -0
- package/src/wirings/workflow/workflow-recovery.ts +162 -0
- package/src/wirings/workflow/workflow-retry-policy.test.ts +1 -1
- package/src/wirings/workflow/workflow-run-authority.test.ts +215 -0
- package/src/wirings/workflow/workflow-run-engine.types.ts +91 -0
- package/src/wirings/workflow/workflow-run-ownership.ts +36 -0
- package/src/wirings/workflow/workflow-suspend.ts +61 -0
- package/src/wirings/workflow/workflow.types.ts +7 -0
- package/src/wirings-stay-decoupled.test.ts +123 -0
- package/tsconfig.json +1 -1
- package/tsconfig.tsbuildinfo +1 -1
- package/src/internal.ts +0 -10
package/src/types/core.types.ts
CHANGED
|
@@ -298,6 +298,11 @@ export interface CoreUserSession {
|
|
|
298
298
|
* Populated by whoever builds the session — core reads them, never fetches.
|
|
299
299
|
*/
|
|
300
300
|
scopes?: string[]
|
|
301
|
+
/**
|
|
302
|
+
* Restricts the session to functions declared `readonly`. The function runner
|
|
303
|
+
* throws `ReadonlySessionError` for anything else.
|
|
304
|
+
*/
|
|
305
|
+
readonly?: boolean
|
|
301
306
|
}
|
|
302
307
|
|
|
303
308
|
/**
|
|
@@ -378,7 +383,11 @@ export type PikkuWire<
|
|
|
378
383
|
MCPTools extends string | never = never,
|
|
379
384
|
TypedWorkflow extends PikkuWorkflowWire | never = PikkuWorkflowWire,
|
|
380
385
|
TriggerOutput = unknown,
|
|
381
|
-
|
|
386
|
+
// Defaulted to `any` rather than to `Out`: the emitted `TypedScenario<Out>`
|
|
387
|
+
// supplies the real context, while `PikkuWire`'s own defaults are what other
|
|
388
|
+
// generics constrain against — and a constraint that pinned the context to
|
|
389
|
+
// `Out` would reject every wire whose scenario output is anything else.
|
|
390
|
+
TypedScenario extends PikkuScenarioWire<any> | never = PikkuScenarioWire<any>,
|
|
382
391
|
TypedActors extends ScenarioPersonas = ScenarioPersonas,
|
|
383
392
|
> = {
|
|
384
393
|
/** Always present — lazily initialised on first access for every function invocation */
|
|
@@ -386,6 +395,11 @@ export type PikkuWire<
|
|
|
386
395
|
} & Partial<{
|
|
387
396
|
wireType: PikkuWiringTypes
|
|
388
397
|
wireId: string
|
|
398
|
+
/**
|
|
399
|
+
* A logger scoped to this invocation, when a host attaches one. Core never
|
|
400
|
+
* sets it; services that log fall back to the singleton logger.
|
|
401
|
+
*/
|
|
402
|
+
logger: Logger
|
|
389
403
|
/** Trace ID for distributed tracing — propagated across remote RPC calls via x-trace-id header */
|
|
390
404
|
traceId: string
|
|
391
405
|
functionId: string
|
package/src/types/state.types.ts
CHANGED
|
@@ -119,12 +119,7 @@ function actorInstructions(persona: ResolvedPersona, task: string): string {
|
|
|
119
119
|
: '',
|
|
120
120
|
`Your goal in this conversation: ${task}.`,
|
|
121
121
|
`Send one message at a time. Set "done" to true only once your goal is clearly accomplished, or clearly impossible.`,
|
|
122
|
-
//
|
|
123
|
-
// that cannot take a JSON *schema* degrades to OpenAI's `json_object`
|
|
124
|
-
// mode — which refuses the request outright unless the word "json" appears
|
|
125
|
-
// somewhere in the prompt. Saying it once here covers all three call sites
|
|
126
|
-
// (turn, approvals, verdict), since each builds on these instructions, and
|
|
127
|
-
// it costs nothing on providers that never needed telling.
|
|
122
|
+
// knowledge: decisions/internals/the-actor-prompt-says-json-because-of-json-object-mode.md
|
|
128
123
|
`Reply with json matching the schema you are given, and nothing else.`,
|
|
129
124
|
]
|
|
130
125
|
.filter(Boolean)
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import type { PikkuRawWire } from '../../types/core.types.js'
|
|
2
|
+
import type { SessionService } from '../../services/user-session-service.js'
|
|
3
|
+
import type { CoreUserSession } from '../../types/core.types.js'
|
|
4
|
+
import type { PikkuRPC } from '../rpc/rpc-types.js'
|
|
5
|
+
import type { AIAgentInput, AIStreamChannel } from './ai-agent.types.js'
|
|
6
|
+
import type { StreamAIAgentOptions } from './ai-agent-prepare.js'
|
|
7
|
+
import { runAIAgent, resumeAIAgentSync } from './ai-agent-runner.js'
|
|
8
|
+
import {
|
|
9
|
+
streamAIAgent,
|
|
10
|
+
resumeAIAgent,
|
|
11
|
+
interruptAIAgent,
|
|
12
|
+
} from './ai-agent-stream.js'
|
|
13
|
+
import { wrapChannelWithAGUI } from './ai-agent-agui.js'
|
|
14
|
+
|
|
15
|
+
export type AgentRPCOptions = {
|
|
16
|
+
sessionService?: SessionService<CoreUserSession>
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* `wire.rpc.agent`, implemented.
|
|
21
|
+
*
|
|
22
|
+
* Lives here rather than in `rpc-runner` so the agent surface is one file next
|
|
23
|
+
* to the runner and stream code it delegates to, instead of a wing of the RPC
|
|
24
|
+
* primitive.
|
|
25
|
+
*/
|
|
26
|
+
export const createAgentRPC = (
|
|
27
|
+
wire: PikkuRawWire,
|
|
28
|
+
options: AgentRPCOptions
|
|
29
|
+
): PikkuRPC['agent'] => {
|
|
30
|
+
const params = () => ({
|
|
31
|
+
sessionService: options.sessionService,
|
|
32
|
+
getCredential: wire.getCredential?.bind(wire),
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
const streamingChannel = (): AIStreamChannel => {
|
|
36
|
+
const channel = wire.channel as unknown as AIStreamChannel | undefined
|
|
37
|
+
if (!channel) throw new Error('No channel available for streaming')
|
|
38
|
+
return channel
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** `run` and `approve` return the same shape; only the call differs. */
|
|
42
|
+
const asRunResult = (result: Awaited<ReturnType<typeof runAIAgent>>) => ({
|
|
43
|
+
runId: result.runId,
|
|
44
|
+
result: result.object ?? result.text,
|
|
45
|
+
usage: result.usage,
|
|
46
|
+
...(result.status === 'suspended' && {
|
|
47
|
+
status: 'suspended' as const,
|
|
48
|
+
pendingApprovals: result.pendingApprovals,
|
|
49
|
+
}),
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
run: async (agentName: string, input: AIAgentInput) =>
|
|
54
|
+
asRunResult(await runAIAgent(agentName, input, params())),
|
|
55
|
+
|
|
56
|
+
stream: async (
|
|
57
|
+
agentName: string,
|
|
58
|
+
input: {
|
|
59
|
+
message: string
|
|
60
|
+
threadId: string
|
|
61
|
+
resourceId: string
|
|
62
|
+
model?: string
|
|
63
|
+
temperature?: number
|
|
64
|
+
},
|
|
65
|
+
streamOptions?: StreamAIAgentOptions
|
|
66
|
+
) => {
|
|
67
|
+
let currentRunId: string | undefined
|
|
68
|
+
await streamAIAgent(
|
|
69
|
+
agentName,
|
|
70
|
+
input,
|
|
71
|
+
wrapChannelWithAGUI(streamingChannel(), {
|
|
72
|
+
threadId: input.threadId,
|
|
73
|
+
getRunId: () => currentRunId,
|
|
74
|
+
}),
|
|
75
|
+
params(),
|
|
76
|
+
undefined,
|
|
77
|
+
{
|
|
78
|
+
...streamOptions,
|
|
79
|
+
onRunCreated: (runId) => {
|
|
80
|
+
currentRunId = runId
|
|
81
|
+
streamOptions?.onRunCreated?.(runId)
|
|
82
|
+
},
|
|
83
|
+
}
|
|
84
|
+
)
|
|
85
|
+
},
|
|
86
|
+
|
|
87
|
+
resume: async (
|
|
88
|
+
runId: string,
|
|
89
|
+
input: { toolCallId: string; approved: boolean },
|
|
90
|
+
streamOptions?: StreamAIAgentOptions
|
|
91
|
+
) => {
|
|
92
|
+
await resumeAIAgent(
|
|
93
|
+
{ runId, ...input },
|
|
94
|
+
wrapChannelWithAGUI(streamingChannel(), { runId }),
|
|
95
|
+
params(),
|
|
96
|
+
streamOptions
|
|
97
|
+
)
|
|
98
|
+
},
|
|
99
|
+
|
|
100
|
+
interrupt: async (runId: string, reason?: 'speech' | 'user' | 'timeout') =>
|
|
101
|
+
interruptAIAgent(
|
|
102
|
+
{ runId, ...(reason ? { reason } : {}) },
|
|
103
|
+
{ sessionService: options.sessionService }
|
|
104
|
+
),
|
|
105
|
+
|
|
106
|
+
approve: async (
|
|
107
|
+
runId: string,
|
|
108
|
+
approvals: { toolCallId: string; approved: boolean }[],
|
|
109
|
+
expectedAgentName?: string
|
|
110
|
+
) =>
|
|
111
|
+
asRunResult(
|
|
112
|
+
await resumeAIAgentSync(
|
|
113
|
+
runId,
|
|
114
|
+
approvals,
|
|
115
|
+
{ sessionService: options.sessionService },
|
|
116
|
+
expectedAgentName
|
|
117
|
+
)
|
|
118
|
+
),
|
|
119
|
+
}
|
|
120
|
+
}
|
|
@@ -372,11 +372,7 @@ export function wrapChannelWithAGUI(
|
|
|
372
372
|
break
|
|
373
373
|
}
|
|
374
374
|
|
|
375
|
-
//
|
|
376
|
-
// pikku-specific ones. Dropping it instead — which is what this did —
|
|
377
|
-
// means a voice agent reached over HTTP is inaudible: `voiceOutput`
|
|
378
|
-
// synthesizes every sentence, the provider bills for it, and nothing
|
|
379
|
-
// gets past the mapper.
|
|
375
|
+
// knowledge: decisions/internals/agent-speech-travels-as-a-custom-agui-event.md
|
|
380
376
|
case 'audio-delta': {
|
|
381
377
|
send({
|
|
382
378
|
type: 'CUSTOM',
|
|
@@ -574,7 +574,7 @@ describe('non-streaming agent interruption', () => {
|
|
|
574
574
|
updateRun: async (_runId: string, patch: unknown) => {
|
|
575
575
|
updates.push(patch)
|
|
576
576
|
},
|
|
577
|
-
resolveApproval: async () =>
|
|
577
|
+
resolveApproval: async () => true,
|
|
578
578
|
},
|
|
579
579
|
} as any)
|
|
580
580
|
|
|
@@ -648,6 +648,7 @@ describe('streaming resume interruption', () => {
|
|
|
648
648
|
// for approvals that have already been given.
|
|
649
649
|
resolveApproval: async () => {
|
|
650
650
|
run.pendingApprovals = []
|
|
651
|
+
return true
|
|
651
652
|
},
|
|
652
653
|
},
|
|
653
654
|
} as any)
|
|
@@ -19,7 +19,14 @@ export function resolveMemoryServices(
|
|
|
19
19
|
} {
|
|
20
20
|
const memoryConfig = agent.memory
|
|
21
21
|
const storage = memoryConfig?.storage
|
|
22
|
-
?
|
|
22
|
+
? // The service is named by string in agent config, so the lookup cannot be
|
|
23
|
+
// checked — only the shape it is required to have.
|
|
24
|
+
(
|
|
25
|
+
singletonServices as unknown as Record<
|
|
26
|
+
string,
|
|
27
|
+
AIStorageService | undefined
|
|
28
|
+
>
|
|
29
|
+
)[memoryConfig.storage]
|
|
23
30
|
: singletonServices.aiStorage
|
|
24
31
|
return { storage }
|
|
25
32
|
}
|
|
@@ -12,7 +12,11 @@ import type {
|
|
|
12
12
|
} from './ai-agent.types.js'
|
|
13
13
|
import type { AIAgentRunnerParams } from '../../services/ai-agent-runner-service.js'
|
|
14
14
|
import { PikkuError } from '../../errors/error-handler.js'
|
|
15
|
-
import {
|
|
15
|
+
import {
|
|
16
|
+
checkAuthPermissions,
|
|
17
|
+
runPermissions,
|
|
18
|
+
type PermissionWire,
|
|
19
|
+
} from '../../permissions.js'
|
|
16
20
|
import { AIProviderNotConfiguredError } from '../../errors/errors.js'
|
|
17
21
|
import { ForbiddenError } from '../../errors/errors.js'
|
|
18
22
|
import { verifyScopes } from '../../scopes.js'
|
|
@@ -319,8 +323,8 @@ export async function assertAgentAuthorized(
|
|
|
319
323
|
|
|
320
324
|
await runPermissions({
|
|
321
325
|
funcPermissions: agent.permissions,
|
|
322
|
-
services: singletonServices
|
|
323
|
-
wire: wire as
|
|
326
|
+
services: singletonServices,
|
|
327
|
+
wire: wire as PermissionWire,
|
|
324
328
|
data: {},
|
|
325
329
|
packageName,
|
|
326
330
|
label: 'agent',
|
|
@@ -395,7 +399,7 @@ export function createScopedChannel(
|
|
|
395
399
|
toolName: event.toolName,
|
|
396
400
|
args: event.args,
|
|
397
401
|
...(event.reason !== undefined ? { reason: event.reason } : {}),
|
|
398
|
-
runId:
|
|
402
|
+
runId: event.runId,
|
|
399
403
|
})
|
|
400
404
|
return
|
|
401
405
|
}
|
|
@@ -940,10 +944,7 @@ export async function prepareAgentRun(
|
|
|
940
944
|
|
|
941
945
|
let messages: AIMessage[] = []
|
|
942
946
|
if (storage) {
|
|
943
|
-
//
|
|
944
|
-
// thread. In voice the next turn lands within a second or two, so without
|
|
945
|
-
// this the model would load context that is missing the very thing it is
|
|
946
|
-
// about to be asked about.
|
|
947
|
+
// knowledge: decisions/internals/agent-context-waits-for-a-tool-result-still-being-written.md
|
|
947
948
|
await awaitPendingInterruptNote(threadId)
|
|
948
949
|
messages = await storage.getMessages(threadId, {
|
|
949
950
|
lastN: memoryConfig?.lastMessages ?? 20,
|
|
@@ -959,10 +960,7 @@ export async function prepareAgentRun(
|
|
|
959
960
|
|
|
960
961
|
const userContent: AIMessage['content'] = input.attachments?.length
|
|
961
962
|
? [
|
|
962
|
-
//
|
|
963
|
-
// real turn — a spoken one carries audio and no text at all — and an
|
|
964
|
-
// empty text part alongside it is a part providers are entitled to
|
|
965
|
-
// reject, for a caller who never wrote one.
|
|
963
|
+
// knowledge: decisions/internals/an-empty-text-part-is-omitted-from-an-agent-message.md
|
|
966
964
|
...(input.message
|
|
967
965
|
? [{ type: 'text' as const, text: input.message }]
|
|
968
966
|
: []),
|
|
@@ -707,7 +707,7 @@ describe('resumeAIAgentSync', () => {
|
|
|
707
707
|
createdAt: new Date(),
|
|
708
708
|
updatedAt: new Date(),
|
|
709
709
|
}),
|
|
710
|
-
resolveApproval: async () =>
|
|
710
|
+
resolveApproval: async () => true,
|
|
711
711
|
updateRun: async () => {},
|
|
712
712
|
},
|
|
713
713
|
} as any)
|
|
@@ -784,6 +784,7 @@ describe('resumeAIAgentSync', () => {
|
|
|
784
784
|
},
|
|
785
785
|
resolveApproval: async (toolCallId: string, status: string) => {
|
|
786
786
|
resolveCalls.push({ toolCallId, status })
|
|
787
|
+
return true
|
|
787
788
|
},
|
|
788
789
|
},
|
|
789
790
|
aiStorage: {
|
|
@@ -816,6 +817,84 @@ describe('resumeAIAgentSync', () => {
|
|
|
816
817
|
])
|
|
817
818
|
})
|
|
818
819
|
|
|
820
|
+
test('an approver that loses the claim never runs the tool', async () => {
|
|
821
|
+
addTestAgent('claim-agent')
|
|
822
|
+
pikkuState(null, 'agent', 'agentsMeta')['claim-agent'].tools = ['refund']
|
|
823
|
+
pikkuState(null, 'rpc', 'meta').refund = 'refund'
|
|
824
|
+
pikkuState(null, 'function', 'meta').refund = {
|
|
825
|
+
description: 'Refund',
|
|
826
|
+
sessionless: true,
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
let executions = 0
|
|
830
|
+
pikkuState(null, 'function', 'functions').set('refund', {
|
|
831
|
+
func: async () => {
|
|
832
|
+
executions++
|
|
833
|
+
return { refunded: true }
|
|
834
|
+
},
|
|
835
|
+
})
|
|
836
|
+
|
|
837
|
+
const run: AgentRunState = {
|
|
838
|
+
runId: 'run-claim',
|
|
839
|
+
agentName: 'claim-agent',
|
|
840
|
+
threadId: 'thread-claim',
|
|
841
|
+
resourceId: 'resource-claim',
|
|
842
|
+
status: 'suspended',
|
|
843
|
+
suspendReason: 'approval',
|
|
844
|
+
pendingApprovals: [
|
|
845
|
+
{
|
|
846
|
+
type: 'tool-call',
|
|
847
|
+
toolCallId: 'tc-claim',
|
|
848
|
+
toolName: 'refund',
|
|
849
|
+
args: {},
|
|
850
|
+
},
|
|
851
|
+
],
|
|
852
|
+
usage: { inputTokens: 0, outputTokens: 0, model: 'test/test-model' },
|
|
853
|
+
createdAt: new Date(),
|
|
854
|
+
updatedAt: new Date(),
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
// Every concurrent approver reads the same `suspended` run with the same
|
|
858
|
+
// pending list; the store hands the claim to exactly one of them.
|
|
859
|
+
let claimsLeft = 1
|
|
860
|
+
pikkuState(null, 'package', 'singletonServices', {
|
|
861
|
+
logger: {
|
|
862
|
+
info: () => {},
|
|
863
|
+
warn: () => {},
|
|
864
|
+
error: () => {},
|
|
865
|
+
debug: () => {},
|
|
866
|
+
},
|
|
867
|
+
aiAgentRunner: {
|
|
868
|
+
run: async (): Promise<AIAgentStepResult> =>
|
|
869
|
+
makeStepResult({
|
|
870
|
+
text: 'Refunded',
|
|
871
|
+
usage: { inputTokens: 1, outputTokens: 1 },
|
|
872
|
+
}),
|
|
873
|
+
},
|
|
874
|
+
aiRunState: {
|
|
875
|
+
getRun: async () => run,
|
|
876
|
+
createRun: async () => 'unused',
|
|
877
|
+
updateRun: async () => {},
|
|
878
|
+
resolveApproval: async () => claimsLeft-- > 0,
|
|
879
|
+
},
|
|
880
|
+
aiStorage: {
|
|
881
|
+
saveMessages: async () => {},
|
|
882
|
+
getMessages: async () => [],
|
|
883
|
+
},
|
|
884
|
+
} as any)
|
|
885
|
+
|
|
886
|
+
const approvals = [{ toolCallId: 'tc-claim', approved: true }]
|
|
887
|
+
await resumeAIAgentSync('run-claim', approvals, asOwner('resource-claim'))
|
|
888
|
+
assert.equal(executions, 1, 'the winner ran the tool once')
|
|
889
|
+
|
|
890
|
+
await assert.rejects(
|
|
891
|
+
() =>
|
|
892
|
+
resumeAIAgentSync('run-claim', approvals, asOwner('resource-claim')),
|
|
893
|
+
/already resolved by another caller/
|
|
894
|
+
)
|
|
895
|
+
assert.equal(executions, 1, 'the loser did not run it a second time')
|
|
896
|
+
})
|
|
897
|
+
|
|
819
898
|
test('records denied approvals and rejects agent mismatches', async () => {
|
|
820
899
|
addTestAgent('resume-agent')
|
|
821
900
|
|
|
@@ -861,6 +940,7 @@ describe('resumeAIAgentSync', () => {
|
|
|
861
940
|
updateRun: async () => {},
|
|
862
941
|
resolveApproval: async (toolCallId: string, status: string) => {
|
|
863
942
|
resolveCalls.push({ toolCallId, status })
|
|
943
|
+
return true
|
|
864
944
|
},
|
|
865
945
|
},
|
|
866
946
|
aiStorage: {
|
|
@@ -959,7 +1039,7 @@ describe('resumeAIAgentSync', () => {
|
|
|
959
1039
|
},
|
|
960
1040
|
aiRunState: {
|
|
961
1041
|
getRun: async () => run,
|
|
962
|
-
resolveApproval: async () =>
|
|
1042
|
+
resolveApproval: async () => true,
|
|
963
1043
|
updateRun: async () => {},
|
|
964
1044
|
},
|
|
965
1045
|
aiStorage: {
|
|
@@ -43,6 +43,12 @@ import {
|
|
|
43
43
|
trackToolExecution,
|
|
44
44
|
} from './ai-agent-interrupt.js'
|
|
45
45
|
import { pikkuState, getSingletonServices } from '../../pikku-state.js'
|
|
46
|
+
import {
|
|
47
|
+
applyInputMiddleware,
|
|
48
|
+
describeApprovals,
|
|
49
|
+
notifyAfterStep,
|
|
50
|
+
toAccumulatedStep,
|
|
51
|
+
} from './ai-agent-turn.js'
|
|
46
52
|
import { resolveModelConfig } from './ai-agent-model-config.js'
|
|
47
53
|
import { AIProviderNotConfiguredError } from '../../errors/errors.js'
|
|
48
54
|
import { randomUUID } from './ai-agent-utils.js'
|
|
@@ -147,26 +153,20 @@ export async function runAIAgent(
|
|
|
147
153
|
// One bag per run, shared by every middleware — see PikkuAIMiddlewareHooks.
|
|
148
154
|
const sharedNotes: Record<string, unknown> = {}
|
|
149
155
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
messages:
|
|
156
|
-
instructions:
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
modifiedInstructions = result.instructions
|
|
161
|
-
}
|
|
162
|
-
}
|
|
156
|
+
const { messages: modifiedMessages, instructions: modifiedInstructions } =
|
|
157
|
+
await applyInputMiddleware(
|
|
158
|
+
aiMiddlewares,
|
|
159
|
+
singletonServices,
|
|
160
|
+
{
|
|
161
|
+
messages: runnerParams.messages,
|
|
162
|
+
instructions: runnerParams.instructions,
|
|
163
|
+
},
|
|
164
|
+
sharedNotes
|
|
165
|
+
)
|
|
163
166
|
runnerParams.messages = modifiedMessages
|
|
164
167
|
runnerParams.instructions = modifiedInstructions
|
|
165
168
|
|
|
166
|
-
//
|
|
167
|
-
// transcript rather than the base64 audio that arrived — see the same note on
|
|
168
|
-
// the streaming path. Identity-checked, because a middleware may legitimately
|
|
169
|
-
// replace the message list with something unrelated to this turn.
|
|
169
|
+
// knowledge: decisions/internals/thread-history-records-the-transcript-not-the-audio.md
|
|
170
170
|
const lastModified = modifiedMessages[modifiedMessages.length - 1]
|
|
171
171
|
const persistedUserMessage =
|
|
172
172
|
lastModified?.id === userMessage.id ? lastModified : userMessage
|
|
@@ -181,10 +181,7 @@ export async function runAIAgent(
|
|
|
181
181
|
updatedAt: new Date(),
|
|
182
182
|
})
|
|
183
183
|
|
|
184
|
-
//
|
|
185
|
-
// visible to `interruptAIAgent` through `aiRunState` either way, so skipping
|
|
186
|
-
// this would leave that call finding the run, passing the ownership check and
|
|
187
|
-
// then failing to stop it — reported as if it were running on another host.
|
|
184
|
+
// knowledge: decisions/internals/a-non-streaming-agent-run-registers-with-airunstate-too.md
|
|
188
185
|
const interruptHandle = registerInterruptibleRun(runId)
|
|
189
186
|
runnerParams.abortSignal = interruptHandle.signal
|
|
190
187
|
runnerParams.tools = trackToolExecution(runnerParams.tools, interruptHandle)
|
|
@@ -216,52 +213,15 @@ export async function runAIAgent(
|
|
|
216
213
|
totalUsage.inputTokens += stepResult.usage.inputTokens
|
|
217
214
|
totalUsage.outputTokens += stepResult.usage.outputTokens
|
|
218
215
|
|
|
219
|
-
|
|
220
|
-
if (mw.afterStep) {
|
|
221
|
-
await mw.afterStep(singletonServices, {
|
|
222
|
-
stepNumber: step,
|
|
223
|
-
text: stepResult.text,
|
|
224
|
-
toolCalls: stepResult.toolCalls,
|
|
225
|
-
toolResults: stepResult.toolResults,
|
|
226
|
-
usage: stepResult.usage,
|
|
227
|
-
finishReason: stepResult.finishReason,
|
|
228
|
-
})
|
|
229
|
-
}
|
|
230
|
-
}
|
|
216
|
+
await notifyAfterStep(aiMiddlewares, singletonServices, step, stepResult)
|
|
231
217
|
|
|
232
|
-
accumulatedSteps.push(
|
|
233
|
-
usage: stepResult.usage,
|
|
234
|
-
toolCalls: stepResult.toolCalls.map((tc) => {
|
|
235
|
-
const tr = stepResult.toolResults.find(
|
|
236
|
-
(r) => r.toolCallId === tc.toolCallId
|
|
237
|
-
)
|
|
238
|
-
return {
|
|
239
|
-
name: tc.toolName,
|
|
240
|
-
args: tc.args as Record<string, unknown>,
|
|
241
|
-
result:
|
|
242
|
-
typeof tr?.result === 'string'
|
|
243
|
-
? tr.result
|
|
244
|
-
: JSON.stringify(tr?.result ?? ''),
|
|
245
|
-
}
|
|
246
|
-
}),
|
|
247
|
-
})
|
|
218
|
+
accumulatedSteps.push(toAccumulatedStep(stepResult))
|
|
248
219
|
|
|
249
220
|
if (stepResult.toolCalls.length === 0) break
|
|
250
221
|
|
|
251
222
|
const approvalsNeeded = checkForApprovals(stepResult, runnerParams.tools)
|
|
252
223
|
if (approvalsNeeded.length > 0) {
|
|
253
|
-
|
|
254
|
-
const toolDef = runnerParams.tools.find(
|
|
255
|
-
(t) => t.name === approval.toolName
|
|
256
|
-
)
|
|
257
|
-
if (toolDef?.approvalDescriptionFn && !approval.reason) {
|
|
258
|
-
try {
|
|
259
|
-
approval.reason = await toolDef.approvalDescriptionFn(
|
|
260
|
-
approval.args
|
|
261
|
-
)
|
|
262
|
-
} catch {}
|
|
263
|
-
}
|
|
264
|
-
}
|
|
224
|
+
await describeApprovals(approvalsNeeded, runnerParams.tools)
|
|
265
225
|
|
|
266
226
|
const pendingApprovals = approvalsNeeded.map((a) =>
|
|
267
227
|
a.agentRunId
|
|
@@ -385,10 +345,7 @@ export async function runAIAgent(
|
|
|
385
345
|
usage: totalUsage,
|
|
386
346
|
}
|
|
387
347
|
} catch (error) {
|
|
388
|
-
//
|
|
389
|
-
// becomes an `errorMessage`. Unlike the streaming path there is no partial
|
|
390
|
-
// reply to hand back — nothing was delivered — so the caller gets a typed
|
|
391
|
-
// throw it can tell apart from a provider outage instead of a result.
|
|
348
|
+
// knowledge: decisions/internals/an-agent-interrupt-is-not-a-failure.md
|
|
392
349
|
const interruption = interruptHandle.interruption
|
|
393
350
|
if (interruption || isAbortError(error)) {
|
|
394
351
|
await aiRunState.updateRun(runId, { status: 'interrupted' })
|
|
@@ -470,11 +427,24 @@ export async function resumeAIAgentSync(
|
|
|
470
427
|
|
|
471
428
|
const savedPendingApprovals = [...(run.pendingApprovals ?? [])]
|
|
472
429
|
|
|
430
|
+
// The read above is not a claim — concurrent resumes all see the same pending
|
|
431
|
+
// list. `resolveApproval` is the claim, and only the caller it returns true
|
|
432
|
+
// for may run the tool.
|
|
433
|
+
const claimedIds = new Set<string>()
|
|
473
434
|
for (const { toolCallId, approved } of approvals) {
|
|
474
|
-
await aiRunState.resolveApproval(
|
|
435
|
+
const claimed = await aiRunState.resolveApproval(
|
|
475
436
|
toolCallId,
|
|
476
437
|
approved ? 'approved' : 'denied'
|
|
477
438
|
)
|
|
439
|
+
if (claimed) {
|
|
440
|
+
claimedIds.add(toolCallId)
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
if (approvals.length > 0 && claimedIds.size === 0) {
|
|
445
|
+
throw new Error(
|
|
446
|
+
`Approvals for run ${runId} were already resolved by another caller`
|
|
447
|
+
)
|
|
478
448
|
}
|
|
479
449
|
|
|
480
450
|
const { tools } = await buildToolDefs(
|
|
@@ -498,6 +468,8 @@ export async function resumeAIAgentSync(
|
|
|
498
468
|
if (pending.type !== 'tool-call') continue
|
|
499
469
|
|
|
500
470
|
const toolCallId = pending.toolCallId
|
|
471
|
+
if (!claimedIds.has(toolCallId)) continue
|
|
472
|
+
|
|
501
473
|
let resultStr: string
|
|
502
474
|
|
|
503
475
|
if (rejectedIds.has(toolCallId)) {
|
|
@@ -623,19 +595,13 @@ async function continueAfterToolResultSync(
|
|
|
623
595
|
// One bag per run, shared by every middleware — see PikkuAIMiddlewareHooks.
|
|
624
596
|
const sharedNotes: Record<string, unknown> = {}
|
|
625
597
|
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
shared: sharedNotes,
|
|
634
|
-
})
|
|
635
|
-
modifiedMessages = result.messages
|
|
636
|
-
modifiedInstructions = result.instructions
|
|
637
|
-
}
|
|
638
|
-
}
|
|
598
|
+
const { messages: modifiedMessages, instructions: modifiedInstructions } =
|
|
599
|
+
await applyInputMiddleware(
|
|
600
|
+
aiMiddlewares,
|
|
601
|
+
singletonServices,
|
|
602
|
+
{ messages: trimmedMessages, instructions: instructions },
|
|
603
|
+
sharedNotes
|
|
604
|
+
)
|
|
639
605
|
|
|
640
606
|
const { tools: resumeTools } = await buildToolDefs(
|
|
641
607
|
params,
|
|
@@ -684,52 +650,15 @@ async function continueAfterToolResultSync(
|
|
|
684
650
|
totalUsage.inputTokens += stepResult.usage.inputTokens
|
|
685
651
|
totalUsage.outputTokens += stepResult.usage.outputTokens
|
|
686
652
|
|
|
687
|
-
|
|
688
|
-
if (mw.afterStep) {
|
|
689
|
-
await mw.afterStep(singletonServices, {
|
|
690
|
-
stepNumber: step,
|
|
691
|
-
text: stepResult.text,
|
|
692
|
-
toolCalls: stepResult.toolCalls,
|
|
693
|
-
toolResults: stepResult.toolResults,
|
|
694
|
-
usage: stepResult.usage,
|
|
695
|
-
finishReason: stepResult.finishReason,
|
|
696
|
-
})
|
|
697
|
-
}
|
|
698
|
-
}
|
|
653
|
+
await notifyAfterStep(aiMiddlewares, singletonServices, step, stepResult)
|
|
699
654
|
|
|
700
|
-
accumulatedSteps.push(
|
|
701
|
-
usage: stepResult.usage,
|
|
702
|
-
toolCalls: stepResult.toolCalls.map((tc) => {
|
|
703
|
-
const tr = stepResult.toolResults.find(
|
|
704
|
-
(r) => r.toolCallId === tc.toolCallId
|
|
705
|
-
)
|
|
706
|
-
return {
|
|
707
|
-
name: tc.toolName,
|
|
708
|
-
args: tc.args as Record<string, unknown>,
|
|
709
|
-
result:
|
|
710
|
-
typeof tr?.result === 'string'
|
|
711
|
-
? tr.result
|
|
712
|
-
: JSON.stringify(tr?.result ?? ''),
|
|
713
|
-
}
|
|
714
|
-
}),
|
|
715
|
-
})
|
|
655
|
+
accumulatedSteps.push(toAccumulatedStep(stepResult))
|
|
716
656
|
|
|
717
657
|
if (stepResult.toolCalls.length === 0) break
|
|
718
658
|
|
|
719
659
|
const approvalsNeeded = checkForApprovals(stepResult, runnerParams.tools)
|
|
720
660
|
if (approvalsNeeded.length > 0) {
|
|
721
|
-
|
|
722
|
-
const toolDef = runnerParams.tools.find(
|
|
723
|
-
(t) => t.name === approval.toolName
|
|
724
|
-
)
|
|
725
|
-
if (toolDef?.approvalDescriptionFn && !approval.reason) {
|
|
726
|
-
try {
|
|
727
|
-
approval.reason = await toolDef.approvalDescriptionFn(
|
|
728
|
-
approval.args
|
|
729
|
-
)
|
|
730
|
-
} catch {}
|
|
731
|
-
}
|
|
732
|
-
}
|
|
661
|
+
await describeApprovals(approvalsNeeded, runnerParams.tools)
|
|
733
662
|
|
|
734
663
|
const pendingApprovals = approvalsNeeded.map((a) =>
|
|
735
664
|
a.agentRunId
|
|
@@ -832,7 +761,7 @@ async function continueAfterToolResultSync(
|
|
|
832
761
|
run.threadId,
|
|
833
762
|
run.resourceId,
|
|
834
763
|
memoryConfig,
|
|
835
|
-
null
|
|
764
|
+
null,
|
|
836
765
|
{
|
|
837
766
|
...result,
|
|
838
767
|
text: outputText,
|