Repository navigation
feat(providers): notion-web live model discovery via getAvailableModels - #7696
Conversation
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.
There was a problem hiding this comment.
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.
| const m = cookie.match(re); | ||
| return m ? decodeURIComponent(m[1].trim()) : ""; |
There was a problem hiding this comment.
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();
}| const headers = buildNotionModelsDiscoveryHeaders( | ||
| cookie.includes("space_id=") ? cookie : `${cookie}; space_id=${spaceId}` | ||
| ); |
There was a problem hiding this comment.
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.
| const headers = buildNotionModelsDiscoveryHeaders( | |
| cookie.includes("space_id=") ? cookie : `${cookie}; space_id=${spaceId}` | |
| ); | |
| const headers = buildNotionModelsDiscoveryHeaders( | |
| extractSpaceIdFromNotionCookie(cookie) ? cookie : cookie + "; space_id=" + spaceId | |
| ); |
| const model = | ||
| typeof notionModel === "string" && | ||
| notionModel.trim() && | ||
| notionModel.trim() !== "notion-ai" | ||
| ? notionModel.trim() | ||
| : ""; |
There was a problem hiding this comment.
The notionModel string is trimmed multiple times redundantly. We can trim it once and reuse the result, which is cleaner and more efficient.
| 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 : ""; |
- 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)
|
Reviewed and validated in an isolated probe worktree off release/v3.8.49: |
…els/pickFirstSpaceId under complexity cap Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
|
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. |
dffff5d
into
cheaper-inference:release/v3.8.49
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.
… 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>
…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>
… 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>
…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>
… 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>
Summary
POST /api/v3/getAvailableModels(same endpoint the browser AI surface uses), so operators see real GPT/Claude/Gemini/Grok/… codenames instead of a single stubnotion-aiid.spaceIdfrom the pasted cookie (space_id=…) or falls back toPOST /api/v3/getSpaces.configtranscript entry withvalue.modelfor non-default model ids so selected models are actually requested onrunInferenceTranscript.Test plan
node --import tsx --test tests/unit/executor-notion-web.test.ts tests/unit/notion-web-models-discovery.test.ts— 30/30 passnode scripts/check/check-file-size.mjs— OK (route 2089 ≤ frozen 2593; new service 286 ≤ cap 800)token_v2+space_idcookie → Providers → sync models /GET /api/providers/{id}/models?refresh=truereturns live listorange-mousse/ambrosia-tart-highthrough notion-webNotes
Follow-up to #7600 / #6758. Undocumented endpoints remain experimental (same risk class as other web-cookie providers).