@pikku/core 0.12.79 → 0.12.82
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 +473 -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/errors/index.d.ts +1 -1
- package/dist/errors/index.js +1 -1
- package/dist/function/function-runner.js +22 -47
- package/dist/function/functions.types.d.ts +9 -5
- package/dist/function/index.d.ts +1 -1
- package/dist/index.d.ts +14 -19
- package/dist/index.js +6 -11
- 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 +16 -5
- package/dist/schema.js +35 -1
- package/dist/services/ai-agent-runner-service.d.ts +7 -0
- package/dist/services/ai-run-state-service.d.ts +17 -1
- package/dist/services/in-memory-ai-run-state-service.d.ts +6 -2
- package/dist/services/in-memory-ai-run-state-service.js +11 -1
- package/dist/services/in-memory-workflow-service.js +2 -0
- package/dist/services/index.d.ts +15 -15
- package/dist/services/index.js +5 -5
- 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/scoped-credential-service.d.ts +21 -0
- package/dist/services/scoped-credential-service.js +53 -0
- 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 +302 -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 -3
- package/dist/types/state.types.d.ts +14 -1
- package/dist/wirings/actor-flow/index.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-finalize.d.ts +58 -0
- package/dist/wirings/ai-agent/ai-agent-finalize.js +138 -0
- package/dist/wirings/ai-agent/ai-agent-interrupt.js +1 -0
- package/dist/wirings/ai-agent/ai-agent-memory.d.ts +2 -8
- package/dist/wirings/ai-agent/ai-agent-memory.js +37 -18
- package/dist/wirings/ai-agent/ai-agent-model-config.d.ts +7 -0
- package/dist/wirings/ai-agent/ai-agent-model-config.js +44 -1
- package/dist/wirings/ai-agent/ai-agent-prepare.js +5 -9
- package/dist/wirings/ai-agent/ai-agent-runner.js +89 -147
- package/dist/wirings/ai-agent/ai-agent-stream.js +109 -97
- package/dist/wirings/ai-agent/ai-agent-turn.d.ts +57 -0
- package/dist/wirings/ai-agent/ai-agent-turn.js +82 -0
- package/dist/wirings/ai-agent/ai-agent.types.d.ts +47 -2
- package/dist/wirings/ai-agent/index.d.ts +8 -7
- package/dist/wirings/ai-agent/index.js +5 -4
- package/dist/wirings/ai-agent/voice-input.js +1 -6
- package/dist/wirings/ai-agent/voice-output.js +2 -12
- package/dist/wirings/ai-scorer/ai-scorer-grade.d.ts +26 -0
- package/dist/wirings/ai-scorer/ai-scorer-grade.js +33 -0
- package/dist/wirings/ai-scorer/ai-scorer-judge.d.ts +17 -0
- package/dist/wirings/ai-scorer/ai-scorer-judge.js +92 -0
- package/dist/wirings/ai-scorer/ai-scorer-live.d.ts +15 -0
- package/dist/wirings/ai-scorer/ai-scorer-live.js +38 -0
- package/dist/wirings/ai-scorer/ai-scorer-registry.d.ts +18 -0
- package/dist/wirings/ai-scorer/ai-scorer-registry.js +46 -0
- package/dist/wirings/ai-scorer/ai-scorer-sampling.d.ts +8 -0
- package/dist/wirings/ai-scorer/ai-scorer-sampling.js +31 -0
- package/dist/wirings/ai-scorer/ai-scorer-snapshots.d.ts +10 -0
- package/dist/wirings/ai-scorer/ai-scorer-snapshots.js +40 -0
- package/dist/wirings/ai-scorer/ai-scorer-worker.d.ts +15 -0
- package/dist/wirings/ai-scorer/ai-scorer-worker.js +58 -0
- package/dist/wirings/ai-scorer/ai-scorer.d.ts +39 -0
- package/dist/wirings/ai-scorer/ai-scorer.js +40 -0
- package/dist/wirings/ai-scorer/ai-scorer.types.d.ts +90 -0
- package/dist/wirings/ai-scorer/ai-scorer.types.js +4 -0
- package/dist/wirings/ai-scorer/index.d.ts +6 -0
- package/dist/wirings/ai-scorer/index.js +5 -0
- 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/index.d.ts +5 -6
- package/dist/wirings/channel/index.js +3 -4
- package/dist/wirings/channel/local/local-channel-runner.js +8 -1
- 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/channel/cli-raw-channel-runner.js +9 -1
- package/dist/wirings/cli/channel/index.d.ts +1 -2
- package/dist/wirings/cli/channel/index.js +0 -1
- package/dist/wirings/cli/cli-runner.js +17 -3
- package/dist/wirings/cli/cli.types.d.ts +0 -8
- package/dist/wirings/cli/command-parser.js +13 -0
- package/dist/wirings/credential/index.d.ts +1 -1
- package/dist/wirings/gateway/gateway-runner.js +9 -2
- package/dist/wirings/gateway/index.d.ts +1 -1
- 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 +14 -15
- package/dist/wirings/http/http.types.d.ts +0 -10
- package/dist/wirings/http/index.d.ts +2 -3
- package/dist/wirings/http/index.js +1 -1
- package/dist/wirings/mcp/index.d.ts +1 -1
- package/dist/wirings/mcp/mcp-runner.d.ts +15 -7
- package/dist/wirings/mcp/mcp-runner.js +18 -11
- package/dist/wirings/persona/index.d.ts +3 -4
- package/dist/wirings/persona/index.js +2 -3
- package/dist/wirings/queue/index.d.ts +1 -3
- package/dist/wirings/queue/index.js +1 -3
- package/dist/wirings/rpc/addon-runner.d.ts +4 -0
- package/dist/wirings/rpc/addon-runner.js +19 -3
- package/dist/wirings/rpc/rpc-runner.d.ts +8 -51
- package/dist/wirings/rpc/rpc-runner.js +55 -86
- package/dist/wirings/rpc/rpc-types.d.ts +7 -0
- package/dist/wirings/rpc/wire-addon.d.ts +13 -0
- package/dist/wirings/rpc/wire-addon.js +4 -0
- package/dist/wirings/scheduler/index.d.ts +1 -1
- package/dist/wirings/secret/validate-secret-definitions.js +2 -2
- package/dist/wirings/trigger/index.d.ts +1 -1
- 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/index.d.ts +5 -6
- package/dist/wirings/virtual-user/index.js +2 -4
- 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 +100 -3
- 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 +12 -7
- package/dist/wirings/workflow/index.js +7 -3
- package/dist/wirings/workflow/pikku-scenario-service.d.ts +17 -3
- package/dist/wirings/workflow/pikku-scenario-service.js +64 -10
- package/dist/wirings/workflow/pikku-workflow-service.d.ts +28 -147
- package/dist/wirings/workflow/pikku-workflow-service.js +85 -493
- package/dist/wirings/workflow/scenario-step.types.d.ts +8 -0
- package/dist/wirings/workflow/workflow-approval-audit.d.ts +16 -0
- package/dist/wirings/workflow/workflow-approval-audit.js +40 -0
- package/dist/wirings/workflow/workflow-approval-policy.d.ts +20 -0
- package/dist/wirings/workflow/workflow-approval-policy.js +48 -0
- package/dist/wirings/workflow/workflow-approval.d.ts +67 -0
- package/dist/wirings/workflow/workflow-approval.js +177 -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-ownership.d.ts +17 -0
- package/dist/wirings/workflow/workflow-run-ownership.js +30 -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 +8 -1
- 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/addon-pikku-meta-ships-at-the-package-root-or-under-dist.md +32 -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-addon-scope-root-loses-to-a-root-the-host-already-declares.md +39 -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 +42 -3
- 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/validate-runs-checks-by-precondition.md +115 -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-function-never-receives-the-secret-service.md +37 -0
- 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 +50 -0
- package/knowledge/decisions/security/an-agent-approval-is-claimed-before-the-tool-runs.md +33 -0
- package/knowledge/decisions/security/an-approval-answer-outlives-the-run-it-answered.md +59 -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 +9 -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 +15 -3
- package/scripts/generate-api-report.d.mts +1 -0
- package/scripts/generate-api-report.mjs +89 -0
- package/scripts/generate-api-report.mts +343 -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/errors/index.ts +1 -1
- package/src/function/function-runner.test.ts +53 -1
- package/src/function/function-runner.ts +33 -48
- package/src/function/functions.types.ts +15 -9
- package/src/function/index.ts +0 -2
- package/src/handle-error.ts +1 -1
- package/src/index.ts +3 -57
- 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 +22 -6
- package/src/public-surface.json +554 -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-agent-runner-service.ts +12 -1
- package/src/services/ai-run-state-service.ts +18 -1
- package/src/services/audit-service.ts +2 -2
- package/src/services/in-memory-ai-run-state-service.ts +16 -2
- package/src/services/in-memory-workflow-service.ts +2 -0
- package/src/services/index.ts +4 -47
- 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/scoped-credential-service.test.ts +86 -0
- package/src/services/scoped-credential-service.ts +63 -0
- 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 +379 -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 +14 -3
- package/src/types/state.types.ts +17 -1
- package/src/wirings/actor-flow/index.ts +0 -3
- 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-finalize.test.ts +186 -0
- package/src/wirings/ai-agent/ai-agent-finalize.ts +197 -0
- package/src/wirings/ai-agent/ai-agent-interrupt.test.ts +2 -1
- package/src/wirings/ai-agent/ai-agent-interrupt.ts +1 -0
- package/src/wirings/ai-agent/ai-agent-memory.ts +62 -39
- package/src/wirings/ai-agent/ai-agent-model-config.test.ts +72 -3
- package/src/wirings/ai-agent/ai-agent-model-config.ts +49 -1
- package/src/wirings/ai-agent/ai-agent-prepare.ts +12 -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 +120 -160
- package/src/wirings/ai-agent/ai-agent-stream-output-hooks.test.ts +353 -0
- package/src/wirings/ai-agent/ai-agent-stream.test.ts +2 -0
- package/src/wirings/ai-agent/ai-agent-stream.ts +152 -117
- package/src/wirings/ai-agent/ai-agent-thread-ownership.test.ts +1 -1
- package/src/wirings/ai-agent/ai-agent-turn.test.ts +67 -0
- package/src/wirings/ai-agent/ai-agent-turn.ts +122 -0
- package/src/wirings/ai-agent/ai-agent.types.ts +65 -5
- package/src/wirings/ai-agent/index.ts +2 -16
- package/src/wirings/ai-agent/voice-input.ts +1 -6
- package/src/wirings/ai-agent/voice-output.ts +2 -12
- package/src/wirings/ai-scorer/ai-scorer-grade.test.ts +106 -0
- package/src/wirings/ai-scorer/ai-scorer-grade.ts +55 -0
- package/src/wirings/ai-scorer/ai-scorer-judge.test.ts +143 -0
- package/src/wirings/ai-scorer/ai-scorer-judge.ts +120 -0
- package/src/wirings/ai-scorer/ai-scorer-live.test.ts +174 -0
- package/src/wirings/ai-scorer/ai-scorer-live.ts +56 -0
- package/src/wirings/ai-scorer/ai-scorer-registry.ts +63 -0
- package/src/wirings/ai-scorer/ai-scorer-sampling.test.ts +34 -0
- package/src/wirings/ai-scorer/ai-scorer-sampling.ts +36 -0
- package/src/wirings/ai-scorer/ai-scorer-snapshots.test.ts +49 -0
- package/src/wirings/ai-scorer/ai-scorer-snapshots.ts +46 -0
- package/src/wirings/ai-scorer/ai-scorer-worker.test.ts +122 -0
- package/src/wirings/ai-scorer/ai-scorer-worker.ts +69 -0
- package/src/wirings/ai-scorer/ai-scorer.ts +76 -0
- package/src/wirings/ai-scorer/ai-scorer.types.ts +107 -0
- package/src/wirings/ai-scorer/index.ts +24 -0
- 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/index.ts +1 -20
- package/src/wirings/channel/local/local-channel-runner.test.ts +68 -0
- package/src/wirings/channel/local/local-channel-runner.ts +9 -2
- 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-channel-runner.test.ts +23 -0
- package/src/wirings/cli/channel/cli-raw-channel-runner.ts +12 -1
- package/src/wirings/cli/channel/cli-raw-client-runner.ts +4 -1
- package/src/wirings/cli/channel/index.ts +0 -7
- package/src/wirings/cli/cli-runner.test.ts +68 -0
- package/src/wirings/cli/cli-runner.ts +25 -5
- 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/credential/index.ts +0 -1
- package/src/wirings/gateway/gateway-channel-meta.test.ts +44 -0
- package/src/wirings/gateway/gateway-runner.ts +27 -19
- package/src/wirings/gateway/index.ts +0 -3
- package/src/wirings/http/http-routes.ts +4 -1
- package/src/wirings/http/http-runner.test.ts +66 -0
- package/src/wirings/http/http-runner.ts +21 -28
- package/src/wirings/http/http.types.ts +0 -15
- package/src/wirings/http/index.ts +2 -8
- package/src/wirings/http/pikku-fetch-http-request.ts +2 -2
- package/src/wirings/http/web-request.ts +1 -1
- package/src/wirings/mcp/index.ts +0 -1
- package/src/wirings/mcp/mcp-runner.test.ts +181 -0
- package/src/wirings/mcp/mcp-runner.ts +37 -21
- package/src/wirings/persona/index.ts +0 -8
- 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/index.ts +0 -14
- package/src/wirings/queue/queue-runner.ts +1 -1
- package/src/wirings/rpc/addon-auth-tags.test.ts +1 -5
- package/src/wirings/rpc/addon-runner.ts +34 -3
- package/src/wirings/rpc/addon-secrets.test.ts +261 -0
- package/src/wirings/rpc/rpc-runner.test.ts +2 -0
- package/src/wirings/rpc/rpc-runner.ts +60 -136
- package/src/wirings/rpc/rpc-types.ts +11 -0
- package/src/wirings/rpc/wire-addon.ts +20 -1
- package/src/wirings/scheduler/index.ts +0 -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/index.ts +0 -1
- package/src/wirings/trigger/pikku-trigger-service.ts +0 -5
- package/src/wirings/trigger/trigger-runner.ts +7 -5
- package/src/wirings/virtual-user/index.ts +0 -16
- 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 +117 -4
- 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 +76 -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 +14 -24
- package/src/wirings/workflow/pikku-scenario-service.ts +101 -27
- package/src/wirings/workflow/pikku-workflow-service.test.ts +15 -14
- package/src/wirings/workflow/pikku-workflow-service.ts +214 -743
- package/src/wirings/workflow/scenario-expectations.test.ts +75 -0
- package/src/wirings/workflow/scenario-hooks.test.ts +52 -0
- package/src/wirings/workflow/scenario-step.types.ts +8 -0
- package/src/wirings/workflow/workflow-approval-audit.ts +47 -0
- package/src/wirings/workflow/workflow-approval-policy.test.ts +524 -0
- package/src/wirings/workflow/workflow-approval-policy.ts +68 -0
- package/src/wirings/workflow/workflow-approval.ts +289 -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 +212 -0
- package/src/wirings/workflow/workflow-run-engine.types.ts +91 -0
- package/src/wirings/workflow/workflow-run-ownership.ts +37 -0
- package/src/wirings/workflow/workflow-suspend.ts +61 -0
- package/src/wirings/workflow/workflow.types.ts +7 -9
- package/src/wirings-stay-decoupled.test.ts +127 -0
- package/tsconfig.json +1 -1
- package/tsconfig.tsbuildinfo +1 -1
- package/dist/internal.d.ts +0 -3
- package/dist/internal.js +0 -2
- package/dist/middleware/timeout.d.ts +0 -9
- package/dist/middleware/timeout.js +0 -15
- package/dist/pikku-response.d.ts +0 -6
- package/dist/pikku-response.js +0 -6
- package/dist/services/gopass-secrets.d.ts +0 -15
- package/dist/services/gopass-secrets.js +0 -76
- package/dist/services/http-scenario-actors.d.ts +0 -75
- package/dist/services/http-scenario-actors.js +0 -195
- package/dist/services/http-user-flow-actors.d.ts +0 -67
- package/dist/services/http-user-flow-actors.js +0 -193
- package/dist/services/scenario-actors-service.d.ts +0 -127
- package/dist/services/scenario-actors-service.js +0 -40
- package/dist/services/user-flow-actors-service.d.ts +0 -39
- package/dist/wirings/credential/wire-credential.d.ts +0 -48
- package/dist/wirings/credential/wire-credential.js +0 -47
- package/dist/wirings/oauth2/oauth2-client.d.ts +0 -47
- package/dist/wirings/oauth2/oauth2-client.js +0 -263
- package/dist/wirings/oauth2/oauth2-routes.d.ts +0 -35
- package/dist/wirings/oauth2/oauth2-routes.js +0 -146
- package/dist/wirings/scope/wire-scope.d.ts +0 -33
- package/dist/wirings/scope/wire-scope.js +0 -32
- package/dist/wirings/workflow/dsl/index.d.ts +0 -5
- package/dist/wirings/workflow/dsl/index.js +0 -4
- package/dist/wirings/workflow/graph/index.d.ts +0 -5
- package/dist/wirings/workflow/graph/index.js +0 -4
- package/src/internal.ts +0 -10
- /package/dist/{services/user-flow-actors-service.js → wirings/workflow/workflow-run-engine.types.js} +0 -0
package/src/types/core.types.ts
CHANGED
|
@@ -188,8 +188,6 @@ export type FunctionRuntimeMeta = {
|
|
|
188
188
|
audit?: {
|
|
189
189
|
durability: AuditDurability
|
|
190
190
|
}
|
|
191
|
-
/** Keeps the full `SecretService`. Set by the inspector, read by the runner. */
|
|
192
|
-
secretBroker?: boolean
|
|
193
191
|
version?: number
|
|
194
192
|
approvalRequired?: boolean
|
|
195
193
|
approvalDescription?: string
|
|
@@ -298,6 +296,11 @@ export interface CoreUserSession {
|
|
|
298
296
|
* Populated by whoever builds the session — core reads them, never fetches.
|
|
299
297
|
*/
|
|
300
298
|
scopes?: string[]
|
|
299
|
+
/**
|
|
300
|
+
* Restricts the session to functions declared `readonly`. The function runner
|
|
301
|
+
* throws `ReadonlySessionError` for anything else.
|
|
302
|
+
*/
|
|
303
|
+
readonly?: boolean
|
|
301
304
|
}
|
|
302
305
|
|
|
303
306
|
/**
|
|
@@ -378,7 +381,10 @@ export type PikkuWire<
|
|
|
378
381
|
MCPTools extends string | never = never,
|
|
379
382
|
TypedWorkflow extends PikkuWorkflowWire | never = PikkuWorkflowWire,
|
|
380
383
|
TriggerOutput = unknown,
|
|
381
|
-
|
|
384
|
+
// `any`, not `Out`: the emitted `TypedScenario<Out>` supplies the real
|
|
385
|
+
// context, and this default is what other generics constrain against. See
|
|
386
|
+
// `ScenarioContext`.
|
|
387
|
+
TypedScenario extends PikkuScenarioWire<any> | never = PikkuScenarioWire<any>,
|
|
382
388
|
TypedActors extends ScenarioPersonas = ScenarioPersonas,
|
|
383
389
|
> = {
|
|
384
390
|
/** Always present — lazily initialised on first access for every function invocation */
|
|
@@ -386,6 +392,11 @@ export type PikkuWire<
|
|
|
386
392
|
} & Partial<{
|
|
387
393
|
wireType: PikkuWiringTypes
|
|
388
394
|
wireId: string
|
|
395
|
+
/**
|
|
396
|
+
* A logger scoped to this invocation, when a host attaches one. Core never
|
|
397
|
+
* sets it; services that log fall back to the singleton logger.
|
|
398
|
+
*/
|
|
399
|
+
logger: Logger
|
|
389
400
|
/** Trace ID for distributed tracing — propagated across remote RPC calls via x-trace-id header */
|
|
390
401
|
traceId: string
|
|
391
402
|
functionId: string
|
package/src/types/state.types.ts
CHANGED
|
@@ -29,6 +29,10 @@ import type {
|
|
|
29
29
|
CoreAIAgent,
|
|
30
30
|
AIAgentMeta,
|
|
31
31
|
} from '../wirings/ai-agent/ai-agent.types.js'
|
|
32
|
+
import type {
|
|
33
|
+
PikkuAIScorer,
|
|
34
|
+
ScorerMeta,
|
|
35
|
+
} from '../wirings/ai-scorer/ai-scorer.types.js'
|
|
32
36
|
import type {
|
|
33
37
|
CoreGateway,
|
|
34
38
|
GatewaysMeta,
|
|
@@ -98,6 +102,10 @@ export interface PikkuPackageState {
|
|
|
98
102
|
variableOverrides?: Record<string, string>
|
|
99
103
|
/** Per-instance name-aliases: logical name the addon reads -> actual project credential name */
|
|
100
104
|
credentialOverrides?: Record<string, string>
|
|
105
|
+
/** Why this instance gets the whole `SecretService` rather than one scoped to its declared secrets */
|
|
106
|
+
globalSecrets?: string
|
|
107
|
+
/** Why this instance gets the whole `CredentialService` rather than one scoped to its declared credentials */
|
|
108
|
+
globalCredentials?: string
|
|
101
109
|
/** Set by `wireRemoteAddon`: this namespace is consumed remotely over HTTP, not bundled */
|
|
102
110
|
remote?: boolean
|
|
103
111
|
/** Remote host base URL (wireRemoteAddon) — string or resolver over singleton services */
|
|
@@ -119,7 +127,7 @@ export interface PikkuPackageState {
|
|
|
119
127
|
meta: HTTPWiringsMeta
|
|
120
128
|
}
|
|
121
129
|
channel: {
|
|
122
|
-
channels: Map<string, CoreChannel<any, any>>
|
|
130
|
+
channels: Map<string, CoreChannel<any, any, any, any, any>>
|
|
123
131
|
meta: ChannelsMeta
|
|
124
132
|
}
|
|
125
133
|
scheduler: {
|
|
@@ -153,6 +161,10 @@ export interface PikkuPackageState {
|
|
|
153
161
|
agent: {
|
|
154
162
|
agents: Map<string, CoreAIAgent>
|
|
155
163
|
agentsMeta: AIAgentMeta
|
|
164
|
+
scorers: Map<string, PikkuAIScorer>
|
|
165
|
+
scorersMeta: ScorerMeta
|
|
166
|
+
/** Alias -> `provider/model`, from the `models` table in pikku.config.json. */
|
|
167
|
+
modelAliases: Record<string, string>
|
|
156
168
|
}
|
|
157
169
|
gateway: {
|
|
158
170
|
gateways: Map<string, CoreGateway>
|
|
@@ -204,5 +216,9 @@ export interface PikkuPackageState {
|
|
|
204
216
|
{ name: string; displayName: string; type: string; oauth2?: boolean }
|
|
205
217
|
> | null
|
|
206
218
|
requiredParentServices: string[] | null
|
|
219
|
+
/** The secret keys this package is allowed to read, emitted by the CLI from
|
|
220
|
+
* what the package declares and is seen to read. `null` when the package
|
|
221
|
+
* was never analysed, which scopes it to nothing. */
|
|
222
|
+
declaredSecrets: string[] | null
|
|
207
223
|
}
|
|
208
224
|
}
|
|
@@ -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',
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { describe, test } from 'node:test'
|
|
2
|
+
import assert from 'node:assert/strict'
|
|
3
|
+
import {
|
|
4
|
+
applyOutputMiddleware,
|
|
5
|
+
finalizeAgentRun,
|
|
6
|
+
type FinalizedRun,
|
|
7
|
+
} from './ai-agent-finalize.js'
|
|
8
|
+
import type { AIAgentStep, PikkuAIMiddlewareHooks } from './ai-agent.types.js'
|
|
9
|
+
import type { AIRunStateService } from '../../services/ai-run-state-service.js'
|
|
10
|
+
|
|
11
|
+
const step = (
|
|
12
|
+
toolCalls: NonNullable<AIAgentStep['toolCalls']>
|
|
13
|
+
): AIAgentStep => ({
|
|
14
|
+
usage: { inputTokens: 0, outputTokens: 0 },
|
|
15
|
+
toolCalls,
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
const call = (name: string, result: string) => ({
|
|
19
|
+
name,
|
|
20
|
+
args: {} as Record<string, unknown>,
|
|
21
|
+
result,
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
describe('applyOutputMiddleware', () => {
|
|
25
|
+
test('hands the middleware every tool call the run made, flattened across steps', async () => {
|
|
26
|
+
const seen: string[][] = []
|
|
27
|
+
const middleware: PikkuAIMiddlewareHooks[] = [
|
|
28
|
+
{
|
|
29
|
+
modifyOutput: (_services, ctx) => {
|
|
30
|
+
seen.push(ctx.toolCalls.map((c) => c.name))
|
|
31
|
+
return { text: ctx.text, messages: ctx.messages }
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
await applyOutputMiddleware(
|
|
37
|
+
middleware,
|
|
38
|
+
{},
|
|
39
|
+
{
|
|
40
|
+
text: 'done',
|
|
41
|
+
messages: [],
|
|
42
|
+
steps: [step([call('search', 'a')]), step([call('fetch', 'b')])],
|
|
43
|
+
usage: { inputTokens: 1, outputTokens: 2 },
|
|
44
|
+
}
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
assert.deepEqual(seen, [['search', 'fetch']])
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
test('a rewritten tool call is redistributed back onto the step it came from', async () => {
|
|
51
|
+
const middleware: PikkuAIMiddlewareHooks[] = [
|
|
52
|
+
{
|
|
53
|
+
modifyOutput: (_services, ctx) => ({
|
|
54
|
+
text: ctx.text,
|
|
55
|
+
messages: ctx.messages,
|
|
56
|
+
toolCalls: ctx.toolCalls.map((c) => ({ ...c, result: '[redacted]' })),
|
|
57
|
+
}),
|
|
58
|
+
},
|
|
59
|
+
]
|
|
60
|
+
|
|
61
|
+
const { steps } = await applyOutputMiddleware(
|
|
62
|
+
middleware,
|
|
63
|
+
{},
|
|
64
|
+
{
|
|
65
|
+
text: 'done',
|
|
66
|
+
messages: [],
|
|
67
|
+
steps: [
|
|
68
|
+
step([call('search', 'secret')]),
|
|
69
|
+
step([call('fetch', 'token')]),
|
|
70
|
+
],
|
|
71
|
+
usage: { inputTokens: 0, outputTokens: 0 },
|
|
72
|
+
}
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
assert.deepEqual(
|
|
76
|
+
steps.map((s) => s.toolCalls?.map((c) => [c.name, c.result])),
|
|
77
|
+
[[['search', '[redacted]']], [['fetch', '[redacted]']]]
|
|
78
|
+
)
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
test('dropping a tool call collapses the calls into the last step rather than mis-attributing them', async () => {
|
|
82
|
+
const middleware: PikkuAIMiddlewareHooks[] = [
|
|
83
|
+
{
|
|
84
|
+
modifyOutput: (_services, ctx) => ({
|
|
85
|
+
text: ctx.text,
|
|
86
|
+
messages: ctx.messages,
|
|
87
|
+
toolCalls: ctx.toolCalls.filter((c) => c.name !== 'search'),
|
|
88
|
+
}),
|
|
89
|
+
},
|
|
90
|
+
]
|
|
91
|
+
|
|
92
|
+
const { steps } = await applyOutputMiddleware(
|
|
93
|
+
middleware,
|
|
94
|
+
{},
|
|
95
|
+
{
|
|
96
|
+
text: 'done',
|
|
97
|
+
messages: [],
|
|
98
|
+
steps: [step([call('search', 'a')]), step([call('fetch', 'b')])],
|
|
99
|
+
usage: { inputTokens: 0, outputTokens: 0 },
|
|
100
|
+
}
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
assert.deepEqual(
|
|
104
|
+
steps.map((s) => s.toolCalls?.map((c) => c.name)),
|
|
105
|
+
[[], ['fetch']]
|
|
106
|
+
)
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
test('runs the chain in reverse registration order, each hook seeing the previous rewrite', async () => {
|
|
110
|
+
const order: string[] = []
|
|
111
|
+
const middleware: PikkuAIMiddlewareHooks[] = [
|
|
112
|
+
{
|
|
113
|
+
modifyOutput: (_services, ctx) => {
|
|
114
|
+
order.push(`first saw ${ctx.text}`)
|
|
115
|
+
return { text: `${ctx.text}+first`, messages: ctx.messages }
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
modifyOutput: (_services, ctx) => {
|
|
120
|
+
order.push(`second saw ${ctx.text}`)
|
|
121
|
+
return { text: `${ctx.text}+second`, messages: ctx.messages }
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
]
|
|
125
|
+
|
|
126
|
+
const { text } = await applyOutputMiddleware(
|
|
127
|
+
middleware,
|
|
128
|
+
{},
|
|
129
|
+
{
|
|
130
|
+
text: 'raw',
|
|
131
|
+
messages: [],
|
|
132
|
+
steps: [],
|
|
133
|
+
usage: { inputTokens: 0, outputTokens: 0 },
|
|
134
|
+
}
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
assert.deepEqual(order, ['second saw raw', 'first saw raw+second'])
|
|
138
|
+
assert.equal(text, 'raw+second+first')
|
|
139
|
+
})
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
describe('finalizeAgentRun', () => {
|
|
143
|
+
const run = (usage: FinalizedRun['usage']): FinalizedRun => ({
|
|
144
|
+
runId: 'run-1',
|
|
145
|
+
agentName: 'assistant',
|
|
146
|
+
threadId: 'thread-1',
|
|
147
|
+
input: 'what is the capital of France?',
|
|
148
|
+
text: 'done',
|
|
149
|
+
steps: [],
|
|
150
|
+
usage,
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
test('completes the run and records its usage', async () => {
|
|
154
|
+
const updates: unknown[] = []
|
|
155
|
+
const aiRunState = {
|
|
156
|
+
updateRun: async (_runId: string, update: unknown) => {
|
|
157
|
+
updates.push(update)
|
|
158
|
+
},
|
|
159
|
+
} as unknown as AIRunStateService
|
|
160
|
+
|
|
161
|
+
await finalizeAgentRun(
|
|
162
|
+
aiRunState,
|
|
163
|
+
run({ inputTokens: 10, outputTokens: 20, model: 'gpt-4o' })
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
assert.deepEqual(updates, [
|
|
167
|
+
{
|
|
168
|
+
status: 'completed',
|
|
169
|
+
usage: { inputTokens: 10, outputTokens: 20, model: 'gpt-4o' },
|
|
170
|
+
},
|
|
171
|
+
])
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
test('omits usage entirely when no model reported one', async () => {
|
|
175
|
+
const updates: any[] = []
|
|
176
|
+
const aiRunState = {
|
|
177
|
+
updateRun: async (_runId: string, update: unknown) => {
|
|
178
|
+
updates.push(update)
|
|
179
|
+
},
|
|
180
|
+
} as unknown as AIRunStateService
|
|
181
|
+
|
|
182
|
+
await finalizeAgentRun(aiRunState, run({ inputTokens: 0, outputTokens: 0 }))
|
|
183
|
+
|
|
184
|
+
assert.deepEqual(updates, [{ status: 'completed' }])
|
|
185
|
+
})
|
|
186
|
+
})
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
AIAgentStep,
|
|
3
|
+
AIMessage,
|
|
4
|
+
PikkuAIMiddlewareHooks,
|
|
5
|
+
} from './ai-agent.types.js'
|
|
6
|
+
import type { AIRunStateService } from '../../services/ai-run-state-service.js'
|
|
7
|
+
import { pikkuState } from '../../pikku-state.js'
|
|
8
|
+
import { scoreFinishedRun } from '../ai-scorer/ai-scorer-live.js'
|
|
9
|
+
import { recordScoreSnapshot } from '../ai-scorer/ai-scorer-snapshots.js'
|
|
10
|
+
|
|
11
|
+
export type RunUsage = {
|
|
12
|
+
inputTokens: number
|
|
13
|
+
outputTokens: number
|
|
14
|
+
model?: string
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Everything a finished run produced, after all middleware has had its say.
|
|
19
|
+
*
|
|
20
|
+
* One snapshot feeds persistence and anything that grades the run, so that what
|
|
21
|
+
* a scorer is shown is exactly what was stored — including the redactions.
|
|
22
|
+
*/
|
|
23
|
+
export type FinalizedRun = {
|
|
24
|
+
runId: string
|
|
25
|
+
agentName: string
|
|
26
|
+
threadId: string
|
|
27
|
+
resourceId?: string
|
|
28
|
+
/** The prompt the run answered — what a scorer grades the answer against. */
|
|
29
|
+
input: string
|
|
30
|
+
text: string
|
|
31
|
+
steps: AIAgentStep[]
|
|
32
|
+
usage: RunUsage
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* The prompt a run answered: the most recent user turn, which is what the model
|
|
37
|
+
* was last asked. On a resumed run the earlier turns are context, not the ask.
|
|
38
|
+
*/
|
|
39
|
+
export const lastUserMessageText = (messages: AIMessage[]): string => {
|
|
40
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
41
|
+
const message = messages[i]
|
|
42
|
+
if (message?.role !== 'user') continue
|
|
43
|
+
if (typeof message.content === 'string') return message.content
|
|
44
|
+
if (Array.isArray(message.content)) {
|
|
45
|
+
return message.content
|
|
46
|
+
.filter((part) => part.type === 'text')
|
|
47
|
+
.map((part) => (part as { text: string }).text)
|
|
48
|
+
.join('\n')
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return ''
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const flattenToolCalls = (
|
|
55
|
+
steps: AIAgentStep[]
|
|
56
|
+
): NonNullable<AIAgentStep['toolCalls']> =>
|
|
57
|
+
steps.flatMap((step) => step.toolCalls ?? [])
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Push a rewritten flat tool-call list back onto the steps it came from,
|
|
61
|
+
* preserving the step boundaries. A hook that adds or drops calls would make
|
|
62
|
+
* the boundaries meaningless, so a length change collapses them into the last
|
|
63
|
+
* step rather than silently mis-attributing calls to the wrong step.
|
|
64
|
+
*/
|
|
65
|
+
const distributeToolCalls = (
|
|
66
|
+
steps: AIAgentStep[],
|
|
67
|
+
toolCalls: NonNullable<AIAgentStep['toolCalls']>
|
|
68
|
+
): AIAgentStep[] => {
|
|
69
|
+
if (toolCalls.length !== flattenToolCalls(steps).length) {
|
|
70
|
+
return steps.map((step, index) => ({
|
|
71
|
+
...step,
|
|
72
|
+
toolCalls: index === steps.length - 1 ? toolCalls : [],
|
|
73
|
+
}))
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
let cursor = 0
|
|
77
|
+
return steps.map((step) => {
|
|
78
|
+
if (!step.toolCalls) return step
|
|
79
|
+
const next = toolCalls.slice(cursor, cursor + step.toolCalls.length)
|
|
80
|
+
cursor += step.toolCalls.length
|
|
81
|
+
return { ...step, toolCalls: next }
|
|
82
|
+
})
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Run the `modifyOutput` chain over a finished non-streaming run.
|
|
87
|
+
*
|
|
88
|
+
* Reverse order, matching the input chain: the middleware registered first
|
|
89
|
+
* wraps the others, so it sees the output last.
|
|
90
|
+
*/
|
|
91
|
+
export const applyOutputMiddleware = async (
|
|
92
|
+
aiMiddlewares: PikkuAIMiddlewareHooks[],
|
|
93
|
+
singletonServices: any,
|
|
94
|
+
input: {
|
|
95
|
+
text: string
|
|
96
|
+
messages: AIMessage[]
|
|
97
|
+
steps: AIAgentStep[]
|
|
98
|
+
usage: { inputTokens: number; outputTokens: number }
|
|
99
|
+
}
|
|
100
|
+
): Promise<{ text: string; messages: AIMessage[]; steps: AIAgentStep[] }> => {
|
|
101
|
+
let text = input.text
|
|
102
|
+
let messages = input.messages
|
|
103
|
+
let steps = input.steps
|
|
104
|
+
let toolCalls = flattenToolCalls(steps)
|
|
105
|
+
|
|
106
|
+
for (let i = aiMiddlewares.length - 1; i >= 0; i--) {
|
|
107
|
+
const mw = aiMiddlewares[i]
|
|
108
|
+
if (!mw.modifyOutput) continue
|
|
109
|
+
const result = await mw.modifyOutput(singletonServices, {
|
|
110
|
+
text,
|
|
111
|
+
messages,
|
|
112
|
+
usage: {
|
|
113
|
+
inputTokens: input.usage.inputTokens,
|
|
114
|
+
outputTokens: input.usage.outputTokens,
|
|
115
|
+
},
|
|
116
|
+
toolCalls,
|
|
117
|
+
})
|
|
118
|
+
text = result.text
|
|
119
|
+
messages = result.messages
|
|
120
|
+
if (result.toolCalls) {
|
|
121
|
+
toolCalls = result.toolCalls
|
|
122
|
+
steps = distributeToolCalls(steps, toolCalls)
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return { text, messages, steps }
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* The one place a run ends successfully.
|
|
131
|
+
*
|
|
132
|
+
* Every path that completes a run — streamed, non-streamed, and resumed after a
|
|
133
|
+
* tool approval — goes through here, so that anything terminal is reachable
|
|
134
|
+
* from all of them and cannot be reordered by a middleware author. Nothing here
|
|
135
|
+
* may rewrite the run: by this point the output middleware has resolved and, on
|
|
136
|
+
* the streaming path, the client already has the reply.
|
|
137
|
+
*/
|
|
138
|
+
export const finalizeAgentRun = async (
|
|
139
|
+
aiRunState: AIRunStateService,
|
|
140
|
+
run: FinalizedRun
|
|
141
|
+
): Promise<void> => {
|
|
142
|
+
await aiRunState.updateRun(run.runId, {
|
|
143
|
+
status: 'completed',
|
|
144
|
+
...(run.usage.model
|
|
145
|
+
? {
|
|
146
|
+
usage: {
|
|
147
|
+
inputTokens: run.usage.inputTokens,
|
|
148
|
+
outputTokens: run.usage.outputTokens,
|
|
149
|
+
model: run.usage.model,
|
|
150
|
+
},
|
|
151
|
+
}
|
|
152
|
+
: {}),
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
// Read rather than `getSingletonServices()`: a process that never registered
|
|
156
|
+
// them grades nothing, which is not an error at the point a run has already
|
|
157
|
+
// succeeded.
|
|
158
|
+
const services = pikkuState(null, 'package', 'singletonServices')
|
|
159
|
+
if (!services) return
|
|
160
|
+
|
|
161
|
+
const snapshot = {
|
|
162
|
+
runId: run.runId,
|
|
163
|
+
agentName: run.agentName,
|
|
164
|
+
threadId: run.threadId,
|
|
165
|
+
...(run.resourceId !== undefined ? { resourceId: run.resourceId } : {}),
|
|
166
|
+
input: run.input,
|
|
167
|
+
output: run.text,
|
|
168
|
+
toolCalls: run.steps.flatMap((step) =>
|
|
169
|
+
(step.toolCalls ?? []).map((call) => ({
|
|
170
|
+
name: call.name,
|
|
171
|
+
args: call.args,
|
|
172
|
+
result: call.result,
|
|
173
|
+
...(call.error !== undefined ? { error: call.error } : {}),
|
|
174
|
+
}))
|
|
175
|
+
),
|
|
176
|
+
usage: {
|
|
177
|
+
inputTokens: run.usage.inputTokens,
|
|
178
|
+
outputTokens: run.usage.outputTokens,
|
|
179
|
+
...(run.usage.model !== undefined ? { model: run.usage.model } : {}),
|
|
180
|
+
},
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// The same object a scenario grades, so an asserted score and a sampled one
|
|
184
|
+
// are the same measurement. A no-op unless a dev server turned retention on.
|
|
185
|
+
recordScoreSnapshot(snapshot)
|
|
186
|
+
|
|
187
|
+
// Best-effort and last: the client already has its answer, so a grading
|
|
188
|
+
// failure must not surface as a failed run.
|
|
189
|
+
try {
|
|
190
|
+
await scoreFinishedRun(snapshot, services)
|
|
191
|
+
} catch (error) {
|
|
192
|
+
services.logger?.error(
|
|
193
|
+
`[pikku] Live scoring failed for run ${run.runId}`,
|
|
194
|
+
error
|
|
195
|
+
)
|
|
196
|
+
}
|
|
197
|
+
}
|
|
@@ -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)
|
|
@@ -381,6 +381,7 @@ export const persistOrphanedToolResults = async (
|
|
|
381
381
|
: typeof entry.result === 'string'
|
|
382
382
|
? entry.result
|
|
383
383
|
: JSON.stringify(entry.result),
|
|
384
|
+
...(entry.error ? { error: entry.error } : {}),
|
|
384
385
|
})),
|
|
385
386
|
undelivered: true,
|
|
386
387
|
createdAt: new Date(),
|