Skip to content

steampowered.com: add get_app, get_search_results and search_store - #134

Merged
yummybomb merged 12 commits into
mainfrom
hypeship/steam-store-tools
Oct 22, 2026
Merged

yummybomb merged 12 commits into
mainfrom
hypeship/steam-store-tools

Conversation

@yummybomb

@yummybomb yummybomb commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Adds the first tools for steampowered.com, which had a bench intent (steam-game-price, from #133) but no tools. All three are page tools on store.steampowered.com:

  • get_app reads an app page: the one open in the tab ({}), or any app by id or store URL (app) from any store page, by fetching that app page's HTML with the tab's session. It doesn't navigate. It returns name, release date, developers, publishers, genres, the Buy-area options (package, bundle, free-to-play) with shown price, original price and discount, the store currency, and the header's review summaries (Recent / All or English Reviews, each with summary, percent positive and count). An age-gated app returns age_check, an adult-only app that Steam sends signed-out visitors to its sign-in page for returns sign_in_required, an unknown id returns not_found, and a failed request returns not_ready.
  • get_search_results reads search results: the rows rendered on the open search page ({}), or a term's results (term) fetched from Steam's server-rendered search page, from any store page. Rows come in page order (type, id, url, title, release, platforms, review summary, price), plus the sort, checked filters and Steam's result total. offset/limit only bound the response. When more results follow the currently rendered range, more_results gives a visible next-page link from the open page. Hidden pagination links can repeat rows already loaded by infinite scrolling, so those links and unverified links from fetched markup are omitted. next_steps explains how to open the fetched search or scroll and read newly appended rows by offset. It accounts for prior pages and appended rows, and stops continuation once all results are loaded. For a signed-in account, Steam hides owned, ignored and wishlisted rows by the account's preferences only in the live page's script, so a term's fetched rows keep them; the message and next_steps say so and point at the open search. The description warns that a row's price can be an edition's sale price: Civilization VII's row shows the $49.99 Deluxe Edition, while the base game is $69.99.
  • search_store types the term into the header's "Search the store" box and clicks submit 100 ms after returning navigation_scheduled (cdp-tools.md §2.2). It's readOnlyHint: false because it navigates the tab, like Google's search_flights. The new bench/tasks/steampowered.com/domain.json lists it under page_actions, so every Steam intent still gets it.

Both reads return registered_tools, available_actions (with a valid input for each action in the current state) and next_steps, as the Google Flights tools in #131 do. On any store page, the reads count as available because they work there when given input. Shared page code is in lib/steam-store.js, inlined with scripts/compose.mjs. The URL patterns are narrowed to store.steampowered.com; url_patterns_reason explains why.

Testing

  • node scripts/validate-registry.mjs: 268 tools valid. node --test "tests/*.test.mjs": 3926 pass. tests/steampowered-store.test.mjs runs each tool from its source against fabricated store markup and checks the result against its outputSchema. It covers fetched pages, app URLs given without https://, age checks, unknown ids, 403/429/500/503 failures versus 404/410 missing pages, final-page pagination with limited responses, appended infinite-scroll rows, next-page fallback without a total, hidden pagination links, fetched-page continuation guidance, sign-in age gates, the signed-in hide-preference note, markup-hidden rows, actions on non-app/non-search store pages, and a message that names no edition's price.
  • get_app and get_search_results were also run on Steam's served HTML for Civilization V, VI and VII, Stardew Valley, Palworld, and a "civilization" search. Prices, discounts, bundle vs package, review summaries and counts, and rendered rows vs total came out right. The earlier next-page link check did not verify that following it advanced beyond the returned rows; that is covered by the follow-up below.
  • Author's live checks on a Kernel browser:
    • 2026-10-20, open pages: get_app on Stardew Valley, Palworld (a 30% discount and three bundles), Hollow Knight (a bundle price behind "Your Price:"), Dota 2 (free to play), ELDEN RING (age_check) and the home page (wrong_page). Also search_store from the home page, then get_search_results in relevance order and Lowest Price order.
    • 2026-10-21, by input from the home page: app 413150 (ok), 292030 (age_check) and 1 (not_found), and the term "stardew valley" (50 of 83 rows). Each took under half a second and the tab stayed put.
    • Pagination regression checks: the final search page (76–83 of 83), including limit: 1, reports no further results. Scrolling the first page from 50 to all 83 rows also reports no further results despite stale pagination text. App success, missing-app and age-check behavior still pass.
    • 2026-10-22, by input from the home page: app 1126320 redirects to /login/?redir=…/agecheck/app/1126320/ and returns sign_in_required (before, not_found); 413150 (ok), 292030 (age_check) and 1 (not_found) unchanged. Twelve age-rated apps were checked: every age gate is a redirect to /agecheck/app/<id>/ or to sign-in, none is shown inline on the app page. The header search box was found on the home, app and search pages, and search_store from the search page navigated to the results.
    • Follow-up pagination checks: the hidden first-page link reproduced rows 26–50 after the tool had returned 1–50. With the fix, both fetched and live infinite-scroll results omit that link. Scrolling returned 49 new visible rows at offset 50 with no duplicate ids. A visible pagination link advanced from rows 26–50 to 51–75 with no duplicate ids.
  • All three are draft; nobody other than the author has QA'd them. Unverified: non-USD stores, delisted and pre-purchase pages, any signed-in account, and fetched markup that Steam hides with CSS classes rather than hidden or inline display: none.

Bench

QA run at c86efbc, with #136's intents and this branch's domain.json (5 pairs, paired medians, low thinking). "Right" means all three base-game prices ($29.99, $59.99, $69.99) and English Reviews summaries (Overwhelmingly Positive, Very Positive, Mixed) match the store, checked by hand.

task model correct C → T time C → T
steam-game-price Haiku 5.5 5/5 → 5/5 0:11 → 0:09
steam-game-price GPT-6 Luna 5/5 → 5/5 0:14 → 0:07
steam-compare-series Haiku 5.5 1/5 → 5/5 right 0:29 → 0:08
steam-compare-series GPT-6 Luna 3/5 → 5/5 right 0:32 → 0:17

Control's wrong answers were Civilization VII's $49.99 Deluxe sale price, Civilization VI at $10.24, and once Civilization V's Recent Reviews. Treatment called a tool in every trial, with no failed invocations.

Earlier trials, before the request-error and pagination fixes:

5 pairs per row, paired medians, tools pointed at this branch locally (that edit isn't committed). Until this merges, a bench run of the Steam intents needs tools pointed at this branch, because domain.json names search_store.

task model correct C → T time C → T
steam-game-price (Stardew Valley) Haiku 5.5 low 5/5 → 5/5 0:10 → 0:09
steam-game-price (Stardew Valley) GPT-6 Luna low 5/5 → 5/5 0:15 → 0:07
steam-compare-series (Civilization V/VI/VII, #136) Haiku 5.5 low 2/5 → 5/5 right base prices 0:36 → 0:12
steam-compare-series (Civilization V/VI/VII, #136) GPT-6 Luna low 1/5 → 5/5 right base prices 0:19 → 0:14

"Right" means all three base-game prices match the store ($29.99, $59.99, $69.99). The bench's own check only verifies the answer's format, so the wrong answers (mostly Civilization VII's $49.99 Deluxe price) still scored as correct. steam-game-price is too easy for the tools to matter: control reads the app page in about 4 s. These runs used the tools before get_app's message stopped naming the first option's price.

… navigating

The tools only read the page open in the tab, so answering anything took
search_store (a navigation), listing the tools again and
get_search_results, while an agent that knew the app's URL could just
open it. get_app now takes app (an id or store URL) and get_search_results
takes term; both read the store's server-rendered page markup with the
tab's session, without leaving the page. Visibility for fetched markup
comes from hidden attributes and inline display: none.
…rice

Civilization VII's search row shows $49.99, the Deluxe Edition at half
price, while the base game costs $69.99. get_search_results told callers
a row answers a price question by itself; it now says to read the app's
page with get_app for the base game's price.
@yummybomb

Copy link
Copy Markdown
Contributor Author

Pushed 7c9b6a1 and 068ac8b:

  • get_app takes app (an id or store URL) and get_search_results takes term. Both read the store's server-rendered page with the tab's session, from any store page, without navigating. Without input they read the open page, as before. Visibility in fetched markup comes from hidden attributes and inline display: none.
  • A fetched age-gated app returns age_check, and an unknown id returns not_found.
  • The get_search_results description no longer says a row's price answers a price question by itself. Civilization VII's row shows $49.99, which is the Deluxe Edition at 50% off; the base game is $69.99.

Live, from the home page, the app and search reads each took under half a second and the tab stayed put. Tests pass (3,920).

Bench (5 pairs each, paired medians):

task model correct C → T time C → T
Stardew Valley price (old) Haiku 5.5 low 5/5 → 5/5 0:10 → 0:09
Stardew Valley price (old) GPT-6 Luna low 5/5 → 5/5 0:15 → 0:07
Civilization V/VI/VII compare (new, #136) Haiku 5.5 low 2/5 → 5/5 right base prices 0:36 → 0:12
Civilization V/VI/VII compare (new, #136) GPT-6 Luna low 1/5 → 5/5 right base prices 0:19 → 0:14

For the comparison, "right" means all three base-game prices match the store ($29.99, $59.99, $69.99). The bench's own check only verifies the answer's format.

@yummybomb

Copy link
Copy Markdown
Contributor Author

Pushed c86efbc:

  • get_app returns sign_in_required for an adult-only app that Steam sends signed-out visitors to its sign-in page for (/login/?redir=…/agecheck/app/<id>/). It returned not_found before. Live: app 1126320.
  • get_search_results says, for a signed-in account, that a term's fetched rows keep owned, ignored and wishlisted items that the open search page hides. Steam applies those preferences only in the live page's script (hide_owned, hide_ignored, hide_wishlist). Not checked with a signed-in account.
  • Checked and unchanged: no age gate is shown inline on an app page (12 age-rated apps all redirect), and the header search box is found on the home, app and search pages.

QA (5 pairs, low thinking): steam-compare-series right answers 1/5 → 5/5 on Haiku 5.5 and 3/5 → 5/5 on GPT-6 Luna, 0:29 → 0:08 and 0:32 → 0:17. steam-game-price 5/5 → 5/5 on both, 0:11 → 0:09 and 0:14 → 0:07.

@yummybomb
yummybomb marked this pull request as ready for review October 22, 2026 15:55
@yummybomb
yummybomb merged commit 8bbfe1b into main Oct 22, 2026
1 check 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.

1 participant