import type { CredentialRef, ModelFamilyListing } from "@yanlinglabs/winter-agent-sdk"; import { RuntimeSdkError } from "../errors.js"; /** * The two runtimes, spelled exactly as the Winter runtime's own messaging layer spells them * (`packages/runtime/src/messaging/adapter.ts`, which Task 0 moves to * `@yanlinglabs/winter-agent-sdk/messaging`). * * ONE DECLARATION, and this is it while Task 0 is in flight: when the messaging subpath lands, this * line becomes a re-export of the SDK's own `RuntimeKind` (see `src/seams/messaging-contract.ts`, * which carries the same note for the messaging shapes). The two unions are character-identical, so * the swap is type-identical and no lane's code moves. */ export type RuntimeKind = "winter-agent" | "claude-agent"; /** * WS-15 §1's persisted-choice contract, populated by the D13 selector at session creation. * * `sdkVersion` vs `engineVersion` are WS-02 §3's two separate identities: the wrapper's and the * runtime engine's. `reason` is a human-readable sentence naming the rule that fired — it is what a * host renders when a user asks "why is this session on that runtime", and what a support log needs * when a persisted selection looks surprising a month later. `decidedAt` is ISO-8601. */ export interface RuntimeSelection { runtimeKind: RuntimeKind; providerId: string; /** * The provider-qualified catalog **row key** (`anthropic/claude-opus-5`) — this session's model * IDENTITY, never the raw model id. * * READ IT AS AN IDENTITY, NOT AS A WIRE VALUE. The id a runtime actually sends is derived from the * row, never from this field: handing `modelRef` straight to a runtime's `model` option sends the * provider prefix with it, and that fails at the endpoint rather than at compile time. WS-17 row 17 * is why the field is qualified at all — `claude-opus-5` is six catalog rows behind six providers, * and two sessions on two of them must not persist byte-identical records. */ modelRef: string; family: string; /** * `"console-profile"` (router 0.0.4, C1): the Anthropic Console CLI's own PROFILE — `ANTHROPIC_PROFILE` * + `ANTHROPIC_CONFIG_DIR`, no bearer credential in the child at all (the profile store the CLI already * manages owns the token). Distinct from `"console-oauth"`, which injects a bearer pair the router * itself resolves (`ANTHROPIC_AUTH_TOKEN` + `ANTHROPIC_BASE_URL`) — reusing that family would either * demand a token this flow never has or let the two mechanisms silently race on precedence. * * WS-20: reachable automatically, by the row's PROVIDER rather than by declaration — the catalog's * `console` provider IS the Console arm, so `candidatesFor` (`./select-runtime.ts`) overrides any * row with `providerId === "console"` to this family regardless of its credential ref's storage kind. * It IS a member of `OFFICIAL_SERVED_AUTH_FAMILIES`, served unconditionally (no protocol gate) — * the D13 auto-selector routes to it on its own for a `console/*` row, the same as any other served * family. */ authFamily: "api-key" | "cloud-credential-chain" | "claude-oauth" | "console-oauth" | "console-profile" | "local-none" | "custom"; sdkVersion: string; engineVersion?: string; reason: string; decidedAt: string; } /** The auth families a `RuntimeSelection` can record, as a name (the union is pinned inline above). */ export type SelectionAuthFamily = RuntimeSelection["authFamily"]; /** * What the host knows about one provider BEYOND "a credential ref exists" — the two facts the D13 * branch is actually taken on. * * WHY THIS EXISTS. `CredentialPresence.byProvider` carries a `CredentialRef["kind"]`, which says * WHERE a credential is stored (`keychain` | `env` | `file` | `inline` | `aws-default-chain` | * `none`). D13 branches on something else entirely: WS-13 §9's own words are "routing decisions use * the persisted runtime profile/AUTH SOURCE — never a raw model-ID substring", and R-7b-1 spells the * official branch's served set as "API key, cloud credential chain, or an approved Claude OAuth". A * storage location cannot express any of those three, and it certainly cannot express the difference * between an Anthropic API key and a Claude OAuth credential, which is the difference between D13's * row 2 and its row 1. So the host — which has the provider descriptor — declares them. * * `protocols` is WS-13 §5's wire-dialect list (`ProviderProtocol` in the catalog: `anthropic-messages`, * `openai-chat-completions`, `bedrock-converse`, …), copied verbatim from the provider descriptor. * The selector reads exactly one bit out of it: does this backend speak the Anthropic dialect — * which is WS-13 §9's "Anthropic-protocol backend" test, and the thing that separates a Claude model * served by its vendor from the same Claude model resold through an OpenAI-shaped gateway. * * KEPT A BARE `string[]`, deliberately: the dialect vocabulary belongs to the catalog package, and * this package must not grow a dependency on it to spell one comparison. */ export interface ProviderAuthView { /** The auth family this provider's CONFIGURED credential belongs to (WS-14 §12's own table). */ authFamily: SelectionAuthFamily; /** The wire dialects the provider speaks, verbatim from its catalog descriptor. */ protocols?: readonly string[]; } /** * Which providers have a credential REF configured — never any credential material. * * WS-13c §4's filter step ("filter by configured credential ref") and WS-14 Execution amendments' * "a pinned alias resolves to the `anthropic` provider ONLY when a credential ref for it is * configured, and never by ambient environment scan" are both decided from this map. The value is the * ref's KIND (the SDK's own `CredentialRef["kind"]`), because the auth family follows from it and * nothing else here needs more. * * LANE D AMENDMENT — `byProvider` REMAINS THE FILTER, `authByProvider` REFINES IT. Presence of a * provider's key in `byProvider` is what admits its rows (unchanged, and it is the only admission * test: an absent key means "no configured credential ref", never "probe the environment"). What the * kind cannot do is name the auth FAMILY, so `authByProvider` carries it where the host knows it. A * provider with no entry there falls back to `authFamilyFromRefKind` in `./select-runtime.ts`, whose * one rule is that an OAuth family is NEVER inferred — `claude-oauth` and `console-oauth` are * reachable only by declaration, so the D14 ship gate can never be tripped by a guess. */ export interface CredentialPresence { readonly byProvider: Readonly>; /** Per-provider auth family + wire dialects, where the host knows them. See `ProviderAuthView`. */ readonly authByProvider?: Readonly>; } /** * The version identities stamped into a selection (WS-02 §3: "the runtime engine and wrapper carry * separate version identities — `sdkVersion` vs `engineVersion`"). * * A PURE FUNCTION CANNOT READ THEM. `RuntimeSelection.sdkVersion` is required and its value depends * on which runtime the table picked, so the caller supplies both candidates and the selector stamps * the one it chose. Absent → `UNKNOWN_VERSION`, which is an honest record rather than a plausible * lie; `selectionVersionsFrom(sdk.versions)` in `./select-runtime.ts` builds this from the * constructor's own matrix report, which is where a host already has the answer. */ export interface SelectionVersions { /** The Winter SDK's own version — stamped into every selection (WS-23: the only runtime). */ winterSdkVersion?: string; /** The runtime ENGINE's version, when the host knows it (WS-02 §3's second identity). */ engineVersion?: string; } /** The D13 decision's inputs. See this file's header for why no raw model id decides the branch. */ export interface SelectionInput { mode: "code" | "dispatch" | "chat"; requested: { slot?: string; model?: string; provider?: string; }; families: ModelFamilyListing; credentials: CredentialPresence; /** * WS-23: RETIRED, accepted and ignored. It told the table whether this router held the official * runtime; no router does any more, so a Claude model always selects the Winter runtime. Optional so * a host that still states it keeps compiling. */ hasClaudePeer?: boolean; /** * WS-23: RETIRED, accepted and ignored. D14's ship gate for Claude OAuth on the official runtime; a * Claude OAuth credential is refused whatever this says, because the runtime it gated is gone and it * never routes to Winter (D28). */ claudeOauthApproved?: boolean; persisted?: RuntimeSelection; /** Stamped into the produced record. Absent → `UNKNOWN_VERSION`. */ versions?: SelectionVersions; /** The ISO-8601 instant to record as `decidedAt`. Absent → now. Present makes a decision reproducible. */ now?: string; } /** * A typed refusal — never a substitution and never a different family (WS-13c §4's own words). * * `slot-unservable`: no candidate survived slot resolution (no configured credential ref, or the * slot names a family this catalog cannot serve). `claude-oauth-not-approved`: the D14 ship gate is * closed and the only route to this selection was Claude OAuth. `runtime-unavailable`: the runtime * the rule selected is not present (e.g. a `claude` slot that must run official, with no official * peer injected). `mode-forbids-runtime`: the mode×runtime matrix has no cell for this pair — * WS-15 §2's "Claude OAuth is Code-only even after D14 approval. Unsupported combinations are * explicit capabilities in the matrix, never silent fallbacks." * * THE FOURTH MEMBER IS LANE D'S ADDITION and it is the one that keeps a rule from becoming a * fallback: without it, Claude OAuth in Dispatch or Chat has exactly two expressible answers, and * both are wrong — route to the official runtime (violating D4's "Dispatch and Chat → Winter-only, * in-daemon") or route to Winter (violating D28's "Claude oAuth never routes to winter"). */ /** * WS-18 W18-3 (P10b): one alternative door a Claude row could be served through — configured or not. * * `authKind` is a `SelectionAuthFamily` for every entry `select-runtime.ts` produces today, but this * field is deliberately wider than that union: it is what a HOST'S hint renders verbatim, and a future * door (a new gateway auth shape the catalog names before this package's auth-family union grows to * match) must still be listable rather than dropped for having no matching literal. */ export interface SelectionAlternative { providerId: string; authKind: string; /** A short human label ("Anthropic API key", "Amazon Bedrock", …) — never a raw provider id alone. */ label: string; } export interface SelectionRefusal { refused: true; /** * `"no-credential"` (W18-3, P10b): a Claude-family model has no credential able to serve it through * ANY door — the catalog has rows for it, every one of them is just unconfigured. `"bare-model-id"` * (WS-20): `requested.model` had no `/` — a provider-qualified tag is the only spelling this phase * accepts. `"provider-mismatch"` (WS-20, review fix): the request's provider field names a different * provider than its model tag's prefix. Widened to `string` beyond the members this package still * returns itself, so a host relaying a refusal value through (e.g. a resume review that read one * from elsewhere) never has to invent a member. */ reason: "slot-unservable" | "claude-oauth-not-approved" | "runtime-unavailable" | "mode-forbids-runtime" | "no-credential" | "bare-model-id" | "provider-mismatch" | (string & {}); /** One sentence naming what was missing, for the host to surface verbatim. */ detail: string; /** * `reason: "no-credential"` ONLY: every catalog row able to serve the requested canonical model, * whether or not it is configured, grouped by provider and auth kind (W18-3). The daemon's hint is * built FROM this list, never from a hardcoded provider list — so an entry here is the only way a * door reaches the user. */ alternatives?: SelectionAlternative[]; } /** * Narrows the union without a `"refused" in x` incantation at every call site. * * GENERIC IN THE NON-REFUSAL HALF, because every door in `src/selection/` returns "the answer OR the * same refusal": `RuntimeSelection | SelectionRefusal` from the two selectors, `ChildRuntimePairing | * SelectionRefusal` from the pairing door. One guard for all of them is the point — a second guard * per return type is a second place for the discriminant to be spelled, and the discriminant is the * whole contract. */ export declare function isSelectionRefusal(value: T | SelectionRefusal): value is SelectionRefusal; /** * The refusal, thrown — how `RuntimeSdk.selectRuntime` reports one. * * THE PLAN PINS TWO SIGNATURES THAT DISAGREE, and this class is the reconciliation. The pure module * function above returns `RuntimeSelection | SelectionRefusal` (a refusal is data — the D13 table has * an answer for every input, and a caller doing capability discovery wants it as a value). The * `RuntimeSdk.selectRuntime` METHOD is pinned as returning `RuntimeSelection` alone, so on that door * a refusal has to leave some other way: it is thrown, carrying the refusal verbatim. A host that * wants the value calls the exported function; a host that wants the method catches this. */ export declare class SelectionRefusedError extends RuntimeSdkError { readonly refusal: SelectionRefusal; constructor(refusal: SelectionRefusal); } /** * A child's own request, resolved independently of the parent's runtime (R-7b-1). * * ONE OBJECT WHERE THE PLAN WRITES TWO ARGUMENTS. The plan's Task 5 line is * `selectChildRuntime(parent, child: { slot?; model?; provider? }, input)`; the spine pinned the * two-argument form the barrel and the seams already import. They describe the same information, so * this shape carries both halves: the `slot`/`model`/`provider` the spawn requested, and the context * the D13 table needs. `slot` is optional here for the same reason it is optional in * `SelectionInput.requested` — a spawn may name a model or a provider instead, and a child that names * nothing takes its session's active slot set, exactly like a top-level session. * * WHAT IS DELIBERATELY ABSENT: anything from the parent. `selectChildRuntime` reads its `parent` * argument for ONE purpose only — reporting whether the resulting pair is cross-runtime (Lane B's * routing bit) — and never as an input to the decision. That is R-7b-1's whole content, and making * the parent structurally absent from this type is what makes it true rather than merely intended. */ export interface ChildSelectionInput { /** The child's slot name (WS-13c §4 resolves it to a canonical model id). */ slot?: string; /** A canonical model id or a catalog row key, when the spawn names a model instead of a slot. */ model?: string; /** A pinned provider id (WS-13c §4's "a pinned `provider`"). */ provider?: string; mode: "code" | "dispatch" | "chat"; families: ModelFamilyListing; credentials: CredentialPresence; /** WS-23: retired, accepted and ignored — see `SelectionInput.hasClaudePeer`. */ hasClaudePeer?: boolean; /** WS-23: retired, accepted and ignored — see `SelectionInput.claudeOauthApproved`. */ claudeOauthApproved?: boolean; /** Stamped into the child's own record. */ versions?: SelectionVersions; /** The ISO-8601 instant to record as the child's `decidedAt`. */ now?: string; } export { selectRuntime } from "./select-runtime.js"; export { selectChildRuntime } from "./child-runtime.js";