Back to home@youngrock-labs

dsh-provider-copilot

use a GitHub Copilot subscription as a DSH LLM provider

Stars
2
Language
TypeScript
Created
Sep 1, 2026
Updated
Sep 3, 2026
GitHub repo

Introduction

dsh-provider-copilot

Bridge a GitHub Copilot subscription into DeepSeek Harness (dsh) as a plain LLM provider.


Scope

  • ✅ Chat completions (streaming)
  • ✅ Model listing (whitelist ∩ remote)
  • ✅ Reasoning-stream passthrough (delta.reasoning_contentreasoning chunk)
  • /copilot login | logout | status commands
  • ✅ JSONL observability with strict field allowlist
  • ❌ Tool / function calling (explicitly out of scope)
  • ❌ Copilot CLI runtime / FFI (not used)

Architecture

The package is a dsh plugin bundle: a cordis object plugin (name/inject/apply) whose apply(ctx) registers a provider route on the harness LLM service. dsh discovers the model group purely from that registration — no dsh source changes are needed.

┌────────────────────────────────────────────────────────────────────┐
│                    dsh (host, any profile)                          │
│  cordis loader ── apply(ctx, config)                               │
│    └─ ctx.llm.registerAdapter(["copilot"], CopilotAdapter)         │
└────────────────┬───────────────────────────────────────────────────┘
                 │
                 ▼
┌────────────────────────────┐   ┌────────────────────────────────┐
│     CopilotAdapter        │──▶│    CopilotClient (HTTP)        │
│  • dsh StreamChunk emit   │   │  • layered timeouts             │
│  • no tools ever          │   │  • SSE parser                   │
│  • whitelist (+ ∩/models) │   │  • 429 retry once                │
│  • alias collapse         │   │  • never hardcodes base URL      │
└────────────┬───────────────┘   └────────────────┬───────────────┘
             │                                    │
             ▼                                    ▼
┌────────────────────────────┐   ┌────────────────────────────────┐
│    JsonlLogger + Metrics   │   │    AuthManager                 │
│  • ring buffer for status  │   │  • Device Flow state machine   │
│  • JSONL 0600/0700 daily   │   │  • token exchange + cache      │
│  • strict field allowlist  │   │  • blocking + bg refresh, dedup│
└────────────────────────────┘   └────────────────────────────────┘

Inside dsh the streamed response uses the harness chunk protocol (block-start / text-delta / reasoning-delta / block-end / usage / finish); reasoning deltas render in dsh's collapsible "thinking" row. The adapter is plain chat: it never forwards tools, and dsh handles that fine — an assistant reply without tool calls ends the turn normally.

Sequence: first call after login

user       dsh         CopilotAdapter   CopilotClient   AuthManager   github
 │  /copilot login     │                │              │             │
 │────────────────────▶│                │              │             │
 │  (returns code+URL; device flow continues in the background)       │
 │◀── code: ABCD-1234 ─│                │              │             │
 │  authorize in browser                                 │◀── ghu_* ─│
 │                    │                │              │──── exchange│
 │                    │                │              │◀ Copilot bearer + endpoints
 │  first prompt      │                │              │             │
 │──────────────────▶│  stream(req)   │              │             │
 │                    │───────────────▶│ getBearer()  │             │
 │                    │                │─────────────▶│ cached      │
 │                    │                │◀── bearer ───│             │
 │                    │                │──── POST /chat/completions (SSE) ─▶
 │                    │◀── StreamChunks│◀── parsed chunks ────────────────
 │◀── rendered ───────│                │              │             │

Non-public API notice

This project uses api.github.com/copilot_internal/v2/token to exchange a GitHub OAuth token for a Copilot bearer token. That endpoint is a non-public, unstable interface; GitHub may change or revoke it at any time. Use in production at your own risk.

Additional compliance notes:

  • User-Agent must start with GitHubCopilot*; anti-scraping otherwise returns a 403 that looks like an auth error.
  • The plugin ships the official VSCode Copilot Chat OAuth client_id (Iv1.b507a08c87ecfe98). Tokens are minted under your GitHub account and used only against the Copilot subscription you own.
  • The Device Flow scope is read:user only. No repository / gist / issue scopes are requested.
  • dsh requires app attribution on every provider HTTP request; the Copilot endpoints additionally require the GitHubCopilot* User-Agent. Verify the header combination you ship against a live request before release.

Install

The package is a dsh bundle: its manifest declares dsh.bundle and it ships a cordis.patch.yml that inserts the plugin row. Installing it with dsh plugin therefore adds it to the profile's bundle layers:

# install from GitHub:
dsh plugin --profile <your-profile> add github:youngrock-labs/dsh-provider-copilot
# install from a local checkout:
dsh plugin --profile <your-profile> add /absolute/path/to/dsh-provider-copilot

Restart dsh (or reload the profile) to activate the plugin. It mounts dormant: the copilot provider group is advertised immediately — it shows up in the model picker without any sign-in — and the first request without credentials reports MISSING_CREDENTIAL with pointers to /copilot login and the environment variables below.

Model picker showing the Copilot (GitHub) group right after installing the plugin

Development without packaging: add the row to the profile patch manually

- id: llm-copilot
  name: './path/to/dsh-provider-copilot/src/plugin/plugin.ts'

Usage — inside dsh

The /copilot login | logout | status commands are real dsh commands, registered when the host command service is present (dsh Web resolves / lines against them):

/copilot login    # Device Flow: prints the verification URL + code and
                  #   finishes authorizing in the background
/copilot status   # source, expiry, model count, p50/p95 latency
/copilot logout   # wipes cache + memory + metrics

/copilot login returns the GitHub verification URL and the one-time code to enter there; authorization continues in the background once you approve it in the browser:

Copilot login prompts for the GitHub device code

In dsh's model picker (the group is visible from install, see above), select a model inside the Copilot (GitHub) group. Selecting a model makes it the default for new sessions; each session keeps its own recorded selection. Pick again any time to switch models. Sending a request requires a credential: set COPILOT_TOKEN / COPILOT_GITHUB_TOKEN, or run /copilot login above. Once signed in, the advertised list is intersected with the upstream /models catalog.

Usage — programmatic (BYOK)

import { AuthManager, CopilotClient, CopilotProvider } from "dsh-provider-copilot";

const auth = new AuthManager({
    byok: { kind: "github", token: process.env.MY_GITHUB_TOKEN! },
});
const client = new CopilotClient({
    getBearer: async () => {
        const s = await auth.getSession();
        return { token: s.token, endpoints: { api: s.endpoints.api } };
    },
});
const provider = new CopilotProvider({ client });

for await (const chunk of provider.stream({
    model: "gpt-4o-mini",
    messages: [{ role: "user", content: "hi" }],
})) {
    if (chunk.type === "text") process.stdout.write(chunk.text);
}

Token source priority

AuthManager resolves credentials in this order:

  1. byok constructor option — a { kind: "bearer" | "github", token }.
  2. env COPILOT_TOKEN — a raw Copilot bearer; skips exchange.
  3. env COPILOT_GITHUB_TOKEN — a GitHub token; drives exchange.
  4. On-disk OAuth cache from a previous /copilot login.
  5. ~/.config/gh/hosts.yml (opt-in: DSH_COPILOT_ALLOW_GH_HOSTS=1).
  6. GH_TOKEN / GITHUB_TOKEN (opt-in: DSH_COPILOT_ALLOW_ENV_GH=1).

Sources 5–6 are opt-in because "any GitHub token" leaking into a Copilot-specific credential surface is easy to do by accident.

Configuration

Env varEffect
COPILOT_TOKENSkip exchange; use as Copilot bearer directly.
COPILOT_GITHUB_TOKENGitHub token to exchange for a Copilot bearer.
DSH_COPILOT_ALLOW_GH_HOSTS1 enables reading gh hosts.yml as fallback.
DSH_COPILOT_ALLOW_ENV_GH1 enables reading GH_TOKEN / GITHUB_TOKEN fallback.
DSH_COPILOT_NO_LOG1 disables JSONL log writes (no directory / files).
XDG_CONFIG_HOMEStandard XDG override for cache & log location.

Observability

  • In-memory ring buffer: last 10 successful calls used for /copilot status p50/p95.
  • JSONL log: ~/.config/dsh/copilot/log/copilot-YYYY-MM-DD.jsonl. Fields are a hard-coded allowlist: ts, requestId, event, model, latencyMs, promptTokens, completionTokens, totalTokens, errorCode, source, sku. Any attempt to log outside that set is rejected at runtime.
  • Retention: 7 days by default; pruning is lazy on the first write of a new UTC day; failures are silent.
  • Perms: dir 0700, files 0600, matching auth cache.

Troubleshooting

SymptomLikely cause / fix
403 scraping from api.github.comNon-Copilot UA. Do not override the built-in headers.
token_exchange_forbidden (403)Account has no Copilot entitlement (individual not subscribed, org/enterprise seat unassigned).
token_exchange_unauthorized (401)Stale GitHub token. /copilot logout then /copilot login.
no_token_sourceNothing to authenticate with. Set COPILOT_TOKEN / COPILOT_GITHUB_TOKEN or run /copilot login.
http_connect_timeoutNetwork / DNS. Check corporate proxy; tune timeouts.connectMs if needed.
http_first_byte_timeoutUpstream slow to respond. Tune timeouts.firstByteMs.
http_idle_timeoutStream stalled mid-flight. Tune timeouts.idleMs.
Model missing from /copilot status countNot in the whitelist ∩ upstream. Pass a custom whitelist to CopilotProvider if you know the id.
unknown subcommand: XOnly login, logout, status are supported.

Development

npm install
npm test           # 160+ tests across auth / client / provider / plugin / commands / observability / e2e
npm run typecheck
npm run lint

New dsh-facing code lives under src/plugin/ (adapter, chunk translation, error taxonomy, plugin entry, dsh command handler); the legacy BYOK CopilotProvider API is unchanged.

Feasibility PoC (Device Flow → models → stream):

npm run poc -- device
export GH_OAUTH_TOKEN=ghu_xxx
npm run poc

Coverage is opt-in:

npm i -D @vitest/coverage-v8@2.1.8   # match your vitest version
npx vitest run --coverage

License

MIT — see LICENSE.