oc-blackbytes 0.3.0 → 0.4.1
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 +7 -6
- package/dist/index.js +204 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -62,7 +62,7 @@ opencode debug config
|
|
|
62
62
|
|
|
63
63
|
## Configuration
|
|
64
64
|
|
|
65
|
-
Create `oc-blackbytes.jsonc` in the OpenCode config directory.
|
|
65
|
+
Create `oc-blackbytes.jsonc` in the OpenCode config directory. For the full configuration guide with recommended models per agent and example setups, see [docs/configuration.md](docs/configuration.md).
|
|
66
66
|
|
|
67
67
|
```jsonc
|
|
68
68
|
{
|
|
@@ -97,11 +97,11 @@ Create `oc-blackbytes.jsonc` in the OpenCode config directory.
|
|
|
97
97
|
| `disabled_tools` | `string[]` | `[]` | Prevents bundled tools from being registered. |
|
|
98
98
|
| `mcp_env_alllowlist` | `string[]` | `[]` | Recognized by the schema for MCP environment filtering workflows. |
|
|
99
99
|
| `hashline_edit` | `boolean` | `true` | Enables the `hashline_edit` tool and `tool.execute.after` hashline post-processing for `read`/`write`. |
|
|
100
|
-
| `model_fallback` | `boolean` | `false` |
|
|
100
|
+
| `model_fallback` | `boolean` | `false` | Enables model fallback resolution: discovers connected providers at init and resolves fallback chains when a preferred model's provider is unavailable. |
|
|
101
101
|
| `auto_update` | `boolean` | `false` | Recognized by the schema for maintenance workflows. |
|
|
102
102
|
| `websearch.provider` | `"exa" \| "tavily"` | `"exa"` | Selects the built-in `websearch` MCP backend. |
|
|
103
103
|
| `agents` | `Record<string, AgentModelConfig>` | `{}` | Per-agent model configuration overrides. See [Per-agent model configuration](#per-agent-model-configuration). |
|
|
104
|
-
| `fallback_models` | `string \| (string \| FallbackModelObject)[]` | — | Global fallback model chain
|
|
104
|
+
| `fallback_models` | `string \| (string \| FallbackModelObject)[]` | — | Global fallback model chain. When `model_fallback: true` and an agent's primary model is unavailable, the plugin walks this chain and uses the first model whose provider is connected. |
|
|
105
105
|
| `_migrations` | `string[]` | `[]` | Internal migration bookkeeping. |
|
|
106
106
|
|
|
107
107
|
## Built-in agents
|
|
@@ -150,9 +150,9 @@ Each agent model config supports:
|
|
|
150
150
|
| `model` | `string` | Model identifier (e.g., `"openai/gpt-5.4"`). Drives prompt variant selection and, for subagents, sets the model hint. |
|
|
151
151
|
| `reasoningEffort` | `string` | Override reasoning effort level for OpenAI reasoning models (`"low"`, `"medium"`, `"high"`). |
|
|
152
152
|
| `temperature` | `number` | Override temperature for the agent. |
|
|
153
|
-
| `fallback_models` | `string \| (string \| object)[]` | Per-agent fallback chain
|
|
153
|
+
| `fallback_models` | `string \| (string \| object)[]` | Per-agent fallback chain — tried before the global `fallback_models` when the primary model's provider is unavailable. Requires `model_fallback: true`. |
|
|
154
154
|
|
|
155
|
-
When a `model` is specified for a subagent, the factory selects the appropriate prompt variant for that model family (Claude, GPT, or Gemini). The primary agent (`bytes`) uses the model hint for prompt selection only — the actual model is determined by the OpenCode UI selection.
|
|
155
|
+
When a `model` is specified for a subagent, the factory selects the appropriate prompt variant for that model family (Claude, GPT, or Gemini). The primary agent (`bytes`) uses the model hint for prompt selection only — the actual model is determined by the OpenCode UI selection. For recommended models per agent, see [docs/configuration.md](docs/configuration.md).
|
|
156
156
|
|
|
157
157
|
## Runtime model parameter adaptation
|
|
158
158
|
|
|
@@ -307,9 +307,10 @@ oc-blackbytes/
|
|
|
307
307
|
│ ├── shared/ # Logger, constants, config path resolution, JSONC utils
|
|
308
308
|
│ ├── compat/ # Compatibility integrations
|
|
309
309
|
│ ├── integrations/ # Additional runtime integrations
|
|
310
|
-
│ ├── services/ #
|
|
310
|
+
│ ├── services/ # Model fallback resolution (provider discovery, fallback chains)
|
|
311
311
|
│ └── stores/ # Reserved state stores
|
|
312
312
|
├── docs/
|
|
313
|
+
│ ├── configuration.md
|
|
313
314
|
│ └── debugging.md
|
|
314
315
|
├── test/
|
|
315
316
|
│ └── config.test.ts
|
package/dist/index.js
CHANGED
|
@@ -2287,6 +2287,201 @@ function createOracleAgent(model2) {
|
|
|
2287
2287
|
}
|
|
2288
2288
|
createOracleAgent.mode = MODE5;
|
|
2289
2289
|
|
|
2290
|
+
// src/services/model-requirements.ts
|
|
2291
|
+
var BUILTIN_FALLBACK_CHAINS = {
|
|
2292
|
+
oracle: [
|
|
2293
|
+
{ model: "gpt-5.4", providers: ["openai", "github-copilot"], reasoningEffort: "high" },
|
|
2294
|
+
{ model: "gemini-3.1-pro", providers: ["google"] },
|
|
2295
|
+
{ model: "claude-opus-4-6", providers: ["anthropic", "github-copilot"] }
|
|
2296
|
+
],
|
|
2297
|
+
explore: [
|
|
2298
|
+
{ model: "gemini-3-flash", providers: ["google"], temperature: 0.1 },
|
|
2299
|
+
{ model: "claude-haiku-4-5", providers: ["anthropic", "github-copilot"], temperature: 0.1 },
|
|
2300
|
+
{ model: "gpt-5-nano", providers: ["openai", "github-copilot"], temperature: 0.1 },
|
|
2301
|
+
{ model: "minimax-m2.7", providers: ["minimax"], temperature: 0.1 }
|
|
2302
|
+
],
|
|
2303
|
+
librarian: [
|
|
2304
|
+
{ model: "claude-haiku-4-5", providers: ["anthropic", "github-copilot"], temperature: 0.2 },
|
|
2305
|
+
{ model: "gemini-3-flash", providers: ["google"], temperature: 0.2 },
|
|
2306
|
+
{ model: "gpt-5-nano", providers: ["openai", "github-copilot"], temperature: 0.2 }
|
|
2307
|
+
],
|
|
2308
|
+
general: [
|
|
2309
|
+
{ model: "claude-sonnet-4-6", providers: ["anthropic", "github-copilot"] },
|
|
2310
|
+
{ model: "kimi-k2.5", providers: ["kimi"] },
|
|
2311
|
+
{ model: "gpt-5.4-mini", providers: ["openai", "github-copilot"] },
|
|
2312
|
+
{ model: "gemini-3.1-pro", providers: ["google"] }
|
|
2313
|
+
]
|
|
2314
|
+
};
|
|
2315
|
+
// src/services/model-resolver.ts
|
|
2316
|
+
async function discoverAvailableModels(client) {
|
|
2317
|
+
try {
|
|
2318
|
+
const result = await client.provider.list();
|
|
2319
|
+
const data = result.data;
|
|
2320
|
+
if (!data) {
|
|
2321
|
+
log("[model-resolver] Provider list returned no data, skipping fallback resolution");
|
|
2322
|
+
return new Map;
|
|
2323
|
+
}
|
|
2324
|
+
const connected = new Set(data.connected);
|
|
2325
|
+
const models = new Map;
|
|
2326
|
+
for (const provider of data.all ?? []) {
|
|
2327
|
+
if (!connected.has(provider.id))
|
|
2328
|
+
continue;
|
|
2329
|
+
const modelIds = new Set(Object.keys(provider.models ?? {}));
|
|
2330
|
+
if (modelIds.size > 0) {
|
|
2331
|
+
models.set(provider.id, modelIds);
|
|
2332
|
+
}
|
|
2333
|
+
}
|
|
2334
|
+
const providerSummary = [...models.entries()].map(([id, m]) => `${id}(${m.size})`).join(", ");
|
|
2335
|
+
log(`[model-resolver] Available: ${providerSummary || "(none)"}`);
|
|
2336
|
+
return models;
|
|
2337
|
+
} catch (e) {
|
|
2338
|
+
log(`[model-resolver] Failed to discover providers: ${e}`);
|
|
2339
|
+
return new Map;
|
|
2340
|
+
}
|
|
2341
|
+
}
|
|
2342
|
+
function resolveModelRef(modelRef, availableModels) {
|
|
2343
|
+
if (availableModels.size === 0)
|
|
2344
|
+
return modelRef;
|
|
2345
|
+
const slashIdx = modelRef.indexOf("/");
|
|
2346
|
+
if (slashIdx === -1)
|
|
2347
|
+
return modelRef;
|
|
2348
|
+
const providerId = modelRef.substring(0, slashIdx);
|
|
2349
|
+
const modelId = modelRef.substring(slashIdx + 1);
|
|
2350
|
+
const providerModels = availableModels.get(providerId);
|
|
2351
|
+
if (!providerModels)
|
|
2352
|
+
return;
|
|
2353
|
+
const matchedModel = prefixMatchModel(modelId, providerModels);
|
|
2354
|
+
return matchedModel ? `${providerId}/${matchedModel}` : undefined;
|
|
2355
|
+
}
|
|
2356
|
+
function prefixMatchModel(modelPrefix, providerModels) {
|
|
2357
|
+
if (providerModels.has(modelPrefix))
|
|
2358
|
+
return modelPrefix;
|
|
2359
|
+
let bestMatch;
|
|
2360
|
+
for (const available of providerModels) {
|
|
2361
|
+
if (available.startsWith(modelPrefix)) {
|
|
2362
|
+
const rest = available.substring(modelPrefix.length);
|
|
2363
|
+
if (rest === "" || /^-\d/.test(rest)) {
|
|
2364
|
+
if (!bestMatch || available.length < bestMatch.length) {
|
|
2365
|
+
bestMatch = available;
|
|
2366
|
+
}
|
|
2367
|
+
}
|
|
2368
|
+
}
|
|
2369
|
+
}
|
|
2370
|
+
return bestMatch;
|
|
2371
|
+
}
|
|
2372
|
+
function resolveChainEntry(entry, availableModels) {
|
|
2373
|
+
for (const provider of entry.providers) {
|
|
2374
|
+
const providerModels = availableModels.get(provider);
|
|
2375
|
+
if (!providerModels)
|
|
2376
|
+
continue;
|
|
2377
|
+
const matched = prefixMatchModel(entry.model, providerModels);
|
|
2378
|
+
if (matched) {
|
|
2379
|
+
return {
|
|
2380
|
+
model: `${provider}/${matched}`,
|
|
2381
|
+
fromFallback: true,
|
|
2382
|
+
reasoningEffort: entry.reasoningEffort,
|
|
2383
|
+
temperature: entry.temperature
|
|
2384
|
+
};
|
|
2385
|
+
}
|
|
2386
|
+
}
|
|
2387
|
+
return;
|
|
2388
|
+
}
|
|
2389
|
+
function walkFallbackChain(fallbacks, availableModels) {
|
|
2390
|
+
if (!fallbacks)
|
|
2391
|
+
return;
|
|
2392
|
+
const chain = typeof fallbacks === "string" ? [fallbacks] : fallbacks;
|
|
2393
|
+
for (const entry of chain) {
|
|
2394
|
+
const modelRef = typeof entry === "string" ? entry : entry.model;
|
|
2395
|
+
const resolvedModel = resolveModelRef(modelRef, availableModels);
|
|
2396
|
+
if (resolvedModel) {
|
|
2397
|
+
return typeof entry === "string" ? { model: resolvedModel, fromFallback: true } : {
|
|
2398
|
+
model: resolvedModel,
|
|
2399
|
+
fromFallback: true,
|
|
2400
|
+
reasoningEffort: entry.reasoningEffort,
|
|
2401
|
+
temperature: entry.temperature
|
|
2402
|
+
};
|
|
2403
|
+
}
|
|
2404
|
+
}
|
|
2405
|
+
return;
|
|
2406
|
+
}
|
|
2407
|
+
function walkBuiltinChain(agentName, availableModels) {
|
|
2408
|
+
const chain = BUILTIN_FALLBACK_CHAINS[agentName];
|
|
2409
|
+
if (!chain)
|
|
2410
|
+
return;
|
|
2411
|
+
for (const entry of chain) {
|
|
2412
|
+
const resolved = resolveChainEntry(entry, availableModels);
|
|
2413
|
+
if (resolved)
|
|
2414
|
+
return resolved;
|
|
2415
|
+
}
|
|
2416
|
+
return;
|
|
2417
|
+
}
|
|
2418
|
+
function resolveAgentModel(agentName, agentConfig, globalFallbacks, availableModels) {
|
|
2419
|
+
const primaryModel = agentConfig?.model;
|
|
2420
|
+
if (!primaryModel) {
|
|
2421
|
+
const perAgentResolved2 = walkFallbackChain(agentConfig?.fallback_models, availableModels);
|
|
2422
|
+
if (perAgentResolved2) {
|
|
2423
|
+
log(` [model-resolver] ${agentName}: resolved \u2192 ${perAgentResolved2.model} (agent fallback)`);
|
|
2424
|
+
return perAgentResolved2;
|
|
2425
|
+
}
|
|
2426
|
+
const builtinResolved2 = walkBuiltinChain(agentName, availableModels);
|
|
2427
|
+
if (builtinResolved2) {
|
|
2428
|
+
log(` [model-resolver] ${agentName}: resolved \u2192 ${builtinResolved2.model} (builtin chain)`);
|
|
2429
|
+
return builtinResolved2;
|
|
2430
|
+
}
|
|
2431
|
+
const globalResolved2 = walkFallbackChain(globalFallbacks, availableModels);
|
|
2432
|
+
if (globalResolved2) {
|
|
2433
|
+
log(` [model-resolver] ${agentName}: resolved \u2192 ${globalResolved2.model} (global fallback)`);
|
|
2434
|
+
return globalResolved2;
|
|
2435
|
+
}
|
|
2436
|
+
return { model: "", fromFallback: false };
|
|
2437
|
+
}
|
|
2438
|
+
const resolvedPrimaryModel = resolveModelRef(primaryModel, availableModels);
|
|
2439
|
+
if (resolvedPrimaryModel) {
|
|
2440
|
+
log(` [model-resolver] ${agentName}: using primary model ${resolvedPrimaryModel}`);
|
|
2441
|
+
return { model: resolvedPrimaryModel, fromFallback: false };
|
|
2442
|
+
}
|
|
2443
|
+
log(` [model-resolver] ${agentName}: primary model ${primaryModel} not available, trying fallbacks...`);
|
|
2444
|
+
const perAgentResolved = walkFallbackChain(agentConfig?.fallback_models, availableModels);
|
|
2445
|
+
if (perAgentResolved) {
|
|
2446
|
+
log(` [model-resolver] ${agentName}: resolved \u2192 ${perAgentResolved.model} (agent fallback)`);
|
|
2447
|
+
return perAgentResolved;
|
|
2448
|
+
}
|
|
2449
|
+
const builtinResolved = walkBuiltinChain(agentName, availableModels);
|
|
2450
|
+
if (builtinResolved) {
|
|
2451
|
+
log(` [model-resolver] ${agentName}: resolved \u2192 ${builtinResolved.model} (builtin chain)`);
|
|
2452
|
+
return builtinResolved;
|
|
2453
|
+
}
|
|
2454
|
+
const globalResolved = walkFallbackChain(globalFallbacks, availableModels);
|
|
2455
|
+
if (globalResolved) {
|
|
2456
|
+
log(` [model-resolver] ${agentName}: resolved \u2192 ${globalResolved.model} (global fallback)`);
|
|
2457
|
+
return globalResolved;
|
|
2458
|
+
}
|
|
2459
|
+
log(` [model-resolver] ${agentName}: no available fallback, using OpenCode default`);
|
|
2460
|
+
return { model: "", fromFallback: false };
|
|
2461
|
+
}
|
|
2462
|
+
function resolveAllAgentModels(pluginConfig, availableModels) {
|
|
2463
|
+
const agentOverrides = pluginConfig.agents;
|
|
2464
|
+
const globalFallbacks = pluginConfig.fallback_models;
|
|
2465
|
+
const agentNames = new Set([
|
|
2466
|
+
...Object.keys(BUILTIN_FALLBACK_CHAINS),
|
|
2467
|
+
...agentOverrides ? Object.keys(agentOverrides) : []
|
|
2468
|
+
]);
|
|
2469
|
+
if (agentNames.size === 0)
|
|
2470
|
+
return;
|
|
2471
|
+
const resolved = {};
|
|
2472
|
+
log("[model-resolver] Resolving agent models with fallback chains...");
|
|
2473
|
+
for (const name of agentNames) {
|
|
2474
|
+
const config = agentOverrides?.[name];
|
|
2475
|
+
const resolution = resolveAgentModel(name, config, globalFallbacks, availableModels);
|
|
2476
|
+
resolved[name] = {
|
|
2477
|
+
...config,
|
|
2478
|
+
model: resolution.model || undefined,
|
|
2479
|
+
...resolution.fromFallback && resolution.reasoningEffort !== undefined && config?.reasoningEffort === undefined ? { reasoningEffort: resolution.reasoningEffort } : {},
|
|
2480
|
+
...resolution.fromFallback && resolution.temperature !== undefined && config?.temperature === undefined ? { temperature: resolution.temperature } : {}
|
|
2481
|
+
};
|
|
2482
|
+
}
|
|
2483
|
+
return resolved;
|
|
2484
|
+
}
|
|
2290
2485
|
// src/handlers/config-handler/agent-config-handler.ts
|
|
2291
2486
|
var DEFAULT_AGENT_NAME = "bytes";
|
|
2292
2487
|
var SUPERSEDED_AGENTS = ["build", "plan"];
|
|
@@ -2363,7 +2558,8 @@ function handleAgentConfig(ctx) {
|
|
|
2363
2558
|
const { disabled_agents: pluginDisabledAgents = [] } = ctx.pluginConfig;
|
|
2364
2559
|
const userAgents = ctx.config.agent;
|
|
2365
2560
|
const userDisabledAgentNames = captureUserDisabledAgents(userAgents);
|
|
2366
|
-
const
|
|
2561
|
+
const effectiveOverrides = ctx.availableModels.size > 0 ? resolveAllAgentModels(ctx.pluginConfig, ctx.availableModels) ?? ctx.pluginConfig.agents : ctx.pluginConfig.agents;
|
|
2562
|
+
const builtinAgents = createBuiltinAgents(effectiveOverrides);
|
|
2367
2563
|
const merged = {
|
|
2368
2564
|
...builtinAgents
|
|
2369
2565
|
};
|
|
@@ -2511,10 +2707,10 @@ function handleMcpConfig(ctx) {
|
|
|
2511
2707
|
}
|
|
2512
2708
|
|
|
2513
2709
|
// src/handlers/config-handler/index.ts
|
|
2514
|
-
function handleConfig(pluginConfig) {
|
|
2710
|
+
function handleConfig(pluginConfig, availableModels) {
|
|
2515
2711
|
return {
|
|
2516
2712
|
config: async (config) => {
|
|
2517
|
-
const configCtx = { config, pluginConfig };
|
|
2713
|
+
const configCtx = { config, pluginConfig, availableModels };
|
|
2518
2714
|
handleMcpConfig(configCtx);
|
|
2519
2715
|
handleAgentConfig(configCtx);
|
|
2520
2716
|
}
|
|
@@ -17634,10 +17830,11 @@ function handleTools(pluginConfig, ctx) {
|
|
|
17634
17830
|
// src/bootstrap.ts
|
|
17635
17831
|
async function createOpenCodePlugin({
|
|
17636
17832
|
input,
|
|
17637
|
-
pluginConfig
|
|
17833
|
+
pluginConfig,
|
|
17834
|
+
availableModels
|
|
17638
17835
|
}) {
|
|
17639
17836
|
return {
|
|
17640
|
-
...handleConfig(pluginConfig),
|
|
17837
|
+
...handleConfig(pluginConfig, availableModels),
|
|
17641
17838
|
...handleChatHeaders(pluginConfig),
|
|
17642
17839
|
...handleChatParams(pluginConfig),
|
|
17643
17840
|
tool: handleTools(pluginConfig, input),
|
|
@@ -31250,7 +31447,8 @@ function loadPluginConfig(_input) {
|
|
|
31250
31447
|
// src/index.ts
|
|
31251
31448
|
var BlackbytesPlugin = async (ctx) => {
|
|
31252
31449
|
const pluginConfig = loadPluginConfig(ctx);
|
|
31253
|
-
|
|
31450
|
+
const availableModels = pluginConfig.model_fallback ? await discoverAvailableModels(ctx.client) : new Map;
|
|
31451
|
+
return createOpenCodePlugin({ input: ctx, pluginConfig, availableModels });
|
|
31254
31452
|
};
|
|
31255
31453
|
var src_default = BlackbytesPlugin;
|
|
31256
31454
|
export {
|