docpull 5.0.0__tar.gz → 5.0.1__tar.gz
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.
- {docpull-5.0.0/src/docpull.egg-info → docpull-5.0.1}/PKG-INFO +1 -1
- {docpull-5.0.0 → docpull-5.0.1}/pyproject.toml +1 -1
- {docpull-5.0.0 → docpull-5.0.1}/src/docpull/__init__.py +1 -1
- {docpull-5.0.0 → docpull-5.0.1}/src/docpull/mcp/server.py +281 -71
- {docpull-5.0.0 → docpull-5.0.1/src/docpull.egg-info}/PKG-INFO +1 -1
- {docpull-5.0.0 → docpull-5.0.1}/tests/test_mcp_server.py +65 -0
- {docpull-5.0.0 → docpull-5.0.1}/LICENSE +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/README.md +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/setup.cfg +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/src/docpull/__main__.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/src/docpull/accounting.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/src/docpull/auth_cli.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/src/docpull/benchmark.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/src/docpull/cache/__init__.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/src/docpull/cache/frontier.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/src/docpull/cache/manager.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/src/docpull/cache/streaming_dedup.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/src/docpull/cli.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/src/docpull/conversion/__init__.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/src/docpull/conversion/chunking.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/src/docpull/conversion/extractor.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/src/docpull/conversion/filings.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/src/docpull/conversion/markdown.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/src/docpull/conversion/protocols.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/src/docpull/conversion/special_cases.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/src/docpull/conversion/trafilatura_extractor.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/src/docpull/core/__init__.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/src/docpull/core/fetcher.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/src/docpull/discovery/__init__.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/src/docpull/discovery/_fetch.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/src/docpull/discovery/composite.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/src/docpull/discovery/contracts.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/src/docpull/discovery/crawler.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/src/docpull/discovery/filters.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/src/docpull/discovery/link_extractors/__init__.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/src/docpull/discovery/link_extractors/enhanced.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/src/docpull/discovery/link_extractors/protocols.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/src/docpull/discovery/link_extractors/static.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/src/docpull/discovery/protocols.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/src/docpull/discovery/sitemap.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/src/docpull/discovery_cli.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/src/docpull/doctor.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/src/docpull/evidence_pack.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/src/docpull/exports.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/src/docpull/fixtures/__init__.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/src/docpull/fixtures/parallel-search-extract.json +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/src/docpull/graph.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/src/docpull/http/__init__.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/src/docpull/http/client.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/src/docpull/http/protocols.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/src/docpull/http/rate_limiter.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/src/docpull/judge.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/src/docpull/local_workflows.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/src/docpull/mcp/__init__.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/src/docpull/mcp/sources.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/src/docpull/mcp/tools.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/src/docpull/metadata_extractor.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/src/docpull/models/__init__.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/src/docpull/models/config.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/src/docpull/models/document.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/src/docpull/models/events.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/src/docpull/models/profiles.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/src/docpull/models/run.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/src/docpull/monitor.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/src/docpull/pack_reader.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/src/docpull/pack_tools.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/src/docpull/parallel_workflows.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/src/docpull/parity.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/src/docpull/parity_cli.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/src/docpull/passk.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/src/docpull/pipeline/__init__.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/src/docpull/pipeline/base.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/src/docpull/pipeline/manifest.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/src/docpull/pipeline/steps/__init__.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/src/docpull/pipeline/steps/chunk.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/src/docpull/pipeline/steps/convert.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/src/docpull/pipeline/steps/dedup.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/src/docpull/pipeline/steps/fetch.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/src/docpull/pipeline/steps/metadata.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/src/docpull/pipeline/steps/render.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/src/docpull/pipeline/steps/save.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/src/docpull/pipeline/steps/save_json.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/src/docpull/pipeline/steps/save_ndjson.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/src/docpull/pipeline/steps/save_okf.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/src/docpull/pipeline/steps/save_sqlite.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/src/docpull/pipeline/steps/validate.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/src/docpull/policy.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/src/docpull/policy_cli.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/src/docpull/provider_adapters.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/src/docpull/provider_capabilities.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/src/docpull/provider_cli.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/src/docpull/provider_keys.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/src/docpull/provider_probes.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/src/docpull/py.typed +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/src/docpull/rendering.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/src/docpull/scraper.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/src/docpull/security/__init__.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/src/docpull/security/download_policy.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/src/docpull/security/robots.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/src/docpull/security/url_validator.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/src/docpull/server.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/src/docpull/skill_export.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/src/docpull/source_scoring.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/src/docpull/time_utils.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/src/docpull.egg-info/SOURCES.txt +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/src/docpull.egg-info/dependency_links.txt +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/src/docpull.egg-info/entry_points.txt +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/src/docpull.egg-info/requires.txt +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/src/docpull.egg-info/top_level.txt +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/tests/test_accounting.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/tests/test_auth_cli.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/tests/test_benchmark.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/tests/test_cache_conditional_get.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/tests/test_chunking.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/tests/test_ci_policy.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/tests/test_cli.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/tests/test_conversion.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/tests/test_convert_step_new.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/tests/test_discovery.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/tests/test_discovery_contracts.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/tests/test_doctor.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/tests/test_document_record.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/tests/test_evidence_pack.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/tests/test_exports.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/tests/test_frontier_resume.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/tests/test_graph.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/tests/test_integration.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/tests/test_judge.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/tests/test_link_extractors.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/tests/test_local_workflows.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/tests/test_mcp_tools.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/tests/test_metadata_extractor.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/tests/test_monitor.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/tests/test_naming.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/tests/test_outputs_e2e.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/tests/test_pack_server.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/tests/test_pack_tools.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/tests/test_parallel_workflows.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/tests/test_parity_workflows.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/tests/test_passk.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/tests/test_pipeline.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/tests/test_policy.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/tests/test_policy_cli.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/tests/test_provider_adapters.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/tests/test_provider_cli.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/tests/test_provider_keys.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/tests/test_provider_probes.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/tests/test_rate_limiter.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/tests/test_real_site_regressions.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/tests/test_rendering.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/tests/test_save_ndjson.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/tests/test_save_sqlite.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/tests/test_security_hardening.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/tests/test_special_cases.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/tests/test_surface_contract.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/tests/test_time_utils.py +0 -0
- {docpull-5.0.0 → docpull-5.0.1}/tests/test_trafilatura_extractor.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: docpull
|
|
3
|
-
Version: 5.0.
|
|
3
|
+
Version: 5.0.1
|
|
4
4
|
Summary: Convert public web pages into clean Markdown for AI agents and RAG
|
|
5
5
|
Author-email: Zachary Roth <support@raintree.technology>
|
|
6
6
|
Maintainer-email: Raintree Technology <support@raintree.technology>
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "docpull"
|
|
7
|
-
version = "5.0.
|
|
7
|
+
version = "5.0.1"
|
|
8
8
|
dynamic = []
|
|
9
9
|
description = "Convert public web pages into clean Markdown for AI agents and RAG"
|
|
10
10
|
readme = {file = "README.md", content-type = "text/markdown"}
|
|
@@ -48,6 +48,7 @@ from __future__ import annotations
|
|
|
48
48
|
|
|
49
49
|
import argparse
|
|
50
50
|
import asyncio
|
|
51
|
+
import json
|
|
51
52
|
import logging
|
|
52
53
|
import sys
|
|
53
54
|
from collections.abc import Awaitable, Callable
|
|
@@ -55,9 +56,14 @@ from pathlib import Path
|
|
|
55
56
|
from typing import Any, Literal, cast
|
|
56
57
|
|
|
57
58
|
from ..accounting import (
|
|
59
|
+
ACCOUNTING_ARTIFACT,
|
|
60
|
+
RunAccounting,
|
|
61
|
+
blocked_action,
|
|
58
62
|
budget_block_payload,
|
|
63
|
+
default_route_steps,
|
|
59
64
|
effective_budget_limit,
|
|
60
|
-
|
|
65
|
+
maybe_write_run_accounting,
|
|
66
|
+
paid_action_blocked,
|
|
61
67
|
)
|
|
62
68
|
from ..discovery import (
|
|
63
69
|
CandidateSourceRecord,
|
|
@@ -136,6 +142,42 @@ SERVER_INSTRUCTIONS = (
|
|
|
136
142
|
# Tools that return free-form Markdown (fetch_url) intentionally omit a
|
|
137
143
|
# schema; the rest expose structured payloads alongside the rendered text.
|
|
138
144
|
|
|
145
|
+
_ACCOUNTING_OUTPUT_SCHEMA = {
|
|
146
|
+
"type": "object",
|
|
147
|
+
"description": (
|
|
148
|
+
"Non-secret run accounting receipt. Mirrors run.accounting.json when an artifact was written."
|
|
149
|
+
),
|
|
150
|
+
"properties": {
|
|
151
|
+
"schema_version": {"type": "integer"},
|
|
152
|
+
"generated_at": {"type": "string"},
|
|
153
|
+
"budget_limit_usd": {"type": ["number", "null"]},
|
|
154
|
+
"estimated_paid_cost_usd": {"type": "number"},
|
|
155
|
+
"actual_paid_cost_usd": {"type": ["number", "null"]},
|
|
156
|
+
"paid_request_count": {"type": "integer"},
|
|
157
|
+
"local_browser_seconds": {"type": "number"},
|
|
158
|
+
"http_request_count": {"type": "integer"},
|
|
159
|
+
"cache_hit_count": {"type": "integer"},
|
|
160
|
+
"blocked_actions": {"type": "array", "items": {"type": "object"}},
|
|
161
|
+
"route_steps": {"type": "array", "items": {"type": "object"}},
|
|
162
|
+
"command": {"type": "string"},
|
|
163
|
+
"metadata": {"type": "object"},
|
|
164
|
+
"artifact_path": {"type": "string"},
|
|
165
|
+
},
|
|
166
|
+
"required": [
|
|
167
|
+
"schema_version",
|
|
168
|
+
"generated_at",
|
|
169
|
+
"budget_limit_usd",
|
|
170
|
+
"estimated_paid_cost_usd",
|
|
171
|
+
"actual_paid_cost_usd",
|
|
172
|
+
"paid_request_count",
|
|
173
|
+
"local_browser_seconds",
|
|
174
|
+
"http_request_count",
|
|
175
|
+
"cache_hit_count",
|
|
176
|
+
"blocked_actions",
|
|
177
|
+
"route_steps",
|
|
178
|
+
],
|
|
179
|
+
}
|
|
180
|
+
|
|
139
181
|
_LIST_SOURCES_OUTPUT_SCHEMA = {
|
|
140
182
|
"type": "object",
|
|
141
183
|
"properties": {
|
|
@@ -274,6 +316,7 @@ _PARALLEL_PACK_OUTPUT_SCHEMA = {
|
|
|
274
316
|
"output_dir": {"type": "string"},
|
|
275
317
|
"dry_run": {"type": "boolean"},
|
|
276
318
|
"estimated_cost_usd": {"type": "number"},
|
|
319
|
+
"accounting": _ACCOUNTING_OUTPUT_SCHEMA,
|
|
277
320
|
},
|
|
278
321
|
"required": ["workflow", "output_dir"],
|
|
279
322
|
}
|
|
@@ -470,6 +513,7 @@ _ANSWER_PACK_OUTPUT_SCHEMA = {
|
|
|
470
513
|
"search": {"type": "object"},
|
|
471
514
|
"brief": {"type": "object"},
|
|
472
515
|
"artifacts": {"type": "object"},
|
|
516
|
+
"accounting": _ACCOUNTING_OUTPUT_SCHEMA,
|
|
473
517
|
},
|
|
474
518
|
"required": ["question", "answer", "search", "artifacts"],
|
|
475
519
|
}
|
|
@@ -494,8 +538,14 @@ _RENDER_URL_OUTPUT_SCHEMA = {
|
|
|
494
538
|
"sidecar_path": {"type": "string"},
|
|
495
539
|
"html_bytes": {"type": "integer"},
|
|
496
540
|
"html_sha256": {"type": "string"},
|
|
541
|
+
"output_dir": {"type": "string"},
|
|
542
|
+
"dry_run": {"type": "boolean"},
|
|
543
|
+
"blocked_by_budget": {"type": "boolean"},
|
|
544
|
+
"budget_limit_usd": {"type": ["number", "null"]},
|
|
545
|
+
"blocked_action": {"type": "object"},
|
|
546
|
+
"accounting": _ACCOUNTING_OUTPUT_SCHEMA,
|
|
497
547
|
},
|
|
498
|
-
"required": ["url", "backend"
|
|
548
|
+
"required": ["url", "backend"],
|
|
499
549
|
}
|
|
500
550
|
|
|
501
551
|
_DISCOVER_SOURCES_OUTPUT_SCHEMA = {
|
|
@@ -615,6 +665,43 @@ def _policy_arg(arguments: dict[str, Any]) -> PolicyConfig:
|
|
|
615
665
|
return PolicyConfig.from_file(policy_path) if policy_path else PolicyConfig()
|
|
616
666
|
|
|
617
667
|
|
|
668
|
+
def _read_accounting_payload(path: Path) -> dict[str, Any] | None:
|
|
669
|
+
try:
|
|
670
|
+
payload = json.loads(path.read_text(encoding="utf-8"))
|
|
671
|
+
except (OSError, json.JSONDecodeError):
|
|
672
|
+
return None
|
|
673
|
+
if not isinstance(payload, dict):
|
|
674
|
+
return None
|
|
675
|
+
payload["artifact_path"] = str(path)
|
|
676
|
+
return payload
|
|
677
|
+
|
|
678
|
+
|
|
679
|
+
def _mcp_accounting_payload(
|
|
680
|
+
accounting: RunAccounting,
|
|
681
|
+
*,
|
|
682
|
+
output_dir: Path | None = None,
|
|
683
|
+
budget_limit_usd: float | None,
|
|
684
|
+
paid_capable: bool,
|
|
685
|
+
) -> dict[str, Any]:
|
|
686
|
+
path = None
|
|
687
|
+
if output_dir is not None:
|
|
688
|
+
path = maybe_write_run_accounting(
|
|
689
|
+
output_dir,
|
|
690
|
+
budget_limit_usd=budget_limit_usd,
|
|
691
|
+
paid_capable=paid_capable,
|
|
692
|
+
accounting=accounting,
|
|
693
|
+
)
|
|
694
|
+
if path is not None:
|
|
695
|
+
artifact_payload = _read_accounting_payload(path)
|
|
696
|
+
if artifact_payload is not None:
|
|
697
|
+
return artifact_payload
|
|
698
|
+
return accounting.to_dict()
|
|
699
|
+
|
|
700
|
+
|
|
701
|
+
def _pack_accounting_payload(pack_dir: Path) -> dict[str, Any] | None:
|
|
702
|
+
return _read_accounting_payload(pack_dir / ACCOUNTING_ARTIFACT)
|
|
703
|
+
|
|
704
|
+
|
|
618
705
|
async def _dispatch_tool(
|
|
619
706
|
name: str,
|
|
620
707
|
arguments: dict[str, Any],
|
|
@@ -669,6 +756,26 @@ async def _dispatch_tool(
|
|
|
669
756
|
cloud_agent_browser_binary = arguments.get("cloud_agent_browser_binary", "agent-browser")
|
|
670
757
|
if not isinstance(cloud_agent_browser_binary, str):
|
|
671
758
|
raise ValueError("'cloud_agent_browser_binary' must be a string")
|
|
759
|
+
output_dir = _path_arg(arguments, "output_dir", "rendered")
|
|
760
|
+
estimated = 0.0
|
|
761
|
+
paid_capable_render = backend in {"vercel-sandbox", "e2b-sandbox"}
|
|
762
|
+
accounting = RunAccounting(
|
|
763
|
+
budget_limit_usd=budget_limit,
|
|
764
|
+
estimated_paid_cost_usd=estimated,
|
|
765
|
+
route_steps=default_route_steps(
|
|
766
|
+
include_local_render=True,
|
|
767
|
+
include_cloud=paid_capable_render,
|
|
768
|
+
budget_limit_usd=budget_limit,
|
|
769
|
+
),
|
|
770
|
+
command="mcp render_url",
|
|
771
|
+
metadata={
|
|
772
|
+
"mcp_tool": "render_url",
|
|
773
|
+
"url": url,
|
|
774
|
+
"runtime": runtime,
|
|
775
|
+
"backend": backend,
|
|
776
|
+
},
|
|
777
|
+
)
|
|
778
|
+
render_blocked = False
|
|
672
779
|
if backend in {"vercel-sandbox", "e2b-sandbox"}:
|
|
673
780
|
from ..models.config import RenderConfig
|
|
674
781
|
from ..rendering import estimate_cloud_render_cost_usd
|
|
@@ -682,40 +789,77 @@ async def _dispatch_tool(
|
|
|
682
789
|
timeout_seconds=float(timeout_arg),
|
|
683
790
|
),
|
|
684
791
|
)
|
|
685
|
-
|
|
686
|
-
|
|
792
|
+
accounting.estimated_paid_cost_usd = estimated
|
|
793
|
+
if paid_action_blocked(budget_limit, estimated_cost_usd=estimated):
|
|
794
|
+
render_blocked = True
|
|
795
|
+
accounting.blocked_actions.append(
|
|
796
|
+
blocked_action(
|
|
797
|
+
f"render:{backend}",
|
|
798
|
+
budget_limit_usd=budget_limit,
|
|
799
|
+
estimated_cost_usd=estimated,
|
|
800
|
+
provider=backend,
|
|
801
|
+
)
|
|
802
|
+
)
|
|
803
|
+
accounting_payload = _mcp_accounting_payload(
|
|
804
|
+
accounting,
|
|
805
|
+
output_dir=output_dir,
|
|
806
|
+
budget_limit_usd=budget_limit,
|
|
807
|
+
paid_capable=True,
|
|
808
|
+
)
|
|
809
|
+
result = ToolResult(
|
|
810
|
+
f"Render blocked by budget before {backend}.",
|
|
811
|
+
data={
|
|
812
|
+
"url": url,
|
|
813
|
+
"backend": backend,
|
|
814
|
+
"output_dir": str(output_dir),
|
|
815
|
+
"dry_run": False,
|
|
816
|
+
**budget_block_payload(
|
|
817
|
+
f"render:{backend}",
|
|
818
|
+
budget_limit_usd=budget_limit,
|
|
819
|
+
estimated_cost_usd=estimated,
|
|
820
|
+
provider=backend,
|
|
821
|
+
),
|
|
822
|
+
"accounting": accounting_payload,
|
|
823
|
+
},
|
|
824
|
+
)
|
|
825
|
+
else:
|
|
826
|
+
accounting.paid_request_count = 1
|
|
827
|
+
if not render_blocked:
|
|
828
|
+
artifact = await render_url_to_directory(
|
|
829
|
+
url,
|
|
830
|
+
output_dir,
|
|
831
|
+
config={
|
|
832
|
+
"mode": "agent-browser",
|
|
833
|
+
"backend": backend,
|
|
834
|
+
"allowed_domains": _string_list_arg(arguments, "allowed_domains"),
|
|
835
|
+
"timeout_seconds": float(timeout_arg),
|
|
836
|
+
"wait_for": wait_for,
|
|
837
|
+
"cloud_agent_browser_install": cloud_agent_browser_install,
|
|
838
|
+
"cloud_result_transport": cloud_result_transport,
|
|
839
|
+
"cloud_max_estimated_cost_usd": cloud_max_estimated_cost,
|
|
840
|
+
"cloud_agent_browser_binary": cloud_agent_browser_binary,
|
|
841
|
+
"e2b_template": template,
|
|
842
|
+
},
|
|
843
|
+
)
|
|
844
|
+
accounting_payload = _mcp_accounting_payload(
|
|
845
|
+
accounting,
|
|
846
|
+
output_dir=output_dir,
|
|
687
847
|
budget_limit_usd=budget_limit,
|
|
688
|
-
|
|
689
|
-
|
|
848
|
+
paid_capable=paid_capable_render,
|
|
849
|
+
)
|
|
850
|
+
payload = {
|
|
851
|
+
"url": artifact.page.url,
|
|
852
|
+
"backend": artifact.page.backend,
|
|
853
|
+
"html_path": str(artifact.html_path),
|
|
854
|
+
"sidecar_path": str(artifact.sidecar_path),
|
|
855
|
+
"html_bytes": artifact.page.html_bytes,
|
|
856
|
+
"html_sha256": artifact.page.html_sha256,
|
|
857
|
+
"accounting": accounting_payload,
|
|
858
|
+
}
|
|
859
|
+
result = ToolResult(
|
|
860
|
+
f"Rendered {artifact.page.html_bytes} bytes: {artifact.html_path}",
|
|
861
|
+
data=payload,
|
|
690
862
|
)
|
|
691
|
-
artifact = await render_url_to_directory(
|
|
692
|
-
url,
|
|
693
|
-
_path_arg(arguments, "output_dir", "rendered"),
|
|
694
|
-
config={
|
|
695
|
-
"mode": "agent-browser",
|
|
696
|
-
"backend": backend,
|
|
697
|
-
"allowed_domains": _string_list_arg(arguments, "allowed_domains"),
|
|
698
|
-
"timeout_seconds": float(timeout_arg),
|
|
699
|
-
"wait_for": wait_for,
|
|
700
|
-
"cloud_agent_browser_install": cloud_agent_browser_install,
|
|
701
|
-
"cloud_result_transport": cloud_result_transport,
|
|
702
|
-
"cloud_max_estimated_cost_usd": cloud_max_estimated_cost,
|
|
703
|
-
"cloud_agent_browser_binary": cloud_agent_browser_binary,
|
|
704
|
-
"e2b_template": template,
|
|
705
|
-
},
|
|
706
|
-
)
|
|
707
|
-
payload = {
|
|
708
|
-
"url": artifact.page.url,
|
|
709
|
-
"backend": artifact.page.backend,
|
|
710
|
-
"html_path": str(artifact.html_path),
|
|
711
|
-
"sidecar_path": str(artifact.sidecar_path),
|
|
712
|
-
"html_bytes": artifact.page.html_bytes,
|
|
713
|
-
"html_sha256": artifact.page.html_sha256,
|
|
714
|
-
}
|
|
715
|
-
result = ToolResult(
|
|
716
|
-
f"Rendered {artifact.page.html_bytes} bytes: {artifact.html_path}",
|
|
717
|
-
data=payload,
|
|
718
|
-
)
|
|
719
863
|
|
|
720
864
|
elif name == "ensure_docs":
|
|
721
865
|
source = _require_str(arguments, "source")
|
|
@@ -794,6 +938,7 @@ async def _dispatch_tool(
|
|
|
794
938
|
max_estimated_cost,
|
|
795
939
|
float(budget_arg) if budget_arg is not None else None,
|
|
796
940
|
)
|
|
941
|
+
output_dir = _path_arg(arguments, "output_dir", "packs/parallel-context-pack")
|
|
797
942
|
request_options = _build_request_options(
|
|
798
943
|
source_policy=source_policy,
|
|
799
944
|
fetch_policy=fetch_policy,
|
|
@@ -808,6 +953,32 @@ async def _dispatch_tool(
|
|
|
808
953
|
else None,
|
|
809
954
|
full_content=True,
|
|
810
955
|
)
|
|
956
|
+
blocked_by_budget = paid_action_blocked(budget_limit, estimated_cost_usd=estimated_cost)
|
|
957
|
+
accounting = RunAccounting(
|
|
958
|
+
budget_limit_usd=budget_limit,
|
|
959
|
+
estimated_paid_cost_usd=estimated_cost,
|
|
960
|
+
route_steps=default_route_steps(
|
|
961
|
+
include_provider=True,
|
|
962
|
+
budget_limit_usd=budget_limit,
|
|
963
|
+
),
|
|
964
|
+
command="mcp parallel_context_pack",
|
|
965
|
+
metadata={
|
|
966
|
+
"mcp_tool": "parallel_context_pack",
|
|
967
|
+
"objective": objective,
|
|
968
|
+
"query_count": len(queries),
|
|
969
|
+
"extract_limit": extract_limit,
|
|
970
|
+
"max_search_results": max_search_results,
|
|
971
|
+
},
|
|
972
|
+
)
|
|
973
|
+
if blocked_by_budget:
|
|
974
|
+
accounting.blocked_actions.append(
|
|
975
|
+
blocked_action(
|
|
976
|
+
"parallel:context-pack",
|
|
977
|
+
budget_limit_usd=budget_limit,
|
|
978
|
+
estimated_cost_usd=estimated_cost,
|
|
979
|
+
provider="parallel",
|
|
980
|
+
)
|
|
981
|
+
)
|
|
811
982
|
if bool(arguments.get("dry_run", False)):
|
|
812
983
|
blocked = (
|
|
813
984
|
budget_block_payload(
|
|
@@ -816,60 +987,95 @@ async def _dispatch_tool(
|
|
|
816
987
|
estimated_cost_usd=estimated_cost,
|
|
817
988
|
provider="parallel",
|
|
818
989
|
)
|
|
819
|
-
if
|
|
990
|
+
if blocked_by_budget
|
|
820
991
|
else {}
|
|
821
992
|
)
|
|
993
|
+
accounting_payload = _mcp_accounting_payload(
|
|
994
|
+
accounting,
|
|
995
|
+
output_dir=None,
|
|
996
|
+
budget_limit_usd=budget_limit,
|
|
997
|
+
paid_capable=True,
|
|
998
|
+
)
|
|
822
999
|
result = ToolResult(
|
|
823
1000
|
"Parallel context pack dry run.",
|
|
824
1001
|
data={
|
|
825
1002
|
"workflow": "context-pack",
|
|
826
|
-
"output_dir": str(
|
|
1003
|
+
"output_dir": str(output_dir),
|
|
827
1004
|
"dry_run": True,
|
|
828
1005
|
"estimated_cost_usd": estimated_cost,
|
|
829
1006
|
"budget_limit_usd": budget_limit,
|
|
830
1007
|
"request_options": request_options,
|
|
831
1008
|
**blocked,
|
|
1009
|
+
"accounting": accounting_payload,
|
|
832
1010
|
},
|
|
833
1011
|
)
|
|
834
1012
|
else:
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
1013
|
+
if blocked_by_budget:
|
|
1014
|
+
accounting_payload = _mcp_accounting_payload(
|
|
1015
|
+
accounting,
|
|
1016
|
+
output_dir=output_dir,
|
|
1017
|
+
budget_limit_usd=budget_limit,
|
|
1018
|
+
paid_capable=True,
|
|
1019
|
+
)
|
|
1020
|
+
result = ToolResult(
|
|
1021
|
+
"Parallel context pack blocked by budget before provider call.",
|
|
1022
|
+
data={
|
|
1023
|
+
"workflow": "context-pack",
|
|
1024
|
+
"output_dir": str(output_dir),
|
|
1025
|
+
"dry_run": False,
|
|
1026
|
+
"estimated_cost_usd": estimated_cost,
|
|
1027
|
+
"budget_limit_usd": budget_limit,
|
|
1028
|
+
"request_options": request_options,
|
|
1029
|
+
**budget_block_payload(
|
|
1030
|
+
"parallel:context-pack",
|
|
1031
|
+
budget_limit_usd=budget_limit,
|
|
1032
|
+
estimated_cost_usd=estimated_cost,
|
|
1033
|
+
provider="parallel",
|
|
1034
|
+
),
|
|
1035
|
+
"accounting": accounting_payload,
|
|
1036
|
+
},
|
|
1037
|
+
)
|
|
1038
|
+
elif estimated_cost > max_estimated_cost:
|
|
842
1039
|
raise ValueError(
|
|
843
1040
|
f"Estimated Parallel cost ${estimated_cost:.3f} exceeds max_estimated_cost "
|
|
844
1041
|
f"${max_estimated_cost:.3f}"
|
|
845
1042
|
)
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
1043
|
+
else:
|
|
1044
|
+
mode_arg = arguments.get("mode")
|
|
1045
|
+
mode = mode_arg if isinstance(mode_arg, str) else "advanced"
|
|
1046
|
+
accounting.paid_request_count = 1
|
|
1047
|
+
pack_path = await asyncio.to_thread(
|
|
1048
|
+
run_live_context_pack,
|
|
1049
|
+
objective=objective,
|
|
1050
|
+
queries=queries,
|
|
1051
|
+
output_dir=output_dir,
|
|
1052
|
+
mode=mode,
|
|
1053
|
+
extract_limit=extract_limit,
|
|
1054
|
+
max_tokens_per_file=DEFAULT_MAX_TOKENS,
|
|
1055
|
+
source_policy=source_policy,
|
|
1056
|
+
max_search_results=max_search_results,
|
|
1057
|
+
client_model=arguments.get("client_model")
|
|
1058
|
+
if isinstance(arguments.get("client_model"), str)
|
|
1059
|
+
else None,
|
|
1060
|
+
estimated_cost_usd=estimated_cost,
|
|
1061
|
+
)
|
|
1062
|
+
accounting.metadata["output_dir"] = str(pack_path)
|
|
1063
|
+
accounting_payload = _mcp_accounting_payload(
|
|
1064
|
+
accounting,
|
|
1065
|
+
output_dir=pack_path,
|
|
1066
|
+
budget_limit_usd=budget_limit,
|
|
1067
|
+
paid_capable=True,
|
|
1068
|
+
)
|
|
1069
|
+
result = ToolResult(
|
|
1070
|
+
f"Wrote Parallel context pack: {pack_path}",
|
|
1071
|
+
data={
|
|
1072
|
+
"workflow": "context-pack",
|
|
1073
|
+
"output_dir": str(pack_path),
|
|
1074
|
+
"dry_run": False,
|
|
1075
|
+
"estimated_cost_usd": estimated_cost,
|
|
1076
|
+
"accounting": accounting_payload,
|
|
1077
|
+
},
|
|
1078
|
+
)
|
|
873
1079
|
|
|
874
1080
|
elif name == "parallel_api_pack":
|
|
875
1081
|
source = _require_str(arguments, "source")
|
|
@@ -1135,13 +1341,17 @@ async def _dispatch_tool(
|
|
|
1135
1341
|
)
|
|
1136
1342
|
|
|
1137
1343
|
elif name == "answer_pack":
|
|
1344
|
+
answer_pack_dir = _path_arg(arguments, "pack_dir")
|
|
1138
1345
|
answer_payload: dict[str, Any] = await asyncio.to_thread(
|
|
1139
1346
|
answer_pack,
|
|
1140
|
-
|
|
1347
|
+
answer_pack_dir,
|
|
1141
1348
|
_require_str(arguments, "question"),
|
|
1142
1349
|
required_domains=_string_list_arg(arguments, "required_domains"),
|
|
1143
1350
|
limit=_coerce_int(arguments.get("limit"), name="limit", default=8),
|
|
1144
1351
|
)
|
|
1352
|
+
existing_accounting = _pack_accounting_payload(answer_pack_dir)
|
|
1353
|
+
if existing_accounting is not None:
|
|
1354
|
+
answer_payload["accounting"] = existing_accounting
|
|
1145
1355
|
answer_raw = answer_payload.get("answer")
|
|
1146
1356
|
answer_data: dict[str, Any] = answer_raw if isinstance(answer_raw, dict) else {}
|
|
1147
1357
|
result = ToolResult(
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: docpull
|
|
3
|
-
Version: 5.0.
|
|
3
|
+
Version: 5.0.1
|
|
4
4
|
Summary: Convert public web pages into clean Markdown for AI agents and RAG
|
|
5
5
|
Author-email: Zachary Roth <support@raintree.technology>
|
|
6
6
|
Maintainer-email: Raintree Technology <support@raintree.technology>
|
|
@@ -17,6 +17,7 @@ pytest.importorskip("mcp.client.stdio")
|
|
|
17
17
|
|
|
18
18
|
from mcp.client.stdio import stdio_client
|
|
19
19
|
|
|
20
|
+
from docpull.accounting import RunAccounting, write_run_accounting
|
|
20
21
|
from docpull.mcp import server as mcp_server
|
|
21
22
|
from mcp import ClientSession, StdioServerParameters
|
|
22
23
|
from tests.pack_fixtures import write_context_pack
|
|
@@ -155,6 +156,38 @@ async def test_mcp_dispatch_tool_handles_success_and_validation_errors(tmp_path)
|
|
|
155
156
|
assert blocked_parallel.is_error is False
|
|
156
157
|
assert blocked_parallel.data is not None
|
|
157
158
|
assert blocked_parallel.data["blocked_by_budget"] is True
|
|
159
|
+
assert blocked_parallel.data["accounting"]["budget_limit_usd"] == 0
|
|
160
|
+
assert blocked_parallel.data["accounting"]["blocked_actions"][0]["provider"] == "parallel"
|
|
161
|
+
|
|
162
|
+
blocked_live_parallel = await mcp_server._dispatch_tool(
|
|
163
|
+
"parallel_context_pack",
|
|
164
|
+
{
|
|
165
|
+
"objective": "Parallel docs",
|
|
166
|
+
"budget": 0,
|
|
167
|
+
"output_dir": str(tmp_path / "blocked-parallel"),
|
|
168
|
+
},
|
|
169
|
+
)
|
|
170
|
+
assert blocked_live_parallel.is_error is False
|
|
171
|
+
assert blocked_live_parallel.data is not None
|
|
172
|
+
assert blocked_live_parallel.data["blocked_by_budget"] is True
|
|
173
|
+
parallel_accounting_path = tmp_path / "blocked-parallel" / "run.accounting.json"
|
|
174
|
+
assert blocked_live_parallel.data["accounting"]["artifact_path"] == str(parallel_accounting_path)
|
|
175
|
+
assert parallel_accounting_path.exists()
|
|
176
|
+
|
|
177
|
+
blocked_cloud_render = await mcp_server._dispatch_tool(
|
|
178
|
+
"render_url",
|
|
179
|
+
{
|
|
180
|
+
"url": "https://example.com",
|
|
181
|
+
"runtime": "vercel",
|
|
182
|
+
"budget": 0,
|
|
183
|
+
"output_dir": str(tmp_path / "blocked-render"),
|
|
184
|
+
},
|
|
185
|
+
)
|
|
186
|
+
assert blocked_cloud_render.is_error is False
|
|
187
|
+
assert blocked_cloud_render.data is not None
|
|
188
|
+
assert blocked_cloud_render.data["blocked_by_budget"] is True
|
|
189
|
+
assert blocked_cloud_render.data["accounting"]["blocked_actions"][0]["provider"] == "vercel-sandbox"
|
|
190
|
+
assert (tmp_path / "blocked-render" / "run.accounting.json").exists()
|
|
158
191
|
|
|
159
192
|
unknown = await mcp_server._dispatch_tool("not_a_tool", {})
|
|
160
193
|
assert unknown.is_error is True
|
|
@@ -173,6 +206,22 @@ async def test_mcp_dispatch_tool_handles_success_and_validation_errors(tmp_path)
|
|
|
173
206
|
assert research.data["workflow"] == "research-pack"
|
|
174
207
|
assert (tmp_path / "research" / "research.result.json").exists()
|
|
175
208
|
|
|
209
|
+
write_run_accounting(
|
|
210
|
+
pack_dir,
|
|
211
|
+
RunAccounting(
|
|
212
|
+
budget_limit_usd=0,
|
|
213
|
+
command="test context-pack",
|
|
214
|
+
),
|
|
215
|
+
)
|
|
216
|
+
answer = await mcp_server._dispatch_tool(
|
|
217
|
+
"answer_pack",
|
|
218
|
+
{"pack_dir": str(pack_dir), "question": "What does Parallel Search return?"},
|
|
219
|
+
)
|
|
220
|
+
assert answer.is_error is False
|
|
221
|
+
assert answer.data is not None
|
|
222
|
+
assert answer.data["accounting"]["budget_limit_usd"] == 0
|
|
223
|
+
assert answer.data["accounting"]["artifact_path"] == str(pack_dir / "run.accounting.json")
|
|
224
|
+
|
|
176
225
|
|
|
177
226
|
@pytest.mark.asyncio
|
|
178
227
|
async def test_stdio_server_lists_and_calls_tools(tmp_path):
|
|
@@ -269,6 +318,22 @@ async def test_stdio_server_lists_and_calls_tools(tmp_path):
|
|
|
269
318
|
assert dry_run.structuredContent is not None
|
|
270
319
|
assert dry_run.structuredContent["dry_run"] is True
|
|
271
320
|
|
|
321
|
+
blocked_render = await session.call_tool(
|
|
322
|
+
"render_url",
|
|
323
|
+
{
|
|
324
|
+
"url": "https://example.com",
|
|
325
|
+
"runtime": "vercel",
|
|
326
|
+
"budget": 0,
|
|
327
|
+
"output_dir": str(tmp_path / "stdio-blocked-render"),
|
|
328
|
+
},
|
|
329
|
+
)
|
|
330
|
+
assert blocked_render.isError is False
|
|
331
|
+
assert blocked_render.structuredContent is not None
|
|
332
|
+
assert blocked_render.structuredContent["blocked_by_budget"] is True
|
|
333
|
+
assert blocked_render.structuredContent["accounting"]["blocked_actions"][0]["provider"] == (
|
|
334
|
+
"vercel-sandbox"
|
|
335
|
+
)
|
|
336
|
+
|
|
272
337
|
prepared = await session.call_tool(
|
|
273
338
|
"pack_prepare",
|
|
274
339
|
{
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|