Skip to content

feat(providers): notion-web live model discovery via getAvailableModels - #7696

Merged
diegosouzapw merged 4 commits into
cheaper-inference:release/v3.8.49from
artickc:feat/notion-web-available-models
Jul 19, 2026
Merged

diegosouzapw merged 4 commits into
cheaper-inference:release/v3.8.49from
artickc:feat/notion-web-available-models

Conversation

@artickc

@artickc artickc commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Live cookie-auth model discovery for notion-web via Notion's internal POST /api/v3/getAvailableModels (same endpoint the browser AI surface uses), so operators see real GPT/Claude/Gemini/Grok/… codenames instead of a single stub notion-ai id.
  • Resolves spaceId from the pasted cookie (space_id=…) or falls back to POST /api/v3/getSpaces.
  • Seeds an offline fallback catalog from the live picker list; discovery failure still returns the seed (intentional local catalog).
  • Executor injects a config transcript entry with value.model for non-default model ids so selected models are actually requested on runInferenceTranscript.

Test plan

  • node --import tsx --test tests/unit/executor-notion-web.test.ts tests/unit/notion-web-models-discovery.test.ts — 30/30 pass
  • node scripts/check/check-file-size.mjs — OK (route 2089 ≤ frozen 2593; new service 286 ≤ cap 800)
  • Manual: paste token_v2 + space_id cookie → Providers → sync models / GET /api/providers/{id}/models?refresh=true returns live list
  • Manual: chat with e.g. orange-mousse / ambrosia-tart-high through notion-web

Notes

Follow-up to #7600 / #6758. Undocumented endpoints remain experimental (same risk class as other web-cookie providers).

Cookie-auth discovery against POST /api/v3/getAvailableModels (spaceId from
cookie or getSpaces) so /api/providers/{id}/models and /v1/models can surface
the real Notion AI picker catalog instead of a single stub notion-ai id.

Also injects a config transcript entry with the selected model codename on
runInferenceTranscript, seeds an offline fallback catalog, and documents that
space_id is needed for reliable discovery.
@artickc
artickc requested a review from diegosouzapw as a code owner July 18, 2026 22:37

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request introduces live model discovery for the Notion AI Web provider via the getAvailableModels endpoint, including fallback offline models, cookie parsing helpers, and updated executor transcript formatting. The review feedback suggests wrapping decodeURIComponent in a try-catch block to handle malformed cookies safely, using extractSpaceIdFromNotionCookie instead of a case-sensitive substring check to prevent duplicate space IDs, and refactoring redundant string trimming in buildNotionTranscript.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread open-sse/services/notionWebModels.ts Outdated
Comment on lines +67 to +68
const m = cookie.match(re);
return m ? decodeURIComponent(m[1].trim()) : "";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

Pasted cookie values from users can sometimes contain malformed percent-encodings (e.g., a raw % character). Calling decodeURIComponent on such values will throw a URIError, which could crash the discovery process. Wrapping it in a try-catch block and falling back to the raw trimmed value is much safer.

  const m = cookie.match(re);
  if (!m) return "";
  try {
    return decodeURIComponent(m[1].trim());
  } catch {
    return m[1].trim();
  }

Comment thread open-sse/services/notionWebModels.ts Outdated
Comment on lines +256 to +258
const headers = buildNotionModelsDiscoveryHeaders(
cookie.includes("space_id=") ? cookie : `${cookie}; space_id=${spaceId}`
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

The check cookie.includes("space_id=") is case-sensitive and only checks for the exact string "space_id=". However, extractSpaceIdFromNotionCookie supports both space_id and spaceId case-insensitively. If the cookie contains spaceId=..., cookie.includes("space_id=") will evaluate to false, causing a duplicate space_id to be appended to the cookie string.

Using extractSpaceIdFromNotionCookie(cookie) as the condition is more robust and consistent with how the space ID is extracted.

Suggested change
const headers = buildNotionModelsDiscoveryHeaders(
cookie.includes("space_id=") ? cookie : `${cookie}; space_id=${spaceId}`
);
const headers = buildNotionModelsDiscoveryHeaders(
extractSpaceIdFromNotionCookie(cookie) ? cookie : cookie + "; space_id=" + spaceId
);

Comment thread open-sse/executors/notion-web.ts Outdated
Comment on lines +139 to +144
const model =
typeof notionModel === "string" &&
notionModel.trim() &&
notionModel.trim() !== "notion-ai"
? notionModel.trim()
: "";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

The notionModel string is trimmed multiple times redundantly. We can trim it once and reuse the result, which is cleaner and more efficient.

Suggested change
const model =
typeof notionModel === "string" &&
notionModel.trim() &&
notionModel.trim() !== "notion-ai"
? notionModel.trim()
: "";
const trimmedModel = typeof notionModel === "string" ? notionModel.trim() : "";
const model = trimmedModel && trimmedModel !== "notion-ai" ? trimmedModel : "";

artickc and others added 2 commits July 19, 2026 02:25
- Safe decodeURIComponent for malformed cookie values
- Use extractSpaceIdFromNotionCookie instead of case-sensitive space_id= includes
- Single trim in buildNotionTranscript
- Sync STRICT docs counts to 265 providers (README/AGENTS/CLAUDE)
@diegosouzapw

Copy link
Copy Markdown
Collaborator

Reviewed and validated in an isolated probe worktree off release/v3.8.49: node --import tsx/esm --test tests/unit/executor-notion-web.test.ts tests/unit/notion-web-models-discovery.test.ts -> 30/30 pass, npm run typecheck:core clean, ESLint clean on all touched files. The safeOutboundFetch + cached/local-catalog fallback pattern matches the rest of the route, and errors are only logged (never leaked into the response body), so this is consistent with the error-sanitization hard rule. Confirmed no conflict with the already-merged #7600 base provider. Nice touch keeping a documented offline fallback catalog for when the endpoint changes shape again. Good to merge as-is.

…els/pickFirstSpaceId under complexity cap

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
@diegosouzapw

Copy link
Copy Markdown
Collaborator

Thanks @artickc! Great feature — notion-web live model discovery via getAvailableModels. I extracted a few helpers from parseNotionAvailableModels/pickFirstSpaceId to keep them under the complexity cap (behavior unchanged, 30/30 tests green), then merged. Validated in local merge-train on devbox @ 516c20438 (FAST gates green: static + changed tests + vitest). The +2 complexity from the new discovery helpers was resolved in-place by extracting helper functions (parseNotionModelEntry, pickSpaceIdFrom*); complexity back to 2058. Stale per-PR CI reds were the shared base-red DRIFT on the release tip.

@diegosouzapw
diegosouzapw merged commit dffff5d into cheaper-inference:release/v3.8.49 Jul 19, 2026
4 of 5 checks passed
artickc added a commit to artickc/OmniRoute that referenced this pull request Jul 19, 2026
Catalog /v1/models now surfaces web-picker names (fable-5, gpt-5.6-sol)
instead of Notion food codenames (acai-budino-high, orange-mousse).
Food codenames stay internal via notionCodename + resolveNotionCodename
for runInferenceTranscript. Legacy codename requests still work; responses
echo the client-facing id.

Also points discovery/inference at app.notion.com (same host as the AI picker).

Follow-up to cheaper-inference#7696.
diegosouzapw added a commit that referenced this pull request Jul 19, 2026
… usage (FINAL) (#7768)

* fix(notion-web): use real picker labels as primary model ids

Catalog /v1/models now surfaces web-picker names (fable-5, gpt-5.6-sol)
instead of Notion food codenames (acai-budino-high, orange-mousse).
Food codenames stay internal via notionCodename + resolveNotionCodename
for runInferenceTranscript. Legacy codename requests still work; responses
echo the client-facing id.

Also points discovery/inference at app.notion.com (same host as the AI picker).

Follow-up to #7696.

* fix(notion-web): explain plan-locked models like Fable 5

Notion returns Fable 5 (acai-budino-high) with isDisabled=true and
disabledReason=business_or_enterprise_plan_required. Keep it out of the
enabled catalog (requests would fail) but surface a discovery warning so
operators know why it is missing. Also warn when space_id is resolved via
getSpaces instead of the cookie.

* feat(notion-web): auto-detect workspace without pasting space_id

Operators only need the raw token_v2 value. When space_id is omitted:
- getSpaces loads all workspaces (browser-like headers + user id)
- each workspace is probed via getAvailableModels
- the richest AI catalog wins

Also softens auth hints so they no longer demand a cookie blob with =.

* fix(notion-web): pick Business workspace so Fable 5 is listed

Probe ALL workspaces instead of early-exiting on the first catalog with
>=8 models. Prefer spaces where Fable is enabled over personal spaces
where Notion returns isDisabled=business_or_enterprise_plan_required.
Cache the chosen spaceId for inference when cookie has no space_id.

* fix(notion-web): working inference + honest token estimates

- runInferenceTranscript: createThread+threadId, config/context/user
  transcript, space/user headers (fixes ValidationError 400)
- Parse modern NDJSON patch/record-map; strip lang tags
- Estimate usage from text (Notion has no metering); mark estimated
- Treat all-zero usage as missing; skip USAGE_TOKEN_BUFFER on estimated
- Keep estimated flag through response sanitizer (was stripped -> flat 2000)

Verified live: fable-5/gpt-5.6-sol chat 200; usage 7 / 65 not constant 2000.

* refactor(notion-web): extract helpers to keep discoverNotionWebModels/execute under the complexity cap

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>

---------

Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com>
Co-authored-by: artickc <artickc@users.noreply.github.com>
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
@diegosouzapw diegosouzapw mentioned this pull request Jul 23, 2026
HouMinXi pushed a commit to HouMinXi/OmniRoute that referenced this pull request Aug 2, 2026
…ls (cheaper-inference#7696)

* feat(providers): notion-web live models via getAvailableModels

Cookie-auth discovery against POST /api/v3/getAvailableModels (spaceId from
cookie or getSpaces) so /api/providers/{id}/models and /v1/models can surface
the real Notion AI picker catalog instead of a single stub notion-ai id.

Also injects a config transcript entry with the selected model codename on
runInferenceTranscript, seeds an offline fallback catalog, and documents that
space_id is needed for reliable discovery.

* fix(providers): address notion-web review + docs provider count

- Safe decodeURIComponent for malformed cookie values
- Use extractSpaceIdFromNotionCookie instead of case-sensitive space_id= includes
- Single trim in buildNotionTranscript
- Sync STRICT docs counts to 265 providers (README/AGENTS/CLAUDE)

* refactor(notion-web): extract helpers to keep parseNotionAvailableModels/pickFirstSpaceId under complexity cap

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>

---------

Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com>
Co-authored-by: artickc <artickc@users.noreply.github.com>
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
HouMinXi pushed a commit to HouMinXi/OmniRoute that referenced this pull request Aug 2, 2026
… usage (FINAL) (cheaper-inference#7768)

* fix(notion-web): use real picker labels as primary model ids

Catalog /v1/models now surfaces web-picker names (fable-5, gpt-5.6-sol)
instead of Notion food codenames (acai-budino-high, orange-mousse).
Food codenames stay internal via notionCodename + resolveNotionCodename
for runInferenceTranscript. Legacy codename requests still work; responses
echo the client-facing id.

Also points discovery/inference at app.notion.com (same host as the AI picker).

Follow-up to cheaper-inference#7696.

* fix(notion-web): explain plan-locked models like Fable 5

Notion returns Fable 5 (acai-budino-high) with isDisabled=true and
disabledReason=business_or_enterprise_plan_required. Keep it out of the
enabled catalog (requests would fail) but surface a discovery warning so
operators know why it is missing. Also warn when space_id is resolved via
getSpaces instead of the cookie.

* feat(notion-web): auto-detect workspace without pasting space_id

Operators only need the raw token_v2 value. When space_id is omitted:
- getSpaces loads all workspaces (browser-like headers + user id)
- each workspace is probed via getAvailableModels
- the richest AI catalog wins

Also softens auth hints so they no longer demand a cookie blob with =.

* fix(notion-web): pick Business workspace so Fable 5 is listed

Probe ALL workspaces instead of early-exiting on the first catalog with
>=8 models. Prefer spaces where Fable is enabled over personal spaces
where Notion returns isDisabled=business_or_enterprise_plan_required.
Cache the chosen spaceId for inference when cookie has no space_id.

* fix(notion-web): working inference + honest token estimates

- runInferenceTranscript: createThread+threadId, config/context/user
  transcript, space/user headers (fixes ValidationError 400)
- Parse modern NDJSON patch/record-map; strip lang tags
- Estimate usage from text (Notion has no metering); mark estimated
- Treat all-zero usage as missing; skip USAGE_TOKEN_BUFFER on estimated
- Keep estimated flag through response sanitizer (was stripped -> flat 2000)

Verified live: fable-5/gpt-5.6-sol chat 200; usage 7 / 65 not constant 2000.

* refactor(notion-web): extract helpers to keep discoverNotionWebModels/execute under the complexity cap

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>

---------

Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com>
Co-authored-by: artickc <artickc@users.noreply.github.com>
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
muhamadgalihsaputra pushed a commit to niyatna/NiyatnaRoute that referenced this pull request Sep 27, 2026
…ls (cheaper-inference#7696)

* feat(providers): notion-web live models via getAvailableModels

Cookie-auth discovery against POST /api/v3/getAvailableModels (spaceId from
cookie or getSpaces) so /api/providers/{id}/models and /v1/models can surface
the real Notion AI picker catalog instead of a single stub notion-ai id.

Also injects a config transcript entry with the selected model codename on
runInferenceTranscript, seeds an offline fallback catalog, and documents that
space_id is needed for reliable discovery.

* fix(providers): address notion-web review + docs provider count

- Safe decodeURIComponent for malformed cookie values
- Use extractSpaceIdFromNotionCookie instead of case-sensitive space_id= includes
- Single trim in buildNotionTranscript
- Sync STRICT docs counts to 265 providers (README/AGENTS/CLAUDE)

* refactor(notion-web): extract helpers to keep parseNotionAvailableModels/pickFirstSpaceId under complexity cap

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>

---------

Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com>
Co-authored-by: artickc <artickc@users.noreply.github.com>
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
muhamadgalihsaputra pushed a commit to niyatna/NiyatnaRoute that referenced this pull request Sep 27, 2026
… usage (FINAL) (cheaper-inference#7768)

* fix(notion-web): use real picker labels as primary model ids

Catalog /v1/models now surfaces web-picker names (fable-5, gpt-5.6-sol)
instead of Notion food codenames (acai-budino-high, orange-mousse).
Food codenames stay internal via notionCodename + resolveNotionCodename
for runInferenceTranscript. Legacy codename requests still work; responses
echo the client-facing id.

Also points discovery/inference at app.notion.com (same host as the AI picker).

Follow-up to cheaper-inference#7696.

* fix(notion-web): explain plan-locked models like Fable 5

Notion returns Fable 5 (acai-budino-high) with isDisabled=true and
disabledReason=business_or_enterprise_plan_required. Keep it out of the
enabled catalog (requests would fail) but surface a discovery warning so
operators know why it is missing. Also warn when space_id is resolved via
getSpaces instead of the cookie.

* feat(notion-web): auto-detect workspace without pasting space_id

Operators only need the raw token_v2 value. When space_id is omitted:
- getSpaces loads all workspaces (browser-like headers + user id)
- each workspace is probed via getAvailableModels
- the richest AI catalog wins

Also softens auth hints so they no longer demand a cookie blob with =.

* fix(notion-web): pick Business workspace so Fable 5 is listed

Probe ALL workspaces instead of early-exiting on the first catalog with
>=8 models. Prefer spaces where Fable is enabled over personal spaces
where Notion returns isDisabled=business_or_enterprise_plan_required.
Cache the chosen spaceId for inference when cookie has no space_id.

* fix(notion-web): working inference + honest token estimates

- runInferenceTranscript: createThread+threadId, config/context/user
  transcript, space/user headers (fixes ValidationError 400)
- Parse modern NDJSON patch/record-map; strip lang tags
- Estimate usage from text (Notion has no metering); mark estimated
- Treat all-zero usage as missing; skip USAGE_TOKEN_BUFFER on estimated
- Keep estimated flag through response sanitizer (was stripped -> flat 2000)

Verified live: fable-5/gpt-5.6-sol chat 200; usage 7 / 65 not constant 2000.

* refactor(notion-web): extract helpers to keep discoverNotionWebModels/execute under the complexity cap

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>

---------

Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com>
Co-authored-by: artickc <artickc@users.noreply.github.com>
Co-authored-by: diegosouzapw <8016841+diegosouzapw@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.

2 participants