Skip to content
Merged
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
3 changes: 1 addition & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,6 @@ All models support tool calling. GLM-5.2, GLM-5.1, and Claude (Fable 5, Opus, So

| Setting | Default | Description |
| -------------------------------------------- | ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `opencode-for-copilot.opencodePlan` | `go` | Active OpenCode plan (`go` subscription or `zen` pay-as-you-go). Picks your default endpoint; the picker always lists the full Go+Zen catalog, prefixed `go/…` or `zen/…` by billing path |
| `opencode-for-copilot.endpoint` | `opencode-go` | Single-value endpoint selector. `opencode-go` / `opencode-go-anthropic` serve OpenCode Go subscription models; `opencode-zen` / `opencode-zen-anthropic` serve OpenCode Zen pay-as-you-go models (including Claude and free models) |
| `opencode-for-copilot.baseUrl` | empty | Optional API endpoint override. When non-empty, overrides the `endpoint` preset. |
| `opencode-for-copilot.maxTokens` | `0` | Max output tokens (`0` = no limit). Useful for cost control |
Expand Down Expand Up @@ -293,7 +292,7 @@ This fork originated from *GLM for VSCode Copilot* and reused its identifiers. I

Key features introduced along the way:

- **Full model catalog** — the `opencode-for-copilot.opencodePlan` setting (`go`, default, or `zen`) picks your default endpoint; every Go and Zen model is listed either way, each prefixed with its billing path (`go/GLM-5.2`, `zen/Claude Sonnet 5`). Picking a `zen/…` model without credits fails with a clear message instead of a cryptic 401.
- **Full model catalog** — every Go and Zen model is listed in the picker, each prefixed with its billing path (`go/GLM-5.2`, `zen/Claude Sonnet 5`). Use `opencode-for-copilot.endpoint` for your default endpoint. Picking a `zen/…` model without credits fails with a clear message instead of a cryptic 401.
- **Live model catalog** — the list is fetched from the official OpenCode endpoints and re-fetched when stale: opening the picker past the TTL refreshes in the background, failed fetches retry after 60 s instead of serving the static fallback for 5 minutes (covers starting VS Code before your VPN is up), and **OpenCode: Refresh Model List** forces an immediate re-fetch.
- **Clearer errors** — a Zen-only-model billing failure explains that the *model choice* is the problem, not the API key.
- **Agent swarm participant** moved to `opencode-for-copilot.pipeline` (re-type `@swarm` once after updating).
Expand Down
1 change: 0 additions & 1 deletion README.zh-cn.md
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,6 @@ API Key 存储在 VS Code 的 `SecretStorage` 中(macOS 钥匙串 / Windows

| 设置项 | 默认值 | 说明 |
| -------------------------------------------- | ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `opencode-for-copilot.opencodePlan` | `go` | 当前使用的 OpenCode 套餐(`go` 订阅或 `zen` 按量付费)。决定默认端点;模型选择器始终列出完整的 Go+Zen 目录,并以 `go/…` 或 `zen/…` 前缀标明计费路径 |
| `opencode-for-copilot.endpoint` | `opencode-go` | 单值端点选择器。`opencode-go` / `opencode-go-anthropic` 使用 OpenCode Go 订阅模型;`opencode-zen` / `opencode-zen-anthropic` 使用 OpenCode Zen 按量付费模型(含 Claude 和免费模型) |
| `opencode-for-copilot.baseUrl` | 留空 | 可选 API endpoint 覆盖项。任何非空值都会优先覆盖 `endpoint` preset。 |
| `opencode-for-copilot.maxTokens` | `0` | 最大输出 Token 数(`0` = 不限制)。可用于成本控制 |
Expand Down
13 changes: 2 additions & 11 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
{
"name": "opencode-for-copilot",
"displayName": "OpenCode for Copilot",
"displayName": "%opencode-for-copilot.displayName%",
"description": "Use OpenCode Go & Zen coding models (GLM, Kimi, DeepSeek, Claude, Grok, Qwen, free models) in the Copilot Chat model picker. Vision, thinking mode, agent tools — zero config, BYOK.",
"icon": "resources/icon.png",
"version": "3.11.13",
"version": "3.11.14",
"packageManager": "pnpm@11.7.0",
"publisher": "abbalochdev",
"license": "MIT",
Expand Down Expand Up @@ -152,15 +152,6 @@
"configuration": {
"title": "%opencode-for-copilot.config.title%",
"properties": {
"opencode-for-copilot.opencodePlan": {
"type": "string",
"enum": [
"go",
"zen"
],
"default": "go",
"markdownDescription": "%opencode-for-copilot.config.opencodePlan.description%"
},
"opencode-for-copilot.agentRoles": {
"type": "object",
"default": {},
Expand Down
2 changes: 1 addition & 1 deletion package.nls.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{
"opencode-for-copilot.displayName": "OpenCode for Copilot",
"opencode-for-copilot.command.setApiKey": "OpenCode: Set Go/Zen API Key",
"opencode-for-copilot.command.getApiKey": "OpenCode: Open API Key Page",
"opencode-for-copilot.command.queryUsage": "OpenCode: Query Usage",
Expand All @@ -22,7 +23,6 @@
"opencode-for-copilot.walkthrough.advancedSettings.title": "Try advanced settings",
"opencode-for-copilot.walkthrough.advancedSettings.description": "Customize providers and model names, configure the vision proxy, enable experimental settings, and more in the extension settings.\n[Open settings](command:opencode-for-copilot.openSettings)",
"opencode-for-copilot.config.title": "OpenCode for Copilot",
"opencode-for-copilot.config.opencodePlan.description": "Which OpenCode plan to use: `go` (subscription; GLM, Kimi, MiniMax, Grok 4.5) or `zen` (pay-as-you-go; also serves Claude and free models). Determines the model list, default endpoint, and which secret the \"Set API Key\" command writes. The two keys are stored separately — use [Set API Key](command:opencode-for-copilot.setApiKey).",
"opencode-for-copilot.config.baseUrl.description": "Optional API base URL override. When non-empty, this value has the highest priority and overrides the `endpoint` preset. Leave empty to use the selected OpenCode endpoint preset.",
"opencode-for-copilot.config.endpoint.description": "OpenCode endpoint selector. Each option resolves to a base URL and wire protocol (OpenAI `/chat/completions` or Anthropic `/v1/messages`). Go serves the curated subscription catalogue; Zen serves pay-as-you-go models. A non-empty `baseUrl` overrides this preset.",
"opencode-for-copilot.config.endpoint.opencodeGo.label": "OpenCode Go (OpenAI)",
Expand Down
4 changes: 2 additions & 2 deletions package.nls.zh-cn.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{
"opencode-for-copilot.displayName": "OpenCode for Copilot",
"opencode-for-copilot.command.setApiKey": "OpenCode: 设置 Go/Zen API Key",
"opencode-for-copilot.command.getApiKey": "OpenCode: 打开 API Key 页面",
"opencode-for-copilot.command.queryUsage": "OpenCode: 查询用量",
Expand All @@ -8,7 +9,6 @@
"opencode-for-copilot.command.openSettings": "OpenCode: 打开设置",
"opencode-for-copilot.command.showLogs": "OpenCode: 显示日志",
"opencode-for-copilot.command.showDiagnostics": "OpenCode: 显示运行时诊断",
"opencode-for-copilot.config.opencodePlan.description": "使用哪个 OpenCode 套餐:`go`(订阅,含 GLM、Kimi、MiniMax、Grok 4.5)或 `zen`(按量付费,额外提供 Claude 与免费模型)。决定模型列表、默认端点,以及“设置 API Key”写入哪个密钥。两个密钥分开存储——使用[设置 API Key](command:opencode-for-copilot.setApiKey)。",
"opencode-for-copilot.config.showProviderPrefix.description": "在模型选择器中为模型名称添加提供商前缀(例如 `Kimi · Kimi K3`),窄面板中也能一眼辨认模型归属。",
"opencode-for-copilot.command.openRequestDumpsFolder": "OpenCode: 打开请求 Dump 目录",
"opencode-for-copilot.command.setPonytailMode": "OpenCode: 设置 Ponytail 模式",
Expand All @@ -22,7 +22,7 @@
"opencode-for-copilot.walkthrough.showModels.description": "如果模型被隐藏,打开 VS Code 的语言模型管理器并显示 OpenCode 模型。\n[打开语言模型管理](command:workbench.action.chat.manage)",
"opencode-for-copilot.walkthrough.advancedSettings.title": "尝试高级设置",
"opencode-for-copilot.walkthrough.advancedSettings.description": "可以在插件设置中自定义提供方与模型名称、自定义视觉代理、开启实验性设置等。\n[打开设置](command:opencode-for-copilot.openSettings)",
"opencode-for-copilot.config.title": "OpenCode for Copilot",
"opencode-for-copilot.config.title": "OpenCode 助手",
"opencode-for-copilot.config.baseUrl.description": "可选的 API 基础 URL 覆盖项。非空时优先级最高,会覆盖 `endpoint` 预设;留空时使用选定的 OpenCode endpoint 预设。",
"opencode-for-copilot.config.endpoint.description": "OpenCode endpoint 选择器。每个选项对应一个基础 URL 和线路协议(OpenAI `/chat/completions` 或 Anthropic `/v1/messages`)。Go 提供订阅精选模型;Zen 提供按量付费模型。`baseUrl` 非空时仍会覆盖此预设。",
"opencode-for-copilot.config.endpoint.opencodeGo.label": "OpenCode Go(OpenAI)",
Expand Down
17 changes: 4 additions & 13 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@ import {
normalizeBaseUrl,
resolveEndpointApiKeyUrl,
resolveEndpointBaseUrl,
resolveEndpointProtocol,
resolvePlanDefaultEndpoint,
type OpencodePlan
resolveEndpointProtocol
} from './endpoint';
import {
getDynamicModels
Expand All @@ -31,7 +29,7 @@ const CUSTOM_MODEL_MAX_OUTPUT_TOKENS = 131_072;
* Resolution order:
* 1. `baseUrl` override (highest priority — covers advanced/proxy use cases)
* 2. `endpoint` preset
* 3. The active OpenCode plan's default endpoint (`opencode-go` / `opencode-zen`)
* 3. `opencode-go` when the endpoint preset is unset or invalid
*/
export function getBaseUrl(): string {
const override = getBaseUrlOverride();
Expand All @@ -56,24 +54,17 @@ export function getBaseUrlOverride(): string | undefined {
*
* Resolution order:
* 1. Explicit `endpoint` setting (always wins)
* 2. The active OpenCode plan's default endpoint (`opencode-go` / `opencode-zen`)
* 2. `opencode-go` when the endpoint preset is unset or invalid
*/
export function getEndpoint(): EndpointPreset {
const config = vscode.workspace.getConfiguration(CONFIG_SECTION);
const explicit = normalizeEndpointPreset(config.get<string>('endpoint'));
return explicit ?? resolvePlanDefaultEndpoint(getOpencodePlan());
}

/** Which OpenCode plan's key and model catalog to use — `opencodePlan` setting. */
export function getOpencodePlan(): OpencodePlan {
const config = vscode.workspace.getConfiguration(CONFIG_SECTION);
return config.get<string>('opencodePlan') === 'zen' ? 'zen' : 'go';
return explicit ?? 'opencode-go';
}

// ---- One-time legacy settings migration (glm-copilot -> opencode-for-copilot) ----

const LEGACY_SETTING_KEYS = [
'opencodePlan',
'agentRoles',
'baseUrl',
'endpoint',
Expand Down
3 changes: 2 additions & 1 deletion src/provider/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,8 @@ export class GLMChatProvider implements vscode.LanguageModelChatProvider {
if (
e.affectsConfiguration(`${CONFIG_SECTION}.apiKey`) ||
e.affectsConfiguration(`${CONFIG_SECTION}.baseUrl`) ||
e.affectsConfiguration(`${CONFIG_SECTION}.endpoint`) || e.affectsConfiguration(`${CONFIG_SECTION}.opencodePlan`) || e.affectsConfiguration(`${CONFIG_SECTION}.customModels`) ||
e.affectsConfiguration(`${CONFIG_SECTION}.endpoint`) ||
e.affectsConfiguration(`${CONFIG_SECTION}.customModels`) ||
e.affectsConfiguration(`${CONFIG_SECTION}.modelIdOverrides`)
) {
// Discard stale GLMClient instances whose baseUrl/apiKey/protocol may have changed.
Expand Down
2 changes: 0 additions & 2 deletions src/runtime/diagnostics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import {
getCodeSimplifierEnabled,
getDebugMode,
getEndpoint,
getOpencodePlan,
getPonytailMode,
} from '../config';
import { CONFIG_SECTION } from '../consts';
Expand Down Expand Up @@ -61,7 +60,6 @@ export function buildRuntimeDiagnosticsReport(context: vscode.ExtensionContext):
'',
'## Configuration',
`- Endpoint preset: ${getEndpoint()}`,
`- OpenCode plan: ${getOpencodePlan()}`,
`- Base URL: ${getBaseUrl()}`,
`- API protocol: ${getApiProtocol()}`,
`- Debug mode: ${getDebugMode()}`,
Expand Down
14 changes: 3 additions & 11 deletions test/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,7 @@ import { MODELS } from '../src/consts';
import {
OPENCODE_GO_ANTHROPIC_BASE_URL,
OPENCODE_GO_API_KEY_URL,
OPENCODE_GO_OPENAI_BASE_URL,
OPENCODE_ZEN_OPENAI_BASE_URL
OPENCODE_GO_OPENAI_BASE_URL
} from '../src/endpoint';
import { __clearConfigurationValues, __setConfigurationValue } from './support/vscode.mock';

Expand Down Expand Up @@ -70,28 +69,21 @@ describe('endpoint preset selection', () => {
__clearConfigurationValues();
});

it('defaults to opencode-go when nothing is configured (plan default)', () => {
it('defaults to opencode-go when nothing is configured', () => {
expect(getEndpoint()).toBe('opencode-go');
expect(getBaseUrl()).toBe(OPENCODE_GO_OPENAI_BASE_URL);
expect(getApiProtocol()).toBe('openai');
expect(getApiKeyUrl()).toBe(OPENCODE_GO_API_KEY_URL);
});

it('defaults to opencode-zen when opencodePlan is zen', () => {
__setConfigurationValue('opencode-for-copilot.opencodePlan', 'zen');

expect(getEndpoint()).toBe('opencode-zen');
expect(getBaseUrl()).toBe(OPENCODE_ZEN_OPENAI_BASE_URL);
});

it('respects an explicit endpoint preset', () => {
__setConfigurationValue('opencode-for-copilot.endpoint', 'opencode-zen-anthropic');

expect(getEndpoint()).toBe('opencode-zen-anthropic');
expect(getApiProtocol()).toBe('anthropic');
});

it('ignores unknown preset values and falls back to the plan default', () => {
it('ignores unknown preset values and falls back to opencode-go', () => {
// Presets removed with the Zhipu/Z.ai endpoint layer must not crash
// users who still carry them in settings.json.
__setConfigurationValue('opencode-for-copilot.endpoint', 'china-coding');
Expand Down
4 changes: 2 additions & 2 deletions test/provider/models.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@ describe('offline fallback baseline', () => {
expect(presets.some((p) => p.startsWith('opencode-zen'))).toBe(true);
});

it('serves the whole baseline regardless of the plan toggle', () => {
__setConfigurationValue('opencode-for-copilot.opencodePlan', 'go');
it('serves the whole baseline regardless of endpoint preset', () => {
__setConfigurationValue('opencode-for-copilot.endpoint', 'opencode-go');
const infos = listProviderModels().map((m) => toChatInfo(m, true));
expect(infos.every((m) => m.isUserSelectable)).toBe(true);
});
Expand Down
8 changes: 4 additions & 4 deletions test/runtime/commands.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ describe('runtime commands', () => {
});

it.each([
['go', OPENCODE_GO_API_KEY_URL],
['zen', OPENCODE_ZEN_API_KEY_URL],
])('opens the API key page for the %s plan', async (plan, expectedUrl) => {
__setConfigurationValue('opencode-for-copilot.opencodePlan', plan);
['opencode-go', OPENCODE_GO_API_KEY_URL],
['opencode-zen', OPENCODE_ZEN_API_KEY_URL],
])('opens the API key page for the %s endpoint', async (endpoint, expectedUrl) => {
__setConfigurationValue('opencode-for-copilot.endpoint', endpoint);
registerCommands({ subscriptions: [] } as unknown as vscode.ExtensionContext);

await vscode.commands.executeCommand('opencode-for-copilot.getApiKey');
Expand Down
Loading