Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,44 @@ Zoo Code builds on the foundation created by Roo Code and continues to expand it
- **More dependable terminal and editing workflows** — fixes for premature terminal completion, task-state races, context management, diff editing, and provider-specific tool use.
- **More control over your workspace** — rules management, per-mode MCP restrictions, multi-root path controls, model reasoning options, and completion change review actions.

## GitHub Copilot

Select **GitHub Copilot** in Zoo Code's provider settings and click **Sign in to GitHub**. GitHub
Copilot Chat must be installed and enabled. Zoo Code shows the signed-in account and refreshes the
model list as Copilot registers models. Select a model and save the profile. VS Code may ask you to
allow Zoo Code to use the model on the first request.

Signing in proves a GitHub identity, not a Copilot subscription. An account without Copilot access
signs in successfully and simply sees no models.

- **Reconnect GitHub** requests a fresh authorization instead of reusing the existing session.
- **Manage GitHub account** opens VS Code's own account menu. VS Code offers extensions no way to
sign out on a user's behalf, so signing out happens there. It affects Copilot and every other
extension using the account.

VS Code manages the GitHub session; Zoo Code never displays or stores the access token.

### Capabilities

Capabilities come from what VS Code reports for each Copilot model, so new models work without code
changes, and nothing about a model is saved into your settings where it could go stale.

- **Context window:** the model's reported input limit, capped by Zoo Code's curated limit where one
is known. Before the host reports, the curated limit is shown, matching what Zoo Code enforces.
- **Images:** read from the capability the host reports for each live model. A model that reports
nothing shows **Image support not reported** instead of being assumed unsupported or borrowed from
Zoo Code's curated catalog, which only describes a model the host has not listed yet. Images are
sent as image data, not text placeholders, and need a VS Code version that supports image parts;
Zoo Code's minimum supported VS Code version stays 1.100.
- **Tools:** VS Code tells extensions only whether a model supports tools, not how many it accepts,
so Zoo Code applies the same advisory it shows for any provider when many MCP tools are enabled.
GitHub plan limits, premium-request allowances, organization policies, and backend limits still apply.
- **Prompt caching:** Copilot owns any backend caching. The public VS Code Language Model API exposes
no cache-control directives or cache-hit usage, so Zoo Code neither enables caching nor reports
savings; the provider-managed label states this and does not promise a cache hit.

The existing **VS Code LM API** provider remains available for models from other extensions.

## What's New in v3.86.0

- ✨ **New model support:** Use GPT-6.1 Sol, get the corrected Claude Opus 5.5 output budget, and select DeepSeek V4.1 Flash on OpenCode Go.
Expand Down
1 change: 1 addition & 0 deletions apps/cli/src/lib/utils/context-window.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ function getModelIdForProvider(config: ProviderSettings): string | undefined {
case retiredProviderIdentifiers.ioIntelligence:
case retiredProviderIdentifiers.roo:
case providerIdentifiers.vscodeLm:
case providerIdentifiers.githubCopilot:
case providerIdentifiers.fakeAi:
case undefined:
return config.apiModelId
Expand Down
4 changes: 4 additions & 0 deletions packages/types/src/__tests__/provider-default-model.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ describe("getProviderDefaultModelId", () => {
expect(getProviderDefaultModelId(providerIdentifiers.vscodeLm)).toBe(vscodeLlmDefaultModelId)
})

it("gives GitHub Copilot the same default as the VS Code LM provider", () => {
expect(getProviderDefaultModelId(providerIdentifiers.githubCopilot)).toBe(vscodeLlmDefaultModelId)
})

it("uses the canonical OpenAI Native default model", () => {
expect(getProviderDefaultModelId(providerIdentifiers.openaiNative)).toBe(openAiNativeDefaultModelId)
})
Expand Down
3 changes: 2 additions & 1 deletion packages/types/src/__tests__/provider-identifiers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ const expectedProviderIdentifiers = [
"ollama",
"lmstudio",
"vscode-lm",
"github-copilot",
"openai",
"fake-ai",
"anthropic",
Expand Down Expand Up @@ -114,7 +115,7 @@ describe("provider identifiers", () => {
providerIdentifiers.kimiCode,
])
expect(localProviders).toEqual([providerIdentifiers.ollama, providerIdentifiers.lmstudio])
expect(internalProviders).toEqual([providerIdentifiers.vscodeLm])
expect(internalProviders).toEqual([providerIdentifiers.vscodeLm, providerIdentifiers.githubCopilot])
expect(customProviders).toEqual([providerIdentifiers.openai])
expect(fauxProviders).toEqual([providerIdentifiers.fakeAi])
})
Expand Down
42 changes: 41 additions & 1 deletion packages/types/src/__tests__/provider-model-id.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import { retiredProviderIdentifiers } from "../provider-identifiers.js"
import { getModelId, modelIdKeys, providerIdentifiers, type ProviderSettings } from "../index.js"
import {
getModelId,
modelIdKeys,
providerIdentifiers,
providerSettingsSchema,
type ProviderSettings,
} from "../index.js"

const expectedModelIdKeys = [
"apiModelId",
Expand Down Expand Up @@ -54,6 +60,40 @@ describe("getModelId", () => {
expect(getModelId(settings)).toBe("vscode-model")
})

it("preserves the exact Copilot model ID through the provider settings schema", () => {
const settings: ProviderSettings = {
apiProvider: providerIdentifiers.githubCopilot,
vsCodeLmModelSelector: { vendor: "copilot", family: "gpt-4o", id: "copilot-model", version: "1" },
}
expect(getModelId(settings)).toBe("copilot-model")
})

it("persists only the selector's identity, never live model capabilities", () => {
const saved = providerSettingsSchema.parse({
apiProvider: providerIdentifiers.githubCopilot,
vsCodeLmModelSelector: { vendor: "copilot", id: "dynamic-model", family: "dynamic-model", version: "1" },
})
expect(saved.vsCodeLmModelSelector).toEqual({
vendor: "copilot",
id: "dynamic-model",
family: "dynamic-model",
version: "1",
})
})

it("drops a stale capability snapshot from profiles saved by earlier builds", () => {
const saved = providerSettingsSchema.parse({
apiProvider: providerIdentifiers.githubCopilot,
vsCodeLmModelSelector: {
vendor: "copilot",
id: "old-model",
modelInfo: { contextWindow: 1, supportsImages: false, supportsPromptCache: false },
},
})
expect(getModelId(saved)).toBe("old-model")
expect(saved.vsCodeLmModelSelector).not.toHaveProperty("modelInfo")
})

it("uses openAiModelId for OpenAI Compatible", () => {
const settings: ProviderSettings = {
apiProvider: providerIdentifiers.openai,
Expand Down
1 change: 1 addition & 0 deletions packages/types/src/provider-identifiers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export const providerIdentifiers = {
ollama: "ollama",
lmstudio: "lmstudio",
vscodeLm: "vscode-lm",
githubCopilot: "github-copilot",
openai: "openai",
fakeAi: "fake-ai",
anthropic: "anthropic",
Expand Down
7 changes: 6 additions & 1 deletion packages/types/src/provider-settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@
* model list.
*/

export const internalProviders = [providerIdentifiers.vscodeLm] as const
export const internalProviders = [providerIdentifiers.vscodeLm, providerIdentifiers.githubCopilot] as const

export type InternalProvider = (typeof internalProviders)[number]

Expand Down Expand Up @@ -491,6 +491,11 @@
label: "VS Code LM API",
models: Object.keys(vscodeLlmModels),
},
[providerIdentifiers.githubCopilot]: {
id: providerIdentifiers.githubCopilot,
label: "GitHub Copilot",

Check warning on line 496 in packages/types/src/provider-settings.ts

View workflow job for this annotation

GitHub Actions / mutation-diff

Mutation test advisory

packages/types/src/provider-settings.ts:496: Survived StringLiteral mutant (replacement: ""). See the job summary for the complete list and resolution guidance.
models: Object.keys(vscodeLlmModels),
},
[providerIdentifiers.xai]: { id: providerIdentifiers.xai, label: "xAI (Grok)", models: Object.keys(xaiModels) },
[providerIdentifiers.zai]: {
id: providerIdentifiers.zai,
Expand Down
3 changes: 2 additions & 1 deletion packages/types/src/provider-settings/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { bedrockProviderDefinition } from "./bedrock.js"
import { vertexProviderDefinition } from "./vertex.js"
import { openAiProviderDefinition } from "./openai.js"
import { ollamaProviderDefinition } from "./ollama.js"
import { vsCodeLmProviderDefinition } from "./vscode-lm.js"
import { githubCopilotProviderDefinition, vsCodeLmProviderDefinition } from "./vscode-lm.js"
import { lmStudioProviderDefinition } from "./lm-studio.js"
import { geminiProviderDefinition } from "./gemini.js"
import { geminiCliProviderDefinition } from "./gemini-cli.js"
Expand Down Expand Up @@ -56,6 +56,7 @@ export const providerDefinitionList = [
openAiProviderDefinition,
ollamaProviderDefinition,
vsCodeLmProviderDefinition,
githubCopilotProviderDefinition,
lmStudioProviderDefinition,
geminiProviderDefinition,
geminiCliProviderDefinition,
Expand Down
30 changes: 19 additions & 11 deletions packages/types/src/provider-settings/vscode-lm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,26 @@ import { z } from "zod"
import { providerIdentifiers } from "../provider-identifiers.js"
import { baseProviderSettingsShape, createProviderDefinition } from "./common.js"

const schema = {
...baseProviderSettingsShape,
vsCodeLmModelSelector: z
.object({
vendor: z.string().optional(),
family: z.string().optional(),
version: z.string().optional(),
id: z.string().optional(),
})
.optional(),
}

export const vsCodeLmProviderDefinition = createProviderDefinition({
apiProvider: providerIdentifiers.vscodeLm,
getModelId: (settings) => settings.vsCodeLmModelSelector?.id,
schema: {
...baseProviderSettingsShape,
vsCodeLmModelSelector: z
.object({
vendor: z.string().optional(),
family: z.string().optional(),
version: z.string().optional(),
id: z.string().optional(),
})
.optional(),
},
schema,
})

export const githubCopilotProviderDefinition = createProviderDefinition({
apiProvider: providerIdentifiers.githubCopilot,
getModelId: (settings) => settings.vsCodeLmModelSelector?.id,
schema,
})
1 change: 1 addition & 0 deletions packages/types/src/providers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ export function getProviderDefaultModelId(
case providerIdentifiers.lmstudio:
return NO_DEFAULT_MODEL_ID
case providerIdentifiers.vscodeLm:
case providerIdentifiers.githubCopilot:
return vscodeLlmDefaultModelId
case providerIdentifiers.sambanova:
return sambaNovaDefaultModelId
Expand Down
37 changes: 36 additions & 1 deletion packages/types/src/providers/vscode-llm.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,50 @@
import { z } from "zod"

import type { ModelInfo } from "../model.js"
import { openAiModelInfoSaneDefaults } from "./openai.js"

export const vsCodeLmModelsMessageTypes = ["requestVsCodeLmModels", "vsCodeLmModels"] as const
export const vsCodeLmModelsMessageTypes = [
"requestVsCodeLmModels",
"vsCodeLmModels",
"githubCopilotSignIn",
"githubCopilotReconnect",
"githubCopilotManageAccount",
"githubCopilotSignInResult",
"githubCopilotModels",
] as const

export const vsCodeLmModelsMessageTypeSchema = z.enum(vsCodeLmModelsMessageTypes)

export const VsCodeLmModelsMessageType = vsCodeLmModelsMessageTypeSchema.enum

export type VsCodeLmModelsMessageType = z.infer<typeof vsCodeLmModelsMessageTypeSchema>

const githubCopilotVendor = "copilot"

Check warning on line 22 in packages/types/src/providers/vscode-llm.ts

View workflow job for this annotation

GitHub Actions / mutation-diff

Mutation test advisory

packages/types/src/providers/vscode-llm.ts:22: Survived StringLiteral mutant (replacement: ""). See the job summary for the complete list and resolution guidance.

/** Identifiers for the language models and sign-in flow GitHub Copilot contributes to VS Code. */
export const githubCopilotLanguageModel = {
vendor: githubCopilotVendor,
/** Selector matching every model the Copilot extension contributes. */
selector: { vendor: githubCopilotVendor },
authProviderId: "github",
/** In Copilot Chat's own preference order, so Zoo recognizes the session Copilot actually uses. */
authScopeSets: [["user:email"], ["read:user"]],
chatExtensionId: "GitHub.copilot-chat",
/** Public Command Palette command; VS Code offers no API to sign out, so Zoo defers to this UI. */
manageAccountsCommandId: "workbench.action.manageAccounts",
} as const

/**
* Baseline for a model whose capabilities are not yet known. Unlike `openAiModelInfoSaneDefaults` it
* leaves vision unset, so a capability nobody reported is not mistaken for one that is unsupported.
*/
export const vscodeLlmBaselineModelInfo: ModelInfo = {

Check warning on line 41 in packages/types/src/providers/vscode-llm.ts

View workflow job for this annotation

GitHub Actions / mutation-diff

Mutation test advisory

packages/types/src/providers/vscode-llm.ts:41: Survived ObjectLiteral mutant (replacement: {}). See the job summary for the complete list and resolution guidance.
contextWindow: openAiModelInfoSaneDefaults.contextWindow,
supportsPromptCache: false,

Check warning on line 43 in packages/types/src/providers/vscode-llm.ts

View workflow job for this annotation

GitHub Actions / mutation-diff

Mutation test advisory

packages/types/src/providers/vscode-llm.ts:43: Survived BooleanLiteral mutant (replacement: true). See the job summary for the complete list and resolution guidance.
inputPrice: 0,
outputPrice: 0,
}

export type VscodeLlmModelId = keyof typeof vscodeLlmModels

export const vscodeLlmDefaultModelId: VscodeLlmModelId = "claude-sonnet-4.5"
Expand Down
18 changes: 16 additions & 2 deletions packages/types/src/vscode-extension-host.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import type { CloudUserInfo, CloudOrganizationMembership, OrganizationAllowList,
import type { SerializedCustomToolDefinition } from "./custom-tool.js"
import type { GitCommit } from "./git.js"
import type { McpServer } from "./mcp.js"
import { RouterModelsMessageType, type ModelRecord, type RouterModels } from "./model.js"
import { RouterModelsMessageType, type ModelInfo, type ModelRecord, type RouterModels } from "./model.js"
import { LmStudioModelsMessageType } from "./providers/lm-studio.js"
import { OllamaModelsMessageType } from "./providers/ollama.js"
import { OpenAiModelsMessageType } from "./providers/openai.js"
Expand Down Expand Up @@ -48,6 +48,8 @@ export interface ExtensionMessage {
| typeof OllamaModelsMessageType.ollamaModels
| typeof LmStudioModelsMessageType.lmStudioModels
| typeof VsCodeLmModelsMessageType.vsCodeLmModels
| typeof VsCodeLmModelsMessageType.githubCopilotModels
| typeof VsCodeLmModelsMessageType.githubCopilotSignInResult
| "vsCodeLmApiAvailable"
| "updatePrompt"
| "systemPrompt"
Expand Down Expand Up @@ -143,7 +145,16 @@ export interface ExtensionMessage {
openAiModels?: string[]
ollamaModels?: ModelRecord
lmStudioModels?: ModelRecord
vsCodeLmModels?: { vendor?: string; family?: string; version?: string; id?: string }[]
vsCodeLmModels?: {
vendor?: string
family?: string
version?: string
id?: string
name?: string
maxInputTokens?: number
modelInfo?: ModelInfo
}[]
githubCopilotAccount?: string | null
mcpServers?: McpServer[]
commits?: GitCommit[]
listApiConfig?: ProviderSettingsEntry[]
Expand Down Expand Up @@ -496,6 +507,9 @@ export interface WebviewMessage {
| typeof LmStudioModelsMessageType.requestLmStudioModels
| "requestRooModels"
| typeof VsCodeLmModelsMessageType.requestVsCodeLmModels
| typeof VsCodeLmModelsMessageType.githubCopilotSignIn
| typeof VsCodeLmModelsMessageType.githubCopilotReconnect
| typeof VsCodeLmModelsMessageType.githubCopilotManageAccount
| "openImage"
| "saveImage"
| "openFile"
Expand Down
6 changes: 6 additions & 0 deletions src/api/__tests__/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ import {
VercelAiGatewayHandler,
VertexHandler,
VsCodeLmHandler,
GitHubCopilotHandler,
XAIHandler,
ZAiHandler,
ZooGatewayHandler,
Expand All @@ -86,6 +87,7 @@ const expectedHandlers = {
[providerIdentifiers.moonshot]: MoonshotHandler,
[providerIdentifiers.kimiCode]: KimiCodeHandler,
[providerIdentifiers.vscodeLm]: VsCodeLmHandler,
[providerIdentifiers.githubCopilot]: GitHubCopilotHandler,
[providerIdentifiers.mistral]: MistralHandler,
[providerIdentifiers.requesty]: RequestyHandler,
[providerIdentifiers.unbound]: UnboundHandler,
Expand Down Expand Up @@ -118,6 +120,10 @@ describe("buildApiHandler", () => {
expect(handler).toBeInstanceOf(Handler)
})

it("keeps the generic VS Code LM provider free of Copilot-specific behavior", () => {
expect(buildApiHandler({ apiProvider: providerIdentifiers.vscodeLm })).not.toBeInstanceOf(GitHubCopilotHandler)
})

it.each([
["an unspecified model", undefined, VertexHandler],
["non-Claude models", "non-claude-test-model", VertexHandler],
Expand Down
3 changes: 3 additions & 0 deletions src/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import {
KimiCodeHandler,
MistralHandler,
VsCodeLmHandler,
GitHubCopilotHandler,
RequestyHandler,
UnboundHandler,
FakeAIHandler,
Expand Down Expand Up @@ -204,6 +205,8 @@ export function buildApiHandler(configuration: ProviderSettings): ApiHandler {
return new KimiCodeHandler(options)
case providerIdentifiers.vscodeLm:
return new VsCodeLmHandler(options)
case providerIdentifiers.githubCopilot:
return new GitHubCopilotHandler(options)
case providerIdentifiers.mistral:
return new MistralHandler(options)
case providerIdentifiers.requesty:
Expand Down
Loading
Loading