youngrock-labs/dsh-provider-copilot 预览 preview

youngrock-labs/dsh-provider-copilot

use a GitHub Copilot subscription as a DSH LLM provider

Project Overview项目介绍

This is a DeepSeek Harness (DSH) plugin that connects an existing GitHub Copilot subscription to DSH as an LLM provider. It supports streaming chat completions, model listing, reasoning content passthrough, and auth commands. Use it when you want to use your Copilot subscription in DSH. It relies on GitHub's non-public internal endpoint, which may be changed or revoked any time.

这是DeepSeek Harness(DSH)的插件,可将你的GitHub Copilot订阅接入DSH作为LLM提供商,支持流式对话补全、模型列表获取、推理内容透传和登录管理命令,适合已有Copilot订阅想在DSH中使用的场景。注意它调用GitHub非公开接口,接口随时可能变更或失效。

Or use CLI install (for developers)或使用命令行安装(适合开发者)

CLI Install命令行安装

dsh plugin --profile web add github:youngrock-labs/dsh-provider-copilot

youngrock-labs/dsh-provider-copilot 加入你的 DSH 配置(web profile)即可启用。

READMEREADME

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 var Effect
COPILOT_TOKEN Skip exchange; use as Copilot bearer directly.
COPILOT_GITHUB_TOKEN GitHub token to exchange for a Copilot bearer.
DSH_COPILOT_ALLOW_GH_HOSTS 1 enables reading gh hosts.yml as fallback.
DSH_COPILOT_ALLOW_ENV_GH 1 enables reading GH_TOKEN / GITHUB_TOKEN fallback.
DSH_COPILOT_NO_LOG 1 disables JSONL log writes (no directory / files).
XDG_CONFIG_HOME Standard 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

Symptom Likely cause / fix
403 scraping from api.github.com Non-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_source Nothing to authenticate with. Set COPILOT_TOKEN / COPILOT_GITHUB_TOKEN or run /copilot login.
http_connect_timeout Network / DNS. Check corporate proxy; tune timeouts.connectMs if needed.
http_first_byte_timeout Upstream slow to respond. Tune timeouts.firstByteMs.
http_idle_timeout Stream stalled mid-flight. Tune timeouts.idleMs.
Model missing from /copilot status count Not in the whitelist ∩ upstream. Pass a custom whitelist to CopilotProvider if you know the id.
unknown subcommand: X Only 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.

上一个 Prev dsh-vision-bench 下一个 Next dsh-tang-governance