npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

copilot2api

v0.4.0

Published

OpenAI- and Anthropic-compatible local API server for GitHub Copilot and Microsoft 365 Copilot

Readme

copilot2api

OpenAI- and Anthropic-compatible local API server for GitHub Copilot and Microsoft 365 Copilot.

Point any tool that speaks the OpenAI or Anthropic API at your Copilot subscription. Two backends are available behind one HTTP surface, selected with --mode:

  • copilot (default) — GitHub Copilot, via the official @github/copilot-sdk, which bundles the Copilot CLI (no separate install needed).
  • m365 — Microsoft 365 Copilot ("Bizchat"), via its SignalR-over-WebSocket endpoint on substrate.office.com.

Both modes expose the same endpoints (/v1/chat/completions, /v1/messages, /v1/responses, /v1/models) and are chosen through an adapter layer, so your client code does not change between them.

Your app (OpenAI/Anthropic HTTP client)
        ↓  HTTP · http://127.0.0.1:4141/v1
copilot2api  ──────────────┬───────────────────────────┐
   copilot adapter         │        m365 adapter        │
        ↓ JSON-RPC         │        ↓ SignalR/WebSocket │
   Copilot CLI (bundled)   │        ↓ (0x1E JSON frames)│
        ↓                  │   wss://substrate.office.com
   GitHub Copilot          │        ↓
                           │   Microsoft 365 Copilot

Install

npm install -g copilot2api

or via Homebrew:

brew tap asllani94/tap
brew install copilot2api

Quick start

copilot2api login   # one-time GitHub device-flow sign-in
copilot2api         # start the server on http://127.0.0.1:4141/v1

Then use it with any OpenAI client:

import OpenAI from "openai";

const openai = new OpenAI({ baseURL: "http://127.0.0.1:4141/v1", apiKey: "unused" });
const res = await openai.chat.completions.create({
  model: "auto",
  messages: [{ role: "user", content: "Hello" }],
  stream: true,
});

Or plain curl:

curl http://127.0.0.1:4141/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{"model":"auto","messages":[{"role":"user","content":"Hello, how many r letters are there in strawberry word"}]}'

Microsoft 365 Copilot mode

Run the same server against Microsoft 365 Copilot instead of GitHub Copilot by setting --mode m365 and supplying a Microsoft-issued access token:

export COPILOT2API_M365_TOKEN="eyJ0eXAiOiJKV1Qi..."   # substrate access token
copilot2api --mode m365

Then call it exactly like the default mode. M365 exposes conversation tones rather than raw models, surfaced as three model ids:

| Model id | Tone | | ----------- | ---------- | | auto | Magic (balanced) | | quick | Chat (fast) | | reasoning | Reasoning (deep) |

curl http://127.0.0.1:4141/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{"model":"reasoning","messages":[{"role":"user","content":"Summarize our Q3 strategy deck."}]}'

Authentication & security

The two modes authenticate very differently, on purpose. Read this before deploying M365 mode.

copilot mode uses your local GitHub Copilot login (device flow). The token lives in the OS keychain and is managed by the bundled Copilot CLI/SDK — this proxy never reads, stores, or forwards it. Sign in once with copilot2api login.

m365 mode performs no interactive login and runs no OAuth flow of its own. You obtain a token through Microsoft's own sign-in and hand it to the proxy out-of-band via config or environment only:

| Setting | Env var(s) | Config file (m365.*) | Required | | --------- | ----------------------------------------------------- | ---------------------- | -------- | | Token | COPILOT2API_M365_TOKEN, M365_COPILOT_TOKEN | token | Yes | | Tenant ID | COPILOT2API_M365_TENANT_ID, M365_TENANT_ID | tenantId | Auto* | | User OID | COPILOT2API_M365_USER_OID, M365_USER_OID | userOid | Auto* |

* Tenant ID and user OID are read from the token's own tid/oid claims when you don't set them explicitly.

Guarantees this mode holds to — enforced in code and covered by tests:

  • The token is sent to exactly one destination: wss://substrate.office.com, the Microsoft endpoint that issued and consumes it (required to open the authenticated WebSocket). It goes nowhere else.
  • The token is never forwarded to the model. Only your prompt text is placed in the chat payload; the credential is never part of what the LLM sees.
  • The token is never logged. Connection URLs (which carry the token as the query parameter substrate requires) are redacted before appearing in any log or error message.
  • The token is decoded only locally, to read oid/tid and check expiry. Its signature is never verified over the network — the proxy is the bearer, not the audience.
  • No CLI flag accepts the token, so it can't leak into shell history or the process list. Config file or environment variable only.

Tokens are short-lived (typically ~1 hour). When one expires the proxy returns a clear 401 telling you to supply a fresh token; refreshing it is up to your own Microsoft sign-in process.

M365 Copilot's SignalR interface is a private, undocumented protocol. This mode is a best-effort client for it and may break if Microsoft changes the backend. Using it must comply with your organization's Microsoft 365 terms and policies — confirm you're authorized before deploying it.

Endpoints

| Endpoint | Description | | --------------------------------- | -------------------------------------------------- | | POST /v1/chat/completions | Chat completions — streaming (SSE) & non-streaming, with function-tool calling (prompt-shimmed) | | POST /v1/responses | Responses API — structured JSON output (text.format) and function tools, both prompt-shimmed (non-streaming) | | POST /v1/messages | Anthropic Messages API — streaming & non-streaming, with tool use (prompt-shimmed) | | POST /v1/messages/count_tokens | Anthropic token counting (size-based estimate) | | GET /v1/models | Models your Copilot plan/policy exposes | | GET /health | Liveness probe (never requires auth) |

POST /chat/completions and GET /models also work without the /v1 prefix, for clients that append their own path.

The Responses API support is enough to drive AI-SDK-based frameworks — see examples/stagehand for AI browser automation (Stagehand + Playwright) running entirely on your Copilot subscription.

Tool calling

The Copilot SDK only ever returns assistant text, so function tools are bridged with a text protocol: the tools you pass are described in the system message, the model emits [tool_call]{...}[/tool_call] blocks, and the proxy parses them back into OpenAI tool_calls / Anthropic tool_use — including mid-stream. It works well with capable models, but it is a shim: expect it to be less robust than native tool calling.

Claude Code

/v1/messages is enough to run Anthropic-native tools against Copilot:

ANTHROPIC_BASE_URL=http://127.0.0.1:4141 \
ANTHROPIC_API_KEY=unused \
claude

If your Copilot plan only exposes auto, alias the model IDs Claude Code asks for via modelMap (see Configuration):

{ "modelMap": { "claude-sonnet-4": "auto", "claude-haiku-4": "auto" } }

Configuration

Precedence: CLI flags > environment variables > config file > defaults.

| Setting | Flag | Env var | Config file key | Default | | --------- | ----------------- | ----------------------------------------------- | --------------- | ---------------------------------- | | Mode | --mode | COPILOT2API_MODE | mode | copilot | | Port | -p, --port | COPILOT2API_PORT | port | 4141 | | Host | --host | COPILOT2API_HOST | host | 127.0.0.1 | | API key | --api-key | COPILOT2API_API_KEY | apiKey | (none — no auth) | | Model map | — | — | modelMap | {} | | M365 token | — (no flag) | COPILOT2API_M365_TOKEN, M365_COPILOT_TOKEN | m365.token | (none) | | M365 tenant | — (no flag) | COPILOT2API_M365_TENANT_ID, M365_TENANT_ID | m365.tenantId | (from token tid) | | M365 user OID | — (no flag) | COPILOT2API_M365_USER_OID, M365_USER_OID | m365.userOid | (from token oid) | | Config | -c, --config | — | — | ~/.config/copilot2api/config.json |

Example config file (~/.config/copilot2api/config.json):

{
  "port": 4141,
  "apiKey": "my-local-secret",
  "modelMap": { "claude-sonnet-4": "auto" }
}

Example config file for M365 mode:

{
  "mode": "m365",
  "apiKey": "my-local-secret",
  "m365": { "token": "eyJ0eXAiOiJKV1Qi..." }
}

The apiKey above is the inbound key your own clients must present to this proxy; it is unrelated to the M365 token and is never forwarded upstream.

When an API key is set, API routes require it as Authorization: Bearer <key> (OpenAI style) or x-api-key: <key> (Anthropic style).

modelMap defines display-ID aliases: keys are accepted as model in requests and translated to the mapped Copilot model ID. Useful when a client insists on specific model names but your plan only exposes auto. /v1/models is unaffected — it lists exactly what your plan exposes.

Notes & limitations

  • Local by design. Binds to 127.0.0.1 by default; the server fronts your Copilot credentials, so treat it like a credential itself.
  • Chat-only bridge. In copilot mode, Copilot's agent tools are disabled, permission requests are rejected, and the SDK's default system message (which describes the proxy host's own directory) is replaced with yours — it behaves as a pure model endpoint.
  • Each request runs in a fresh session; the message history is rendered into a single transcript prompt (both modes are stateless per request).
  • Available models depend on the backend: copilot mode lists what your Copilot plan/org policy exposes (you may only see auto); m365 mode exposes the three tone-backed ids (auto/quick/reasoning).
  • usage token counts are not reported by either backend and are returned as zeros; /v1/messages/count_tokens returns a size-based estimate.
  • Anthropic/OpenAI request fields with no backend equivalent (max_tokens, temperature, metadata, ...) are accepted and ignored.
  • copilot mode requires an active GitHub Copilot subscription and is subject to GitHub's terms. m365 mode requires a Microsoft 365 Copilot license and is subject to your organization's Microsoft 365 terms. Check your org's policies before deploying either beyond personal use.

Troubleshooting

"Session was not created with authentication info" right after installing — the Copilot token lives in the macOS keychain, and the first access from a new binary (e.g. a fresh Homebrew install) can be denied non-interactively. Run one interactive command to grant access, then restart the server:

copilot2api login   # or re-run it; answering the keychain prompt once is enough

Development

git clone https://github.com/asllani94/copilot2api
cd copilot2api
npm install
npm start

License

MIT