@pikku/core 0.12.84 → 0.12.86
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 +424 -0
- package/dist/bootstrap-compat/root.d.ts +20 -0
- package/dist/classification/column-form.d.ts +32 -0
- package/dist/classification/column-form.js +42 -0
- package/dist/classification/data-classification.d.ts +64 -0
- package/dist/classification/index.d.ts +13 -0
- package/dist/classification/index.js +2 -0
- package/dist/classification/secret-value.d.ts +56 -0
- package/dist/classification/secret-value.js +46 -0
- package/dist/crypto-utils.d.ts +1 -1
- package/dist/ecosystem/agent-scorer.d.ts +1 -1
- package/dist/ecosystem/agent.d.ts +4 -1
- package/dist/ecosystem/channel/local.d.ts +2 -0
- package/dist/ecosystem/channel/local.js +2 -0
- package/dist/ecosystem/channel/serverless.d.ts +9 -0
- package/dist/ecosystem/channel/serverless.js +1 -0
- package/dist/ecosystem/channel.d.ts +3 -0
- package/dist/ecosystem/channel.js +1 -0
- package/dist/ecosystem/cli.d.ts +1 -1
- package/dist/ecosystem/cli.js +1 -1
- package/dist/ecosystem/credential.d.ts +1 -0
- package/dist/ecosystem/crypto-utils.d.ts +8 -0
- package/dist/ecosystem/crypto-utils.js +1 -0
- package/dist/ecosystem/dev.d.ts +1 -0
- package/dist/ecosystem/dev.js +1 -0
- package/{src/ecosystem/testing.ts → dist/ecosystem/errors.d.ts} +3 -3
- package/dist/ecosystem/errors.js +2 -0
- package/dist/ecosystem/function.d.ts +1 -1
- package/dist/ecosystem/function.js +1 -1
- package/dist/ecosystem/http.d.ts +3 -1
- package/dist/ecosystem/http.js +3 -1
- package/dist/ecosystem/mcp.d.ts +1 -1
- package/dist/ecosystem/mcp.js +1 -1
- package/dist/ecosystem/middleware.d.ts +2 -1
- package/dist/ecosystem/middleware.js +2 -1
- package/dist/ecosystem/persona.d.ts +4 -2
- package/dist/ecosystem/persona.js +2 -1
- package/dist/ecosystem/queue.d.ts +4 -2
- package/dist/ecosystem/queue.js +3 -1
- package/dist/ecosystem/role.d.ts +1 -0
- package/dist/ecosystem/rpc.d.ts +2 -0
- package/dist/ecosystem/rpc.js +1 -0
- package/dist/ecosystem/scenario.d.ts +8 -2
- package/dist/ecosystem/scenario.js +5 -1
- package/dist/ecosystem/scheduler.d.ts +2 -1
- package/dist/ecosystem/scheduler.js +2 -1
- package/dist/ecosystem/schema.d.ts +2 -1
- package/dist/ecosystem/schema.js +1 -1
- package/dist/ecosystem/scope.d.ts +2 -0
- package/dist/ecosystem/scope.js +1 -0
- package/dist/ecosystem/secret.d.ts +2 -0
- package/dist/ecosystem/secret.js +1 -0
- package/dist/ecosystem/services/local-content.d.ts +2 -1
- package/dist/ecosystem/services/local-content.js +1 -1
- package/dist/ecosystem/services/local-meta.d.ts +1 -0
- package/dist/ecosystem/services/local-meta.js +1 -0
- package/dist/ecosystem/services.d.ts +11 -3
- package/dist/ecosystem/services.js +7 -1
- package/dist/ecosystem/trigger.d.ts +1 -0
- package/dist/ecosystem/trigger.js +1 -1
- package/dist/ecosystem/types.d.ts +6 -3
- package/dist/ecosystem/types.js +3 -2
- package/dist/ecosystem/variable.d.ts +1 -0
- package/dist/ecosystem/virtual-user.d.ts +3 -0
- package/dist/ecosystem/virtual-user.js +3 -0
- package/dist/ecosystem/workflow.d.ts +2 -0
- package/dist/ecosystem/workflow.js +2 -0
- package/dist/errors/index.d.ts +8 -0
- package/dist/errors/index.js +1 -0
- package/dist/errors/serialized-error.d.ts +7 -0
- package/dist/function/function-meta.types.d.ts +139 -0
- package/dist/function/function-runner.d.ts +3 -2
- package/dist/function/function-runner.js +31 -11
- package/dist/function/functions.types.d.ts +10 -2
- package/dist/function/index.d.ts +8 -2
- package/dist/function/index.js +4 -1
- package/dist/function/pikku-request.d.ts +8 -0
- package/dist/function/pikku-request.js +15 -0
- package/dist/middleware/auth-apikey.d.ts +2 -10
- package/dist/middleware/auth-apikey.js +1 -1
- package/dist/middleware/auth-bearer.d.ts +2 -10
- package/dist/middleware/auth-bearer.js +1 -1
- package/dist/middleware/auth-cookie.d.ts +2 -10
- package/dist/middleware/auth-cookie.js +1 -1
- package/dist/middleware/cors.d.ts +5 -11
- package/dist/middleware/cors.js +13 -6
- package/dist/middleware/index.d.ts +3 -0
- package/dist/middleware/index.js +1 -0
- package/dist/middleware/middleware-factories.d.ts +9 -0
- package/dist/middleware/middleware-factories.js +19 -0
- package/dist/middleware/middleware.types.d.ts +27 -0
- package/dist/middleware/middleware.types.js +1 -0
- package/dist/middleware/remote-auth.d.ts +2 -2
- package/dist/middleware/remote-auth.js +1 -1
- package/dist/middleware/telemetry.d.ts +4 -20
- package/dist/middleware/telemetry.js +1 -1
- package/dist/middleware-runner.d.ts +2 -1
- package/dist/pikku-state.d.ts +2 -1
- package/dist/services/audit-service.d.ts +1 -1
- package/dist/services/credential-wire-service.js +1 -1
- package/dist/services/email-service.d.ts +1 -1
- package/dist/services/file-scenario-run-store.d.ts +58 -0
- package/dist/services/file-scenario-run-store.js +214 -0
- package/dist/services/in-memory-workflow-service.d.ts +1 -1
- package/dist/services/index.d.ts +5 -1
- package/dist/services/index.js +3 -0
- package/dist/services/local-secrets.d.ts +1 -1
- package/dist/services/local-secrets.js +1 -1
- package/dist/services/logger.d.ts +7 -7
- package/dist/services/meta-service.d.ts +2 -1
- package/dist/services/scoped-secret-service.d.ts +1 -1
- package/dist/services/secret-service.d.ts +1 -1
- package/dist/services/typed-secret-service.d.ts +16 -2
- package/dist/services/typed-secret-service.js +5 -0
- package/dist/services/webhook-service.d.ts +1 -1
- package/dist/services/workflow-service.d.ts +2 -1
- package/dist/testing/index.d.ts +1 -0
- package/dist/testing/index.js +1 -0
- package/dist/types/core.types.d.ts +30 -180
- package/dist/types/core.types.js +1 -19
- package/dist/types/index.d.ts +2 -0
- package/dist/types/index.js +1 -0
- package/dist/types/state.types.d.ts +3 -1
- package/dist/utils.d.ts +13 -0
- package/dist/utils.js +2 -0
- package/dist/wirings/addon/addon-runner.d.ts +17 -0
- package/dist/wirings/addon/addon-runner.js +150 -0
- package/dist/wirings/addon/index.d.ts +6 -0
- package/dist/wirings/addon/index.js +3 -0
- package/dist/wirings/addon/remote-addon-auth.d.ts +16 -0
- package/dist/wirings/addon/remote-addon-auth.js +39 -0
- package/dist/wirings/addon/wire-addon.d.ts +80 -0
- package/dist/wirings/addon/wire-addon.js +120 -0
- package/dist/wirings/addon/wire-remote-addon.d.ts +22 -0
- package/dist/wirings/addon/wire-remote-addon.js +11 -0
- package/dist/wirings/agent/agent-prepare.d.ts +3 -3
- package/dist/wirings/agent/agent-prepare.js +1 -1
- package/dist/wirings/agent/agent-runner.js +2 -3
- package/dist/wirings/agent/agent-stream.js +2 -2
- package/dist/wirings/agent/agent-utils.d.ts +13 -0
- package/dist/wirings/agent/agent-utils.js +16 -0
- package/dist/wirings/agent/agent.types.d.ts +3 -2
- package/dist/wirings/agent/index.d.ts +1 -1
- package/dist/wirings/agent/voice-input.js +1 -1
- package/dist/wirings/agent/voice-output.js +1 -1
- package/dist/wirings/agent-scorer/agent-scorer-judge.d.ts +13 -0
- package/dist/wirings/agent-scorer/agent-scorer-judge.js +52 -0
- package/dist/wirings/agent-scorer/agent-scorer.d.ts +7 -1
- package/dist/wirings/agent-scorer/agent-scorer.js +1 -0
- package/dist/wirings/agent-scorer/agent-scorer.types.d.ts +20 -0
- package/dist/wirings/agent-scorer/index.d.ts +1 -1
- package/dist/wirings/channel/channel-common.d.ts +2 -1
- package/dist/wirings/channel/channel-middleware-runner.d.ts +2 -1
- package/dist/wirings/channel/channel.types.d.ts +1 -1
- package/dist/wirings/channel/serverless/index.d.ts +1 -0
- package/dist/wirings/cli/channel/index.d.ts +1 -1
- package/dist/wirings/cli/cli-runner.d.ts +2 -1
- package/dist/wirings/cli/cli-runner.js +4 -1
- package/dist/wirings/cli/cli.types.d.ts +21 -2
- package/dist/wirings/cli/command-parser.js +157 -23
- package/dist/wirings/gateway/gateway.types.d.ts +2 -1
- package/dist/wirings/http/http-runner.d.ts +1 -1
- package/dist/wirings/http/http-runner.js +1 -1
- package/dist/wirings/http/http.types.d.ts +2 -2
- package/dist/wirings/http/index.d.ts +1 -0
- package/dist/wirings/http/index.js +1 -0
- package/dist/wirings/http/pikku-fetch-http-request.js +1 -1
- package/dist/wirings/http/pikku-fetch-http-response.js +2 -2
- package/dist/wirings/mcp/mcp.types.d.ts +1 -1
- package/dist/wirings/persona/index.d.ts +1 -1
- package/dist/wirings/persona/index.js +1 -1
- package/dist/wirings/persona/persona.types.d.ts +14 -0
- package/dist/wirings/queue/queue.types.d.ts +1 -1
- package/dist/wirings/queue/signed-queue-service.d.ts +1 -1
- package/dist/wirings/rpc/index.d.ts +0 -6
- package/dist/wirings/rpc/index.js +0 -3
- package/dist/wirings/rpc/remote-addon-auth.d.ts +1 -1
- package/dist/wirings/rpc/remote-addon-auth.js +1 -1
- package/dist/wirings/rpc/rpc-runner.js +2 -2
- package/dist/wirings/rpc/wire-addon.d.ts +1 -1
- package/dist/wirings/scheduler/scheduler.types.d.ts +2 -1
- package/dist/wirings/scope/index.d.ts +1 -0
- package/dist/wirings/scope/index.js +1 -0
- package/dist/wirings/secret/secret.types.d.ts +10 -0
- package/dist/wirings/secret/validate-secret-definitions.js +2 -0
- package/dist/wirings/variable/validate-variable-definitions.js +2 -0
- package/dist/wirings/variable/variable.types.d.ts +9 -0
- package/dist/wirings/virtual-user/index.d.ts +1 -1
- package/dist/wirings/virtual-user/index.js +1 -1
- package/dist/wirings/virtual-user/prepare-virtual-user-run.d.ts +1 -1
- package/dist/wirings/virtual-user/virtual-user-derive.d.ts +1 -1
- package/dist/wirings/workflow/graph/graph-node.d.ts +9 -2
- package/dist/wirings/workflow/graph/graph-node.js +2 -0
- package/dist/wirings/workflow/graph/graph-runner.d.ts +1 -0
- package/dist/wirings/workflow/graph/graph-runner.js +283 -31
- package/dist/wirings/workflow/graph/workflow-graph.types.d.ts +16 -1
- package/dist/wirings/workflow/index.d.ts +2 -0
- package/dist/wirings/workflow/index.js +1 -0
- package/dist/wirings/workflow/pikku-scenario-service.d.ts +14 -4
- package/dist/wirings/workflow/pikku-scenario-service.js +44 -16
- package/dist/wirings/workflow/pikku-workflow-service.d.ts +2 -1
- package/dist/wirings/workflow/pikku-workflow-service.js +3 -4
- package/dist/wirings/workflow/run-timeline.d.ts +1 -1
- package/dist/wirings/workflow/scenario-run.types.d.ts +171 -0
- package/dist/wirings/workflow/scenario-run.types.js +1 -0
- package/dist/wirings/workflow/scenario-step-guards.d.ts +1 -2
- package/dist/wirings/workflow/scenario-step-guards.js +0 -8
- package/dist/wirings/workflow/scenario-step.types.d.ts +27 -12
- package/dist/wirings/workflow/workflow-approval.js +2 -1
- package/dist/wirings/workflow/workflow-run-engine.types.d.ts +2 -1
- package/dist/wirings/workflow/workflow.types.d.ts +2 -1
- package/knowledge/decisions/index.md +2 -0
- package/knowledge/decisions/internals/a-scenario-step-template-is-offered-unfilled.md +1 -1
- package/knowledge/decisions/internals/a-secret-that-fails-to-decrypt-fails-the-whole-read.md +1 -1
- package/knowledge/decisions/internals/a-virtual-user-run-is-not-a-workflow-and-not-a-queued-job.md +2 -2
- package/knowledge/decisions/internals/a-wall-clock-threshold-is-a-load-test-in-disguise.md +6 -6
- package/knowledge/decisions/internals/a-workflow-wire-is-built-from-the-run-not-from-the-rpc-service.md +1 -1
- package/knowledge/decisions/internals/addon-singleton-services-are-cached-per-namespace-not-per-package.md +1 -1
- package/knowledge/decisions/internals/addon-workflow-names-are-prefixed-with-the-consumer-namespace.md +1 -1
- package/knowledge/decisions/internals/an-addon-scope-root-loses-to-a-root-the-host-already-declares.md +1 -1
- package/knowledge/decisions/internals/channel-message-handlers-accept-three-config-shapes.md +1 -1
- package/knowledge/decisions/internals/channel-middleware-caches-only-statically-resolved-middleware.md +1 -1
- package/knowledge/decisions/internals/channel-user-id-is-persisted-after-onconnect-middleware-runs.md +1 -1
- package/knowledge/decisions/internals/cli-option-names-are-camelcase-in-state-and-kebab-on-the-command-line.md +1 -1
- package/knowledge/decisions/internals/cli-parse-errors-are-routed-by-message-prefix.md +1 -1
- package/knowledge/decisions/internals/cli-unknown-long-options-warn-instead-of-failing.md +1 -1
- package/knowledge/decisions/internals/core-column-form-is-an-axis-of-its-own.md +4 -4
- package/knowledge/decisions/internals/core-data-classification-brand-is-an-optional-property.md +1 -1
- package/knowledge/decisions/internals/core-function-runner-restores-the-wire-fields-it-overwrites.md +3 -3
- package/knowledge/decisions/internals/core-hot-reload-merges-generated-meta-never-replaces-it.md +2 -2
- package/knowledge/decisions/internals/core-hot-reload-owns-its-module-registry.md +2 -2
- package/knowledge/decisions/internals/core-middleware-order-is-scope-then-priority.md +1 -1
- package/knowledge/decisions/internals/core-schema-defaults-apply-on-every-transport.md +1 -1
- package/knowledge/decisions/internals/core-scopes-are-an-and-gate-separate-from-permissions.md +3 -3
- package/knowledge/decisions/internals/gateway-adapters-resolve-lazily-and-are-promise-cached.md +1 -1
- package/knowledge/decisions/internals/gateway-listener-middleware-runs-without-an-rpc-on-the-wire.md +4 -4
- package/knowledge/decisions/internals/gateway-wiring-is-a-meta-wiring-over-http-and-channels.md +1 -1
- package/knowledge/decisions/internals/http-router-matches-normalized-paths-but-returns-registered-ones.md +1 -1
- package/knowledge/decisions/internals/in-a-scenario-a-4xx-is-data-not-an-exception.md +1 -1
- package/knowledge/decisions/internals/in-memory-workflow-history-aliases-the-live-step-object.md +1 -1
- package/knowledge/decisions/internals/index.md +4 -2
- package/knowledge/decisions/internals/istanbul-statement-counts-attach-to-the-start-line-only.md +1 -1
- package/knowledge/decisions/internals/one-project-shape-check-two-validators.md +1 -1
- package/knowledge/decisions/internals/queue-jobs-always-carry-an-explicit-attempts-count.md +1 -1
- package/knowledge/decisions/internals/remote-addons-dispatch-over-http-instead-of-local-meta.md +1 -1
- package/knowledge/decisions/internals/rpc-names-resolve-through-package-scope-before-root.md +1 -1
- package/knowledge/decisions/internals/scenario-given-and-when-are-sugar-but-then-is-not.md +2 -2
- package/knowledge/decisions/internals/scenarios-live-in-files-named-for-them.md +5 -5
- package/knowledge/decisions/internals/the-actor-prompt-says-json-because-of-json-object-mode.md +1 -1
- package/knowledge/decisions/internals/the-api-report-pins-members-not-just-names.md +1 -1
- package/knowledge/decisions/internals/the-ecosystem-entry-point-carries-the-adapter-surface.md +43 -19
- package/knowledge/decisions/internals/the-persona-runtime-is-exported-from-the-persona-entry-point.md +1 -1
- package/knowledge/decisions/internals/the-worker-disposition-is-the-one-that-is-not-testing.md +1 -1
- package/knowledge/decisions/internals/validate-runs-checks-by-precondition.md +3 -3
- package/knowledge/decisions/internals/virtual-user-step-order-comes-from-insertion-order.md +1 -1
- package/knowledge/decisions/internals/webhook-delivery-history-records-every-attempt-best-effort.md +1 -1
- package/knowledge/decisions/internals/wiring-registries-erase-the-generics-their-wire-functions-capture.md +1 -1
- package/knowledge/decisions/internals/workflow-approval-expiry-is-decided-from-a-recorded-deadline.md +1 -1
- package/knowledge/decisions/internals/workflow-core-never-imports-a-browser-driver.md +1 -1
- package/knowledge/decisions/internals/workflow-inline-runs-report-their-run-id-before-they-can-fail.md +1 -1
- package/knowledge/decisions/internals/workflow-invocation-id-is-the-dedupe-key-not-step-id.md +1 -1
- package/knowledge/decisions/internals/workflow-run-capabilities-are-extensions-not-subclasses.md +1 -1
- package/knowledge/decisions/internals/workflow-run-timeline-is-a-pure-fold-over-durable-history.md +1 -1
- package/knowledge/decisions/internals/workflow-scenario-assertions-never-retry-and-record-one-step.md +9 -5
- package/knowledge/decisions/internals/workflow-scenario-hooks-are-a-scenario-only-affordance.md +1 -1
- package/knowledge/decisions/internals/workflow-step-rpc-name-is-provenance-only.md +2 -2
- package/knowledge/decisions/internals/workflow-suspend-and-approval-reasons-are-durable-step-identities.md +1 -1
- package/knowledge/decisions/security/a-function-never-receives-the-secret-service.md +1 -1
- package/knowledge/decisions/security/a-step-runs-the-function-the-workflow-dispatched-it-with.md +1 -1
- package/knowledge/decisions/security/a-virtual-user-is-never-offered-a-step-that-would-forge-its-own-oracle.md +2 -3
- package/knowledge/decisions/security/actor-sign-in-is-proven-by-set-cookie-not-a-non-empty-jar.md +1 -1
- package/knowledge/decisions/security/addon-auth-and-tags-only-tighten.md +1 -1
- package/knowledge/decisions/security/agent-gate-requires-a-session-only-when-auth-is-true.md +1 -1
- package/knowledge/decisions/security/an-approval-answer-outlives-the-run-it-answered.md +3 -3
- package/knowledge/decisions/security/console-addon-privileged-functions-gate-themselves.md +4 -3
- package/knowledge/decisions/security/core-safe-fetch-blocks-ssrf-by-host-literal-not-dns.md +1 -1
- package/knowledge/decisions/security/gateway-middleware-sessions-must-be-bridged-onto-the-wire.md +2 -2
- package/knowledge/decisions/security/global-permissions-and-function-permissions-are-independent-gates.md +1 -1
- package/knowledge/decisions/security/http-request-bodies-are-bounded-before-they-are-buffered.md +2 -2
- package/knowledge/decisions/security/index.md +2 -0
- package/knowledge/decisions/security/mcp-internal-error-details-are-double-gated-on-production.md +1 -1
- package/knowledge/decisions/security/queue-jobs-carry-the-producers-pikku-user-id.md +3 -3
- package/knowledge/decisions/security/remote-addon-tokens-are-client-credentials-not-mesh-trust.md +1 -1
- package/knowledge/decisions/security/scaffold-features-are-authenticated-unless-opted-out.md +2 -2
- package/knowledge/decisions/security/self-authentication-is-declared-not-detected.md +1 -1
- package/knowledge/decisions/security/signed-content-urls-bind-the-request-path.md +2 -2
- package/knowledge/index.md +3 -1
- package/knowledge/questions/channel-middleware-accepts-bare-factories-that-nothing-resolves.md +1 -1
- package/knowledge/questions/index.md +2 -0
- package/package.json +14 -13
- package/run-tests.sh +0 -0
- package/scripts/generate-api-report.d.mts +1 -1
- package/scripts/generate-api-report.mjs +67 -60
- package/scripts/generate-api-report.mts +11 -45
- package/src/api-report.test.ts +70 -1
- package/src/app-alias-surface.test.ts +179 -0
- package/src/app-leaf-surface.test.ts +217 -0
- package/src/bootstrap-compat/root.ts +20 -0
- package/src/{column-form.test.ts → classification/column-form.test.ts} +2 -2
- package/src/{data-classification.ts → classification/data-classification.ts} +1 -5
- package/src/classification/index.ts +38 -0
- package/src/{secret-value.test.ts → classification/secret-value.test.ts} +2 -2
- package/src/{secret-value.ts → classification/secret-value.ts} +1 -6
- package/src/crypto-utils.ts +1 -1
- package/src/dev/reload-meta.test.ts +1 -1
- package/src/errors/index.ts +9 -0
- package/src/errors/serialized-error.ts +9 -0
- package/src/factory-functions.test.ts +4 -2
- package/src/function/function-meta.types.ts +151 -0
- package/src/function/function-runner.test.ts +4 -2
- package/src/function/function-runner.ts +47 -14
- package/src/function/functions.types.ts +11 -7
- package/src/function/index.ts +24 -5
- package/src/function/list.types.ts +1 -3
- package/src/middleware/auth-apikey.ts +4 -2
- package/src/middleware/auth-bearer.test.ts +1 -1
- package/src/middleware/auth-bearer.ts +4 -2
- package/src/middleware/auth-cookie.ts +4 -1
- package/src/middleware/cors.test.ts +126 -5
- package/src/middleware/cors.ts +19 -8
- package/src/middleware/index.ts +16 -0
- package/src/middleware/middleware-factories.ts +62 -0
- package/src/middleware/middleware.types.ts +61 -0
- package/src/middleware/remote-auth.test.ts +1 -1
- package/src/middleware/remote-auth.ts +1 -1
- package/src/middleware/telemetry.ts +4 -2
- package/src/middleware-runner.test.ts +2 -3
- package/src/middleware-runner.ts +2 -2
- package/src/no-root-barrel.test.ts +114 -0
- package/src/permissions.ts +2 -4
- package/src/pikku-state.ts +3 -1
- package/src/public-surface.json +56 -241
- package/src/public-surface.json.README +3 -3
- package/src/public-surface.test.ts +5 -5
- package/src/remote.test.ts +1 -1
- package/src/removed-legacy-exports.test.ts +5 -1
- package/src/services/audit-service.ts +1 -1
- package/src/services/credential-wire-service.ts +1 -1
- package/src/services/email-service.ts +2 -4
- package/src/services/file-scenario-run-store.test.ts +349 -0
- package/src/services/file-scenario-run-store.ts +282 -0
- package/src/services/in-memory-workflow-service.ts +1 -1
- package/src/services/index.ts +13 -1
- package/src/services/local-content-request-handler.ts +1 -2
- package/src/services/local-secrets.ts +1 -1
- package/src/services/logger.ts +7 -7
- package/src/services/meta-service.ts +2 -2
- package/src/services/queue-webhook-service.test.ts +1 -1
- package/src/services/scoped-secret-service.ts +1 -1
- package/src/services/secret-service.ts +1 -1
- package/src/services/typed-secret-service.test.ts +52 -0
- package/src/services/typed-secret-service.ts +26 -2
- package/src/services/webhook-service.ts +1 -1
- package/src/services/workflow-service.ts +2 -1
- package/src/template-alias-surface.test.ts +240 -0
- package/src/testing/index.ts +1 -0
- package/src/types/core.types.ts +33 -285
- package/src/types/index.ts +2 -0
- package/src/types/state.types.ts +6 -5
- package/src/types/typed-credentials.test.ts +72 -0
- package/src/utils.ts +34 -0
- package/src/wirings/{rpc → addon}/addon-scopes.test.ts +1 -1
- package/src/wirings/addon/index.ts +12 -0
- package/src/wirings/{rpc → addon}/remote-addon-auth.ts +4 -1
- package/src/wirings/{rpc → addon}/wire-addon.ts +37 -2
- package/src/wirings/agent/agent-prepare.ts +8 -5
- package/src/wirings/agent/agent-runner.ts +2 -3
- package/src/wirings/agent/agent-stream.ts +2 -3
- package/src/wirings/agent/agent-utils.ts +19 -0
- package/src/wirings/agent/agent.types.ts +3 -3
- package/src/wirings/agent/index.ts +1 -0
- package/src/wirings/agent/voice-input.ts +1 -1
- package/src/wirings/agent/voice-output.test.ts +2 -4
- package/src/wirings/agent/voice-output.ts +1 -1
- package/src/wirings/agent-scorer/agent-scorer-judge.test.ts +145 -0
- package/src/wirings/agent-scorer/agent-scorer-judge.ts +60 -0
- package/src/wirings/agent-scorer/agent-scorer.ts +8 -0
- package/src/wirings/agent-scorer/agent-scorer.types.ts +21 -0
- package/src/wirings/agent-scorer/index.ts +1 -0
- package/src/wirings/channel/channel-common.ts +4 -2
- package/src/wirings/channel/channel-handler.ts +2 -5
- package/src/wirings/channel/channel-middleware-runner.ts +1 -1
- package/src/wirings/channel/channel-rpc.types.ts +1 -5
- package/src/wirings/channel/channel.types.ts +1 -2
- package/src/wirings/channel/local/local-channel-runner.test.ts +1 -2
- package/src/wirings/channel/serverless/index.ts +1 -0
- package/src/wirings/cli/channel/cli-raw-channel-runner.ts +2 -2
- package/src/wirings/cli/channel/index.ts +1 -3
- package/src/wirings/cli/cli-runner.test.ts +1 -1
- package/src/wirings/cli/cli-runner.ts +11 -7
- package/src/wirings/cli/cli.types.ts +24 -3
- package/src/wirings/cli/command-parser.test.ts +388 -1
- package/src/wirings/cli/command-parser.ts +189 -29
- package/src/wirings/gateway/gateway-runner.test.ts +1 -2
- package/src/wirings/gateway/gateway-runner.ts +3 -6
- package/src/wirings/gateway/gateway.types.ts +5 -4
- package/src/wirings/http/http-routes.test.ts +1 -2
- package/src/wirings/http/http-runner-addon-ref.test.ts +238 -0
- package/src/wirings/http/http-runner.test.ts +3 -8
- package/src/wirings/http/http-runner.ts +9 -3
- package/src/wirings/http/http.types.ts +3 -12
- package/src/wirings/http/index.ts +1 -0
- package/src/wirings/http/pikku-fetch-http-request.ts +1 -1
- package/src/wirings/http/pikku-fetch-http-response.ts +3 -2
- package/src/wirings/mcp/mcp-runner.test.ts +1 -1
- package/src/wirings/mcp/mcp.types.ts +1 -2
- package/src/wirings/persona/index.ts +1 -4
- package/src/wirings/persona/persona.types.ts +14 -0
- package/src/wirings/queue/queue-identity.test.ts +1 -1
- package/src/wirings/queue/queue.types.ts +2 -6
- package/src/wirings/queue/signed-queue-service.ts +1 -1
- package/src/wirings/rpc/index.ts +0 -12
- package/src/wirings/rpc/rpc-runner.test.ts +4 -4
- package/src/wirings/rpc/rpc-runner.ts +3 -3
- package/src/wirings/scheduler/scheduler.types.ts +2 -5
- package/src/wirings/scope/index.ts +1 -0
- package/src/wirings/secret/secret.types.ts +10 -0
- package/src/wirings/secret/validate-secret-definitions.test.ts +23 -0
- package/src/wirings/secret/validate-secret-definitions.ts +2 -0
- package/src/wirings/variable/validate-variable-definitions.test.ts +23 -0
- package/src/wirings/variable/validate-variable-definitions.ts +2 -0
- package/src/wirings/variable/variable.types.ts +9 -0
- package/src/wirings/virtual-user/index.ts +1 -4
- package/src/wirings/virtual-user/prepare-virtual-user-run.ts +1 -1
- package/src/wirings/virtual-user/virtual-user-derive.test.ts +1 -1
- package/src/wirings/virtual-user/virtual-user-derive.ts +1 -1
- package/src/wirings/virtual-user/virtual-user.types.ts +1 -5
- package/src/wirings/workflow/graph/graph-foreach-queued.test.ts +569 -0
- package/src/wirings/workflow/graph/graph-foreach.test.ts +626 -0
- package/src/wirings/workflow/graph/graph-node.ts +16 -1
- package/src/wirings/workflow/graph/graph-runner.ts +387 -61
- package/src/wirings/workflow/graph/workflow-graph.types.ts +28 -3
- package/src/wirings/workflow/index.ts +8 -0
- package/src/wirings/workflow/pikku-scenario-service.ts +59 -19
- package/src/wirings/workflow/pikku-workflow-service.ts +6 -8
- package/src/wirings/workflow/run-timeline.ts +1 -1
- package/src/wirings/workflow/scenario-expectations.test.ts +28 -13
- package/src/wirings/workflow/scenario-run.types.ts +186 -0
- package/src/wirings/workflow/scenario-step-guards.ts +1 -14
- package/src/wirings/workflow/scenario-step.test.ts +32 -7
- package/src/wirings/workflow/scenario-step.types.ts +27 -12
- package/src/wirings/workflow/workflow-approval-policy.test.ts +117 -47
- package/src/wirings/workflow/workflow-approval.ts +2 -1
- package/src/wirings/workflow/workflow-run-engine.types.ts +2 -1
- package/src/wirings/workflow/workflow.types.ts +4 -12
- package/src/wirings-stay-decoupled.test.ts +6 -3
- package/tsconfig.tsbuildinfo +1 -1
- package/tsconfig.type-tests.json +3 -2
- package/dist/ecosystem/ai-agent.d.ts +0 -18
- package/dist/ecosystem/ai-agent.js +0 -6
- package/dist/ecosystem/ai-scorer.d.ts +0 -12
- package/dist/ecosystem/ai-scorer.js +0 -5
- package/dist/services/ai-agent-runner-service.d.ts +0 -220
- package/dist/services/ai-run-state-service.d.ts +0 -29
- package/dist/services/ai-storage-service.d.ts +0 -18
- package/dist/services/in-memory-ai-run-state-service.d.ts +0 -19
- package/dist/services/in-memory-ai-run-state-service.js +0 -54
- package/dist/testing/service-tests/ai-storage-service-tests.d.ts +0 -3
- package/dist/testing/service-tests/ai-storage-service-tests.js +0 -302
- package/dist/wirings/ai-agent/agent-rpc.d.ts +0 -15
- package/dist/wirings/ai-agent/agent-rpc.js +0 -53
- package/dist/wirings/ai-agent/ai-agent-agui.d.ts +0 -79
- package/dist/wirings/ai-agent/ai-agent-agui.js +0 -327
- package/dist/wirings/ai-agent/ai-agent-finalize.d.ts +0 -58
- package/dist/wirings/ai-agent/ai-agent-finalize.js +0 -138
- package/dist/wirings/ai-agent/ai-agent-helpers.d.ts +0 -35
- package/dist/wirings/ai-agent/ai-agent-helpers.js +0 -47
- package/dist/wirings/ai-agent/ai-agent-interrupt.d.ts +0 -153
- package/dist/wirings/ai-agent/ai-agent-interrupt.js +0 -257
- package/dist/wirings/ai-agent/ai-agent-memory.d.ts +0 -42
- package/dist/wirings/ai-agent/ai-agent-memory.js +0 -333
- package/dist/wirings/ai-agent/ai-agent-model-config.d.ts +0 -16
- package/dist/wirings/ai-agent/ai-agent-model-config.js +0 -51
- package/dist/wirings/ai-agent/ai-agent-prepare.d.ts +0 -117
- package/dist/wirings/ai-agent/ai-agent-prepare.js +0 -728
- package/dist/wirings/ai-agent/ai-agent-registry.d.ts +0 -16
- package/dist/wirings/ai-agent/ai-agent-registry.js +0 -51
- package/dist/wirings/ai-agent/ai-agent-runner.d.ts +0 -7
- package/dist/wirings/ai-agent/ai-agent-runner.js +0 -580
- package/dist/wirings/ai-agent/ai-agent-stream.d.ts +0 -41
- package/dist/wirings/ai-agent/ai-agent-stream.js +0 -1029
- package/dist/wirings/ai-agent/ai-agent-turn.d.ts +0 -57
- package/dist/wirings/ai-agent/ai-agent-turn.js +0 -82
- package/dist/wirings/ai-agent/ai-agent-utils.d.ts +0 -1
- package/dist/wirings/ai-agent/ai-agent-utils.js +0 -1
- package/dist/wirings/ai-agent/ai-agent.types.d.ts +0 -625
- package/dist/wirings/ai-agent/index.d.ts +0 -12
- package/dist/wirings/ai-agent/index.js +0 -10
- package/dist/wirings/ai-agent/voice-input.d.ts +0 -69
- package/dist/wirings/ai-agent/voice-input.js +0 -150
- package/dist/wirings/ai-agent/voice-output.d.ts +0 -85
- package/dist/wirings/ai-agent/voice-output.js +0 -196
- package/dist/wirings/ai-scorer/ai-scorer-grade.d.ts +0 -26
- package/dist/wirings/ai-scorer/ai-scorer-grade.js +0 -33
- package/dist/wirings/ai-scorer/ai-scorer-judge.d.ts +0 -17
- package/dist/wirings/ai-scorer/ai-scorer-judge.js +0 -92
- package/dist/wirings/ai-scorer/ai-scorer-live.d.ts +0 -15
- package/dist/wirings/ai-scorer/ai-scorer-live.js +0 -38
- package/dist/wirings/ai-scorer/ai-scorer-registry.d.ts +0 -18
- package/dist/wirings/ai-scorer/ai-scorer-registry.js +0 -46
- package/dist/wirings/ai-scorer/ai-scorer-sampling.d.ts +0 -8
- package/dist/wirings/ai-scorer/ai-scorer-sampling.js +0 -31
- package/dist/wirings/ai-scorer/ai-scorer-snapshots.d.ts +0 -10
- package/dist/wirings/ai-scorer/ai-scorer-snapshots.js +0 -40
- package/dist/wirings/ai-scorer/ai-scorer-worker.d.ts +0 -15
- package/dist/wirings/ai-scorer/ai-scorer-worker.js +0 -58
- package/dist/wirings/ai-scorer/ai-scorer.d.ts +0 -39
- package/dist/wirings/ai-scorer/ai-scorer.js +0 -40
- package/dist/wirings/ai-scorer/ai-scorer.types.d.ts +0 -90
- package/dist/wirings/ai-scorer/ai-scorer.types.js +0 -4
- package/dist/wirings/ai-scorer/index.d.ts +0 -6
- package/dist/wirings/ai-scorer/index.js +0 -5
- package/src/ecosystem/agent-scorer.ts +0 -24
- package/src/ecosystem/agent.ts +0 -42
- package/src/ecosystem/channel/local.ts +0 -1
- package/src/ecosystem/channel.ts +0 -22
- package/src/ecosystem/cli/channel.ts +0 -25
- package/src/ecosystem/cli.ts +0 -21
- package/src/ecosystem/credential.ts +0 -10
- package/src/ecosystem/dev.ts +0 -12
- package/src/ecosystem/function.ts +0 -28
- package/src/ecosystem/gateway.ts +0 -4
- package/src/ecosystem/hmac.ts +0 -1
- package/src/ecosystem/http.ts +0 -22
- package/src/ecosystem/mcp.ts +0 -17
- package/src/ecosystem/middleware.ts +0 -27
- package/src/ecosystem/node-host-resolver.ts +0 -1
- package/src/ecosystem/node.ts +0 -5
- package/src/ecosystem/oauth2.ts +0 -4
- package/src/ecosystem/persona.ts +0 -30
- package/src/ecosystem/queue.ts +0 -23
- package/src/ecosystem/remote.ts +0 -9
- package/src/ecosystem/role.ts +0 -13
- package/src/ecosystem/rpc.ts +0 -7
- package/src/ecosystem/safe-fetch.ts +0 -8
- package/src/ecosystem/scenario.ts +0 -32
- package/src/ecosystem/scheduler.ts +0 -5
- package/src/ecosystem/schema.ts +0 -1
- package/src/ecosystem/scope.ts +0 -14
- package/src/ecosystem/secret.ts +0 -15
- package/src/ecosystem/services/istanbul-coverage.ts +0 -1
- package/src/ecosystem/services/local-content-request-handler.ts +0 -16
- package/src/ecosystem/services/local-content.ts +0 -1
- package/src/ecosystem/services/v8-coverage.ts +0 -1
- package/src/ecosystem/services.ts +0 -82
- package/src/ecosystem/trigger.ts +0 -7
- package/src/ecosystem/types.ts +0 -34
- package/src/ecosystem/variable.ts +0 -14
- package/src/ecosystem/virtual-user.ts +0 -48
- package/src/ecosystem/workflow.ts +0 -68
- package/src/ecosystem.ts +0 -40
- package/src/index.ts +0 -244
- /package/dist/{services/ai-agent-runner-service.js → bootstrap-compat/root.js} +0 -0
- /package/dist/{services/ai-run-state-service.js → classification/data-classification.js} +0 -0
- /package/dist/{services/ai-storage-service.js → errors/serialized-error.js} +0 -0
- /package/dist/{wirings/ai-agent/ai-agent.types.js → function/function-meta.types.js} +0 -0
- /package/src/{column-form.ts → classification/column-form.ts} +0 -0
- /package/src/{pikku-request.test.ts → function/pikku-request.test.ts} +0 -0
- /package/src/{pikku-request.ts → function/pikku-request.ts} +0 -0
- /package/src/wirings/{rpc → addon}/addon-auth-tags.test.ts +0 -0
- /package/src/wirings/{rpc → addon}/addon-runner.ts +0 -0
- /package/src/wirings/{rpc → addon}/addon-secrets.test.ts +0 -0
- /package/src/wirings/{rpc → addon}/wire-addon.test.ts +0 -0
- /package/src/wirings/{rpc → addon}/wire-remote-addon.ts +0 -0
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Raised when every audio part in a turn turned out to be non-speech.
|
|
3
|
-
*
|
|
4
|
-
* Distinct from a transcription failure, because the response is different: a
|
|
5
|
-
* failure is worth reporting, whereas this is the microphone doing its job on a
|
|
6
|
-
* pause. A voice loop should catch it and go back to listening without running
|
|
7
|
-
* the agent — answering a hallucinated sentence is worse than answering nothing.
|
|
8
|
-
*/
|
|
9
|
-
export declare class NoSpeechDetectedError extends Error {
|
|
10
|
-
constructor();
|
|
11
|
-
}
|
|
12
|
-
/**
|
|
13
|
-
* Whether nothing usable was said — an empty transcript, and nothing cleverer
|
|
14
|
-
* than that.
|
|
15
|
-
*
|
|
16
|
-
* There was a confidence gate here once, and it is worth recording why it is
|
|
17
|
-
* gone. Whisper is trained on subtitles, so audio it cannot parse comes back
|
|
18
|
-
* not as nothing but as stock filler appended to real speech: "Hello, my name
|
|
19
|
-
* is Yasir. Thank you." where only the first sentence was spoken. The obvious
|
|
20
|
-
* defence is to drop low-confidence segments, and it does not work. Measured on
|
|
21
|
-
* a real turn, the invented "Thank you." scored `avg_logprob` -0.52 against
|
|
22
|
-
* -0.30 for the genuine sentence beside it, and `no_speech_prob` read 0.000 for
|
|
23
|
-
* both. Whisper is *confident* when it invents, because the text it invents is
|
|
24
|
-
* high-probability text. No threshold separates those two numbers, and one set
|
|
25
|
-
* low enough to try would take real quiet speech with it.
|
|
26
|
-
*
|
|
27
|
-
* The fix belongs at the model, not behind it: an ASR that is not a
|
|
28
|
-
* subtitle-trained autoregressive decoder returns an empty string on non-speech
|
|
29
|
-
* and there is nothing left to filter. Hence this reduces to asking whether
|
|
30
|
-
* anything came back at all — which is a property every provider reports
|
|
31
|
-
* honestly, unlike per-segment confidence (Nemotron, for one, hardcodes
|
|
32
|
-
* `avg_logprob` to 0.0).
|
|
33
|
-
*/
|
|
34
|
-
export declare const readsAsNonSpeech: (result: {
|
|
35
|
-
text?: string;
|
|
36
|
-
}) => boolean;
|
|
37
|
-
/**
|
|
38
|
-
* Shared-notes key recording whether the user's turn arrived as audio.
|
|
39
|
-
*
|
|
40
|
-
* Written here because this is the last point at which it is knowable: the
|
|
41
|
-
* transcript that replaces the audio is indistinguishable from something typed.
|
|
42
|
-
* Read by `voiceOutput` to decide whether to answer aloud.
|
|
43
|
-
*
|
|
44
|
-
* Absent — rather than `false` — when this middleware is not wired at all, which
|
|
45
|
-
* is what lets `voiceOutput` still speak for a caller that has no voice input.
|
|
46
|
-
*/
|
|
47
|
-
export declare const SPOKEN_TURN = "voice:spokenTurn";
|
|
48
|
-
/**
|
|
49
|
-
* Shared-notes key holding what the user was heard to say, when they spoke.
|
|
50
|
-
*
|
|
51
|
-
* Absent on a typed turn. Read by the stream wiring, which forwards it to the
|
|
52
|
-
* client as a `transcript` event — a voice client has no idea what its own
|
|
53
|
-
* audio said, and without this the user's turn shows up in the UI as an empty
|
|
54
|
-
* bubble followed by an answer to a question they cannot see.
|
|
55
|
-
*/
|
|
56
|
-
export declare const SPOKEN_TRANSCRIPT = "voice:transcript";
|
|
57
|
-
export declare const voiceInput: (config?: {
|
|
58
|
-
language?: string;
|
|
59
|
-
model?: string;
|
|
60
|
-
allowedAudioHosts?: string[];
|
|
61
|
-
}) => import("./ai-agent.types.js").PikkuAIMiddlewareHooks<Record<string, unknown>, import("../../types/core.types.js").CoreSingletonServices<{
|
|
62
|
-
logLevel?: import("../../services/logger.js").LogLevel;
|
|
63
|
-
secrets?: {
|
|
64
|
-
requireAllowedHosts?: boolean;
|
|
65
|
-
};
|
|
66
|
-
workflow?: import("../workflow/workflow.types.js").WorkflowServiceConfig;
|
|
67
|
-
webhook?: import("../../services/webhook-service.js").WebhookServiceConfig;
|
|
68
|
-
postgres?: import("../../types/core.types.js").PostgresConfig;
|
|
69
|
-
}>>;
|
|
@@ -1,150 +0,0 @@
|
|
|
1
|
-
import { pikkuAIMiddleware } from '../../types/core.types.js';
|
|
2
|
-
import { safeFetch } from '../../utils/safe-fetch.js';
|
|
3
|
-
function base64ToUint8Array(base64) {
|
|
4
|
-
const binary = atob(base64);
|
|
5
|
-
const bytes = new Uint8Array(binary.length);
|
|
6
|
-
for (let i = 0; i < binary.length; i++) {
|
|
7
|
-
bytes[i] = binary.charCodeAt(i);
|
|
8
|
-
}
|
|
9
|
-
return bytes;
|
|
10
|
-
}
|
|
11
|
-
const MAX_AUDIO_SIZE = 50 * 1024 * 1024;
|
|
12
|
-
/**
|
|
13
|
-
* Raised when every audio part in a turn turned out to be non-speech.
|
|
14
|
-
*
|
|
15
|
-
* Distinct from a transcription failure, because the response is different: a
|
|
16
|
-
* failure is worth reporting, whereas this is the microphone doing its job on a
|
|
17
|
-
* pause. A voice loop should catch it and go back to listening without running
|
|
18
|
-
* the agent — answering a hallucinated sentence is worse than answering nothing.
|
|
19
|
-
*/
|
|
20
|
-
export class NoSpeechDetectedError extends Error {
|
|
21
|
-
constructor() {
|
|
22
|
-
super('No speech was detected in the audio.');
|
|
23
|
-
this.name = 'NoSpeechDetectedError';
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
/**
|
|
27
|
-
* Whether nothing usable was said — an empty transcript, and nothing cleverer
|
|
28
|
-
* than that.
|
|
29
|
-
*
|
|
30
|
-
* There was a confidence gate here once, and it is worth recording why it is
|
|
31
|
-
* gone. Whisper is trained on subtitles, so audio it cannot parse comes back
|
|
32
|
-
* not as nothing but as stock filler appended to real speech: "Hello, my name
|
|
33
|
-
* is Yasir. Thank you." where only the first sentence was spoken. The obvious
|
|
34
|
-
* defence is to drop low-confidence segments, and it does not work. Measured on
|
|
35
|
-
* a real turn, the invented "Thank you." scored `avg_logprob` -0.52 against
|
|
36
|
-
* -0.30 for the genuine sentence beside it, and `no_speech_prob` read 0.000 for
|
|
37
|
-
* both. Whisper is *confident* when it invents, because the text it invents is
|
|
38
|
-
* high-probability text. No threshold separates those two numbers, and one set
|
|
39
|
-
* low enough to try would take real quiet speech with it.
|
|
40
|
-
*
|
|
41
|
-
* The fix belongs at the model, not behind it: an ASR that is not a
|
|
42
|
-
* subtitle-trained autoregressive decoder returns an empty string on non-speech
|
|
43
|
-
* and there is nothing left to filter. Hence this reduces to asking whether
|
|
44
|
-
* anything came back at all — which is a property every provider reports
|
|
45
|
-
* honestly, unlike per-segment confidence (Nemotron, for one, hardcodes
|
|
46
|
-
* `avg_logprob` to 0.0).
|
|
47
|
-
*/
|
|
48
|
-
export const readsAsNonSpeech = (result) => !(result.text ?? '').trim();
|
|
49
|
-
async function fetchAsUint8Array(url, allowedAudioHosts) {
|
|
50
|
-
const response = await safeFetch(url, {}, { allowedHosts: allowedAudioHosts });
|
|
51
|
-
const contentLength = response.headers.get('content-length');
|
|
52
|
-
if (contentLength && parseInt(contentLength, 10) > MAX_AUDIO_SIZE) {
|
|
53
|
-
throw new Error('Audio file exceeds maximum size');
|
|
54
|
-
}
|
|
55
|
-
const buffer = await response.arrayBuffer();
|
|
56
|
-
if (buffer.byteLength > MAX_AUDIO_SIZE) {
|
|
57
|
-
throw new Error('Audio file exceeds maximum size');
|
|
58
|
-
}
|
|
59
|
-
return new Uint8Array(buffer);
|
|
60
|
-
}
|
|
61
|
-
/**
|
|
62
|
-
* Shared-notes key recording whether the user's turn arrived as audio.
|
|
63
|
-
*
|
|
64
|
-
* Written here because this is the last point at which it is knowable: the
|
|
65
|
-
* transcript that replaces the audio is indistinguishable from something typed.
|
|
66
|
-
* Read by `voiceOutput` to decide whether to answer aloud.
|
|
67
|
-
*
|
|
68
|
-
* Absent — rather than `false` — when this middleware is not wired at all, which
|
|
69
|
-
* is what lets `voiceOutput` still speak for a caller that has no voice input.
|
|
70
|
-
*/
|
|
71
|
-
export const SPOKEN_TURN = 'voice:spokenTurn';
|
|
72
|
-
/**
|
|
73
|
-
* Shared-notes key holding what the user was heard to say, when they spoke.
|
|
74
|
-
*
|
|
75
|
-
* Absent on a typed turn. Read by the stream wiring, which forwards it to the
|
|
76
|
-
* client as a `transcript` event — a voice client has no idea what its own
|
|
77
|
-
* audio said, and without this the user's turn shows up in the UI as an empty
|
|
78
|
-
* bubble followed by an answer to a question they cannot see.
|
|
79
|
-
*/
|
|
80
|
-
export const SPOKEN_TRANSCRIPT = 'voice:transcript';
|
|
81
|
-
export const voiceInput = (config) => pikkuAIMiddleware({
|
|
82
|
-
modifyInput: async (services, { messages, instructions, shared }) => {
|
|
83
|
-
const aiAgentRunner = services.aiAgentRunner;
|
|
84
|
-
if (!aiAgentRunner?.transcribe)
|
|
85
|
-
return { messages, instructions };
|
|
86
|
-
const last = messages[messages.length - 1];
|
|
87
|
-
const parts = last?.role === 'user' && typeof last.content !== 'string'
|
|
88
|
-
? last.content
|
|
89
|
-
: undefined;
|
|
90
|
-
const hasAudio = !!parts?.some((p) => p.type === 'file' && !!p.mediaType?.startsWith('audio/'));
|
|
91
|
-
// Recorded on every turn this middleware sees, spoken or not, so that a
|
|
92
|
-
// typed turn is an explicit `false` downstream rather than a silence that
|
|
93
|
-
// could equally mean nobody was asked.
|
|
94
|
-
shared[SPOKEN_TURN] = hasAudio;
|
|
95
|
-
if (!hasAudio || !parts)
|
|
96
|
-
return { messages, instructions };
|
|
97
|
-
const updatedContent = [];
|
|
98
|
-
const heard = [];
|
|
99
|
-
for (const p of parts) {
|
|
100
|
-
if (!(p.type === 'file' && p.mediaType?.startsWith('audio/'))) {
|
|
101
|
-
updatedContent.push(p);
|
|
102
|
-
continue;
|
|
103
|
-
}
|
|
104
|
-
if (!config?.model) {
|
|
105
|
-
throw new Error('voiceInput requires a transcription model (e.g. openai/whisper-1)');
|
|
106
|
-
}
|
|
107
|
-
const audioData = p.data
|
|
108
|
-
? base64ToUint8Array(p.data)
|
|
109
|
-
: await fetchAsUint8Array(p.url, config.allowedAudioHosts);
|
|
110
|
-
if (audioData.byteLength > MAX_AUDIO_SIZE) {
|
|
111
|
-
throw new Error('Audio file exceeds maximum size');
|
|
112
|
-
}
|
|
113
|
-
const result = await aiAgentRunner.transcribe({
|
|
114
|
-
model: config.model,
|
|
115
|
-
audio: audioData,
|
|
116
|
-
...(config.language
|
|
117
|
-
? {
|
|
118
|
-
providerOptions: {
|
|
119
|
-
openai: {
|
|
120
|
-
language: config.language,
|
|
121
|
-
},
|
|
122
|
-
},
|
|
123
|
-
}
|
|
124
|
-
: {}),
|
|
125
|
-
});
|
|
126
|
-
// Dropped when nothing was said: an empty text part is still a turn the
|
|
127
|
-
// model will answer, and what it answers is a guess about what it could
|
|
128
|
-
// not hear.
|
|
129
|
-
if (readsAsNonSpeech(result))
|
|
130
|
-
continue;
|
|
131
|
-
heard.push(result.text);
|
|
132
|
-
updatedContent.push({ type: 'text', text: result.text });
|
|
133
|
-
}
|
|
134
|
-
// Every part was audio, and none of it was speech. There is nothing left
|
|
135
|
-
// to send, and a message with no content is not a question.
|
|
136
|
-
if (updatedContent.length === 0)
|
|
137
|
-
throw new NoSpeechDetectedError();
|
|
138
|
-
// knowledge: decisions/internals/an-empty-transcript-is-not-recorded.md
|
|
139
|
-
if (heard.length > 0) {
|
|
140
|
-
shared[SPOKEN_TRANSCRIPT] = heard.join(' ');
|
|
141
|
-
}
|
|
142
|
-
return {
|
|
143
|
-
messages: [
|
|
144
|
-
...messages.slice(0, -1),
|
|
145
|
-
{ ...last, content: updatedContent },
|
|
146
|
-
],
|
|
147
|
-
instructions,
|
|
148
|
-
};
|
|
149
|
-
},
|
|
150
|
-
});
|
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
export type VoiceOutputState = {
|
|
2
|
-
textBuffer?: string;
|
|
3
|
-
/**
|
|
4
|
-
* The audio emitted so far, as a chain.
|
|
5
|
-
*
|
|
6
|
-
* Synthesis for every finished sentence starts immediately, so the second
|
|
7
|
-
* sentence is being generated while the first is still being spoken. That
|
|
8
|
-
* also means the second can finish first — a short sentence after a long one
|
|
9
|
-
* routinely does. Chaining the *emissions* keeps the client receiving audio
|
|
10
|
-
* in the order it was written while leaving the generation overlapped.
|
|
11
|
-
*/
|
|
12
|
-
tail?: Promise<void>;
|
|
13
|
-
/** Whether this reply has already said it cannot be spoken aloud. */
|
|
14
|
-
reportedUnspeakable?: boolean;
|
|
15
|
-
};
|
|
16
|
-
/**
|
|
17
|
-
* The scripts a model can pronounce, and optionally the voice each one needs.
|
|
18
|
-
*
|
|
19
|
-
* The array form is for a model whose voice is the same whatever the script.
|
|
20
|
-
* The record form exists because that is not the common case: Kokoro speaks
|
|
21
|
-
* Mandarin, Japanese and Hindi well, but only through a voice belonging to that
|
|
22
|
-
* language. Asked for Chinese in its default American-English voice it spells
|
|
23
|
-
* the characters out — 9.9 seconds of "Chinese letter, Chinese letter" for a
|
|
24
|
-
* sentence the right voice says in 3.5. Same model, same text, same price.
|
|
25
|
-
*/
|
|
26
|
-
export type SpeakableScripts = string[] | Record<string, string>;
|
|
27
|
-
/**
|
|
28
|
-
* The scripts present in `text` that the model was not said to handle.
|
|
29
|
-
*
|
|
30
|
-
* Silence would be the wrong answer here and so would a best effort. Handed
|
|
31
|
-
* Arabic — which it has no voice for at all — Kokoro does not fail and does not
|
|
32
|
-
* stay quiet: it reads out the *letter names*, twenty-four seconds of "Arabic
|
|
33
|
-
* meem, Arabic ra" for a one-line sentence, which is worse than either. Naming
|
|
34
|
-
* the gap lets the caller say so instead.
|
|
35
|
-
*/
|
|
36
|
-
export declare const unspeakableScripts: (text: string, speakable: SpeakableScripts) => string[];
|
|
37
|
-
/**
|
|
38
|
-
* The voice to speak `text` in, or `fallback` if no mapped script appears.
|
|
39
|
-
*
|
|
40
|
-
* A sentence is regularly in more than one script, so which one decides is
|
|
41
|
-
* settled by {@link SCRIPT_PRECEDENCE} rather than by the order the config
|
|
42
|
-
* happens to be written in.
|
|
43
|
-
*/
|
|
44
|
-
export declare const voiceForText: (text: string, speakable: SpeakableScripts, fallback?: string) => string | undefined;
|
|
45
|
-
export declare const voiceOutput: (config?: {
|
|
46
|
-
model?: string;
|
|
47
|
-
format?: string;
|
|
48
|
-
voice?: string;
|
|
49
|
-
instructions?: string;
|
|
50
|
-
speed?: number;
|
|
51
|
-
language?: string;
|
|
52
|
-
/**
|
|
53
|
-
* Scripts the configured model can actually pronounce, and the voice each
|
|
54
|
-
* needs — see {@link SpeakableScripts}. Anything written in another script is
|
|
55
|
-
* left unspoken and reported once, as a `voice-unsupported` data event,
|
|
56
|
-
* rather than sent to a model that will mangle it.
|
|
57
|
-
*
|
|
58
|
-
* Omitted means no check: every model is trusted with every script, which is
|
|
59
|
-
* the right default for a provider that genuinely is multilingual.
|
|
60
|
-
*/
|
|
61
|
-
speakableScripts?: SpeakableScripts;
|
|
62
|
-
/**
|
|
63
|
-
* Speak every reply, including answers to turns that were typed.
|
|
64
|
-
*
|
|
65
|
-
* The default is to speak only what was spoken to. An agent is usually one
|
|
66
|
-
* agent serving both kinds of caller — the same thread is typed at from a
|
|
67
|
-
* desk and talked to from a phone — and synthesizing a reply nobody is
|
|
68
|
-
* listening to costs real money on every sentence of every turn. Whether the
|
|
69
|
-
* user is in a call is not something the client needs to declare: sending
|
|
70
|
-
* audio is what saying so looks like.
|
|
71
|
-
*
|
|
72
|
-
* Set this for the cases where speech is not a reply to speech at all — a
|
|
73
|
-
* read-aloud accessibility mode, a kiosk, anything whose whole output is
|
|
74
|
-
* meant to be heard.
|
|
75
|
-
*/
|
|
76
|
-
always?: boolean;
|
|
77
|
-
}) => import("./ai-agent.types.js").PikkuAIMiddlewareHooks<VoiceOutputState, import("../../types/core.types.js").CoreSingletonServices<{
|
|
78
|
-
logLevel?: import("../../services/logger.js").LogLevel;
|
|
79
|
-
secrets?: {
|
|
80
|
-
requireAllowedHosts?: boolean;
|
|
81
|
-
};
|
|
82
|
-
workflow?: import("../workflow/workflow.types.js").WorkflowServiceConfig;
|
|
83
|
-
webhook?: import("../../services/webhook-service.js").WebhookServiceConfig;
|
|
84
|
-
postgres?: import("../../types/core.types.js").PostgresConfig;
|
|
85
|
-
}>>;
|
|
@@ -1,196 +0,0 @@
|
|
|
1
|
-
import { pikkuAIMiddleware } from '../../types/core.types.js';
|
|
2
|
-
import { SPOKEN_TURN } from './voice-input.js';
|
|
3
|
-
function bufferToBase64(data) {
|
|
4
|
-
let binary = '';
|
|
5
|
-
for (let i = 0; i < data.length; i++) {
|
|
6
|
-
binary += String.fromCharCode(data[i]);
|
|
7
|
-
}
|
|
8
|
-
return btoa(binary);
|
|
9
|
-
}
|
|
10
|
-
const SENTENCE_BOUNDARY = /[.!?]\s*$/;
|
|
11
|
-
function isSentenceBoundary(text) {
|
|
12
|
-
return SENTENCE_BOUNDARY.test(text);
|
|
13
|
-
}
|
|
14
|
-
/**
|
|
15
|
-
* Scripts a speech model can pronounce, as named by {@link voiceOutput}'s
|
|
16
|
-
* `speakableScripts`.
|
|
17
|
-
*
|
|
18
|
-
* Script rather than language because script is what is decidable from the text
|
|
19
|
-
* alone. Spanish, French and English are one test, not three — and the models
|
|
20
|
-
* that handle one Latin-script language generally handle the others. What a
|
|
21
|
-
* model cannot do is invent a phoneme set it was never given.
|
|
22
|
-
*/
|
|
23
|
-
const SCRIPT_RANGES = {
|
|
24
|
-
latin: /[A-ɏ]/,
|
|
25
|
-
devanagari: /[ऀ-ॿ]/,
|
|
26
|
-
han: /[㐀-鿿]/,
|
|
27
|
-
kana: /[-ヿ]/,
|
|
28
|
-
arabic: /[-ۿݐ-ݿ]/,
|
|
29
|
-
cyrillic: /[Ѐ-ӿ]/,
|
|
30
|
-
hangul: /[가-ᄀ-ᇿ]/,
|
|
31
|
-
hebrew: /[-]/,
|
|
32
|
-
greek: /[Ͱ-Ͽ]/,
|
|
33
|
-
thai: /[-]/,
|
|
34
|
-
};
|
|
35
|
-
/**
|
|
36
|
-
* Which script decides the voice when a sentence contains several.
|
|
37
|
-
*
|
|
38
|
-
* The ordering is by how much a script narrows down the language, not by
|
|
39
|
-
* preference. Kana appears only in Japanese, so a sentence holding any settles
|
|
40
|
-
* it — while Han is written in both Chinese and Japanese and so cannot, which
|
|
41
|
-
* matters because ordinary Japanese is mostly kanji. Latin comes last for the
|
|
42
|
-
* same reason from the other end: it turns up inside sentences in every other
|
|
43
|
-
* script, and it is the one whose voice is least often the one that counts. A
|
|
44
|
-
* Chinese voice reading an English word is accented; an English voice reading
|
|
45
|
-
* Chinese spells it out.
|
|
46
|
-
*
|
|
47
|
-
* Everything else — Han included — sits between the two in config order.
|
|
48
|
-
*/
|
|
49
|
-
const SCRIPT_PRECEDENCE = { kana: -1, latin: 1 };
|
|
50
|
-
const scriptRank = (name) => SCRIPT_PRECEDENCE[name] ?? 0;
|
|
51
|
-
const scriptNames = (speakable) => Array.isArray(speakable) ? speakable : Object.keys(speakable);
|
|
52
|
-
/**
|
|
53
|
-
* The scripts present in `text` that the model was not said to handle.
|
|
54
|
-
*
|
|
55
|
-
* Silence would be the wrong answer here and so would a best effort. Handed
|
|
56
|
-
* Arabic — which it has no voice for at all — Kokoro does not fail and does not
|
|
57
|
-
* stay quiet: it reads out the *letter names*, twenty-four seconds of "Arabic
|
|
58
|
-
* meem, Arabic ra" for a one-line sentence, which is worse than either. Naming
|
|
59
|
-
* the gap lets the caller say so instead.
|
|
60
|
-
*/
|
|
61
|
-
export const unspeakableScripts = (text, speakable) => {
|
|
62
|
-
const allowed = new Set(scriptNames(speakable));
|
|
63
|
-
return Object.entries(SCRIPT_RANGES)
|
|
64
|
-
.filter(([name, range]) => !allowed.has(name) && range.test(text))
|
|
65
|
-
.map(([name]) => name);
|
|
66
|
-
};
|
|
67
|
-
/**
|
|
68
|
-
* The voice to speak `text` in, or `fallback` if no mapped script appears.
|
|
69
|
-
*
|
|
70
|
-
* A sentence is regularly in more than one script, so which one decides is
|
|
71
|
-
* settled by {@link SCRIPT_PRECEDENCE} rather than by the order the config
|
|
72
|
-
* happens to be written in.
|
|
73
|
-
*/
|
|
74
|
-
export const voiceForText = (text, speakable, fallback) => {
|
|
75
|
-
if (Array.isArray(speakable))
|
|
76
|
-
return fallback;
|
|
77
|
-
const mapped = Object.entries(speakable).sort(([a], [b]) => scriptRank(a) - scriptRank(b));
|
|
78
|
-
for (const [name, voice] of mapped) {
|
|
79
|
-
if (SCRIPT_RANGES[name]?.test(text))
|
|
80
|
-
return voice;
|
|
81
|
-
}
|
|
82
|
-
return fallback;
|
|
83
|
-
};
|
|
84
|
-
async function synthesizeAudio(aiAgentRunner, input) {
|
|
85
|
-
const result = await aiAgentRunner.generateSpeech?.({
|
|
86
|
-
model: input.model,
|
|
87
|
-
text: input.text,
|
|
88
|
-
voice: input.voice,
|
|
89
|
-
outputFormat: input.format,
|
|
90
|
-
instructions: input.instructions,
|
|
91
|
-
speed: input.speed,
|
|
92
|
-
language: input.language,
|
|
93
|
-
abortSignal: input.abortSignal,
|
|
94
|
-
});
|
|
95
|
-
if (!result) {
|
|
96
|
-
throw new Error('voiceOutput requires an aiAgentRunner with generateSpeech support');
|
|
97
|
-
}
|
|
98
|
-
return {
|
|
99
|
-
bytes: result.audio.uint8Array,
|
|
100
|
-
format: result.audio.format || input.format || 'pcm16',
|
|
101
|
-
};
|
|
102
|
-
}
|
|
103
|
-
export const voiceOutput = (config) => pikkuAIMiddleware({
|
|
104
|
-
modifyOutputStream: async (services, { event, state, shared, emit, signal }) => {
|
|
105
|
-
const { aiAgentRunner, logger } = services;
|
|
106
|
-
if (!aiAgentRunner?.generateSpeech)
|
|
107
|
-
return event;
|
|
108
|
-
// knowledge: decisions/internals/voice-output-speaks-unless-voice-input-explicitly-says-otherwise.md
|
|
109
|
-
if (!config?.always && shared[SPOKEN_TURN] === false)
|
|
110
|
-
return event;
|
|
111
|
-
/**
|
|
112
|
-
* Queue a finished sentence for speech.
|
|
113
|
-
*
|
|
114
|
-
* Nothing here is awaited by the caller. Synthesis is kicked off at once
|
|
115
|
-
* and its emission is chained behind the previous sentence, which is
|
|
116
|
-
* what keeps the text flowing at the speed the model writes it instead
|
|
117
|
-
* of the speed the speech provider answers.
|
|
118
|
-
*/
|
|
119
|
-
const speak = (text) => {
|
|
120
|
-
if (!config?.model) {
|
|
121
|
-
throw new Error('voiceOutput requires a speech model (e.g. openai/tts-1)');
|
|
122
|
-
}
|
|
123
|
-
// knowledge: decisions/internals/speech-synthesis-picks-a-voice-per-sentence-and-warns-once.md
|
|
124
|
-
let voice = config.voice;
|
|
125
|
-
if (config.speakableScripts) {
|
|
126
|
-
const unspeakable = unspeakableScripts(text, config.speakableScripts);
|
|
127
|
-
voice = voiceForText(text, config.speakableScripts, config.voice);
|
|
128
|
-
if (unspeakable.length > 0) {
|
|
129
|
-
if (!state.reportedUnspeakable) {
|
|
130
|
-
state.reportedUnspeakable = true;
|
|
131
|
-
state.tail = (state.tail ?? Promise.resolve()).then(() => emit({
|
|
132
|
-
type: 'data',
|
|
133
|
-
name: 'voice-unsupported',
|
|
134
|
-
data: { model: config.model, scripts: unspeakable },
|
|
135
|
-
}));
|
|
136
|
-
}
|
|
137
|
-
return;
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
// Settled into a value rather than left as a rejectable promise: it
|
|
141
|
-
// sits unobserved until the chain reaches it, and an unhandled
|
|
142
|
-
// rejection in that window would take the process down.
|
|
143
|
-
const synthesis = synthesizeAudio(aiAgentRunner, {
|
|
144
|
-
model: config.model,
|
|
145
|
-
text,
|
|
146
|
-
voice,
|
|
147
|
-
format: config?.format,
|
|
148
|
-
instructions: config?.instructions,
|
|
149
|
-
speed: config?.speed,
|
|
150
|
-
language: config?.language,
|
|
151
|
-
// Barge-in should stop the bill, not just the playback. Without this
|
|
152
|
-
// every sentence already in flight is synthesized in full and paid
|
|
153
|
-
// for after the user has stopped listening.
|
|
154
|
-
abortSignal: signal,
|
|
155
|
-
}).then((audio) => ({ ok: true, audio }), (error) => ({ ok: false, error }));
|
|
156
|
-
state.tail = (state.tail ?? Promise.resolve()).then(async () => {
|
|
157
|
-
const result = await synthesis;
|
|
158
|
-
if (!result.ok) {
|
|
159
|
-
// One sentence going unspoken is better than the rest of the reply
|
|
160
|
-
// never arriving, so the chain carries on.
|
|
161
|
-
logger?.error(`voiceOutput could not synthesize a sentence: ${String(result.error)}`);
|
|
162
|
-
return;
|
|
163
|
-
}
|
|
164
|
-
await emit({
|
|
165
|
-
type: 'audio-delta',
|
|
166
|
-
data: bufferToBase64(result.audio.bytes),
|
|
167
|
-
format: result.audio.format,
|
|
168
|
-
text,
|
|
169
|
-
});
|
|
170
|
-
});
|
|
171
|
-
};
|
|
172
|
-
if (event.type === 'done') {
|
|
173
|
-
const remaining = state.textBuffer ?? '';
|
|
174
|
-
state.textBuffer = '';
|
|
175
|
-
if (remaining.trim()) {
|
|
176
|
-
speak(remaining);
|
|
177
|
-
}
|
|
178
|
-
// The only place this hook waits. The reply is already over, so the
|
|
179
|
-
// wait costs the reader nothing, and it is what makes `audio-done`
|
|
180
|
-
// mean what it says rather than racing the last chunk.
|
|
181
|
-
await state.tail;
|
|
182
|
-
state.tail = undefined;
|
|
183
|
-
return [{ type: 'audio-done' }, event];
|
|
184
|
-
}
|
|
185
|
-
if (event.type !== 'text-delta')
|
|
186
|
-
return event;
|
|
187
|
-
state.textBuffer = `${state.textBuffer ?? ''}${event.text}`;
|
|
188
|
-
if (isSentenceBoundary(state.textBuffer)) {
|
|
189
|
-
const text = state.textBuffer;
|
|
190
|
-
state.textBuffer = '';
|
|
191
|
-
speak(text);
|
|
192
|
-
}
|
|
193
|
-
// Returned without waiting for any audio — the entire point.
|
|
194
|
-
return event;
|
|
195
|
-
},
|
|
196
|
-
});
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import type { ScoreJob, ScorerOutput } from './ai-scorer.types.js';
|
|
2
|
-
/**
|
|
3
|
-
* Grade one run with one scorer.
|
|
4
|
-
*
|
|
5
|
-
* The single path both callers take — the lane worker on live traffic and the
|
|
6
|
-
* scenario grading RPC — so a scenario's grade is the same computation the
|
|
7
|
-
* production sampler would have made, not an approximation of it.
|
|
8
|
-
*
|
|
9
|
-
* Persisting is optional because the two callers differ on it: a live grade is
|
|
10
|
-
* only useful once recorded, while a scenario asserts on the returned value and
|
|
11
|
-
* runs against servers that may have no run-state adapter at all.
|
|
12
|
-
*/
|
|
13
|
-
export declare const gradeRun: (job: ScoreJob, services: {
|
|
14
|
-
aiAgentRunner?: unknown;
|
|
15
|
-
aiRunState?: {
|
|
16
|
-
saveScore: (score: {
|
|
17
|
-
runId: string;
|
|
18
|
-
scorerName: string;
|
|
19
|
-
score: number;
|
|
20
|
-
reason?: string;
|
|
21
|
-
metadata?: Record<string, unknown>;
|
|
22
|
-
}) => Promise<void>;
|
|
23
|
-
};
|
|
24
|
-
}, options: {
|
|
25
|
-
persist: boolean;
|
|
26
|
-
}) => Promise<ScorerOutput>;
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import { runJudge } from './ai-scorer-judge.js';
|
|
2
|
-
import { resolveAIScorer } from './ai-scorer-registry.js';
|
|
3
|
-
/**
|
|
4
|
-
* Grade one run with one scorer.
|
|
5
|
-
*
|
|
6
|
-
* The single path both callers take — the lane worker on live traffic and the
|
|
7
|
-
* scenario grading RPC — so a scenario's grade is the same computation the
|
|
8
|
-
* production sampler would have made, not an approximation of it.
|
|
9
|
-
*
|
|
10
|
-
* Persisting is optional because the two callers differ on it: a live grade is
|
|
11
|
-
* only useful once recorded, while a scenario asserts on the returned value and
|
|
12
|
-
* runs against servers that may have no run-state adapter at all.
|
|
13
|
-
*/
|
|
14
|
-
export const gradeRun = async (job, services, options) => {
|
|
15
|
-
const { scorerName, ...input } = job;
|
|
16
|
-
const scorer = resolveAIScorer(scorerName);
|
|
17
|
-
const result = scorer.score
|
|
18
|
-
? await scorer.score(input, services)
|
|
19
|
-
: await runJudge(scorer, input, services.aiAgentRunner);
|
|
20
|
-
if (options.persist) {
|
|
21
|
-
if (!services.aiRunState) {
|
|
22
|
-
throw new Error(`AI run state service not initialized: cannot record the '${scorerName}' grade of run ${job.runId}`);
|
|
23
|
-
}
|
|
24
|
-
await services.aiRunState.saveScore({
|
|
25
|
-
runId: job.runId,
|
|
26
|
-
scorerName,
|
|
27
|
-
score: result.score,
|
|
28
|
-
...(result.reason !== undefined ? { reason: result.reason } : {}),
|
|
29
|
-
...(result.metadata !== undefined ? { metadata: result.metadata } : {}),
|
|
30
|
-
});
|
|
31
|
-
}
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import type { AIAgentRunnerService } from '../../services/ai-agent-runner-service.js';
|
|
2
|
-
import type { PikkuAIScorer, ScorerInput, ScorerJudgeConfig, ScorerOutput } from './ai-scorer.types.js';
|
|
3
|
-
/**
|
|
4
|
-
* The message a judge grades when its scorer supplies no `prompt`.
|
|
5
|
-
*
|
|
6
|
-
* A reference-based judge is shown the answer key; a reference-free one is not,
|
|
7
|
-
* and grades the answer on its own merits.
|
|
8
|
-
*/
|
|
9
|
-
export declare const buildJudgePrompt: (judge: ScorerJudgeConfig, input: ScorerInput) => string;
|
|
10
|
-
/**
|
|
11
|
-
* Run an LLM judge and force the `{ score, reason }` contract, so a scorer
|
|
12
|
-
* author writes a rubric rather than a parser.
|
|
13
|
-
*
|
|
14
|
-
* A judge has no tools, which is the one case where an output schema is
|
|
15
|
-
* reliably honoured.
|
|
16
|
-
*/
|
|
17
|
-
export declare const runJudge: (scorer: PikkuAIScorer<any>, input: ScorerInput, aiAgentRunner: AIAgentRunnerService | undefined) => Promise<ScorerOutput>;
|
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
import { randomUUID } from '../ai-agent/ai-agent-utils.js';
|
|
2
|
-
const JUDGE_OUTPUT_SCHEMA = {
|
|
3
|
-
type: 'object',
|
|
4
|
-
properties: {
|
|
5
|
-
score: {
|
|
6
|
-
type: 'number',
|
|
7
|
-
minimum: 0,
|
|
8
|
-
maximum: 1,
|
|
9
|
-
description: 'How well the answer meets the rubric. 1 is best.',
|
|
10
|
-
},
|
|
11
|
-
reason: {
|
|
12
|
-
type: 'string',
|
|
13
|
-
description: 'One or two sentences explaining the score.',
|
|
14
|
-
},
|
|
15
|
-
},
|
|
16
|
-
required: ['score', 'reason'],
|
|
17
|
-
additionalProperties: false,
|
|
18
|
-
};
|
|
19
|
-
/**
|
|
20
|
-
* The message a judge grades when its scorer supplies no `prompt`.
|
|
21
|
-
*
|
|
22
|
-
* A reference-based judge is shown the answer key; a reference-free one is not,
|
|
23
|
-
* and grades the answer on its own merits.
|
|
24
|
-
*/
|
|
25
|
-
export const buildJudgePrompt = (judge, input) => {
|
|
26
|
-
if (judge.prompt)
|
|
27
|
-
return judge.prompt(input);
|
|
28
|
-
const sections = [`User asked:\n${input.input}`];
|
|
29
|
-
if (input.reference !== undefined) {
|
|
30
|
-
sections.push(`Reference answer:\n${input.reference}`);
|
|
31
|
-
}
|
|
32
|
-
sections.push(`Assistant answered:\n${input.output}`);
|
|
33
|
-
sections.push(judge.goal);
|
|
34
|
-
sections.push('Respond with a score 0..1 and a brief reason.');
|
|
35
|
-
return sections.join('\n\n');
|
|
36
|
-
};
|
|
37
|
-
/**
|
|
38
|
-
* Clamp rather than reject: a model that answers 1.2 has still made a legible
|
|
39
|
-
* judgement, and failing the job would lose it. A non-number has not.
|
|
40
|
-
*/
|
|
41
|
-
const normalizeScore = (scorerName, value) => {
|
|
42
|
-
if (typeof value !== 'number' || Number.isNaN(value)) {
|
|
43
|
-
throw new Error(`Judge '${scorerName}' returned a non-numeric score: ${JSON.stringify(value)}`);
|
|
44
|
-
}
|
|
45
|
-
return Math.min(1, Math.max(0, value));
|
|
46
|
-
};
|
|
47
|
-
/**
|
|
48
|
-
* Run an LLM judge and force the `{ score, reason }` contract, so a scorer
|
|
49
|
-
* author writes a rubric rather than a parser.
|
|
50
|
-
*
|
|
51
|
-
* A judge has no tools, which is the one case where an output schema is
|
|
52
|
-
* reliably honoured.
|
|
53
|
-
*/
|
|
54
|
-
export const runJudge = async (scorer, input, aiAgentRunner) => {
|
|
55
|
-
const judge = scorer.judge;
|
|
56
|
-
if (!judge) {
|
|
57
|
-
throw new Error(`Scorer '${scorer.name}' is not a judge`);
|
|
58
|
-
}
|
|
59
|
-
if (!aiAgentRunner) {
|
|
60
|
-
throw new Error(`Judge '${scorer.name}' needs an AI provider, but no aiAgentRunner is registered in this process. ` +
|
|
61
|
-
`A worker deployed apart from the API has to register one to run the slow scoring lane.`);
|
|
62
|
-
}
|
|
63
|
-
if (scorer.requiresReference && input.reference === undefined) {
|
|
64
|
-
throw new Error(`Judge '${scorer.name}' grades against a reference answer, but none was supplied`);
|
|
65
|
-
}
|
|
66
|
-
const messages = [
|
|
67
|
-
{
|
|
68
|
-
id: randomUUID(),
|
|
69
|
-
role: 'user',
|
|
70
|
-
content: buildJudgePrompt(judge, input),
|
|
71
|
-
createdAt: new Date(),
|
|
72
|
-
},
|
|
73
|
-
];
|
|
74
|
-
const result = await aiAgentRunner.run({
|
|
75
|
-
model: judge.model,
|
|
76
|
-
instructions: judge.goal,
|
|
77
|
-
messages,
|
|
78
|
-
tools: [],
|
|
79
|
-
maxSteps: 1,
|
|
80
|
-
toolChoice: 'none',
|
|
81
|
-
outputSchema: JUDGE_OUTPUT_SCHEMA,
|
|
82
|
-
});
|
|
83
|
-
const object = (result.object ?? {});
|
|
84
|
-
return {
|
|
85
|
-
score: normalizeScore(scorer.name, object.score),
|
|
86
|
-
...(typeof object.reason === 'string' ? { reason: object.reason } : {}),
|
|
87
|
-
metadata: {
|
|
88
|
-
judgeModel: judge.model,
|
|
89
|
-
judgeTokens: result.usage.inputTokens + result.usage.outputTokens,
|
|
90
|
-
},
|
|
91
|
-
};
|
|
92
|
-
};
|