@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
|
@@ -51,6 +51,13 @@ export interface AIToolResult {
|
|
|
51
51
|
id: string
|
|
52
52
|
name: string
|
|
53
53
|
result: string
|
|
54
|
+
/**
|
|
55
|
+
* Set when the tool threw rather than returned. Carried separately from
|
|
56
|
+
* `result`, which is a rendered string by the time it is persisted — a tool
|
|
57
|
+
* may legitimately return text beginning `Error:`, so the prefix cannot be
|
|
58
|
+
* read as a failure signal.
|
|
59
|
+
*/
|
|
60
|
+
error?: string
|
|
54
61
|
}
|
|
55
62
|
|
|
56
63
|
export interface AIMessage {
|
|
@@ -82,7 +89,13 @@ export interface AIMessage {
|
|
|
82
89
|
|
|
83
90
|
export interface AIAgentStep {
|
|
84
91
|
usage: { inputTokens: number; outputTokens: number }
|
|
85
|
-
toolCalls?: {
|
|
92
|
+
toolCalls?: {
|
|
93
|
+
name: string
|
|
94
|
+
args: Record<string, unknown>
|
|
95
|
+
result: string
|
|
96
|
+
/** The failure message, when the tool threw rather than returned. */
|
|
97
|
+
error?: string
|
|
98
|
+
}[]
|
|
86
99
|
}
|
|
87
100
|
|
|
88
101
|
export interface AIAgentInputAttachment {
|
|
@@ -233,16 +246,40 @@ export interface PikkuAIMiddlewareHooks<
|
|
|
233
246
|
| AIStreamEvent[]
|
|
234
247
|
| null
|
|
235
248
|
|
|
249
|
+
/**
|
|
250
|
+
* The last chance to rewrite what the run produced, before it is persisted
|
|
251
|
+
* and returned.
|
|
252
|
+
*
|
|
253
|
+
* It does **not** run on a streamed run: there the text has already reached
|
|
254
|
+
* the client and each step is flushed to storage as it goes, so nothing could
|
|
255
|
+
* act on what this returned. Use {@link modifyOutputStream} to rewrite a
|
|
256
|
+
* streamed reply — a middleware that implements only this one is warned about
|
|
257
|
+
* when an agent it is attached to streams.
|
|
258
|
+
*
|
|
259
|
+
* `toolCalls` is here so a redaction pass covers the whole run record rather
|
|
260
|
+
* than just the visible answer: the tool arguments and results are persisted
|
|
261
|
+
* and handed to anything that grades the run, and scrubbing the reply alone
|
|
262
|
+
* leaves them untouched.
|
|
263
|
+
*/
|
|
236
264
|
modifyOutput?: (
|
|
237
265
|
services: Services,
|
|
238
266
|
ctx: {
|
|
239
267
|
text: string
|
|
240
268
|
messages: AIMessage[]
|
|
241
269
|
usage: { inputTokens: number; outputTokens: number }
|
|
270
|
+
toolCalls: NonNullable<AIAgentStep['toolCalls']>
|
|
242
271
|
}
|
|
243
272
|
) =>
|
|
244
|
-
| Promise<{
|
|
245
|
-
|
|
273
|
+
| Promise<{
|
|
274
|
+
text: string
|
|
275
|
+
messages: AIMessage[]
|
|
276
|
+
toolCalls?: NonNullable<AIAgentStep['toolCalls']>
|
|
277
|
+
}>
|
|
278
|
+
| {
|
|
279
|
+
text: string
|
|
280
|
+
messages: AIMessage[]
|
|
281
|
+
toolCalls?: NonNullable<AIAgentStep['toolCalls']>
|
|
282
|
+
}
|
|
246
283
|
|
|
247
284
|
beforeToolCall?: (
|
|
248
285
|
services: Services,
|
|
@@ -273,7 +310,13 @@ export interface PikkuAIMiddlewareHooks<
|
|
|
273
310
|
stepNumber: number
|
|
274
311
|
text: string
|
|
275
312
|
toolCalls: { toolCallId: string; toolName: string; args: unknown }[]
|
|
276
|
-
toolResults: {
|
|
313
|
+
toolResults: {
|
|
314
|
+
toolCallId: string
|
|
315
|
+
toolName: string
|
|
316
|
+
result: unknown
|
|
317
|
+
/** Set when the tool threw rather than returned. */
|
|
318
|
+
error?: string
|
|
319
|
+
}[]
|
|
277
320
|
usage: { inputTokens: number; outputTokens: number }
|
|
278
321
|
finishReason: string
|
|
279
322
|
}
|
|
@@ -301,6 +344,7 @@ export type CoreAIAgent<
|
|
|
301
344
|
PikkuPermission = CorePikkuPermission<any, any>,
|
|
302
345
|
PikkuMiddleware = CorePikkuMiddleware<any>,
|
|
303
346
|
Scope extends string = string,
|
|
347
|
+
Scorer extends string = string,
|
|
304
348
|
> = {
|
|
305
349
|
name: string
|
|
306
350
|
description: string
|
|
@@ -327,6 +371,16 @@ export type CoreAIAgent<
|
|
|
327
371
|
tools?: unknown[]
|
|
328
372
|
agents?: unknown[]
|
|
329
373
|
workflows?: unknown[]
|
|
374
|
+
/**
|
|
375
|
+
* Grades this agent's finished runs on live traffic, named by the generated
|
|
376
|
+
* `ScorerName` union rather than by `ref()` — a scorer is not a function, so
|
|
377
|
+
* there is nothing in the function map for a ref to resolve against.
|
|
378
|
+
*
|
|
379
|
+
* A reference-based judge listed here is never sampled: live traffic has no
|
|
380
|
+
* answer key. Scenarios name scorers directly and may grade with scorers an
|
|
381
|
+
* agent does not ship with.
|
|
382
|
+
*/
|
|
383
|
+
scorers?: Scorer[]
|
|
330
384
|
agentMode?: 'delegate' | 'supervise'
|
|
331
385
|
memory?: AIAgentMemoryConfig
|
|
332
386
|
maxSteps?: number
|
|
@@ -392,6 +446,12 @@ export type AIStreamEvent =
|
|
|
392
446
|
toolCallId: string
|
|
393
447
|
toolName: string
|
|
394
448
|
result: unknown
|
|
449
|
+
/**
|
|
450
|
+
* The failure message, set when the tool threw rather than returned.
|
|
451
|
+
* Carried explicitly because a tool may legitimately return text that
|
|
452
|
+
* reads like an error, so `result` cannot be matched on to tell.
|
|
453
|
+
*/
|
|
454
|
+
error?: string
|
|
395
455
|
agent?: string
|
|
396
456
|
session?: string
|
|
397
457
|
}
|
|
@@ -413,7 +473,7 @@ export type AIStreamEvent =
|
|
|
413
473
|
toolName: string
|
|
414
474
|
args: unknown
|
|
415
475
|
reason?: string
|
|
416
|
-
runId
|
|
476
|
+
runId: string
|
|
417
477
|
agent?: string
|
|
418
478
|
session?: string
|
|
419
479
|
}
|
|
@@ -5,8 +5,9 @@ export {
|
|
|
5
5
|
agentApprove,
|
|
6
6
|
agentInterrupt,
|
|
7
7
|
} from './ai-agent-helpers.js'
|
|
8
|
-
export { wrapChannelWithAGUI
|
|
8
|
+
export { wrapChannelWithAGUI } from './ai-agent-agui.js'
|
|
9
9
|
export { runAIAgent, resumeAIAgentSync } from './ai-agent-runner.js'
|
|
10
|
+
export { resolveModelAlias } from './ai-agent-model-config.js'
|
|
10
11
|
export {
|
|
11
12
|
streamAIAgent,
|
|
12
13
|
resumeAIAgent,
|
|
@@ -14,7 +15,6 @@ export {
|
|
|
14
15
|
} from './ai-agent-stream.js'
|
|
15
16
|
export {
|
|
16
17
|
voiceInput,
|
|
17
|
-
readsAsNonSpeech,
|
|
18
18
|
NoSpeechDetectedError,
|
|
19
19
|
SPOKEN_TURN,
|
|
20
20
|
SPOKEN_TRANSCRIPT,
|
|
@@ -27,21 +27,12 @@ export {
|
|
|
27
27
|
} from './voice-output.js'
|
|
28
28
|
export {
|
|
29
29
|
AgentInterruptedError,
|
|
30
|
-
awaitPendingInterruptNote,
|
|
31
|
-
getInFlightTools,
|
|
32
|
-
isAbortError,
|
|
33
|
-
isRunInterruptible,
|
|
34
|
-
persistOrphanedToolResults,
|
|
35
|
-
registerInterruptibleRun,
|
|
36
30
|
signalRunInterrupt,
|
|
37
|
-
trackInterruptNote,
|
|
38
|
-
trackToolExecution,
|
|
39
31
|
} from './ai-agent-interrupt.js'
|
|
40
32
|
export type {
|
|
41
33
|
AgentInterruption,
|
|
42
34
|
AgentInterruptResult,
|
|
43
35
|
InterruptibleRunHandle,
|
|
44
|
-
OrphanedToolResult,
|
|
45
36
|
} from './ai-agent-interrupt.js'
|
|
46
37
|
export {
|
|
47
38
|
type RunAIAgentParams,
|
|
@@ -50,18 +41,13 @@ export {
|
|
|
50
41
|
ToolCredentialRequired,
|
|
51
42
|
canAccessThread,
|
|
52
43
|
isOwnedByPrincipal,
|
|
53
|
-
sessionPrincipals,
|
|
54
44
|
threadOwnerConstraint,
|
|
55
45
|
} from './ai-agent-prepare.js'
|
|
56
46
|
export {
|
|
57
47
|
addAIAgent,
|
|
58
|
-
approveAIAgent,
|
|
59
|
-
getAIAgents,
|
|
60
|
-
getAIAgentsMeta,
|
|
61
48
|
} from './ai-agent-registry.js'
|
|
62
49
|
export type {
|
|
63
50
|
AIAgentInput,
|
|
64
|
-
AIAgentInputAttachment,
|
|
65
51
|
AIAgentMeta,
|
|
66
52
|
AIAgentMemoryConfig,
|
|
67
53
|
AIAgentStep,
|
|
@@ -164,12 +164,7 @@ export const voiceInput = (config?: {
|
|
|
164
164
|
// to send, and a message with no content is not a question.
|
|
165
165
|
if (updatedContent.length === 0) throw new NoSpeechDetectedError()
|
|
166
166
|
|
|
167
|
-
//
|
|
168
|
-
// reads as non-speech and still have content — an image with a silent
|
|
169
|
-
// caption clip — which leaves nothing above to throw. Recording `''` here
|
|
170
|
-
// would send a transcript event saying the user said nothing, and a client
|
|
171
|
-
// that tells "not transcribed yet" from "transcribed" by the key being
|
|
172
|
-
// absent would render the turn as an empty bubble rather than a pending one.
|
|
167
|
+
// knowledge: decisions/internals/an-empty-transcript-is-not-recorded.md
|
|
173
168
|
if (heard.length > 0) {
|
|
174
169
|
shared[SPOKEN_TRANSCRIPT] = heard.join(' ')
|
|
175
170
|
}
|
|
@@ -206,11 +206,7 @@ export const voiceOutput = (config?: {
|
|
|
206
206
|
}
|
|
207
207
|
if (!aiAgentRunner?.generateSpeech) return event
|
|
208
208
|
|
|
209
|
-
//
|
|
210
|
-
// every turn it handles, so `false` means a real user really typed; the
|
|
211
|
-
// key being absent means nothing reported either way — no voice input is
|
|
212
|
-
// wired — and that caller's replies are spoken exactly as they were before
|
|
213
|
-
// this option existed.
|
|
209
|
+
// knowledge: decisions/internals/voice-output-speaks-unless-voice-input-explicitly-says-otherwise.md
|
|
214
210
|
if (!config?.always && shared[SPOKEN_TURN] === false) return event
|
|
215
211
|
|
|
216
212
|
/**
|
|
@@ -228,13 +224,7 @@ export const voiceOutput = (config?: {
|
|
|
228
224
|
)
|
|
229
225
|
}
|
|
230
226
|
|
|
231
|
-
//
|
|
232
|
-
// should still speak the half it can, and repeating the notice for
|
|
233
|
-
// every sentence of a long one would bury the reply itself.
|
|
234
|
-
//
|
|
235
|
-
// Per sentence is also what makes the voice right. A reply that answers
|
|
236
|
-
// in English and then quotes a Chinese title is two sentences in two
|
|
237
|
-
// scripts, and each is synthesized in the voice its own script needs.
|
|
227
|
+
// knowledge: decisions/internals/speech-synthesis-picks-a-voice-per-sentence-and-warns-once.md
|
|
238
228
|
let voice = config.voice
|
|
239
229
|
if (config.speakableScripts) {
|
|
240
230
|
const unspeakable = unspeakableScripts(text, config.speakableScripts)
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { beforeEach, describe, test } from 'node:test'
|
|
2
|
+
import assert from 'node:assert/strict'
|
|
3
|
+
import { pikkuState, resetPikkuState } from '../../pikku-state.js'
|
|
4
|
+
import { gradeRun } from './ai-scorer-grade.js'
|
|
5
|
+
import { pikkuAIJudge, pikkuAIScorer } from './ai-scorer.js'
|
|
6
|
+
import type { ScoreJob } from './ai-scorer.types.js'
|
|
7
|
+
|
|
8
|
+
const job = (overrides: Partial<ScoreJob> = {}): ScoreJob => ({
|
|
9
|
+
scorerName: 'correctness',
|
|
10
|
+
runId: 'run-1',
|
|
11
|
+
agentName: 'assistant',
|
|
12
|
+
input: 'what is the capital of France?',
|
|
13
|
+
output: 'Paris',
|
|
14
|
+
toolCalls: [],
|
|
15
|
+
usage: { inputTokens: 10, outputTokens: 5 },
|
|
16
|
+
...overrides,
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
describe('gradeRun', () => {
|
|
20
|
+
beforeEach(() => resetPikkuState())
|
|
21
|
+
|
|
22
|
+
test('a scenario grade is returned and not written to the live record', async () => {
|
|
23
|
+
pikkuState(null, 'agent', 'scorers').set(
|
|
24
|
+
'correctness',
|
|
25
|
+
pikkuAIScorer({
|
|
26
|
+
name: 'correctness',
|
|
27
|
+
description: 'Matches the answer key',
|
|
28
|
+
requiresReference: true,
|
|
29
|
+
score: (input) => ({
|
|
30
|
+
score: input.output === input.reference ? 1 : 0,
|
|
31
|
+
}),
|
|
32
|
+
})
|
|
33
|
+
)
|
|
34
|
+
let saves = 0
|
|
35
|
+
|
|
36
|
+
const result = await gradeRun(
|
|
37
|
+
job({ reference: 'Paris' }),
|
|
38
|
+
{
|
|
39
|
+
aiRunState: {
|
|
40
|
+
saveScore: async () => {
|
|
41
|
+
saves++
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
{ persist: false }
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
assert.deepEqual(result, { score: 1 })
|
|
49
|
+
assert.equal(saves, 0)
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
test('a reference-based scorer sees the answer key it was given', async () => {
|
|
53
|
+
pikkuState(null, 'agent', 'scorers').set(
|
|
54
|
+
'correctness',
|
|
55
|
+
pikkuAIScorer({
|
|
56
|
+
name: 'correctness',
|
|
57
|
+
description: 'Matches the answer key',
|
|
58
|
+
requiresReference: true,
|
|
59
|
+
score: (input) => ({
|
|
60
|
+
score: input.output === input.reference ? 1 : 0,
|
|
61
|
+
}),
|
|
62
|
+
})
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
const result = await gradeRun(
|
|
66
|
+
job({ reference: 'Lyon' }),
|
|
67
|
+
{},
|
|
68
|
+
{
|
|
69
|
+
persist: false,
|
|
70
|
+
}
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
assert.equal(result.score, 0)
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
test('a judge grades without a score function, and reports the model it used', async () => {
|
|
77
|
+
pikkuState(null, 'agent', 'scorers').set(
|
|
78
|
+
'helpfulness',
|
|
79
|
+
pikkuAIJudge({
|
|
80
|
+
name: 'helpfulness',
|
|
81
|
+
description: 'Is the answer useful',
|
|
82
|
+
model: 'claude-opus-5',
|
|
83
|
+
goal: 'Grade helpfulness.',
|
|
84
|
+
})
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
const result = await gradeRun(
|
|
88
|
+
job({ scorerName: 'helpfulness' }),
|
|
89
|
+
{
|
|
90
|
+
aiAgentRunner: {
|
|
91
|
+
run: async () => ({
|
|
92
|
+
object: { score: 0.75, reason: 'Correct but terse.' },
|
|
93
|
+
usage: { inputTokens: 80, outputTokens: 20 },
|
|
94
|
+
}),
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
{ persist: false }
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
assert.equal(result.score, 0.75)
|
|
101
|
+
assert.deepEqual(result.metadata, {
|
|
102
|
+
judgeModel: 'claude-opus-5',
|
|
103
|
+
judgeTokens: 100,
|
|
104
|
+
})
|
|
105
|
+
})
|
|
106
|
+
})
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { runJudge } from './ai-scorer-judge.js'
|
|
2
|
+
import { resolveAIScorer } from './ai-scorer-registry.js'
|
|
3
|
+
import type { ScoreJob, ScorerOutput } from './ai-scorer.types.js'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Grade one run with one scorer.
|
|
7
|
+
*
|
|
8
|
+
* The single path both callers take — the lane worker on live traffic and the
|
|
9
|
+
* scenario grading RPC — so a scenario's grade is the same computation the
|
|
10
|
+
* production sampler would have made, not an approximation of it.
|
|
11
|
+
*
|
|
12
|
+
* Persisting is optional because the two callers differ on it: a live grade is
|
|
13
|
+
* only useful once recorded, while a scenario asserts on the returned value and
|
|
14
|
+
* runs against servers that may have no run-state adapter at all.
|
|
15
|
+
*/
|
|
16
|
+
export const gradeRun = async (
|
|
17
|
+
job: ScoreJob,
|
|
18
|
+
services: {
|
|
19
|
+
aiAgentRunner?: unknown
|
|
20
|
+
aiRunState?: {
|
|
21
|
+
saveScore: (score: {
|
|
22
|
+
runId: string
|
|
23
|
+
scorerName: string
|
|
24
|
+
score: number
|
|
25
|
+
reason?: string
|
|
26
|
+
metadata?: Record<string, unknown>
|
|
27
|
+
}) => Promise<void>
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
options: { persist: boolean }
|
|
31
|
+
): Promise<ScorerOutput> => {
|
|
32
|
+
const { scorerName, ...input } = job
|
|
33
|
+
const scorer = resolveAIScorer(scorerName)
|
|
34
|
+
|
|
35
|
+
const result = scorer.score
|
|
36
|
+
? await scorer.score(input, services)
|
|
37
|
+
: await runJudge(scorer, input, services.aiAgentRunner as never)
|
|
38
|
+
|
|
39
|
+
if (options.persist) {
|
|
40
|
+
if (!services.aiRunState) {
|
|
41
|
+
throw new Error(
|
|
42
|
+
`AI run state service not initialized: cannot record the '${scorerName}' grade of run ${job.runId}`
|
|
43
|
+
)
|
|
44
|
+
}
|
|
45
|
+
await services.aiRunState.saveScore({
|
|
46
|
+
runId: job.runId,
|
|
47
|
+
scorerName,
|
|
48
|
+
score: result.score,
|
|
49
|
+
...(result.reason !== undefined ? { reason: result.reason } : {}),
|
|
50
|
+
...(result.metadata !== undefined ? { metadata: result.metadata } : {}),
|
|
51
|
+
})
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return result
|
|
55
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { describe, test } from 'node:test'
|
|
2
|
+
import assert from 'node:assert/strict'
|
|
3
|
+
import { buildJudgePrompt, runJudge } from './ai-scorer-judge.js'
|
|
4
|
+
import { pikkuAIJudge } from './ai-scorer.js'
|
|
5
|
+
import type { ScorerInput } from './ai-scorer.types.js'
|
|
6
|
+
import type { AIAgentRunnerService } from '../../services/ai-agent-runner-service.js'
|
|
7
|
+
|
|
8
|
+
const input = (overrides: Partial<ScorerInput> = {}): ScorerInput => ({
|
|
9
|
+
runId: 'run-1',
|
|
10
|
+
agentName: 'assistant',
|
|
11
|
+
input: 'what is the capital of France?',
|
|
12
|
+
output: 'Paris',
|
|
13
|
+
toolCalls: [],
|
|
14
|
+
usage: { inputTokens: 10, outputTokens: 5 },
|
|
15
|
+
...overrides,
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
const runner = (
|
|
19
|
+
object: unknown,
|
|
20
|
+
seen?: { params: any }
|
|
21
|
+
): AIAgentRunnerService =>
|
|
22
|
+
({
|
|
23
|
+
run: async (params: any) => {
|
|
24
|
+
if (seen) seen.params = params
|
|
25
|
+
return { object, usage: { inputTokens: 100, outputTokens: 20 } }
|
|
26
|
+
},
|
|
27
|
+
}) as unknown as AIAgentRunnerService
|
|
28
|
+
|
|
29
|
+
describe('buildJudgePrompt', () => {
|
|
30
|
+
test('shows the answer key to a reference-based judge', () => {
|
|
31
|
+
const scorer = pikkuAIJudge({
|
|
32
|
+
name: 'correctness',
|
|
33
|
+
description: 'Is the answer right',
|
|
34
|
+
model: 'claude-opus-5',
|
|
35
|
+
goal: 'Grade correctness.',
|
|
36
|
+
requiresReference: true,
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
const prompt = buildJudgePrompt(
|
|
40
|
+
scorer.judge!,
|
|
41
|
+
input({ reference: 'Paris' })
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
assert.match(prompt, /Reference answer:\nParis/)
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
test('withholds a reference section when there is no answer key', () => {
|
|
48
|
+
const scorer = pikkuAIJudge({
|
|
49
|
+
name: 'helpfulness',
|
|
50
|
+
description: 'Is the answer useful',
|
|
51
|
+
model: 'claude-opus-5',
|
|
52
|
+
goal: 'Grade helpfulness.',
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
const prompt = buildJudgePrompt(scorer.judge!, input())
|
|
56
|
+
|
|
57
|
+
assert.doesNotMatch(prompt, /Reference answer/)
|
|
58
|
+
assert.match(prompt, /Grade helpfulness\./)
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
test('a scorer that supplies its own prompt replaces the framing entirely', () => {
|
|
62
|
+
const scorer = pikkuAIJudge({
|
|
63
|
+
name: 'custom',
|
|
64
|
+
description: 'Custom framing',
|
|
65
|
+
model: 'claude-opus-5',
|
|
66
|
+
goal: 'ignored',
|
|
67
|
+
prompt: (run) => `Only this: ${run.output}`,
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
assert.equal(buildJudgePrompt(scorer.judge!, input()), 'Only this: Paris')
|
|
71
|
+
})
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
describe('runJudge', () => {
|
|
75
|
+
const helpfulness = pikkuAIJudge({
|
|
76
|
+
name: 'helpfulness',
|
|
77
|
+
description: 'Is the answer useful',
|
|
78
|
+
model: 'claude-opus-5',
|
|
79
|
+
goal: 'Grade helpfulness.',
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
test('records the score, the reason and what the judgement cost', async () => {
|
|
83
|
+
const result = await runJudge(
|
|
84
|
+
helpfulness,
|
|
85
|
+
input(),
|
|
86
|
+
runner({ score: 0.8, reason: 'Direct and correct.' })
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
assert.equal(result.score, 0.8)
|
|
90
|
+
assert.equal(result.reason, 'Direct and correct.')
|
|
91
|
+
assert.deepEqual(result.metadata, {
|
|
92
|
+
judgeModel: 'claude-opus-5',
|
|
93
|
+
judgeTokens: 120,
|
|
94
|
+
})
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
test('clamps an out-of-range score rather than losing the judgement', async () => {
|
|
98
|
+
const high = await runJudge(helpfulness, input(), runner({ score: 1.4 }))
|
|
99
|
+
const low = await runJudge(helpfulness, input(), runner({ score: -2 }))
|
|
100
|
+
|
|
101
|
+
assert.equal(high.score, 1)
|
|
102
|
+
assert.equal(low.score, 0)
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
test('rejects a non-numeric score, which is not a judgement at all', async () => {
|
|
106
|
+
await assert.rejects(
|
|
107
|
+
() => runJudge(helpfulness, input(), runner({ score: 'great' })),
|
|
108
|
+
/non-numeric score/
|
|
109
|
+
)
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
test('gives the judge no tools, so its structured output is actually honoured', async () => {
|
|
113
|
+
const seen = { params: undefined as any }
|
|
114
|
+
await runJudge(helpfulness, input(), runner({ score: 1 }, seen))
|
|
115
|
+
|
|
116
|
+
assert.deepEqual(seen.params.tools, [])
|
|
117
|
+
assert.equal(seen.params.toolChoice, 'none')
|
|
118
|
+
assert.equal(seen.params.maxSteps, 1)
|
|
119
|
+
assert.equal(seen.params.model, 'claude-opus-5')
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
test('names the missing provider rather than failing as an undefined call', async () => {
|
|
123
|
+
await assert.rejects(
|
|
124
|
+
() => runJudge(helpfulness, input(), undefined),
|
|
125
|
+
/needs an AI provider/
|
|
126
|
+
)
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
test('refuses to grade a reference-based judge with no answer key', async () => {
|
|
130
|
+
const correctness = pikkuAIJudge({
|
|
131
|
+
name: 'correctness',
|
|
132
|
+
description: 'Is the answer right',
|
|
133
|
+
model: 'claude-opus-5',
|
|
134
|
+
goal: 'Grade correctness.',
|
|
135
|
+
requiresReference: true,
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
await assert.rejects(
|
|
139
|
+
() => runJudge(correctness, input(), runner({ score: 1 })),
|
|
140
|
+
/grades against a reference answer/
|
|
141
|
+
)
|
|
142
|
+
})
|
|
143
|
+
})
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import type { AIAgentRunnerService } from '../../services/ai-agent-runner-service.js'
|
|
2
|
+
import type { AIMessage } from '../ai-agent/ai-agent.types.js'
|
|
3
|
+
import { randomUUID } from '../ai-agent/ai-agent-utils.js'
|
|
4
|
+
import type {
|
|
5
|
+
PikkuAIScorer,
|
|
6
|
+
ScorerInput,
|
|
7
|
+
ScorerJudgeConfig,
|
|
8
|
+
ScorerOutput,
|
|
9
|
+
} from './ai-scorer.types.js'
|
|
10
|
+
|
|
11
|
+
const JUDGE_OUTPUT_SCHEMA = {
|
|
12
|
+
type: 'object',
|
|
13
|
+
properties: {
|
|
14
|
+
score: {
|
|
15
|
+
type: 'number',
|
|
16
|
+
minimum: 0,
|
|
17
|
+
maximum: 1,
|
|
18
|
+
description: 'How well the answer meets the rubric. 1 is best.',
|
|
19
|
+
},
|
|
20
|
+
reason: {
|
|
21
|
+
type: 'string',
|
|
22
|
+
description: 'One or two sentences explaining the score.',
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
required: ['score', 'reason'],
|
|
26
|
+
additionalProperties: false,
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* The message a judge grades when its scorer supplies no `prompt`.
|
|
31
|
+
*
|
|
32
|
+
* A reference-based judge is shown the answer key; a reference-free one is not,
|
|
33
|
+
* and grades the answer on its own merits.
|
|
34
|
+
*/
|
|
35
|
+
export const buildJudgePrompt = (
|
|
36
|
+
judge: ScorerJudgeConfig,
|
|
37
|
+
input: ScorerInput
|
|
38
|
+
): string => {
|
|
39
|
+
if (judge.prompt) return judge.prompt(input)
|
|
40
|
+
|
|
41
|
+
const sections = [`User asked:\n${input.input}`]
|
|
42
|
+
if (input.reference !== undefined) {
|
|
43
|
+
sections.push(`Reference answer:\n${input.reference}`)
|
|
44
|
+
}
|
|
45
|
+
sections.push(`Assistant answered:\n${input.output}`)
|
|
46
|
+
sections.push(judge.goal)
|
|
47
|
+
sections.push('Respond with a score 0..1 and a brief reason.')
|
|
48
|
+
return sections.join('\n\n')
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Clamp rather than reject: a model that answers 1.2 has still made a legible
|
|
53
|
+
* judgement, and failing the job would lose it. A non-number has not.
|
|
54
|
+
*/
|
|
55
|
+
const normalizeScore = (scorerName: string, value: unknown): number => {
|
|
56
|
+
if (typeof value !== 'number' || Number.isNaN(value)) {
|
|
57
|
+
throw new Error(
|
|
58
|
+
`Judge '${scorerName}' returned a non-numeric score: ${JSON.stringify(value)}`
|
|
59
|
+
)
|
|
60
|
+
}
|
|
61
|
+
return Math.min(1, Math.max(0, value))
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Run an LLM judge and force the `{ score, reason }` contract, so a scorer
|
|
66
|
+
* author writes a rubric rather than a parser.
|
|
67
|
+
*
|
|
68
|
+
* A judge has no tools, which is the one case where an output schema is
|
|
69
|
+
* reliably honoured.
|
|
70
|
+
*/
|
|
71
|
+
export const runJudge = async (
|
|
72
|
+
scorer: PikkuAIScorer<any>,
|
|
73
|
+
input: ScorerInput,
|
|
74
|
+
aiAgentRunner: AIAgentRunnerService | undefined
|
|
75
|
+
): Promise<ScorerOutput> => {
|
|
76
|
+
const judge = scorer.judge
|
|
77
|
+
if (!judge) {
|
|
78
|
+
throw new Error(`Scorer '${scorer.name}' is not a judge`)
|
|
79
|
+
}
|
|
80
|
+
if (!aiAgentRunner) {
|
|
81
|
+
throw new Error(
|
|
82
|
+
`Judge '${scorer.name}' needs an AI provider, but no aiAgentRunner is registered in this process. ` +
|
|
83
|
+
`A worker deployed apart from the API has to register one to run the slow scoring lane.`
|
|
84
|
+
)
|
|
85
|
+
}
|
|
86
|
+
if (scorer.requiresReference && input.reference === undefined) {
|
|
87
|
+
throw new Error(
|
|
88
|
+
`Judge '${scorer.name}' grades against a reference answer, but none was supplied`
|
|
89
|
+
)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const messages: AIMessage[] = [
|
|
93
|
+
{
|
|
94
|
+
id: randomUUID(),
|
|
95
|
+
role: 'user',
|
|
96
|
+
content: buildJudgePrompt(judge, input),
|
|
97
|
+
createdAt: new Date(),
|
|
98
|
+
},
|
|
99
|
+
]
|
|
100
|
+
|
|
101
|
+
const result = await aiAgentRunner.run({
|
|
102
|
+
model: judge.model,
|
|
103
|
+
instructions: judge.goal,
|
|
104
|
+
messages,
|
|
105
|
+
tools: [],
|
|
106
|
+
maxSteps: 1,
|
|
107
|
+
toolChoice: 'none',
|
|
108
|
+
outputSchema: JUDGE_OUTPUT_SCHEMA,
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
const object = (result.object ?? {}) as { score?: unknown; reason?: unknown }
|
|
112
|
+
return {
|
|
113
|
+
score: normalizeScore(scorer.name, object.score),
|
|
114
|
+
...(typeof object.reason === 'string' ? { reason: object.reason } : {}),
|
|
115
|
+
metadata: {
|
|
116
|
+
judgeModel: judge.model,
|
|
117
|
+
judgeTokens: result.usage.inputTokens + result.usage.outputTokens,
|
|
118
|
+
},
|
|
119
|
+
}
|
|
120
|
+
}
|