@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,200 @@
|
|
|
1
|
+
import { judge } from "../eval/judge-scorer.mjs";
|
|
2
|
+
import { agent } from "../agent/agent.mjs";
|
|
3
|
+
import { PROMPT_JUDGE_RUBRIC } from "../prompt/prompt-validate.mjs";
|
|
4
|
+
|
|
5
|
+
//#region ../@warlock.js/ai/src/prompts/prompts-validate.ts
|
|
6
|
+
/**
|
|
7
|
+
* Placeholder matcher — kept in lock-step with the matcher
|
|
8
|
+
* `renderPlaceholders` (`src/system-prompt/render-placeholders.ts`) and the
|
|
9
|
+
* legacy `prompt-validate` lint both use, so the deterministic validator sees
|
|
10
|
+
* the exact same `{{key}}` / `{{a.b}}` / `{{key|default}}` set the renderer
|
|
11
|
+
* substitutes. Global so every occurrence is collected.
|
|
12
|
+
*/
|
|
13
|
+
const PLACEHOLDER_PATTERN = /\{\{\s*([^{}]+?)\s*\}\}/g;
|
|
14
|
+
/**
|
|
15
|
+
* Collect every distinct placeholder occurrence from a template, in first-seen
|
|
16
|
+
* order. A key is considered to "have a default" only when EVERY occurrence of
|
|
17
|
+
* it carries one — a single bare `{{key}}` means the renderer can leave it
|
|
18
|
+
* unresolved, so the key is still required.
|
|
19
|
+
*/
|
|
20
|
+
function collectPlaceholders(template) {
|
|
21
|
+
const byPath = /* @__PURE__ */ new Map();
|
|
22
|
+
const order = [];
|
|
23
|
+
for (const match of template.matchAll(PLACEHOLDER_PATTERN)) {
|
|
24
|
+
const [rawPath, rawDefault] = match[1].split("|");
|
|
25
|
+
const path = rawPath.trim();
|
|
26
|
+
if (path.length === 0) continue;
|
|
27
|
+
const hasDefault = rawDefault !== void 0;
|
|
28
|
+
if (!byPath.has(path)) {
|
|
29
|
+
byPath.set(path, hasDefault);
|
|
30
|
+
order.push(path);
|
|
31
|
+
} else byPath.set(path, (byPath.get(path) ?? false) && hasDefault);
|
|
32
|
+
}
|
|
33
|
+
return order.map((path) => ({
|
|
34
|
+
path,
|
|
35
|
+
hasDefault: byPath.get(path) ?? false
|
|
36
|
+
}));
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Run the deterministic (model-free) half of validation over a resolved prompt
|
|
40
|
+
* body. Reports every `{{key}}` placeholder that has NO inline default and is
|
|
41
|
+
* neither supplied in `provided` nor declared in `declared` (the prompt's
|
|
42
|
+
* `meta.required` plus any caller-declared keys).
|
|
43
|
+
*
|
|
44
|
+
* Pure and synchronous — the only required half of `validate`; the LLM-judge
|
|
45
|
+
* half is optional and layered on top.
|
|
46
|
+
*
|
|
47
|
+
* @param text - The resolved prompt body (placeholders may still be present).
|
|
48
|
+
* @param provided - Placeholder keys the caller has supplied a value for.
|
|
49
|
+
* @param declared - Placeholder keys declared as known/required (e.g. `meta.required`).
|
|
50
|
+
*/
|
|
51
|
+
function findMissingPlaceholders(text, provided, declared) {
|
|
52
|
+
const missing = [];
|
|
53
|
+
for (const { path, hasDefault } of collectPlaceholders(text)) {
|
|
54
|
+
if (hasDefault) continue;
|
|
55
|
+
if (provided.has(path) || declared.has(path)) continue;
|
|
56
|
+
missing.push(path);
|
|
57
|
+
}
|
|
58
|
+
return missing;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* A `meta.required` key absent from the template entirely — declared as
|
|
62
|
+
* required but never referenced — is itself a defect worth surfacing. Returns
|
|
63
|
+
* the declared keys that appear nowhere in the body.
|
|
64
|
+
*/
|
|
65
|
+
function findUnreferencedRequired(text, required) {
|
|
66
|
+
const present = new Set(collectPlaceholders(text).map((p) => p.path));
|
|
67
|
+
return required.filter((key) => !present.has(key));
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Build the one-shot judge agent the optional LLM-as-judge pass runs. Mirrors
|
|
71
|
+
* the legacy `prompt.ts` judge agent (strict-JSON instruction so the verdict
|
|
72
|
+
* parses even without an output schema), so the two validate paths share one
|
|
73
|
+
* judging contract.
|
|
74
|
+
*/
|
|
75
|
+
function buildJudgeAgent(model) {
|
|
76
|
+
return agent({
|
|
77
|
+
name: "prompt-quality-judge",
|
|
78
|
+
model,
|
|
79
|
+
systemPrompt: "You are a strict prompt-quality grader. Respond with JSON only: { \"score\": <0..1>, \"passed\": <true|false>, \"reason\": \"<short explanation>\" }."
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Run the optional LLM-as-judge pass over a resolved prompt body, REUSING the
|
|
84
|
+
* eval `judge` scorer (the same path `prompt().validate` uses) so there is no
|
|
85
|
+
* second judging implementation.
|
|
86
|
+
*
|
|
87
|
+
* **Nova-safe by contract.** The judge NEVER throws here: the eval scorer
|
|
88
|
+
* already degrades a broken judge to `score: 0` with a failure reason, and any
|
|
89
|
+
* exception that still escapes (model wiring, agent construction) is caught.
|
|
90
|
+
* Both degrade paths surface `score: undefined` plus an issue note — so a flaky
|
|
91
|
+
* judge can never fail an otherwise-valid prompt.
|
|
92
|
+
*
|
|
93
|
+
* @param text - The resolved prompt body under evaluation.
|
|
94
|
+
* @param model - The model that powers the judge agent.
|
|
95
|
+
*/
|
|
96
|
+
async function judgePromptBody(text, model) {
|
|
97
|
+
try {
|
|
98
|
+
const verdict = await judge({
|
|
99
|
+
agent: buildJudgeAgent(model),
|
|
100
|
+
rubric: PROMPT_JUDGE_RUBRIC
|
|
101
|
+
})({
|
|
102
|
+
case: {
|
|
103
|
+
name: "prompt-quality",
|
|
104
|
+
input: "Grade the system prompt below."
|
|
105
|
+
},
|
|
106
|
+
text,
|
|
107
|
+
result: { text },
|
|
108
|
+
output: void 0
|
|
109
|
+
});
|
|
110
|
+
if (verdict.score === 0 && typeof verdict.reason === "string" && /^judge (failed|returned no parseable)/.test(verdict.reason)) return { issues: [`LLM-judge unavailable: ${verdict.reason}`] };
|
|
111
|
+
return {
|
|
112
|
+
score: verdict.score,
|
|
113
|
+
issues: verdict.reason ? [verdict.reason] : []
|
|
114
|
+
};
|
|
115
|
+
} catch (error) {
|
|
116
|
+
return { issues: [`LLM-judge unavailable: ${error instanceof Error ? error.message : String(error)}`] };
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Non-cryptographic 53-bit string hash (cyrb53) — deterministic across runs
|
|
121
|
+
* and platforms, with no `node:crypto` dependency (keeps the validate path
|
|
122
|
+
* usable in any runtime). Mirrors the VCR request hash; collision-resistant
|
|
123
|
+
* enough for a per-prompt judge-verdict keyspace. Returned as base-36.
|
|
124
|
+
*/
|
|
125
|
+
function hashString(input) {
|
|
126
|
+
let h1 = 3735928559;
|
|
127
|
+
let h2 = 1103547991;
|
|
128
|
+
for (let i = 0; i < input.length; i++) {
|
|
129
|
+
const ch = input.charCodeAt(i);
|
|
130
|
+
h1 = Math.imul(h1 ^ ch, 2654435761);
|
|
131
|
+
h2 = Math.imul(h2 ^ ch, 1597334677);
|
|
132
|
+
}
|
|
133
|
+
h1 = Math.imul(h1 ^ h1 >>> 16, 2246822507);
|
|
134
|
+
h1 ^= Math.imul(h2 ^ h2 >>> 13, 3266489909);
|
|
135
|
+
h2 = Math.imul(h2 ^ h2 >>> 16, 2246822507);
|
|
136
|
+
h2 ^= Math.imul(h1 ^ h1 >>> 13, 3266489909);
|
|
137
|
+
return (4294967296 * (2097151 & h2) + (h1 >>> 0)).toString(36);
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Build the judge-verdict cache key for a resolved prompt body + judge model.
|
|
141
|
+
* Combines the model's `provider:name` identity with a content hash of the
|
|
142
|
+
* body, so the same prompt graded by the same judge hits the cache, while any
|
|
143
|
+
* change to either misses it.
|
|
144
|
+
*/
|
|
145
|
+
function judgeCacheKey(text, model) {
|
|
146
|
+
return `prompts.judge.${model.provider}:${model.name}.${hashString(text)}`;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Run the judge pass with an OPTIONAL memo cache in front. On a hit, the stored
|
|
150
|
+
* {@link JudgeOutcome} is returned without a model call; on a miss, the live
|
|
151
|
+
* judge runs and a USABLE verdict (one carrying a `score`) is written back.
|
|
152
|
+
* Degraded outcomes (no score) are NOT cached — a transient judge failure must
|
|
153
|
+
* never poison the memo. A `null`/absent cache degrades to a direct judge call.
|
|
154
|
+
*
|
|
155
|
+
* Cache I/O is itself fault-tolerant: a `get`/`set` that rejects is swallowed
|
|
156
|
+
* so a flaky cache can never break (or fail) validation.
|
|
157
|
+
*
|
|
158
|
+
* @param text - The resolved prompt body under evaluation.
|
|
159
|
+
* @param model - The judge model.
|
|
160
|
+
* @param cache - Optional verdict memo (any `CacheDriver`-like get/set surface).
|
|
161
|
+
*/
|
|
162
|
+
async function judgePromptBodyCached(text, model, cache) {
|
|
163
|
+
if (!cache) return judgePromptBody(text, model);
|
|
164
|
+
const key = judgeCacheKey(text, model);
|
|
165
|
+
const cached = await readJudgeCache(cache, key);
|
|
166
|
+
if (cached) return cached;
|
|
167
|
+
const outcome = await judgePromptBody(text, model);
|
|
168
|
+
if (outcome.score !== void 0) await writeJudgeCache(cache, key, outcome);
|
|
169
|
+
return outcome;
|
|
170
|
+
}
|
|
171
|
+
/** Read a cached verdict, swallowing any cache fault (treated as a miss). */
|
|
172
|
+
async function readJudgeCache(cache, key) {
|
|
173
|
+
try {
|
|
174
|
+
return await cache.get(key) ?? void 0;
|
|
175
|
+
} catch {
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
/** Write a verdict, swallowing any cache fault (best-effort memo). */
|
|
180
|
+
async function writeJudgeCache(cache, key, outcome) {
|
|
181
|
+
try {
|
|
182
|
+
await cache.set(key, outcome);
|
|
183
|
+
} catch {}
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Resolve the body + declared-required keys for a validation target that is a
|
|
187
|
+
* `SystemPromptContract` (named or anonymous). The declared set is the
|
|
188
|
+
* prompt's `meta.required` (when present).
|
|
189
|
+
*/
|
|
190
|
+
function describeContractTarget(contract) {
|
|
191
|
+
const meta = contract.meta();
|
|
192
|
+
return {
|
|
193
|
+
text: contract.resolve(),
|
|
194
|
+
required: meta?.required ?? []
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
//#endregion
|
|
199
|
+
export { describeContractTarget, findMissingPlaceholders, findUnreferencedRequired, judgePromptBodyCached };
|
|
200
|
+
//# sourceMappingURL=prompts-validate.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prompts-validate.mjs","names":[],"sources":["../../../../../../../@warlock.js/ai/src/prompts/prompts-validate.ts"],"sourcesContent":["import { agent } from \"../agent/agent\";\nimport type { AgentContract } from \"../contracts/agent/agent.contract\";\nimport type { ModelContract } from \"../contracts/model.contract\";\nimport type { SystemPromptContract } from \"../contracts/system-prompt.contract\";\nimport { judge } from \"../eval/judge-scorer\";\nimport { PROMPT_JUDGE_RUBRIC } from \"../prompt/prompt-validate\";\nimport type { PromptJudgeCacheLike } from \"./prompts-manager.type\";\n\n/**\n * Placeholder matcher — kept in lock-step with the matcher\n * `renderPlaceholders` (`src/system-prompt/render-placeholders.ts`) and the\n * legacy `prompt-validate` lint both use, so the deterministic validator sees\n * the exact same `{{key}}` / `{{a.b}}` / `{{key|default}}` set the renderer\n * substitutes. Global so every occurrence is collected.\n */\nconst PLACEHOLDER_PATTERN = /\\{\\{\\s*([^{}]+?)\\s*\\}\\}/g;\n\n/**\n * One parsed placeholder occurrence — the key path (the part before any\n * `|default`) and whether the occurrence carried an inline default.\n */\ntype ParsedPlaceholder = {\n /** The dot-path key, e.g. `language` or `user.name`. */\n readonly path: string;\n /** Whether THIS occurrence declared an inline `{{key|default}}` fallback. */\n readonly hasDefault: boolean;\n};\n\n/**\n * Collect every distinct placeholder occurrence from a template, in first-seen\n * order. A key is considered to \"have a default\" only when EVERY occurrence of\n * it carries one — a single bare `{{key}}` means the renderer can leave it\n * unresolved, so the key is still required.\n */\nfunction collectPlaceholders(template: string): ParsedPlaceholder[] {\n const byPath = new Map<string, boolean>();\n const order: string[] = [];\n\n for (const match of template.matchAll(PLACEHOLDER_PATTERN)) {\n const [rawPath, rawDefault] = match[1].split(\"|\");\n const path = rawPath.trim();\n\n if (path.length === 0) {\n continue;\n }\n\n const hasDefault = rawDefault !== undefined;\n\n if (!byPath.has(path)) {\n byPath.set(path, hasDefault);\n order.push(path);\n } else {\n // A key only counts as defaulted when ALL of its occurrences default.\n byPath.set(path, (byPath.get(path) ?? false) && hasDefault);\n }\n }\n\n return order.map(path => ({ path, hasDefault: byPath.get(path) ?? false }));\n}\n\n/**\n * Run the deterministic (model-free) half of validation over a resolved prompt\n * body. Reports every `{{key}}` placeholder that has NO inline default and is\n * neither supplied in `provided` nor declared in `declared` (the prompt's\n * `meta.required` plus any caller-declared keys).\n *\n * Pure and synchronous — the only required half of `validate`; the LLM-judge\n * half is optional and layered on top.\n *\n * @param text - The resolved prompt body (placeholders may still be present).\n * @param provided - Placeholder keys the caller has supplied a value for.\n * @param declared - Placeholder keys declared as known/required (e.g. `meta.required`).\n */\nexport function findMissingPlaceholders(\n text: string,\n provided: ReadonlySet<string>,\n declared: ReadonlySet<string>,\n): string[] {\n const missing: string[] = [];\n\n for (const { path, hasDefault } of collectPlaceholders(text)) {\n if (hasDefault) {\n continue;\n }\n\n if (provided.has(path) || declared.has(path)) {\n continue;\n }\n\n missing.push(path);\n }\n\n return missing;\n}\n\n/**\n * A `meta.required` key absent from the template entirely — declared as\n * required but never referenced — is itself a defect worth surfacing. Returns\n * the declared keys that appear nowhere in the body.\n */\nexport function findUnreferencedRequired(\n text: string,\n required: readonly string[],\n): string[] {\n const present = new Set(collectPlaceholders(text).map(p => p.path));\n\n return required.filter(key => !present.has(key));\n}\n\n/**\n * Build the one-shot judge agent the optional LLM-as-judge pass runs. Mirrors\n * the legacy `prompt.ts` judge agent (strict-JSON instruction so the verdict\n * parses even without an output schema), so the two validate paths share one\n * judging contract.\n */\nfunction buildJudgeAgent(model: ModelContract): AgentContract<unknown> {\n return agent({\n name: \"prompt-quality-judge\",\n model,\n systemPrompt:\n \"You are a strict prompt-quality grader. Respond with JSON only: \" +\n '{ \"score\": <0..1>, \"passed\": <true|false>, \"reason\": \"<short explanation>\" }.',\n });\n}\n\n/** Outcome of the optional LLM-as-judge pass over a resolved prompt body. */\nexport type JudgeOutcome = {\n /**\n * The judge score in `[0, 1]`, or `undefined` when the judge degraded\n * (errored, returned no parseable verdict, or threw) — never a misleading\n * `0` masquerading as a real verdict.\n */\n readonly score?: number;\n /** Human-readable issues raised by the judge (its reason, or a degrade note). */\n readonly issues: string[];\n};\n\n/**\n * Run the optional LLM-as-judge pass over a resolved prompt body, REUSING the\n * eval `judge` scorer (the same path `prompt().validate` uses) so there is no\n * second judging implementation.\n *\n * **Nova-safe by contract.** The judge NEVER throws here: the eval scorer\n * already degrades a broken judge to `score: 0` with a failure reason, and any\n * exception that still escapes (model wiring, agent construction) is caught.\n * Both degrade paths surface `score: undefined` plus an issue note — so a flaky\n * judge can never fail an otherwise-valid prompt.\n *\n * @param text - The resolved prompt body under evaluation.\n * @param model - The model that powers the judge agent.\n */\nexport async function judgePromptBody(\n text: string,\n model: ModelContract,\n): Promise<JudgeOutcome> {\n try {\n const judgeAgent = buildJudgeAgent(model);\n const scorer = judge({ agent: judgeAgent, rubric: PROMPT_JUDGE_RUBRIC });\n\n const verdict = await scorer({\n case: { name: \"prompt-quality\", input: \"Grade the system prompt below.\" },\n text,\n // `result` is unused by the judge scorer's prompt builder; a minimal\n // stand-in keeps the structural contract satisfied without a real run.\n result: { text } as never,\n output: undefined,\n });\n\n // The eval scorer signals a degraded judge with score 0 + a diagnostic\n // reason (\"judge failed: …\" / \"judge returned no parseable verdict\"). Treat\n // that as \"no usable score\" rather than a real 0 verdict.\n const degraded =\n verdict.score === 0 &&\n typeof verdict.reason === \"string\" &&\n /^judge (failed|returned no parseable)/.test(verdict.reason);\n\n if (degraded) {\n return {\n issues: [`LLM-judge unavailable: ${verdict.reason}`],\n };\n }\n\n return {\n score: verdict.score,\n issues: verdict.reason ? [verdict.reason] : [],\n };\n } catch (error) {\n // Last-resort guard: never let a judge failure throw out of validate().\n const message = error instanceof Error ? error.message : String(error);\n\n return {\n issues: [`LLM-judge unavailable: ${message}`],\n };\n }\n}\n\n/**\n * Non-cryptographic 53-bit string hash (cyrb53) — deterministic across runs\n * and platforms, with no `node:crypto` dependency (keeps the validate path\n * usable in any runtime). Mirrors the VCR request hash; collision-resistant\n * enough for a per-prompt judge-verdict keyspace. Returned as base-36.\n */\nfunction hashString(input: string): string {\n let h1 = 0xdeadbeef;\n let h2 = 0x41c6ce57;\n\n for (let i = 0; i < input.length; i++) {\n const ch = input.charCodeAt(i);\n\n h1 = Math.imul(h1 ^ ch, 2654435761);\n h2 = Math.imul(h2 ^ ch, 1597334677);\n }\n\n h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507);\n h1 ^= Math.imul(h2 ^ (h2 >>> 13), 3266489909);\n h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507);\n h2 ^= Math.imul(h1 ^ (h1 >>> 13), 3266489909);\n\n const combined = 4294967296 * (2097151 & h2) + (h1 >>> 0);\n\n return combined.toString(36);\n}\n\n/**\n * Build the judge-verdict cache key for a resolved prompt body + judge model.\n * Combines the model's `provider:name` identity with a content hash of the\n * body, so the same prompt graded by the same judge hits the cache, while any\n * change to either misses it.\n */\nexport function judgeCacheKey(text: string, model: ModelContract): string {\n return `prompts.judge.${model.provider}:${model.name}.${hashString(text)}`;\n}\n\n/**\n * Run the judge pass with an OPTIONAL memo cache in front. On a hit, the stored\n * {@link JudgeOutcome} is returned without a model call; on a miss, the live\n * judge runs and a USABLE verdict (one carrying a `score`) is written back.\n * Degraded outcomes (no score) are NOT cached — a transient judge failure must\n * never poison the memo. A `null`/absent cache degrades to a direct judge call.\n *\n * Cache I/O is itself fault-tolerant: a `get`/`set` that rejects is swallowed\n * so a flaky cache can never break (or fail) validation.\n *\n * @param text - The resolved prompt body under evaluation.\n * @param model - The judge model.\n * @param cache - Optional verdict memo (any `CacheDriver`-like get/set surface).\n */\nexport async function judgePromptBodyCached(\n text: string,\n model: ModelContract,\n cache?: PromptJudgeCacheLike,\n): Promise<JudgeOutcome> {\n if (!cache) {\n return judgePromptBody(text, model);\n }\n\n const key = judgeCacheKey(text, model);\n\n const cached = await readJudgeCache(cache, key);\n\n if (cached) {\n return cached;\n }\n\n const outcome = await judgePromptBody(text, model);\n\n // Only memoize a usable verdict — never a degraded (scoreless) one.\n if (outcome.score !== undefined) {\n await writeJudgeCache(cache, key, outcome);\n }\n\n return outcome;\n}\n\n/** Read a cached verdict, swallowing any cache fault (treated as a miss). */\nasync function readJudgeCache(\n cache: PromptJudgeCacheLike,\n key: string,\n): Promise<JudgeOutcome | undefined> {\n try {\n const value = await cache.get<JudgeOutcome>(key);\n\n return value ?? undefined;\n } catch {\n return undefined;\n }\n}\n\n/** Write a verdict, swallowing any cache fault (best-effort memo). */\nasync function writeJudgeCache(\n cache: PromptJudgeCacheLike,\n key: string,\n outcome: JudgeOutcome,\n): Promise<void> {\n try {\n await cache.set(key, outcome);\n } catch {\n // Best-effort — a failed memo write never affects the validation result.\n }\n}\n\n/**\n * Resolve the body + declared-required keys for a validation target that is a\n * `SystemPromptContract` (named or anonymous). The declared set is the\n * prompt's `meta.required` (when present).\n */\nexport function describeContractTarget(contract: SystemPromptContract): {\n text: string;\n required: readonly string[];\n} {\n const meta = contract.meta();\n\n return {\n text: contract.resolve(),\n required: meta?.required ?? [],\n };\n}\n"],"mappings":";;;;;;;;;;;;AAeA,MAAM,sBAAsB;;;;;;;AAmB5B,SAAS,oBAAoB,UAAuC;CAClE,MAAM,yBAAS,IAAI,IAAqB;CACxC,MAAM,QAAkB,CAAC;CAEzB,KAAK,MAAM,SAAS,SAAS,SAAS,mBAAmB,GAAG;EAC1D,MAAM,CAAC,SAAS,cAAc,MAAM,EAAE,CAAC,MAAM,GAAG;EAChD,MAAM,OAAO,QAAQ,KAAK;EAE1B,IAAI,KAAK,WAAW,GAClB;EAGF,MAAM,aAAa,eAAe;EAElC,IAAI,CAAC,OAAO,IAAI,IAAI,GAAG;GACrB,OAAO,IAAI,MAAM,UAAU;GAC3B,MAAM,KAAK,IAAI;EACjB,OAEE,OAAO,IAAI,OAAO,OAAO,IAAI,IAAI,KAAK,UAAU,UAAU;CAE9D;CAEA,OAAO,MAAM,KAAI,UAAS;EAAE;EAAM,YAAY,OAAO,IAAI,IAAI,KAAK;CAAM,EAAE;AAC5E;;;;;;;;;;;;;;AAeA,SAAgB,wBACd,MACA,UACA,UACU;CACV,MAAM,UAAoB,CAAC;CAE3B,KAAK,MAAM,EAAE,MAAM,gBAAgB,oBAAoB,IAAI,GAAG;EAC5D,IAAI,YACF;EAGF,IAAI,SAAS,IAAI,IAAI,KAAK,SAAS,IAAI,IAAI,GACzC;EAGF,QAAQ,KAAK,IAAI;CACnB;CAEA,OAAO;AACT;;;;;;AAOA,SAAgB,yBACd,MACA,UACU;CACV,MAAM,UAAU,IAAI,IAAI,oBAAoB,IAAI,CAAC,CAAC,KAAI,MAAK,EAAE,IAAI,CAAC;CAElE,OAAO,SAAS,QAAO,QAAO,CAAC,QAAQ,IAAI,GAAG,CAAC;AACjD;;;;;;;AAQA,SAAS,gBAAgB,OAA8C;CACrE,OAAO,MAAM;EACX,MAAM;EACN;EACA,cACE;CAEJ,CAAC;AACH;;;;;;;;;;;;;;;AA4BA,eAAsB,gBACpB,MACA,OACuB;CACvB,IAAI;EAIF,MAAM,UAAU,MAFD,MAAM;GAAE,OADJ,gBAAgB,KACI;GAAG,QAAQ;EAAoB,CAE3C,CAAC,CAAC;GAC3B,MAAM;IAAE,MAAM;IAAkB,OAAO;GAAiC;GACxE;GAGA,QAAQ,EAAE,KAAK;GACf,QAAQ;EACV,CAAC;EAUD,IAJE,QAAQ,UAAU,KAClB,OAAO,QAAQ,WAAW,YAC1B,wCAAwC,KAAK,QAAQ,MAAM,GAG3D,OAAO,EACL,QAAQ,CAAC,0BAA0B,QAAQ,QAAQ,EACrD;EAGF,OAAO;GACL,OAAO,QAAQ;GACf,QAAQ,QAAQ,SAAS,CAAC,QAAQ,MAAM,IAAI,CAAC;EAC/C;CACF,SAAS,OAAO;EAId,OAAO,EACL,QAAQ,CAAC,0BAHK,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,GAGvB,EAC9C;CACF;AACF;;;;;;;AAQA,SAAS,WAAW,OAAuB;CACzC,IAAI,KAAK;CACT,IAAI,KAAK;CAET,KAAK,IAAI,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;EACrC,MAAM,KAAK,MAAM,WAAW,CAAC;EAE7B,KAAK,KAAK,KAAK,KAAK,IAAI,UAAU;EAClC,KAAK,KAAK,KAAK,KAAK,IAAI,UAAU;CACpC;CAEA,KAAK,KAAK,KAAK,KAAM,OAAO,IAAK,UAAU;CAC3C,MAAM,KAAK,KAAK,KAAM,OAAO,IAAK,UAAU;CAC5C,KAAK,KAAK,KAAK,KAAM,OAAO,IAAK,UAAU;CAC3C,MAAM,KAAK,KAAK,KAAM,OAAO,IAAK,UAAU;CAI5C,QAFiB,cAAc,UAAU,OAAO,OAAO,GAExC,CAAC,SAAS,EAAE;AAC7B;;;;;;;AAQA,SAAgB,cAAc,MAAc,OAA8B;CACxE,OAAO,iBAAiB,MAAM,SAAS,GAAG,MAAM,KAAK,GAAG,WAAW,IAAI;AACzE;;;;;;;;;;;;;;;AAgBA,eAAsB,sBACpB,MACA,OACA,OACuB;CACvB,IAAI,CAAC,OACH,OAAO,gBAAgB,MAAM,KAAK;CAGpC,MAAM,MAAM,cAAc,MAAM,KAAK;CAErC,MAAM,SAAS,MAAM,eAAe,OAAO,GAAG;CAE9C,IAAI,QACF,OAAO;CAGT,MAAM,UAAU,MAAM,gBAAgB,MAAM,KAAK;CAGjD,IAAI,QAAQ,UAAU,QACpB,MAAM,gBAAgB,OAAO,KAAK,OAAO;CAG3C,OAAO;AACT;;AAGA,eAAe,eACb,OACA,KACmC;CACnC,IAAI;EAGF,OAAO,MAFa,MAAM,IAAkB,GAAG,KAE/B;CAClB,QAAQ;EACN;CACF;AACF;;AAGA,eAAe,gBACb,OACA,KACA,SACe;CACf,IAAI;EACF,MAAM,MAAM,IAAI,KAAK,OAAO;CAC9B,QAAQ,CAER;AACF;;;;;;AAOA,SAAgB,uBAAuB,UAGrC;CACA,MAAM,OAAO,SAAS,KAAK;CAE3B,OAAO;EACL,MAAM,SAAS,QAAQ;EACvB,UAAU,MAAM,YAAY,CAAC;CAC/B;AACF"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { tool } from "../tool/tool.mjs";
|
|
2
|
+
|
|
3
|
+
//#region ../@warlock.js/ai/src/rag/as-tool.ts
|
|
4
|
+
/**
|
|
5
|
+
* A minimal, schema-library-agnostic Standard Schema for `{ query: string }`.
|
|
6
|
+
*
|
|
7
|
+
* Built by hand (no `seal` / `zod` import) so `asTool()` stays dependency-
|
|
8
|
+
* free and matches the framework's own `passthroughSchema` style — the
|
|
9
|
+
* `~standard.validate` returns `{ issues }` on a bad shape so the tool
|
|
10
|
+
* runtime surfaces a `SchemaValidationError` exactly like any other tool.
|
|
11
|
+
*/
|
|
12
|
+
function ragToolSchema() {
|
|
13
|
+
return { "~standard": {
|
|
14
|
+
version: 1,
|
|
15
|
+
vendor: "warlock-ai-rag",
|
|
16
|
+
validate: (value) => {
|
|
17
|
+
if (!value || typeof value !== "object" || typeof value.query !== "string") return { issues: [{ message: "rag tool input must be { query: string }" }] };
|
|
18
|
+
return { value: { query: value.query } };
|
|
19
|
+
}
|
|
20
|
+
} };
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Build a `ToolContract<{ query: string }, RetrieveResult>` that exposes a
|
|
24
|
+
* rag's `retrieve()` to an agent's `tools: []` loop.
|
|
25
|
+
*
|
|
26
|
+
* `retrieve()` is a leaf operation (no inner executable report to nest),
|
|
27
|
+
* so the plain `tool()` factory is the right shape — not `compositeAsTool`.
|
|
28
|
+
* The resulting contract has `invoke`, so `isExecutableTool` returns false
|
|
29
|
+
* and `normalizeAgentTools` passes it through untouched. On a thrown
|
|
30
|
+
* retrieval error the runtime serializes `{ error }` back to the agent for
|
|
31
|
+
* self-correction; the run does not abort.
|
|
32
|
+
*
|
|
33
|
+
* The tool name defaults to `retrieve_<name>` — namespaced by the rag's
|
|
34
|
+
* name because the agent tool surface has no duplicate-name collision
|
|
35
|
+
* guard (first match wins silently).
|
|
36
|
+
*/
|
|
37
|
+
function ragAsTool(name, retrieveFn, options = {}) {
|
|
38
|
+
return tool({
|
|
39
|
+
name: options.name ?? `retrieve_${name}`,
|
|
40
|
+
description: options.description ?? `Search the "${name}" knowledge base and return the most relevant cited passages for a query.`,
|
|
41
|
+
input: ragToolSchema(),
|
|
42
|
+
execute: async ({ query }) => retrieveFn(query, options.retrieve)
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
//#endregion
|
|
47
|
+
export { ragAsTool };
|
|
48
|
+
//# sourceMappingURL=as-tool.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"as-tool.mjs","names":[],"sources":["../../../../../../../@warlock.js/ai/src/rag/as-tool.ts"],"sourcesContent":["import type { StandardSchemaV1 } from \"@standard-schema/spec\";\nimport type { ToolContract } from \"../tool/tool\";\nimport { tool } from \"../tool/tool\";\nimport type { RetrieveOptions, RetrieveResult } from \"./contracts/citation.type\";\nimport type { RagAsToolOptions } from \"./contracts/rag-config.type\";\n\n/** The validated input shape of a rag tool. */\ntype RagToolInput = { query: string };\n\n/**\n * A minimal, schema-library-agnostic Standard Schema for `{ query: string }`.\n *\n * Built by hand (no `seal` / `zod` import) so `asTool()` stays dependency-\n * free and matches the framework's own `passthroughSchema` style — the\n * `~standard.validate` returns `{ issues }` on a bad shape so the tool\n * runtime surfaces a `SchemaValidationError` exactly like any other tool.\n */\nfunction ragToolSchema(): StandardSchemaV1<RagToolInput> {\n return {\n \"~standard\": {\n version: 1,\n vendor: \"warlock-ai-rag\",\n validate: (value: unknown) => {\n if (\n !value ||\n typeof value !== \"object\" ||\n typeof (value as { query?: unknown }).query !== \"string\"\n ) {\n return {\n issues: [{ message: \"rag tool input must be { query: string }\" }],\n };\n }\n\n return { value: { query: (value as RagToolInput).query } };\n },\n },\n };\n}\n\n/**\n * Build a `ToolContract<{ query: string }, RetrieveResult>` that exposes a\n * rag's `retrieve()` to an agent's `tools: []` loop.\n *\n * `retrieve()` is a leaf operation (no inner executable report to nest),\n * so the plain `tool()` factory is the right shape — not `compositeAsTool`.\n * The resulting contract has `invoke`, so `isExecutableTool` returns false\n * and `normalizeAgentTools` passes it through untouched. On a thrown\n * retrieval error the runtime serializes `{ error }` back to the agent for\n * self-correction; the run does not abort.\n *\n * The tool name defaults to `retrieve_<name>` — namespaced by the rag's\n * name because the agent tool surface has no duplicate-name collision\n * guard (first match wins silently).\n */\nexport function ragAsTool(\n name: string,\n retrieveFn: (query: string, options?: RetrieveOptions) => Promise<RetrieveResult>,\n options: RagAsToolOptions = {},\n): ToolContract<RagToolInput, RetrieveResult> {\n const toolName = options.name ?? `retrieve_${name}`;\n\n return tool<RagToolInput, RetrieveResult>({\n name: toolName,\n description:\n options.description ??\n `Search the \"${name}\" knowledge base and return the most relevant cited passages for a query.`,\n input: ragToolSchema(),\n execute: async ({ query }) => retrieveFn(query, options.retrieve),\n });\n}\n"],"mappings":";;;;;;;;;;;AAiBA,SAAS,gBAAgD;CACvD,OAAO,EACL,aAAa;EACX,SAAS;EACT,QAAQ;EACR,WAAW,UAAmB;GAC5B,IACE,CAAC,SACD,OAAO,UAAU,YACjB,OAAQ,MAA8B,UAAU,UAEhD,OAAO,EACL,QAAQ,CAAC,EAAE,SAAS,2CAA2C,CAAC,EAClE;GAGF,OAAO,EAAE,OAAO,EAAE,OAAQ,MAAuB,MAAM,EAAE;EAC3D;CACF,EACF;AACF;;;;;;;;;;;;;;;;AAiBA,SAAgB,UACd,MACA,YACA,UAA4B,CAAC,GACe;CAG5C,OAAO,KAAmC;EACxC,MAHe,QAAQ,QAAQ,YAAY;EAI3C,aACE,QAAQ,eACR,eAAe,KAAK;EACtB,OAAO,cAAc;EACrB,SAAS,OAAO,EAAE,YAAY,WAAW,OAAO,QAAQ,QAAQ;CAClE,CAAC;AACH"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Chunk, ChunkOptions } from "../contracts/chunk-options.type.mjs";
|
|
2
|
+
|
|
3
|
+
//#region ../@warlock.js/ai/src/rag/chunk/chunk.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Split `text` into citation-bearing {@link Chunk}s according to
|
|
6
|
+
* {@link ChunkOptions}, dispatching on `options.type`:
|
|
7
|
+
*
|
|
8
|
+
* - `"recursive"` (default) — separator-aware greedy packing.
|
|
9
|
+
* - `"markdown"` — heading/section-aware, then recursive within sections.
|
|
10
|
+
* - `"sentence"` — packs whole sentences.
|
|
11
|
+
* - `"fixed"` — back-to-back character windows.
|
|
12
|
+
*
|
|
13
|
+
* All strategies are character-based (tokenizer-free) and record the exact
|
|
14
|
+
* `[start, end)` span of every chunk in the original text. Empty or
|
|
15
|
+
* whitespace-only input yields `[]`.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* const chunks = chunk(markdownDoc, { type: "markdown", size: 800, overlap: 120 });
|
|
19
|
+
* for (const c of chunks) console.log(c.index, c.span, c.text);
|
|
20
|
+
*/
|
|
21
|
+
declare function chunk(text: string, options?: ChunkOptions): Chunk[];
|
|
22
|
+
//#endregion
|
|
23
|
+
export { chunk };
|
|
24
|
+
//# sourceMappingURL=chunk.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chunk.d.mts","names":[],"sources":["../../../../../../../../@warlock.js/ai/src/rag/chunk/chunk.ts"],"mappings":";;;;;;;;;;AA6BsE;;;;;;;;;;iBAAtD,KAAA,CAAM,IAAA,UAAc,OAAA,GAAS,YAAA,GAAoB,KAAK"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { fixedChunk } from "./fixed.mjs";
|
|
2
|
+
import { DEFAULT_SEPARATORS, recursiveChunk } from "./recursive.mjs";
|
|
3
|
+
import { markdownChunk } from "./markdown.mjs";
|
|
4
|
+
import { sentenceChunk } from "./sentence.mjs";
|
|
5
|
+
|
|
6
|
+
//#region ../@warlock.js/ai/src/rag/chunk/chunk.ts
|
|
7
|
+
/** Default target chunk size in characters. */
|
|
8
|
+
const DEFAULT_CHUNK_SIZE = 1e3;
|
|
9
|
+
/** Default character overlap carried between adjacent chunks. */
|
|
10
|
+
const DEFAULT_CHUNK_OVERLAP = 200;
|
|
11
|
+
/**
|
|
12
|
+
* Split `text` into citation-bearing {@link Chunk}s according to
|
|
13
|
+
* {@link ChunkOptions}, dispatching on `options.type`:
|
|
14
|
+
*
|
|
15
|
+
* - `"recursive"` (default) — separator-aware greedy packing.
|
|
16
|
+
* - `"markdown"` — heading/section-aware, then recursive within sections.
|
|
17
|
+
* - `"sentence"` — packs whole sentences.
|
|
18
|
+
* - `"fixed"` — back-to-back character windows.
|
|
19
|
+
*
|
|
20
|
+
* All strategies are character-based (tokenizer-free) and record the exact
|
|
21
|
+
* `[start, end)` span of every chunk in the original text. Empty or
|
|
22
|
+
* whitespace-only input yields `[]`.
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* const chunks = chunk(markdownDoc, { type: "markdown", size: 800, overlap: 120 });
|
|
26
|
+
* for (const c of chunks) console.log(c.index, c.span, c.text);
|
|
27
|
+
*/
|
|
28
|
+
function chunk(text, options = {}) {
|
|
29
|
+
const type = options.type ?? "recursive";
|
|
30
|
+
const size = options.size ?? 1e3;
|
|
31
|
+
const overlap = options.overlap ?? 200;
|
|
32
|
+
const separators = options.separators ?? DEFAULT_SEPARATORS;
|
|
33
|
+
if (text.trim().length === 0) return [];
|
|
34
|
+
switch (type) {
|
|
35
|
+
case "markdown": return markdownChunk(text, size, overlap, separators);
|
|
36
|
+
case "sentence": return sentenceChunk(text, size, overlap);
|
|
37
|
+
case "fixed": return fixedChunk(text, size, overlap);
|
|
38
|
+
default: return recursiveChunk(text, size, overlap, separators);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
//#endregion
|
|
43
|
+
export { chunk };
|
|
44
|
+
//# sourceMappingURL=chunk.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chunk.mjs","names":[],"sources":["../../../../../../../../@warlock.js/ai/src/rag/chunk/chunk.ts"],"sourcesContent":["import type { Chunk, ChunkOptions } from \"../contracts/chunk-options.type\";\nimport { fixedChunk } from \"./fixed\";\nimport { markdownChunk } from \"./markdown\";\nimport { DEFAULT_SEPARATORS, recursiveChunk } from \"./recursive\";\nimport { sentenceChunk } from \"./sentence\";\n\n/** Default target chunk size in characters. */\nexport const DEFAULT_CHUNK_SIZE = 1000;\n\n/** Default character overlap carried between adjacent chunks. */\nexport const DEFAULT_CHUNK_OVERLAP = 200;\n\n/**\n * Split `text` into citation-bearing {@link Chunk}s according to\n * {@link ChunkOptions}, dispatching on `options.type`:\n *\n * - `\"recursive\"` (default) — separator-aware greedy packing.\n * - `\"markdown\"` — heading/section-aware, then recursive within sections.\n * - `\"sentence\"` — packs whole sentences.\n * - `\"fixed\"` — back-to-back character windows.\n *\n * All strategies are character-based (tokenizer-free) and record the exact\n * `[start, end)` span of every chunk in the original text. Empty or\n * whitespace-only input yields `[]`.\n *\n * @example\n * const chunks = chunk(markdownDoc, { type: \"markdown\", size: 800, overlap: 120 });\n * for (const c of chunks) console.log(c.index, c.span, c.text);\n */\nexport function chunk(text: string, options: ChunkOptions = {}): Chunk[] {\n const type = options.type ?? \"recursive\";\n const size = options.size ?? DEFAULT_CHUNK_SIZE;\n const overlap = options.overlap ?? DEFAULT_CHUNK_OVERLAP;\n const separators = options.separators ?? DEFAULT_SEPARATORS;\n\n // Empty or whitespace-only input yields no chunks — index() then writes\n // nothing and never embeds an empty batch.\n if (text.trim().length === 0) {\n return [];\n }\n\n switch (type) {\n case \"markdown\":\n return markdownChunk(text, size, overlap, separators);\n\n case \"sentence\":\n return sentenceChunk(text, size, overlap);\n\n case \"fixed\":\n return fixedChunk(text, size, overlap);\n\n case \"recursive\":\n default:\n return recursiveChunk(text, size, overlap, separators);\n }\n}\n"],"mappings":";;;;;;;AAOA,MAAa,qBAAqB;;AAGlC,MAAa,wBAAwB;;;;;;;;;;;;;;;;;;AAmBrC,SAAgB,MAAM,MAAc,UAAwB,CAAC,GAAY;CACvE,MAAM,OAAO,QAAQ,QAAQ;CAC7B,MAAM,OAAO,QAAQ;CACrB,MAAM,UAAU,QAAQ;CACxB,MAAM,aAAa,QAAQ,cAAc;CAIzC,IAAI,KAAK,KAAK,CAAC,CAAC,WAAW,GACzB,OAAO,CAAC;CAGV,QAAQ,MAAR;EACE,KAAK,YACH,OAAO,cAAc,MAAM,MAAM,SAAS,UAAU;EAEtD,KAAK,YACH,OAAO,cAAc,MAAM,MAAM,OAAO;EAE1C,KAAK,SACH,OAAO,WAAW,MAAM,MAAM,OAAO;EAGvC,SACE,OAAO,eAAe,MAAM,MAAM,SAAS,UAAU;CACzD;AACF"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
//#region ../@warlock.js/ai/src/rag/chunk/fixed.ts
|
|
2
|
+
/**
|
|
3
|
+
* Fixed-window character splitter.
|
|
4
|
+
*
|
|
5
|
+
* The simplest strategy: slices the text into back-to-back windows of
|
|
6
|
+
* `size` characters, stepping forward by `size - overlap` so adjacent
|
|
7
|
+
* windows share `overlap` characters. Boundary-unaware — it will cut
|
|
8
|
+
* mid-word — but deterministic and dep-free. Spans are exact by
|
|
9
|
+
* construction.
|
|
10
|
+
*/
|
|
11
|
+
function fixedChunk(text, size, overlap) {
|
|
12
|
+
if (text.length === 0) return [];
|
|
13
|
+
const step = Math.max(1, size - overlap);
|
|
14
|
+
const chunks = [];
|
|
15
|
+
let index = 0;
|
|
16
|
+
for (let cursor = 0; cursor < text.length; cursor += step) {
|
|
17
|
+
const start = cursor;
|
|
18
|
+
const end = Math.min(cursor + size, text.length);
|
|
19
|
+
chunks.push({
|
|
20
|
+
text: text.slice(start, end),
|
|
21
|
+
index,
|
|
22
|
+
span: [start, end]
|
|
23
|
+
});
|
|
24
|
+
index += 1;
|
|
25
|
+
if (end >= text.length) break;
|
|
26
|
+
}
|
|
27
|
+
return chunks;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
//#endregion
|
|
31
|
+
export { fixedChunk };
|
|
32
|
+
//# sourceMappingURL=fixed.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fixed.mjs","names":[],"sources":["../../../../../../../../@warlock.js/ai/src/rag/chunk/fixed.ts"],"sourcesContent":["import type { Chunk } from \"../contracts/chunk-options.type\";\n\n/**\n * Fixed-window character splitter.\n *\n * The simplest strategy: slices the text into back-to-back windows of\n * `size` characters, stepping forward by `size - overlap` so adjacent\n * windows share `overlap` characters. Boundary-unaware — it will cut\n * mid-word — but deterministic and dep-free. Spans are exact by\n * construction.\n */\nexport function fixedChunk(text: string, size: number, overlap: number): Chunk[] {\n if (text.length === 0) {\n return [];\n }\n\n const step = Math.max(1, size - overlap);\n const chunks: Chunk[] = [];\n let index = 0;\n\n for (let cursor = 0; cursor < text.length; cursor += step) {\n const start = cursor;\n const end = Math.min(cursor + size, text.length);\n\n chunks.push({ text: text.slice(start, end), index, span: [start, end] });\n index += 1;\n\n if (end >= text.length) {\n break;\n }\n }\n\n return chunks;\n}\n"],"mappings":";;;;;;;;;;AAWA,SAAgB,WAAW,MAAc,MAAc,SAA0B;CAC/E,IAAI,KAAK,WAAW,GAClB,OAAO,CAAC;CAGV,MAAM,OAAO,KAAK,IAAI,GAAG,OAAO,OAAO;CACvC,MAAM,SAAkB,CAAC;CACzB,IAAI,QAAQ;CAEZ,KAAK,IAAI,SAAS,GAAG,SAAS,KAAK,QAAQ,UAAU,MAAM;EACzD,MAAM,QAAQ;EACd,MAAM,MAAM,KAAK,IAAI,SAAS,MAAM,KAAK,MAAM;EAE/C,OAAO,KAAK;GAAE,MAAM,KAAK,MAAM,OAAO,GAAG;GAAG;GAAO,MAAM,CAAC,OAAO,GAAG;EAAE,CAAC;EACvE,SAAS;EAET,IAAI,OAAO,KAAK,QACd;CAEJ;CAEA,OAAO;AACT"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { DEFAULT_SEPARATORS, recursiveChunk } from "./recursive.mjs";
|
|
2
|
+
|
|
3
|
+
//#region ../@warlock.js/ai/src/rag/chunk/markdown.ts
|
|
4
|
+
/** Matches an ATX Markdown heading line (`#` … `######`) at line start. */
|
|
5
|
+
const HEADING_LINE = /^#{1,6}[ \t].*$/gm;
|
|
6
|
+
/**
|
|
7
|
+
* Markdown heading/section-aware splitter.
|
|
8
|
+
*
|
|
9
|
+
* Splits the document on ATX heading boundaries (`#`…`######`) first so a
|
|
10
|
+
* section's heading stays glued to its body, then applies the recursive
|
|
11
|
+
* character splitter WITHIN each section so any section larger than `size`
|
|
12
|
+
* is broken down further. Sections at or under `size` are emitted whole.
|
|
13
|
+
* Spans are exact relative to the original document.
|
|
14
|
+
*/
|
|
15
|
+
function markdownChunk(text, size, overlap, separators = DEFAULT_SEPARATORS) {
|
|
16
|
+
if (text.length === 0) return [];
|
|
17
|
+
const sections = splitSections(text);
|
|
18
|
+
const chunks = [];
|
|
19
|
+
let index = 0;
|
|
20
|
+
for (const section of sections) {
|
|
21
|
+
const body = text.slice(section.start, section.end);
|
|
22
|
+
if (body.trim().length === 0) continue;
|
|
23
|
+
if (body.length <= size) {
|
|
24
|
+
chunks.push({
|
|
25
|
+
text: body,
|
|
26
|
+
index,
|
|
27
|
+
span: [section.start, section.end]
|
|
28
|
+
});
|
|
29
|
+
index += 1;
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
const inner = recursiveChunk(body, size, overlap, separators);
|
|
33
|
+
for (const piece of inner) {
|
|
34
|
+
chunks.push({
|
|
35
|
+
text: piece.text,
|
|
36
|
+
index,
|
|
37
|
+
span: [section.start + piece.span[0], section.start + piece.span[1]]
|
|
38
|
+
});
|
|
39
|
+
index += 1;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return chunks;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Carve the document into sections, each beginning at a heading line and
|
|
46
|
+
* running until the next heading (the preamble before the first heading is
|
|
47
|
+
* its own section). Spans cover the whole document with no gaps.
|
|
48
|
+
*/
|
|
49
|
+
function splitSections(text) {
|
|
50
|
+
const starts = [];
|
|
51
|
+
let match;
|
|
52
|
+
HEADING_LINE.lastIndex = 0;
|
|
53
|
+
while ((match = HEADING_LINE.exec(text)) !== null) starts.push(match.index);
|
|
54
|
+
if (starts.length === 0) return [{
|
|
55
|
+
start: 0,
|
|
56
|
+
end: text.length
|
|
57
|
+
}];
|
|
58
|
+
const sections = [];
|
|
59
|
+
if (starts[0] > 0) sections.push({
|
|
60
|
+
start: 0,
|
|
61
|
+
end: starts[0]
|
|
62
|
+
});
|
|
63
|
+
starts.forEach((start, position) => {
|
|
64
|
+
const end = position + 1 < starts.length ? starts[position + 1] : text.length;
|
|
65
|
+
sections.push({
|
|
66
|
+
start,
|
|
67
|
+
end
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
return sections;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
//#endregion
|
|
74
|
+
export { markdownChunk };
|
|
75
|
+
//# sourceMappingURL=markdown.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"markdown.mjs","names":[],"sources":["../../../../../../../../@warlock.js/ai/src/rag/chunk/markdown.ts"],"sourcesContent":["import type { Chunk } from \"../contracts/chunk-options.type\";\nimport { DEFAULT_SEPARATORS, recursiveChunk } from \"./recursive\";\n\n/** Matches an ATX Markdown heading line (`#` … `######`) at line start. */\nconst HEADING_LINE = /^#{1,6}[ \\t].*$/gm;\n\n/**\n * Markdown heading/section-aware splitter.\n *\n * Splits the document on ATX heading boundaries (`#`…`######`) first so a\n * section's heading stays glued to its body, then applies the recursive\n * character splitter WITHIN each section so any section larger than `size`\n * is broken down further. Sections at or under `size` are emitted whole.\n * Spans are exact relative to the original document.\n */\nexport function markdownChunk(\n text: string,\n size: number,\n overlap: number,\n separators: string[] = DEFAULT_SEPARATORS,\n): Chunk[] {\n if (text.length === 0) {\n return [];\n }\n\n const sections = splitSections(text);\n const chunks: Chunk[] = [];\n let index = 0;\n\n for (const section of sections) {\n const body = text.slice(section.start, section.end);\n\n if (body.trim().length === 0) {\n continue;\n }\n\n if (body.length <= size) {\n chunks.push({\n text: body,\n index,\n span: [section.start, section.end],\n });\n index += 1;\n\n continue;\n }\n\n // Recurse within the section, then shift the relative spans to\n // absolute document offsets and renumber sequentially.\n const inner = recursiveChunk(body, size, overlap, separators);\n\n for (const piece of inner) {\n chunks.push({\n text: piece.text,\n index,\n span: [section.start + piece.span[0], section.start + piece.span[1]],\n });\n index += 1;\n }\n }\n\n return chunks;\n}\n\n/** A section's absolute `[start, end)` span (heading line + body until next heading). */\ntype SectionSpan = {\n start: number;\n end: number;\n};\n\n/**\n * Carve the document into sections, each beginning at a heading line and\n * running until the next heading (the preamble before the first heading is\n * its own section). Spans cover the whole document with no gaps.\n */\nfunction splitSections(text: string): SectionSpan[] {\n const starts: number[] = [];\n let match: RegExpExecArray | null;\n\n HEADING_LINE.lastIndex = 0;\n\n while ((match = HEADING_LINE.exec(text)) !== null) {\n starts.push(match.index);\n }\n\n // No headings at all — the whole document is one section.\n if (starts.length === 0) {\n return [{ start: 0, end: text.length }];\n }\n\n const sections: SectionSpan[] = [];\n\n // Preamble before the first heading, if any.\n if (starts[0] > 0) {\n sections.push({ start: 0, end: starts[0] });\n }\n\n starts.forEach((start, position) => {\n const end = position + 1 < starts.length ? starts[position + 1] : text.length;\n\n sections.push({ start, end });\n });\n\n return sections;\n}\n"],"mappings":";;;;AAIA,MAAM,eAAe;;;;;;;;;;AAWrB,SAAgB,cACd,MACA,MACA,SACA,aAAuB,oBACd;CACT,IAAI,KAAK,WAAW,GAClB,OAAO,CAAC;CAGV,MAAM,WAAW,cAAc,IAAI;CACnC,MAAM,SAAkB,CAAC;CACzB,IAAI,QAAQ;CAEZ,KAAK,MAAM,WAAW,UAAU;EAC9B,MAAM,OAAO,KAAK,MAAM,QAAQ,OAAO,QAAQ,GAAG;EAElD,IAAI,KAAK,KAAK,CAAC,CAAC,WAAW,GACzB;EAGF,IAAI,KAAK,UAAU,MAAM;GACvB,OAAO,KAAK;IACV,MAAM;IACN;IACA,MAAM,CAAC,QAAQ,OAAO,QAAQ,GAAG;GACnC,CAAC;GACD,SAAS;GAET;EACF;EAIA,MAAM,QAAQ,eAAe,MAAM,MAAM,SAAS,UAAU;EAE5D,KAAK,MAAM,SAAS,OAAO;GACzB,OAAO,KAAK;IACV,MAAM,MAAM;IACZ;IACA,MAAM,CAAC,QAAQ,QAAQ,MAAM,KAAK,IAAI,QAAQ,QAAQ,MAAM,KAAK,EAAE;GACrE,CAAC;GACD,SAAS;EACX;CACF;CAEA,OAAO;AACT;;;;;;AAaA,SAAS,cAAc,MAA6B;CAClD,MAAM,SAAmB,CAAC;CAC1B,IAAI;CAEJ,aAAa,YAAY;CAEzB,QAAQ,QAAQ,aAAa,KAAK,IAAI,OAAO,MAC3C,OAAO,KAAK,MAAM,KAAK;CAIzB,IAAI,OAAO,WAAW,GACpB,OAAO,CAAC;EAAE,OAAO;EAAG,KAAK,KAAK;CAAO,CAAC;CAGxC,MAAM,WAA0B,CAAC;CAGjC,IAAI,OAAO,KAAK,GACd,SAAS,KAAK;EAAE,OAAO;EAAG,KAAK,OAAO;CAAG,CAAC;CAG5C,OAAO,SAAS,OAAO,aAAa;EAClC,MAAM,MAAM,WAAW,IAAI,OAAO,SAAS,OAAO,WAAW,KAAK,KAAK;EAEvE,SAAS,KAAK;GAAE;GAAO;EAAI,CAAC;CAC9B,CAAC;CAED,OAAO;AACT"}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
//#region ../@warlock.js/ai/src/rag/chunk/recursive.ts
|
|
2
|
+
/** Default separators for the recursive splitter, tried largest-unit first. */
|
|
3
|
+
const DEFAULT_SEPARATORS = [
|
|
4
|
+
"\n\n",
|
|
5
|
+
"\n",
|
|
6
|
+
". ",
|
|
7
|
+
" ",
|
|
8
|
+
""
|
|
9
|
+
];
|
|
10
|
+
/**
|
|
11
|
+
* Recursive character splitter (the default strategy).
|
|
12
|
+
*
|
|
13
|
+
* Walks `separators` largest-unit-first: it splits the text on the first
|
|
14
|
+
* separator, then packs the resulting pieces into chunks up to `size`
|
|
15
|
+
* characters, carrying `overlap` characters forward between adjacent
|
|
16
|
+
* chunks. Any single piece that is itself larger than `size` is split
|
|
17
|
+
* again on the next-finer separator, recursing until a piece fits (the
|
|
18
|
+
* `""` separator is the final char-by-char fallback).
|
|
19
|
+
*
|
|
20
|
+
* Every emitted chunk records its exact `[start, end)` character span in
|
|
21
|
+
* the ORIGINAL text so a citation can point back precisely — spans are
|
|
22
|
+
* tracked by index-of search as packed pieces are joined.
|
|
23
|
+
*
|
|
24
|
+
* Character-based and deliberately tokenizer-free.
|
|
25
|
+
*/
|
|
26
|
+
function recursiveChunk(text, size, overlap, separators = DEFAULT_SEPARATORS) {
|
|
27
|
+
return packPieces(text, anchorPieces(text, splitToPieces(text, size, separators)), size, overlap);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Recursively split `text` into pieces no larger than `size` using the
|
|
31
|
+
* ordered separator list. Pieces preserve original characters (no
|
|
32
|
+
* trimming) so downstream span anchoring stays exact.
|
|
33
|
+
*/
|
|
34
|
+
function splitToPieces(text, size, separators) {
|
|
35
|
+
if (text.length <= size) return text.length > 0 ? [text] : [];
|
|
36
|
+
const [separator, ...rest] = separators;
|
|
37
|
+
if (separator === void 0 || separator === "") return hardSplit(text, size);
|
|
38
|
+
const segments = splitKeepingSeparator(text, separator);
|
|
39
|
+
const pieces = [];
|
|
40
|
+
for (const segment of segments) {
|
|
41
|
+
if (segment.length === 0) continue;
|
|
42
|
+
if (segment.length <= size) {
|
|
43
|
+
pieces.push(segment);
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
pieces.push(...splitToPieces(segment, size, rest));
|
|
47
|
+
}
|
|
48
|
+
return pieces;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Split on `separator` but re-attach the separator to the end of each
|
|
52
|
+
* preceding segment, so concatenating the segments reconstructs the
|
|
53
|
+
* original text verbatim (keeping spans exact).
|
|
54
|
+
*/
|
|
55
|
+
function splitKeepingSeparator(text, separator) {
|
|
56
|
+
const raw = text.split(separator);
|
|
57
|
+
const segments = [];
|
|
58
|
+
raw.forEach((part, position) => {
|
|
59
|
+
const isLast = position === raw.length - 1;
|
|
60
|
+
segments.push(isLast ? part : part + separator);
|
|
61
|
+
});
|
|
62
|
+
return segments;
|
|
63
|
+
}
|
|
64
|
+
/** Hard char-window split for a unit larger than `size` with no usable separator. */
|
|
65
|
+
function hardSplit(text, size) {
|
|
66
|
+
const pieces = [];
|
|
67
|
+
for (let cursor = 0; cursor < text.length; cursor += size) pieces.push(text.slice(cursor, cursor + size));
|
|
68
|
+
return pieces;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Map each piece back to its absolute offset using a monotonic cursor —
|
|
72
|
+
* pieces are emitted in document order, so the next occurrence at-or-after
|
|
73
|
+
* the cursor is the correct one even for repeated substrings.
|
|
74
|
+
*/
|
|
75
|
+
function anchorPieces(text, pieces) {
|
|
76
|
+
const anchored = [];
|
|
77
|
+
let cursor = 0;
|
|
78
|
+
for (const piece of pieces) {
|
|
79
|
+
const start = text.indexOf(piece, cursor);
|
|
80
|
+
const resolvedStart = start === -1 ? cursor : start;
|
|
81
|
+
const end = resolvedStart + piece.length;
|
|
82
|
+
anchored.push({
|
|
83
|
+
text: piece,
|
|
84
|
+
start: resolvedStart,
|
|
85
|
+
end
|
|
86
|
+
});
|
|
87
|
+
cursor = end;
|
|
88
|
+
}
|
|
89
|
+
return anchored;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Greedily pack anchored pieces into chunks up to `size` characters, then
|
|
93
|
+
* carry `overlap` trailing characters from each emitted chunk into the
|
|
94
|
+
* next so context is not lost at a boundary. Spans are taken straight
|
|
95
|
+
* from the anchored pieces, so the overlap text is part of the next
|
|
96
|
+
* chunk's span exactly.
|
|
97
|
+
*/
|
|
98
|
+
function packPieces(text, pieces, size, overlap) {
|
|
99
|
+
const chunks = [];
|
|
100
|
+
let bufferStart = -1;
|
|
101
|
+
let bufferEnd = -1;
|
|
102
|
+
let index = 0;
|
|
103
|
+
const flush = () => {
|
|
104
|
+
if (bufferStart === -1) return;
|
|
105
|
+
chunks.push({
|
|
106
|
+
text: text.slice(bufferStart, bufferEnd),
|
|
107
|
+
index,
|
|
108
|
+
span: [bufferStart, bufferEnd]
|
|
109
|
+
});
|
|
110
|
+
index += 1;
|
|
111
|
+
};
|
|
112
|
+
for (const piece of pieces) {
|
|
113
|
+
if (bufferStart === -1) {
|
|
114
|
+
bufferStart = piece.start;
|
|
115
|
+
bufferEnd = piece.end;
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
if (piece.end - bufferStart <= size) {
|
|
119
|
+
bufferEnd = piece.end;
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
flush();
|
|
123
|
+
bufferStart = overlap > 0 ? Math.max(bufferStart, piece.start - overlap) : piece.start;
|
|
124
|
+
bufferEnd = piece.end;
|
|
125
|
+
}
|
|
126
|
+
flush();
|
|
127
|
+
return chunks;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
//#endregion
|
|
131
|
+
export { DEFAULT_SEPARATORS, recursiveChunk };
|
|
132
|
+
//# sourceMappingURL=recursive.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recursive.mjs","names":[],"sources":["../../../../../../../../@warlock.js/ai/src/rag/chunk/recursive.ts"],"sourcesContent":["import type { Chunk } from \"../contracts/chunk-options.type\";\n\n/** Default separators for the recursive splitter, tried largest-unit first. */\nexport const DEFAULT_SEPARATORS: string[] = [\"\\n\\n\", \"\\n\", \". \", \" \", \"\"];\n\n/**\n * Recursive character splitter (the default strategy).\n *\n * Walks `separators` largest-unit-first: it splits the text on the first\n * separator, then packs the resulting pieces into chunks up to `size`\n * characters, carrying `overlap` characters forward between adjacent\n * chunks. Any single piece that is itself larger than `size` is split\n * again on the next-finer separator, recursing until a piece fits (the\n * `\"\"` separator is the final char-by-char fallback).\n *\n * Every emitted chunk records its exact `[start, end)` character span in\n * the ORIGINAL text so a citation can point back precisely — spans are\n * tracked by index-of search as packed pieces are joined.\n *\n * Character-based and deliberately tokenizer-free.\n */\nexport function recursiveChunk(\n text: string,\n size: number,\n overlap: number,\n separators: string[] = DEFAULT_SEPARATORS,\n): Chunk[] {\n const pieces = splitToPieces(text, size, separators);\n\n // Re-anchor each packed piece to its absolute offset in `text`. Pieces\n // are non-overlapping and in document order, so a forward cursor finds\n // each one's true start even when the same substring repeats.\n const spans = anchorPieces(text, pieces);\n\n return packPieces(text, spans, size, overlap);\n}\n\n/**\n * Recursively split `text` into pieces no larger than `size` using the\n * ordered separator list. Pieces preserve original characters (no\n * trimming) so downstream span anchoring stays exact.\n */\nfunction splitToPieces(text: string, size: number, separators: string[]): string[] {\n if (text.length <= size) {\n return text.length > 0 ? [text] : [];\n }\n\n const [separator, ...rest] = separators;\n\n // Exhausted every separator (or hit the char fallback) — hard-split by\n // size so an oversize unit never blows the budget.\n if (separator === undefined || separator === \"\") {\n return hardSplit(text, size);\n }\n\n const segments = splitKeepingSeparator(text, separator);\n const pieces: string[] = [];\n\n for (const segment of segments) {\n if (segment.length === 0) {\n continue;\n }\n\n if (segment.length <= size) {\n pieces.push(segment);\n\n continue;\n }\n\n pieces.push(...splitToPieces(segment, size, rest));\n }\n\n return pieces;\n}\n\n/**\n * Split on `separator` but re-attach the separator to the end of each\n * preceding segment, so concatenating the segments reconstructs the\n * original text verbatim (keeping spans exact).\n */\nfunction splitKeepingSeparator(text: string, separator: string): string[] {\n const raw = text.split(separator);\n const segments: string[] = [];\n\n raw.forEach((part, position) => {\n const isLast = position === raw.length - 1;\n\n segments.push(isLast ? part : part + separator);\n });\n\n return segments;\n}\n\n/** Hard char-window split for a unit larger than `size` with no usable separator. */\nfunction hardSplit(text: string, size: number): string[] {\n const pieces: string[] = [];\n\n for (let cursor = 0; cursor < text.length; cursor += size) {\n pieces.push(text.slice(cursor, cursor + size));\n }\n\n return pieces;\n}\n\n/** A piece plus its absolute `[start, end)` span in the original text. */\ntype AnchoredPiece = {\n text: string;\n start: number;\n end: number;\n};\n\n/**\n * Map each piece back to its absolute offset using a monotonic cursor —\n * pieces are emitted in document order, so the next occurrence at-or-after\n * the cursor is the correct one even for repeated substrings.\n */\nfunction anchorPieces(text: string, pieces: string[]): AnchoredPiece[] {\n const anchored: AnchoredPiece[] = [];\n let cursor = 0;\n\n for (const piece of pieces) {\n const start = text.indexOf(piece, cursor);\n const resolvedStart = start === -1 ? cursor : start;\n const end = resolvedStart + piece.length;\n\n anchored.push({ text: piece, start: resolvedStart, end });\n cursor = end;\n }\n\n return anchored;\n}\n\n/**\n * Greedily pack anchored pieces into chunks up to `size` characters, then\n * carry `overlap` trailing characters from each emitted chunk into the\n * next so context is not lost at a boundary. Spans are taken straight\n * from the anchored pieces, so the overlap text is part of the next\n * chunk's span exactly.\n */\nfunction packPieces(\n text: string,\n pieces: AnchoredPiece[],\n size: number,\n overlap: number,\n): Chunk[] {\n const chunks: Chunk[] = [];\n\n let bufferStart = -1;\n let bufferEnd = -1;\n let index = 0;\n\n const flush = (): void => {\n if (bufferStart === -1) {\n return;\n }\n\n chunks.push({\n text: text.slice(bufferStart, bufferEnd),\n index,\n span: [bufferStart, bufferEnd],\n });\n index += 1;\n };\n\n for (const piece of pieces) {\n if (bufferStart === -1) {\n bufferStart = piece.start;\n bufferEnd = piece.end;\n\n continue;\n }\n\n const projected = piece.end - bufferStart;\n\n if (projected <= size) {\n bufferEnd = piece.end;\n\n continue;\n }\n\n flush();\n\n // Start the next buffer `overlap` chars before this piece (clamped to\n // the previous chunk's start) so adjacent chunks share context.\n const overlapStart = overlap > 0 ? Math.max(bufferStart, piece.start - overlap) : piece.start;\n\n bufferStart = overlapStart;\n bufferEnd = piece.end;\n }\n\n flush();\n\n return chunks;\n}\n"],"mappings":";;AAGA,MAAa,qBAA+B;CAAC;CAAQ;CAAM;CAAM;CAAK;AAAE;;;;;;;;;;;;;;;;;AAkBxE,SAAgB,eACd,MACA,MACA,SACA,aAAuB,oBACd;CAQT,OAAO,WAAW,MAFJ,aAAa,MALZ,cAAc,MAAM,MAAM,UAKH,CAEV,GAAG,MAAM,OAAO;AAC9C;;;;;;AAOA,SAAS,cAAc,MAAc,MAAc,YAAgC;CACjF,IAAI,KAAK,UAAU,MACjB,OAAO,KAAK,SAAS,IAAI,CAAC,IAAI,IAAI,CAAC;CAGrC,MAAM,CAAC,WAAW,GAAG,QAAQ;CAI7B,IAAI,cAAc,UAAa,cAAc,IAC3C,OAAO,UAAU,MAAM,IAAI;CAG7B,MAAM,WAAW,sBAAsB,MAAM,SAAS;CACtD,MAAM,SAAmB,CAAC;CAE1B,KAAK,MAAM,WAAW,UAAU;EAC9B,IAAI,QAAQ,WAAW,GACrB;EAGF,IAAI,QAAQ,UAAU,MAAM;GAC1B,OAAO,KAAK,OAAO;GAEnB;EACF;EAEA,OAAO,KAAK,GAAG,cAAc,SAAS,MAAM,IAAI,CAAC;CACnD;CAEA,OAAO;AACT;;;;;;AAOA,SAAS,sBAAsB,MAAc,WAA6B;CACxE,MAAM,MAAM,KAAK,MAAM,SAAS;CAChC,MAAM,WAAqB,CAAC;CAE5B,IAAI,SAAS,MAAM,aAAa;EAC9B,MAAM,SAAS,aAAa,IAAI,SAAS;EAEzC,SAAS,KAAK,SAAS,OAAO,OAAO,SAAS;CAChD,CAAC;CAED,OAAO;AACT;;AAGA,SAAS,UAAU,MAAc,MAAwB;CACvD,MAAM,SAAmB,CAAC;CAE1B,KAAK,IAAI,SAAS,GAAG,SAAS,KAAK,QAAQ,UAAU,MACnD,OAAO,KAAK,KAAK,MAAM,QAAQ,SAAS,IAAI,CAAC;CAG/C,OAAO;AACT;;;;;;AAcA,SAAS,aAAa,MAAc,QAAmC;CACrE,MAAM,WAA4B,CAAC;CACnC,IAAI,SAAS;CAEb,KAAK,MAAM,SAAS,QAAQ;EAC1B,MAAM,QAAQ,KAAK,QAAQ,OAAO,MAAM;EACxC,MAAM,gBAAgB,UAAU,KAAK,SAAS;EAC9C,MAAM,MAAM,gBAAgB,MAAM;EAElC,SAAS,KAAK;GAAE,MAAM;GAAO,OAAO;GAAe;EAAI,CAAC;EACxD,SAAS;CACX;CAEA,OAAO;AACT;;;;;;;;AASA,SAAS,WACP,MACA,QACA,MACA,SACS;CACT,MAAM,SAAkB,CAAC;CAEzB,IAAI,cAAc;CAClB,IAAI,YAAY;CAChB,IAAI,QAAQ;CAEZ,MAAM,cAAoB;EACxB,IAAI,gBAAgB,IAClB;EAGF,OAAO,KAAK;GACV,MAAM,KAAK,MAAM,aAAa,SAAS;GACvC;GACA,MAAM,CAAC,aAAa,SAAS;EAC/B,CAAC;EACD,SAAS;CACX;CAEA,KAAK,MAAM,SAAS,QAAQ;EAC1B,IAAI,gBAAgB,IAAI;GACtB,cAAc,MAAM;GACpB,YAAY,MAAM;GAElB;EACF;EAIA,IAFkB,MAAM,MAAM,eAEb,MAAM;GACrB,YAAY,MAAM;GAElB;EACF;EAEA,MAAM;EAMN,cAFqB,UAAU,IAAI,KAAK,IAAI,aAAa,MAAM,QAAQ,OAAO,IAAI,MAAM;EAGxF,YAAY,MAAM;CACpB;CAEA,MAAM;CAEN,OAAO;AACT"}
|