From 837e98789008cb121e6e600d19e82408bbc3350b Mon Sep 17 00:00:00 2001 From: Daniel Yudelevich Date: Sun, 18 Oct 2026 19:59:14 -0700 Subject: [PATCH 1/3] feat(sdk): soft-deprecate discover consensus and enhanced The platform now decides consensus itself and no longer runs AI result enhancement, so both discover request fields are ignored. The regenerated DiscoverParams drops them; passing either still sends it and raises a DeprecationWarning, scoped to discover so contacts consensus is untouched. CLI help for --consensus, --enhanced and --variance says what the server does now. --- .../discolike-cli/src/discolike_cli/bulk.py | 3 ++- .../src/discolike_cli/discover.py | 7 ++++--- .../src/discolike/_generated/requests.py | 18 +----------------- packages/discolike/src/discolike/_models.py | 11 +++++++++-- .../discolike/tests/test_requests_module.py | 13 +++++++++++++ 5 files changed, 29 insertions(+), 23 deletions(-) diff --git a/packages/discolike-cli/src/discolike_cli/bulk.py b/packages/discolike-cli/src/discolike_cli/bulk.py index 43b136d..bd75c92 100644 --- a/packages/discolike-cli/src/discolike_cli/bulk.py +++ b/packages/discolike-cli/src/discolike_cli/bulk.py @@ -158,7 +158,8 @@ def companies_command( employee_range: str | None = typer.Option(None, help="Employee count range filter."), revenue_range: str | None = typer.Option(None, help="Revenue range filter."), variance: str | None = typer.Option( - None, help="Result diversity: LOW, MID_LOW, MEDIUM, MID_HIGH, HIGH, UNRESTRICTED." + None, + help="How strictly the list stops at the end of the ICP's market: LOW stops earliest, HIGH latest, UNRESTRICTED never.", ), exclusion_query_id: list[str] | None = typer.Option(None, help=EXCLUSION_QUERY_ID_HELP), param: list[str] | None = typer.Option(None, "--param", help=PARAM_HELP), diff --git a/packages/discolike-cli/src/discolike_cli/discover.py b/packages/discolike-cli/src/discolike_cli/discover.py index 7d743f8..c206e02 100644 --- a/packages/discolike-cli/src/discolike_cli/discover.py +++ b/packages/discolike-cli/src/discolike_cli/discover.py @@ -105,10 +105,11 @@ def discover_command( max_digital_footprint: int | None = typer.Option(None, help="Maximum digital footprint score."), min_similarity: int | None = typer.Option(None, help="Minimum similarity score to include (0-99)."), variance: str | None = typer.Option( - None, help="Result diversity control: LOW, MID_LOW, MEDIUM, MID_HIGH, HIGH, UNRESTRICTED." + None, + help="How strictly the list stops at the end of the ICP's market: LOW stops earliest, HIGH latest, UNRESTRICTED never.", ), consensus: int | None = typer.Option( - None, help="Number of top results for the consensus search vector (1-20); higher reduces specificity." + None, help="Deprecated, no-op. Discover applies consensus itself to single-domain lookalike searches." ), redirect: bool | None = typer.Option(None, "--redirect/--no-redirect", help=REDIRECT_HELP), exclude_leadgen: bool | None = typer.Option( @@ -118,7 +119,7 @@ def discover_command( None, "--retrieval/--no-retrieval", help="Enable page data retrieval using the Extract API." ), enhanced: bool | None = typer.Option( - None, "--enhanced/--no-enhanced", help="Enable AI-powered result enhancement for improved relevance." + None, "--enhanced/--no-enhanced", help="Deprecated, no-op. Discover no longer adds AI-suggested domains." ), include_search_domains: bool | None = typer.Option( None, diff --git a/packages/discolike/src/discolike/_generated/requests.py b/packages/discolike/src/discolike/_generated/requests.py index 11a159d..a38e76b 100644 --- a/packages/discolike/src/discolike/_generated/requests.py +++ b/packages/discolike/src/discolike/_generated/requests.py @@ -2071,13 +2071,6 @@ class DiscoverParams(DiscolikeRequest): title="Retrieval", ), ] = False - enhanced: Annotated[ - bool | None, - Field( - description="Enable AI-powered result enhancement for improved relevance.", - title="Enhanced", - ), - ] = False include_search_domains: Annotated[ bool | None, Field( @@ -2094,15 +2087,6 @@ class DiscoverParams(DiscolikeRequest): title="Max Records", ), ] = 100 - consensus: Annotated[ - int | None, - Field( - description="Number of top results for consensus search vector (1-20). Higher values reduce specificity.", - ge=1, - le=20, - title="Consensus", - ), - ] = 1 min_similarity: Annotated[ int | None, Field( @@ -2115,7 +2099,7 @@ class DiscoverParams(DiscolikeRequest): variance: Annotated[ Literal["LOW", "MID_LOW", "MEDIUM", "MID_HIGH", "HIGH", "UNRESTRICTED"] | None, Field( - description="Result diversity control: LOW, MID_LOW, MEDIUM, MID_HIGH, HIGH, UNRESTRICTED.", + description="Where the list stops: LOW earliest (rows ~70% fit), MID_LOW, MEDIUM, MID_HIGH, HIGH latest (~30%). UNRESTRICTED never stops.", title="Variance", ), ] = "UNRESTRICTED" diff --git a/packages/discolike/src/discolike/_models.py b/packages/discolike/src/discolike/_models.py index a806b7f..2f18796 100644 --- a/packages/discolike/src/discolike/_models.py +++ b/packages/discolike/src/discolike/_models.py @@ -30,6 +30,12 @@ "contact search no longer filters on email verification; verify emails instead." ), } +DEPRECATED_MODEL_FIELDS: dict[str, dict[str, str]] = { + "DiscoverParams": { + "consensus": "consensus is deprecated and ignored: discover applies consensus itself to single-domain lookalike searches.", + "enhanced": "enhanced is deprecated and ignored: discover no longer adds AI-suggested domains.", + }, +} class DiscolikeModel(pydantic.BaseModel): @@ -78,8 +84,9 @@ def _validate_geo_shapes(self) -> DiscolikeRequest: @classmethod def _warn_deprecated_fields(cls, data: object) -> object: if isinstance(data, dict): - for name in sorted(DEPRECATED_REQUEST_FIELDS.keys() & data.keys()): - warnings.warn(DEPRECATED_REQUEST_FIELDS[name], DeprecationWarning, stacklevel=2) + deprecated = DEPRECATED_REQUEST_FIELDS | DEPRECATED_MODEL_FIELDS.get(cls.__name__, {}) + for name in sorted(deprecated.keys() & data.keys()): + warnings.warn(deprecated[name], DeprecationWarning, stacklevel=2) return data # Mirrors the platform's text_or_intake rule on ProspectingMessageRequest, which the spec cannot express. diff --git a/packages/discolike/tests/test_requests_module.py b/packages/discolike/tests/test_requests_module.py index 21d52f4..660f93e 100644 --- a/packages/discolike/tests/test_requests_module.py +++ b/packages/discolike/tests/test_requests_module.py @@ -53,6 +53,19 @@ def test_email_validated_is_deprecated_but_still_accepted(model: type[DiscolikeR assert request.to_wire() == {"email_validated": True} +@pytest.mark.parametrize("name", ["consensus", "enhanced"]) +def test_discover_consensus_and_enhanced_are_deprecated_but_still_sent(name: str) -> None: + with pytest.warns(DeprecationWarning, match=f"{name} is deprecated"): + request = DiscoverParams.model_validate({name: 2 if name == "consensus" else True}) + assert name in request.to_wire() + + +def test_contacts_consensus_is_not_deprecated() -> None: + with warnings.catch_warnings(): + warnings.simplefilter("error") + assert ContactsSearchParams(consensus=2).to_wire() == {"consensus": 2} + + def test_unset_email_validated_does_not_warn() -> None: with warnings.catch_warnings(): warnings.simplefilter("error") From d8d3c52566fd177e105234c76be7323a5148777e Mon Sep 17 00:00:00 2001 From: Daniel Yudelevich Date: Sun, 18 Oct 2026 20:24:53 -0700 Subject: [PATCH 2/3] fix(sdk): declare the open question on prospecting runs The dev spec's ProspectingRunResponse gained question, the ProspectingOpenQuestion a needs_input run waits on; without it the contract check against dev fails for every PR. --- .../discolike/src/discolike/resources/prospecting.py | 11 +++++++++++ packages/discolike/tests/test_prospecting.py | 9 +++++++++ 2 files changed, 20 insertions(+) diff --git a/packages/discolike/src/discolike/resources/prospecting.py b/packages/discolike/src/discolike/resources/prospecting.py index 5c34df8..7740d40 100644 --- a/packages/discolike/src/discolike/resources/prospecting.py +++ b/packages/discolike/src/discolike/resources/prospecting.py @@ -103,6 +103,14 @@ class ProspectingInFlight(DiscolikeModel): progress: int | None = None +class ProspectingOpenQuestion(DiscolikeModel): + question_seq: int | None + reason: str + content: str + replies: list[str] = Field(default_factory=list) + estimate: dict[str, Any] | None = None + + class ProspectingRunSummary(DiscolikeModel): run_id: UUID status: ProspectingStatus @@ -145,6 +153,9 @@ class ProspectingRun(DiscolikeModel): actions_used: int = 0 max_actions: int error: str | None = None + question: ProspectingOpenQuestion | None = Field( + default=None, description="What a needs_input run waits on; replies are exact labels, empty for free text." + ) title: str | None = None plan_version: int = 1 approved_plan_version: int | None = None diff --git a/packages/discolike/tests/test_prospecting.py b/packages/discolike/tests/test_prospecting.py index 95a9957..0c48d9a 100644 --- a/packages/discolike/tests/test_prospecting.py +++ b/packages/discolike/tests/test_prospecting.py @@ -735,3 +735,12 @@ def handler(request: httpx2.Request) -> httpx2.Response: with make_client(handler) as client, pytest.raises(ValidationError): client.prospecting.answer_intake(RUN_ID, {}, idempotency_key="k") assert seen == [] + + +def test_run_parses_the_open_question_of_a_needs_input_run() -> None: + question = {"question_seq": 4, "reason": "plan_review", "content": "Approve the plan?", "replies": ["Approve"]} + run = module.ProspectingRun.model_validate(run_payload() | {"question": question}) + assert run.question == module.ProspectingOpenQuestion( + question_seq=4, reason="plan_review", content="Approve the plan?", replies=["Approve"] + ) + assert module.ProspectingRun.model_validate(run_payload()).question is None From 19ba64d249ead76d0aa6c81e780e42dd4b316322 Mon Sep 17 00:00:00 2001 From: Daniel Yudelevich Date: Sun, 18 Oct 2026 20:30:16 -0700 Subject: [PATCH 3/3] fix(cli): list every variance level in the --variance help --- packages/discolike-cli/src/discolike_cli/bulk.py | 2 +- packages/discolike-cli/src/discolike_cli/discover.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/discolike-cli/src/discolike_cli/bulk.py b/packages/discolike-cli/src/discolike_cli/bulk.py index bd75c92..0025b52 100644 --- a/packages/discolike-cli/src/discolike_cli/bulk.py +++ b/packages/discolike-cli/src/discolike_cli/bulk.py @@ -159,7 +159,7 @@ def companies_command( revenue_range: str | None = typer.Option(None, help="Revenue range filter."), variance: str | None = typer.Option( None, - help="How strictly the list stops at the end of the ICP's market: LOW stops earliest, HIGH latest, UNRESTRICTED never.", + help="Where the list stops: LOW (earliest), MID_LOW, MEDIUM, MID_HIGH, HIGH (latest), UNRESTRICTED (never).", ), exclusion_query_id: list[str] | None = typer.Option(None, help=EXCLUSION_QUERY_ID_HELP), param: list[str] | None = typer.Option(None, "--param", help=PARAM_HELP), diff --git a/packages/discolike-cli/src/discolike_cli/discover.py b/packages/discolike-cli/src/discolike_cli/discover.py index c206e02..a1537bb 100644 --- a/packages/discolike-cli/src/discolike_cli/discover.py +++ b/packages/discolike-cli/src/discolike_cli/discover.py @@ -106,7 +106,7 @@ def discover_command( min_similarity: int | None = typer.Option(None, help="Minimum similarity score to include (0-99)."), variance: str | None = typer.Option( None, - help="How strictly the list stops at the end of the ICP's market: LOW stops earliest, HIGH latest, UNRESTRICTED never.", + help="Where the list stops: LOW (earliest), MID_LOW, MEDIUM, MID_HIGH, HIGH (latest), UNRESTRICTED (never).", ), consensus: int | None = typer.Option( None, help="Deprecated, no-op. Discover applies consensus itself to single-domain lookalike searches."