@bobhuhu/dsh-llm-github-copilot-cli
A standalone DeepSeek Harness provider that uses the published GitHub Copilot
SDK and its official CLI subprocess. The DSH provider route is copilot.
The plugin does not call Copilot service endpoints directly, implement OAuth,
read credentials, copy a client identity, or carry a fork of the Copilot SDK.
Authentication, protocol handling, model routing, and service communication
remain owned by the official CLI.
Runtime architecture
DSH host
-> @bobhuhu/dsh-llm-github-copilot-cli
-> @github/copilot-sdk 1.0.11
-> official @github/copilot 1.0.81 CLI subprocess
-> GitHub Copilot service
The package pins and verifies both official GitHub packages at startup. It
resolves the public @github/copilot/npm-loader.js entry and connects through
RuntimeConnection.forStdio(). A mismatched SDK, CLI package, platform package,
or binary fails closed.
Supported versions
| Component | Supported version |
|---|
| Published DSH | 0.1.1-rc.2 |
| Tagged DSH source host | dsh-v0.1.2-alpha.1 at cd5ef8148158c3a752a658978873241fdf8e2bbc |
@github/copilot-sdk | 1.0.11 |
@github/copilot CLI package | 1.0.81 |
| Node.js | `^20.19.0 |
One package artifact supports both DSH contracts. Compatibility for the
CallId/ToolCallId, image-handle, placeholder, image-access, usage, and model
discovery differences is isolated behind runtime capability probes. Other DSH
versions are not claimed as compatible.
Capabilities
- Dynamic official model discovery with exact model ids, context metadata,
and supported reasoning-effort metadata. Discovered vision metadata remains
authoritative except for the pinned official
auto router: CLI 1.0.81
omits its vision flag while its public SDK attachment path demonstrably
accepts images; an explicit future vision: false still wins.
- Ordered streaming text, reasoning, DSH tool-call blocks, usage, terminal
finish reasons, request ids, and stable DSH errors.
- Stateful mapping between a DSH conversation and an official Copilot session.
- Versioned DSH replay state for warm continuation, cold official-session
resume, structural integrity checks, finite pending-work expiry,
stale-state rejection, and deterministic full-history bootstrap.
- Declaration-only DSH tools. Copilot may request only the exact tools declared
by DSH; DSH remains the sole approver and executor.
- Parallel external tool calls, rich text/image tool results, failed results,
multiple tool rounds, pending-work retention, and restart recovery through
the public official SDK pending-tool RPC.
- DSH image attachments with verified MIME type, byte, count, and pixel limits;
protected current-user/latest-tool images; configurable overflow policy; and
sequential, aggregate-bounded reads.
- Same-conversation serialization, cross-conversation concurrency, bounded
cancellation, late-event fencing, unhealthy-client rotation, and bounded
plugin disposal.
- Localized English/Chinese read-only status through
/copilot-status, the Web
settings section, and POST /github-copilot-cli/status; command status lists
the exact discovered model ids as well as their count.
- Guidance-only
/copilot-login and /copilot-logout commands. They never read,
store, refresh, or delete credentials.
All model-facing Copilot built-in tools, MCP tools, skills, plugins, shell, Git,
filesystem, canvas, extension, and agent capabilities are excluded. The
effective official tool catalog is checked before each request and immediately
after every catalog/MCP lifecycle refresh. A monotonic refresh revision forces
metadata reads to repeat when a refresh arrives during inspection; any
unexpected tool fails the request closed. copilot-cli may report a connection lifecycle for its official
github-mcp-server, but that record is not treated as tool availability unless
the authoritative effective catalog exposes an MCP tool.
The pinned CLI can occasionally draft a built-in tool call even when only a
DSH custom tool is exposed, reject that draft internally, and retry the root
turn. The adapter quarantines that candidate and accepts only its correlated
failed-execution lifecycle; an actual, successful, or uncorrelated undeclared
execution still fails closed. Declared tool deltas whose toolName is briefly
null or absent are buffered by call id and released only after the declared
name is authoritatively resolved.
DSH 0.1.1-rc.2 advertises optional glob.path, grep.path, and
grep.include as ordinary strings even though the filesystem-search runtime
rejects blank values after schema validation. For these exact DSH tool shapes,
the adapter buffers the candidate until complete arguments are available and
uses the official CLI permission callback to reject an invalid candidate with
repair feedback. The CLI performs the retry; the adapter neither changes
"" to "." nor removes fields from a model-produced call. DSH therefore
receives only the official repaired call.
Authentication
Authenticate with the official GitHub Copilot CLI before using the provider:
npx @github/copilot@1.0.81 login
If the CLI is already installed globally, copilot login is equivalent. The
provider starts the same official CLI mode with useLoggedInUser: true; it does
not inspect how the official CLI stores the login.
To log out, launch the official CLI and run /logout:
npx @github/copilot@1.0.81
Installation
Published package
After the package is published:
dsh plugin --profile web add @bobhuhu/dsh-llm-github-copilot-cli
GitHub source
The repository commits prebuilt dist/ files and has no prepare script, so
GitHub installation does not need authorization to compile this package:
dsh plugin --profile web add 'github:<owner>/<repository>#<commit-or-tag>'
Quote a GitHub specifier containing # in zsh.
Local packed artifact
npm ci
npm pack
dsh plugin --profile web add /absolute/path/bobhuhu-dsh-llm-github-copilot-cli-0.1.5.tgz
npm pack rebuilds the package, verifies the packed allowlist, source maps,
browser bundle, and public server imports, then runs the deterministic test
suite before producing the tarball. Consumers use its precompiled ESM and
declarations.
Native dependency authorization
The official SDK depends on koffi, which has a native install script. pnpm 11
blocks dependency build scripts until the profile explicitly allows them. Add
this to the profile-owned workspace file (for example,
~/.dsh/profiles/web/pnpm-workspace.yaml):
allowBuilds:
koffi: true
Then run the same dsh plugin ... add command again. This profile directory is
used by DSH for plugins even when DSH itself was installed globally with npm;
it is not a source-checkout-only file. No allowBuilds entry is needed for this
plugin package because the distributed artifact is already built.
DSH Web attachment boundary
The current client bundle does not register any conversation.input.* slot,
input trigger, file picker, drag/paste handler, attachment rail, or model
selector. DSH owns the complete Chat Input surface; this provider contributes
only its status settings page plus server-side provider, model, reasoning-effort,
streaming, tool, image, and replay behavior.
The official DSH 0.1.1-rc.2 and 0.1.2-alpha.1 attachment contracts are
image-only: their composer attachment type is kind: "image", and their public
actions are onAddImages and onRemoveImage. They do not expose a
provider-neutral arbitrary-file picker or an additive generic-attachment item
interface. The provider therefore does not invent a Copilot-specific composer
entry. Native PNG, JPEG, WebP, and GIF drag/drop remains owned and rendered by
DSH. Generic-file browser intake must wait for a provider-neutral DSH contract.
The server keeps its content-addressed generic-file validation and official SDK
{ type: "file", path, displayName } projection for backward compatibility
with already serialized opaque references and non-UI callers. It is not exposed
as a DSH Web composer capability by this plugin.
The CLI exposes binary files twice: live user.message events carry inline
data, while persisted events replace those bytes with assetId and
byteLength. The isolation guard accepts both documented runtime forms only
when the SHA-256 identity matches the provider's content-addressed path. It
continues to reject unknown fields, altered paths or display names, omitted
content, and mismatched bytes or asset ids.
0.1.3 fix verification
Version 0.1.3 accepts the official CLI's root-level prompt-cache transition
from an agent-initiated DSH tool-result continuation to the next
user-initiated ordinary turn. Earlier builds treated the prior root
initiator: "agent" as delegated capability drift even when the DSH tool
catalog, system prompt, and cache configuration were unchanged. Delegated
initiators and real tool-definition drift remain fail-closed. The release probe
reproduces the exact user -> agent -> user native checkpoint sequence through
DSH Web with an authenticated Copilot Free account.
The same release also closes the DSH filesystem-search schema/runtime gap for
glob and grep. Its release probe installs the local tarball with
dsh plugin --profile web add, deliberately makes the official model propose
{"pattern":"**/pom.xml","path":""}, and requires the official permission
log to show one rejected candidate followed by one path-omitting repair. The
DSH session log must contain exactly the repaired call, one successful result,
zero failed results, and a completed turn.
The official auto catalog entry is a router, not a concrete text-only model.
With the pinned SDK 1.0.11 and CLI 1.0.81, its supports object is empty,
but the public SDK accepts image blob attachments and routes them successfully.
Version 0.1.3 therefore exposes image input for exactly this omitted-field
case; it still preserves an explicit vision: false, and it does not infer
vision for any other model id.
The authenticated Copilot Free release probe installs the packed 0.1.3
artifact into an isolated DSH Web profile and exercises both image ingress
paths with the same 149,228-byte JPEG: a user image attachment, and a
DSH-owned read_image tool result. Both turns complete through copilot/auto;
the official event logs contain one image/jpeg blob or binary respectively,
and both resolve to gpt-5-mini without a failed DSH tool result.
0.1.2 fix verification
Version 0.1.2 keeps successfully consumed pending-tool replay tombstones from
being applied to the next ordinary non-pending replay, so a completed DSH tool
turn can continue normally without weakening duplicate tool-result rejection.
Only a definitely consumed generation may advance: still-pending, cancelled,
or ambiguously cleaned-up generations remain blocked, including when a terminal
callback arrives late. Deterministic regressions also include the normal DSH
system-prompt and skill-catalog context messages injected before the next user
message, plus consecutive external-tool rounds. A pending external-tool replay
also requires the original system instructions until its result is consumed;
completed ordinary turns still support system-prompt rebinding.
The release probe exercises that exact sequence through DSH Web with an
authenticated Copilot Free account: one allowlisted tool execution, one
persisted successful result, then a completed ordinary follow-up in the same
session with no STALE_REPLAY.
0.1.1 fix verification
Version 0.1.1 fixes the scoped Web client registration required by DSH
client-modules. The release probe fetches the client bundle from the running
DSH /plugins/@bobhuhu/dsh-llm-github-copilot-cli/client.js route, executes it,
and requires ModuleLoader.load to register the exact npm package name.
0.1.0 verification scope
The release gate was exercised with the published DSH 0.1.1-rc.2, the exact
tagged 0.1.2-alpha.1 source host at commit
cd5ef8148158c3a752a658978873241fdf8e2bbc, the packed npm artifact, the pinned
official SDK/CLI, and an authenticated Copilot Free account. The alpha host was
installed with its frozen lockfile and built before the plugin tests. The gate
covers fresh Web and headless profile installation, the native koffi build,
clean-package imports, dynamic model discovery, exact text and tool streaming,
high-fragmentation tool arguments, zero-tool isolation, active and pending
cancellation, child-process fencing, unrelated-session concurrency, replay
expiry, recovery on the same DSH session, official-session restart, and
redacted Web status. Both host generations passed the six-session cancellation,
expiry, concurrency, and recovery lifecycle probe against the packed artifact.
For 0.1.0, the Free catalog exposed one usable auto route whose omitted
vision flag was conservatively treated as text-only, so image input was not
claimed as live-tested then. Version 0.1.3 replaces that old conclusion with
public-SDK and installed-DSH evidence for both direct and tool-result images.
Additional paid-only model ids remain entitlement-dependent and are not
claimed as tested by the Free account.
Usage
Select provider copilot and one of the exact ids returned by model discovery.
Examples of status surfaces:
/copilot-status
/copilot-login
/copilot-logout
The provider starts lazily on model discovery, status, or the first generation
request. Authentication or entitlement failure is reported by status and does
not prevent DSH itself from booting. /copilot-status reports the exact model
ids returned by the official catalog without exposing login identity or paths.
The Web status query uses POST so a real browser supplies the Origin header
required by the route. It always requires a loopback peer, a loopback Host,
and a matching explicit loopback Origin. On DSH 0.1.2-alpha.1, the public
Connection service also validates the normal signed DSH Web session cookie.
DSH 0.1.1-rc.2 does not expose public session validation to plugins, so that
host is limited to the loopback and same-origin fence. The response is redacted:
it contains no login identity, credential, runtime path, or raw provider error.
The command surfaces remain local DSH commands.
Configuration
The Cordis plugin entry accepts these optional fields:
| Field | Default | Meaning |
|---|
catalogTtlMs | 300000 | Successful official model-catalog cache lifetime |
negativeCatalogTtlMs | 5000 | Empty or failed catalog refresh lifetime |
defaultContextWindow | 262144 | Conservative context for an exact id absent from the advisory catalog |
fallbackModels | [] | Explicit model metadata used only when official discovery returns no usable catalog |
streamIdleTimeoutMs | 300000 | Maximum time without relevant response progress; duplicate, empty, and subagent-only events do not extend it |
pendingWorkRetentionMs | 300000 | Lifetime of replayable external-tool work |
maxTrackedPendingConversations | 4096 | Maximum simultaneous or recently retained pending replay conversations |
bridgeIdleTtlMs | 300000 | Lifetime of an idle conversation bridge before eviction |
cleanupTimeoutMs | 5000 | Per-session abort/disconnect deadline |
startupTimeoutMs | 60000 | Official CLI startup and status-query deadline |
shutdownTimeoutMs | 5000 | Official client shutdown deadline before force-stop; an unconfirmed force-stop fails teardown explicitly |
imageOverflowPolicy | offload-oldest | offload-oldest or error |
defaultImagePixelBudget | 16000000 | Maximum decoded pixels per image |
maxInlineRequestImageBytes |
The direct-API reference provider also accepts oauthTokenEnv, baseURL,
defaultMaxTokens, and per-model maxTokens. This official-CLI adapter
recognizes those compatibility fields so they cannot disappear silently, but
rejects them before provider registration. Authentication and endpoint
selection belong to the official CLI, while the pinned public SDK session API
does not expose an output-token limit. The same public-interface limitation is
why request-level maxTokens is rejected before session mutation.
Fallback models are configuration, never fabricated discovery results. A
failed or cancelled refresh preserves the last successful catalog. Whenever a
cached or configured fallback hides a refresh failure, the provider emits a
local diagnostic rather than presenting the fallback as fresh discovery.
Fallback ids and optional names must be non-empty, reasoning efforts and media
types must be unique, and vision metadata is accepted only for a vision model.
Request and replay behavior
Request options temperature, maxTokens, and stop are rejected with
UNSUPPORTED_OPTION before a native session is created or mutated because the
pinned public SDK session API does not expose those controls. Supported
reasoning efforts are validated against official per-model metadata.
This also means DSH auxiliary features that always populate maxTokens,
including the compaction and session-title implementations in the two pinned
hosts, need a different provider until the public Copilot session API exposes an
equivalent limit. The adapter fails explicitly instead of pretending to honor
the limit.
The model catalog is advisory in DSH. An exact model id that is absent from a
cached catalog is passed through unchanged with conservative text-only metadata;
the official runtime either accepts that exact id or returns its model error.
The adapter never substitutes a different model.
The official catalog's max_prompt_images value is preserved as discovery
metadata but is not enforced as an adapter-side count limit. The pinned official
SDK/CLI accepts multiple distinct images and repeated references when the same
catalog advertises 1, so the official runtime remains authoritative instead
of the adapter rejecting or deleting images first.
Request-image byte handling is applied in two phases. The pre-read phase uses a
per-image-capped source-size estimate only for the configured local
offload-oldest budget; strict error mode never rejects from an untrusted
source-size estimate. The exact phase uses storage-derived immutable bytes. It
reads protected current-user and trailing tool-result images first, then keeps
eligible images from newest to oldest within the aggregate budget, with at most
one candidate image beyond that budget in memory. Every occurrence remains in
the model-visible prompt, while repeated references to one durable image are
read, budgeted, and attached once. Pending tool-result images are all passed to
the official runtime and use their exact derived occurrence payload against the
configured local aggregate byte budget.
purpose remains model-hidden. Auxiliary compaction and session-title
requests use a replay namespace isolated from the ordinary conversation, and
the adapter does not put purpose, session identity, attribution, or credentials
into service request headers.
With valid replay state, the provider continues or resumes the same official
session. Without usable replay, it serializes the complete ordered DSH history
into one deterministic bootstrap prompt and records that mode in the next replay
state. This preserves DSH-visible history but is not a lossless import of
arbitrary foreign assistant history into native Copilot roles; the public SDK
has no such import operation.
Each successful turn closes its SDK session wrapper and releases its client
lease; the next turn creates a fresh wrapper that resumes the same validated
official session id. Replay state is bound to the DSH session, model, system,
tool catalog, transcript, purpose, official session, request correlation, and
pending-tool correlations. A SHA-256 integrity fingerprint detects torn or
tampered replay structure. It is not a cryptographic attestation that an
official request id originated at GitHub because the public SDK supplies no
verification key or signed correlation. Missing, corrupt, expired, or
cross-session pending replay fails closed before a tool result is submitted.
Cancelling retained pending work resumes the recorded official session when
necessary and calls the public SDK abort operation within the cleanup bound.
An aborted finish is returned only after that native cancellation succeeds or
the same official session generation emits its authoritative root abort/
aborted-idle event. The latter closes the race where the CLI has already
cancelled the turn but the concurrent abort RPC response is lost. Uncorrelated,
missing, or timed-out cancellation evidence still fails explicitly instead of
claiming that later replay is safe. One cancellation request owns at most one
official resume and abort attempt; concurrent callers join that result, and
teardown never retries an ambiguous native mutation.
Concurrent continuations use an explicit replay-ownership handshake. A sibling
may rely on another request only after that request has claimed pending work in
its session bridge. If the first request fails during model or option
validation, cancellation responsibility transfers to the waiting sibling.
Pending replay ownership is held in a bounded conversation ledger until the
replay retention and cleanup grace expire. The adapter fails closed when the
configured maxTrackedPendingConversations limit is reached, while expired
entries are pruned so later conversations can proceed. Expired replay is still
rejected from its validated persisted expiry before any native resume, result
RPC, or abort, even after the in-memory ledger entry has been pruned.
Plugin disposal applies the same rule to pending work, including a continuation
still blocked in model discovery or tool-result image preparation. An
interrupted DSH prepared-call handshake exposes the complete request to teardown
before the official client is stopped. Disposal then waits for the active
bridge, resumes cold replay when necessary, and confirms the public SDK abort
without submitting a result. An anonymous stream that detaches while native
session creation is still in flight remains tracked until full late-wrapper
cleanup is confirmed. An unconfirmed pending abort, late-wrapper cleanup, or
runtime shutdown rejects teardown.
DSH tool results are submitted exactly once during a healthy in-process
continuation. If submission succeeds but the stream subsequently fails, the
bridge is poisoned rather than risking a duplicate submission. A process crash
in the narrow interval after the public pending-tool RPC succeeds but before DSH
persists new replay state cannot be made atomic because the public SDK exposes
neither an idempotency key nor a query for completed pending requests.
After the RPC acknowledges a result, the official CLI may replay the completed
tool request while advancing the native session. The adapter suppresses that
event only when its call ID, request ID, tool name, and arguments exactly match
the validated pending replay; conflicting correlations fail closed and later
new tool calls are still returned to DSH.
The adapter issues one official SDK send per DSH attempt. DSH owns the exposed
retry policy. The pinned official CLI may perform internal transport recovery;
the public SDK does not expose a switch that can prove those internal retries
are disabled.
Official usage fields are independently optional. Cache-only usage is preserved
as cache read/write tokens with zero uncached input and output; an aggregate
total is emitted only when every accumulated usage event supplies authoritative
input and output counters.
Updates and removal
For a registry installation:
dsh plugin --profile web update @bobhuhu/dsh-llm-github-copilot-cli
For a pinned GitHub installation, re-run add with the new quoted commit or
tag. Remove either form with:
dsh plugin --profile web remove @bobhuhu/dsh-llm-github-copilot-cli
Troubleshooting
loaded without registering "@bobhuhu/dsh-llm-github-copilot-cli": version
0.1.0 used the pre-scope client registration id. Update to 0.1.1 or newer,
restart dsh web, and reload the browser page.
ERR_PNPM_IGNORED_BUILDS ... koffi: add koffi: true under allowBuilds
in the selected DSH profile, then retry installation.
- Authentication unavailable: run the official CLI login command above, then
use
/copilot-status again.
- Version mismatch at startup: reinstall the plugin so its exact
@github/copilot-sdk@1.0.11 and @github/copilot@1.0.81 dependency graph is
restored.
RUNTIME_SHUTDOWN_FAILED: the manager intentionally refuses to start another
CLI because shutdown of the previous process was not confirmed. Dispose the
host, inspect the reported runtime failure, and restart only after resolving
the orphan-process condition.
UNSUPPORTED_OPTION: remove temperature, maxTokens, or stop from that
DSH request. Configure another compaction or session-title provider when the
calling DSH feature always supplies maxTokens.
REPLAY_REQUIRED: continue with the exact replay state, DSH conversation,
model, and pending tool results returned by the provider.
STALE_REPLAY: use the newest replay state for that conversation and never
resubmit a result from an older pending generation. If every ordinary message
after a successfully completed tool turn fails this way, update to 0.1.2 or
newer and restart dsh web; earlier builds incorrectly applied the pending
replay tombstone to the following non-pending replay.
REPLAY_EXPIRED, INVALID_REPLAY, or SESSION_POISONED: discard the unsafe
continuation and start a new conversation; the provider will not guess or
revive pending correlations.
TOOL_ISOLATION_FAILED: the official runtime exposed something outside the
exact DSH tool allowlist. The provider intentionally refuses to continue. If
the exact detail is prompt-cache telemetry reported model-facing capability drift immediately after a completed tool continuation, update to 0.1.3 or
newer and restart dsh web; earlier builds rejected the official root
agent -> user cache-frontier transition.
Security boundary
The adapter imports only public package APIs, spawns only the official packaged
CLI through the SDK, and never calls a Copilot HTTP endpoint. Source and packed
artifact checks prohibit copied SDK implementation, OAuth/token code, private
protocol access, credentials, local paths, and development caches.
License
The adapter is MIT licensed. The installed official Copilot CLI remains subject
to its own license and GitHub terms. See THIRD_PARTY_NOTICES.md.