@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
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { InvalidRequestError } from "../errors/invalid-request-error.mjs";
|
|
2
|
+
import { OutboundPolicyError } from "../errors/outbound-policy-error.mjs";
|
|
2
3
|
import "../errors/index.mjs";
|
|
4
|
+
import { fetchTextWithPolicy } from "../security/outbound-policy.mjs";
|
|
3
5
|
import { resolveAttachment } from "./resolve-attachment.mjs";
|
|
4
6
|
import { readFile } from "node:fs/promises";
|
|
5
|
-
import { extname } from "node:path";
|
|
7
|
+
import { extname, isAbsolute, relative, resolve } from "node:path";
|
|
6
8
|
|
|
7
9
|
//#region ../@warlock.js/ai/src/utils/prepare-attachment-part.ts
|
|
8
10
|
const IMAGE_EXTENSIONS_TO_MEDIA_TYPE = {
|
|
@@ -13,6 +15,14 @@ const IMAGE_EXTENSIONS_TO_MEDIA_TYPE = {
|
|
|
13
15
|
".gif": "image/gif"
|
|
14
16
|
};
|
|
15
17
|
const TEXT_EXTENSIONS = new Set([".txt"]);
|
|
18
|
+
const AUDIO_EXTENSIONS_TO_MEDIA_TYPE = {
|
|
19
|
+
".mp3": "audio/mpeg",
|
|
20
|
+
".wav": "audio/wav",
|
|
21
|
+
".m4a": "audio/mp4",
|
|
22
|
+
".ogg": "audio/ogg",
|
|
23
|
+
".weba": "audio/webm"
|
|
24
|
+
};
|
|
25
|
+
const PDF_EXTENSIONS = new Set([".pdf"]);
|
|
16
26
|
/**
|
|
17
27
|
* Convert a user-supplied `Attachment` into a provider-ready
|
|
18
28
|
* `ContentPart` the model adapter can consume without doing any I/O of
|
|
@@ -40,10 +50,21 @@ const TEXT_EXTENSIONS = new Set([".txt"]);
|
|
|
40
50
|
* @example
|
|
41
51
|
* await prepareAttachmentPart({ type: "text", source: "./notes.txt" });
|
|
42
52
|
* // → { type: "text", text: "<file contents>" }
|
|
53
|
+
*
|
|
54
|
+
* **Trust boundary (S1).** Attachment references are often user-controlled,
|
|
55
|
+
* so server-side I/O is policy-gated by `policy` ({@link AttachmentPolicy}):
|
|
56
|
+
* remote text fetches are default-deny and, when enabled, run through the
|
|
57
|
+
* shared `OutboundPolicy` (scheme/host/private-IP/max-bytes/timeout); local
|
|
58
|
+
* reads honor an `allowedRoots` sandbox; bare-string local paths warn
|
|
59
|
+
* (staged deprecation). URL *image* attachments are passed to the provider
|
|
60
|
+
* untouched (never fetched here).
|
|
43
61
|
*/
|
|
44
|
-
async function prepareAttachmentPart(attachment) {
|
|
45
|
-
|
|
46
|
-
|
|
62
|
+
async function prepareAttachmentPart(attachment, policy) {
|
|
63
|
+
const kind = resolveKind(attachment);
|
|
64
|
+
const bareString = typeof attachment === "string";
|
|
65
|
+
if (kind === "text") return prepareTextPart(attachment, policy, bareString);
|
|
66
|
+
if (kind === "image") return prepareImagePart(attachment, policy, bareString);
|
|
67
|
+
return prepareBinaryPart(attachment, kind, policy, bareString);
|
|
47
68
|
}
|
|
48
69
|
/**
|
|
49
70
|
* Decide whether the attachment is text or image. Tagged forms win
|
|
@@ -56,14 +77,50 @@ function resolveKind(attachment) {
|
|
|
56
77
|
const extension = path ? extname(stripQuery(path)).toLowerCase() : "";
|
|
57
78
|
if (IMAGE_EXTENSIONS_TO_MEDIA_TYPE[extension]) return "image";
|
|
58
79
|
if (TEXT_EXTENSIONS.has(extension)) return "text";
|
|
59
|
-
|
|
80
|
+
if (PDF_EXTENSIONS.has(extension)) return "pdf";
|
|
81
|
+
if (AUDIO_EXTENSIONS_TO_MEDIA_TYPE[extension]) return "audio";
|
|
82
|
+
throw new InvalidRequestError("Cannot infer attachment type from input — pass an explicit `{ type: 'image' | 'text' | 'pdf' | 'audio', source: ... }` or use a recognized extension (.png, .jpg, .jpeg, .webp, .gif, .txt, .pdf, .mp3, .wav, .m4a, .ogg, .weba)");
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Produce a `pdf` / `audio` ContentPart (A2). URLs pass through; local
|
|
86
|
+
* paths are read and base64-encoded with a media type inferred from the
|
|
87
|
+
* kind (`application/pdf`) or extension (audio); inline base64 passes
|
|
88
|
+
* through. Same `AttachmentPolicy` gating as image/text reads.
|
|
89
|
+
*/
|
|
90
|
+
async function prepareBinaryPart(attachment, kind, policy, bareString) {
|
|
91
|
+
const resolved = resolveAttachment(attachment);
|
|
92
|
+
if (resolved.type === "url") return {
|
|
93
|
+
type: kind,
|
|
94
|
+
source: { url: resolved.value }
|
|
95
|
+
};
|
|
96
|
+
if (resolved.type === "base64") return {
|
|
97
|
+
type: kind,
|
|
98
|
+
source: {
|
|
99
|
+
base64: resolved.value,
|
|
100
|
+
mediaType: resolved.mediaType
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
const mediaType = kind === "pdf" ? "application/pdf" : inferAudioMediaType(resolved.value);
|
|
104
|
+
if (!mediaType) throw new InvalidRequestError(`Cannot infer media type for ${kind} path "${resolved.value}" — use a recognized extension or pass \`{ type: '${kind}', source: { base64, mediaType } }\``, { context: { path: resolved.value } });
|
|
105
|
+
enforceLocalPathPolicy(resolved.value, bareString, policy);
|
|
106
|
+
return {
|
|
107
|
+
type: kind,
|
|
108
|
+
source: {
|
|
109
|
+
base64: (await readFile(resolved.value)).toString("base64"),
|
|
110
|
+
mediaType
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
/** Infer an audio media type from a path's extension. */
|
|
115
|
+
function inferAudioMediaType(path) {
|
|
116
|
+
return AUDIO_EXTENSIONS_TO_MEDIA_TYPE[extname(stripQuery(path)).toLowerCase()];
|
|
60
117
|
}
|
|
61
118
|
/**
|
|
62
119
|
* Produce an `image` ContentPart. URLs pass through; paths are
|
|
63
120
|
* read from disk and base64-encoded with an inferred media type.
|
|
64
121
|
* Inline base64 attachments pass through unchanged.
|
|
65
122
|
*/
|
|
66
|
-
async function prepareImagePart(attachment) {
|
|
123
|
+
async function prepareImagePart(attachment, policy, bareString) {
|
|
67
124
|
const inferredMediaType = isTaggedAttachment(attachment) ? void 0 : inferImageMediaType(attachment);
|
|
68
125
|
const resolved = resolveAttachment(attachment);
|
|
69
126
|
if (resolved.type === "url") return {
|
|
@@ -79,6 +136,7 @@ async function prepareImagePart(attachment) {
|
|
|
79
136
|
};
|
|
80
137
|
const mediaType = inferredMediaType ?? inferImageMediaType(resolved.value);
|
|
81
138
|
if (!mediaType) throw new InvalidRequestError(`Cannot infer media type for path "${resolved.value}" — use a recognized image extension or pass \`{ type: 'image', source: { base64, mediaType } }\``, { context: { path: resolved.value } });
|
|
139
|
+
enforceLocalPathPolicy(resolved.value, bareString, policy);
|
|
82
140
|
return {
|
|
83
141
|
type: "image",
|
|
84
142
|
source: {
|
|
@@ -93,28 +151,62 @@ async function prepareImagePart(attachment) {
|
|
|
93
151
|
* result joins the conversation as an additional text part the model
|
|
94
152
|
* sees before responding.
|
|
95
153
|
*/
|
|
96
|
-
async function prepareTextPart(attachment) {
|
|
154
|
+
async function prepareTextPart(attachment, policy, bareString) {
|
|
97
155
|
const resolved = resolveAttachment(attachment);
|
|
98
156
|
if (resolved.type === "url") {
|
|
99
|
-
|
|
100
|
-
|
|
157
|
+
if (!policy?.allowRemoteFetch) throw new OutboundPolicyError(`remote text attachment fetch is disabled by default — set \`attachmentPolicy.allowRemoteFetch: true\` (with an \`outbound\` policy) to fetch "${resolved.value}"`, { context: { url: resolved.value } });
|
|
158
|
+
const result = await fetchTextWithPolicy(resolved.value, policy.outbound ?? {});
|
|
159
|
+
if (!result.ok) throw new InvalidRequestError(`Failed to fetch text attachment "${resolved.value}" — status ${result.status}`, { context: {
|
|
101
160
|
url: resolved.value,
|
|
102
|
-
status:
|
|
161
|
+
status: result.status
|
|
103
162
|
} });
|
|
104
163
|
return {
|
|
105
164
|
type: "text",
|
|
106
|
-
text:
|
|
165
|
+
text: result.text
|
|
107
166
|
};
|
|
108
167
|
}
|
|
109
168
|
if (resolved.type === "base64") return {
|
|
110
169
|
type: "text",
|
|
111
170
|
text: Buffer.from(resolved.value, "base64").toString("utf8")
|
|
112
171
|
};
|
|
172
|
+
enforceLocalPathPolicy(resolved.value, bareString, policy);
|
|
113
173
|
return {
|
|
114
174
|
type: "text",
|
|
115
175
|
text: await readFile(resolved.value, "utf8")
|
|
116
176
|
};
|
|
117
177
|
}
|
|
178
|
+
/** Process-lifetime flag so the bare-string deprecation warns at most once. */
|
|
179
|
+
let warnedBareLocalPath = false;
|
|
180
|
+
/**
|
|
181
|
+
* Enforce the local-file half of {@link AttachmentPolicy} (S1):
|
|
182
|
+
*
|
|
183
|
+
* - **Bare-string local paths** are staged for deprecation. With
|
|
184
|
+
* `allowBareLocalPaths: false` they hard-deny now; otherwise they warn
|
|
185
|
+
* once (outside tests) — the typed `StorageFile.absolutePath` route is
|
|
186
|
+
* the supported way to read a local file.
|
|
187
|
+
* - **`allowedRoots` sandbox** — when set, the resolved path must live
|
|
188
|
+
* inside one of the roots, else the read is refused.
|
|
189
|
+
*/
|
|
190
|
+
function enforceLocalPathPolicy(path, bareString, policy) {
|
|
191
|
+
if (bareString) {
|
|
192
|
+
if (policy?.allowBareLocalPaths === false) throw new OutboundPolicyError(`local file attachment via a bare string path ("${path}") is disabled — pass a typed \`{ type, source: { absolutePath } }\` StorageFile, or set \`attachmentPolicy.allowBareLocalPaths: true\``, { context: { path } });
|
|
193
|
+
if (!warnedBareLocalPath && !process.env.VITEST && process.env.NODE_ENV !== "test") {
|
|
194
|
+
warnedBareLocalPath = true;
|
|
195
|
+
console.warn("[warlock-ai] reading a local file attachment from a bare string path is deprecated and will be denied by default in a future minor. Pass a typed `{ type, source: { absolutePath } }` StorageFile and confine reads with `attachmentPolicy.allowedRoots`.");
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
const roots = policy?.allowedRoots;
|
|
199
|
+
if (roots && roots.length > 0) {
|
|
200
|
+
const target = resolve(path);
|
|
201
|
+
if (!roots.some((root) => {
|
|
202
|
+
const rel = relative(resolve(root), target);
|
|
203
|
+
return rel === "" || !rel.startsWith("..") && !isAbsolute(rel);
|
|
204
|
+
})) throw new OutboundPolicyError(`local file attachment "${path}" is outside the allowed roots`, { context: {
|
|
205
|
+
path,
|
|
206
|
+
allowedRoots: roots
|
|
207
|
+
} });
|
|
208
|
+
}
|
|
209
|
+
}
|
|
118
210
|
function isTaggedAttachment(attachment) {
|
|
119
211
|
return typeof attachment === "object" && attachment !== null && "type" in attachment;
|
|
120
212
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prepare-attachment-part.mjs","names":[],"sources":["../../../../../../../@warlock.js/ai/src/utils/prepare-attachment-part.ts"],"sourcesContent":["import { readFile } from \"node:fs/promises\";\nimport { extname } from \"node:path\";\nimport type { Attachment } from \"../contracts/attachment.type\";\nimport type { ContentPart } from \"../contracts/content-part.type\";\nimport { InvalidRequestError } from \"../errors\";\nimport { resolveAttachment } from \"./resolve-attachment\";\n\nconst IMAGE_EXTENSIONS_TO_MEDIA_TYPE: Record<string, string> = {\n \".png\": \"image/png\",\n \".jpg\": \"image/jpeg\",\n \".jpeg\": \"image/jpeg\",\n \".webp\": \"image/webp\",\n \".gif\": \"image/gif\",\n};\n\nconst TEXT_EXTENSIONS = new Set([\".txt\"]);\n\ntype AttachmentKind = \"image\" | \"text\";\n\n/**\n * Convert a user-supplied `Attachment` into a provider-ready\n * `ContentPart` the model adapter can consume without doing any I/O of\n * its own.\n *\n * Kind resolution:\n * - Tagged `{ type: \"image\", source }` / `{ type: \"text\", source }`\n * trusts the caller's intent.\n * - Shorthand (raw string / `StorageFileShape`) infers from the file\n * extension. Image extensions (`.png`/`.jpg`/`.jpeg`/`.webp`/`.gif`)\n * map to `\"image\"`. `.txt` maps to `\"text\"`. Anything else throws\n * `InvalidRequestError` — silent inference on ambiguous inputs\n * causes silent bugs.\n *\n * Local paths are read from disk; images are base64-encoded inline,\n * text files are read as UTF-8 strings and returned as a `text`\n * `ContentPart`. Remote URLs for image attachments are passed through\n * unchanged; remote URLs for text attachments are fetched so the\n * adapter never needs network access.\n *\n * @example\n * await prepareAttachmentPart(\"./photo.png\");\n * // → { type: \"image\", source: { base64: \"...\", mediaType: \"image/png\" } }\n *\n * @example\n * await prepareAttachmentPart({ type: \"text\", source: \"./notes.txt\" });\n * // → { type: \"text\", text: \"<file contents>\" }\n */\nexport async function prepareAttachmentPart(\n attachment: Attachment,\n): Promise<ContentPart> {\n const kind = resolveKind(attachment);\n\n if (kind === \"text\") {\n return prepareTextPart(attachment);\n }\n\n return prepareImagePart(attachment);\n}\n\n/**\n * Decide whether the attachment is text or image. Tagged forms win\n * immediately; for shorthand we inspect the extension. Throws if the\n * shorthand doesn't look like anything we recognize.\n */\nfunction resolveKind(attachment: Attachment): AttachmentKind {\n if (isTaggedAttachment(attachment)) {\n return attachment.type;\n }\n\n const path = extractPath(attachment);\n const extension = path ? extname(stripQuery(path)).toLowerCase() : \"\";\n\n if (IMAGE_EXTENSIONS_TO_MEDIA_TYPE[extension]) {\n return \"image\";\n }\n\n if (TEXT_EXTENSIONS.has(extension)) {\n return \"text\";\n }\n\n throw new InvalidRequestError(\n \"Cannot infer attachment type from input — pass an explicit `{ type: 'image' | 'text', source: ... }` or use a recognized extension (.png, .jpg, .jpeg, .webp, .gif, .txt)\",\n );\n}\n\n/**\n * Produce an `image` ContentPart. URLs pass through; paths are\n * read from disk and base64-encoded with an inferred media type.\n * Inline base64 attachments pass through unchanged.\n */\nasync function prepareImagePart(attachment: Attachment): Promise<ContentPart> {\n const inferredMediaType = isTaggedAttachment(attachment)\n ? undefined\n : inferImageMediaType(attachment);\n\n const resolved = resolveAttachment(attachment);\n\n if (resolved.type === \"url\") {\n return { type: \"image\", source: { url: resolved.value } };\n }\n\n if (resolved.type === \"base64\") {\n return {\n type: \"image\",\n source: { base64: resolved.value, mediaType: resolved.mediaType },\n };\n }\n\n const mediaType = inferredMediaType ?? inferImageMediaType(resolved.value);\n\n if (!mediaType) {\n throw new InvalidRequestError(\n `Cannot infer media type for path \"${resolved.value}\" — use a recognized image extension or pass ` +\n \"`{ type: 'image', source: { base64, mediaType } }`\",\n { context: { path: resolved.value } },\n );\n }\n\n const bytes = await readFile(resolved.value);\n\n return {\n type: \"image\",\n source: { base64: bytes.toString(\"base64\"), mediaType },\n };\n}\n\n/**\n * Produce a `text` ContentPart. URLs are fetched as UTF-8, paths are\n * read from disk as UTF-8, inline base64 is decoded to UTF-8. The\n * result joins the conversation as an additional text part the model\n * sees before responding.\n */\nasync function prepareTextPart(attachment: Attachment): Promise<ContentPart> {\n const resolved = resolveAttachment(attachment);\n\n if (resolved.type === \"url\") {\n const response = await fetch(resolved.value);\n\n if (!response.ok) {\n throw new InvalidRequestError(\n `Failed to fetch text attachment \"${resolved.value}\" — status ${response.status}`,\n { context: { url: resolved.value, status: response.status } },\n );\n }\n\n return { type: \"text\", text: await response.text() };\n }\n\n if (resolved.type === \"base64\") {\n const decoded = Buffer.from(resolved.value, \"base64\").toString(\"utf8\");\n\n return { type: \"text\", text: decoded };\n }\n\n const bytes = await readFile(resolved.value, \"utf8\");\n\n return { type: \"text\", text: bytes };\n}\n\nfunction isTaggedAttachment(\n attachment: Attachment,\n): attachment is Extract<Attachment, { type: string }> {\n return (\n typeof attachment === \"object\" &&\n attachment !== null &&\n \"type\" in attachment\n );\n}\n\nfunction inferImageMediaType(input: unknown): string | undefined {\n const path = extractPath(input);\n\n if (!path) {\n return undefined;\n }\n\n const extension = extname(stripQuery(path)).toLowerCase();\n\n return IMAGE_EXTENSIONS_TO_MEDIA_TYPE[extension];\n}\n\nfunction extractPath(input: unknown): string | undefined {\n if (typeof input === \"string\") {\n return input;\n }\n\n if (typeof input === \"object\" && input !== null) {\n const storage = input as { url?: string; absolutePath?: string };\n return storage.url ?? storage.absolutePath;\n }\n\n return undefined;\n}\n\nfunction stripQuery(path: string): string {\n const queryIndex = path.indexOf(\"?\");\n\n return queryIndex === -1 ? path : path.slice(0, queryIndex);\n}\n"],"mappings":";;;;;;;AAOA,MAAM,iCAAyD;CAC7D,QAAQ;CACR,QAAQ;CACR,SAAS;CACT,SAAS;CACT,QAAQ;AACV;AAEA,MAAM,kBAAkB,IAAI,IAAI,CAAC,MAAM,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgCxC,eAAsB,sBACpB,YACsB;CAGtB,IAFa,YAAY,UAElB,MAAM,QACX,OAAO,gBAAgB,UAAU;CAGnC,OAAO,iBAAiB,UAAU;AACpC;;;;;;AAOA,SAAS,YAAY,YAAwC;CAC3D,IAAI,mBAAmB,UAAU,GAC/B,OAAO,WAAW;CAGpB,MAAM,OAAO,YAAY,UAAU;CACnC,MAAM,YAAY,OAAO,QAAQ,WAAW,IAAI,CAAC,CAAC,CAAC,YAAY,IAAI;CAEnE,IAAI,+BAA+B,YACjC,OAAO;CAGT,IAAI,gBAAgB,IAAI,SAAS,GAC/B,OAAO;CAGT,MAAM,IAAI,oBACR,2KACF;AACF;;;;;;AAOA,eAAe,iBAAiB,YAA8C;CAC5E,MAAM,oBAAoB,mBAAmB,UAAU,IACnD,SACA,oBAAoB,UAAU;CAElC,MAAM,WAAW,kBAAkB,UAAU;CAE7C,IAAI,SAAS,SAAS,OACpB,OAAO;EAAE,MAAM;EAAS,QAAQ,EAAE,KAAK,SAAS,MAAM;CAAE;CAG1D,IAAI,SAAS,SAAS,UACpB,OAAO;EACL,MAAM;EACN,QAAQ;GAAE,QAAQ,SAAS;GAAO,WAAW,SAAS;EAAU;CAClE;CAGF,MAAM,YAAY,qBAAqB,oBAAoB,SAAS,KAAK;CAEzE,IAAI,CAAC,WACH,MAAM,IAAI,oBACR,qCAAqC,SAAS,MAAM,oGAEpD,EAAE,SAAS,EAAE,MAAM,SAAS,MAAM,EAAE,CACtC;CAKF,OAAO;EACL,MAAM;EACN,QAAQ;GAAE,SAAQ,MAJA,SAAS,SAAS,KAAK,EAIlB,CAAC,SAAS,QAAQ;GAAG;EAAU;CACxD;AACF;;;;;;;AAQA,eAAe,gBAAgB,YAA8C;CAC3E,MAAM,WAAW,kBAAkB,UAAU;CAE7C,IAAI,SAAS,SAAS,OAAO;EAC3B,MAAM,WAAW,MAAM,MAAM,SAAS,KAAK;EAE3C,IAAI,CAAC,SAAS,IACZ,MAAM,IAAI,oBACR,oCAAoC,SAAS,MAAM,aAAa,SAAS,UACzE,EAAE,SAAS;GAAE,KAAK,SAAS;GAAO,QAAQ,SAAS;EAAO,EAAE,CAC9D;EAGF,OAAO;GAAE,MAAM;GAAQ,MAAM,MAAM,SAAS,KAAK;EAAE;CACrD;CAEA,IAAI,SAAS,SAAS,UAGpB,OAAO;EAAE,MAAM;EAAQ,MAFP,OAAO,KAAK,SAAS,OAAO,QAAQ,CAAC,CAAC,SAAS,MAE5B;CAAE;CAKvC,OAAO;EAAE,MAAM;EAAQ,MAAM,MAFT,SAAS,SAAS,OAAO,MAAM;CAEhB;AACrC;AAEA,SAAS,mBACP,YACqD;CACrD,OACE,OAAO,eAAe,YACtB,eAAe,QACf,UAAU;AAEd;AAEA,SAAS,oBAAoB,OAAoC;CAC/D,MAAM,OAAO,YAAY,KAAK;CAE9B,IAAI,CAAC,MACH;CAKF,OAAO,+BAFW,QAAQ,WAAW,IAAI,CAAC,CAAC,CAAC,YAEE;AAChD;AAEA,SAAS,YAAY,OAAoC;CACvD,IAAI,OAAO,UAAU,UACnB,OAAO;CAGT,IAAI,OAAO,UAAU,YAAY,UAAU,MAAM;EAC/C,MAAM,UAAU;EAChB,OAAO,QAAQ,OAAO,QAAQ;CAChC;AAGF;AAEA,SAAS,WAAW,MAAsB;CACxC,MAAM,aAAa,KAAK,QAAQ,GAAG;CAEnC,OAAO,eAAe,KAAK,OAAO,KAAK,MAAM,GAAG,UAAU;AAC5D"}
|
|
1
|
+
{"version":3,"file":"prepare-attachment-part.mjs","names":["resolvePath"],"sources":["../../../../../../../@warlock.js/ai/src/utils/prepare-attachment-part.ts"],"sourcesContent":["import { readFile } from \"node:fs/promises\";\nimport { extname, isAbsolute, relative, resolve as resolvePath } from \"node:path\";\nimport type { AttachmentPolicy } from \"../contracts/attachment-policy.type\";\nimport type { Attachment } from \"../contracts/attachment.type\";\nimport type { ContentPart } from \"../contracts/content-part.type\";\nimport { InvalidRequestError, OutboundPolicyError } from \"../errors\";\nimport { fetchTextWithPolicy } from \"../security/outbound-policy\";\nimport { resolveAttachment } from \"./resolve-attachment\";\n\nconst IMAGE_EXTENSIONS_TO_MEDIA_TYPE: Record<string, string> = {\n \".png\": \"image/png\",\n \".jpg\": \"image/jpeg\",\n \".jpeg\": \"image/jpeg\",\n \".webp\": \"image/webp\",\n \".gif\": \"image/gif\",\n};\n\nconst TEXT_EXTENSIONS = new Set([\".txt\"]);\n\nconst AUDIO_EXTENSIONS_TO_MEDIA_TYPE: Record<string, string> = {\n \".mp3\": \"audio/mpeg\",\n \".wav\": \"audio/wav\",\n \".m4a\": \"audio/mp4\",\n \".ogg\": \"audio/ogg\",\n \".weba\": \"audio/webm\",\n};\n\nconst PDF_EXTENSIONS = new Set([\".pdf\"]);\n\ntype AttachmentKind = \"image\" | \"text\" | \"pdf\" | \"audio\";\n\n/**\n * Convert a user-supplied `Attachment` into a provider-ready\n * `ContentPart` the model adapter can consume without doing any I/O of\n * its own.\n *\n * Kind resolution:\n * - Tagged `{ type: \"image\", source }` / `{ type: \"text\", source }`\n * trusts the caller's intent.\n * - Shorthand (raw string / `StorageFileShape`) infers from the file\n * extension. Image extensions (`.png`/`.jpg`/`.jpeg`/`.webp`/`.gif`)\n * map to `\"image\"`. `.txt` maps to `\"text\"`. Anything else throws\n * `InvalidRequestError` — silent inference on ambiguous inputs\n * causes silent bugs.\n *\n * Local paths are read from disk; images are base64-encoded inline,\n * text files are read as UTF-8 strings and returned as a `text`\n * `ContentPart`. Remote URLs for image attachments are passed through\n * unchanged; remote URLs for text attachments are fetched so the\n * adapter never needs network access.\n *\n * @example\n * await prepareAttachmentPart(\"./photo.png\");\n * // → { type: \"image\", source: { base64: \"...\", mediaType: \"image/png\" } }\n *\n * @example\n * await prepareAttachmentPart({ type: \"text\", source: \"./notes.txt\" });\n * // → { type: \"text\", text: \"<file contents>\" }\n *\n * **Trust boundary (S1).** Attachment references are often user-controlled,\n * so server-side I/O is policy-gated by `policy` ({@link AttachmentPolicy}):\n * remote text fetches are default-deny and, when enabled, run through the\n * shared `OutboundPolicy` (scheme/host/private-IP/max-bytes/timeout); local\n * reads honor an `allowedRoots` sandbox; bare-string local paths warn\n * (staged deprecation). URL *image* attachments are passed to the provider\n * untouched (never fetched here).\n */\nexport async function prepareAttachmentPart(\n attachment: Attachment,\n policy?: AttachmentPolicy,\n): Promise<ContentPart> {\n const kind = resolveKind(attachment);\n const bareString = typeof attachment === \"string\";\n\n if (kind === \"text\") {\n return prepareTextPart(attachment, policy, bareString);\n }\n\n if (kind === \"image\") {\n return prepareImagePart(attachment, policy, bareString);\n }\n\n return prepareBinaryPart(attachment, kind, policy, bareString);\n}\n\n/**\n * Decide whether the attachment is text or image. Tagged forms win\n * immediately; for shorthand we inspect the extension. Throws if the\n * shorthand doesn't look like anything we recognize.\n */\nfunction resolveKind(attachment: Attachment): AttachmentKind {\n if (isTaggedAttachment(attachment)) {\n return attachment.type;\n }\n\n const path = extractPath(attachment);\n const extension = path ? extname(stripQuery(path)).toLowerCase() : \"\";\n\n if (IMAGE_EXTENSIONS_TO_MEDIA_TYPE[extension]) {\n return \"image\";\n }\n\n if (TEXT_EXTENSIONS.has(extension)) {\n return \"text\";\n }\n\n if (PDF_EXTENSIONS.has(extension)) {\n return \"pdf\";\n }\n\n if (AUDIO_EXTENSIONS_TO_MEDIA_TYPE[extension]) {\n return \"audio\";\n }\n\n throw new InvalidRequestError(\n \"Cannot infer attachment type from input — pass an explicit `{ type: 'image' | 'text' | 'pdf' | 'audio', source: ... }` or use a recognized extension (.png, .jpg, .jpeg, .webp, .gif, .txt, .pdf, .mp3, .wav, .m4a, .ogg, .weba)\",\n );\n}\n\n/**\n * Produce a `pdf` / `audio` ContentPart (A2). URLs pass through; local\n * paths are read and base64-encoded with a media type inferred from the\n * kind (`application/pdf`) or extension (audio); inline base64 passes\n * through. Same `AttachmentPolicy` gating as image/text reads.\n */\nasync function prepareBinaryPart(\n attachment: Attachment,\n kind: \"pdf\" | \"audio\",\n policy: AttachmentPolicy | undefined,\n bareString: boolean,\n): Promise<ContentPart> {\n const resolved = resolveAttachment(attachment);\n\n if (resolved.type === \"url\") {\n return { type: kind, source: { url: resolved.value } };\n }\n\n if (resolved.type === \"base64\") {\n return { type: kind, source: { base64: resolved.value, mediaType: resolved.mediaType } };\n }\n\n const mediaType =\n kind === \"pdf\" ? \"application/pdf\" : inferAudioMediaType(resolved.value);\n\n if (!mediaType) {\n throw new InvalidRequestError(\n `Cannot infer media type for ${kind} path \"${resolved.value}\" — use a recognized extension or pass ` +\n `\\`{ type: '${kind}', source: { base64, mediaType } }\\``,\n { context: { path: resolved.value } },\n );\n }\n\n enforceLocalPathPolicy(resolved.value, bareString, policy);\n const bytes = await readFile(resolved.value);\n\n return { type: kind, source: { base64: bytes.toString(\"base64\"), mediaType } };\n}\n\n/** Infer an audio media type from a path's extension. */\nfunction inferAudioMediaType(path: string): string | undefined {\n return AUDIO_EXTENSIONS_TO_MEDIA_TYPE[extname(stripQuery(path)).toLowerCase()];\n}\n\n/**\n * Produce an `image` ContentPart. URLs pass through; paths are\n * read from disk and base64-encoded with an inferred media type.\n * Inline base64 attachments pass through unchanged.\n */\nasync function prepareImagePart(\n attachment: Attachment,\n policy: AttachmentPolicy | undefined,\n bareString: boolean,\n): Promise<ContentPart> {\n const inferredMediaType = isTaggedAttachment(attachment)\n ? undefined\n : inferImageMediaType(attachment);\n\n const resolved = resolveAttachment(attachment);\n\n if (resolved.type === \"url\") {\n // URL images are handed to the provider as a URL — the provider\n // fetches them, not us — so there's no server-side SSRF surface here.\n return { type: \"image\", source: { url: resolved.value } };\n }\n\n if (resolved.type === \"base64\") {\n return {\n type: \"image\",\n source: { base64: resolved.value, mediaType: resolved.mediaType },\n };\n }\n\n const mediaType = inferredMediaType ?? inferImageMediaType(resolved.value);\n\n if (!mediaType) {\n throw new InvalidRequestError(\n `Cannot infer media type for path \"${resolved.value}\" — use a recognized image extension or pass ` +\n \"`{ type: 'image', source: { base64, mediaType } }`\",\n { context: { path: resolved.value } },\n );\n }\n\n enforceLocalPathPolicy(resolved.value, bareString, policy);\n const bytes = await readFile(resolved.value);\n\n return {\n type: \"image\",\n source: { base64: bytes.toString(\"base64\"), mediaType },\n };\n}\n\n/**\n * Produce a `text` ContentPart. URLs are fetched as UTF-8, paths are\n * read from disk as UTF-8, inline base64 is decoded to UTF-8. The\n * result joins the conversation as an additional text part the model\n * sees before responding.\n */\nasync function prepareTextPart(\n attachment: Attachment,\n policy: AttachmentPolicy | undefined,\n bareString: boolean,\n): Promise<ContentPart> {\n const resolved = resolveAttachment(attachment);\n\n if (resolved.type === \"url\") {\n // Default-deny: a remote text attachment is a server-side fetch of\n // user-controlled input — refuse unless the app explicitly opted in,\n // then run it through the shared OutboundPolicy (scheme/host/private-\n // IP/max-bytes/timeout).\n if (!policy?.allowRemoteFetch) {\n throw new OutboundPolicyError(\n `remote text attachment fetch is disabled by default — set \\`attachmentPolicy.allowRemoteFetch: true\\` (with an \\`outbound\\` policy) to fetch \"${resolved.value}\"`,\n { context: { url: resolved.value } },\n );\n }\n\n const result = await fetchTextWithPolicy(resolved.value, policy.outbound ?? {});\n\n if (!result.ok) {\n throw new InvalidRequestError(\n `Failed to fetch text attachment \"${resolved.value}\" — status ${result.status}`,\n { context: { url: resolved.value, status: result.status } },\n );\n }\n\n return { type: \"text\", text: result.text };\n }\n\n if (resolved.type === \"base64\") {\n const decoded = Buffer.from(resolved.value, \"base64\").toString(\"utf8\");\n\n return { type: \"text\", text: decoded };\n }\n\n enforceLocalPathPolicy(resolved.value, bareString, policy);\n const bytes = await readFile(resolved.value, \"utf8\");\n\n return { type: \"text\", text: bytes };\n}\n\n/** Process-lifetime flag so the bare-string deprecation warns at most once. */\nlet warnedBareLocalPath = false;\n\n/**\n * Enforce the local-file half of {@link AttachmentPolicy} (S1):\n *\n * - **Bare-string local paths** are staged for deprecation. With\n * `allowBareLocalPaths: false` they hard-deny now; otherwise they warn\n * once (outside tests) — the typed `StorageFile.absolutePath` route is\n * the supported way to read a local file.\n * - **`allowedRoots` sandbox** — when set, the resolved path must live\n * inside one of the roots, else the read is refused.\n */\nfunction enforceLocalPathPolicy(\n path: string,\n bareString: boolean,\n policy: AttachmentPolicy | undefined,\n): void {\n if (bareString) {\n if (policy?.allowBareLocalPaths === false) {\n throw new OutboundPolicyError(\n `local file attachment via a bare string path (\"${path}\") is disabled — pass a typed \\`{ type, source: { absolutePath } }\\` StorageFile, or set \\`attachmentPolicy.allowBareLocalPaths: true\\``,\n { context: { path } },\n );\n }\n\n if (!warnedBareLocalPath && !process.env.VITEST && process.env.NODE_ENV !== \"test\") {\n warnedBareLocalPath = true;\n console.warn(\n \"[warlock-ai] reading a local file attachment from a bare string path is deprecated and will be denied by default in a future minor. \" +\n \"Pass a typed `{ type, source: { absolutePath } }` StorageFile and confine reads with `attachmentPolicy.allowedRoots`.\",\n );\n }\n }\n\n const roots = policy?.allowedRoots;\n if (roots && roots.length > 0) {\n const target = resolvePath(path);\n const inside = roots.some(root => {\n const rel = relative(resolvePath(root), target);\n return rel === \"\" || (!rel.startsWith(\"..\") && !isAbsolute(rel));\n });\n\n if (!inside) {\n throw new OutboundPolicyError(\n `local file attachment \"${path}\" is outside the allowed roots`,\n { context: { path, allowedRoots: roots } },\n );\n }\n }\n}\n\nfunction isTaggedAttachment(\n attachment: Attachment,\n): attachment is Extract<Attachment, { type: string }> {\n return (\n typeof attachment === \"object\" &&\n attachment !== null &&\n \"type\" in attachment\n );\n}\n\nfunction inferImageMediaType(input: unknown): string | undefined {\n const path = extractPath(input);\n\n if (!path) {\n return undefined;\n }\n\n const extension = extname(stripQuery(path)).toLowerCase();\n\n return IMAGE_EXTENSIONS_TO_MEDIA_TYPE[extension];\n}\n\nfunction extractPath(input: unknown): string | undefined {\n if (typeof input === \"string\") {\n return input;\n }\n\n if (typeof input === \"object\" && input !== null) {\n const storage = input as { url?: string; absolutePath?: string };\n return storage.url ?? storage.absolutePath;\n }\n\n return undefined;\n}\n\nfunction stripQuery(path: string): string {\n const queryIndex = path.indexOf(\"?\");\n\n return queryIndex === -1 ? path : path.slice(0, queryIndex);\n}\n"],"mappings":";;;;;;;;;AASA,MAAM,iCAAyD;CAC7D,QAAQ;CACR,QAAQ;CACR,SAAS;CACT,SAAS;CACT,QAAQ;AACV;AAEA,MAAM,kBAAkB,IAAI,IAAI,CAAC,MAAM,CAAC;AAExC,MAAM,iCAAyD;CAC7D,QAAQ;CACR,QAAQ;CACR,QAAQ;CACR,QAAQ;CACR,SAAS;AACX;AAEA,MAAM,iBAAiB,IAAI,IAAI,CAAC,MAAM,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAwCvC,eAAsB,sBACpB,YACA,QACsB;CACtB,MAAM,OAAO,YAAY,UAAU;CACnC,MAAM,aAAa,OAAO,eAAe;CAEzC,IAAI,SAAS,QACX,OAAO,gBAAgB,YAAY,QAAQ,UAAU;CAGvD,IAAI,SAAS,SACX,OAAO,iBAAiB,YAAY,QAAQ,UAAU;CAGxD,OAAO,kBAAkB,YAAY,MAAM,QAAQ,UAAU;AAC/D;;;;;;AAOA,SAAS,YAAY,YAAwC;CAC3D,IAAI,mBAAmB,UAAU,GAC/B,OAAO,WAAW;CAGpB,MAAM,OAAO,YAAY,UAAU;CACnC,MAAM,YAAY,OAAO,QAAQ,WAAW,IAAI,CAAC,CAAC,CAAC,YAAY,IAAI;CAEnE,IAAI,+BAA+B,YACjC,OAAO;CAGT,IAAI,gBAAgB,IAAI,SAAS,GAC/B,OAAO;CAGT,IAAI,eAAe,IAAI,SAAS,GAC9B,OAAO;CAGT,IAAI,+BAA+B,YACjC,OAAO;CAGT,MAAM,IAAI,oBACR,kOACF;AACF;;;;;;;AAQA,eAAe,kBACb,YACA,MACA,QACA,YACsB;CACtB,MAAM,WAAW,kBAAkB,UAAU;CAE7C,IAAI,SAAS,SAAS,OACpB,OAAO;EAAE,MAAM;EAAM,QAAQ,EAAE,KAAK,SAAS,MAAM;CAAE;CAGvD,IAAI,SAAS,SAAS,UACpB,OAAO;EAAE,MAAM;EAAM,QAAQ;GAAE,QAAQ,SAAS;GAAO,WAAW,SAAS;EAAU;CAAE;CAGzF,MAAM,YACJ,SAAS,QAAQ,oBAAoB,oBAAoB,SAAS,KAAK;CAEzE,IAAI,CAAC,WACH,MAAM,IAAI,oBACR,+BAA+B,KAAK,SAAS,SAAS,MAAM,oDAC5C,KAAK,uCACrB,EAAE,SAAS,EAAE,MAAM,SAAS,MAAM,EAAE,CACtC;CAGF,uBAAuB,SAAS,OAAO,YAAY,MAAM;CAGzD,OAAO;EAAE,MAAM;EAAM,QAAQ;GAAE,SAAQ,MAFnB,SAAS,SAAS,KAAK,EAEC,CAAC,SAAS,QAAQ;GAAG;EAAU;CAAE;AAC/E;;AAGA,SAAS,oBAAoB,MAAkC;CAC7D,OAAO,+BAA+B,QAAQ,WAAW,IAAI,CAAC,CAAC,CAAC,YAAY;AAC9E;;;;;;AAOA,eAAe,iBACb,YACA,QACA,YACsB;CACtB,MAAM,oBAAoB,mBAAmB,UAAU,IACnD,SACA,oBAAoB,UAAU;CAElC,MAAM,WAAW,kBAAkB,UAAU;CAE7C,IAAI,SAAS,SAAS,OAGpB,OAAO;EAAE,MAAM;EAAS,QAAQ,EAAE,KAAK,SAAS,MAAM;CAAE;CAG1D,IAAI,SAAS,SAAS,UACpB,OAAO;EACL,MAAM;EACN,QAAQ;GAAE,QAAQ,SAAS;GAAO,WAAW,SAAS;EAAU;CAClE;CAGF,MAAM,YAAY,qBAAqB,oBAAoB,SAAS,KAAK;CAEzE,IAAI,CAAC,WACH,MAAM,IAAI,oBACR,qCAAqC,SAAS,MAAM,oGAEpD,EAAE,SAAS,EAAE,MAAM,SAAS,MAAM,EAAE,CACtC;CAGF,uBAAuB,SAAS,OAAO,YAAY,MAAM;CAGzD,OAAO;EACL,MAAM;EACN,QAAQ;GAAE,SAAQ,MAJA,SAAS,SAAS,KAAK,EAIlB,CAAC,SAAS,QAAQ;GAAG;EAAU;CACxD;AACF;;;;;;;AAQA,eAAe,gBACb,YACA,QACA,YACsB;CACtB,MAAM,WAAW,kBAAkB,UAAU;CAE7C,IAAI,SAAS,SAAS,OAAO;EAK3B,IAAI,CAAC,QAAQ,kBACX,MAAM,IAAI,oBACR,iJAAiJ,SAAS,MAAM,IAChK,EAAE,SAAS,EAAE,KAAK,SAAS,MAAM,EAAE,CACrC;EAGF,MAAM,SAAS,MAAM,oBAAoB,SAAS,OAAO,OAAO,YAAY,CAAC,CAAC;EAE9E,IAAI,CAAC,OAAO,IACV,MAAM,IAAI,oBACR,oCAAoC,SAAS,MAAM,aAAa,OAAO,UACvE,EAAE,SAAS;GAAE,KAAK,SAAS;GAAO,QAAQ,OAAO;EAAO,EAAE,CAC5D;EAGF,OAAO;GAAE,MAAM;GAAQ,MAAM,OAAO;EAAK;CAC3C;CAEA,IAAI,SAAS,SAAS,UAGpB,OAAO;EAAE,MAAM;EAAQ,MAFP,OAAO,KAAK,SAAS,OAAO,QAAQ,CAAC,CAAC,SAAS,MAE5B;CAAE;CAGvC,uBAAuB,SAAS,OAAO,YAAY,MAAM;CAGzD,OAAO;EAAE,MAAM;EAAQ,MAAM,MAFT,SAAS,SAAS,OAAO,MAAM;CAEhB;AACrC;;AAGA,IAAI,sBAAsB;;;;;;;;;;;AAY1B,SAAS,uBACP,MACA,YACA,QACM;CACN,IAAI,YAAY;EACd,IAAI,QAAQ,wBAAwB,OAClC,MAAM,IAAI,oBACR,kDAAkD,KAAK,0IACvD,EAAE,SAAS,EAAE,KAAK,EAAE,CACtB;EAGF,IAAI,CAAC,uBAAuB,CAAC,QAAQ,IAAI,UAAU,QAAQ,IAAI,aAAa,QAAQ;GAClF,sBAAsB;GACtB,QAAQ,KACN,2PAEF;EACF;CACF;CAEA,MAAM,QAAQ,QAAQ;CACtB,IAAI,SAAS,MAAM,SAAS,GAAG;EAC7B,MAAM,SAASA,QAAY,IAAI;EAM/B,IAAI,CALW,MAAM,MAAK,SAAQ;GAChC,MAAM,MAAM,SAASA,QAAY,IAAI,GAAG,MAAM;GAC9C,OAAO,QAAQ,MAAO,CAAC,IAAI,WAAW,IAAI,KAAK,CAAC,WAAW,GAAG;EAChE,CAEU,GACR,MAAM,IAAI,oBACR,0BAA0B,KAAK,iCAC/B,EAAE,SAAS;GAAE;GAAM,cAAc;EAAM,EAAE,CAC3C;CAEJ;AACF;AAEA,SAAS,mBACP,YACqD;CACrD,OACE,OAAO,eAAe,YACtB,eAAe,QACf,UAAU;AAEd;AAEA,SAAS,oBAAoB,OAAoC;CAC/D,MAAM,OAAO,YAAY,KAAK;CAE9B,IAAI,CAAC,MACH;CAKF,OAAO,+BAFW,QAAQ,WAAW,IAAI,CAAC,CAAC,CAAC,YAEE;AAChD;AAEA,SAAS,YAAY,OAAoC;CACvD,IAAI,OAAO,UAAU,UACnB,OAAO;CAGT,IAAI,OAAO,UAAU,YAAY,UAAU,MAAM;EAC/C,MAAM,UAAU;EAChB,OAAO,QAAQ,OAAO,QAAQ;CAChC;AAGF;AAEA,SAAS,WAAW,MAAsB;CACxC,MAAM,aAAa,KAAK,QAAQ,GAAG;CAEnC,OAAO,eAAe,KAAK,OAAO,KAAK,MAAM,GAAG,UAAU;AAC5D"}
|
|
@@ -8,9 +8,10 @@ import { Attachment, ResolvedAttachment } from "../contracts/attachment.type.mjs
|
|
|
8
8
|
*
|
|
9
9
|
* Resolution rules:
|
|
10
10
|
* - `{ base64, mediaType }` → `{ type: "base64", value, mediaType }`.
|
|
11
|
-
* - `StorageFileShape` (`{ url?, absolutePath? }`) →
|
|
12
|
-
*
|
|
13
|
-
* becomes `{ type: "path" }
|
|
11
|
+
* - `StorageFileShape` (`{ url?, absolutePath? }`) → `absolutePath` wins
|
|
12
|
+
* over `url` when both are present (prefer the local file over an extra
|
|
13
|
+
* remote hop). Absolute path becomes `{ type: "path" }`; url becomes
|
|
14
|
+
* `{ type: "url" }`.
|
|
14
15
|
* - String starting with `http://` / `https://` → `{ type: "url" }`.
|
|
15
16
|
* - Any other string → `{ type: "path" }`.
|
|
16
17
|
* - Tagged `{ type: "image" | "text", source }` → recurses into `source`.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resolve-attachment.d.mts","names":[],"sources":["../../../../../../../@warlock.js/ai/src/utils/resolve-attachment.ts"],"mappings":";;;;;
|
|
1
|
+
{"version":3,"file":"resolve-attachment.d.mts","names":[],"sources":["../../../../../../../@warlock.js/ai/src/utils/resolve-attachment.ts"],"mappings":";;;;;AAoCA;;;;;;;;AAA6E;;;;;;;;;;;;;;;;;iBAA7D,iBAAA,CAAkB,UAAA,EAAY,UAAA,GAAa,kBAAkB"}
|
|
@@ -10,9 +10,10 @@ const REMOTE_URL_PATTERN = /^https?:\/\//i;
|
|
|
10
10
|
*
|
|
11
11
|
* Resolution rules:
|
|
12
12
|
* - `{ base64, mediaType }` → `{ type: "base64", value, mediaType }`.
|
|
13
|
-
* - `StorageFileShape` (`{ url?, absolutePath? }`) →
|
|
14
|
-
*
|
|
15
|
-
* becomes `{ type: "path" }
|
|
13
|
+
* - `StorageFileShape` (`{ url?, absolutePath? }`) → `absolutePath` wins
|
|
14
|
+
* over `url` when both are present (prefer the local file over an extra
|
|
15
|
+
* remote hop). Absolute path becomes `{ type: "path" }`; url becomes
|
|
16
|
+
* `{ type: "url" }`.
|
|
16
17
|
* - String starting with `http://` / `https://` → `{ type: "url" }`.
|
|
17
18
|
* - Any other string → `{ type: "path" }`.
|
|
18
19
|
* - Tagged `{ type: "image" | "text", source }` → recurses into `source`.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resolve-attachment.mjs","names":[],"sources":["../../../../../../../@warlock.js/ai/src/utils/resolve-attachment.ts"],"sourcesContent":["import type {\n Attachment,\n AttachmentSource,\n ResolvedAttachment,\n} from \"../contracts/attachment.type\";\nimport { InvalidRequestError } from \"../errors\";\n\nconst REMOTE_URL_PATTERN = /^https?:\\/\\//i;\n\n/**\n * Normalize a user-supplied `Attachment` (or bare `AttachmentSource`)\n * into a `ResolvedAttachment` the agent can hand to file-reading code\n * without re-discriminating the input variant.\n *\n * Resolution rules:\n * - `{ base64, mediaType }` → `{ type: \"base64\", value, mediaType }`.\n * - `StorageFileShape` (`{ url?, absolutePath? }`) →
|
|
1
|
+
{"version":3,"file":"resolve-attachment.mjs","names":[],"sources":["../../../../../../../@warlock.js/ai/src/utils/resolve-attachment.ts"],"sourcesContent":["import type {\n Attachment,\n AttachmentSource,\n ResolvedAttachment,\n} from \"../contracts/attachment.type\";\nimport { InvalidRequestError } from \"../errors\";\n\nconst REMOTE_URL_PATTERN = /^https?:\\/\\//i;\n\n/**\n * Normalize a user-supplied `Attachment` (or bare `AttachmentSource`)\n * into a `ResolvedAttachment` the agent can hand to file-reading code\n * without re-discriminating the input variant.\n *\n * Resolution rules:\n * - `{ base64, mediaType }` → `{ type: \"base64\", value, mediaType }`.\n * - `StorageFileShape` (`{ url?, absolutePath? }`) → `absolutePath` wins\n * over `url` when both are present (prefer the local file over an extra\n * remote hop). Absolute path becomes `{ type: \"path\" }`; url becomes\n * `{ type: \"url\" }`.\n * - String starting with `http://` / `https://` → `{ type: \"url\" }`.\n * - Any other string → `{ type: \"path\" }`.\n * - Tagged `{ type: \"image\" | \"text\", source }` → recurses into `source`.\n *\n * Throws `InvalidRequestError` on obviously invalid input (empty\n * string, storage object with neither url nor absolutePath, missing\n * source field).\n *\n * @example\n * resolveAttachment(\"https://cdn.example.com/doc.pdf\");\n * // → { type: \"url\", value: \"https://cdn.example.com/doc.pdf\" }\n *\n * @example\n * resolveAttachment({ type: \"image\", source: \"/tmp/x.png\" });\n * // → { type: \"path\", value: \"/tmp/x.png\" }\n */\nexport function resolveAttachment(attachment: Attachment): ResolvedAttachment {\n if (\n typeof attachment === \"object\" &&\n attachment !== null &&\n \"type\" in attachment\n ) {\n return resolveSource(attachment.source);\n }\n\n return resolveSource(attachment);\n}\n\nfunction resolveSource(source: AttachmentSource): ResolvedAttachment {\n if (typeof source === \"string\") {\n if (!source) {\n throw new InvalidRequestError(\"Cannot resolve empty attachment string\");\n }\n\n if (REMOTE_URL_PATTERN.test(source)) {\n return { type: \"url\", value: source };\n }\n\n return { type: \"path\", value: source };\n }\n\n // StorageFile objects (from @warlock.js/core) can expose a `base64`\n // property alongside `url` / `absolutePath`. Check storage shape\n // first so we don't treat a StorageFile as an inline-bytes payload.\n if (\"url\" in source || \"absolutePath\" in source) {\n const storage = source as { url?: string; absolutePath?: string };\n\n if (storage.absolutePath) {\n return { type: \"path\", value: storage.absolutePath };\n }\n\n if (storage.url) {\n return { type: \"url\", value: storage.url };\n }\n\n throw new InvalidRequestError(\n \"Storage attachment has neither url nor absolutePath\",\n );\n }\n\n if (\"base64\" in source) {\n if (!source.base64 || !source.mediaType) {\n throw new InvalidRequestError(\n \"Inline attachment requires both `base64` and `mediaType`\",\n );\n }\n\n return {\n type: \"base64\",\n value: source.base64,\n mediaType: source.mediaType,\n };\n }\n\n throw new InvalidRequestError(\n \"Unrecognized attachment source — expected a string path/URL, a StorageFile, or `{ base64, mediaType }`\",\n );\n}\n"],"mappings":";;;;AAOA,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6B3B,SAAgB,kBAAkB,YAA4C;CAC5E,IACE,OAAO,eAAe,YACtB,eAAe,QACf,UAAU,YAEV,OAAO,cAAc,WAAW,MAAM;CAGxC,OAAO,cAAc,UAAU;AACjC;AAEA,SAAS,cAAc,QAA8C;CACnE,IAAI,OAAO,WAAW,UAAU;EAC9B,IAAI,CAAC,QACH,MAAM,IAAI,oBAAoB,wCAAwC;EAGxE,IAAI,mBAAmB,KAAK,MAAM,GAChC,OAAO;GAAE,MAAM;GAAO,OAAO;EAAO;EAGtC,OAAO;GAAE,MAAM;GAAQ,OAAO;EAAO;CACvC;CAKA,IAAI,SAAS,UAAU,kBAAkB,QAAQ;EAC/C,MAAM,UAAU;EAEhB,IAAI,QAAQ,cACV,OAAO;GAAE,MAAM;GAAQ,OAAO,QAAQ;EAAa;EAGrD,IAAI,QAAQ,KACV,OAAO;GAAE,MAAM;GAAO,OAAO,QAAQ;EAAI;EAG3C,MAAM,IAAI,oBACR,qDACF;CACF;CAEA,IAAI,YAAY,QAAQ;EACtB,IAAI,CAAC,OAAO,UAAU,CAAC,OAAO,WAC5B,MAAM,IAAI,oBACR,0DACF;EAGF,OAAO;GACL,MAAM;GACN,OAAO,OAAO;GACd,WAAW,OAAO;EACpB;CACF;CAEA,MAAM,IAAI,oBACR,wGACF;AACF"}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { BaseReport } from "../contracts/result/base-report.type.mjs";
|
|
2
|
+
|
|
3
|
+
//#region ../@warlock.js/ai/src/utils/run-context.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* The ambient run frame an executable reads when it finishes building
|
|
6
|
+
* its report. When present, a child execution (an `agent.execute()`,
|
|
7
|
+
* `workflow.execute()`, `supervisor.execute()` call) auto-attaches its
|
|
8
|
+
* report to `sink` and inherits the frame's `rootRunId` / `sessionId`
|
|
9
|
+
* lineage — so reports nest under the enclosing run with NO manual id
|
|
10
|
+
* threading by the dev.
|
|
11
|
+
*
|
|
12
|
+
* Installed by orchestration primitives (supervisor / orchestrator /
|
|
13
|
+
* team) around the synchronous + async body of an intent's callback,
|
|
14
|
+
* so any agent the callback invokes directly — `agent.execute(...)`
|
|
15
|
+
* rather than `ctx.run(...)` — still lands in the trace tree.
|
|
16
|
+
*/
|
|
17
|
+
type RunFrame = {
|
|
18
|
+
/**
|
|
19
|
+
* The `children[]` array of the enclosing report node. A child
|
|
20
|
+
* execution pushes its assembled report here on completion.
|
|
21
|
+
*/
|
|
22
|
+
sink: BaseReport[];
|
|
23
|
+
/**
|
|
24
|
+
* The outermost run-id this subtree belongs to. Propagated onto the
|
|
25
|
+
* child report's `rootRunId` so flat-row consumers group it with the
|
|
26
|
+
* enclosing run.
|
|
27
|
+
*/
|
|
28
|
+
rootRunId: string;
|
|
29
|
+
/**
|
|
30
|
+
* Run-id of the enclosing node (the immediate parent of any child
|
|
31
|
+
* captured through this frame). Stamped onto the child's
|
|
32
|
+
* `parentRunId`.
|
|
33
|
+
*/
|
|
34
|
+
parentRunId: string;
|
|
35
|
+
/**
|
|
36
|
+
* Session identifier propagated onto the captured child's subtree.
|
|
37
|
+
* `undefined` when the enclosing run had none.
|
|
38
|
+
*/
|
|
39
|
+
sessionId?: string;
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* Run `fn` with `frame` installed as the ambient {@link RunFrame} for
|
|
43
|
+
* the entire async subtree it spawns. Restores the previous frame (or
|
|
44
|
+
* none) when `fn` settles. Returns whatever `fn` returns.
|
|
45
|
+
*
|
|
46
|
+
* Nesting is natural: a callback that itself dispatches a nested
|
|
47
|
+
* supervisor installs a fresh frame for the inner run, and the inner
|
|
48
|
+
* frame shadows the outer one for the inner subtree only — exactly the
|
|
49
|
+
* tree shape the report models.
|
|
50
|
+
*/
|
|
51
|
+
declare function withRunFrame<T>(frame: RunFrame, fn: () => T): T;
|
|
52
|
+
/**
|
|
53
|
+
* Run `fn` with NO ambient {@link RunFrame} installed for its async
|
|
54
|
+
* subtree, restoring the previous frame when `fn` settles. Used by the
|
|
55
|
+
* supervisor's EXPLICIT capture paths (`ctx.run(...)` /
|
|
56
|
+
* `ctx.intents.X.execute()`) — those already push the child report onto
|
|
57
|
+
* the callback's `children[]` themselves, so the child must NOT also
|
|
58
|
+
* self-capture via the ambient frame (which would double-count it).
|
|
59
|
+
*/
|
|
60
|
+
declare function withoutRunFrame<T>(fn: () => T): T;
|
|
61
|
+
/**
|
|
62
|
+
* Read the current ambient {@link RunFrame}, or `undefined` when no
|
|
63
|
+
* orchestration callback is on the stack. An executable calls this at
|
|
64
|
+
* report-build time: a present frame means "you were invoked inside a
|
|
65
|
+
* callback — attach yourself to its tree".
|
|
66
|
+
*/
|
|
67
|
+
declare function currentRunFrame(): RunFrame | undefined;
|
|
68
|
+
/**
|
|
69
|
+
* Capture a freshly-built child `report` onto the current ambient
|
|
70
|
+
* {@link RunFrame} when one is installed. Pushes the report onto the
|
|
71
|
+
* frame's `sink` so it nests under the enclosing node, and rewrites the
|
|
72
|
+
* report's lineage (`rootRunId`, `parentRunId`, `sessionId`) to the
|
|
73
|
+
* frame's — mirroring how `step.agent` / `ctx.run` capture child
|
|
74
|
+
* reports explicitly, but driven ambiently.
|
|
75
|
+
*
|
|
76
|
+
* No-op (returns `false`) when no frame is installed — a standalone
|
|
77
|
+
* `agent.execute()` keeps its self-root untouched. Returns `true` when
|
|
78
|
+
* the report was captured so the caller can suppress its own terminal
|
|
79
|
+
* lineage stamp if needed.
|
|
80
|
+
*
|
|
81
|
+
* The lineage rewrite is intentionally shallow on the root + deep on
|
|
82
|
+
* descendants would double-stamp; callers pass the already-lineage-
|
|
83
|
+
* stamped subtree (self-root), and this relinks only the root's
|
|
84
|
+
* `rootRunId` / `parentRunId` / `sessionId`. Descendants already carry
|
|
85
|
+
* the child's own self-root as their `rootRunId`; the enclosing
|
|
86
|
+
* primitive's terminal `stampReportLineage` pass (run once on the
|
|
87
|
+
* outer tree) rewrites the whole subtree to the true outer root. This
|
|
88
|
+
* keeps capture cheap and defers the single authoritative relink to
|
|
89
|
+
* the outer build.
|
|
90
|
+
*/
|
|
91
|
+
declare function captureChildReport(report: BaseReport): boolean;
|
|
92
|
+
//#endregion
|
|
93
|
+
export { RunFrame, captureChildReport, currentRunFrame, withRunFrame, withoutRunFrame };
|
|
94
|
+
//# sourceMappingURL=run-context.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run-context.d.mts","names":[],"sources":["../../../../../../../@warlock.js/ai/src/utils/run-context.ts"],"mappings":";;;;;AAgBA;;;;;;;;;;AAsBW;KAtBC,QAAA;EAqDgB;;;;EAhD1B,IAAA,EAAM,UAAU;EAgD8C;;;;;EA1C9D,SAAA;EA0C+C;;;AAAe;AAYhE;EAhDE,WAAA;EAgD6B;;;;EA3C7B,SAAA;AAAA;;AA2CgD;AAUlD;;;;AAA2C;AAsC3C;;;iBA5DgB,YAAA,IAAgB,KAAA,EAAO,QAAA,EAAU,EAAA,QAAU,CAAA,GAAI,CAAA;AA4DV;;;;;;;;AAAA,iBAhDrC,eAAA,IAAmB,EAAA,QAAU,CAAA,GAAI,CAAC;;;;;;;iBAUlC,eAAA,IAAmB,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;iBAsC3B,kBAAA,CAAmB,MAAkB,EAAV,UAAU"}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
|
+
|
|
3
|
+
//#region ../@warlock.js/ai/src/utils/run-context.ts
|
|
4
|
+
/**
|
|
5
|
+
* Process-wide async-local store holding the current {@link RunFrame}.
|
|
6
|
+
* A single shared instance so a frame installed by the supervisor is
|
|
7
|
+
* visible to an agent running several `await`s deep inside a callback,
|
|
8
|
+
* across module boundaries. Empty (returns `undefined`) outside any
|
|
9
|
+
* orchestration callback — standalone `agent.execute()` is unaffected.
|
|
10
|
+
*/
|
|
11
|
+
const runFrameStore = new AsyncLocalStorage();
|
|
12
|
+
/**
|
|
13
|
+
* A second async-local flag set whenever execution is nested inside a
|
|
14
|
+
* parent capture — by BOTH {@link withRunFrame} (ambient capture) and
|
|
15
|
+
* {@link withoutRunFrame} (explicit parent capture, which clears the frame).
|
|
16
|
+
* Lets the `observeAll` gate skip self-routing a nested run regardless of
|
|
17
|
+
* which capture path its parent uses — the parent already captures it.
|
|
18
|
+
*/
|
|
19
|
+
const nestedStore = new AsyncLocalStorage();
|
|
20
|
+
/**
|
|
21
|
+
* Run `fn` with `frame` installed as the ambient {@link RunFrame} for
|
|
22
|
+
* the entire async subtree it spawns. Restores the previous frame (or
|
|
23
|
+
* none) when `fn` settles. Returns whatever `fn` returns.
|
|
24
|
+
*
|
|
25
|
+
* Nesting is natural: a callback that itself dispatches a nested
|
|
26
|
+
* supervisor installs a fresh frame for the inner run, and the inner
|
|
27
|
+
* frame shadows the outer one for the inner subtree only — exactly the
|
|
28
|
+
* tree shape the report models.
|
|
29
|
+
*/
|
|
30
|
+
function withRunFrame(frame, fn) {
|
|
31
|
+
return nestedStore.run(true, () => runFrameStore.run(frame, fn));
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Run `fn` with NO ambient {@link RunFrame} installed for its async
|
|
35
|
+
* subtree, restoring the previous frame when `fn` settles. Used by the
|
|
36
|
+
* supervisor's EXPLICIT capture paths (`ctx.run(...)` /
|
|
37
|
+
* `ctx.intents.X.execute()`) — those already push the child report onto
|
|
38
|
+
* the callback's `children[]` themselves, so the child must NOT also
|
|
39
|
+
* self-capture via the ambient frame (which would double-count it).
|
|
40
|
+
*/
|
|
41
|
+
function withoutRunFrame(fn) {
|
|
42
|
+
return nestedStore.run(true, () => runFrameStore.run(void 0, fn));
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Read the current ambient {@link RunFrame}, or `undefined` when no
|
|
46
|
+
* orchestration callback is on the stack. An executable calls this at
|
|
47
|
+
* report-build time: a present frame means "you were invoked inside a
|
|
48
|
+
* callback — attach yourself to its tree".
|
|
49
|
+
*/
|
|
50
|
+
function currentRunFrame() {
|
|
51
|
+
return runFrameStore.getStore();
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* `true` when execution is nested inside a parent capture — set by
|
|
55
|
+
* {@link withRunFrame} (ambient) or {@link withoutRunFrame} (explicit). Read
|
|
56
|
+
* by the observe-all gate so a nested run, which its parent already captures
|
|
57
|
+
* into the trace tree, is not also self-routed as a standalone top-level
|
|
58
|
+
* trace. `false` for a standalone (root) run.
|
|
59
|
+
*/
|
|
60
|
+
function isNestedRun() {
|
|
61
|
+
return nestedStore.getStore() === true;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Capture a freshly-built child `report` onto the current ambient
|
|
65
|
+
* {@link RunFrame} when one is installed. Pushes the report onto the
|
|
66
|
+
* frame's `sink` so it nests under the enclosing node, and rewrites the
|
|
67
|
+
* report's lineage (`rootRunId`, `parentRunId`, `sessionId`) to the
|
|
68
|
+
* frame's — mirroring how `step.agent` / `ctx.run` capture child
|
|
69
|
+
* reports explicitly, but driven ambiently.
|
|
70
|
+
*
|
|
71
|
+
* No-op (returns `false`) when no frame is installed — a standalone
|
|
72
|
+
* `agent.execute()` keeps its self-root untouched. Returns `true` when
|
|
73
|
+
* the report was captured so the caller can suppress its own terminal
|
|
74
|
+
* lineage stamp if needed.
|
|
75
|
+
*
|
|
76
|
+
* The lineage rewrite is intentionally shallow on the root + deep on
|
|
77
|
+
* descendants would double-stamp; callers pass the already-lineage-
|
|
78
|
+
* stamped subtree (self-root), and this relinks only the root's
|
|
79
|
+
* `rootRunId` / `parentRunId` / `sessionId`. Descendants already carry
|
|
80
|
+
* the child's own self-root as their `rootRunId`; the enclosing
|
|
81
|
+
* primitive's terminal `stampReportLineage` pass (run once on the
|
|
82
|
+
* outer tree) rewrites the whole subtree to the true outer root. This
|
|
83
|
+
* keeps capture cheap and defers the single authoritative relink to
|
|
84
|
+
* the outer build.
|
|
85
|
+
*/
|
|
86
|
+
function captureChildReport(report) {
|
|
87
|
+
const frame = runFrameStore.getStore();
|
|
88
|
+
if (!frame) return false;
|
|
89
|
+
report.parentRunId = frame.parentRunId;
|
|
90
|
+
report.rootRunId = frame.rootRunId;
|
|
91
|
+
if (frame.sessionId !== void 0) report.sessionId = frame.sessionId;
|
|
92
|
+
frame.sink.push(report);
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
//#endregion
|
|
97
|
+
export { captureChildReport, currentRunFrame, isNestedRun, withRunFrame, withoutRunFrame };
|
|
98
|
+
//# sourceMappingURL=run-context.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run-context.mjs","names":[],"sources":["../../../../../../../@warlock.js/ai/src/utils/run-context.ts"],"sourcesContent":["import { AsyncLocalStorage } from \"node:async_hooks\";\nimport type { BaseReport } from \"../contracts/result/base-report.type\";\n\n/**\n * The ambient run frame an executable reads when it finishes building\n * its report. When present, a child execution (an `agent.execute()`,\n * `workflow.execute()`, `supervisor.execute()` call) auto-attaches its\n * report to `sink` and inherits the frame's `rootRunId` / `sessionId`\n * lineage — so reports nest under the enclosing run with NO manual id\n * threading by the dev.\n *\n * Installed by orchestration primitives (supervisor / orchestrator /\n * team) around the synchronous + async body of an intent's callback,\n * so any agent the callback invokes directly — `agent.execute(...)`\n * rather than `ctx.run(...)` — still lands in the trace tree.\n */\nexport type RunFrame = {\n /**\n * The `children[]` array of the enclosing report node. A child\n * execution pushes its assembled report here on completion.\n */\n sink: BaseReport[];\n /**\n * The outermost run-id this subtree belongs to. Propagated onto the\n * child report's `rootRunId` so flat-row consumers group it with the\n * enclosing run.\n */\n rootRunId: string;\n /**\n * Run-id of the enclosing node (the immediate parent of any child\n * captured through this frame). Stamped onto the child's\n * `parentRunId`.\n */\n parentRunId: string;\n /**\n * Session identifier propagated onto the captured child's subtree.\n * `undefined` when the enclosing run had none.\n */\n sessionId?: string;\n};\n\n/**\n * Process-wide async-local store holding the current {@link RunFrame}.\n * A single shared instance so a frame installed by the supervisor is\n * visible to an agent running several `await`s deep inside a callback,\n * across module boundaries. Empty (returns `undefined`) outside any\n * orchestration callback — standalone `agent.execute()` is unaffected.\n */\nconst runFrameStore = new AsyncLocalStorage<RunFrame | undefined>();\n\n/**\n * A second async-local flag set whenever execution is nested inside a\n * parent capture — by BOTH {@link withRunFrame} (ambient capture) and\n * {@link withoutRunFrame} (explicit parent capture, which clears the frame).\n * Lets the `observeAll` gate skip self-routing a nested run regardless of\n * which capture path its parent uses — the parent already captures it.\n */\nconst nestedStore = new AsyncLocalStorage<boolean>();\n\n/**\n * Run `fn` with `frame` installed as the ambient {@link RunFrame} for\n * the entire async subtree it spawns. Restores the previous frame (or\n * none) when `fn` settles. Returns whatever `fn` returns.\n *\n * Nesting is natural: a callback that itself dispatches a nested\n * supervisor installs a fresh frame for the inner run, and the inner\n * frame shadows the outer one for the inner subtree only — exactly the\n * tree shape the report models.\n */\nexport function withRunFrame<T>(frame: RunFrame, fn: () => T): T {\n return nestedStore.run(true, () => runFrameStore.run(frame, fn));\n}\n\n/**\n * Run `fn` with NO ambient {@link RunFrame} installed for its async\n * subtree, restoring the previous frame when `fn` settles. Used by the\n * supervisor's EXPLICIT capture paths (`ctx.run(...)` /\n * `ctx.intents.X.execute()`) — those already push the child report onto\n * the callback's `children[]` themselves, so the child must NOT also\n * self-capture via the ambient frame (which would double-count it).\n */\nexport function withoutRunFrame<T>(fn: () => T): T {\n return nestedStore.run(true, () => runFrameStore.run(undefined, fn));\n}\n\n/**\n * Read the current ambient {@link RunFrame}, or `undefined` when no\n * orchestration callback is on the stack. An executable calls this at\n * report-build time: a present frame means \"you were invoked inside a\n * callback — attach yourself to its tree\".\n */\nexport function currentRunFrame(): RunFrame | undefined {\n return runFrameStore.getStore();\n}\n\n/**\n * `true` when execution is nested inside a parent capture — set by\n * {@link withRunFrame} (ambient) or {@link withoutRunFrame} (explicit). Read\n * by the observe-all gate so a nested run, which its parent already captures\n * into the trace tree, is not also self-routed as a standalone top-level\n * trace. `false` for a standalone (root) run.\n */\nexport function isNestedRun(): boolean {\n return nestedStore.getStore() === true;\n}\n\n/**\n * Capture a freshly-built child `report` onto the current ambient\n * {@link RunFrame} when one is installed. Pushes the report onto the\n * frame's `sink` so it nests under the enclosing node, and rewrites the\n * report's lineage (`rootRunId`, `parentRunId`, `sessionId`) to the\n * frame's — mirroring how `step.agent` / `ctx.run` capture child\n * reports explicitly, but driven ambiently.\n *\n * No-op (returns `false`) when no frame is installed — a standalone\n * `agent.execute()` keeps its self-root untouched. Returns `true` when\n * the report was captured so the caller can suppress its own terminal\n * lineage stamp if needed.\n *\n * The lineage rewrite is intentionally shallow on the root + deep on\n * descendants would double-stamp; callers pass the already-lineage-\n * stamped subtree (self-root), and this relinks only the root's\n * `rootRunId` / `parentRunId` / `sessionId`. Descendants already carry\n * the child's own self-root as their `rootRunId`; the enclosing\n * primitive's terminal `stampReportLineage` pass (run once on the\n * outer tree) rewrites the whole subtree to the true outer root. This\n * keeps capture cheap and defers the single authoritative relink to\n * the outer build.\n */\nexport function captureChildReport(report: BaseReport): boolean {\n const frame = runFrameStore.getStore();\n\n if (!frame) {\n return false;\n }\n\n report.parentRunId = frame.parentRunId;\n report.rootRunId = frame.rootRunId;\n\n if (frame.sessionId !== undefined) {\n report.sessionId = frame.sessionId;\n }\n\n frame.sink.push(report);\n\n return true;\n}\n"],"mappings":";;;;;;;;;;AAgDA,MAAM,gBAAgB,IAAI,kBAAwC;;;;;;;;AASlE,MAAM,cAAc,IAAI,kBAA2B;;;;;;;;;;;AAYnD,SAAgB,aAAgB,OAAiB,IAAgB;CAC/D,OAAO,YAAY,IAAI,YAAY,cAAc,IAAI,OAAO,EAAE,CAAC;AACjE;;;;;;;;;AAUA,SAAgB,gBAAmB,IAAgB;CACjD,OAAO,YAAY,IAAI,YAAY,cAAc,IAAI,QAAW,EAAE,CAAC;AACrE;;;;;;;AAQA,SAAgB,kBAAwC;CACtD,OAAO,cAAc,SAAS;AAChC;;;;;;;;AASA,SAAgB,cAAuB;CACrC,OAAO,YAAY,SAAS,MAAM;AACpC;;;;;;;;;;;;;;;;;;;;;;;;AAyBA,SAAgB,mBAAmB,QAA6B;CAC9D,MAAM,QAAQ,cAAc,SAAS;CAErC,IAAI,CAAC,OACH,OAAO;CAGT,OAAO,cAAc,MAAM;CAC3B,OAAO,YAAY,MAAM;CAEzB,IAAI,MAAM,cAAc,QACtB,OAAO,YAAY,MAAM;CAG3B,MAAM,KAAK,KAAK,MAAM;CAEtB,OAAO;AACT"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
//#region ../@warlock.js/ai/src/vcr/cassette-io.ts
|
|
2
|
+
let fsModule;
|
|
3
|
+
/**
|
|
4
|
+
* Resolve `node:fs/promises` once and memoize it.
|
|
5
|
+
*/
|
|
6
|
+
async function loadFs() {
|
|
7
|
+
if (!fsModule) fsModule = await import("node:fs/promises");
|
|
8
|
+
return fsModule;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Build a fresh, empty cassette for a model identity. Used when the path
|
|
12
|
+
* does not exist yet (first record run).
|
|
13
|
+
*/
|
|
14
|
+
function emptyCassette(model, provider) {
|
|
15
|
+
return {
|
|
16
|
+
version: 1,
|
|
17
|
+
model,
|
|
18
|
+
provider,
|
|
19
|
+
entries: []
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Load a cassette from disk. Returns a fresh empty cassette (not an error)
|
|
24
|
+
* when the file does not exist — the common first-record case. Any other I/O
|
|
25
|
+
* or parse failure rejects so corruption is never silently swallowed.
|
|
26
|
+
*/
|
|
27
|
+
async function loadCassette(path, model, provider) {
|
|
28
|
+
const fs = await loadFs();
|
|
29
|
+
let raw;
|
|
30
|
+
try {
|
|
31
|
+
raw = await fs.readFile(path, "utf8");
|
|
32
|
+
} catch (error) {
|
|
33
|
+
if (error.code === "ENOENT") return emptyCassette(model, provider);
|
|
34
|
+
throw error;
|
|
35
|
+
}
|
|
36
|
+
const parsed = JSON.parse(raw);
|
|
37
|
+
return {
|
|
38
|
+
version: 1,
|
|
39
|
+
model: parsed.model ?? model,
|
|
40
|
+
provider: parsed.provider ?? provider,
|
|
41
|
+
entries: Array.isArray(parsed.entries) ? parsed.entries : []
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Write a cassette to disk as pretty-printed JSON, creating the parent
|
|
46
|
+
* directory if needed so a brand-new `./cassettes/foo.json` path just works.
|
|
47
|
+
*/
|
|
48
|
+
async function saveCassette(path, cassette) {
|
|
49
|
+
const fs = await loadFs();
|
|
50
|
+
const dir = (await import("node:path")).dirname(path);
|
|
51
|
+
if (dir && dir !== "." && dir !== path) await fs.mkdir(dir, { recursive: true });
|
|
52
|
+
await fs.writeFile(path, JSON.stringify(cassette, void 0, 2), "utf8");
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
//#endregion
|
|
56
|
+
export { emptyCassette, loadCassette, saveCassette };
|
|
57
|
+
//# sourceMappingURL=cassette-io.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cassette-io.mjs","names":[],"sources":["../../../../../../../@warlock.js/ai/src/vcr/cassette-io.ts"],"sourcesContent":["import type { Cassette } from \"./vcr.type\";\n\n/**\n * Lazily-resolved `node:fs/promises` module. VCR cassette I/O only touches\n * disk on construct (load) and on `save()` — keeping the import lazy means\n * importing the `vcr` factory never eagerly pulls `node:fs`, which keeps the\n * surface usable in non-node bundles that never call a disk path.\n */\ntype FsPromises = typeof import(\"node:fs/promises\");\n\nlet fsModule: FsPromises | undefined;\n\n/**\n * Resolve `node:fs/promises` once and memoize it.\n */\nasync function loadFs(): Promise<FsPromises> {\n if (!fsModule) {\n fsModule = await import(\"node:fs/promises\");\n }\n\n return fsModule;\n}\n\n/**\n * Build a fresh, empty cassette for a model identity. Used when the path\n * does not exist yet (first record run).\n */\nexport function emptyCassette(model: string, provider: string): Cassette {\n return {\n version: 1,\n model,\n provider,\n entries: [],\n };\n}\n\n/**\n * Load a cassette from disk. Returns a fresh empty cassette (not an error)\n * when the file does not exist — the common first-record case. Any other I/O\n * or parse failure rejects so corruption is never silently swallowed.\n */\nexport async function loadCassette(\n path: string,\n model: string,\n provider: string,\n): Promise<Cassette> {\n const fs = await loadFs();\n\n let raw: string;\n\n try {\n raw = await fs.readFile(path, \"utf8\");\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") {\n return emptyCassette(model, provider);\n }\n\n throw error;\n }\n\n const parsed = JSON.parse(raw) as Cassette;\n\n return {\n version: 1,\n model: parsed.model ?? model,\n provider: parsed.provider ?? provider,\n entries: Array.isArray(parsed.entries) ? parsed.entries : [],\n };\n}\n\n/**\n * Write a cassette to disk as pretty-printed JSON, creating the parent\n * directory if needed so a brand-new `./cassettes/foo.json` path just works.\n */\nexport async function saveCassette(path: string, cassette: Cassette): Promise<void> {\n const fs = await loadFs();\n const nodePath = await import(\"node:path\");\n const dir = nodePath.dirname(path);\n\n if (dir && dir !== \".\" && dir !== path) {\n await fs.mkdir(dir, { recursive: true });\n }\n\n await fs.writeFile(path, JSON.stringify(cassette, undefined, 2), \"utf8\");\n}\n"],"mappings":";AAUA,IAAI;;;;AAKJ,eAAe,SAA8B;CAC3C,IAAI,CAAC,UACH,WAAW,MAAM,OAAO;CAG1B,OAAO;AACT;;;;;AAMA,SAAgB,cAAc,OAAe,UAA4B;CACvE,OAAO;EACL,SAAS;EACT;EACA;EACA,SAAS,CAAC;CACZ;AACF;;;;;;AAOA,eAAsB,aACpB,MACA,OACA,UACmB;CACnB,MAAM,KAAK,MAAM,OAAO;CAExB,IAAI;CAEJ,IAAI;EACF,MAAM,MAAM,GAAG,SAAS,MAAM,MAAM;CACtC,SAAS,OAAO;EACd,IAAK,MAAgC,SAAS,UAC5C,OAAO,cAAc,OAAO,QAAQ;EAGtC,MAAM;CACR;CAEA,MAAM,SAAS,KAAK,MAAM,GAAG;CAE7B,OAAO;EACL,SAAS;EACT,OAAO,OAAO,SAAS;EACvB,UAAU,OAAO,YAAY;EAC7B,SAAS,MAAM,QAAQ,OAAO,OAAO,IAAI,OAAO,UAAU,CAAC;CAC7D;AACF;;;;;AAMA,eAAsB,aAAa,MAAc,UAAmC;CAClF,MAAM,KAAK,MAAM,OAAO;CAExB,MAAM,OAAM,MADW,OAAO,aACV,CAAC,QAAQ,IAAI;CAEjC,IAAI,OAAO,QAAQ,OAAO,QAAQ,MAChC,MAAM,GAAG,MAAM,KAAK,EAAE,WAAW,KAAK,CAAC;CAGzC,MAAM,GAAG,UAAU,MAAM,KAAK,UAAU,UAAU,QAAW,CAAC,GAAG,MAAM;AACzE"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { AIError, AIErrorOptions } from "../errors/ai-error.mjs";
|
|
2
|
+
|
|
3
|
+
//#region ../@warlock.js/ai/src/vcr/errors.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Payload for {@link VcrCassetteMissError}. Carries the looked-up request
|
|
6
|
+
* hash and the cassette path so a failing CI run names exactly which call
|
|
7
|
+
* was not recorded.
|
|
8
|
+
*/
|
|
9
|
+
type VcrCassetteMissErrorOptions = AIErrorOptions & {
|
|
10
|
+
/** The normalized request hash that found no matching cassette entry. */requestHash?: string; /** Cassette file path the lookup ran against. */
|
|
11
|
+
path?: string;
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Thrown when a `vcr(model, { mode: "replay" })` call finds no cassette
|
|
15
|
+
* entry matching the request hash.
|
|
16
|
+
*
|
|
17
|
+
* **The whole point of deterministic tests.** VCR in `replay` mode never
|
|
18
|
+
* falls back to a live provider call on a miss — that would silently
|
|
19
|
+
* re-introduce network/non-determinism into a test that asked for the
|
|
20
|
+
* opposite. Instead it throws this error so the run fails loud, telling
|
|
21
|
+
* the developer to re-record the cassette (run once in `record`/`auto`).
|
|
22
|
+
*
|
|
23
|
+
* Extends {@link AIError} directly (not `ProviderError`) — a cassette miss
|
|
24
|
+
* is a harness/config failure, not a provider failure.
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* try {
|
|
28
|
+
* await vcrModel.complete(messages);
|
|
29
|
+
* } catch (error) {
|
|
30
|
+
* if (error instanceof VcrCassetteMissError) {
|
|
31
|
+
* console.error("Re-record the cassette:", error.path);
|
|
32
|
+
* }
|
|
33
|
+
* }
|
|
34
|
+
*/
|
|
35
|
+
declare class VcrCassetteMissError extends AIError {
|
|
36
|
+
readonly requestHash?: string;
|
|
37
|
+
readonly path?: string;
|
|
38
|
+
constructor(message: string, options?: VcrCassetteMissErrorOptions);
|
|
39
|
+
}
|
|
40
|
+
//#endregion
|
|
41
|
+
export { VcrCassetteMissError, VcrCassetteMissErrorOptions };
|
|
42
|
+
//# sourceMappingURL=errors.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.mts","names":[],"sources":["../../../../../../../@warlock.js/ai/src/vcr/errors.ts"],"mappings":";;;;AAQA;;;;KAAY,2BAAA,GAA8B,cAAc;EAEtD,yEAAA,WAAA,WAEI;EAAJ,IAAA;AAAA;;;;;;;;;;;;AA6ByE;;;;;;;;;;;cAJ9D,oBAAA,SAA6B,OAAO;EAAA,SAC/B,WAAA;EAAA,SACA,IAAA;cAEG,OAAA,UAAiB,OAAA,GAAU,2BAAA;AAAA"}
|