Skip to content

feat: coingecko opg price feed#56

Merged
kylexqian merged 18 commits intoani/token-updatefrom
feat/coingecko-opg-price-feed
Apr 21, 2026
Merged

feat: coingecko opg price feed#56
kylexqian merged 18 commits intoani/token-updatefrom
feat/coingecko-opg-price-feed

Conversation

@kylexqian
Copy link
Copy Markdown
Collaborator

@kylexqian kylexqian commented Apr 20, 2026

Reworked design of the coingecko OPG price feed

  • Have OPGFeedPrice run in the background and does not interact with chat controller at all
  • Price fetching has a TTL, retry mechanism, some delay between each, etc.
  • dynamic price function now takes in a callable function to get the current price
  • We have some status report from this price feed now reportable from the health check endpoint --> this should help us monitor if our TEEs are updating their prices correctly while they're running
  • Added changes to handle inference errors before / after payment
  1. Error case before inference (e.g. wrong model name, error fetching price) -- these return a 503 / 400 error to the user now
  2. Error case after inference (e.g. malformed JSON or no usage from provider) -- these are logged and are considered errors from the provider, not really preventable on our side outside of logging.

@adambalogh Current behavior is that if the price fetch fails we just continue using the last cached value. Errors will be logged / this can be checked in the health check to see last successful API request. Not sure if it's more preferable here to return an error for all requests while we have stale data.

kylexqian and others added 6 commits April 19, 2026 21:38
Replaces the hardcoded mock price (Decimal("1")) with a real CoinGecko
price feed that runs as a background daemon thread.

Key behaviour:
- Fetches OPG/USD price from CoinGecko /simple/token_price/base at startup
  and every 5 minutes thereafter (well within free-tier rate limits)
- Retries up to 3 times per refresh cycle with 10s delay between attempts;
  exits retry loop immediately on 429 (rate-limited) to avoid hammering
- Retains the last known good price on exhausted retries so live traffic
  is not disrupted by a transient CoinGecko outage
- Logs a WARNING when the cached price is older than 2× the refresh
  interval (background loop may be stuck)
- Tracks last_success, last_error, consecutive_failures, total_fetches,
  total_errors via get_status() / get_price_feed_status()
- get_price() raises ValueError when no price has ever been fetched,
  which propagates through dynamic_session_cost_calculator and the
  existing strict _resolve_session_request_cost monkey-patch to return
  HTTP 500 rather than silently charging an incorrect amount

Also adds:
- 29 unit tests (all mocked, no network required) covering the fetch
  helper, retry logic, rate-limit handling, stale warning, stats, and
  module-level singleton functions
- 4 integration tests that hit the live CoinGecko API; OPG-specific
  tests skip gracefully until the token is fully indexed

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Moves opg_price_feed.py into a dedicated package matching the layout of
the existing tee_gateway/heartbeat/ package:

  tee_gateway/price_feed/
    __init__.py   — re-exports public API (OPGPriceFeed, PriceFeedConfig,
                    start_price_feed, get_opg_price_usd, get_price_feed_status)
    config.py     — all constants (COINGECKO_BASE_URL, COINGECKO_PLATFORM,
                    FETCH_TIMEOUT, refresh/retry defaults, stale threshold)
                    plus the PriceFeedConfig frozen dataclass
    feed.py       — OPGPriceFeed class, fetch_opg_price(), singleton helpers

Also renames the test files to match:
  test_opg_price_feed.py            -> test_price_feed.py
  test_opg_price_feed_integration.py -> test_price_feed_integration.py

No behaviour changes — all 29 unit tests still pass.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…lator

Replace module-level singleton in price_feed with a factory/closure
pattern. make_cost_calculator(price_feed) in util.py binds an OPGPriceFeed
instance explicitly, eliminating hidden global state. Tests updated:
TestModuleLevelFunctions removed, TestMakeCostCalculator added (10 cases).
Also fix missing Any import in feed.py and unused PriceFeedConfig import.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…_cost + named function

Remove the factory/closure pattern and _PriceSource Protocol. util.py now
exports calculate_session_cost(context, get_price) — a plain function that
accepts a Callable[[], Decimal]. __main__.py wires it via a named
_session_cost_calculator function that passes _price_feed.get_price directly.
Tests updated to match the new signature.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

This comment was marked as outdated.

kylexqian and others added 3 commits April 19, 2026 23:04
Replace dynamic_session_cost_calculator import with calculate_session_cost
and pass _get_price (OPG=$1.00) to all call sites.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- start() is now non-blocking: initial fetch runs inside the background
  thread instead of blocking the caller
- start() is idempotent: duplicate calls are a no-op if thread is alive
- Clear last_error on successful refresh so /health reflects recovery
- Gate integration tests behind RUN_INTEGRATION_TESTS env var to prevent
  real CoinGecko calls in CI by default
- Fix stale docstring references to make_cost_calculator → calculate_session_cost

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 9 out of 9 changed files in this pull request and generated 4 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread .github/workflows/test.yml Outdated
Comment thread tee_gateway/__main__.py Outdated
Comment thread tests/test_pricing.py Outdated
Comment thread tests/test_pricing.py Outdated
kylexqian and others added 3 commits April 19, 2026 23:32
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Rename test classes and update comment/error message in __main__.py to
match the current calculate_session_cost / _session_cost_calculator names.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace the _strict_resolve_session_request_cost monkey-patch (which was
patching a method not called in the upto session flow) with a proper
before_request hook that rejects inference requests early if pricing
would fail:
  - 503 if the OPG price feed has no valid price yet
  - 400 if the requested model is not in the registry

_session_cost_calculator now logs CRITICAL with full traceback on any
post-inference cost failure (e.g. missing usage field) so uncharged
requests are never silently missed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 9 out of 9 changed files in this pull request and generated 5 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread tee_gateway/price_feed/feed.py Outdated
Comment thread tee_gateway/price_feed/feed.py Outdated
Comment thread tee_gateway/price_feed/feed.py Outdated
Comment thread tee_gateway/__main__.py Outdated
Comment thread tee_gateway/util.py Outdated
…hread safety)

- Update start() docstring: replace monkey-patch reference with
  pre-inference gate
- Make fetch_opg_price() validate response.json() is a dict before
  calling .get(), raising ValueError instead of AttributeError on
  unexpected CoinGecko response shapes
- Make start() idempotency check thread-safe under _lock
- Clarify CRITICAL log: provider error, x402 swallows exception so
  client is not charged
- Update calculate_session_cost docstring: replace monkey-patch
  reference with pre-inference gate and CRITICAL log behavior

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Comment thread tee_gateway/util.py
kylexqian and others added 2 commits April 20, 2026 14:27
Before the TGE cutover (2026-04-21 12:30 UTC) get_price() returns a fixed
$0.10 fallback so inference requests can be priced before OPG is listed on
CoinGecko. After the cutover the live cached price is used. Also adds a
guard in fetch_opg_price() rejecting non-positive or non-finite prices from
the API response.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
get_price() now raises ValueError if the cached price is older than 4 hours,
preventing billing on a price that is too outdated. A warning is still logged
at the existing 2 × refresh_interval threshold (~10 min) as an early signal.
The pre-inference gate in __main__.py will surface this as a 503.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 9 out of 9 changed files in this pull request and generated 4 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread tee_gateway/price_feed/feed.py Outdated
Comment thread tee_gateway/price_feed/feed.py
Comment thread tee_gateway/util.py Outdated
Comment thread tee_gateway/test/test_price_feed.py
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@kylexqian kylexqian merged commit e100e2d into ani/token-update Apr 21, 2026
5 checks passed
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.

4 participants