From 21b7bf2113f89355f32a044f4839b389f092547a Mon Sep 17 00:00:00 2001 From: Ho Lim Date: Tue, 16 Jun 2026 22:02:29 -0700 Subject: [PATCH 1/6] docs: add inference model task-fit guide --- docs/inference/inference-options.mdx | 30 ++++++++ test/inference-options-docs.test.ts | 106 +++++++++++++++++++++++++++ 2 files changed, 136 insertions(+) create mode 100644 test/inference-options-docs.test.ts diff --git a/docs/inference/inference-options.mdx b/docs/inference/inference-options.mdx index f07adf448a1..488ae152868 100644 --- a/docs/inference/inference-options.mdx +++ b/docs/inference/inference-options.mdx @@ -74,6 +74,36 @@ The managed install/start vLLM entry appears by default on DGX Spark and DGX Sta | Local Ollama | Routes to a local Ollama instance on `localhost:11434`. NemoClaw detects installed models, offers starter models if none are present, pulls and warms the selected model, and validates it. | Selected during onboarding. For more information, refer to [Use a Local Inference Server](use-local-inference). | | Model Router | Starts a host-side router on port `4000`, registers it as an OpenAI-compatible provider, and keeps the sandbox pointed at `inference.local`. Set `NEMOCLAW_PROVIDER=routed` for non-interactive setup. | The router pool defines the model names. | +## Model Task-Fit Guide + +Use this table as starter guidance when selecting a curated cloud model during onboarding. +The provider catalog remains authoritative for exact context-window limits, availability, and current pricing. +The relative labels below are qualitative and compare models within the curated onboarding choices, not across every model a provider offers. + +| Model | Best-for task type | Relative latency | Tool-use quality | Context-window fit | Relative cost | +|---|---|---|---|---|---| +| `nvidia/nemotron-3-super-120b-a12b` | Default hosted agent work, multi-step planning, and tool-heavy shell workflows | Medium | Strong default for OpenClaw tool loops | Large agent context | Medium | +| `nvidia/nemotron-3-ultra-550b-a55b` | Quality-sensitive reasoning, careful synthesis, and complex reviews | Higher | Strong for complex tool plans | Large agent context | Higher | +| `nvidia/nemotron-3-nano-omni-30b-a3b-reasoning` | Reasoning-first and multimodal experiments where a compact hosted model is enough | Medium | Good after the smoke probe confirms final-answer content | Large agent context | Medium | +| `z-ai/glm-5.1` | General chat, multilingual text work, and fast iteration | Low-to-medium | Good for straightforward tool loops | Large agent context | Low-to-medium | +| `minimaxai/minimax-m2.7` | Long-form writing, multi-turn assistant work, and broad instruction following | Medium | Good for structured assistant turns | Large agent context | Medium | +| `moonshotai/kimi-k2.6` | Coding tasks and shell-heavy agent trajectories | Medium | Strong with NemoClaw's Kimi tool-call compatibility path | Large-context friendly | Medium | +| `openai/gpt-oss-120b` | Hosted open-weight style experimentation and cost-aware general agents | Medium | Good when provider-side tool calling is enabled | Large agent context | Medium | +| `deepseek-ai/deepseek-v4-pro` | Code, math, and reasoning-heavy problem solving | Medium-to-high | Strong when the endpoint supports tool calls | Large agent context | Medium-to-high | +| `gpt-5.4` | Default OpenAI-backed agent work and general high-quality reasoning | Medium | Strong | Large agent context | Medium-to-high | +| `gpt-5.4-mini` | Latency-sensitive routine automation and repeated helper calls | Low | Good | Medium-to-large context | Low | +| `gpt-5.4-nano` | Very low-latency classification, routing, extraction, and small helper tasks | Very low | Basic to good for simple tool loops | Medium context | Very low | +| `gpt-5.4-pro-2026-03-05` | Quality-first complex reasoning where latency and cost are secondary | Highest | Validate Responses API support before relying on long tool loops | Large agent context | Highest | +| `claude-sonnet-4-6` | Balanced coding, writing, analysis, and multi-step tool work | Medium | Strong | Large agent context | Medium-to-high | +| `claude-haiku-4-5` | Fast summarization, routing, extraction, and lightweight assistant turns | Low | Good for simple tool loops | Medium-to-large context | Low | +| `claude-opus-4-6` | Deep analysis, careful writing, and quality-first planning | Higher | Strong | Large agent context | Higher | +| `gemini-3.1-pro-preview` | Large-context analysis, synthesis, and preview-feature evaluation | Medium-to-high | Good; validate tool continuation for the selected provider path | Very large context | Medium-to-high | +| `gemini-3.1-flash-lite-preview` | Low-cost extraction, classification, and simple helper calls | Low | Basic to good for simple tool loops | Medium-to-large context | Low | +| `gemini-3-flash-preview` | Fast general assistant tasks and preview-feature evaluation | Low | Good for simple tool loops | Large context | Low | +| `gemini-2.5-pro` | Large-context analysis, long-document synthesis, and complex reasoning | Medium-to-high | Good | Very large context | Medium-to-high | +| `gemini-2.5-flash` | Latency-sensitive general assistant and multimodal tasks | Low | Good for simple tool loops | Large context | Low | +| `gemini-2.5-flash-lite` | Lowest-cost helper calls, extraction, and classification | Very low | Basic to good for simple tool loops | Medium-to-large context | Very low | + ## Choosing the Right Option for Nemotron NVIDIA Nemotron models expose OpenAI-compatible APIs across every supported deployment surface, so two onboarding options can route to Nemotron. diff --git a/test/inference-options-docs.test.ts b/test/inference-options-docs.test.ts new file mode 100644 index 00000000000..4336ed9ab96 --- /dev/null +++ b/test/inference-options-docs.test.ts @@ -0,0 +1,106 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +import fs from "node:fs"; +import { createRequire } from "node:module"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import type * as TypeScript from "typescript"; +import { describe, expect, it } from "vitest"; + +const require = createRequire(import.meta.url); +const ts = require("typescript") as typeof TypeScript; +const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); +const inferenceOptionsPath = path.join(repoRoot, "docs", "inference", "inference-options.mdx"); +const inferenceConfigPath = path.join(repoRoot, "src", "lib", "inference", "config.ts"); + +function sectionBetween(markdown: string, heading: string, nextHeading: string): string { + const start = markdown.indexOf(heading); + const end = markdown.indexOf(nextHeading, start + heading.length); + expect(start).toBeGreaterThanOrEqual(0); + expect(end).toBeGreaterThan(start); + return markdown.slice(start, end); +} + +function unwrapConstAssertion(expression: TypeScript.Expression): TypeScript.Expression { + return ts.isAsExpression(expression) ? unwrapConstAssertion(expression.expression) : expression; +} + +function readCuratedCloudModelIds(): string[] { + const source = fs.readFileSync(inferenceConfigPath, "utf8"); + const sourceFile = ts.createSourceFile( + inferenceConfigPath, + source, + ts.ScriptTarget.Latest, + true, + ts.ScriptKind.TS, + ); + + for (const statement of sourceFile.statements) { + const isExported = statement.modifiers?.some( + (modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword, + ); + if (!isExported || !ts.isVariableStatement(statement)) continue; + + for (const declaration of statement.declarationList.declarations) { + if (declaration.name.getText(sourceFile) !== "CLOUD_MODEL_OPTIONS") continue; + const initializer = declaration.initializer && unwrapConstAssertion(declaration.initializer); + expect(initializer && ts.isArrayLiteralExpression(initializer)).toBe(true); + + return (initializer as TypeScript.ArrayLiteralExpression).elements.map((element) => { + expect(ts.isObjectLiteralExpression(element)).toBe(true); + const idProperty = (element as TypeScript.ObjectLiteralExpression).properties.find( + (property) => + ts.isPropertyAssignment(property) && + property.name.getText(sourceFile) === "id" && + ts.isStringLiteralLike(unwrapConstAssertion(property.initializer)), + ); + expect(idProperty).toBeTruthy(); + const idInitializer = unwrapConstAssertion( + (idProperty as TypeScript.PropertyAssignment).initializer, + ); + return (idInitializer as TypeScript.StringLiteral).text; + }); + } + } + + throw new Error("CLOUD_MODEL_OPTIONS export was not found"); +} + +describe("inference options model task-fit docs (#4755)", () => { + it("keeps a per-model task-fit comparison table for curated onboarding models", () => { + const markdown = fs.readFileSync(inferenceOptionsPath, "utf8"); + const section = sectionBetween( + markdown, + "## Model Task-Fit Guide", + "## Choosing the Right Option for Nemotron", + ); + + expect(section).toContain( + "| Model | Best-for task type | Relative latency | Tool-use quality | Context-window fit | Relative cost |", + ); + expect(section).toContain("provider catalog remains authoritative"); + expect(section).not.toMatch(/\bTBD\b|\bTODO\b/i); + + const expectedModelIds = [ + ...readCuratedCloudModelIds(), + "gpt-5.4", + "gpt-5.4-mini", + "gpt-5.4-nano", + "gpt-5.4-pro-2026-03-05", + "claude-sonnet-4-6", + "claude-haiku-4-5", + "claude-opus-4-6", + "gemini-3.1-pro-preview", + "gemini-3.1-flash-lite-preview", + "gemini-3-flash-preview", + "gemini-2.5-pro", + "gemini-2.5-flash", + "gemini-2.5-flash-lite", + ]; + + for (const modelId of expectedModelIds) { + expect(section).toContain(`| \`${modelId}\` |`); + } + }); +}); From 7ddf29626cee84055a85033ce4620458d0eb3968 Mon Sep 17 00:00:00 2001 From: Ho Lim Date: Wed, 17 Jun 2026 18:07:40 -0700 Subject: [PATCH 2/6] test: document inference docs helpers --- test/inference-options-docs.test.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/inference-options-docs.test.ts b/test/inference-options-docs.test.ts index 4336ed9ab96..6ce2cd897dd 100644 --- a/test/inference-options-docs.test.ts +++ b/test/inference-options-docs.test.ts @@ -14,6 +14,7 @@ const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".." const inferenceOptionsPath = path.join(repoRoot, "docs", "inference", "inference-options.mdx"); const inferenceConfigPath = path.join(repoRoot, "src", "lib", "inference", "config.ts"); +/** Extracts a bounded Markdown section so the assertions stay scoped to the guide. */ function sectionBetween(markdown: string, heading: string, nextHeading: string): string { const start = markdown.indexOf(heading); const end = markdown.indexOf(nextHeading, start + heading.length); @@ -22,10 +23,12 @@ function sectionBetween(markdown: string, heading: string, nextHeading: string): return markdown.slice(start, end); } +/** Removes TypeScript `as const` wrappers before inspecting literal AST nodes. */ function unwrapConstAssertion(expression: TypeScript.Expression): TypeScript.Expression { return ts.isAsExpression(expression) ? unwrapConstAssertion(expression.expression) : expression; } +/** Reads the curated cloud model IDs from the source config instead of duplicating them in docs tests. */ function readCuratedCloudModelIds(): string[] { const source = fs.readFileSync(inferenceConfigPath, "utf8"); const sourceFile = ts.createSourceFile( From fd4e480cd37afa655d8a3bfe1e069afdaea30b8c Mon Sep 17 00:00:00 2001 From: Ho Lim Date: Wed, 17 Jun 2026 18:13:39 -0700 Subject: [PATCH 3/6] test: use block docstrings for inference docs helpers --- test/inference-options-docs.test.ts | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/test/inference-options-docs.test.ts b/test/inference-options-docs.test.ts index 6ce2cd897dd..8932dc5b044 100644 --- a/test/inference-options-docs.test.ts +++ b/test/inference-options-docs.test.ts @@ -14,7 +14,9 @@ const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".." const inferenceOptionsPath = path.join(repoRoot, "docs", "inference", "inference-options.mdx"); const inferenceConfigPath = path.join(repoRoot, "src", "lib", "inference", "config.ts"); -/** Extracts a bounded Markdown section so the assertions stay scoped to the guide. */ +/** + * Extracts a bounded Markdown section so the assertions stay scoped to the guide. + */ function sectionBetween(markdown: string, heading: string, nextHeading: string): string { const start = markdown.indexOf(heading); const end = markdown.indexOf(nextHeading, start + heading.length); @@ -23,12 +25,16 @@ function sectionBetween(markdown: string, heading: string, nextHeading: string): return markdown.slice(start, end); } -/** Removes TypeScript `as const` wrappers before inspecting literal AST nodes. */ +/** + * Removes TypeScript `as const` wrappers before inspecting literal AST nodes. + */ function unwrapConstAssertion(expression: TypeScript.Expression): TypeScript.Expression { return ts.isAsExpression(expression) ? unwrapConstAssertion(expression.expression) : expression; } -/** Reads the curated cloud model IDs from the source config instead of duplicating them in docs tests. */ +/** + * Reads curated cloud model IDs from source config instead of duplicating them in docs tests. + */ function readCuratedCloudModelIds(): string[] { const source = fs.readFileSync(inferenceConfigPath, "utf8"); const sourceFile = ts.createSourceFile( From d5309baa7d6c9b6e6569d2babc9493689add12dc Mon Sep 17 00:00:00 2001 From: Ho Lim Date: Tue, 23 Jun 2026 17:27:24 -0700 Subject: [PATCH 4/6] docs: address task-fit guide review feedback --- docs/inference/inference-options.mdx | 4 +- test/inference-options-docs.test.ts | 83 +++++++++++----------------- 2 files changed, 35 insertions(+), 52 deletions(-) diff --git a/docs/inference/inference-options.mdx b/docs/inference/inference-options.mdx index 488ae152868..2d881a8043a 100644 --- a/docs/inference/inference-options.mdx +++ b/docs/inference/inference-options.mdx @@ -97,10 +97,10 @@ The relative labels below are qualitative and compare models within the curated | `claude-sonnet-4-6` | Balanced coding, writing, analysis, and multi-step tool work | Medium | Strong | Large agent context | Medium-to-high | | `claude-haiku-4-5` | Fast summarization, routing, extraction, and lightweight assistant turns | Low | Good for simple tool loops | Medium-to-large context | Low | | `claude-opus-4-6` | Deep analysis, careful writing, and quality-first planning | Higher | Strong | Large agent context | Higher | -| `gemini-3.1-pro-preview` | Large-context analysis, synthesis, and preview-feature evaluation | Medium-to-high | Good; validate tool continuation for the selected provider path | Very large context | Medium-to-high | +| `gemini-3.1-pro-preview` | Large-context analysis, synthesis, and preview-feature evaluation | Medium-to-high | Good; validate tool continuation for the selected provider path | Extensive context | Medium-to-high | | `gemini-3.1-flash-lite-preview` | Low-cost extraction, classification, and simple helper calls | Low | Basic to good for simple tool loops | Medium-to-large context | Low | | `gemini-3-flash-preview` | Fast general assistant tasks and preview-feature evaluation | Low | Good for simple tool loops | Large context | Low | -| `gemini-2.5-pro` | Large-context analysis, long-document synthesis, and complex reasoning | Medium-to-high | Good | Very large context | Medium-to-high | +| `gemini-2.5-pro` | Large-context analysis, long-document synthesis, and complex reasoning | Medium-to-high | Good | Extensive context | Medium-to-high | | `gemini-2.5-flash` | Latency-sensitive general assistant and multimodal tasks | Low | Good for simple tool loops | Large context | Low | | `gemini-2.5-flash-lite` | Lowest-cost helper calls, extraction, and classification | Very low | Basic to good for simple tool loops | Medium-to-large context | Very low | diff --git a/test/inference-options-docs.test.ts b/test/inference-options-docs.test.ts index 8932dc5b044..151c65a4376 100644 --- a/test/inference-options-docs.test.ts +++ b/test/inference-options-docs.test.ts @@ -14,17 +14,6 @@ const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".." const inferenceOptionsPath = path.join(repoRoot, "docs", "inference", "inference-options.mdx"); const inferenceConfigPath = path.join(repoRoot, "src", "lib", "inference", "config.ts"); -/** - * Extracts a bounded Markdown section so the assertions stay scoped to the guide. - */ -function sectionBetween(markdown: string, heading: string, nextHeading: string): string { - const start = markdown.indexOf(heading); - const end = markdown.indexOf(nextHeading, start + heading.length); - expect(start).toBeGreaterThanOrEqual(0); - expect(end).toBeGreaterThan(start); - return markdown.slice(start, end); -} - /** * Removes TypeScript `as const` wrappers before inspecting literal AST nodes. */ @@ -37,59 +26,53 @@ function unwrapConstAssertion(expression: TypeScript.Expression): TypeScript.Exp */ function readCuratedCloudModelIds(): string[] { const source = fs.readFileSync(inferenceConfigPath, "utf8"); - const sourceFile = ts.createSourceFile( - inferenceConfigPath, - source, - ts.ScriptTarget.Latest, - true, - ts.ScriptKind.TS, - ); + const sourceFile = ts.createSourceFile(inferenceConfigPath, source, ts.ScriptTarget.Latest, true); - for (const statement of sourceFile.statements) { - const isExported = statement.modifiers?.some( - (modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword, - ); - if (!isExported || !ts.isVariableStatement(statement)) continue; + const declaration = sourceFile.statements + .filter( + (statement): statement is TypeScript.VariableStatement => + ts.isVariableStatement(statement) && + (statement.modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword) ?? + false), + ) + .flatMap((statement) => Array.from(statement.declarationList.declarations)) + .find((candidate) => candidate.name.getText(sourceFile) === "CLOUD_MODEL_OPTIONS"); + expect(declaration).toBeTruthy(); - for (const declaration of statement.declarationList.declarations) { - if (declaration.name.getText(sourceFile) !== "CLOUD_MODEL_OPTIONS") continue; - const initializer = declaration.initializer && unwrapConstAssertion(declaration.initializer); - expect(initializer && ts.isArrayLiteralExpression(initializer)).toBe(true); + const initializer = declaration?.initializer && unwrapConstAssertion(declaration.initializer); + expect(initializer && ts.isArrayLiteralExpression(initializer)).toBe(true); - return (initializer as TypeScript.ArrayLiteralExpression).elements.map((element) => { - expect(ts.isObjectLiteralExpression(element)).toBe(true); - const idProperty = (element as TypeScript.ObjectLiteralExpression).properties.find( - (property) => - ts.isPropertyAssignment(property) && - property.name.getText(sourceFile) === "id" && - ts.isStringLiteralLike(unwrapConstAssertion(property.initializer)), - ); - expect(idProperty).toBeTruthy(); - const idInitializer = unwrapConstAssertion( - (idProperty as TypeScript.PropertyAssignment).initializer, - ); - return (idInitializer as TypeScript.StringLiteral).text; - }); - } - } - - throw new Error("CLOUD_MODEL_OPTIONS export was not found"); + return (initializer as TypeScript.ArrayLiteralExpression).elements.map((element) => { + expect(ts.isObjectLiteralExpression(element)).toBe(true); + const idProperty = (element as TypeScript.ObjectLiteralExpression).properties.find( + (property) => + ts.isPropertyAssignment(property) && + property.name.getText(sourceFile) === "id" && + ts.isStringLiteralLike(unwrapConstAssertion(property.initializer)), + ); + expect(idProperty).toBeTruthy(); + const idInitializer = unwrapConstAssertion( + (idProperty as TypeScript.PropertyAssignment).initializer, + ); + return (idInitializer as TypeScript.StringLiteral).text; + }); } describe("inference options model task-fit docs (#4755)", () => { it("keeps a per-model task-fit comparison table for curated onboarding models", () => { const markdown = fs.readFileSync(inferenceOptionsPath, "utf8"); - const section = sectionBetween( - markdown, - "## Model Task-Fit Guide", - "## Choosing the Right Option for Nemotron", - ); + const start = markdown.indexOf("## Model Task-Fit Guide"); + const end = markdown.indexOf("## Choosing the Right Option for Nemotron", start); + expect(start).toBeGreaterThanOrEqual(0); + expect(end).toBeGreaterThan(start); + const section = markdown.slice(start, end); expect(section).toContain( "| Model | Best-for task type | Relative latency | Tool-use quality | Context-window fit | Relative cost |", ); expect(section).toContain("provider catalog remains authoritative"); expect(section).not.toMatch(/\bTBD\b|\bTODO\b/i); + expect(section).not.toContain("Very large context"); const expectedModelIds = [ ...readCuratedCloudModelIds(), From e772e8df6cea0790b41a23338eca1fc75ce951b9 Mon Sep 17 00:00:00 2001 From: Miyoung Choi Date: Mon, 29 Jun 2026 16:11:24 -0700 Subject: [PATCH 5/6] Update test/inference-options-docs.test.ts --- test/inference-options-docs.test.ts | 63 +++++++++++++++++++++++++---- 1 file changed, 55 insertions(+), 8 deletions(-) diff --git a/test/inference-options-docs.test.ts b/test/inference-options-docs.test.ts index 151c65a4376..ffd65440b5f 100644 --- a/test/inference-options-docs.test.ts +++ b/test/inference-options-docs.test.ts @@ -13,6 +13,7 @@ const ts = require("typescript") as typeof TypeScript; const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); const inferenceOptionsPath = path.join(repoRoot, "docs", "inference", "inference-options.mdx"); const inferenceConfigPath = path.join(repoRoot, "src", "lib", "inference", "config.ts"); +const modelPromptsPath = path.join(repoRoot, "src", "lib", "inference", "model-prompts.ts"); /** * Removes TypeScript `as const` wrappers before inspecting literal AST nodes. @@ -21,12 +22,12 @@ function unwrapConstAssertion(expression: TypeScript.Expression): TypeScript.Exp return ts.isAsExpression(expression) ? unwrapConstAssertion(expression.expression) : expression; } -/** - * Reads curated cloud model IDs from source config instead of duplicating them in docs tests. - */ -function readCuratedCloudModelIds(): string[] { - const source = fs.readFileSync(inferenceConfigPath, "utf8"); - const sourceFile = ts.createSourceFile(inferenceConfigPath, source, ts.ScriptTarget.Latest, true); +function readExportedConstInitializer( + sourcePath: string, + exportName: string, +): { sourceFile: TypeScript.SourceFile; initializer: TypeScript.Expression } { + const source = fs.readFileSync(sourcePath, "utf8"); + const sourceFile = ts.createSourceFile(sourcePath, source, ts.ScriptTarget.Latest, true); const declaration = sourceFile.statements .filter( @@ -36,11 +37,21 @@ function readCuratedCloudModelIds(): string[] { false), ) .flatMap((statement) => Array.from(statement.declarationList.declarations)) - .find((candidate) => candidate.name.getText(sourceFile) === "CLOUD_MODEL_OPTIONS"); + .find((candidate) => candidate.name.getText(sourceFile) === exportName); expect(declaration).toBeTruthy(); const initializer = declaration?.initializer && unwrapConstAssertion(declaration.initializer); - expect(initializer && ts.isArrayLiteralExpression(initializer)).toBe(true); + expect(initializer).toBeTruthy(); + + return { sourceFile, initializer: initializer as TypeScript.Expression }; +} + +function readCuratedCloudModelIds(): string[] { + const { sourceFile, initializer } = readExportedConstInitializer( + inferenceConfigPath, + "CLOUD_MODEL_OPTIONS", + ); + expect(ts.isArrayLiteralExpression(initializer)).toBe(true); return (initializer as TypeScript.ArrayLiteralExpression).elements.map((element) => { expect(ts.isObjectLiteralExpression(element)).toBe(true); @@ -58,6 +69,42 @@ function readCuratedCloudModelIds(): string[] { }); } +function readRemoteModelIds(providerKey: string): string[] { + const { sourceFile, initializer } = readExportedConstInitializer( + modelPromptsPath, + "REMOTE_MODEL_OPTIONS", + ); + expect(ts.isObjectLiteralExpression(initializer)).toBe(true); + + const providerProperty = (initializer as TypeScript.ObjectLiteralExpression).properties.find( + (property) => + ts.isPropertyAssignment(property) && property.name.getText(sourceFile) === providerKey, + ); + expect(providerProperty).toBeTruthy(); + + const providerInitializer = unwrapConstAssertion( + (providerProperty as TypeScript.PropertyAssignment).initializer, + ); + expect(ts.isArrayLiteralExpression(providerInitializer)).toBe(true); + + return (providerInitializer as TypeScript.ArrayLiteralExpression).elements.map((element) => { + expect(ts.isStringLiteralLike(unwrapConstAssertion(element))).toBe(true); + return (unwrapConstAssertion(element) as TypeScript.StringLiteral).text; + }); +} + +/** + * Reads curated onboarding model IDs from source config instead of duplicating them in docs tests. + */ +function readCuratedOnboardingModelIds(): string[] { + return [ + ...readCuratedCloudModelIds(), + ...readRemoteModelIds("openai"), + ...readRemoteModelIds("anthropic"), + ...readRemoteModelIds("gemini"), + ]; +} + describe("inference options model task-fit docs (#4755)", () => { it("keeps a per-model task-fit comparison table for curated onboarding models", () => { const markdown = fs.readFileSync(inferenceOptionsPath, "utf8"); From cd46d8d29290c70674eec6f0686038815b11af44 Mon Sep 17 00:00:00 2001 From: Miyoung Choi Date: Mon, 29 Jun 2026 16:11:33 -0700 Subject: [PATCH 6/6] Update test/inference-options-docs.test.ts --- test/inference-options-docs.test.ts | 19 +------------------ 1 file changed, 1 insertion(+), 18 deletions(-) diff --git a/test/inference-options-docs.test.ts b/test/inference-options-docs.test.ts index ffd65440b5f..07f49f629e3 100644 --- a/test/inference-options-docs.test.ts +++ b/test/inference-options-docs.test.ts @@ -121,24 +121,7 @@ describe("inference options model task-fit docs (#4755)", () => { expect(section).not.toMatch(/\bTBD\b|\bTODO\b/i); expect(section).not.toContain("Very large context"); - const expectedModelIds = [ - ...readCuratedCloudModelIds(), - "gpt-5.4", - "gpt-5.4-mini", - "gpt-5.4-nano", - "gpt-5.4-pro-2026-03-05", - "claude-sonnet-4-6", - "claude-haiku-4-5", - "claude-opus-4-6", - "gemini-3.1-pro-preview", - "gemini-3.1-flash-lite-preview", - "gemini-3-flash-preview", - "gemini-2.5-pro", - "gemini-2.5-flash", - "gemini-2.5-flash-lite", - ]; - - for (const modelId of expectedModelIds) { + for (const modelId of readCuratedOnboardingModelIds()) { expect(section).toContain(`| \`${modelId}\` |`); } });