diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 3f63a67..7f3f5c8 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.22.1" + ".": "0.23.0" } \ No newline at end of file diff --git a/.stats.yml b/.stats.yml index f09aad2..d7f028a 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ -configured_endpoints: 22 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/context-dev/context.dev-c4e3d32c831ceda41c21a08b227addc6969457f9b2824f5afb4297d81ce6b379.yml -openapi_spec_hash: 8e825842065c24f11fd3f51fdd705337 -config_hash: 70354330f92ce169beabc98696ebb9a3 +configured_endpoints: 23 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/context-dev/context.dev-24aa0941b29bf383cfb7dfb8cafd75999346bffb07b33738a89c3c9bbeb798a9.yml +openapi_spec_hash: 5530a1589a20c8c337e7f6ebaaa0aecb +config_hash: f481fca4de4b1657023180d4fc47ef22 diff --git a/CHANGELOG.md b/CHANGELOG.md index 3e6482b..5288f36 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## 0.23.0 (2026-05-16) + +Full Changelog: [v0.22.1...v0.23.0](https://github.com/context-dot-dev/context-python-sdk/compare/v0.22.1...v0.23.0) + +### Features + +* **api:** manual updates ([bce4c91](https://github.com/context-dot-dev/context-python-sdk/commit/bce4c91688d28054c361ee403be2333229a33f41)) + ## 0.22.1 (2026-05-13) Full Changelog: [v0.22.0...v0.22.1](https://github.com/context-dot-dev/context-python-sdk/compare/v0.22.0...v0.22.1) diff --git a/api.md b/api.md index 3dd9e9a..04e3161 100644 --- a/api.md +++ b/api.md @@ -7,6 +7,7 @@ from context.dev.types import ( WebExtractFontsResponse, WebExtractStyleguideResponse, WebScreenshotResponse, + WebSearchResponse, WebWebCrawlMdResponse, WebWebScrapeHTMLResponse, WebWebScrapeImagesResponse, @@ -20,6 +21,7 @@ Methods: - client.web.extract_fonts(\*\*params) -> WebExtractFontsResponse - client.web.extract_styleguide(\*\*params) -> WebExtractStyleguideResponse - client.web.screenshot(\*\*params) -> WebScreenshotResponse +- client.web.search(\*\*params) -> WebSearchResponse - client.web.web_crawl_md(\*\*params) -> WebWebCrawlMdResponse - client.web.web_scrape_html(\*\*params) -> WebWebScrapeHTMLResponse - client.web.web_scrape_images(\*\*params) -> WebWebScrapeImagesResponse diff --git a/pyproject.toml b/pyproject.toml index ee2a59f..a714cf7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "context.dev" -version = "0.22.1" +version = "0.23.0" description = "The official Python library for the context.dev API" dynamic = ["readme"] license = "Apache-2.0" diff --git a/src/context/dev/_version.py b/src/context/dev/_version.py index bc205aa..6992694 100644 --- a/src/context/dev/_version.py +++ b/src/context/dev/_version.py @@ -1,4 +1,4 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. __title__ = "context.dev" -__version__ = "0.22.1" # x-release-please-version +__version__ = "0.23.0" # x-release-please-version diff --git a/src/context/dev/resources/web.py b/src/context/dev/resources/web.py index 61b54bb..c8b66f6 100644 --- a/src/context/dev/resources/web.py +++ b/src/context/dev/resources/web.py @@ -7,6 +7,7 @@ import httpx from ..types import ( + web_search_params, web_screenshot_params, web_web_crawl_md_params, web_extract_fonts_params, @@ -16,7 +17,7 @@ web_extract_styleguide_params, web_web_scrape_sitemap_params, ) -from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given +from .._types import Body, Omit, Query, Headers, NotGiven, SequenceNotStr, omit, not_given from .._utils import maybe_transform, async_maybe_transform from .._compat import cached_property from .._resource import SyncAPIResource, AsyncAPIResource @@ -27,6 +28,7 @@ async_to_streamed_response_wrapper, ) from .._base_client import make_request_options +from ..types.web_search_response import WebSearchResponse from ..types.web_screenshot_response import WebScreenshotResponse from ..types.web_web_crawl_md_response import WebWebCrawlMdResponse from ..types.web_extract_fonts_response import WebExtractFontsResponse @@ -266,6 +268,73 @@ def screenshot( cast_to=WebScreenshotResponse, ) + def search( + self, + *, + query: str, + exclude_domains: SequenceNotStr[str] | Omit = omit, + freshness: Literal["last_24_hours", "last_week", "last_month", "last_year"] | Omit = omit, + include_domains: SequenceNotStr[str] | Omit = omit, + markdown_options: web_search_params.MarkdownOptions | Omit = omit, + query_fanout: bool | Omit = omit, + timeout_ms: int | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> WebSearchResponse: + """ + Search the web and optionally scrape each result to Markdown in one round-trip. + + Args: + query: Natural-language search query. + + exclude_domains: Blocklist — drop results from these domains. Example: ["pinterest.com", + "reddit.com"]. + + freshness: Restrict results to content published within this window. + + include_domains: Allowlist — only return results from these domains. Example: ["arxiv.org", + "github.com"]. + + markdown_options: Inline Markdown scraping for each result. Set `enabled: true` to activate. + + query_fanout: Expand the query into multiple parallel variants for broader recall. + + timeout_ms: Optional timeout in milliseconds for the request. If the request takes longer + than this value, it will be aborted with a 408 status code. Maximum allowed + value is 300000ms (5 minutes). + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._post( + "/web/search", + body=maybe_transform( + { + "query": query, + "exclude_domains": exclude_domains, + "freshness": freshness, + "include_domains": include_domains, + "markdown_options": markdown_options, + "query_fanout": query_fanout, + "timeout_ms": timeout_ms, + }, + web_search_params.WebSearchParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=WebSearchResponse, + ) + def web_crawl_md( self, *, @@ -882,6 +951,73 @@ async def screenshot( cast_to=WebScreenshotResponse, ) + async def search( + self, + *, + query: str, + exclude_domains: SequenceNotStr[str] | Omit = omit, + freshness: Literal["last_24_hours", "last_week", "last_month", "last_year"] | Omit = omit, + include_domains: SequenceNotStr[str] | Omit = omit, + markdown_options: web_search_params.MarkdownOptions | Omit = omit, + query_fanout: bool | Omit = omit, + timeout_ms: int | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> WebSearchResponse: + """ + Search the web and optionally scrape each result to Markdown in one round-trip. + + Args: + query: Natural-language search query. + + exclude_domains: Blocklist — drop results from these domains. Example: ["pinterest.com", + "reddit.com"]. + + freshness: Restrict results to content published within this window. + + include_domains: Allowlist — only return results from these domains. Example: ["arxiv.org", + "github.com"]. + + markdown_options: Inline Markdown scraping for each result. Set `enabled: true` to activate. + + query_fanout: Expand the query into multiple parallel variants for broader recall. + + timeout_ms: Optional timeout in milliseconds for the request. If the request takes longer + than this value, it will be aborted with a 408 status code. Maximum allowed + value is 300000ms (5 minutes). + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return await self._post( + "/web/search", + body=await async_maybe_transform( + { + "query": query, + "exclude_domains": exclude_domains, + "freshness": freshness, + "include_domains": include_domains, + "markdown_options": markdown_options, + "query_fanout": query_fanout, + "timeout_ms": timeout_ms, + }, + web_search_params.WebSearchParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=WebSearchResponse, + ) + async def web_crawl_md( self, *, @@ -1284,6 +1420,9 @@ def __init__(self, web: WebResource) -> None: self.screenshot = to_raw_response_wrapper( web.screenshot, ) + self.search = to_raw_response_wrapper( + web.search, + ) self.web_crawl_md = to_raw_response_wrapper( web.web_crawl_md, ) @@ -1314,6 +1453,9 @@ def __init__(self, web: AsyncWebResource) -> None: self.screenshot = async_to_raw_response_wrapper( web.screenshot, ) + self.search = async_to_raw_response_wrapper( + web.search, + ) self.web_crawl_md = async_to_raw_response_wrapper( web.web_crawl_md, ) @@ -1344,6 +1486,9 @@ def __init__(self, web: WebResource) -> None: self.screenshot = to_streamed_response_wrapper( web.screenshot, ) + self.search = to_streamed_response_wrapper( + web.search, + ) self.web_crawl_md = to_streamed_response_wrapper( web.web_crawl_md, ) @@ -1374,6 +1519,9 @@ def __init__(self, web: AsyncWebResource) -> None: self.screenshot = async_to_streamed_response_wrapper( web.screenshot, ) + self.search = async_to_streamed_response_wrapper( + web.search, + ) self.web_crawl_md = async_to_streamed_response_wrapper( web.web_crawl_md, ) diff --git a/src/context/dev/types/__init__.py b/src/context/dev/types/__init__.py index 60c8ac7..8c237b0 100644 --- a/src/context/dev/types/__init__.py +++ b/src/context/dev/types/__init__.py @@ -2,7 +2,9 @@ from __future__ import annotations +from .web_search_params import WebSearchParams as WebSearchParams from .ai_ai_query_params import AIAIQueryParams as AIAIQueryParams +from .web_search_response import WebSearchResponse as WebSearchResponse from .ai_ai_query_response import AIAIQueryResponse as AIAIQueryResponse from .brand_retrieve_params import BrandRetrieveParams as BrandRetrieveParams from .web_screenshot_params import WebScreenshotParams as WebScreenshotParams diff --git a/src/context/dev/types/web_search_params.py b/src/context/dev/types/web_search_params.py new file mode 100644 index 0000000..5c3e89f --- /dev/null +++ b/src/context/dev/types/web_search_params.py @@ -0,0 +1,103 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, Required, Annotated, TypedDict + +from .._types import SequenceNotStr +from .._utils import PropertyInfo + +__all__ = ["WebSearchParams", "MarkdownOptions", "MarkdownOptionsPdf"] + + +class WebSearchParams(TypedDict, total=False): + query: Required[str] + """Natural-language search query.""" + + exclude_domains: Annotated[SequenceNotStr[str], PropertyInfo(alias="excludeDomains")] + """Blocklist — drop results from these domains. + + Example: ["pinterest.com", "reddit.com"]. + """ + + freshness: Literal["last_24_hours", "last_week", "last_month", "last_year"] + """Restrict results to content published within this window.""" + + include_domains: Annotated[SequenceNotStr[str], PropertyInfo(alias="includeDomains")] + """Allowlist — only return results from these domains. + + Example: ["arxiv.org", "github.com"]. + """ + + markdown_options: Annotated[MarkdownOptions, PropertyInfo(alias="markdownOptions")] + """Inline Markdown scraping for each result. Set `enabled: true` to activate.""" + + query_fanout: Annotated[bool, PropertyInfo(alias="queryFanout")] + """Expand the query into multiple parallel variants for broader recall.""" + + timeout_ms: Annotated[int, PropertyInfo(alias="timeoutMS")] + """Optional timeout in milliseconds for the request. + + If the request takes longer than this value, it will be aborted with a 408 + status code. Maximum allowed value is 300000ms (5 minutes). + """ + + +class MarkdownOptionsPdf(TypedDict, total=False): + """PDF handling. Use start/end to bound text extraction and OCR to a page range.""" + + end: int + """Last PDF page to parse (1-based, inclusive). + + Defaults to the final page. Must be >= start. + """ + + should_parse: Annotated[bool, PropertyInfo(alias="shouldParse")] + """Parse PDF URLs. When false, PDF results are skipped with WEBSITE_ACCESS_ERROR.""" + + start: int + """First PDF page to parse (1-based, inclusive). Defaults to page 1.""" + + +class MarkdownOptions(TypedDict, total=False): + """Inline Markdown scraping for each result. Set `enabled: true` to activate.""" + + enabled: bool + """Scrape each result to Markdown. Off by default to keep search cheap and fast.""" + + include_frames: Annotated[bool, PropertyInfo(alias="includeFrames")] + """Render iframe contents into the Markdown.""" + + include_images: Annotated[bool, PropertyInfo(alias="includeImages")] + """Emit image references in the Markdown.""" + + include_links: Annotated[bool, PropertyInfo(alias="includeLinks")] + """Keep hyperlinks in the Markdown.""" + + max_age_ms: Annotated[int, PropertyInfo(alias="maxAgeMs")] + """Cache TTL in ms for scraped Markdown keyed by URL + options. + + Default 1 day, max 30 days. Set to 0 to force a fresh scrape. + """ + + pdf: MarkdownOptionsPdf + """PDF handling. Use start/end to bound text extraction and OCR to a page range.""" + + shorten_base64_images: Annotated[bool, PropertyInfo(alias="shortenBase64Images")] + """Truncate inline base64 image payloads to keep responses small.""" + + timeout_ms: Annotated[int, PropertyInfo(alias="timeoutMS")] + """Optional timeout in milliseconds for the request. + + If the request takes longer than this value, it will be aborted with a 408 + status code. Maximum allowed value is 300000ms (5 minutes). + """ + + use_main_content_only: Annotated[bool, PropertyInfo(alias="useMainContentOnly")] + """Strip nav, header, footer, and sidebar — keep only the primary article content.""" + + wait_for_ms: Annotated[int, PropertyInfo(alias="waitForMs")] + """Extra wait after page load before rendering, in ms (0–30000). + + Useful for JS-heavy pages. + """ diff --git a/src/context/dev/types/web_search_response.py b/src/context/dev/types/web_search_response.py new file mode 100644 index 0000000..2e55d97 --- /dev/null +++ b/src/context/dev/types/web_search_response.py @@ -0,0 +1,45 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional +from typing_extensions import Literal + +from .._models import BaseModel + +__all__ = ["WebSearchResponse", "Result", "ResultMarkdown"] + + +class ResultMarkdown(BaseModel): + """Markdown scrape status and content for this result.""" + + code: Literal["SUCCESS", "NOT_REQUESTED", "TIMEOUT", "WEBSITE_ACCESS_ERROR", "ERROR"] + """Per-result scrape outcome. Inspect this before reading `markdown`.""" + + markdown: Optional[str] = None + """GFM Markdown of the page. + + Null unless markdownOptions.enabled is true and scraping succeeded. + """ + + +class Result(BaseModel): + description: str + """Snippet excerpt from the page.""" + + markdown: ResultMarkdown + """Markdown scrape status and content for this result.""" + + relevance: Literal["high", "medium", "low"] + """Model-judged relevance to the original query.""" + + title: str + """Page title.""" + + url: str + """Canonical result URL.""" + + +class WebSearchResponse(BaseModel): + query: str + """Echo of the original query (useful when fanout was enabled).""" + + results: List[Result] diff --git a/tests/api_resources/test_web.py b/tests/api_resources/test_web.py index 51f2720..ddb16a2 100644 --- a/tests/api_resources/test_web.py +++ b/tests/api_resources/test_web.py @@ -10,6 +10,7 @@ from context.dev import ContextDev, AsyncContextDev from tests.utils import assert_matches_type from context.dev.types import ( + WebSearchResponse, WebScreenshotResponse, WebWebCrawlMdResponse, WebWebScrapeMdResponse, @@ -149,6 +150,69 @@ def test_streaming_response_screenshot(self, client: ContextDev) -> None: assert cast(Any, response.is_closed) is True + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_search(self, client: ContextDev) -> None: + web = client.web.search( + query="x", + ) + assert_matches_type(WebSearchResponse, web, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_search_with_all_params(self, client: ContextDev) -> None: + web = client.web.search( + query="x", + exclude_domains=["string"], + freshness="last_24_hours", + include_domains=["string"], + markdown_options={ + "enabled": True, + "include_frames": True, + "include_images": True, + "include_links": True, + "max_age_ms": 0, + "pdf": { + "end": 1, + "should_parse": True, + "start": 1, + }, + "shorten_base64_images": True, + "timeout_ms": 1000, + "use_main_content_only": True, + "wait_for_ms": 0, + }, + query_fanout=True, + timeout_ms=1000, + ) + assert_matches_type(WebSearchResponse, web, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_search(self, client: ContextDev) -> None: + response = client.web.with_raw_response.search( + query="x", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + web = response.parse() + assert_matches_type(WebSearchResponse, web, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_search(self, client: ContextDev) -> None: + with client.web.with_streaming_response.search( + query="x", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + web = response.parse() + assert_matches_type(WebSearchResponse, web, path=["response"]) + + assert cast(Any, response.is_closed) is True + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_method_web_crawl_md(self, client: ContextDev) -> None: @@ -540,6 +604,69 @@ async def test_streaming_response_screenshot(self, async_client: AsyncContextDev assert cast(Any, response.is_closed) is True + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_search(self, async_client: AsyncContextDev) -> None: + web = await async_client.web.search( + query="x", + ) + assert_matches_type(WebSearchResponse, web, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_search_with_all_params(self, async_client: AsyncContextDev) -> None: + web = await async_client.web.search( + query="x", + exclude_domains=["string"], + freshness="last_24_hours", + include_domains=["string"], + markdown_options={ + "enabled": True, + "include_frames": True, + "include_images": True, + "include_links": True, + "max_age_ms": 0, + "pdf": { + "end": 1, + "should_parse": True, + "start": 1, + }, + "shorten_base64_images": True, + "timeout_ms": 1000, + "use_main_content_only": True, + "wait_for_ms": 0, + }, + query_fanout=True, + timeout_ms=1000, + ) + assert_matches_type(WebSearchResponse, web, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_search(self, async_client: AsyncContextDev) -> None: + response = await async_client.web.with_raw_response.search( + query="x", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + web = await response.parse() + assert_matches_type(WebSearchResponse, web, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_search(self, async_client: AsyncContextDev) -> None: + async with async_client.web.with_streaming_response.search( + query="x", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + web = await response.parse() + assert_matches_type(WebSearchResponse, web, path=["response"]) + + assert cast(Any, response.is_closed) is True + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_method_web_crawl_md(self, async_client: AsyncContextDev) -> None: