Skip to content

Python SDK 2.5.0 (research background mode + frontier effort)#24

Closed
tyler5673 wants to merge 17 commits into
mainfrom
release/2.5.0
Closed

Python SDK 2.5.0 (research background mode + frontier effort)#24
tyler5673 wants to merge 17 commits into
mainfrom
release/2.5.0

Conversation

@tyler5673

@tyler5673 tyler5673 commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Summary

Python SDK 2.5.0 release introducing background-mode research: async task submission, polling, and streaming for long-running research tasks. Also adds the new frontier research effort tier for highest-quality, long-running research, and the new lite finance research effort tier for quick financial questions.

New Features

Method Description
you.research(background=True) Queues research tasks asynchronously, returns TaskResponse with task_id and stream_url instead of blocking for inline ResearchResponse
you.get_research_task(task_id=...) Poll status of a background task via GET /v1/research/{task_id}, returns TaskDetail with result populated on completion
you.stream_research_task(task_id=..., from_id=0) Stream real-time SSE events via GET /v1/research/{task_id}/stream. Terminal event names: response.done, complete, completed (success); error, failed, cancelled (failure)
ResearchEffort.FRONTIER New highest-quality research effort tier. Runs over longer durations (up to 4 hours) with improved quality and accuracy. Only works with the task-based API (background=true); sending frontier without background=true returns a 422.
FinanceResearchEffort.LITE New quick-response tier for the Finance Research API. Returns answers fast for straightforward financial questions. The default remains deep. No migration required; existing DEEP and EXHAUSTIVE values are unchanged.

Convenience Helpers (youdotcom.research_helpers)

Hand-maintained, regen-safe module:

Helper Description
research_background() / _async() Submit and return TaskResponse directly (no Union narrowing)
poll_research_task() / _async() Poll until terminal status (completed, failed, cancelled). Defaults: interval_s=2.0, timeout_s=600.0. For frontier tasks, pass timeout_s=14400 explicitly.
research_and_wait() / _async() Submit + stream SSE events until a terminal event arrives, then fetch the final TaskDetail. timeout_s auto-adjusts based on research_effort: 600s for standard/deep/exhaustive, 14400s (4 hours) for frontier. Bounded re-poll tolerates backend commit races.
stream_research() / _async() Tolerant SSE iterator that surfaces undocumented event types as raw dicts instead of crashing (recommended over stream_research_task() for real tasks)

Key Changes

  • you.research() return type widened to Union[ResearchResponse, TaskResponse]. Runtime behavior unchanged when background=False (default). Statically-typed callers accessing res.output may need an isinstance(res, ResearchResponse) narrow to satisfy mypy/pyright.
  • New ResearchEffort.FRONTIER enum value for the highest-quality, longest-running research tier. Documented in CHANGELOG.md, MIGRATION.md, docs/models/researcheffort.md, and docs/sdks/you/README.md.
  • New FinanceResearchEffort.LITE enum value for quick finance research answers. Documented in CHANGELOG.md, MIGRATION.md, docs/models/financeresearcheffort.md, and docs/sdks/you/README.md.
  • New models: TaskResponse, TaskDetail, ResearchTaskStreamEvent, Event enum, and 8 typed error classes.
  • stream_research recommended over stream_research_task for real tasks — the server emits intermediate workflow events not in the strict Event enum, which causes ResponseValidationError in the generated decoder. The tolerant helper surfaces them as raw dicts instead.
  • Completion-race re-poll: _resolve_from_final_get re-polls up to 3 times when the stream signals OK but the GET status hasn't caught up, with a clear error for terminal non-completed vs. exhausted attempts.
  • Sync/async timeout parity: both research_and_wait variants bound the SSE per-read timeout to timeout_s (or caller's timeout_ms when provided) and catch httpx.TimeoutException for graceful fallback.

Testing

  • Unit tests with mockserver + httpx MockTransport (86 tests)
  • Live tests pass against production API (frontier background: PASS ~10min, frontier 422: PASS, finance lite: SKIP until server deploys)
  • pylint 10.00/10, mypy clean, Go mockserver builds clean
  • Ponytail and honest code reviews completed; all P2 findings fixed
  • New tests: test_research_frontier_effort_with_background, test_frontier_without_background_raises_422, test_finance_research_lite_effort, frontier auto-timeout, completion-race re-poll (sync+async), async httpx.ReadTimeout fallback

See CHANGELOG.md for full release notes and MIGRATION.md for upgrade guidance from 2.4.0.

tyler5673 and others added 7 commits July 21, 2026 15:02
Adds background-mode research (async task submission, polling,
streaming) to the SDK. Adds you.research(background=True),
you.get_research_task(), you.stream_research_task(), and the
hand-maintained youdotcom.research_helpers module. All 2.4.0
features unchanged.

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
- Remove dead RawStreamEvent.retry field (never read by helpers/tests)
- Extract _resolve_from_final_get[_async] helpers (dedup 4x final-GET logic)
- Table-drive _raise_stream_error[_async] via _STREAM_ERROR_BRANCHES tuple
- Factor _stream_build_kwargs/_stream_hook_ctx shared by sync/async stream-open
- Inline _poll_detail[_async] into their single callers
- P1 fix: replace daemon-thread + consumer.join with single-threaded loop
  using httpx read timeout (no more leaked blocked threads on socket hangs)
- P2 fix: remove dead deadline/time.monotonic() inner check (unreachable -
  httpx.TimeoutException already enforces the timeout)
- P2 fix: remove stream_exc/except BaseException indirection (fossil of
  threaded design; exceptions now propagate naturally)
- P3: fix research_and_wait docstring (one GET -> a GET, second in fallback)
- P3: document retry_config=None divergence from generated stream_research_task
- Add 7 new typed-error tests (403/500/4XX/5XX/async 401/403/500)
- Fix live test TypeError masking: check 'TaskResponse' in str(e) before skip
- Extract _make_wait_handler factory + shared mock stream classes in tests

158/158 tests pass, pylint 10.00/10, mypy clean.

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
…fallback, TypeError masking)

- CHANGELOG: fix stream_research_task terminal event list (was missing
  'completed' in ok set and 'failed' in err set; now matches the actual
  _TERMINAL_STREAM_EVENTS_OK/ERR frozensets in research_helpers.py)
- CHANGELOG: document research_and_wait timeout/stream-close fallback GET
  behavior (was described as just 'fetch the final TaskDetail')
- MIGRATION: fix streaming example terminal event set to include 'complete'
  (was only 'response.done' + 'completed')
- MIGRATION: document research_and_wait fallback GET behavior
- examples: fix research_and_wait_example TypeError masking — check
  'TaskResponse' in str(e) before skipping, re-raise real TypeErrors
  (matches the fix applied to test_live.py)
- PR #23 description: fix research_and_wait row (was 'No polling fallback';
  now documents the final-GET fallback) and stream_research_task terminal
  event list

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
…atches async)

Addresses PR feedback (claude[bot] review 2026-07-22): sync research_and_wait
used timeout_s only as a per-read stall timeout (httpx read timeout), while
the async variant used asyncio.wait_for as a total wall-clock budget. This
meant a server streaming heartbeats forever would never time out in sync mode.

Fix: add a total deadline check inside the stream loop that breaks and sets
timed_out=True when time.monotonic() >= deadline. Both sync and async now
enforce timeout_s as a total budget.

Added test_research_and_wait_total_deadline_not_stall_timeout: streams
non-terminal 'ping' events every 10ms (fast enough to not trip the per-read
timeout) and verifies the total deadline fires at timeout_s=0.5s.

159/159 tests pass, pylint 10.00/10, mypy clean.

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
…t-non-completed test

Addresses PR feedback (claude[bot] review 2026-07-22T00:57):
- Fix Go mockserver comment: said 'four' terminal events but SDK treats six
  (OK: response.done, complete, completed; ERROR: error, failed, cancelled)
- Add test_research_and_wait_ok_event_but_get_non_completed_raises: covers
  the defensive branch where stream emits response.done but the follow-up
  GET returns non-completed status (RuntimeError path in _resolve_from_final_get)

160/160 tests pass, pylint 10.00/10, mypy clean, Go builds clean.

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
Add ResearchEffort.FRONTIER for the highest-quality, longest-running
research tasks (up to 4 hours). Frontier only works with background=true;
sending it without returns 422. The enum value is injected via overlay
(overlays/python_overlay.yaml) so it survives Speakeasy regenerations.

- Update docstrings in researcheffort.py, researchop.py, sdk.py
- Update CHANGELOG.md, MIGRATION.md, docs/models/researcheffort.md,
  docs/sdks/you/README.md
- Add tests: frontier+background happy path, frontier-without-background 422
- Add research_stream_example() to examples/api-example-calls.py
- Update PR #23 description

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
…ll defaults

research_and_wait now auto-selects timeout_s based on research_effort
when omitted: 600s (10 min) for standard/deep/exhaustive, 14400s (4 hr)
for frontier. Explicit timeout_s always takes precedence. poll_research_task
cannot auto-detect (receives task_id, not effort) so its defaults
(interval_s=2.0, timeout_s=600.0) are now documented in docstrings and
MIGRATION.md with a per-tier guidance table.

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
Comment thread src/youdotcom/research_helpers.py
Comment thread src/youdotcom/research_helpers.py
Comment thread src/youdotcom/research_helpers.py Outdated
Add LITE tier to FinanceResearchEffort enum (overlay-injected, same
pattern as ResearchEffort.FRONTIER). Update docstrings, docs, CHANGELOG,
and MIGRATION. Add test_finance_research_lite_effort.

Fix three PR #24 review items:
1. _resolve_from_final_get now re-polls up to 3 times (1s interval) when
   the stream signals OK but GET returns non-completed, tolerating backend
   commit races. Updated test_research_and_wait_ok_event_but_get_non_completed_raises
   and added test_research_and_wait_ok_event_repoll_succeeds.
2. research_and_wait_async now catches httpx.TimeoutException in addition
   to asyncio.TimeoutError, matching sync behavior. Added
   test_async_research_and_wait_httpx_readtimeout_falls_back_to_get.
3. Removed misplaced pylint disable-next=protected-access in stream_research.

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
Comment thread src/youdotcom/research_helpers.py
tyler5673 and others added 2 commits July 23, 2026 10:46
- test_frontier_background_completes: submits frontier task with
  background=true via research_and_wait, verifies TaskDetail completed.
  Passed live in 607s.
- test_frontier_without_background_raises_422: verifies 422 when frontier
  is sent without background=true. Passed live.
- test_finance_research_lite_effort: verifies LITE returns a quick answer.
  Skips gracefully when server hasn't deployed the tier yet (422).

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
research_and_wait_async now passes stream_timeout_ms=int(timeout_s*1000)
when the caller didn't set an explicit timeout_ms, matching the sync
variant. Without this, a caller-set You(timeout_ms=60000) would cap SSE
reads at 60s and cause premature TimeoutError on frontier tasks with
auto timeout_s=14400. Also updated the async docstring to note the
per-read timeout parity.

Addresses PR #24 review comment 3640119913.

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
Comment thread src/youdotcom/research_helpers.py
Comment thread tests/test_research_helpers.py
…final_get

When the re-poll loop hits a terminal non-completed status (failed/
cancelled) on the first GET, raise immediately with a clear message
instead of falling through to the misleading after-N-attempts error.
Added test_research_and_wait_ok_event_but_get_failed_raises_immediately
to cover the early-break branch.

Addresses PR #24 review comments 3640244682 and 3640245293.

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
Comment thread src/youdotcom/research_helpers.py
Comment thread src/youdotcom/research_helpers.py
…ests

Sync research_and_wait now honors caller timeout_ms for the stream read
when provided (matching async), falling back to timeout_s*1000 when not.
Updated both docstrings to accurately describe the shared behavior.

Added async mirrors for repoll-succeeds and ok-event-but-get-failed
tests, closing the coverage gap flagged in review.

Addresses PR #24 review comments 3640262585 and 3640262855 (the latter
was already fixed in 039fee1 but commented on an older revision).

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
@youdotcom-oss youdotcom-oss deleted a comment from claude Bot Jul 23, 2026
@youdotcom-oss youdotcom-oss deleted a comment from claude Bot Jul 23, 2026
@youdotcom-oss youdotcom-oss deleted a comment from claude Bot Jul 23, 2026
@youdotcom-oss youdotcom-oss deleted a comment from claude Bot Jul 23, 2026
@youdotcom-oss youdotcom-oss deleted a comment from claude Bot Jul 23, 2026
Comment thread src/youdotcom/research_helpers.py Outdated
Comment thread src/youdotcom/research_helpers.py
Comment thread tests/test_research_helpers.py
… at timeout_s

1. research_and_wait[_async] now catches stream-open failures (transient
   500, 404 on just-created task, etc.) and falls back to poll_research_task
   so a successfully-submitted task is never abandoned.
2. SSE per-read timeout is now capped at timeout_s*1000 in both variants,
   preventing a caller-set timeout_ms larger than timeout_s from causing
   the sync wall-clock to exceed the budget.
3. Added test_async_frontier_auto_timeout_completes_without_premature_timeout
   to mirror the sync frontier auto-timeout coverage.

Addresses PR #24 review comments 3640308132, 3640308929, 3640309427.

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
Comment thread src/youdotcom/research_helpers.py
The sync variant previously only caught httpx.TimeoutException during
stream iteration, letting non-timeout errors (RemoteProtocolError,
ConnectError on dropped connections) propagate unrecoverably. Now
catches Exception and falls back to poll_research_task, matching the
async variant's behavior and the stream-open failure fallback.

Addresses PR #24 review comment 3640374576.

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
@youdotcom-oss youdotcom-oss deleted a comment from claude Bot Jul 23, 2026
@youdotcom-oss youdotcom-oss deleted a comment from claude Bot Jul 23, 2026
Comment thread src/youdotcom/research_helpers.py Outdated
Comment thread src/youdotcom/research_helpers.py Outdated
…from timeout_ms

- Change `except Exception` to `except httpx.TransportError` for both
  stream-open and mid-stream fallbacks (sync + async). Typed auth errors
  (401/403/404/500 from _raise_stream_error) now propagate to the caller
  instead of being swallowed and masked by a different error type from
  poll_research_task.
- Decouple SSE per-read timeout from caller's timeout_ms. Always use
  int(timeout_s * 1000) for stream reads. Previously, a caller with
  timeout_ms=30000 on a frontier task (timeout_s=14400) would get a
  misleading "did not complete within 14400s" after only 30s.
- Add 5 new tests: stream-open TransportError -> poll succeeds (sync +
  async), stream-open 401 -> typed error propagates (sync + async),
  mid-stream TransportError -> poll succeeds (sync).

Addresses PR #24 review comments 3640425979 and 3640426348.

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
Comment thread src/youdotcom/research_helpers.py
…andler

The `return poll_research_task(...)` inside the `try` block meant
`finally: stream.close()` only ran after the entire poll loop completed,
keeping the failed SSE connection open during polling. Now calling
`stream.close()` explicitly before the return so cleanup happens first.

Addresses PR #24 review comment 3640517073.

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
Comment thread src/youdotcom/research_helpers.py Outdated
@claude

claude Bot commented Jul 23, 2026

Copy link
Copy Markdown

Review Summary — 2.5.0 (research background mode + frontier effort)

Reviewed with focus on the hand-maintained surfaces (research_helpers.py, tests/, CHANGELOG.md, MIGRATION.md, overlays/python_overlay.yaml, pyproject.toml). The auto-generated src/youdotcom/ churn was spot-checked, not line-reviewed. This is a high-quality PR — approve after the one minor doc nit below (non-blocking).

Code quality & correctness ✅

research_helpers.py is well-structured and the sync/async variants are faithful mirrors. I verified the important bits:

  • Exception ordering in both research_and_wait variants is correct: httpx.TimeoutException (a subclass of httpx.TransportError) is caught before the TransportError fallback branch, so stalls → TimeoutError and genuine transport failures → poll fallback, as intended.
  • Completion-race re-poll (_resolve_from_final_get) correctly distinguishes terminal-non-completed (raise immediately) from not-yet-committed (bounded re-poll), and detail is always bound before the post-loop RuntimeError.
  • Total wall-clock deadline is enforced independently of the per-read SSE timeout (sync via in-loop deadline check + bounded read timeout; async via asyncio.wait_for).
  • The double stream.close() in the sync mid-stream TransportError path is harmless — EventStream.close() delegates to httpx response.close(), which is idempotent.
  • The tolerant _decode_raw_event / stream_research correctly surface undocumented event names as RawStreamEvent instead of failing pydantic validation, and _raise_stream_error mirrors the generated typed-error branches (401/403/404/500 → typed, else YouDefaultError).

Test coverage ✅ (excellent)

test_research_helpers.py (+1550) is thorough and non-vacuous — real assertions across: TaskResponse narrowing + TypeError guard, poll terminal/timeout/failed, research_and_wait OK/error/timeout/stream-close/re-poll-success/re-poll-exhausted/terminal-failed, frontier auto-timeout + explicit override, stream-open & mid-stream transport-error fallbacks, typed stream errors, and the tolerant decoder. Sync and async paths are both covered. test_research.py adds direct SDK-method coverage (background return type, get/stream methods, error paths, frontier-without-background → 422). The Go mockserver SSE framing, routing, and JSON payloads were separately verified to match both the generated Event-enum decoder and the tolerant helper decoder. (I was not able to execute the suite in this sandbox — approvals blocked python/pytest — so correctness is from static tracing, not a green run.)

Documentation ✅

CHANGELOG.md, MIGRATION.md, README, USAGE, and the model docs are accurate and consistent with the code (enum values, return-type widening, auto-timeout table, stream_research vs stream_research_task guidance). examples/api-example-calls.py additions are correct and match the documented usage.

Breaking changes ✅

The you.research() return type widening to Union[ResearchResponse, TaskResponse] is the only breaking change, and it's runtime-compatible (default background=False still returns ResearchResponse). MIGRATION.md documents this clearly, including the static-typing isinstance/cast note. New enum values are purely additive.

Security ✅

No hardcoded secrets, no injection surface. Test/mock credentials are obvious placeholders (test-api-key, all-zero UUIDs). Overlay changes are limited to additive enum values and additionalProperties: true on TaskDetail.result/input.

Minor / non-blocking

  1. [inline] research_and_wait sync docstring claims the SSE per-read timeout can use timeout_ms "when provided", but the code always uses timeout_s; the async docstring is already correct.
  2. Trivial: CHANGELOG.md dates 2.5.0 as 2026-07-20 while the PR merges 2026-07-23 — confirm the intended release date.
  3. Optional: no explicit test for an async mid-stream (non-timeout) TransportError fallback — the sync equivalent and the async open-error/read-timeout cases are covered, so this is a small gap only.

🤖 Generated with Claude Code

The SSE per-read timeout was decoupled from timeout_ms in a previous
commit but the sync docstring still mentioned "(or the caller's
timeout_ms when provided)". Now matches the actual behavior and the
async variant's docstring.

Addresses PR #24 review comment 3640569229.

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
@youdotcom-oss youdotcom-oss deleted a comment from claude Bot Jul 23, 2026
@youdotcom-oss youdotcom-oss deleted a comment from claude Bot Jul 23, 2026
@tyler5673 tyler5673 closed this Jul 23, 2026
tyler5673 added a commit that referenced this pull request Jul 23, 2026
…rs (#25)

* feat: Python SDK 2.5.0 — Research Background Mode

Adds background-mode research (async task submission, polling,
streaming) to the SDK. Adds you.research(background=True),
you.get_research_task(), you.stream_research_task(), and the
hand-maintained youdotcom.research_helpers module. All 2.4.0
features unchanged.

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>

* refactor: ponytail shrinks + P2 fixes for research_helpers

- Remove dead RawStreamEvent.retry field (never read by helpers/tests)
- Extract _resolve_from_final_get[_async] helpers (dedup 4x final-GET logic)
- Table-drive _raise_stream_error[_async] via _STREAM_ERROR_BRANCHES tuple
- Factor _stream_build_kwargs/_stream_hook_ctx shared by sync/async stream-open
- Inline _poll_detail[_async] into their single callers
- P1 fix: replace daemon-thread + consumer.join with single-threaded loop
  using httpx read timeout (no more leaked blocked threads on socket hangs)
- P2 fix: remove dead deadline/time.monotonic() inner check (unreachable -
  httpx.TimeoutException already enforces the timeout)
- P2 fix: remove stream_exc/except BaseException indirection (fossil of
  threaded design; exceptions now propagate naturally)
- P3: fix research_and_wait docstring (one GET -> a GET, second in fallback)
- P3: document retry_config=None divergence from generated stream_research_task
- Add 7 new typed-error tests (403/500/4XX/5XX/async 401/403/500)
- Fix live test TypeError masking: check 'TaskResponse' in str(e) before skip
- Extract _make_wait_handler factory + shared mock stream classes in tests

158/158 tests pass, pylint 10.00/10, mypy clean.

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>

* docs: fix 2.5.0 doc inaccuracies (terminal events, research_and_wait fallback, TypeError masking)

- CHANGELOG: fix stream_research_task terminal event list (was missing
  'completed' in ok set and 'failed' in err set; now matches the actual
  _TERMINAL_STREAM_EVENTS_OK/ERR frozensets in research_helpers.py)
- CHANGELOG: document research_and_wait timeout/stream-close fallback GET
  behavior (was described as just 'fetch the final TaskDetail')
- MIGRATION: fix streaming example terminal event set to include 'complete'
  (was only 'response.done' + 'completed')
- MIGRATION: document research_and_wait fallback GET behavior
- examples: fix research_and_wait_example TypeError masking — check
  'TaskResponse' in str(e) before skipping, re-raise real TypeErrors
  (matches the fix applied to test_live.py)
- PR #23 description: fix research_and_wait row (was 'No polling fallback';
  now documents the final-GET fallback) and stream_research_task terminal
  event list

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>

* fix: sync research_and_wait now enforces total wall-clock deadline (matches async)

Addresses PR feedback (claude[bot] review 2026-07-22): sync research_and_wait
used timeout_s only as a per-read stall timeout (httpx read timeout), while
the async variant used asyncio.wait_for as a total wall-clock budget. This
meant a server streaming heartbeats forever would never time out in sync mode.

Fix: add a total deadline check inside the stream loop that breaks and sets
timed_out=True when time.monotonic() >= deadline. Both sync and async now
enforce timeout_s as a total budget.

Added test_research_and_wait_total_deadline_not_stall_timeout: streams
non-terminal 'ping' events every 10ms (fast enough to not trip the per-read
timeout) and verifies the total deadline fires at timeout_s=0.5s.

159/159 tests pass, pylint 10.00/10, mypy clean.

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>

* test: fix Go mock comment (four->six terminal events) + add ok-but-get-non-completed test

Addresses PR feedback (claude[bot] review 2026-07-22T00:57):
- Fix Go mockserver comment: said 'four' terminal events but SDK treats six
  (OK: response.done, complete, completed; ERROR: error, failed, cancelled)
- Add test_research_and_wait_ok_event_but_get_non_completed_raises: covers
  the defensive branch where stream emits response.done but the follow-up
  GET returns non-completed status (RuntimeError path in _resolve_from_final_get)

160/160 tests pass, pylint 10.00/10, mypy clean, Go builds clean.

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>

* feat: add frontier research effort tier + streaming example

Add ResearchEffort.FRONTIER for the highest-quality, longest-running
research tasks (up to 4 hours). Frontier only works with background=true;
sending it without returns 422. The enum value is injected via overlay
(overlays/python_overlay.yaml) so it survives Speakeasy regenerations.

- Update docstrings in researcheffort.py, researchop.py, sdk.py
- Update CHANGELOG.md, MIGRATION.md, docs/models/researcheffort.md,
  docs/sdks/you/README.md
- Add tests: frontier+background happy path, frontier-without-background 422
- Add research_stream_example() to examples/api-example-calls.py
- Update PR #23 description

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>

* feat: auto-adjust research_and_wait timeout for frontier + surface poll defaults

research_and_wait now auto-selects timeout_s based on research_effort
when omitted: 600s (10 min) for standard/deep/exhaustive, 14400s (4 hr)
for frontier. Explicit timeout_s always takes precedence. poll_research_task
cannot auto-detect (receives task_id, not effort) so its defaults
(interval_s=2.0, timeout_s=600.0) are now documented in docstrings and
MIGRATION.md with a per-tier guidance table.

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>

* feat: add FinanceResearchEffort.LITE + fix PR review findings

Add LITE tier to FinanceResearchEffort enum (overlay-injected, same
pattern as ResearchEffort.FRONTIER). Update docstrings, docs, CHANGELOG,
and MIGRATION. Add test_finance_research_lite_effort.

Fix three PR #24 review items:
1. _resolve_from_final_get now re-polls up to 3 times (1s interval) when
   the stream signals OK but GET returns non-completed, tolerating backend
   commit races. Updated test_research_and_wait_ok_event_but_get_non_completed_raises
   and added test_research_and_wait_ok_event_repoll_succeeds.
2. research_and_wait_async now catches httpx.TimeoutException in addition
   to asyncio.TimeoutError, matching sync behavior. Added
   test_async_research_and_wait_httpx_readtimeout_falls_back_to_get.
3. Removed misplaced pylint disable-next=protected-access in stream_research.

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>

* test: add live tests for frontier research + finance lite effort

- test_frontier_background_completes: submits frontier task with
  background=true via research_and_wait, verifies TaskDetail completed.
  Passed live in 607s.
- test_frontier_without_background_raises_422: verifies 422 when frontier
  is sent without background=true. Passed live.
- test_finance_research_lite_effort: verifies LITE returns a quick answer.
  Skips gracefully when server hasn't deployed the tier yet (422).

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>

* fix: bound async SSE per-read timeout to timeout_s (match sync variant)

research_and_wait_async now passes stream_timeout_ms=int(timeout_s*1000)
when the caller didn't set an explicit timeout_ms, matching the sync
variant. Without this, a caller-set You(timeout_ms=60000) would cap SSE
reads at 60s and cause premature TimeoutError on frontier tasks with
auto timeout_s=14400. Also updated the async docstring to note the
per-read timeout parity.

Addresses PR #24 review comment 3640119913.

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>

* fix: distinguish early-break from exhausted re-poll in _resolve_from_final_get

When the re-poll loop hits a terminal non-completed status (failed/
cancelled) on the first GET, raise immediately with a clear message
instead of falling through to the misleading after-N-attempts error.
Added test_research_and_wait_ok_event_but_get_failed_raises_immediately
to cover the early-break branch.

Addresses PR #24 review comments 3640244682 and 3640245293.

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>

* fix: align sync/async stream timeout_ms handling + add async repoll tests

Sync research_and_wait now honors caller timeout_ms for the stream read
when provided (matching async), falling back to timeout_s*1000 when not.
Updated both docstrings to accurately describe the shared behavior.

Added async mirrors for repoll-succeeds and ok-event-but-get-failed
tests, closing the coverage gap flagged in review.

Addresses PR #24 review comments 3640262585 and 3640262855 (the latter
was already fixed in 039fee1 but commented on an older revision).

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>

* fix: stream-open failure falls back to polling + cap per-read timeout at timeout_s

1. research_and_wait[_async] now catches stream-open failures (transient
   500, 404 on just-created task, etc.) and falls back to poll_research_task
   so a successfully-submitted task is never abandoned.
2. SSE per-read timeout is now capped at timeout_s*1000 in both variants,
   preventing a caller-set timeout_ms larger than timeout_s from causing
   the sync wall-clock to exceed the budget.
3. Added test_async_frontier_auto_timeout_completes_without_premature_timeout
   to mirror the sync frontier auto-timeout coverage.

Addresses PR #24 review comments 3640308132, 3640308929, 3640309427.

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>

* fix: sync research_and_wait falls back to polling on mid-stream errors

The sync variant previously only caught httpx.TimeoutException during
stream iteration, letting non-timeout errors (RemoteProtocolError,
ConnectError on dropped connections) propagate unrecoverably. Now
catches Exception and falls back to poll_research_task, matching the
async variant's behavior and the stream-open failure fallback.

Addresses PR #24 review comment 3640374576.

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>

* fix: narrow fallback to httpx.TransportError + decouple SSE per-read from timeout_ms

- Change `except Exception` to `except httpx.TransportError` for both
  stream-open and mid-stream fallbacks (sync + async). Typed auth errors
  (401/403/404/500 from _raise_stream_error) now propagate to the caller
  instead of being swallowed and masked by a different error type from
  poll_research_task.
- Decouple SSE per-read timeout from caller's timeout_ms. Always use
  int(timeout_s * 1000) for stream reads. Previously, a caller with
  timeout_ms=30000 on a frontier task (timeout_s=14400) would get a
  misleading "did not complete within 14400s" after only 30s.
- Add 5 new tests: stream-open TransportError -> poll succeeds (sync +
  async), stream-open 401 -> typed error propagates (sync + async),
  mid-stream TransportError -> poll succeeds (sync).

Addresses PR #24 review comments 3640425979 and 3640426348.

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>

* fix: close stream before fallback poll in mid-stream TransportError handler

The `return poll_research_task(...)` inside the `try` block meant
`finally: stream.close()` only ran after the entire poll loop completed,
keeping the failed SSE connection open during polling. Now calling
`stream.close()` explicitly before the return so cleanup happens first.

Addresses PR #24 review comment 3640517073.

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>

* docs: fix sync docstring to drop stale timeout_ms parenthetical

The SSE per-read timeout was decoupled from timeout_ms in a previous
commit but the sync docstring still mentioned "(or the caller's
timeout_ms when provided)". Now matches the actual behavior and the
async variant's docstring.

Addresses PR #24 review comment 3640569229.

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>

* ci: add Droid Auto Review workflow for automated PR reviews

Triggers on PR open/synchronize/reopen for internal contributors.
External contributors can be reviewed via workflow_dispatch.
Requires FACTORY_API_KEY secret and Factory Droid GitHub App installed.

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>

* ci: remove Claude Code review workflow in favor of Droid Auto Review

The droid-review.yml workflow now handles automated PR reviews.
Removes the claude.yml workflow that used anthropics/claude-code-action.

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>

* fix: sync Speakeasy version files to 2.5.0

gen.yaml python.version and gen.lock releaseVersion were left at 2.4.0
while pyproject.toml and _version.py were already 2.5.0. Without this
sync, the next speakeasy run would silently revert the version bump.

Addresses PR #25 review comment 3640806529.

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>

* chore: remove .speakeasy config and generate-sdk workflow

No longer using Speakeasy for SDK generation. Removes:
- .speakeasy/ (gen.yaml, gen.lock, workflow.yaml, workflow.lock, etc.)
- .github/workflows/generate-sdk.yml (depended on speakeasy bump/run)

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>

* fix: address Droid Auto Review feedback (P0/P1/P2)

- P0: Fix actions/checkout@v7 -> @v4 (v7 not published)
- P1: Fix broken link in README.md (models/.md -> researchtaskstreamevent.md)
- P1: Make MIGRATION.md code blocks self-contained with imports + You()
- P2: Add TODO comment for curl|sh integrity verification in workflow_dispatch

Addresses PR #25 review comments 3641083815, 3641083827, 3641083831, 3641083837.

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>

* docs: fix TaskDetail/TaskResponse timestamp types (date -> datetime)

Generated docs incorrectly linked to Python date objects instead of
datetime. Updated to match the actual SDK types in taskdetail.py and
taskresponse.py.

Addresses PR #25 review comments 3641189866 and 3641189887.

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>

* docs: add frontier to ResearchRequest research_effort field docs

The researchrequest.md docs listed lite/standard/deep/exhaustive but
omitted frontier (which requires background=true). Added frontier with
the background constraint note.

Addresses PR #25 review comment 3641288647.

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>

---------

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant