@warlock.js/ai 4.4.0 → 4.5.0
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 +52 -55
- package/cjs/index.cjs +221 -14147
- package/cjs/src-C02yzsLs.cjs +22991 -0
- package/cjs/src-C02yzsLs.cjs.map +1 -0
- package/cjs/src-DFibP2FQ.cjs +30 -0
- package/esm/agent/agent-config.type.d.mts +90 -1
- package/esm/agent/agent-config.type.d.mts.map +1 -1
- package/esm/agent/agent-input-builder.mjs +27 -6
- package/esm/agent/agent-input-builder.mjs.map +1 -1
- package/esm/agent/agent.d.mts +40 -1
- package/esm/agent/agent.d.mts.map +1 -1
- package/esm/agent/agent.mjs +203 -30
- package/esm/agent/agent.mjs.map +1 -1
- package/esm/agent/index.d.mts +2 -1
- package/esm/agent/index.mjs +1 -0
- package/esm/agent/judge-config.type.d.mts +33 -0
- package/esm/agent/judge-config.type.d.mts.map +1 -0
- package/esm/agent/judge-config.type.mjs +13 -0
- package/esm/agent/judge-config.type.mjs.map +1 -0
- package/esm/ai-openai/src/embedder.mjs +4 -0
- package/esm/ai-openai/src/index.mjs +4 -0
- package/esm/ai-openai/src/model.mjs +5 -0
- package/esm/ai-openai/src/sdk.mjs +6 -0
- package/esm/ai-openai/src/utils/index.mjs +4 -0
- package/esm/ai-openai/src/utils/to-openai-tools.mjs +3 -0
- package/esm/ai-openai/src/utils/wrap-openai-error.mjs +4 -0
- package/esm/ai.d.mts +119 -53
- package/esm/ai.d.mts.map +1 -1
- package/esm/ai.mjs +43 -8
- package/esm/ai.mjs.map +1 -1
- package/esm/batch/batch.d.mts.map +1 -1
- package/esm/batch/batch.mjs +21 -1
- package/esm/batch/batch.mjs.map +1 -1
- package/esm/batch/batch.type.d.mts +11 -4
- package/esm/batch/batch.type.d.mts.map +1 -1
- package/esm/config.d.mts +39 -3
- package/esm/config.d.mts.map +1 -1
- package/esm/config.mjs +26 -2
- package/esm/config.mjs.map +1 -1
- package/esm/contracts/agent/agent-options.type.d.mts +11 -3
- package/esm/contracts/agent/agent-options.type.d.mts.map +1 -1
- package/esm/contracts/agent/eval.type.d.mts +43 -2
- package/esm/contracts/agent/eval.type.d.mts.map +1 -1
- package/esm/contracts/attachment-policy.type.d.mts +51 -0
- package/esm/contracts/attachment-policy.type.d.mts.map +1 -0
- package/esm/contracts/attachment.type.d.mts +16 -7
- package/esm/contracts/attachment.type.d.mts.map +1 -1
- package/esm/contracts/content-part.type.d.mts +14 -5
- package/esm/contracts/content-part.type.d.mts.map +1 -1
- package/esm/contracts/events/supervisor-events.type.d.mts.map +1 -1
- package/esm/contracts/index.d.mts +6 -4
- package/esm/contracts/model.contract.d.mts +11 -8
- package/esm/contracts/model.contract.d.mts.map +1 -1
- package/esm/contracts/orchestrator/index.d.mts +1 -0
- package/esm/contracts/orchestrator/orchestrator-config.type.d.mts +31 -2
- package/esm/contracts/orchestrator/orchestrator-config.type.d.mts.map +1 -1
- package/esm/contracts/orchestrator/session-lock.contract.d.mts +47 -0
- package/esm/contracts/orchestrator/session-lock.contract.d.mts.map +1 -0
- package/esm/contracts/planner/index.d.mts +1 -1
- package/esm/contracts/planner/planner-config.type.d.mts +31 -0
- package/esm/contracts/planner/planner-config.type.d.mts.map +1 -1
- package/esm/contracts/planner/planner-execute-options.type.d.mts +48 -1
- package/esm/contracts/planner/planner-execute-options.type.d.mts.map +1 -1
- package/esm/contracts/planner/planner-result.type.d.mts +8 -0
- package/esm/contracts/planner/planner-result.type.d.mts.map +1 -1
- package/esm/contracts/planner/planner.contract.d.mts +1 -1
- package/esm/contracts/result/agent-result.type.d.mts +6 -4
- package/esm/contracts/result/agent-result.type.d.mts.map +1 -1
- package/esm/contracts/result/base-report.type.d.mts +21 -3
- package/esm/contracts/result/base-report.type.d.mts.map +1 -1
- package/esm/contracts/result/base-report.type.mjs.map +1 -1
- package/esm/contracts/result/execution-report.type.d.mts +53 -1
- package/esm/contracts/result/execution-report.type.d.mts.map +1 -1
- package/esm/contracts/result/supervisor-result.type.d.mts +14 -2
- package/esm/contracts/result/supervisor-result.type.d.mts.map +1 -1
- package/esm/contracts/result/workflow-result.type.d.mts +2 -1
- package/esm/contracts/result/workflow-result.type.d.mts.map +1 -1
- package/esm/contracts/supervisor/supervisor-config.type.d.mts +29 -0
- package/esm/contracts/supervisor/supervisor-config.type.d.mts.map +1 -1
- package/esm/contracts/system-prompt.contract.d.mts +103 -1
- package/esm/contracts/system-prompt.contract.d.mts.map +1 -1
- package/esm/contracts/team/index.d.mts +1 -0
- package/esm/contracts/team/team-config.type.d.mts +127 -0
- package/esm/contracts/team/team-config.type.d.mts.map +1 -0
- package/esm/contracts/tool.contract.d.mts +4 -2
- package/esm/contracts/tool.contract.d.mts.map +1 -1
- package/esm/contracts/workflow/step.contract.d.mts +30 -5
- package/esm/contracts/workflow/step.contract.d.mts.map +1 -1
- package/esm/contracts/workflow/workflow.contract.d.mts +16 -0
- package/esm/contracts/workflow/workflow.contract.d.mts.map +1 -1
- package/esm/errors/error-code.type.d.mts +1 -1
- package/esm/errors/index.d.mts +1 -0
- package/esm/errors/index.mjs +1 -0
- package/esm/errors/outbound-policy-error.d.mts +27 -0
- package/esm/errors/outbound-policy-error.d.mts.map +1 -0
- package/esm/errors/outbound-policy-error.mjs +32 -0
- package/esm/errors/outbound-policy-error.mjs.map +1 -0
- package/esm/eval/dataset.d.mts +28 -0
- package/esm/eval/dataset.d.mts.map +1 -0
- package/esm/eval/dataset.mjs +112 -0
- package/esm/eval/dataset.mjs.map +1 -0
- package/esm/eval/dataset.type.d.mts +53 -0
- package/esm/eval/dataset.type.d.mts.map +1 -0
- package/esm/eval/eval-runner.d.mts.map +1 -1
- package/esm/eval/eval-runner.mjs +16 -2
- package/esm/eval/eval-runner.mjs.map +1 -1
- package/esm/eval/index.d.mts +20 -1
- package/esm/eval/index.d.mts.map +1 -1
- package/esm/eval/index.mjs +20 -2
- package/esm/eval/index.mjs.map +1 -1
- package/esm/eval/regression.d.mts +30 -0
- package/esm/eval/regression.d.mts.map +1 -0
- package/esm/eval/regression.mjs +51 -0
- package/esm/eval/regression.mjs.map +1 -0
- package/esm/eval/report-json.d.mts +30 -0
- package/esm/eval/report-json.d.mts.map +1 -0
- package/esm/eval/report-json.mjs +33 -0
- package/esm/eval/report-json.mjs.map +1 -0
- package/esm/eval/report-junit.d.mts +22 -0
- package/esm/eval/report-junit.d.mts.map +1 -0
- package/esm/eval/report-junit.mjs +60 -0
- package/esm/eval/report-junit.mjs.map +1 -0
- package/esm/guard/contracts/guard-options.type.d.mts +165 -0
- package/esm/guard/contracts/guard-options.type.d.mts.map +1 -0
- package/esm/guard/contracts/guardrail.contract.d.mts +78 -0
- package/esm/guard/contracts/guardrail.contract.d.mts.map +1 -0
- package/esm/guard/contracts/index.d.mts +4 -0
- package/esm/guard/contracts/openai-client.contract.d.mts +51 -0
- package/esm/guard/contracts/openai-client.contract.d.mts.map +1 -0
- package/esm/guard/contracts/verdict.type.d.mts +76 -0
- package/esm/guard/contracts/verdict.type.d.mts.map +1 -0
- package/esm/guard/detectors/index.d.mts +4 -0
- package/esm/guard/detectors/index.mjs +6 -0
- package/esm/guard/detectors/injection.d.mts +34 -0
- package/esm/guard/detectors/injection.d.mts.map +1 -0
- package/esm/guard/detectors/injection.mjs +254 -0
- package/esm/guard/detectors/injection.mjs.map +1 -0
- package/esm/guard/detectors/moderation.d.mts +32 -0
- package/esm/guard/detectors/moderation.d.mts.map +1 -0
- package/esm/guard/detectors/moderation.mjs +134 -0
- package/esm/guard/detectors/moderation.mjs.map +1 -0
- package/esm/guard/detectors/pii.d.mts +41 -0
- package/esm/guard/detectors/pii.d.mts.map +1 -0
- package/esm/guard/detectors/pii.mjs +199 -0
- package/esm/guard/detectors/pii.mjs.map +1 -0
- package/esm/guard/detectors/topic.d.mts +29 -0
- package/esm/guard/detectors/topic.d.mts.map +1 -0
- package/esm/guard/detectors/topic.mjs +99 -0
- package/esm/guard/detectors/topic.mjs.map +1 -0
- package/esm/guard/errors.d.mts +12 -0
- package/esm/guard/errors.d.mts.map +1 -0
- package/esm/guard/errors.mjs +18 -0
- package/esm/guard/errors.mjs.map +1 -0
- package/esm/guard/guard.d.mts +67 -0
- package/esm/guard/guard.d.mts.map +1 -0
- package/esm/guard/guard.mjs +209 -0
- package/esm/guard/guard.mjs.map +1 -0
- package/esm/guard/guardrail.d.mts +39 -0
- package/esm/guard/guardrail.d.mts.map +1 -0
- package/esm/guard/guardrail.mjs +22 -0
- package/esm/guard/guardrail.mjs.map +1 -0
- package/esm/human/contracts/approval.type.d.mts +154 -0
- package/esm/human/contracts/approval.type.d.mts.map +1 -0
- package/esm/human/contracts/human-approval.type.d.mts +38 -0
- package/esm/human/contracts/human-approval.type.d.mts.map +1 -0
- package/esm/human/contracts/index.d.mts +4 -0
- package/esm/human/contracts/interrupt-store.contract.d.mts +112 -0
- package/esm/human/contracts/interrupt-store.contract.d.mts.map +1 -0
- package/esm/human/contracts/resume.type.d.mts +77 -0
- package/esm/human/contracts/resume.type.d.mts.map +1 -0
- package/esm/human/errors.d.mts +84 -0
- package/esm/human/errors.d.mts.map +1 -0
- package/esm/human/errors.mjs +60 -0
- package/esm/human/errors.mjs.map +1 -0
- package/esm/human/human-approval.d.mts +57 -0
- package/esm/human/human-approval.d.mts.map +1 -0
- package/esm/human/human-approval.mjs +170 -0
- package/esm/human/human-approval.mjs.map +1 -0
- package/esm/human/policy.d.mts +55 -0
- package/esm/human/policy.d.mts.map +1 -0
- package/esm/human/policy.mjs +67 -0
- package/esm/human/policy.mjs.map +1 -0
- package/esm/human/register.mjs +37 -0
- package/esm/human/register.mjs.map +1 -0
- package/esm/human/resume-seed.mjs +53 -0
- package/esm/human/resume-seed.mjs.map +1 -0
- package/esm/human/resume.d.mts +54 -0
- package/esm/human/resume.d.mts.map +1 -0
- package/esm/human/resume.mjs +121 -0
- package/esm/human/resume.mjs.map +1 -0
- package/esm/human/stores/index.mjs +5 -0
- package/esm/human/stores/memory.d.mts +30 -0
- package/esm/human/stores/memory.d.mts.map +1 -0
- package/esm/human/stores/memory.mjs +91 -0
- package/esm/human/stores/memory.mjs.map +1 -0
- package/esm/human/stores/pg.d.mts +59 -0
- package/esm/human/stores/pg.d.mts.map +1 -0
- package/esm/human/stores/pg.mjs +220 -0
- package/esm/human/stores/pg.mjs.map +1 -0
- package/esm/human/stores/redis.d.mts +56 -0
- package/esm/human/stores/redis.d.mts.map +1 -0
- package/esm/human/stores/redis.mjs +201 -0
- package/esm/human/stores/redis.mjs.map +1 -0
- package/esm/index.d.mts +113 -22
- package/esm/index.mjs +82 -11
- package/esm/middleware/builtins/budget.mjs +6 -2
- package/esm/middleware/builtins/budget.mjs.map +1 -1
- package/esm/middleware/utils/extract-user-text.d.mts +8 -1
- package/esm/middleware/utils/extract-user-text.d.mts.map +1 -1
- package/esm/middleware/utils/extract-user-text.mjs +8 -1
- package/esm/middleware/utils/extract-user-text.mjs.map +1 -1
- package/esm/object-stream/index.d.mts +2 -0
- package/esm/object-stream/index.mjs +4 -0
- package/esm/object-stream/parse-partial-json.d.mts +22 -0
- package/esm/object-stream/parse-partial-json.d.mts.map +1 -0
- package/esm/object-stream/parse-partial-json.mjs +78 -0
- package/esm/object-stream/parse-partial-json.mjs.map +1 -0
- package/esm/object-stream/stream-object.d.mts +68 -0
- package/esm/object-stream/stream-object.d.mts.map +1 -0
- package/esm/object-stream/stream-object.mjs +104 -0
- package/esm/object-stream/stream-object.mjs.map +1 -0
- package/esm/observe/index.mjs +4 -0
- package/esm/observe/observer-registry.d.mts +30 -0
- package/esm/observe/observer-registry.d.mts.map +1 -0
- package/esm/observe/observer-registry.mjs +51 -0
- package/esm/observe/observer-registry.mjs.map +1 -0
- package/esm/observe/observer.contract.d.mts +40 -0
- package/esm/observe/observer.contract.d.mts.map +1 -0
- package/esm/observe/resolve-observers.d.mts +40 -0
- package/esm/observe/resolve-observers.d.mts.map +1 -0
- package/esm/observe/resolve-observers.mjs +73 -0
- package/esm/observe/resolve-observers.mjs.map +1 -0
- package/esm/orchestrator/execution.d.mts.map +1 -1
- package/esm/orchestrator/execution.mjs +5 -2
- package/esm/orchestrator/execution.mjs.map +1 -1
- package/esm/orchestrator/index.d.mts +1 -0
- package/esm/orchestrator/index.mjs +1 -0
- package/esm/orchestrator/orchestrator.d.mts.map +1 -1
- package/esm/orchestrator/orchestrator.mjs +39 -6
- package/esm/orchestrator/orchestrator.mjs.map +1 -1
- package/esm/orchestrator/session-lock.d.mts +25 -0
- package/esm/orchestrator/session-lock.d.mts.map +1 -0
- package/esm/orchestrator/session-lock.mjs +83 -0
- package/esm/orchestrator/session-lock.mjs.map +1 -0
- package/esm/planner/dag-scheduler.mjs +97 -0
- package/esm/planner/dag-scheduler.mjs.map +1 -0
- package/esm/planner/plan-prompt.d.mts +1 -1
- package/esm/planner/plan-prompt.d.mts.map +1 -1
- package/esm/planner/plan-prompt.mjs +2 -1
- package/esm/planner/plan-prompt.mjs.map +1 -1
- package/esm/planner/planner-run.d.mts.map +1 -1
- package/esm/planner/planner-run.mjs +300 -29
- package/esm/planner/planner-run.mjs.map +1 -1
- package/esm/planner/planner.mjs +1 -1
- package/esm/planner/planner.mjs.map +1 -1
- package/esm/prompt/errors.d.mts +57 -0
- package/esm/prompt/errors.d.mts.map +1 -0
- package/esm/prompt/errors.mjs +73 -0
- package/esm/prompt/errors.mjs.map +1 -0
- package/esm/prompt/index.d.mts +3 -0
- package/esm/prompt/index.mjs +4 -0
- package/esm/prompt/prompt-langfuse-sync.mjs +104 -0
- package/esm/prompt/prompt-langfuse-sync.mjs.map +1 -0
- package/esm/prompt/prompt-langfuse-sync.type.d.mts +32 -0
- package/esm/prompt/prompt-langfuse-sync.type.d.mts.map +1 -0
- package/esm/prompt/prompt-validate.mjs +170 -0
- package/esm/prompt/prompt-validate.mjs.map +1 -0
- package/esm/prompt/prompt.d.mts +54 -0
- package/esm/prompt/prompt.d.mts.map +1 -0
- package/esm/prompt/prompt.mjs +218 -0
- package/esm/prompt/prompt.mjs.map +1 -0
- package/esm/prompt/prompt.type.d.mts +174 -0
- package/esm/prompt/prompt.type.d.mts.map +1 -0
- package/esm/prompts/index.d.mts +3 -0
- package/esm/prompts/index.mjs +3 -0
- package/esm/prompts/prompts-manager.contract.d.mts +154 -0
- package/esm/prompts/prompts-manager.contract.d.mts.map +1 -0
- package/esm/prompts/prompts-manager.d.mts +38 -0
- package/esm/prompts/prompts-manager.d.mts.map +1 -0
- package/esm/prompts/prompts-manager.mjs +410 -0
- package/esm/prompts/prompts-manager.mjs.map +1 -0
- package/esm/prompts/prompts-manager.type.d.mts +172 -0
- package/esm/prompts/prompts-manager.type.d.mts.map +1 -0
- package/esm/prompts/prompts-validate.mjs +200 -0
- package/esm/prompts/prompts-validate.mjs.map +1 -0
- package/esm/rag/as-tool.mjs +48 -0
- package/esm/rag/as-tool.mjs.map +1 -0
- package/esm/rag/chunk/chunk.d.mts +24 -0
- package/esm/rag/chunk/chunk.d.mts.map +1 -0
- package/esm/rag/chunk/chunk.mjs +44 -0
- package/esm/rag/chunk/chunk.mjs.map +1 -0
- package/esm/rag/chunk/fixed.mjs +32 -0
- package/esm/rag/chunk/fixed.mjs.map +1 -0
- package/esm/rag/chunk/markdown.mjs +75 -0
- package/esm/rag/chunk/markdown.mjs.map +1 -0
- package/esm/rag/chunk/recursive.mjs +132 -0
- package/esm/rag/chunk/recursive.mjs.map +1 -0
- package/esm/rag/chunk/sentence.mjs +73 -0
- package/esm/rag/chunk/sentence.mjs.map +1 -0
- package/esm/rag/contracts/chunk-options.type.d.mts +35 -0
- package/esm/rag/contracts/chunk-options.type.d.mts.map +1 -0
- package/esm/rag/contracts/citation.type.d.mts +35 -0
- package/esm/rag/contracts/citation.type.d.mts.map +1 -0
- package/esm/rag/contracts/index.d.mts +4 -0
- package/esm/rag/contracts/rag-config.type.d.mts +68 -0
- package/esm/rag/contracts/rag-config.type.d.mts.map +1 -0
- package/esm/rag/contracts/rag-document.type.d.mts +21 -0
- package/esm/rag/contracts/rag-document.type.d.mts.map +1 -0
- package/esm/rag/hybrid/bm25.d.mts +23 -0
- package/esm/rag/hybrid/bm25.d.mts.map +1 -0
- package/esm/rag/hybrid/bm25.mjs +51 -0
- package/esm/rag/hybrid/bm25.mjs.map +1 -0
- package/esm/rag/hybrid/hybrid-rank.d.mts +33 -0
- package/esm/rag/hybrid/hybrid-rank.d.mts.map +1 -0
- package/esm/rag/hybrid/hybrid-rank.mjs +29 -0
- package/esm/rag/hybrid/hybrid-rank.mjs.map +1 -0
- package/esm/rag/hybrid/rrf.d.mts +25 -0
- package/esm/rag/hybrid/rrf.d.mts.map +1 -0
- package/esm/rag/hybrid/rrf.mjs +30 -0
- package/esm/rag/hybrid/rrf.mjs.map +1 -0
- package/esm/rag/index.d.mts +15 -0
- package/esm/rag/index.mjs +11 -0
- package/esm/rag/rag.d.mts +38 -0
- package/esm/rag/rag.d.mts.map +1 -0
- package/esm/rag/rag.mjs +126 -0
- package/esm/rag/rag.mjs.map +1 -0
- package/esm/rag/rerank/keyword-reranker.d.mts +32 -0
- package/esm/rag/rerank/keyword-reranker.d.mts.map +1 -0
- package/esm/rag/rerank/keyword-reranker.mjs +58 -0
- package/esm/rag/rerank/keyword-reranker.mjs.map +1 -0
- package/esm/rag/rerank/llm-reranker.d.mts +36 -0
- package/esm/rag/rerank/llm-reranker.d.mts.map +1 -0
- package/esm/rag/rerank/llm-reranker.mjs +85 -0
- package/esm/rag/rerank/llm-reranker.mjs.map +1 -0
- package/esm/rag/rerank/reranker.contract.d.mts +28 -0
- package/esm/rag/rerank/reranker.contract.d.mts.map +1 -0
- package/esm/rag/retrieve.mjs +68 -0
- package/esm/rag/retrieve.mjs.map +1 -0
- package/esm/rag/store/cache-vector-store.d.mts +27 -0
- package/esm/rag/store/cache-vector-store.d.mts.map +1 -0
- package/esm/rag/store/cache-vector-store.mjs +48 -0
- package/esm/rag/store/cache-vector-store.mjs.map +1 -0
- package/esm/rag/store/vector-store.contract.d.mts +38 -0
- package/esm/rag/store/vector-store.contract.d.mts.map +1 -0
- package/esm/rag/transforms/multi-query.d.mts +27 -0
- package/esm/rag/transforms/multi-query.d.mts.map +1 -0
- package/esm/rag/transforms/multi-query.mjs +41 -0
- package/esm/rag/transforms/multi-query.mjs.map +1 -0
- package/esm/security/index.mjs +5 -0
- package/esm/security/outbound-policy.d.mts +46 -0
- package/esm/security/outbound-policy.d.mts.map +1 -0
- package/esm/security/outbound-policy.mjs +187 -0
- package/esm/security/outbound-policy.mjs.map +1 -0
- package/esm/security/outbound-policy.type.d.mts +74 -0
- package/esm/security/outbound-policy.type.d.mts.map +1 -0
- package/esm/security/private-ip.d.mts +15 -0
- package/esm/security/private-ip.d.mts.map +1 -0
- package/esm/security/private-ip.mjs +48 -0
- package/esm/security/private-ip.mjs.map +1 -0
- package/esm/security/redact.d.mts +59 -0
- package/esm/security/redact.d.mts.map +1 -0
- package/esm/security/redact.mjs +122 -0
- package/esm/security/redact.mjs.map +1 -0
- package/esm/serve/serve.d.mts +50 -0
- package/esm/serve/serve.d.mts.map +1 -0
- package/esm/serve/serve.mjs +90 -0
- package/esm/serve/serve.mjs.map +1 -0
- package/esm/serve/sse.d.mts +20 -0
- package/esm/serve/sse.d.mts.map +1 -0
- package/esm/serve/sse.mjs +25 -0
- package/esm/serve/sse.mjs.map +1 -0
- package/esm/serve/stream-to-sse.d.mts +29 -0
- package/esm/serve/stream-to-sse.d.mts.map +1 -0
- package/esm/serve/stream-to-sse.mjs +37 -0
- package/esm/serve/stream-to-sse.mjs.map +1 -0
- package/esm/skills/catalog.d.mts +49 -0
- package/esm/skills/catalog.d.mts.map +1 -0
- package/esm/skills/catalog.mjs +140 -0
- package/esm/skills/catalog.mjs.map +1 -0
- package/esm/skills/contracts/skill-record.type.d.mts +37 -0
- package/esm/skills/contracts/skill-record.type.d.mts.map +1 -0
- package/esm/skills/contracts/skills-config.type.d.mts +108 -0
- package/esm/skills/contracts/skills-config.type.d.mts.map +1 -0
- package/esm/skills/contracts/skills-store.contract.d.mts +28 -0
- package/esm/skills/contracts/skills-store.contract.d.mts.map +1 -0
- package/esm/skills/contracts/skills.contract.d.mts +43 -0
- package/esm/skills/contracts/skills.contract.d.mts.map +1 -0
- package/esm/skills/index.d.mts +16 -0
- package/esm/skills/index.mjs +14 -0
- package/esm/skills/load-skill-tool.d.mts +38 -0
- package/esm/skills/load-skill-tool.d.mts.map +1 -0
- package/esm/skills/load-skill-tool.mjs +65 -0
- package/esm/skills/load-skill-tool.mjs.map +1 -0
- package/esm/skills/review-gate.d.mts +33 -0
- package/esm/skills/review-gate.d.mts.map +1 -0
- package/esm/skills/review-gate.mjs +60 -0
- package/esm/skills/review-gate.mjs.map +1 -0
- package/esm/skills/save-skill-tool.d.mts +39 -0
- package/esm/skills/save-skill-tool.d.mts.map +1 -0
- package/esm/skills/save-skill-tool.mjs +65 -0
- package/esm/skills/save-skill-tool.mjs.map +1 -0
- package/esm/skills/skills.d.mts +33 -0
- package/esm/skills/skills.d.mts.map +1 -0
- package/esm/skills/skills.mjs +109 -0
- package/esm/skills/skills.mjs.map +1 -0
- package/esm/skills/sources/directory-source.d.mts +19 -0
- package/esm/skills/sources/directory-source.d.mts.map +1 -0
- package/esm/skills/sources/directory-source.mjs +108 -0
- package/esm/skills/sources/directory-source.mjs.map +1 -0
- package/esm/skills/sources/index.d.mts +18 -0
- package/esm/skills/sources/index.d.mts.map +1 -0
- package/esm/skills/sources/index.mjs +27 -0
- package/esm/skills/sources/index.mjs.map +1 -0
- package/esm/skills/sources/parse-frontmatter.d.mts +27 -0
- package/esm/skills/sources/parse-frontmatter.d.mts.map +1 -0
- package/esm/skills/sources/parse-frontmatter.mjs +46 -0
- package/esm/skills/sources/parse-frontmatter.mjs.map +1 -0
- package/esm/skills/sources/store-source.d.mts +14 -0
- package/esm/skills/sources/store-source.d.mts.map +1 -0
- package/esm/skills/sources/store-source.mjs +15 -0
- package/esm/skills/sources/store-source.mjs.map +1 -0
- package/esm/skills/sources/url-source.d.mts +29 -0
- package/esm/skills/sources/url-source.d.mts.map +1 -0
- package/esm/skills/sources/url-source.mjs +117 -0
- package/esm/skills/sources/url-source.mjs.map +1 -0
- package/esm/skills/store/mock-skills-store.d.mts +57 -0
- package/esm/skills/store/mock-skills-store.d.mts.map +1 -0
- package/esm/skills/store/mock-skills-store.mjs +100 -0
- package/esm/skills/store/mock-skills-store.mjs.map +1 -0
- package/esm/skills/store/procedural-skill-store.d.mts +30 -0
- package/esm/skills/store/procedural-skill-store.d.mts.map +1 -0
- package/esm/skills/store/procedural-skill-store.mjs +125 -0
- package/esm/skills/store/procedural-skill-store.mjs.map +1 -0
- package/esm/supervisor/as-tool.mjs +2 -2
- package/esm/supervisor/as-tool.mjs.map +1 -1
- package/esm/supervisor/execution.d.mts.map +1 -1
- package/esm/supervisor/execution.mjs +31 -28
- package/esm/supervisor/execution.mjs.map +1 -1
- package/esm/supervisor/supervisor.d.mts.map +1 -1
- package/esm/supervisor/supervisor.mjs +8 -3
- package/esm/supervisor/supervisor.mjs.map +1 -1
- package/esm/system-prompt/index.d.mts +4 -0
- package/esm/system-prompt/system-prompt.d.mts +68 -4
- package/esm/system-prompt/system-prompt.d.mts.map +1 -1
- package/esm/system-prompt/system-prompt.mjs +89 -5
- package/esm/system-prompt/system-prompt.mjs.map +1 -1
- package/esm/team/gates.mjs +48 -0
- package/esm/team/gates.mjs.map +1 -0
- package/esm/team/index.d.mts +1 -0
- package/esm/team/index.mjs +3 -0
- package/esm/team/team.d.mts +42 -0
- package/esm/team/team.d.mts.map +1 -0
- package/esm/team/team.mjs +94 -0
- package/esm/team/team.mjs.map +1 -0
- package/esm/tool/executable-as-tool.d.mts.map +1 -1
- package/esm/tool/executable-as-tool.mjs +2 -2
- package/esm/tool/executable-as-tool.mjs.map +1 -1
- package/esm/tool/tool.d.mts.map +1 -1
- package/esm/tool/tool.mjs +2 -2
- package/esm/tool/tool.mjs.map +1 -1
- package/esm/utils/compute-cost.d.mts +17 -1
- package/esm/utils/compute-cost.d.mts.map +1 -1
- package/esm/utils/compute-cost.mjs +26 -1
- package/esm/utils/compute-cost.mjs.map +1 -1
- package/esm/utils/extract-json-lenient.d.mts +42 -0
- package/esm/utils/extract-json-lenient.d.mts.map +1 -0
- package/esm/utils/extract-json-lenient.mjs +97 -0
- package/esm/utils/extract-json-lenient.mjs.map +1 -0
- package/esm/utils/index.d.mts +4 -2
- package/esm/utils/index.mjs +3 -1
- package/esm/utils/json-schema.d.mts +1 -1
- package/esm/utils/prepare-attachment-part.d.mts +10 -1
- package/esm/utils/prepare-attachment-part.d.mts.map +1 -1
- package/esm/utils/prepare-attachment-part.mjs +103 -11
- package/esm/utils/prepare-attachment-part.mjs.map +1 -1
- package/esm/utils/resolve-attachment.d.mts +4 -3
- package/esm/utils/resolve-attachment.d.mts.map +1 -1
- package/esm/utils/resolve-attachment.mjs +4 -3
- package/esm/utils/resolve-attachment.mjs.map +1 -1
- package/esm/utils/run-context.d.mts +94 -0
- package/esm/utils/run-context.d.mts.map +1 -0
- package/esm/utils/run-context.mjs +98 -0
- package/esm/utils/run-context.mjs.map +1 -0
- package/esm/vcr/cassette-io.mjs +57 -0
- package/esm/vcr/cassette-io.mjs.map +1 -0
- package/esm/vcr/errors.d.mts +42 -0
- package/esm/vcr/errors.d.mts.map +1 -0
- package/esm/vcr/errors.mjs +37 -0
- package/esm/vcr/errors.mjs.map +1 -0
- package/esm/vcr/hash-request.d.mts +28 -0
- package/esm/vcr/hash-request.d.mts.map +1 -0
- package/esm/vcr/hash-request.mjs +118 -0
- package/esm/vcr/hash-request.mjs.map +1 -0
- package/esm/vcr/index.d.mts +4 -0
- package/esm/vcr/index.mjs +5 -0
- package/esm/vcr/vcr.d.mts +32 -0
- package/esm/vcr/vcr.d.mts.map +1 -0
- package/esm/vcr/vcr.mjs +248 -0
- package/esm/vcr/vcr.mjs.map +1 -0
- package/esm/vcr/vcr.type.d.mts +118 -0
- package/esm/vcr/vcr.type.d.mts.map +1 -0
- package/esm/workflow/as-tool.mjs +2 -2
- package/esm/workflow/as-tool.mjs.map +1 -1
- package/esm/workflow/engine.mjs +1 -0
- package/esm/workflow/engine.mjs.map +1 -1
- package/esm/workflow/step-runner.mjs +19 -20
- package/esm/workflow/step-runner.mjs.map +1 -1
- package/esm/workflow/workflow.d.mts.map +1 -1
- package/esm/workflow/workflow.mjs +8 -3
- package/esm/workflow/workflow.mjs.map +1 -1
- package/llms-full.txt +1683 -68
- package/llms.txt +16 -4
- package/package.json +7 -3
- package/skills/README.md +40 -4
- package/skills/ai-dx-helpers/SKILL.md +2 -2
- package/skills/approve-tool-calls/SKILL.md +134 -0
- package/skills/attach-ai-middleware/SKILL.md +1 -1
- package/skills/detect-and-redact-pii/SKILL.md +104 -0
- package/skills/durable-resume/SKILL.md +128 -0
- package/skills/escalate-block-to-human/SKILL.md +85 -0
- package/skills/eval-datasets-and-ci/SKILL.md +117 -0
- package/skills/guard-input-output/SKILL.md +117 -0
- package/skills/manage-prompts/SKILL.md +186 -0
- package/skills/observe-ai-flows/SKILL.md +94 -0
- package/skills/record-replay-llm/SKILL.md +92 -0
- package/skills/run-ai-agent/SKILL.md +26 -1
- package/skills/run-ai-rag/SKILL.md +139 -0
- package/skills/run-ai-team/SKILL.md +107 -0
- package/skills/run-orchestrator/SKILL.md +2 -0
- package/skills/run-planner/SKILL.md +73 -8
- package/skills/run-supervisor/SKILL.md +20 -1
- package/skills/use-runtime-skills/SKILL.md +106 -0
- package/skills/write-system-prompt/SKILL.md +30 -1
- package/cjs/index.cjs.map +0 -1
- package/esm/contracts/result/index.d.mts +0 -15
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
//#region ../@warlock.js/ai/src/human/stores/pg.ts
|
|
2
|
+
/**
|
|
3
|
+
* Default backing table — provisions the store with no extra config when
|
|
4
|
+
* the dev runs {@link InterruptStore.schema} through their migration tool.
|
|
5
|
+
*/
|
|
6
|
+
const DEFAULT_TABLE = "warlock_ai_human_interrupts";
|
|
7
|
+
/**
|
|
8
|
+
* Allowed characters in a Postgres identifier (table name). The table name
|
|
9
|
+
* is interpolated into DDL/DML, so anything outside this conservative
|
|
10
|
+
* ASCII subset is rejected — interpolating an arbitrary string would be a
|
|
11
|
+
* SQL-injection footgun (mirrors `@warlock.js/ai`'s pg stores).
|
|
12
|
+
*/
|
|
13
|
+
const SAFE_IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
14
|
+
/**
|
|
15
|
+
* Module specifier for the optional `pg` driver. Held in a `string`
|
|
16
|
+
* variable so the dynamic `import()` is not statically resolved at
|
|
17
|
+
* compile time — `pg` is an optional peer that need not be installed for
|
|
18
|
+
* this package to type-check or for a memory-only consumer to run.
|
|
19
|
+
*/
|
|
20
|
+
const PG_MODULE = "pg";
|
|
21
|
+
/**
|
|
22
|
+
* Curated install string surfaced (at use time) when a `connectionString`
|
|
23
|
+
* is configured but the optional `pg` driver is absent. Never thrown at
|
|
24
|
+
* import — a memory-only consumer must be able to load this module.
|
|
25
|
+
*/
|
|
26
|
+
const PG_INSTALL_INSTRUCTIONS = `
|
|
27
|
+
The @warlock.js/ai Postgres interrupt store requires the pg package.
|
|
28
|
+
Install it with:
|
|
29
|
+
|
|
30
|
+
npm install pg
|
|
31
|
+
|
|
32
|
+
Or with your preferred package manager:
|
|
33
|
+
|
|
34
|
+
pnpm add pg
|
|
35
|
+
yarn add pg
|
|
36
|
+
`.trim();
|
|
37
|
+
/**
|
|
38
|
+
* Lazily import `pg` and return a `Pool` built from `connectionString`. A
|
|
39
|
+
* bare `catch` rethrows the curated install string — a missing optional
|
|
40
|
+
* peer surfaces as actionable guidance, never a raw resolution error.
|
|
41
|
+
*/
|
|
42
|
+
async function buildPgClient(connectionString) {
|
|
43
|
+
let sdk;
|
|
44
|
+
try {
|
|
45
|
+
sdk = await import(PG_MODULE);
|
|
46
|
+
} catch {
|
|
47
|
+
throw new Error(PG_INSTALL_INSTRUCTIONS);
|
|
48
|
+
}
|
|
49
|
+
return new sdk.Pool({ connectionString });
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Coerce a Postgres timestamp/text column to an ISO string. `pg` returns
|
|
53
|
+
* `TIMESTAMPTZ` as a `Date`; normalize to the ISO wire shape the
|
|
54
|
+
* {@link PendingInterrupt} contract declares.
|
|
55
|
+
*/
|
|
56
|
+
function toIso(value) {
|
|
57
|
+
if (value instanceof Date) return value.toISOString();
|
|
58
|
+
return value;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Map a raw DB row to a {@link PendingInterrupt}. Column names match the
|
|
62
|
+
* reference DDL 1:1; the `request` JSONB rides one column, so it is parsed
|
|
63
|
+
* defensively (node-postgres parses `JSONB` already, but some pool
|
|
64
|
+
* wrappers hand back the raw string).
|
|
65
|
+
*/
|
|
66
|
+
function rowToRecord(row) {
|
|
67
|
+
const request = typeof row.request === "string" ? JSON.parse(row.request) : row.request;
|
|
68
|
+
return {
|
|
69
|
+
interruptId: row.interrupt_id,
|
|
70
|
+
request,
|
|
71
|
+
status: row.status,
|
|
72
|
+
savedAt: toIso(row.saved_at)
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Postgres-backed {@link InterruptStore} — one durable row per pending
|
|
77
|
+
* interrupt, keyed by `interrupt_id`.
|
|
78
|
+
*
|
|
79
|
+
* Owns: durable round-tripping of the {@link PendingInterrupt} envelope so
|
|
80
|
+
* a reviewer can rule out-of-process (a webhook approves hours later, in a
|
|
81
|
+
* different process), the reference DDL via {@link PgInterruptStore.schema},
|
|
82
|
+
* and prefix-filtered enumeration. Does NOT own: the connection lifecycle
|
|
83
|
+
* (a dev-supplied client is never closed; a store-built `Pool` from a
|
|
84
|
+
* `connectionString` is also left open for the process to reuse) or schema
|
|
85
|
+
* migration (the dev runs `schema()` through their own tool — never
|
|
86
|
+
* auto-migrated).
|
|
87
|
+
*
|
|
88
|
+
* Like the snapshot store, a call has exactly one live interrupt, so
|
|
89
|
+
* `save()` upserts on the `interrupt_id` primary key.
|
|
90
|
+
*
|
|
91
|
+
* Front it with the {@link pg} factory — callers never `new` it.
|
|
92
|
+
*/
|
|
93
|
+
var PgInterruptStore = class {
|
|
94
|
+
constructor(options) {
|
|
95
|
+
const table = options.table ?? DEFAULT_TABLE;
|
|
96
|
+
if (!SAFE_IDENTIFIER.test(table)) throw new TypeError(`ai.human.interrupt.pg: invalid table name '${table}'. Allowed: [A-Za-z_][A-Za-z0-9_]*.`);
|
|
97
|
+
this.table = table;
|
|
98
|
+
if (options.client) {
|
|
99
|
+
if (typeof options.client.query !== "function") throw new TypeError("ai.human.interrupt.pg requires a 'client' option implementing { query(text, params) } — pass a pg.Pool or pg.Client.");
|
|
100
|
+
this.clientPromise = Promise.resolve(options.client);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
if (options.connectionString) {
|
|
104
|
+
this.clientPromise = buildPgClient(options.connectionString);
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
throw new TypeError("ai.human.interrupt.pg requires either a 'client' or a 'connectionString' option.");
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Resolve the backing client, surfacing the lazy `pg` import's curated
|
|
111
|
+
* install string on the first call that needs it.
|
|
112
|
+
*/
|
|
113
|
+
client() {
|
|
114
|
+
return this.clientPromise;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Persist a pending interrupt, keyed by its own `interrupt_id`. Upserts
|
|
118
|
+
* — a call has exactly one live interrupt, so a second save for the same
|
|
119
|
+
* id overwrites the payload rather than appending.
|
|
120
|
+
*/
|
|
121
|
+
async save(record) {
|
|
122
|
+
await (await this.client()).query(`INSERT INTO ${this.table} (interrupt_id, request, status, saved_at)
|
|
123
|
+
VALUES ($1, $2::jsonb, $3, $4)
|
|
124
|
+
ON CONFLICT (interrupt_id) DO UPDATE
|
|
125
|
+
SET request = EXCLUDED.request,
|
|
126
|
+
status = EXCLUDED.status,
|
|
127
|
+
saved_at = EXCLUDED.saved_at`, [
|
|
128
|
+
record.interruptId,
|
|
129
|
+
JSON.stringify(record.request),
|
|
130
|
+
record.status,
|
|
131
|
+
record.savedAt
|
|
132
|
+
]);
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Load the interrupt for an `interruptId`, or `undefined` when none is
|
|
136
|
+
* recorded.
|
|
137
|
+
*/
|
|
138
|
+
async load(interruptId) {
|
|
139
|
+
const { rows } = await (await this.client()).query(`SELECT interrupt_id, request, status, saved_at
|
|
140
|
+
FROM ${this.table}
|
|
141
|
+
WHERE interrupt_id = $1`, [interruptId]);
|
|
142
|
+
if (rows.length === 0) return;
|
|
143
|
+
return rowToRecord(rows[0]);
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Drop the interrupt for an `interruptId`. Idempotent — deleting an
|
|
147
|
+
* absent id deletes zero rows.
|
|
148
|
+
*/
|
|
149
|
+
async delete(interruptId) {
|
|
150
|
+
await (await this.client()).query(`DELETE FROM ${this.table} WHERE interrupt_id = $1`, [interruptId]);
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* List the interrupt ids known to the store, optionally filtered by a
|
|
154
|
+
* prefix. The `_` and `%` LIKE wildcards in the prefix are escaped so an
|
|
155
|
+
* opaque interruptId that happens to contain them is matched literally.
|
|
156
|
+
*/
|
|
157
|
+
async list(prefix) {
|
|
158
|
+
const client = await this.client();
|
|
159
|
+
if (prefix === void 0) {
|
|
160
|
+
const { rows } = await client.query(`SELECT interrupt_id FROM ${this.table}`);
|
|
161
|
+
return rows.map((row) => row.interrupt_id);
|
|
162
|
+
}
|
|
163
|
+
const escaped = prefix.replace(/\\/g, "\\\\").replace(/_/g, "\\_").replace(/%/g, "\\%");
|
|
164
|
+
const { rows } = await client.query(`SELECT interrupt_id FROM ${this.table}
|
|
165
|
+
WHERE interrupt_id LIKE $1 ESCAPE '\\'`, [`${escaped}%`]);
|
|
166
|
+
return rows.map((row) => row.interrupt_id);
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Return the reference DDL for this store's backing table, interpolating
|
|
170
|
+
* the configured table name. The dev runs it through their migration
|
|
171
|
+
* tool — the framework never auto-migrates.
|
|
172
|
+
*
|
|
173
|
+
* @example
|
|
174
|
+
* await pool.query(store.schema());
|
|
175
|
+
*/
|
|
176
|
+
schema() {
|
|
177
|
+
return [
|
|
178
|
+
`CREATE TABLE IF NOT EXISTS ${this.table} (`,
|
|
179
|
+
` interrupt_id TEXT PRIMARY KEY,`,
|
|
180
|
+
` request JSONB NOT NULL,`,
|
|
181
|
+
` status TEXT NOT NULL,`,
|
|
182
|
+
` saved_at TIMESTAMPTZ NOT NULL DEFAULT now()`,
|
|
183
|
+
`);`,
|
|
184
|
+
`CREATE INDEX IF NOT EXISTS idx_${this.table}_saved_at`,
|
|
185
|
+
` ON ${this.table} (saved_at);`
|
|
186
|
+
].join("\n");
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
/**
|
|
190
|
+
* Create a Postgres-backed {@link InterruptStore}. Either pass a live
|
|
191
|
+
* `pg.Pool` / `pg.Client` (`{ client }`) — `@warlock.js/ai` never
|
|
192
|
+
* imports `pg` in that case — or a `{ connectionString }` and let the
|
|
193
|
+
* store lazily `import("pg")` to build its own pool. When `pg` is not
|
|
194
|
+
* installed, the curated install string surfaces on first use, never at
|
|
195
|
+
* import. Run {@link InterruptStore.schema} through your migration tool
|
|
196
|
+
* once before use; the store never auto-migrates.
|
|
197
|
+
*
|
|
198
|
+
* @example
|
|
199
|
+
* import { Pool } from "pg";
|
|
200
|
+
* import { ai } from "@warlock.js/ai";
|
|
201
|
+
*
|
|
202
|
+
* const pool = new Pool({ connectionString: process.env.DATABASE_URL });
|
|
203
|
+
* const store = ai.human.interrupt.pg({ client: pool });
|
|
204
|
+
*
|
|
205
|
+
* // Once, via your migration tooling:
|
|
206
|
+
* // await pool.query(store.schema());
|
|
207
|
+
*
|
|
208
|
+
* @example
|
|
209
|
+
* // Let the store build its own pool from a connection string:
|
|
210
|
+
* const store = ai.human.interrupt.pg({
|
|
211
|
+
* connectionString: process.env.DATABASE_URL,
|
|
212
|
+
* });
|
|
213
|
+
*/
|
|
214
|
+
function pg(options) {
|
|
215
|
+
return new PgInterruptStore(options);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
//#endregion
|
|
219
|
+
export { pg };
|
|
220
|
+
//# sourceMappingURL=pg.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pg.mjs","names":[],"sources":["../../../../../../../../@warlock.js/ai/src/human/stores/pg.ts"],"sourcesContent":["import type {\n InterruptStore,\n PendingInterrupt,\n PgClientLike,\n} from \"../contracts/interrupt-store.contract\";\n\n/**\n * Options for the Postgres {@link InterruptStore}.\n *\n * Two mutually-supportive ways to supply the connection:\n * - **`client`** — pass an already-built `pg.Pool` / `pg.Client` (anything\n * satisfying {@link PgClientLike}). The store only ever calls `query`\n * and never opens or closes it; a single pool can back both an\n * orchestrator's checkpoint/snapshot stores and this interrupt table.\n * - **`connectionString`** — let the store lazily `import(\"pg\")` and build\n * its own `Pool`. `@warlock.js/ai` takes **no** hard dependency on\n * `pg` (it is an optional peer); when it is absent the store throws a\n * curated install string at first use, never a raw module-resolution\n * stack trace at import.\n *\n * Exactly one of the two must be present.\n */\nexport interface PgInterruptOptions {\n /** An already-built `pg.Pool` / `pg.Client` — anything matching {@link PgClientLike}. */\n client?: PgClientLike;\n\n /** Connection string the store passes to a lazily-imported `pg.Pool`. */\n connectionString?: string;\n\n /**\n * Backing table name. Defaults to `warlock_ai_human_interrupts`. Must be\n * a safe SQL identifier — it is interpolated into DDL/DML.\n */\n table?: string;\n}\n\n/**\n * Default backing table — provisions the store with no extra config when\n * the dev runs {@link InterruptStore.schema} through their migration tool.\n */\nconst DEFAULT_TABLE = \"warlock_ai_human_interrupts\";\n\n/**\n * Allowed characters in a Postgres identifier (table name). The table name\n * is interpolated into DDL/DML, so anything outside this conservative\n * ASCII subset is rejected — interpolating an arbitrary string would be a\n * SQL-injection footgun (mirrors `@warlock.js/ai`'s pg stores).\n */\nconst SAFE_IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_]*$/;\n\n/**\n * Module specifier for the optional `pg` driver. Held in a `string`\n * variable so the dynamic `import()` is not statically resolved at\n * compile time — `pg` is an optional peer that need not be installed for\n * this package to type-check or for a memory-only consumer to run.\n */\nconst PG_MODULE = \"pg\";\n\n/**\n * Curated install string surfaced (at use time) when a `connectionString`\n * is configured but the optional `pg` driver is absent. Never thrown at\n * import — a memory-only consumer must be able to load this module.\n */\nconst PG_INSTALL_INSTRUCTIONS = `\nThe @warlock.js/ai Postgres interrupt store requires the pg package.\nInstall it with:\n\n npm install pg\n\nOr with your preferred package manager:\n\n pnpm add pg\n yarn add pg\n`.trim();\n\n/**\n * Minimal structural view of a `pg.Pool` constructor — just enough of the\n * `pg` module surface for the store to build a client when handed a\n * `connectionString`. Declared locally (rather than `typeof import(\"pg\")`)\n * so this module type-checks even when `pg` is not installed.\n */\ninterface PgModuleLike {\n Pool: new (config: { connectionString: string }) => PgClientLike;\n}\n\n/**\n * Lazily import `pg` and return a `Pool` built from `connectionString`. A\n * bare `catch` rethrows the curated install string — a missing optional\n * peer surfaces as actionable guidance, never a raw resolution error.\n */\nasync function buildPgClient(connectionString: string): Promise<PgClientLike> {\n let sdk: PgModuleLike;\n\n try {\n sdk = (await import(PG_MODULE)) as unknown as PgModuleLike;\n } catch {\n throw new Error(PG_INSTALL_INSTRUCTIONS);\n }\n\n return new sdk.Pool({ connectionString });\n}\n\n/**\n * Coerce a Postgres timestamp/text column to an ISO string. `pg` returns\n * `TIMESTAMPTZ` as a `Date`; normalize to the ISO wire shape the\n * {@link PendingInterrupt} contract declares.\n */\nfunction toIso(value: unknown): string {\n if (value instanceof Date) {\n return value.toISOString();\n }\n\n return value as string;\n}\n\n/**\n * Map a raw DB row to a {@link PendingInterrupt}. Column names match the\n * reference DDL 1:1; the `request` JSONB rides one column, so it is parsed\n * defensively (node-postgres parses `JSONB` already, but some pool\n * wrappers hand back the raw string).\n */\nfunction rowToRecord(row: Record<string, unknown>): PendingInterrupt {\n const request =\n typeof row.request === \"string\" ? JSON.parse(row.request) : row.request;\n\n return {\n interruptId: row.interrupt_id as string,\n request: request as PendingInterrupt[\"request\"],\n status: row.status as PendingInterrupt[\"status\"],\n savedAt: toIso(row.saved_at),\n };\n}\n\n/**\n * Postgres-backed {@link InterruptStore} — one durable row per pending\n * interrupt, keyed by `interrupt_id`.\n *\n * Owns: durable round-tripping of the {@link PendingInterrupt} envelope so\n * a reviewer can rule out-of-process (a webhook approves hours later, in a\n * different process), the reference DDL via {@link PgInterruptStore.schema},\n * and prefix-filtered enumeration. Does NOT own: the connection lifecycle\n * (a dev-supplied client is never closed; a store-built `Pool` from a\n * `connectionString` is also left open for the process to reuse) or schema\n * migration (the dev runs `schema()` through their own tool — never\n * auto-migrated).\n *\n * Like the snapshot store, a call has exactly one live interrupt, so\n * `save()` upserts on the `interrupt_id` primary key.\n *\n * Front it with the {@link pg} factory — callers never `new` it.\n */\nclass PgInterruptStore implements InterruptStore {\n /** Validated backing table name, safe to interpolate into SQL. */\n private readonly table: string;\n\n /**\n * A ready client, or a promise resolving to one the store builds lazily\n * from a `connectionString`. Resolved once and memoized so the optional\n * `pg` import happens at most once.\n */\n private clientPromise: Promise<PgClientLike>;\n\n public constructor(options: PgInterruptOptions) {\n const table = options.table ?? DEFAULT_TABLE;\n\n if (!SAFE_IDENTIFIER.test(table)) {\n throw new TypeError(\n `ai.human.interrupt.pg: invalid table name '${table}'. Allowed: [A-Za-z_][A-Za-z0-9_]*.`,\n );\n }\n\n this.table = table;\n\n if (options.client) {\n if (typeof options.client.query !== \"function\") {\n throw new TypeError(\n \"ai.human.interrupt.pg requires a 'client' option implementing { query(text, params) } — pass a pg.Pool or pg.Client.\",\n );\n }\n\n this.clientPromise = Promise.resolve(options.client);\n\n return;\n }\n\n if (options.connectionString) {\n // Defer the optional `pg` import to first use — a curated install\n // string surfaces from `buildPgClient`, not at construction.\n this.clientPromise = buildPgClient(options.connectionString);\n\n return;\n }\n\n throw new TypeError(\n \"ai.human.interrupt.pg requires either a 'client' or a 'connectionString' option.\",\n );\n }\n\n /**\n * Resolve the backing client, surfacing the lazy `pg` import's curated\n * install string on the first call that needs it.\n */\n private client(): Promise<PgClientLike> {\n return this.clientPromise;\n }\n\n /**\n * Persist a pending interrupt, keyed by its own `interrupt_id`. Upserts\n * — a call has exactly one live interrupt, so a second save for the same\n * id overwrites the payload rather than appending.\n */\n public async save(record: PendingInterrupt): Promise<void> {\n const client = await this.client();\n\n await client.query(\n `INSERT INTO ${this.table} (interrupt_id, request, status, saved_at)\n VALUES ($1, $2::jsonb, $3, $4)\n ON CONFLICT (interrupt_id) DO UPDATE\n SET request = EXCLUDED.request,\n status = EXCLUDED.status,\n saved_at = EXCLUDED.saved_at`,\n [\n record.interruptId,\n JSON.stringify(record.request),\n record.status,\n record.savedAt,\n ],\n );\n }\n\n /**\n * Load the interrupt for an `interruptId`, or `undefined` when none is\n * recorded.\n */\n public async load(\n interruptId: string,\n ): Promise<PendingInterrupt | undefined> {\n const client = await this.client();\n\n const { rows } = await client.query(\n `SELECT interrupt_id, request, status, saved_at\n FROM ${this.table}\n WHERE interrupt_id = $1`,\n [interruptId],\n );\n\n if (rows.length === 0) {\n return undefined;\n }\n\n return rowToRecord(rows[0] as Record<string, unknown>);\n }\n\n /**\n * Drop the interrupt for an `interruptId`. Idempotent — deleting an\n * absent id deletes zero rows.\n */\n public async delete(interruptId: string): Promise<void> {\n const client = await this.client();\n\n await client.query(\n `DELETE FROM ${this.table} WHERE interrupt_id = $1`,\n [interruptId],\n );\n }\n\n /**\n * List the interrupt ids known to the store, optionally filtered by a\n * prefix. The `_` and `%` LIKE wildcards in the prefix are escaped so an\n * opaque interruptId that happens to contain them is matched literally.\n */\n public async list(prefix?: string): Promise<string[]> {\n const client = await this.client();\n\n if (prefix === undefined) {\n const { rows } = await client.query(\n `SELECT interrupt_id FROM ${this.table}`,\n );\n\n return rows.map(\n (row) => (row as Record<string, unknown>).interrupt_id as string,\n );\n }\n\n const escaped = prefix\n .replace(/\\\\/g, \"\\\\\\\\\")\n .replace(/_/g, \"\\\\_\")\n .replace(/%/g, \"\\\\%\");\n\n const { rows } = await client.query(\n `SELECT interrupt_id FROM ${this.table}\n WHERE interrupt_id LIKE $1 ESCAPE '\\\\'`,\n [`${escaped}%`],\n );\n\n return rows.map(\n (row) => (row as Record<string, unknown>).interrupt_id as string,\n );\n }\n\n /**\n * Return the reference DDL for this store's backing table, interpolating\n * the configured table name. The dev runs it through their migration\n * tool — the framework never auto-migrates.\n *\n * @example\n * await pool.query(store.schema());\n */\n public schema(): string {\n return [\n `CREATE TABLE IF NOT EXISTS ${this.table} (`,\n ` interrupt_id TEXT PRIMARY KEY,`,\n ` request JSONB NOT NULL,`,\n ` status TEXT NOT NULL,`,\n ` saved_at TIMESTAMPTZ NOT NULL DEFAULT now()`,\n `);`,\n `CREATE INDEX IF NOT EXISTS idx_${this.table}_saved_at`,\n ` ON ${this.table} (saved_at);`,\n ].join(\"\\n\");\n }\n}\n\n/**\n * Create a Postgres-backed {@link InterruptStore}. Either pass a live\n * `pg.Pool` / `pg.Client` (`{ client }`) — `@warlock.js/ai` never\n * imports `pg` in that case — or a `{ connectionString }` and let the\n * store lazily `import(\"pg\")` to build its own pool. When `pg` is not\n * installed, the curated install string surfaces on first use, never at\n * import. Run {@link InterruptStore.schema} through your migration tool\n * once before use; the store never auto-migrates.\n *\n * @example\n * import { Pool } from \"pg\";\n * import { ai } from \"@warlock.js/ai\";\n *\n * const pool = new Pool({ connectionString: process.env.DATABASE_URL });\n * const store = ai.human.interrupt.pg({ client: pool });\n *\n * // Once, via your migration tooling:\n * // await pool.query(store.schema());\n *\n * @example\n * // Let the store build its own pool from a connection string:\n * const store = ai.human.interrupt.pg({\n * connectionString: process.env.DATABASE_URL,\n * });\n */\nexport function pg(options: PgInterruptOptions): InterruptStore {\n return new PgInterruptStore(options);\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { InterruptStore, RedisClientLike } from "../contracts/interrupt-store.contract.mjs";
|
|
2
|
+
|
|
3
|
+
//#region ../@warlock.js/ai/src/human/stores/redis.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Options for the Redis {@link InterruptStore}.
|
|
6
|
+
*
|
|
7
|
+
* Two mutually-supportive ways to supply the connection:
|
|
8
|
+
* - **`client`** — pass an already-connected `redis` client (anything
|
|
9
|
+
* satisfying {@link RedisClientLike}). The store only calls
|
|
10
|
+
* `get` / `set` / `del` and never connects or quits it.
|
|
11
|
+
* - **`url`** — let the store lazily `import("redis")`, build a client
|
|
12
|
+
* from the url, and connect it. `@warlock.js/ai` takes **no** hard
|
|
13
|
+
* dependency on `redis` (it is an optional peer); when it is absent the
|
|
14
|
+
* store throws a curated install string at first use, never a raw
|
|
15
|
+
* module-resolution stack trace at import.
|
|
16
|
+
*
|
|
17
|
+
* Exactly one of the two must be present.
|
|
18
|
+
*/
|
|
19
|
+
interface RedisInterruptOptions {
|
|
20
|
+
/** An already-connected `redis` client — anything matching {@link RedisClientLike}. */
|
|
21
|
+
client?: RedisClientLike;
|
|
22
|
+
/** Connection url the store passes to a lazily-imported `createClient`. */
|
|
23
|
+
url?: string;
|
|
24
|
+
/**
|
|
25
|
+
* Key prefix prepended to every key this store writes. Lets one Redis
|
|
26
|
+
* database back multiple stores without collision. Defaults to
|
|
27
|
+
* `warlock:ai-human:interrupt:`.
|
|
28
|
+
*/
|
|
29
|
+
prefix?: string;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Create a Redis-backed {@link InterruptStore}. Either pass a connected
|
|
33
|
+
* `redis` client (`{ client }`) — `@warlock.js/ai` never imports
|
|
34
|
+
* `redis` in that case — or a `{ url }` and let the store lazily
|
|
35
|
+
* `import("redis")`, build, and connect a client. When `redis` is not
|
|
36
|
+
* installed, the curated install string surfaces on first use, never at
|
|
37
|
+
* import. {@link InterruptStore.schema} returns an empty string; Redis
|
|
38
|
+
* needs no migration.
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* import { createClient } from "redis";
|
|
42
|
+
* import { ai } from "@warlock.js/ai";
|
|
43
|
+
*
|
|
44
|
+
* const client = createClient({ url: process.env.REDIS_URL });
|
|
45
|
+
* await client.connect();
|
|
46
|
+
*
|
|
47
|
+
* const store = ai.human.interrupt.redis({ client });
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* // Let the store build + connect its own client from a url:
|
|
51
|
+
* const store = ai.human.interrupt.redis({ url: process.env.REDIS_URL });
|
|
52
|
+
*/
|
|
53
|
+
declare function redis(options: RedisInterruptOptions): InterruptStore;
|
|
54
|
+
//#endregion
|
|
55
|
+
export { RedisInterruptOptions, redis };
|
|
56
|
+
//# sourceMappingURL=redis.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redis.d.mts","names":[],"sources":["../../../../../../../../@warlock.js/ai/src/human/stores/redis.ts"],"mappings":";;;;;AAqBA;;;;;;;;;AAYQ;AA+RR;;;UA3SiB,qBAAA;EA2Sc;EAzS7B,MAAA,GAAS,eAAe;EAyS6B;EAtSrD,GAAA;EAsSmE;;;;;EA/RnE,MAAA;AAAA;;;;;;;;;;;;;;;;;;;;;;;iBA+Rc,KAAA,CAAM,OAAA,EAAS,qBAAA,GAAwB,cAAc"}
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
//#region ../@warlock.js/ai/src/human/stores/redis.ts
|
|
2
|
+
/**
|
|
3
|
+
* Default key prefix — namespaces the store's keys inside a shared Redis
|
|
4
|
+
* database so interrupt records coexist with other data without collision.
|
|
5
|
+
*/
|
|
6
|
+
const DEFAULT_PREFIX = "warlock:ai-human:interrupt:";
|
|
7
|
+
/**
|
|
8
|
+
* Index key (under the configured prefix) holding the JSON array of live
|
|
9
|
+
* interrupt ids. The structural {@link RedisClientLike} surface exposes no
|
|
10
|
+
* `SCAN` / `KEYS`, so enumeration for `list()` is self-maintained.
|
|
11
|
+
*/
|
|
12
|
+
const INDEX_SUFFIX = "index";
|
|
13
|
+
/**
|
|
14
|
+
* Module specifier for the optional `redis` driver. Held in a `string`
|
|
15
|
+
* variable so the dynamic `import()` is not statically resolved at compile
|
|
16
|
+
* time — `redis` is an optional peer that need not be installed for this
|
|
17
|
+
* package to type-check or for a memory-only consumer to run.
|
|
18
|
+
*/
|
|
19
|
+
const REDIS_MODULE = "redis";
|
|
20
|
+
/**
|
|
21
|
+
* Curated install string surfaced (at use time) when a `url` is configured
|
|
22
|
+
* but the optional `redis` driver is absent. Never thrown at import — a
|
|
23
|
+
* memory-only consumer must be able to load this module.
|
|
24
|
+
*/
|
|
25
|
+
const REDIS_INSTALL_INSTRUCTIONS = `
|
|
26
|
+
The @warlock.js/ai Redis interrupt store requires the redis package.
|
|
27
|
+
Install it with:
|
|
28
|
+
|
|
29
|
+
npm install redis
|
|
30
|
+
|
|
31
|
+
Or with your preferred package manager:
|
|
32
|
+
|
|
33
|
+
pnpm add redis
|
|
34
|
+
yarn add redis
|
|
35
|
+
`.trim();
|
|
36
|
+
/**
|
|
37
|
+
* Lazily import `redis`, build a client from `url`, and connect it. A bare
|
|
38
|
+
* `catch` rethrows the curated install string — a missing optional peer
|
|
39
|
+
* surfaces as actionable guidance, never a raw resolution error.
|
|
40
|
+
*/
|
|
41
|
+
async function buildRedisClient(url) {
|
|
42
|
+
let sdk;
|
|
43
|
+
try {
|
|
44
|
+
sdk = await import(REDIS_MODULE);
|
|
45
|
+
} catch {
|
|
46
|
+
throw new Error(REDIS_INSTALL_INSTRUCTIONS);
|
|
47
|
+
}
|
|
48
|
+
const client = sdk.createClient({ url });
|
|
49
|
+
await client.connect();
|
|
50
|
+
return client;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Redis-backed {@link InterruptStore} — one JSON string value per pending
|
|
54
|
+
* interrupt, under a namespaced key, plus a self-maintained id index so
|
|
55
|
+
* `list()` works without `SCAN`/`KEYS`.
|
|
56
|
+
*
|
|
57
|
+
* Owns: durable round-tripping of the {@link PendingInterrupt} envelope so
|
|
58
|
+
* a reviewer can rule out-of-process, the namespaced key layout, and the
|
|
59
|
+
* per-store id index that backs enumeration. Does NOT own: durability
|
|
60
|
+
* guarantees beyond Redis's own, the connection lifecycle (a dev-supplied
|
|
61
|
+
* client is never disconnected; a store-built client from a `url` is left
|
|
62
|
+
* connected for the process to reuse), or migration —
|
|
63
|
+
* {@link RedisInterruptStore.schema} returns an empty string.
|
|
64
|
+
*
|
|
65
|
+
* A call has exactly one live interrupt, so `save()` overwrites the key.
|
|
66
|
+
*
|
|
67
|
+
* Front it with the {@link redis} factory — callers never `new` it.
|
|
68
|
+
*/
|
|
69
|
+
var RedisInterruptStore = class {
|
|
70
|
+
constructor(options) {
|
|
71
|
+
this.prefix = options.prefix ?? DEFAULT_PREFIX;
|
|
72
|
+
if (options.client) {
|
|
73
|
+
if (typeof options.client.get !== "function" || typeof options.client.set !== "function" || typeof options.client.del !== "function") throw new TypeError("ai.human.interrupt.redis requires a 'client' option implementing { get, set, del } — pass a connected redis client.");
|
|
74
|
+
this.clientPromise = Promise.resolve(options.client);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
if (options.url) {
|
|
78
|
+
this.clientPromise = buildRedisClient(options.url);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
throw new TypeError("ai.human.interrupt.redis requires either a 'client' or a 'url' option.");
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Resolve the backing client, surfacing the lazy `redis` import's
|
|
85
|
+
* curated install string on the first call that needs it.
|
|
86
|
+
*/
|
|
87
|
+
client() {
|
|
88
|
+
return this.clientPromise;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Persist a pending interrupt, keyed by its own `interruptId`, and index
|
|
92
|
+
* the id for enumeration. Overwrites any prior record for the same id —
|
|
93
|
+
* a call has exactly one live interrupt.
|
|
94
|
+
*/
|
|
95
|
+
async save(record) {
|
|
96
|
+
await (await this.client()).set(this.recordKey(record.interruptId), JSON.stringify(record));
|
|
97
|
+
await this.indexId(record.interruptId);
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Load the interrupt for an `interruptId`, or `undefined` when the key is
|
|
101
|
+
* missing. Redis returns `null` for an absent key — converted to
|
|
102
|
+
* `undefined` at the boundary.
|
|
103
|
+
*/
|
|
104
|
+
async load(interruptId) {
|
|
105
|
+
const raw = await (await this.client()).get(this.recordKey(interruptId));
|
|
106
|
+
if (raw === null) return;
|
|
107
|
+
return JSON.parse(raw);
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Drop the interrupt for an `interruptId` and de-index its id. Idempotent
|
|
111
|
+
* — deleting an absent id is a no-op.
|
|
112
|
+
*/
|
|
113
|
+
async delete(interruptId) {
|
|
114
|
+
await (await this.client()).del(this.recordKey(interruptId));
|
|
115
|
+
await this.deindexId(interruptId);
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* List the interrupt ids known to the store, optionally filtered by a
|
|
119
|
+
* prefix. Reads the self-maintained index document.
|
|
120
|
+
*/
|
|
121
|
+
async list(prefix) {
|
|
122
|
+
const ids = await this.readIndex();
|
|
123
|
+
if (prefix === void 0) return ids;
|
|
124
|
+
return ids.filter((id) => id.startsWith(prefix));
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Redis needs no relational table — there is nothing to migrate. Returns
|
|
128
|
+
* an empty string so callers can treat `schema()` uniformly across
|
|
129
|
+
* drivers.
|
|
130
|
+
*/
|
|
131
|
+
schema() {
|
|
132
|
+
return "";
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Read and parse the id index, defaulting to an empty list when absent.
|
|
136
|
+
*/
|
|
137
|
+
async readIndex() {
|
|
138
|
+
const raw = await (await this.client()).get(this.indexKey());
|
|
139
|
+
if (raw === null) return [];
|
|
140
|
+
return JSON.parse(raw);
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Add an interrupt id to the index, no-op when already present.
|
|
144
|
+
*/
|
|
145
|
+
async indexId(interruptId) {
|
|
146
|
+
const ids = await this.readIndex();
|
|
147
|
+
if (ids.includes(interruptId)) return;
|
|
148
|
+
ids.push(interruptId);
|
|
149
|
+
await (await this.client()).set(this.indexKey(), JSON.stringify(ids));
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Remove an interrupt id from the index, no-op when absent.
|
|
153
|
+
*/
|
|
154
|
+
async deindexId(interruptId) {
|
|
155
|
+
const ids = await this.readIndex();
|
|
156
|
+
const next = ids.filter((id) => id !== interruptId);
|
|
157
|
+
if (next.length === ids.length) return;
|
|
158
|
+
await (await this.client()).set(this.indexKey(), JSON.stringify(next));
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Key for a single interrupt record — `<prefix><interruptId>`.
|
|
162
|
+
*/
|
|
163
|
+
recordKey(interruptId) {
|
|
164
|
+
return `${this.prefix}${interruptId}`;
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Key for the self-maintained id index — `<prefix>index`.
|
|
168
|
+
*/
|
|
169
|
+
indexKey() {
|
|
170
|
+
return `${this.prefix}${INDEX_SUFFIX}`;
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
/**
|
|
174
|
+
* Create a Redis-backed {@link InterruptStore}. Either pass a connected
|
|
175
|
+
* `redis` client (`{ client }`) — `@warlock.js/ai` never imports
|
|
176
|
+
* `redis` in that case — or a `{ url }` and let the store lazily
|
|
177
|
+
* `import("redis")`, build, and connect a client. When `redis` is not
|
|
178
|
+
* installed, the curated install string surfaces on first use, never at
|
|
179
|
+
* import. {@link InterruptStore.schema} returns an empty string; Redis
|
|
180
|
+
* needs no migration.
|
|
181
|
+
*
|
|
182
|
+
* @example
|
|
183
|
+
* import { createClient } from "redis";
|
|
184
|
+
* import { ai } from "@warlock.js/ai";
|
|
185
|
+
*
|
|
186
|
+
* const client = createClient({ url: process.env.REDIS_URL });
|
|
187
|
+
* await client.connect();
|
|
188
|
+
*
|
|
189
|
+
* const store = ai.human.interrupt.redis({ client });
|
|
190
|
+
*
|
|
191
|
+
* @example
|
|
192
|
+
* // Let the store build + connect its own client from a url:
|
|
193
|
+
* const store = ai.human.interrupt.redis({ url: process.env.REDIS_URL });
|
|
194
|
+
*/
|
|
195
|
+
function redis(options) {
|
|
196
|
+
return new RedisInterruptStore(options);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
//#endregion
|
|
200
|
+
export { redis };
|
|
201
|
+
//# sourceMappingURL=redis.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redis.mjs","names":[],"sources":["../../../../../../../../@warlock.js/ai/src/human/stores/redis.ts"],"sourcesContent":["import type {\n InterruptStore,\n PendingInterrupt,\n RedisClientLike,\n} from \"../contracts/interrupt-store.contract\";\n\n/**\n * Options for the Redis {@link InterruptStore}.\n *\n * Two mutually-supportive ways to supply the connection:\n * - **`client`** — pass an already-connected `redis` client (anything\n * satisfying {@link RedisClientLike}). The store only calls\n * `get` / `set` / `del` and never connects or quits it.\n * - **`url`** — let the store lazily `import(\"redis\")`, build a client\n * from the url, and connect it. `@warlock.js/ai` takes **no** hard\n * dependency on `redis` (it is an optional peer); when it is absent the\n * store throws a curated install string at first use, never a raw\n * module-resolution stack trace at import.\n *\n * Exactly one of the two must be present.\n */\nexport interface RedisInterruptOptions {\n /** An already-connected `redis` client — anything matching {@link RedisClientLike}. */\n client?: RedisClientLike;\n\n /** Connection url the store passes to a lazily-imported `createClient`. */\n url?: string;\n\n /**\n * Key prefix prepended to every key this store writes. Lets one Redis\n * database back multiple stores without collision. Defaults to\n * `warlock:ai-human:interrupt:`.\n */\n prefix?: string;\n}\n\n/**\n * Default key prefix — namespaces the store's keys inside a shared Redis\n * database so interrupt records coexist with other data without collision.\n */\nconst DEFAULT_PREFIX = \"warlock:ai-human:interrupt:\";\n\n/**\n * Index key (under the configured prefix) holding the JSON array of live\n * interrupt ids. The structural {@link RedisClientLike} surface exposes no\n * `SCAN` / `KEYS`, so enumeration for `list()` is self-maintained.\n */\nconst INDEX_SUFFIX = \"index\";\n\n/**\n * Module specifier for the optional `redis` driver. Held in a `string`\n * variable so the dynamic `import()` is not statically resolved at compile\n * time — `redis` is an optional peer that need not be installed for this\n * package to type-check or for a memory-only consumer to run.\n */\nconst REDIS_MODULE = \"redis\";\n\n/**\n * Curated install string surfaced (at use time) when a `url` is configured\n * but the optional `redis` driver is absent. Never thrown at import — a\n * memory-only consumer must be able to load this module.\n */\nconst REDIS_INSTALL_INSTRUCTIONS = `\nThe @warlock.js/ai Redis interrupt store requires the redis package.\nInstall it with:\n\n npm install redis\n\nOr with your preferred package manager:\n\n pnpm add redis\n yarn add redis\n`.trim();\n\n/**\n * Minimal structural view of the `redis` module surface — just enough to\n * build and connect a client from a url. Declared locally (rather than\n * `typeof import(\"redis\")`) so this module type-checks even when `redis`\n * is not installed.\n */\ninterface RedisModuleLike {\n createClient(config: {\n url: string;\n }): RedisClientLike & { connect(): Promise<unknown> };\n}\n\n/**\n * Lazily import `redis`, build a client from `url`, and connect it. A bare\n * `catch` rethrows the curated install string — a missing optional peer\n * surfaces as actionable guidance, never a raw resolution error.\n */\nasync function buildRedisClient(url: string): Promise<RedisClientLike> {\n let sdk: RedisModuleLike;\n\n try {\n sdk = (await import(REDIS_MODULE)) as unknown as RedisModuleLike;\n } catch {\n throw new Error(REDIS_INSTALL_INSTRUCTIONS);\n }\n\n const client = sdk.createClient({ url });\n await client.connect();\n\n return client;\n}\n\n/**\n * Redis-backed {@link InterruptStore} — one JSON string value per pending\n * interrupt, under a namespaced key, plus a self-maintained id index so\n * `list()` works without `SCAN`/`KEYS`.\n *\n * Owns: durable round-tripping of the {@link PendingInterrupt} envelope so\n * a reviewer can rule out-of-process, the namespaced key layout, and the\n * per-store id index that backs enumeration. Does NOT own: durability\n * guarantees beyond Redis's own, the connection lifecycle (a dev-supplied\n * client is never disconnected; a store-built client from a `url` is left\n * connected for the process to reuse), or migration —\n * {@link RedisInterruptStore.schema} returns an empty string.\n *\n * A call has exactly one live interrupt, so `save()` overwrites the key.\n *\n * Front it with the {@link redis} factory — callers never `new` it.\n */\nclass RedisInterruptStore implements InterruptStore {\n /** Key prefix namespacing every key this store writes. */\n private readonly prefix: string;\n\n /**\n * A ready client, or a promise resolving to one the store builds lazily\n * from a `url`. Resolved once and memoized so the optional `redis`\n * import + connect happens at most once.\n */\n private clientPromise: Promise<RedisClientLike>;\n\n public constructor(options: RedisInterruptOptions) {\n this.prefix = options.prefix ?? DEFAULT_PREFIX;\n\n if (options.client) {\n if (\n typeof options.client.get !== \"function\" ||\n typeof options.client.set !== \"function\" ||\n typeof options.client.del !== \"function\"\n ) {\n throw new TypeError(\n \"ai.human.interrupt.redis requires a 'client' option implementing { get, set, del } — pass a connected redis client.\",\n );\n }\n\n this.clientPromise = Promise.resolve(options.client);\n\n return;\n }\n\n if (options.url) {\n // Defer the optional `redis` import to first use — a curated install\n // string surfaces from `buildRedisClient`, not at construction.\n this.clientPromise = buildRedisClient(options.url);\n\n return;\n }\n\n throw new TypeError(\n \"ai.human.interrupt.redis requires either a 'client' or a 'url' option.\",\n );\n }\n\n /**\n * Resolve the backing client, surfacing the lazy `redis` import's\n * curated install string on the first call that needs it.\n */\n private client(): Promise<RedisClientLike> {\n return this.clientPromise;\n }\n\n /**\n * Persist a pending interrupt, keyed by its own `interruptId`, and index\n * the id for enumeration. Overwrites any prior record for the same id —\n * a call has exactly one live interrupt.\n */\n public async save(record: PendingInterrupt): Promise<void> {\n const client = await this.client();\n\n await client.set(this.recordKey(record.interruptId), JSON.stringify(record));\n await this.indexId(record.interruptId);\n }\n\n /**\n * Load the interrupt for an `interruptId`, or `undefined` when the key is\n * missing. Redis returns `null` for an absent key — converted to\n * `undefined` at the boundary.\n */\n public async load(\n interruptId: string,\n ): Promise<PendingInterrupt | undefined> {\n const client = await this.client();\n const raw = await client.get(this.recordKey(interruptId));\n\n if (raw === null) {\n return undefined;\n }\n\n return JSON.parse(raw) as PendingInterrupt;\n }\n\n /**\n * Drop the interrupt for an `interruptId` and de-index its id. Idempotent\n * — deleting an absent id is a no-op.\n */\n public async delete(interruptId: string): Promise<void> {\n const client = await this.client();\n\n await client.del(this.recordKey(interruptId));\n await this.deindexId(interruptId);\n }\n\n /**\n * List the interrupt ids known to the store, optionally filtered by a\n * prefix. Reads the self-maintained index document.\n */\n public async list(prefix?: string): Promise<string[]> {\n const ids = await this.readIndex();\n\n if (prefix === undefined) {\n return ids;\n }\n\n return ids.filter((id) => id.startsWith(prefix));\n }\n\n /**\n * Redis needs no relational table — there is nothing to migrate. Returns\n * an empty string so callers can treat `schema()` uniformly across\n * drivers.\n */\n public schema(): string {\n return \"\";\n }\n\n /**\n * Read and parse the id index, defaulting to an empty list when absent.\n */\n private async readIndex(): Promise<string[]> {\n const client = await this.client();\n const raw = await client.get(this.indexKey());\n\n if (raw === null) {\n return [];\n }\n\n return JSON.parse(raw) as string[];\n }\n\n /**\n * Add an interrupt id to the index, no-op when already present.\n */\n private async indexId(interruptId: string): Promise<void> {\n const ids = await this.readIndex();\n\n if (ids.includes(interruptId)) {\n return;\n }\n\n ids.push(interruptId);\n\n const client = await this.client();\n await client.set(this.indexKey(), JSON.stringify(ids));\n }\n\n /**\n * Remove an interrupt id from the index, no-op when absent.\n */\n private async deindexId(interruptId: string): Promise<void> {\n const ids = await this.readIndex();\n const next = ids.filter((id) => id !== interruptId);\n\n if (next.length === ids.length) {\n return;\n }\n\n const client = await this.client();\n await client.set(this.indexKey(), JSON.stringify(next));\n }\n\n /**\n * Key for a single interrupt record — `<prefix><interruptId>`.\n */\n private recordKey(interruptId: string): string {\n return `${this.prefix}${interruptId}`;\n }\n\n /**\n * Key for the self-maintained id index — `<prefix>index`.\n */\n private indexKey(): string {\n return `${this.prefix}${INDEX_SUFFIX}`;\n }\n}\n\n/**\n * Create a Redis-backed {@link InterruptStore}. Either pass a connected\n * `redis` client (`{ client }`) — `@warlock.js/ai` never imports\n * `redis` in that case — or a `{ url }` and let the store lazily\n * `import(\"redis\")`, build, and connect a client. When `redis` is not\n * installed, the curated install string surfaces on first use, never at\n * import. {@link InterruptStore.schema} returns an empty string; Redis\n * needs no migration.\n *\n * @example\n * import { createClient } from \"redis\";\n * import { ai } from \"@warlock.js/ai\";\n *\n * const client = createClient({ url: process.env.REDIS_URL });\n * await client.connect();\n *\n * const store = ai.human.interrupt.redis({ client });\n *\n * @example\n * // Let the store build + connect its own client from a url:\n * const store = ai.human.interrupt.redis({ url: process.env.REDIS_URL });\n */\nexport function redis(options: RedisInterruptOptions): InterruptStore {\n return new RedisInterruptStore(options);\n}\n"],"mappings":"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"}
|