@gnahz77/opencode-copilot-multi-auth 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -12,7 +12,7 @@ Add the plugin to your `opencode` config:
12
12
  {
13
13
  "$schema": "https://opencode.ai/config.json",
14
14
  "plugin": [
15
- "@gnahz77/opencode-copilot-multi-auth@0.1.1"
15
+ "@gnahz77/opencode-copilot-multi-auth@0.1.2"
16
16
  ]
17
17
  }
18
18
  ```
@@ -25,7 +25,7 @@ If you also want the TUI command support provided by this package, add the same
25
25
  {
26
26
  "$schema": "https://opencode.ai/tui.json",
27
27
  "plugin": [
28
- "@gnahz77/opencode-copilot-multi-auth@0.1.1"
28
+ "@gnahz77/opencode-copilot-multi-auth@0.1.2"
29
29
  ]
30
30
  }
31
31
  ```
@@ -157,11 +157,13 @@ The pool stores one object per account under `accounts` in a `version: 1` docume
157
157
  | `id` | Stable human-friendly identifier stored with the account record. |
158
158
  | `name` | Display name for the account. |
159
159
  | `enabled` | Whether the account can participate in automatic routing. Disabled accounts stay stored but are ignored for winner selection. |
160
- | `priority` | Higher values win when multiple enabled accounts can serve the same raw model ID. |
161
- | `allowlist` | Exact raw model IDs this account is allowed to serve. If non-empty, the account can only serve models listed here. |
162
- | `blocklist` | Exact raw model IDs this account must never serve. If both `allowlist` and `blocklist` are non-empty, the plugin checks `allowlist` first and then applies `blocklist`. |
160
+ | `priority` | Lower values win when multiple enabled accounts can serve the same raw model ID. |
161
+ | `allowlist` | Raw model IDs or `*` wildcard patterns this account is allowed to serve. If non-empty, the account can only serve models that match one of these entries. |
162
+ | `blocklist` | Raw model IDs or `*` wildcard patterns this account must never serve. If both `allowlist` and `blocklist` are non-empty, the plugin checks `allowlist` first and then applies `blocklist`. |
163
163
 
164
- Automatic routing works on the raw Copilot model IDs that `opencode` already uses. The plugin filters eligible accounts by `enabled`, then checks `allowlist` first (when non-empty, the model must be listed there), then applies `blocklist`, and finally picks exactly one winning account by highest `priority` (with a stable key-based tie-breaker). The model ID itself is not rewritten, so account identity does not appear in model IDs.
164
+ Automatic routing works on the raw Copilot model IDs that `opencode` already uses. The plugin filters eligible accounts by `enabled`, then checks `allowlist` first (when non-empty, the model must match one of its exact entries or wildcard patterns), then applies `blocklist`, and finally picks exactly one winning account by lowest `priority` (with a stable key-based tie-breaker). The model ID itself is not rewritten, so account identity does not appear in model IDs.
165
+
166
+ Wildcard matching is case-sensitive and currently supports `*` for zero or more characters anywhere in the pattern. For example, `claude-*` matches `claude-sonnet-4.6` and `claude-opus-4.6`.
165
167
 
166
168
  Example pool file:
167
169
 
@@ -175,7 +177,7 @@ Example pool file:
175
177
  "name": "Work",
176
178
  "enabled": true,
177
179
  "priority": 100,
178
- "allowlist": ["claude-sonnet-4.6"],
180
+ "allowlist": ["claude-*"],
179
181
  "blocklist": [],
180
182
  "deployment": "github.com",
181
183
  "domain": "github.com",
package/dist/auth.js CHANGED
@@ -2,7 +2,7 @@ import { API_VERSION } from "./constants.js";
2
2
  import { getSelectedAccountExpiredError, getSelectedAccountMissingError } from "./errors.js";
3
3
  import { readPool, resolveWinnerAccount, writePool } from "./pool.js";
4
4
  import { stripRoutingHeaders } from "./routing.js";
5
- import { applyBaseURLToRequestInput, getConversationMetadata, isValidBaseURL, normalizeDomain, } from "./utils.js";
5
+ import { applyBaseURLToRequestInput, getConversationMetadata, isValidBaseURL, normalizeDomain, normalizeHeaderObject, } from "./utils.js";
6
6
  export function getUrls(domain) {
7
7
  const apiDomain = domain === "github.com" ? "api.github.com" : `api.${domain}`;
8
8
  return {
@@ -77,7 +77,7 @@ function getHeader(headers, name) {
77
77
  export function buildHeaders(init, info, isVision, isAgent) {
78
78
  const explicitInitiator = getHeader(init?.headers, "x-initiator");
79
79
  const headers = {
80
- ...(init?.headers ?? {}),
80
+ ...normalizeHeaderObject(init?.headers),
81
81
  Authorization: `Bearer ${info.refresh}`,
82
82
  "Copilot-Integration-Id": "copilot-developer-cli",
83
83
  "Openai-Intent": "conversation-agent",
package/dist/index.d.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  import type { Plugin } from "@opencode-ai/plugin";
2
2
  import { lookupGitHubIdentity } from "./auth.js";
3
3
  import { deriveAccountKey, getPoolPath, readPool, resolveWinnerAccount, upsertAccount, writePool } from "./pool.js";
4
+ import { injectRoutingHeaders, stripRoutingHeaders } from "./routing.js";
4
5
  export { getPoolPath, readPool, writePool, deriveAccountKey, lookupGitHubIdentity, upsertAccount, resolveWinnerAccount };
6
+ export { injectRoutingHeaders, stripRoutingHeaders };
5
7
  export declare const CopilotAuthPlugin: Plugin;
package/dist/index.js CHANGED
@@ -2,8 +2,10 @@ import { CLIENT_ID, OAUTH_POLLING_SAFETY_MARGIN_MS, OAUTH_SCOPES, ROUTING_ACCOUN
2
2
  import { buildHeaders, fetchEntitlement, fetchWithSelectedAccount, getBaseURL, getUrls, lookupGitHubIdentity, resolveSelectedPoolAccount, } from "./auth.js";
3
3
  import { buildPoolBackedModels, normalizeExistingModels, resolveProviderModels } from "./models.js";
4
4
  import { deriveAccountKey, getPoolPath, readPool, resolveWinnerAccount, upsertAccount, writePool, } from "./pool.js";
5
+ import { injectRoutingHeaders, stripRoutingHeaders } from "./routing.js";
5
6
  import { getHeader, getConversationMetadata, getRequestedRawModelId, normalizeDomain, resolveClaudeThinkingBudget, } from "./utils.js";
6
7
  export { getPoolPath, readPool, writePool, deriveAccountKey, lookupGitHubIdentity, upsertAccount, resolveWinnerAccount };
8
+ export { injectRoutingHeaders, stripRoutingHeaders };
7
9
  export const CopilotAuthPlugin = async (input) => {
8
10
  return {
9
11
  provider: {
package/dist/models.d.ts CHANGED
@@ -130,5 +130,64 @@ export declare function resolveProviderModels(existingModels: Record<string, SDK
130
130
  };
131
131
  }>;
132
132
  export declare function buildPoolBackedModels(existingModels: Record<string, SDKModel> | undefined, pool: AccountPool): Promise<{
133
- [k: string]: SDKModel;
133
+ [k: string]: {
134
+ cost: {
135
+ input: number;
136
+ output: number;
137
+ cache: {
138
+ read: number;
139
+ write: number;
140
+ };
141
+ };
142
+ api: {
143
+ url: string;
144
+ npm: string;
145
+ id: string;
146
+ };
147
+ id: string;
148
+ providerID: string;
149
+ name: string;
150
+ family?: string;
151
+ capabilities: {
152
+ temperature: boolean;
153
+ reasoning: boolean;
154
+ attachment: boolean;
155
+ toolcall: boolean;
156
+ input: {
157
+ text: boolean;
158
+ audio: boolean;
159
+ image: boolean;
160
+ video: boolean;
161
+ pdf: boolean;
162
+ };
163
+ output: {
164
+ text: boolean;
165
+ audio: boolean;
166
+ image: boolean;
167
+ video: boolean;
168
+ pdf: boolean;
169
+ };
170
+ interleaved: boolean | {
171
+ field: "reasoning_content" | "reasoning_details";
172
+ };
173
+ };
174
+ limit: {
175
+ context: number;
176
+ input?: number;
177
+ output: number;
178
+ };
179
+ status: "alpha" | "beta" | "deprecated" | "active";
180
+ options: {
181
+ [key: string]: unknown;
182
+ };
183
+ headers: {
184
+ [key: string]: string;
185
+ };
186
+ release_date: string;
187
+ variants?: {
188
+ [key: string]: {
189
+ [key: string]: unknown;
190
+ };
191
+ };
192
+ };
134
193
  }>;
package/dist/models.js CHANGED
@@ -115,7 +115,7 @@ export async function buildPoolBackedModels(existingModels, pool) {
115
115
  const enabledAccounts = (Array.isArray(pool?.accounts) ? pool.accounts : [])
116
116
  .filter((account) => account?.enabled !== false);
117
117
  if (enabledAccounts.length === 0) {
118
- return {};
118
+ return normalizeExistingModels(existingModels);
119
119
  }
120
120
  const candidatesByModel = new Map();
121
121
  for (const account of enabledAccounts) {
@@ -138,6 +138,9 @@ export async function buildPoolBackedModels(existingModels, pool) {
138
138
  }
139
139
  }
140
140
  const existingById = new Map(Object.values(existingModels ?? {}).map((model) => [model?.api?.id ?? model?.id, model]));
141
+ if (candidatesByModel.size === 0) {
142
+ return normalizeExistingModels(existingModels);
143
+ }
141
144
  return Object.fromEntries([...candidatesByModel.entries()]
142
145
  .sort(([left], [right]) => left.localeCompare(right))
143
146
  .map(([rawModelId, { liveModel, baseURL }]) => [
package/dist/pool.js CHANGED
@@ -2,7 +2,7 @@ import { chmodSync, existsSync, mkdirSync, readFileSync, renameSync, writeFileSy
2
2
  import { homedir } from "os";
3
3
  import { dirname } from "path";
4
4
  import { ACCOUNT_POOL_SCHEMA_VERSION } from "./constants.js";
5
- import { normalizeDomain, normalizeIdSource, normalizeList, normalizePriority, preserveStringOrDefault } from "./utils.js";
5
+ import { matchesAnyModelIdPattern, normalizeDomain, normalizeIdSource, normalizeList, normalizePriority, preserveStringOrDefault, } from "./utils.js";
6
6
  export function getPoolPath() {
7
7
  return `${homedir()}/.local/share/opencode/copilot-auth.json`;
8
8
  }
@@ -137,17 +137,17 @@ export function upsertAccount(pool, accountData) {
137
137
  export function resolveWinnerAccount(rawModelId, pool) {
138
138
  const canAccountServeModel = (account) => {
139
139
  const allowlist = normalizeList(account?.allowlist);
140
- if (allowlist.length > 0 && !allowlist.includes(rawModelId)) {
140
+ if (allowlist.length > 0 && !matchesAnyModelIdPattern(allowlist, rawModelId)) {
141
141
  return false;
142
142
  }
143
143
  const blocklist = normalizeList(account?.blocklist);
144
- return !blocklist.includes(rawModelId);
144
+ return !matchesAnyModelIdPattern(blocklist, rawModelId);
145
145
  };
146
146
  const candidates = (Array.isArray(pool?.accounts) ? pool.accounts : [])
147
147
  .filter((account) => account?.enabled !== false)
148
148
  .filter(canAccountServeModel)
149
149
  .sort((left, right) => {
150
- const priorityDelta = normalizePriority(right?.priority) - normalizePriority(left?.priority);
150
+ const priorityDelta = normalizePriority(left?.priority) - normalizePriority(right?.priority);
151
151
  if (priorityDelta !== 0) {
152
152
  return priorityDelta;
153
153
  }
package/dist/utils.d.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  import type { ConversationMetadata, HeaderObject } from "./types.js";
2
2
  export declare function normalizeHeaderObject(headers: HeaderObject): Record<string, string>;
3
3
  export declare function normalizeList(value: unknown): string[];
4
+ export declare function matchesModelIdPattern(pattern: unknown, rawModelId: string): boolean;
5
+ export declare function matchesAnyModelIdPattern(patterns: unknown, rawModelId: string): boolean;
4
6
  export declare function normalizePriority(value: unknown): number;
5
7
  export declare function normalizeDomain(urlOrDomain: unknown): string;
6
8
  export declare function normalizeIdSource(value: unknown): string;
package/dist/utils.js CHANGED
@@ -28,6 +28,19 @@ export function normalizeHeaderObject(headers) {
28
28
  export function normalizeList(value) {
29
29
  return Array.isArray(value) ? value : [];
30
30
  }
31
+ function escapeRegex(value) {
32
+ return value.replace(/[|\\{}()[\]^$+?.]/g, "\\$&");
33
+ }
34
+ export function matchesModelIdPattern(pattern, rawModelId) {
35
+ if (typeof pattern !== "string") {
36
+ return false;
37
+ }
38
+ const matcher = new RegExp(`^${escapeRegex(pattern).replaceAll("*", ".*")}$`);
39
+ return matcher.test(rawModelId);
40
+ }
41
+ export function matchesAnyModelIdPattern(patterns, rawModelId) {
42
+ return normalizeList(patterns).some((pattern) => matchesModelIdPattern(pattern, rawModelId));
43
+ }
31
44
  export function normalizePriority(value) {
32
45
  return Number.isInteger(value) ? value : 0;
33
46
  }
package/index.mjs CHANGED
@@ -2,9 +2,11 @@ export {
2
2
  CopilotAuthPlugin,
3
3
  deriveAccountKey,
4
4
  getPoolPath,
5
+ injectRoutingHeaders,
5
6
  lookupGitHubIdentity,
6
7
  readPool,
7
8
  resolveWinnerAccount,
9
+ stripRoutingHeaders,
8
10
  upsertAccount,
9
11
  writePool,
10
12
  } from "./dist/index.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gnahz77/opencode-copilot-multi-auth",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",