{"version":3,"sources":["../src/llm.ts"],"sourcesContent":["// Provider-agnostic LLM chat — the `llm.chat@1` slot (SERVICE_PROVIDERS_SPEC;\n// LLM_AND_AGENTS_SPEC §8 D5).\n//\n// An app calls ONE chat slot and never worries about which provider the user has a\n// key for: the HOST resolves which vendor answers from the key the user holds\n// (`SecretView.boundOrigin`) plus their `preferredImplementation` choice, normalizes\n// the wire format, injects the key host-side at the §6 net:fetch point (the\n// look-at-nothing proxy), and streams normalized deltas back. The app never names a\n// vendor, never sees the key, and needs NO `net:fetch`/`secrets` grant of its own —\n// only the `llm:chat` capability (elevated, app-scoped: a fork earns it by consent).\n//\n// Inert until the host implements `protocol-llm` (the `chat` stream) + the\n// `llm-provider` describe channel; the contract ships here so apps (the file-explorer\n// summarize fork) can be written against it — exactly how `secrets.ts` shipped ahead\n// of `protocol-secrets`.\nimport { invokeStream } from './catalog';\nimport { createPushChannel } from './pushChannel';\nimport { LLM_PROVIDER, REQUEST_LLM_PROVIDER } from './generated/protocol';\n\n/** Who authored a {@link ChatMessage}. */\nexport type ChatRole = 'system' | 'user' | 'assistant' | 'tool';\n\n/** A part of a message. `image` is only honored when the resolved provider\n *  advertises `features.vision` (§2.5); `tool-use`/`tool-result` only when it\n *  advertises `features.tools` — branch on {@link describeChat} first. */\nexport type ContentPart =\n  | { type: 'text'; text: string }\n  | { type: 'image'; mimeType: string; data: string } // data: base64, no data: URL prefix\n  // A tool call the model emitted on a prior `assistant` turn — replay it in the\n  // conversation so a follow-up request carries the agentic history. Pairs with the\n  // streamed `tool-call` {@link ChatDelta} that first surfaced it.\n  | { type: 'tool-use'; id: string; name: string; input: Record<string, unknown> }\n  // A block of the model's own REASONING from a prior `assistant` turn (R3-335).\n  // Honored only when the resolved provider advertises `features.reasoning`.\n  //\n  // Echo these back. On some providers a reasoning block must be replayed — with its\n  // `signature` intact and BEFORE the turn's text/tool-use — for the following turn to\n  // be accepted at all; a loop that drops them is quietly lossy across turns in a way\n  // that shows up as degraded output rather than an error. Pairs with the streamed\n  // `reasoning` {@link ChatDelta}.\n  | { type: 'reasoning'; text: string; signature?: string }\n  // Reasoning the provider REDACTED: opaque bytes with no readable text, which still\n  // have to be echoed back in place to keep the chain valid. Never render it.\n  | { type: 'reasoning-redacted'; data: string }\n  // The result of executing a `tool-use`, fed back so the model can continue. Carried\n  // on a `user`/`tool`-role message; `toolCallId` matches the `tool-use` `id`.\n  | { type: 'tool-result'; toolCallId: string; content: string; isError?: boolean };\n\n/** One message in a {@link ChatRequest}: a role plus its content parts. */\nexport interface ChatMessage {\n  role: ChatRole;\n  content: ContentPart[];\n}\n\n/** A tool the model may call — honored only when `features.tools`. */\nexport interface ToolDef {\n  name: string;\n  description?: string;\n  /** JSON-Schema for the tool's arguments. */\n  inputSchema: Record<string, unknown>;\n}\n\n/** A host-brokered chat completion request: the messages plus optional tools,\n *  response format, and model hint (each honored per the provider's features). */\nexport interface ChatRequest {\n  messages: ChatMessage[];\n  /** Honored only when the resolved provider advertises `features.tools`. */\n  tools?: ToolDef[];\n  /** `'json'` honored only when `features.jsonMode`. Defaults to `'text'`. */\n  responseFormat?: 'text' | 'json';\n  maxTokens?: number;\n  /** An ABSTRACT tier hint, never a vendor model id — the host maps it to a concrete\n   *  model on the resolved provider. Omit to take the provider's default. */\n  modelHint?: 'fast' | 'smart';\n  /** A concrete provider-and-model choice, naming one of the user's CONNECTED providers\n   *  (R3-620, LLM_AND_AGENTS_SPEC §0 editing-session exception). When present it WINS over\n   *  `modelHint`; the host validates the pair against the user's connected set and refuses\n   *  with `provider-not-connected` otherwise. Only an editing-session principal may read the\n   *  chooseable set (via `describeChat()`'s `connectedProviders`, gated `llm:chooseModel`);\n   *  a stage app still passes at most the abstract hint. */\n  model?: { providerId: string; model: string };\n  /** Abort the completion mid-stream. When it fires, the SDK sends the host a cancel\n   *  frame so the host aborts the upstream provider request and STOPS BILLING the\n   *  user's key — not merely stops the app-side iterator (LLM_AND_AGENTS_SPEC §3.3\n   *  \"abort the in-flight LLM request\", R3-224). Not sent over the wire (an\n   *  `AbortSignal` isn't serializable); handled SDK-side. */\n  signal?: AbortSignal;\n}\n\n/** One streamed chunk. Consumers typically accumulate `text-delta`s. */\nexport type ChatDelta =\n  | { type: 'text-delta'; text: string }\n  | { type: 'tool-call'; id: string; name: string; input: unknown }\n  // R3-335 — the model's reasoning as it streams. `reasoning-delta` carries the text\n  // incrementally (render it live); the terminal `reasoning` carries the WHOLE block\n  // plus the `signature` the provider may require on the echo, and is what a caller\n  // should put back into the conversation. A provider without reasoning emits neither.\n  | { type: 'reasoning-delta'; text: string }\n  | { type: 'reasoning'; text: string; signature?: string }\n  | { type: 'reasoning-redacted'; data: string }\n  // Token accounting for the turn. `cacheReadTokens`/`cacheWriteTokens` are present\n  // only on providers that report prompt caching (R3-336) — they are what makes a\n  // caching claim verifiable rather than believed, and their ABSENCE is meaningful:\n  // it says this provider reports nothing, not that nothing was cached.\n  | {\n      type: 'usage';\n      inputTokens: number;\n      outputTokens: number;\n      cacheReadTokens?: number;\n      cacheWriteTokens?: number;\n      /** R3-1124 (LLM_AND_AGENTS §4.2): the round's price. The host's estimate from the\n       *  catalogue rates unless the provider returned its own cost (OpenRouter's\n       *  `usage.cost` — then `estimated` is false and that value wins). Absent when no\n       *  rate and no schedule exists — unknown is absent, never zero. Credits are the\n       *  plan's own unit, never converted. */\n      cost?: { amount: number; unit: 'usd' | 'credits'; estimated: boolean; rule?: string };\n    };\n\n/** Why generation stopped: natural `end`, `length` cap, a `tool` call, or content `filtered`. */\nexport type ChatStopReason = 'end' | 'length' | 'tool' | 'filtered';\n\n/** The terminal value of the {@link chat} stream. */\nexport interface ChatResult {\n  stopReason: ChatStopReason;\n}\n\n/**\n * Stream a chat completion from whichever provider the user has configured.\n *\n * ```ts\n * let summary = '';\n * for await (const d of chat({ messages: [{ role: 'user', content: [{ type: 'text', text }] }] })) {\n *   if (d.type === 'text-delta') summary += d.text;\n * }\n * ```\n *\n * Requires the `llm:chat` capability. If no provider is bound, the host first\n * draws the SP-7 connect-me gate itself (R3-456: the app never draws a\n * credential prompt — that is host chrome, SECRETS_SPEC S3):\n * - the user connects a key → the call retries once and streams normally;\n * - the user declines → the generator throws `code: 'cancelled'` (the same code\n *   a declined powerbox produces — a working degraded state: catch it and\n *   degrade, e.g. skip the AI feature);\n * - an older host without the gate throws `code: 'provider-not-configured'`.\n *\n * `provider-not-configured` also fires when a key EXISTS but was never entered\n * under the routing the provider now takes (R3-583, SECRETS_SPEC §2.1 C6 /\n * §7.1 S4): a key an app added through `requestAddSecret`, or one sealed before\n * the provider's row moved to the proxied path, is refused because its routing\n * disclosure was never shown. The host raises the same connect gate in-call,\n * carrying the re-entry door, so the throw means the user declined it or\n * settled without a usable key; the recovery is the user entering the key again\n * under the disclosure. The app cannot repair this itself — surface the refusal\n * (its message is the re-entry instruction). A decline latches for the app's\n * session (TS-19/21: the dialog must not be farmed), so a retry re-throws\n * without re-opening the gate; the user re-enters the key from Settings or in a\n * fresh session. Do not retry in a loop.\n *\n * A signed-out user throws `code: 'auth-required'`; an un-granted call throws\n * `forbidden`.\n */\nexport function chat(req: ChatRequest): AsyncGenerator<ChatDelta, ChatResult, void> {\n  // Peel `signal` out of the request before it becomes wire params — an AbortSignal\n  // can't cross the postMessage boundary as data; it drives the SDK-side cancel frame.\n  const { signal, ...params } = req;\n  return invokeStream<ChatDelta, ChatResult>('llm:chat', params as unknown as Record<string, unknown>, signal);\n}\n\n/** The resolved provider's advertised abilities (SERVICE_PROVIDERS_SPEC §2.5) — read\n *  to branch/degrade (offer image upload only when `vision`). */\nexport interface ChatFeatures {\n  vision: boolean;\n  tools: boolean;\n  jsonMode: boolean;\n  /** R3-335: the provider emits reasoning blocks. Read it to decide whether to render\n   *  a thinking surface at all — an empty affordance on a provider that never thinks\n   *  is worse than none. Normalized to `false` by the channel when a host predating\n   *  R3-335 omits it, so this is never `undefined` in practice. */\n  reasoning: boolean;\n  maxContextTokens: number;\n}\n\n/** How the resolved provider's requests physically leave the browser. Read it to\n *  DESCRIBE routing where that matters to the person (\"runs in your browser\" vs\n *  \"routed through immediately.run\") — never to draw host chrome or a consent prompt,\n *  which remain the host's (UI_AS_APPS §8 T15). */\nexport type ChatExecutor = 'browser-direct' | 'backend-proxied';\n\n/** The concrete model each abstract tier resolves to right now. */\nexport interface ChatTierModels {\n  fast: string;\n  smart: string;\n}\n\n/** One CONNECTED provider the user may choose a model from, for a per-conversation model\n *  choice in an editing-session workbench (R3-620, LLM_AND_AGENTS_SPEC §0). Names and ids\n *  only — never keys, usage, balance or routing. */\nexport interface ChatProviderChoice {\n  /** Opaque provider id, e.g. `llm.chat.anthropic` — matches {@link ChatRequest.model}.providerId. */\n  providerId: string;\n  /** The provider's human name, for a picker label. */\n  displayName: string;\n  /** The chooseable model names — the catalogue-recommended set plus the two the user has\n   *  chosen. A closed list here would re-introduce the ids-rot problem, so `model` is passed\n   *  through to the adapter exactly as the Settings field is; this list is suggestions. */\n  models: string[];\n  /** R3-1074: THIS provider's adapter features, so a run on a chosen provider sizes itself\n   *  by that provider's context window and vision rather than the default provider's (or the\n   *  conservative nothing). Absent on a host predating the field — keep your conservative\n   *  answer then. Optional on the wire, so an older host stays valid. */\n  features?: ChatFeatures;\n}\n\n/** Info about the provider the host resolved for this app. `null` when no provider\n *  is bound (SP-7: prompt the user to add a key before calling {@link chat}). */\nexport interface ChatProviderInfo {\n  /** Opaque provider id, e.g. `llm.chat.anthropic` — never a vendor secret or model id. */\n  providerId: string;\n  /** True for Host-proxied providers (host-vouched, SP-9); false for app-level ones,\n   *  whose `features` are an untrusted claim. */\n  hostVouched: boolean;\n  features: ChatFeatures;\n  /** The provider's human name, e.g. `OpenRouter` — what to put in front of a person.\n   *  Absent on a host that predates the field: fall back to your own copy rather than\n   *  rendering the id, which is a platform identifier and not a name. */\n  displayName?: string;\n  /** How this provider's requests leave the browser. Absent on a host that predates the\n   *  field, which is NOT the same as `browser-direct` — say nothing about routing rather\n   *  than guess at it. */\n  executor?: ChatExecutor;\n  /**\n   * The concrete model each {@link ChatRequest.modelHint} tier resolves to — what a\n   * `smart` request would actually run, after the user's own preference.\n   *\n   * Read-only, and it does not weaken `LLM_AND_AGENTS_SPEC §0`: an app still names no\n   * model, and {@link ChatRequest} still carries only the abstract hint. It is here so an\n   * app can be HONEST about what answered — a transcript that says which model wrote a\n   * reply, a warning that names the model about to be spent on — instead of showing a\n   * blank where the platform knows the answer. The user picks the model in host settings;\n   * the app reports it.\n   *\n   * Absent on a host that predates the field. It changes when the user changes their\n   * preference, so read it through {@link onChatProviderChange} rather than caching it.\n   */\n  models?: ChatTierModels;\n  /**\n   * The user's CONNECTED providers and their chooseable models, for a per-conversation\n   * model choice (R3-620). Only present when this frame holds the ELEVATED `llm:chooseModel`\n   * capability (an editing-session workbench); the host strips it for everyone else, so a\n   * stage app sees `undefined` and can offer only the abstract `modelHint` path.\n   *\n   * Read-only: names and ids only. No keys, usage, balance or routing. Absent also on a host\n   * that predates the field.\n   */\n  connectedProviders?: ChatProviderChoice[];\n}\n\n/**\n * Whether the host has told us about a provider yet, and if so whether one is bound —\n * and, since R3-688, whether this frame may ask at all.\n *\n * THREE states, because two is the bug (R3-300). `describeChat()` returns `null` both\n * when no provider is configured AND when the channel has not answered — so an app\n * cannot tell \"you need a key\" from \"ask again in a moment\", and consuming apps\n * rendered a misleading \"connect a key\" banner at users who had one. `unknown` is the\n * state before the host answers; it is not an error and not a prompt to act.\n *\n * **`unknown` is TRANSIENT — the host answers every frame** (R3-419;\n * `LLM_AND_AGENTS_SPEC §4.1` R-LLM-1..3). An app that does not hold `llm:chat` is not\n * met with silence. So it is correct to treat a `unknown` that persists as a host bug\n * rather than as a state to design around, and WRONG to render a spinner with no\n * timeout on it. (Before R3-419 the host withheld the channel entirely from an\n * ungranted frame, and `unknown` stood forever — that is the failure this note exists\n * to keep from being re-created on the app side.)\n *\n * The FOURTH state, `ungranted` (R3-688; `LLM_AND_AGENTS_SPEC §4.1` R-LLM-2 as\n * annotated, `GROVE_AGENT_SPEC` G-GA-10): the host answered, and the reason this frame\n * cannot reach a model is that **it does not hold `llm:chat`** — not that the user has\n * no key. R3-419 deliberately answered an ungranted frame with the same payload as a\n * keyless one, because the SDK could not carry the difference; that made the distinct\n * \"not granted\" cause the reach-card contract promises (G-GA-10) uncomputable — an\n * ungranted fork of an app rendered \"connect a key\" at a user who had one. The host now\n * marks the grantless answer with an optional `ungranted: true` on the SAME message\n * (additive — an older SDK never reads the field and degrades to `not-configured`;\n * an older host never sends the field and the app degrades the same way). The mark is\n * HOST-derived from the frame's resolved grant set at push time (the channel view in\n * `channelAcl.ts`), never from anything this frame asserts.\n */\nexport type ChatProviderState =\n  | { status: 'unknown' }\n  | { status: 'not-configured' }\n  | { status: 'ungranted' }\n  | { status: 'configured'; provider: ChatProviderInfo };\n\n// The `llm-provider` describe channel (Recipe A): the host pushes the resolved\n// provider info on change and replays it on register-frame, gated by `llm:chat`.\n// A message with no `provider` key is ignored; an explicit `null` means \"no provider\n// bound\", which is now REPRESENTABLE as distinct from \"not yet answered\".\n// The channel's VALUE stays exactly what the wire carries — `ChatProviderInfo | null` —\n// because the protocol snapshot gate reads this type as the channel's shape. The state\n// read lives BESIDE it: `answered` records whether the host has ever spoken on this\n// channel, which is the one bit `null` cannot carry, and `ungrantedMark` (R3-688) reads\n// the host's optional grantless marker off the same message — also beside the value, not\n// inside it. Deriving the state rather than widening the channel keeps the declared wire\n// VALUE unchanged (SDK_PACKAGING_SPEC §9: additive-only; the optional message field\n// follows the `theme`/`form-factor` insets precedent — old readers ignore it, and the\n// sandbox-protocol descriptor's \"read off\" line catches up at its next release).\n/**\n * Reconcile what the host actually sent with what this SDK declares.\n *\n * `features.reasoning` arrived after `ChatFeatures` shipped, so a host predating it\n * omits the key. `undefined` reads as falsy everywhere EXCEPT a `'reasoning' in\n * features` check, which is exactly the kind of difference that produces one wrong\n * branch a year later — so it is normalized here, once, rather than left to every\n * caller. Absent means \"does not reason\": the fail-closed reading.\n *\n * `displayName`, `executor` and `models` arrived later still, and for them absence is a\n * REAL answer an app is told to handle (\"this host does not say\"), so they are left\n * absent rather than filled in. What is dropped is a value that is present but not\n * usable — an `executor` outside the union, a `models` missing a tier — because a\n * half-answer rendered as fact is worse than the honest blank the app already handles.\n *\n * Exported for its own test; not part of the public surface (`index.ts` re-exports\n * this module wholesale, so it is reachable — it is documented as internal rather\n * than hidden behind a lie).\n * @internal\n */\nconst EXECUTORS: readonly ChatExecutor[] = ['browser-direct', 'backend-proxied'];\n\nconst usableModels = (raw: unknown): ChatTierModels | undefined => {\n  if (!raw || typeof raw !== 'object') return undefined;\n  const { fast, smart } = raw as Partial<ChatTierModels>;\n  return typeof fast === 'string' && fast && typeof smart === 'string' && smart ? { fast, smart } : undefined;\n};\n\n/** The one reasoning-fails-closed normalization both features reads share (R3-1074,\n *  review round 1's R6 — a second copy is where the next ChatFeatures key drifts). */\nconst withReasoningClosed = (f: Partial<ChatFeatures>): ChatFeatures =>\n  ({ ...f, reasoning: f.reasoning === true } as ChatFeatures);\n\n/** A choice's features cross only COMPLETE (R3-1074): a present half-answer — a\n *  non-object, an array, or an object missing a required key — is dropped, exactly\n *  like a half-answered `models` pair. Absent stays absent (an older host); present\n *  but unusable never renders as fact. */\nconst usableFeatures = (raw: unknown): ChatFeatures | undefined => {\n  if (!raw || typeof raw !== 'object' || Array.isArray(raw)) return undefined;\n  const f = raw as Partial<ChatFeatures>;\n  if (\n    typeof f.vision !== 'boolean' ||\n    typeof f.tools !== 'boolean' ||\n    typeof f.jsonMode !== 'boolean' ||\n    typeof f.maxContextTokens !== 'number'\n  )\n    return undefined;\n  return withReasoningClosed(f);\n};\n\n/** Validate the wire's `connectedProviders` list, keeping only usable entries. The gating\n *  (whether the list arrives at all) is the host's — `normalizeProviderInfo` merely refuses\n *  to pass through a malformed list, exactly as it refuses a half-answered `models` pair. */\nconst usableConnectedProviders = (raw: unknown): ChatProviderChoice[] | undefined => {\n  if (!Array.isArray(raw)) return undefined;\n  const out: ChatProviderChoice[] = [];\n  for (const item of raw) {\n    if (!item || typeof item !== 'object') continue;\n    const { providerId, displayName, models } = item as Partial<ChatProviderChoice>;\n    if (typeof providerId !== 'string' || !providerId) continue;\n    if (typeof displayName !== 'string' || !displayName) continue;\n    const cleanModels = Array.isArray(models) ? models.filter((m): m is string => typeof m === 'string' && !!m) : [];\n    const features = usableFeatures((item as Partial<ChatProviderChoice>).features);\n    out.push({ providerId, displayName, models: cleanModels, ...(features ? { features } : {}) });\n  }\n  return out.length > 0 ? out : undefined;\n};\n\nexport function normalizeProviderInfo(provider: ChatProviderInfo | null): ChatProviderInfo | null {\n  if (!provider) return null;\n  // The LATER fields (displayName/executor/models/connectedProviders) are taken OFF the value\n  // and put back only if usable — spreading and then overwriting would leave an unusable key\n  // present, and `key in provider` is exactly how an app is told to ask whether the host said\n  // anything.\n  const {\n    displayName: rawName,\n    executor: rawExecutor,\n    models: rawModels,\n    connectedProviders: rawConnected,\n    ...rest\n  } = provider;\n  // The wire value is whatever the host sent, which may predate any of these fields — so\n  // read it as partial rather than trusting the declared type, and decide each explicitly.\n  const wire = provider.features as Partial<ChatFeatures>;\n  const executor = EXECUTORS.includes(rawExecutor as ChatExecutor) ? (rawExecutor as ChatExecutor) : undefined;\n  const displayName = typeof rawName === 'string' && rawName ? rawName : undefined;\n  const models = usableModels(rawModels);\n  const connectedProviders = usableConnectedProviders(rawConnected);\n  return {\n    ...rest,\n    features: withReasoningClosed(wire),\n    ...(displayName ? { displayName } : {}),\n    ...(executor ? { executor } : {}),\n    ...(models ? { models } : {}),\n    ...(connectedProviders ? { connectedProviders } : {}),\n  };\n}\n\nlet answered = false;\n// R3-688 — the host's grantless marker, read off the SAME message (`ungranted: true`\n// beside `provider`). It is message-arrival state beside `answered`, not part of the\n// wire VALUE: the channel's value stays `ChatProviderInfo | null`, so the protocol\n// snapshot gate keeps reading the unchanged shape.\nlet ungrantedMark = false;\nconst channel = createPushChannel<ChatProviderInfo | null>({\n  pushType: LLM_PROVIDER,\n  requestType: REQUEST_LLM_PROVIDER,\n  initial: null,\n  parse: (msg) => {\n    if (!('provider' in msg)) return undefined;\n    answered = true;\n    ungrantedMark = msg.ungranted === true;\n    return normalizeProviderInfo((msg.provider as ChatProviderInfo | null) ?? null);\n  },\n});\n\n/**\n * Derive the four-state read from what the host has said. Pure, and exported for its\n * own test (`llmProviderState.test.ts`) like `normalizeProviderInfo` — not part of the\n * public surface beyond `index.ts`'s wholesale re-export.\n *\n * Order is load-bearing: `unknown` wins until the host answers (an `ungranted` mark\n * cannot exist before an answer — it arrives ON the answer); then the host's grantless\n * mark; then the provider. A host that predates the mark never sets it, so its\n * grantless answer derives `not-configured` — the exact pre-R3-688 contract, which is\n * the additive degradation the fourth state promises.\n *\n * @internal\n */\nexport function deriveChatProviderState(\n  answered: boolean,\n  ungranted: boolean,\n  provider: ChatProviderInfo | null,\n): ChatProviderState {\n  if (!answered) return { status: 'unknown' };\n  if (ungranted) return { status: 'ungranted' };\n  return provider ? { status: 'configured', provider } : { status: 'not-configured' };\n}\n\n/** Derive the state from the wire value plus what the host has said beside it. */\nconst stateOf = (provider: ChatProviderInfo | null): ChatProviderState =>\n  deriveChatProviderState(answered, ungrantedMark, provider);\n\n/**\n * The provider the host resolved for this app, or `null`.\n *\n * Kept for compatibility (`ways_of_working §6`, additive-only): it collapses `unknown`\n * and `not-configured` to `null`. Prefer {@link describeChatState} when the difference\n * matters — which is any time you would render \"connect a key\", because doing that in\n * the `unknown` state is exactly the false banner R3-300 fixes.\n */\nexport const describeChat = (): ChatProviderInfo | null => channel.get();\n\n/** The four-state read: `unknown` before the host answers; then the host's `ungranted`\n *  mark, or the provider (configured / not-configured). */\nexport const describeChatState = (): ChatProviderState => stateOf(channel.get());\n\n/** Subscribe to provider changes (key added/revoked, preference changed). Invoked\n *  immediately with the current value, then on every change. Returns unsubscribe. */\nexport const onChatProviderChange = (listener: (provider: ChatProviderInfo | null) => void): (() => void) =>\n  channel.onChange(listener);\n\n/** Subscribe to the provider-state description. */\nexport const onChatProviderStateChange = (listener: (state: ChatProviderState) => void): (() => void) =>\n  channel.onChange((p) => listener(stateOf(p)));\n\n/** React hook returning the resolved chat provider (or `null`), re-rendering on\n *  change — gate the summarize affordance on `provider !== null`. */\nexport const useChatProvider = (): ChatProviderInfo | null => channel.use();\n\n/**\n * React hook returning the four-state description.\n *\n * Use this to render provider state honestly: show nothing (or a neutral placeholder)\n * while `unknown`, the connect affordance only on `not-configured`, the NOT-GRANTED\n * cause — never the connect copy — on `ungranted`, and the provider's name on\n * `configured`.\n */\nexport const useChatProviderState = (): ChatProviderState => stateOf(channel.use());\n"],"mappings":"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","names":["answered"]}