opencode-cmd-provider 1.2.1 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,61 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.3.0 - 2026-08-23
4
+
5
+ Feature: dual-transport Provider API — non-Go plans now use the documented
6
+ `/provider/v1/*` endpoints with self-healing fallback to the legacy transport.
7
+
8
+ ### Features
9
+
10
+ - **Provider API routing** (issues #51, #53): non-Go plans (goat, pro, max,
11
+ max20, teampro, provider aliases) route through the documented Provider API —
12
+ `claude-*` models to `POST /provider/v1/messages` (Anthropic shape), everything
13
+ else to `POST /provider/v1/chat/completions` (OpenAI shape) — with retry,
14
+ timeout, abort, and redaction parity with the legacy transport. Go /
15
+ individual-go sessions stay byte-for-byte on the legacy `POST /alpha/generate`
16
+ wire format, proven by golden byte-parity tests.
17
+ - **Session-cached plan resolution** (issue #54): transport is chosen per model
18
+ instance from the shared plan-resolution seam — explicit override →
19
+ `COMMANDCODE_PLAN` env → cached `GET /alpha/whoami` → default Provider API.
20
+ Only a resolved `go` selects the legacy transport; next session after a plan
21
+ upgrade auto-switches.
22
+ - **Self-healing upgrade flip** (issue #56): if a plan-detection miss sends a
23
+ true Go user to the Provider API, a documented `403 upgrade_required` pins the
24
+ session to the legacy transport and retries the same call once there — no
25
+ second Provider API hit, no double-counted usage.
26
+ - **ZDR passthrough** (issue #57): `CMD_ZDR=1` sends `x-cmd-zdr: 1` on every
27
+ Provider API request; the documented `422 cmd_zdr_no_providers` flows through
28
+ the existing error/redaction pipeline. The legacy transport never sends it.
29
+ - **Transport hardening** (issue #58): the finish part now waits for a trailing
30
+ OpenAI usage-only chunk so cost reflects real token counts; non-image file
31
+ parts are rejected with a clear role-aware error instead of silently
32
+ base64-encoding; stateful SSE parsers complete tool calls whose arguments
33
+ arrive across multiple events.
34
+
35
+ ### Fixes
36
+
37
+ - Restored the "Command Code" TUI sidebar section for Ox Alpha and DeepSeek V4
38
+ Flash Vision (exp): the deals-coverage gate now fails loudly when scraped
39
+ records lack a snapshot model, and the fixtures were refreshed to cover every
40
+ model (issue #61).
41
+
42
+ ### Chores
43
+
44
+ - Added the `refresh` project skill documenting the offline catalog refresh
45
+ (`npm run refresh` from `tests/fixtures/*.html`).
46
+ - New test suites: provider transport, parity, upgrade-fallback, ZDR, and
47
+ deals coverage — all wired into `test:unit`.
48
+
49
+ ## 1.2.2 - 2026-08-22
50
+
51
+ Chore: catalog refresh to `command-code@1.32.1`.
52
+
53
+ - Added `deepseek/deepseek-v4-flash-vision-exp` (DeepSeek V4 Flash Vision (exp)):
54
+ 1M context, text+image input, reasoning efforts `high`/`max`, $0.22/$0.66 per
55
+ 1M input/output tokens.
56
+ - Added reasoning efforts for `stealth/ox-alpha` (`low`, `high`, `max`).
57
+ - Deals catalog unchanged (56 entries).
58
+
3
59
  ## 1.2.1 - 2026-08-21
4
60
 
5
61
  Fix: `/connect` no longer lists Command Code — the plugin failed to load.
@@ -1,7 +1,7 @@
1
- export declare const FACTS_SOURCE_URL = "https://unpkg.com/command-code@1.31.0/dist/bundled/command-code-knowledge/reference/models.md";
2
- export declare const MODALITIES_SOURCE_URL = "https://unpkg.com/command-code@1.31.0/dist/cli.mjs";
3
- export declare const FACTS_PACKAGE_VERSION = "1.31.0";
4
- export declare const FACTS_LAST_REFRESHED = "2026-08-21";
1
+ export declare const FACTS_SOURCE_URL = "https://unpkg.com/command-code@1.32.1/dist/bundled/command-code-knowledge/reference/models.md";
2
+ export declare const MODALITIES_SOURCE_URL = "https://unpkg.com/command-code@1.32.1/dist/cli.mjs";
3
+ export declare const FACTS_PACKAGE_VERSION = "1.32.1";
4
+ export declare const FACTS_LAST_REFRESHED = "2026-08-22";
5
5
  export declare const MODEL_EFFORTS: Readonly<Record<string, readonly string[]>>;
6
6
  export declare const MODEL_COSTS: Readonly<Record<string, {
7
7
  input: number;
@@ -4,17 +4,19 @@
4
4
  // command-code npm package's bundled model catalog (models.md), plus input
5
5
  // modalities parsed from its CLI bundle (dist/cli.mjs). Regenerate
6
6
  // with `npm run refresh:snapshot`.
7
- export const FACTS_SOURCE_URL = "https://unpkg.com/command-code@1.31.0/dist/bundled/command-code-knowledge/reference/models.md";
8
- export const MODALITIES_SOURCE_URL = "https://unpkg.com/command-code@1.31.0/dist/cli.mjs";
9
- export const FACTS_PACKAGE_VERSION = "1.31.0";
10
- export const FACTS_LAST_REFRESHED = "2026-08-21";
7
+ export const FACTS_SOURCE_URL = "https://unpkg.com/command-code@1.32.1/dist/bundled/command-code-knowledge/reference/models.md";
8
+ export const MODALITIES_SOURCE_URL = "https://unpkg.com/command-code@1.32.1/dist/cli.mjs";
9
+ export const FACTS_PACKAGE_VERSION = "1.32.1";
10
+ export const FACTS_LAST_REFRESHED = "2026-08-22";
11
11
  export const MODEL_EFFORTS = {
12
12
  "deepseek/deepseek-v4-pro": ["high", "max"],
13
13
  "deepseek/deepseek-v4-flash": ["high", "max"],
14
+ "deepseek/deepseek-v4-flash-vision-exp": ["high", "max"],
14
15
  "zai-org/GLM-5.3": ["low", "high", "max"],
15
16
  "zai-org/GLM-5.2": ["high", "max"],
16
17
  "Qwen/Qwen3.8-Max": ["low", "medium", "xhigh"],
17
18
  "Qwen/Qwen3.8-27B": ["low", "medium", "xhigh"],
19
+ "stealth/ox-alpha": ["low", "high", "max"],
18
20
  "claude-sonnet-5": ["low", "medium", "high", "xhigh", "max"],
19
21
  "claude-sonnet-4-6": ["low", "medium", "high", "xhigh", "max"],
20
22
  "claude-fable-5": ["low", "medium", "high", "xhigh", "max"],
@@ -40,6 +42,7 @@ export const MODEL_EFFORTS = {
40
42
  export const MODEL_COSTS = {
41
43
  "deepseek/deepseek-v4-pro": { input: 0.66, output: 1.98, cacheRead: 0.022, cacheWrite: 0 },
42
44
  "deepseek/deepseek-v4-flash": { input: 0.22, output: 0.66, cacheRead: 0.007, cacheWrite: 0 },
45
+ "deepseek/deepseek-v4-flash-vision-exp": { input: 0.22, output: 0.66, cacheRead: 0.01, cacheWrite: 0 },
43
46
  "moonshotai/Kimi-K3": { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 0 },
44
47
  "moonshotai/Kimi-K2.7-Code": { input: 0.95, output: 4, cacheRead: 0.19, cacheWrite: 0 },
45
48
  "moonshotai/Kimi-K2.7-Code-Highspeed": { input: 1.9, output: 8, cacheRead: 0.38, cacheWrite: 0 },
@@ -110,6 +113,7 @@ export const MODEL_INPUT_MODALITIES = {
110
113
  "claude-opus-5": ["text", "image"],
111
114
  "claude-sonnet-4-6": ["text", "image"],
112
115
  "claude-sonnet-5": ["text", "image"],
116
+ "deepseek/deepseek-v4-flash-vision-exp": ["text", "image"],
113
117
  "google/gemini-3.1-flash-lite": ["text", "image"],
114
118
  "google/gemini-3.5-flash": ["text", "image"],
115
119
  "google/gemini-3.5-flash-lite": ["text", "image"],
@@ -20,6 +20,7 @@ export const MODEL_SNAPSHOT = [
20
20
  { id: "gpt-5.4-mini", name: "GPT-5.4 Mini", contextLength: 400000 },
21
21
  { id: "deepseek/deepseek-v4-pro", name: "DeepSeek V4 Pro (latest)", contextLength: 1000000 },
22
22
  { id: "deepseek/deepseek-v4-flash", name: "DeepSeek V4 Flash (latest)", contextLength: 1000000 },
23
+ { id: "deepseek/deepseek-v4-flash-vision-exp", name: "DeepSeek V4 Flash Vision (exp)", contextLength: 1000000 },
23
24
  { id: "moonshotai/Kimi-K3", name: "Kimi K3", contextLength: 1000000 },
24
25
  { id: "moonshotai/Kimi-K2.7-Code", name: "Kimi K2.7 Code", contextLength: 256000 },
25
26
  { id: "moonshotai/Kimi-K2.7-Code-Highspeed", name: "Kimi K2.7 Code HighSpeed", contextLength: 262000 },
@@ -51,5 +51,5 @@ export interface ModelDeals {
51
51
  export declare const MODEL_DEALS: Readonly<Record<string, ModelDeals>>;
52
52
  export declare const PLAN_CATALOG: Readonly<Record<PlanId, PlanInfo>>;
53
53
  export declare const DEAL_SOURCE_URL = "https://commandcode.ai/docs/resources/pricing-limits";
54
- export declare const DEAL_LAST_REFRESHED = "2026-08-21";
54
+ export declare const DEAL_LAST_REFRESHED = "2026-08-22";
55
55
  export declare const DEAL_PACKAGE_VERSION = "docs";
@@ -13,6 +13,7 @@ export const MODEL_DEALS = {
13
13
  "claude-sonnet-4-6": { tier: "premium", benchmark: { "intelligence": 48.4 }, free: false, allowance: { "pro": 20 } },
14
14
  "claude-sonnet-5": { tier: "premium", benchmark: { "intelligence": 55.3, "tokPerSec": 81.9 }, free: false, allowance: { "pro": 20 } },
15
15
  "deepseek/deepseek-v4-flash": { tier: "opensource", benchmark: { "intelligence": 51.8, "tokPerSec": 114.6 }, peakOffPeak: { "peak": { "input": 0.44, "output": 1.32, "cacheRead": 0.014, "cacheWrite": 0 }, "offPeak": { "input": 0.22, "output": 0.66, "cacheRead": 0.007, "cacheWrite": 0 }, "windows": "01–04 & 06–10 UTC" }, free: false, allowance: { "goat": 60, "pro": 70 } },
16
+ "deepseek/deepseek-v4-flash-vision-exp": { tier: "opensource", peakOffPeak: { "peak": { "input": 0.44, "output": 1.32, "cacheRead": 0.02, "cacheWrite": 0 }, "offPeak": { "input": 0.22, "output": 0.66, "cacheRead": 0.01, "cacheWrite": 0 }, "windows": "01–04 & 06–10 UTC" }, free: false, allowance: { "goat": 20, "pro": 30 } },
16
17
  "deepseek/deepseek-v4-pro": { tier: "opensource", benchmark: { "intelligence": 53.2, "tokPerSec": 75.3 }, peakOffPeak: { "peak": { "input": 1.32, "output": 3.96, "cacheRead": 0.044, "cacheWrite": 0 }, "offPeak": { "input": 0.66, "output": 1.98, "cacheRead": 0.022, "cacheWrite": 0 }, "windows": "01–04 & 06–10 UTC" }, free: false, allowance: { "goat": 20, "pro": 30 } },
17
18
  "google/gemini-3.1-flash-lite": { tier: "premium", benchmark: { "intelligence": 25.6 }, free: false, allowance: { "pro": 20 } },
18
19
  "google/gemini-3.5-flash": { tier: "premium", benchmark: { "intelligence": 52 }, free: false, allowance: { "pro": 20 } },
@@ -47,6 +48,7 @@ export const MODEL_DEALS = {
47
48
  "Qwen/Qwen3.8-27B": { tier: "opensource", benchmark: { "intelligence": 52 }, free: false, allowance: { "goat": 70, "pro": 80 } },
48
49
  "Qwen/Qwen3.8-Max": { tier: "opensource", benchmark: { "intelligence": 58.1, "tokPerSec": 46.7 }, free: false, allowance: { "goat": 20, "pro": 30 } },
49
50
  "sakana/fugu-ultra": { tier: "premium", free: false },
51
+ "stealth/ox-alpha": { tier: "opensource", free: true },
50
52
  "stepfun/Step-3.5-Flash": { tier: "opensource", benchmark: { "intelligence": 26.5 }, free: false, allowance: { "goat": 20, "pro": 30 } },
51
53
  "stepfun/Step-3.7-Flash": { tier: "opensource", benchmark: { "intelligence": 30.9, "tokPerSec": 120.1 }, free: false, allowance: { "goat": 20, "pro": 30 } },
52
54
  "tencent/hy3-paid": { tier: "opensource", benchmark: { "intelligence": 42.2, "tokPerSec": 74.8 }, free: false, allowance: { "goat": 70, "pro": 80 } },
@@ -72,5 +74,5 @@ export const PLAN_CATALOG = {
72
74
  provider: { price: 15, credits: 0, window5h: 0, windowWeek: 0, display: "Provider" },
73
75
  };
74
76
  export const DEAL_SOURCE_URL = "https://commandcode.ai/docs/resources/pricing-limits";
75
- export const DEAL_LAST_REFRESHED = "2026-08-21";
77
+ export const DEAL_LAST_REFRESHED = "2026-08-22";
76
78
  export const DEAL_PACKAGE_VERSION = "docs";
@@ -1,7 +1,39 @@
1
1
  import { z } from "zod";
2
2
  import { type ModelDeals, type PlanId, type PlanInfo } from "./catalog.js";
3
3
  export declare function normalizePlan(value: unknown): PlanId | undefined;
4
- export declare function resolvePlan(planArg: string | undefined, env?: NodeJS.ProcessEnv): Promise<PlanId>;
4
+ /**
5
+ * Per-instance whoami cache. Passed by transport selection so the
6
+ * `GET /alpha/whoami` fetch happens at most once per model instance — even
7
+ * under concurrent first calls, because the cache holds the in-flight
8
+ * attempt; the Deals tool path resolves without a cache (fetch per call, as
9
+ * before).
10
+ */
11
+ export interface PlanResolutionCache {
12
+ /** The whoami attempt (in flight or settled); its `plan` is the resolved
13
+ * plan or undefined when the fetch failed / no key was present. */
14
+ whoami?: Promise<{
15
+ plan: PlanId | undefined;
16
+ }>;
17
+ }
18
+ export interface ResolvePlanOptions {
19
+ /** Fallback when no override resolves and whoami yields nothing. Deals
20
+ * intelligence keeps the "go" default; transport selection passes
21
+ * "provider". */
22
+ defaultPlan?: PlanId;
23
+ /** Per-instance whoami cache (see PlanResolutionCache). */
24
+ cache?: PlanResolutionCache;
25
+ /** Resolved API key for the whoami GET (defaults to the
26
+ * COMMANDCODE_API_KEY env var); transport selection passes the model's
27
+ * resolved key. */
28
+ apiKey?: string;
29
+ /** Base URL for the whoami GET (defaults to getApiBase(env)); transport
30
+ * selection passes the model's baseURL option. */
31
+ baseURL?: string;
32
+ /** Fetch implementation for the whoami GET (defaults to the global fetch),
33
+ * so transport selection honours the same injected fetch as inference. */
34
+ fetch?: typeof fetch;
35
+ }
36
+ export declare function resolvePlan(planArg: string | undefined, env?: NodeJS.ProcessEnv, options?: ResolvePlanOptions): Promise<PlanId>;
5
37
  export declare function renderPlanSummary(plan: PlanId, deals?: Readonly<Record<string, ModelDeals>>, catalog?: Readonly<Record<PlanId, PlanInfo>>): string;
6
38
  export declare function planSummaryTool(): {
7
39
  description: string;
@@ -1,7 +1,9 @@
1
1
  // src/deals/plan-summary.ts — cmd_plan_summary tool: plan-aware allowance
2
2
  // breakdown. Plan resolution: tool arg → COMMANDCODE_PLAN → live /alpha/whoami
3
- // (when a key is present and the network works) → default "go". Rendering is a
4
- // pure function so tests never touch the network.
3
+ // (when a key is present and the network works) → default. Deals intelligence
4
+ // keeps the "go" default; transport selection reuses resolvePlan() with
5
+ // { defaultPlan: "provider" } so only a resolved "go" picks the legacy
6
+ // transport. Rendering is a pure function so tests never touch the network.
5
7
  import { z } from "zod";
6
8
  import { MODEL_COSTS } from "../catalog/facts.js";
7
9
  import { MODEL_DEALS, PLAN_CATALOG, } from "./catalog.js";
@@ -44,31 +46,45 @@ export function normalizePlan(value) {
44
46
  return undefined;
45
47
  return PLAN_ALIASES[value.toLowerCase()];
46
48
  }
47
- export async function resolvePlan(planArg, env = process.env) {
49
+ export async function resolvePlan(planArg, env = process.env, options = {}) {
48
50
  const fromArg = normalizePlan(planArg);
49
51
  if (fromArg)
50
52
  return fromArg;
51
53
  const fromEnv = normalizePlan(env.COMMANDCODE_PLAN);
52
54
  if (fromEnv)
53
55
  return fromEnv;
54
- if (env.COMMANDCODE_API_KEY) {
55
- try {
56
- const response = await fetch(`${getApiBase(env)}/alpha/whoami`, {
57
- headers: { authorization: `Bearer ${env.COMMANDCODE_API_KEY}` },
58
- signal: AbortSignal.timeout(5000),
59
- });
60
- if (response.ok) {
61
- const body = (await response.json());
62
- const plan = normalizePlan(body.planId ?? body.plan?.id);
63
- if (plan)
64
- return plan;
56
+ const cache = options.cache;
57
+ const whoamiPlan = cache?.whoami
58
+ ? (await cache.whoami).plan
59
+ : (await fetchWhoamiPlan(env, cache, options)).plan;
60
+ return whoamiPlan ?? options.defaultPlan ?? "go";
61
+ }
62
+ async function fetchWhoamiPlan(env, cache, options) {
63
+ const key = options.apiKey ?? env.COMMANDCODE_API_KEY;
64
+ const base = options.baseURL ?? getApiBase(env);
65
+ const fetchImpl = options.fetch ?? fetch;
66
+ const attempt = (async () => {
67
+ let plan;
68
+ if (key) {
69
+ try {
70
+ const response = await fetchImpl(`${base}/alpha/whoami`, {
71
+ headers: { authorization: `Bearer ${key}` },
72
+ signal: AbortSignal.timeout(5000),
73
+ });
74
+ if (response.ok) {
75
+ const body = (await response.json());
76
+ plan = normalizePlan(body.planId ?? body.plan?.id);
77
+ }
78
+ }
79
+ catch {
80
+ // offline, timeout or unreachable — fall through to the default
65
81
  }
66
82
  }
67
- catch {
68
- // offline or unreachable — fall through to the default
69
- }
70
- }
71
- return "go";
83
+ return { plan };
84
+ })();
85
+ if (cache)
86
+ cache.whoami = attempt;
87
+ return attempt;
72
88
  }
73
89
  const REQUEST_PROFILE = { input: 800, output: 200, cacheRead: 50_000 };
74
90
  export function renderPlanSummary(plan, deals = MODEL_DEALS, catalog = PLAN_CATALOG) {
package/dist/src/env.d.ts CHANGED
@@ -1,2 +1,11 @@
1
1
  export declare const DEFAULT_API_BASE = "https://api.commandcode.ai";
2
2
  export declare function getApiBase(env?: NodeJS.ProcessEnv): string;
3
+ /**
4
+ * Zero data retention (ZDR) opt-in, mirroring the CLI's `CMD_ZDR=1` (per the
5
+ * Provider API docs: "the same opt-in the CLI exposes via CMD_ZDR=1"). Only
6
+ * the exact value `1` opts in — unset, empty, `0`, or any other value leaves
7
+ * ZDR off, so no `x-cmd-zdr` header is ever sent unless the user explicitly
8
+ * asked for it. The legacy /alpha/generate transport never sends the header
9
+ * regardless of this value.
10
+ */
11
+ export declare function getCmdZdr(env?: NodeJS.ProcessEnv): boolean;
package/dist/src/env.js CHANGED
@@ -3,3 +3,14 @@ export const DEFAULT_API_BASE = "https://api.commandcode.ai";
3
3
  export function getApiBase(env = process.env) {
4
4
  return env.COMMANDCODE_API_BASE ?? DEFAULT_API_BASE;
5
5
  }
6
+ /**
7
+ * Zero data retention (ZDR) opt-in, mirroring the CLI's `CMD_ZDR=1` (per the
8
+ * Provider API docs: "the same opt-in the CLI exposes via CMD_ZDR=1"). Only
9
+ * the exact value `1` opts in — unset, empty, `0`, or any other value leaves
10
+ * ZDR off, so no `x-cmd-zdr` header is ever sent unless the user explicitly
11
+ * asked for it. The legacy /alpha/generate transport never sends the header
12
+ * regardless of this value.
13
+ */
14
+ export function getCmdZdr(env = process.env) {
15
+ return env.CMD_ZDR === "1";
16
+ }
@@ -10,6 +10,7 @@ export interface CommandCodeModelOptions {
10
10
  maxRetries?: number;
11
11
  maxRetryDelayMs?: number;
12
12
  authPaths?: readonly string[];
13
+ plan?: string;
13
14
  }
14
15
  export declare class CommandCodeLanguageModel implements LanguageModelV3 {
15
16
  private readonly options;
@@ -22,6 +23,34 @@ export declare class CommandCodeLanguageModel implements LanguageModelV3 {
22
23
  constructor(options: CommandCodeModelOptions, modelId: string);
23
24
  private apiBase;
24
25
  private costForModel;
26
+ /**
27
+ * Per-instance whoami cache: the `GET /alpha/whoami` fetch happens at most
28
+ * once for the lifetime of this model instance and is reused across turns.
29
+ */
30
+ private readonly planCache;
31
+ /**
32
+ * Safety-net flag (issue #56): once the Provider API answers a documented
33
+ * `403 upgrade_required`, the session is pinned to the legacy
34
+ * `/alpha/generate` transport for the lifetime of this model instance —
35
+ * subsequent turns stay on legacy without re-hitting the Provider API (no
36
+ * second 403). The Provider API has no path for Go-plan users (that is
37
+ * exactly what the 403 documents), so the plugin's legacy transport is the
38
+ * only way to keep serving a plan-detection miss that routed a true Go user
39
+ * there.
40
+ */
41
+ private pinnedToLegacy;
42
+ /**
43
+ * Resolves the transport plan through the shared plan-resolution seam:
44
+ * explicit override (providerOptions plan, model option `plan`) →
45
+ * COMMANDCODE_PLAN env → cached whoami → default Provider API. Only a
46
+ * resolved `go` selects the legacy transport; every other resolution
47
+ * selects the Provider API. The whoami fetch is cached for the lifetime of
48
+ * this instance (see planCache) and honours the same resolved key, base URL
49
+ * and injected fetch as inference.
50
+ */
51
+ private shouldUseProviderTransport;
52
+ private planArgFor;
53
+ private providerEndpoint;
25
54
  doGenerate(options: ModelCallOptions): Promise<LanguageModelV3GenerateResult>;
26
55
  doStream(options: ModelCallOptions): Promise<{
27
56
  stream: ReadableStream<LanguageModelV3StreamPart>;
@@ -35,5 +64,25 @@ export declare class CommandCodeLanguageModel implements LanguageModelV3 {
35
64
  private runOnce;
36
65
  private bodyFor;
37
66
  private headersFor;
67
+ private providerBodyFor;
68
+ private providerHeadersFor;
69
+ private providerRunStream;
38
70
  private runStream;
71
+ /**
72
+ * Deep internal seam: single SSE transport behind a small interface.
73
+ * All retry/timeout/abort/redaction/stream-parsing/cost/fallback logic
74
+ * lives here; callers supply only the endpoint URL, body, headers and
75
+ * the event→parts mapper. Depth gives leverage (N callers) and locality
76
+ * (fix once, fixed everywhere). The eventToParts adapter varies across
77
+ * the seam (CC vs OpenAI vs Anthropic) while the transport stays fixed.
78
+ *
79
+ * The optional legacyFallback implements the issue #56 safety net: when the
80
+ * Provider API answers a documented `403 upgrade_required` (Go plan, no API
81
+ * access), the session is pinned to the legacy `/alpha/generate` transport
82
+ * and the same call retries once there — the retry is bounded because only
83
+ * the provider descriptor carries flipOnUpgradeRequired. The pin is sticky
84
+ * for the lifetime of this model instance (no second Provider API hit on
85
+ * later turns).
86
+ */
87
+ private transportStream;
39
88
  }