Skip to content
Back to skills

Add Provider

ASecurity

Use when adding a new LLM provider to Lyntai (a new backend/model source behind IModelProvider, or bridging an existing Microsoft.Extensions.AI IChatClient). Covers the correct pattern, the verdict/streaming/timeout invariants, and stub-based tests.

  • 2 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added October 23, 2026
ai-agentsgoshellnodeazureapibackend

Works with

  • terminal
  • cli
  • api

Security analysis

A100/100

Scanned November 11, 2026

npx -y skills add JiarongGu/Lyntai --skill add-provider --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Add Provider?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Add Provider
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/jiaronggu-add-provider/badge)](https://www.skillsdirectory.com/skills/jiaronggu-add-provider)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: add-provider
description: Use when adding a new LLM provider to Lyntai (a new backend/model source behind IModelProvider, or bridging an existing Microsoft.Extensions.AI IChatClient). Covers the correct pattern, the verdict/streaming/timeout invariants, and stub-based tests.
---
<!-- daoris: mirror of .agents/skills/add-provider/SKILL.md for agents that read only .claude/skills — edit that file, not this; daoris sync rewrites this one -->

# Add an LLM provider to Lyntai

Read `.agents/knowledge/extending-lyntai.md` (§Add an LLM provider) and `.agents/knowledge/llm-and-router.md` first.

## Decide the path
1. **Is it reachable over HTTP on a wire we speak?** (OpenAI/Azure/Ollama/OpenRouter/vLLM/llama-server/Groq/DeepSeek/…) → don't
   write a provider. The consumer calls `builder.AddHttpProvider("id", o => o.BaseUrl = …)`, picking a
   wire (`AddHttpProvider` for OpenAI-shaped, `AddOllamaProvider` for Ollama-native). Most vendors ship such
   an endpoint, so this is the answer far more often than not (**D146**).
2. **Is it a spawned CLI agent?** (`claude`, a sibling CLI) → an **`ICliBackend`** plus a thin provider. See the
   CLI checklist below — the spawn/verdict/streaming/maintenance invariants already live in
   `CliProviderEngine`, and a second copy of them is how they drifted before.
3. **Reachable, but its own wire format?** → a **BRIDGE**, not a provider:
   `builder.AddBridgeProvider("id", (req, ct) => …)` wraps any client that already answers — a vendor SDK,
   an in-house service, an MEAI `IChatClient` — in the few lines of mapping you actually need, and costs the
   library no dependency (**D147**). Return a non-Ok `ProviderVerdict` rather than throwing.
4. Otherwise write a native `IModelProvider`. WHERE it lives is the footprint test below, never a default.

## Where the code lives — the footprint test (`docs/DECISIONS.md` D25)

**A package boundary must answer "which dependency does this isolate?"** A CLI backend or a native provider
that needs nothing beyond Core/BCL — or only managed `Microsoft.Extensions.Http` — is **a class in
`src/Lyntai.Providers.Basic/`**, next to `ClaudeCliBackend`, `CodexCliBackend` and
`HttpModelProvider`. Namespaces stay `Lyntai.Providers.<Name>` inside that one assembly (D25:
consolidating packages must not force a consumer to edit a `using`).

It earns its own `src/Lyntai.Providers.<Name>/` package (project-ref `Lyntai.Core` only, never
adapter→adapter) **the moment it drags a native runtime, a platform-specific API, or a dependency a
consumer might refuse** — `Lyntai.Providers.LlamaSharp` (LLamaSharp + a native backend) is the worked example.
Do not create one for tidiness: a published package id can never be freed or reused (D23), so a needless
id is permanent. If it does earn a package, scaffold it — see the Baselines bullet below; never hand-roll
the csproj.

## CLI-backend checklist (the `ICliBackend` path)
- [ ] A class in `src/Lyntai.Providers.Basic/` (footprint test above) — a CLI backend adds no dependency of
      its own, so it essentially never earns a package. `ClaudeCliBackend` and `CodexCliBackend` live there.
- [ ] **MEASURE the CLI first** — `<cmd> --help`, `--version`, and `--help` on each subcommand you intend to
      drive. Record what you measured (version + date) in the backend's XML docs. **Never name a command you
      haven't run**: a CLI that reads an unrecognized token as a PROMPT answers it, so a guessed subcommand
      spends tokens on every call while the build stays green.
- [ ] `<Name>CliBackend : CliBackendBase` — `Id`, `DefaultCommand`, `CommandEnvironmentVariables`
      (put `LYNTAI_PROVIDER_CMD` first so the stub seam works), `BuildCompletionArgs`, `ParseLine`. Set
      `PromptDelivery = Argument` only if the CLI truly takes its prompt positionally.
- [ ] Optional capabilities ONLY where measured: `VersionArgs` (default `--version`), `UpdateArgs`,
      `AuthStatusArgs` + `ParseAuthStatus`, `LogoutArgs`, `TryBuildLoginArgs`, `TryBuildInstallArgs`.
      Refuse unknown free-form values (`FlagShaped`) instead of forwarding them into argv.
- [ ] `<Name>CliProvider` — forwards to `CliProviderEngine` and implements exactly the capability interfaces
      that backend supports; its `Capabilities` come from `CliComposition.Capabilities(backend)`, so the
      tool-call declaration cannot disagree with the backend's. Copy `ClaudeCliProvider` (pure forwarding).
- [ ] `Add<Name>CliProvider(this LyntaiBuilder, …, string id = <backend name>)` extension: the id defaults
      to the BACKEND's name, never a role, and a second registration (a second portable install) passes its
      own. Resolve the tool provisioner through `CliComposition.Provisioner(sp, id, defaultId)`. A builder
      method names what it REGISTERS, with the vendor qualifying it (**D137**).
- [ ] Tests: the parsing/argv-building unit-tested through `FakeProcessRunner` (never a real binary — and
      NEVER `login`/`logout`/`install` against one, which mutate a developer's machine), plus a real-spawn
      test against `provider-stub.mjs`. Add the CLI's shapes to the stub as needed.
- [ ] Baselines: confirm Core's own baseline is untouched. A class added to `Lyntai.Providers.Basic`
      needs only Core's and `Lyntai.Providers.Basic`'s baselines reviewed. **Only if the backend earns its own package**
      (footprint test above): scaffold with `node devtools/dev.mjs new-package Lyntai.Providers.<Name>` —
      it registers all NINE registries `check-packages` gates (`packableProjects`, the solution,
      `<Description>`, `ApiSurfaceTests.Assemblies()`, the SEPARATE `Loaded` anchor map, the baseline file,
      the test project's `ProjectReference`, the `docs/AOT.md` row, the README row). Do not hand-roll the
      csproj: the misses are silent — a package absent from `Assemblies()` has no API gate at all.
- [ ] `node devtools/dev.mjs verify` green.

## Native provider checklist (non-CLI)
- [ ] A class in `src/Lyntai.Providers.Basic/` unless the backend drags a dependency a consumer might
      refuse — the footprint test above. `HttpModelProvider` lives there (managed
      `Microsoft.Extensions.Http` only); `Lyntai.Providers.LlamaSharp` earned its own package.
- [ ] `MyProvider : IModelProvider` — the seam REQUIRES exactly two members, `Id` and **`Capabilities`**
      (what the router reads BEFORE spending anything; it has no default precisely because a silent
      capability serves nothing and would make the backend permanently invisible). Everything else is
      defaulted to an `Unsupported` verdict, so override only what you serve: `CompleteAsync`,
      `StreamAsync`, and `IsAvailable`/`ProbeAsync` where a real check exists — and DECLARE each in
      `Capabilities`: `Produces` lists `ProviderKinds.Text` (a configured candidate that does not is refused at
      composition, D178), and `Operations` lists `ProviderOperation.Complete` / `.Stream` for each door you
      override, since the text router never asks an undeclared door.
- [ ] Failures classified via `ProviderVerdictClassifier` — no local heuristics.
- [ ] An HTTP backend classifies through the THREE-argument `FromHttpFailure(status, body, hasCredentials)`
      (copy `HttpModelProvider`): an uncredentialed 401/403 is `NotConfigured`, not `AuthFailed`
      (`llm-and-router.md` §Verdict taxonomy, D31).
- [ ] Empty/no output → `Failed` (and a terminal `Error` chunk when streaming), never `Ok`.
- [ ] Streaming iterates `GuardedStream.ReadAll` — its inactivity clock is the timeout
      (`llm-and-router.md` §Provider streaming timeout). Yield `Content` only for non-empty text; end with
      one `Final`(usage) or `Error`.
- [ ] Spawning a CLI → go through `ProcessRunner` (never shell out directly).
- [ ] `Add<Name>Provider(this LyntaiBuilder, …)` extension in the adapter package — a builder method names
      what it REGISTERS, with the vendor qualifying it (**D137**); register into the
      `IEnumerable<IModelProvider>` collection, resolve deps from the container.
- [ ] Baselines/registries: same rule as the CLI checklist above — Core untouched, and a package only if
      the footprint test says so, scaffolded with `node devtools/dev.mjs new-package`, never by hand.
- [ ] Tests against a stub only (stubbed `HttpMessageHandler`, or `provider-stub.mjs` via
      `LYNTAI_PROVIDER_CMD`) — cover each verdict, streaming order, empty→Failed. Never a live endpoint.
- [ ] `node devtools/dev.mjs verify` green.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

View all in ai-agents →

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.