oc-blackbytes 0.2.0 → 0.4.0

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.
Files changed (3) hide show
  1. package/README.md +68 -7
  2. package/dist/index.js +338 -15
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -4,17 +4,20 @@ An OpenCode plugin for workflow automation. It provisions built-in MCP servers,
4
4
 
5
5
  ## What the plugin provides
6
6
 
7
- The plugin wires four OpenCode hook surfaces:
7
+ The plugin wires five OpenCode hook surfaces:
8
8
 
9
9
  - `config` — merges built-in MCP servers and agents into the active OpenCode config
10
10
  - `chat.headers` — injects `x-initiator: agent` for supported GitHub Copilot providers
11
11
  - `tool` — registers bundled local tools for structured editing and codebase search
12
12
  - `tool.execute.after` — post-processes `read`/`write` output when hashline editing is enabled
13
+ - `chat.params` — adapts model parameters at runtime based on actual model family and agent role
13
14
 
14
15
  ## Features
15
16
 
16
17
  - **Built-in MCP provisioning** — configures `websearch`, `context7`, and `grep_app`
17
18
  - **Agent installation** — provides `bytes` as the default primary agent plus `explore`, `oracle`, `librarian`, and `general`
19
+ - **Per-agent model configuration** — each agent can target a specific model with tailored reasoning effort, temperature, and fallback chains via the `agents` config field
20
+ - **Runtime model parameter adaptation** — the `chat.params` hook detects the actual model family at inference time and applies provider-correct parameters (Claude thinking, OpenAI reasoning effort) while stripping incompatible options
18
21
  - **Local tool registration** — exposes `hashline_edit`, `ast_grep_search`, `ast_grep_replace`, `grep`, and `glob`
19
22
  - **Hashline editing workflow** — transforms `read` output into `LINE#ID` anchors and turns successful `write` output into concise line-count summaries
20
23
  - **Config merging pipeline** — merges built-in MCPs and agents with user-defined config while preserving explicit user disables
@@ -59,7 +62,7 @@ opencode debug config
59
62
 
60
63
  ## Configuration
61
64
 
62
- 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).
63
66
 
64
67
  ```jsonc
65
68
  {
@@ -73,6 +76,13 @@ Create `oc-blackbytes.jsonc` in the OpenCode config directory.
73
76
 
74
77
  "websearch": {
75
78
  "provider": "exa"
79
+ },
80
+
81
+ "agents": {
82
+ "oracle": { "model": "openai/gpt-5.4", "reasoningEffort": "high" },
83
+ "explore": { "model": "google/gemini-3-flash", "temperature": 0.1 },
84
+ "librarian": { "model": "minimax/minimax-m2.7" },
85
+ "general": { "model": "anthropic/claude-sonnet-4-6" }
76
86
  }
77
87
  }
78
88
  ```
@@ -87,9 +97,11 @@ Create `oc-blackbytes.jsonc` in the OpenCode config directory.
87
97
  | `disabled_tools` | `string[]` | `[]` | Prevents bundled tools from being registered. |
88
98
  | `mcp_env_alllowlist` | `string[]` | `[]` | Recognized by the schema for MCP environment filtering workflows. |
89
99
  | `hashline_edit` | `boolean` | `true` | Enables the `hashline_edit` tool and `tool.execute.after` hashline post-processing for `read`/`write`. |
90
- | `model_fallback` | `boolean` | `false` | Recognized by the schema for model compatibility workflows. |
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. |
91
101
  | `auto_update` | `boolean` | `false` | Recognized by the schema for maintenance workflows. |
92
102
  | `websearch.provider` | `"exa" \| "tavily"` | `"exa"` | Selects the built-in `websearch` MCP backend. |
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. 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. |
93
105
  | `_migrations` | `string[]` | `[]` | Internal migration bookkeeping. |
94
106
 
95
107
  ## Built-in agents
@@ -106,6 +118,54 @@ The plugin merges these agents into the OpenCode config and sets `default_agent`
106
118
 
107
119
  The merge behavior also preserves explicit user disables (`disable: true`), removes entries listed in `disabled_agents`, uses the OpenCode `permission` map format, and marks OpenCode's default `build` and `plan` agents as disabled unless the user configures them directly.
108
120
 
121
+ ### Per-agent model configuration
122
+
123
+ The `agents` field accepts a record of agent names to model configuration objects:
124
+
125
+ ```jsonc
126
+ {
127
+ "agents": {
128
+ "oracle": {
129
+ "model": "openai/gpt-5.4",
130
+ "reasoningEffort": "high"
131
+ },
132
+ "explore": {
133
+ "model": "google/gemini-3-flash",
134
+ "temperature": 0.1
135
+ },
136
+ "librarian": {
137
+ "model": "minimax/minimax-m2.7"
138
+ },
139
+ "general": {
140
+ "model": "anthropic/claude-sonnet-4-6"
141
+ }
142
+ }
143
+ }
144
+ ```
145
+
146
+ Each agent model config supports:
147
+
148
+ | Field | Type | Description |
149
+ |---|---|---|
150
+ | `model` | `string` | Model identifier (e.g., `"openai/gpt-5.4"`). Drives prompt variant selection and, for subagents, sets the model hint. |
151
+ | `reasoningEffort` | `string` | Override reasoning effort level for OpenAI reasoning models (`"low"`, `"medium"`, `"high"`). |
152
+ | `temperature` | `number` | Override temperature for the agent. |
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
+
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
+
157
+ ## Runtime model parameter adaptation
158
+
159
+ The `chat.params` hook fires on every LLM call and applies provider-correct parameters based on the actual runtime model:
160
+
161
+ | Model Family | Behavior |
162
+ |---|---|
163
+ | **Claude** (Anthropic) | Applies extended thinking with per-agent budget tokens (bytes: 32K, oracle: 32K, general: 16K) when the model supports reasoning. Strips `reasoningEffort` and `textVerbosity`. |
164
+ | **GPT** (OpenAI) | Applies `reasoningEffort` per agent (oracle: `"high"`, bytes/general: `"medium"`) when the model supports reasoning. Strips `thinking`. |
165
+ | **Gemini / Other** | Strips all provider-specific options (`thinking`, `reasoningEffort`, `textVerbosity`). |
166
+
167
+ User overrides from the `agents` config take priority over these defaults. Agents without thinking defaults (`explore`, `librarian`) skip reasoning configuration for speed and cost efficiency.
168
+
109
169
  ## Built-in MCP servers
110
170
 
111
171
  | Server | Auth | Behavior |
@@ -235,9 +295,9 @@ For CLI usage, `OPENCODE_CONFIG_DIR` overrides the default OpenCode config direc
235
295
  oc-blackbytes/
236
296
  ├── src/
237
297
  │ ├── index.ts # Plugin entry
238
- │ ├── bootstrap.ts # Hook assembly
239
- │ ├── config/ # JSONC config loading + Zod schemas
240
- │ ├── handlers/ # Hook handlers for config, tools, chat headers, and post-processing
298
+ │ ├── bootstrap.ts # Hook assembly (config, chat.headers, chat.params, tool, tool.execute.after)
299
+ │ ├── config/ # JSONC config loading + Zod schemas (including per-agent model config)
300
+ │ ├── handlers/ # Hook handlers for config, tools, chat headers, chat params, and post-processing
241
301
  │ ├── extensions/
242
302
  │ │ ├── agents/ # bytes/explore/oracle/librarian/general agent definitions
243
303
  │ │ ├── hooks/ # Hook-related extension helpers
@@ -247,9 +307,10 @@ oc-blackbytes/
247
307
  │ ├── shared/ # Logger, constants, config path resolution, JSONC utils
248
308
  │ ├── compat/ # Compatibility integrations
249
309
  │ ├── integrations/ # Additional runtime integrations
250
- │ ├── services/ # Reserved service modules
310
+ │ ├── services/ # Model fallback resolution (provider discovery, fallback chains)
251
311
  │ └── stores/ # Reserved state stores
252
312
  ├── docs/
313
+ │ ├── configuration.md
253
314
  │ └── debugging.md
254
315
  ├── test/
255
316
  │ └── config.test.ts
package/dist/index.js CHANGED
@@ -1028,6 +1028,15 @@ function isGptModel(model) {
1028
1028
  return modelName.includes("gpt");
1029
1029
  }
1030
1030
  var GEMINI_PROVIDERS = ["google/", "google-vertex/"];
1031
+ var CLAUDE_PROVIDERS = ["anthropic/"];
1032
+ function isClaudeModel(model) {
1033
+ if (CLAUDE_PROVIDERS.some((prefix) => model.startsWith(prefix)))
1034
+ return true;
1035
+ if (model.startsWith("github-copilot/") && extractModelName(model).toLowerCase().startsWith("claude"))
1036
+ return true;
1037
+ const modelName = extractModelName(model).toLowerCase();
1038
+ return modelName.startsWith("claude-");
1039
+ }
1031
1040
  function isGeminiModel(model) {
1032
1041
  if (GEMINI_PROVIDERS.some((prefix) => model.startsWith(prefix)))
1033
1042
  return true;
@@ -1042,6 +1051,88 @@ function createAgentToolRestrictions(denyTools) {
1042
1051
  permission: Object.fromEntries(denyTools.map((tool) => [tool, "deny"]))
1043
1052
  };
1044
1053
  }
1054
+ // src/handlers/chat-params-handler.ts
1055
+ var CLAUDE_THINKING_DEFAULTS = {
1056
+ bytes: { type: "enabled", budgetTokens: 32000 },
1057
+ oracle: { type: "enabled", budgetTokens: 32000 },
1058
+ general: { type: "enabled", budgetTokens: 16000 }
1059
+ };
1060
+ var OPENAI_REASONING_DEFAULTS = {
1061
+ bytes: "medium",
1062
+ oracle: "high",
1063
+ general: "medium"
1064
+ };
1065
+ function detectModelFamily(providerID, modelRef) {
1066
+ if (providerID === "anthropic")
1067
+ return "claude";
1068
+ if (providerID === "openai")
1069
+ return "openai";
1070
+ if (providerID === "google" || providerID === "google-vertex")
1071
+ return "gemini";
1072
+ if (isClaudeModel(modelRef))
1073
+ return "claude";
1074
+ if (isGptModel(modelRef))
1075
+ return "openai";
1076
+ if (isGeminiModel(modelRef))
1077
+ return "gemini";
1078
+ return "other";
1079
+ }
1080
+ function applyClaude(agentName, supportsReasoning, options) {
1081
+ if (supportsReasoning) {
1082
+ const defaults = CLAUDE_THINKING_DEFAULTS[agentName];
1083
+ if (defaults) {
1084
+ options.thinking = { ...defaults };
1085
+ }
1086
+ }
1087
+ delete options.reasoningEffort;
1088
+ delete options.textVerbosity;
1089
+ }
1090
+ function applyOpenAI(agentName, supportsReasoning, userReasoningEffort, options) {
1091
+ if (supportsReasoning) {
1092
+ const effort = userReasoningEffort ?? OPENAI_REASONING_DEFAULTS[agentName];
1093
+ if (effort) {
1094
+ options.reasoningEffort = effort;
1095
+ }
1096
+ }
1097
+ delete options.thinking;
1098
+ }
1099
+ function stripProviderOptions(options) {
1100
+ delete options.thinking;
1101
+ delete options.reasoningEffort;
1102
+ delete options.textVerbosity;
1103
+ }
1104
+ function handleChatParams(pluginConfig) {
1105
+ return {
1106
+ "chat.params": async (input, output) => {
1107
+ const agentName = input.agent;
1108
+ const model2 = input.model;
1109
+ const providerID = model2.providerID;
1110
+ const modelID = model2.id;
1111
+ const modelRef = providerID ? `${providerID}/${modelID}` : modelID;
1112
+ const family = detectModelFamily(providerID, modelRef);
1113
+ const agentOverride = pluginConfig.agents?.[agentName];
1114
+ const supportsReasoning = model2.capabilities?.reasoning ?? false;
1115
+ log(`[chat.params] agent=${agentName} model=${modelRef} family=${family} reasoning=${supportsReasoning}`);
1116
+ switch (family) {
1117
+ case "claude": {
1118
+ applyClaude(agentName, supportsReasoning, output.options);
1119
+ break;
1120
+ }
1121
+ case "openai": {
1122
+ applyOpenAI(agentName, supportsReasoning, agentOverride?.reasoningEffort, output.options);
1123
+ break;
1124
+ }
1125
+ default: {
1126
+ stripProviderOptions(output.options);
1127
+ break;
1128
+ }
1129
+ }
1130
+ if (agentOverride?.temperature !== undefined) {
1131
+ output.temperature = agentOverride.temperature;
1132
+ }
1133
+ }
1134
+ };
1135
+ }
1045
1136
  // src/extensions/agents/bytes/agent.ts
1046
1137
  var BYTES_DESCRIPTION = "Primary coding agent. Handles end-to-end software engineering: planning, implementation, debugging, refactoring, and code review. Delegates to specialized subagents (Oracle, Explore, Librarian, General) when elevated reasoning, broad search, multi-repo context, or heavy implementation is needed.";
1047
1138
  var SHARED_SECTIONS = {
@@ -2196,18 +2287,229 @@ function createOracleAgent(model2) {
2196
2287
  }
2197
2288
  createOracleAgent.mode = MODE5;
2198
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 isModelAvailable(modelRef, availableModels) {
2343
+ if (availableModels.size === 0)
2344
+ return true;
2345
+ const slashIdx = modelRef.indexOf("/");
2346
+ if (slashIdx === -1)
2347
+ return true;
2348
+ const providerId = modelRef.substring(0, slashIdx);
2349
+ return availableModels.has(providerId);
2350
+ }
2351
+ function prefixMatchModel(modelPrefix, providerModels) {
2352
+ if (providerModels.has(modelPrefix))
2353
+ return modelPrefix;
2354
+ let bestMatch;
2355
+ for (const available of providerModels) {
2356
+ if (available.startsWith(modelPrefix)) {
2357
+ const rest = available.substring(modelPrefix.length);
2358
+ if (rest === "" || /^-\d/.test(rest)) {
2359
+ if (!bestMatch || available.length < bestMatch.length) {
2360
+ bestMatch = available;
2361
+ }
2362
+ }
2363
+ }
2364
+ }
2365
+ return bestMatch;
2366
+ }
2367
+ function resolveChainEntry(entry, availableModels) {
2368
+ for (const provider of entry.providers) {
2369
+ const providerModels = availableModels.get(provider);
2370
+ if (!providerModels)
2371
+ continue;
2372
+ const matched = prefixMatchModel(entry.model, providerModels);
2373
+ if (matched) {
2374
+ return {
2375
+ model: `${provider}/${matched}`,
2376
+ fromFallback: true,
2377
+ reasoningEffort: entry.reasoningEffort,
2378
+ temperature: entry.temperature
2379
+ };
2380
+ }
2381
+ }
2382
+ return;
2383
+ }
2384
+ function walkFallbackChain(fallbacks, availableModels) {
2385
+ if (!fallbacks)
2386
+ return;
2387
+ const chain = typeof fallbacks === "string" ? [fallbacks] : fallbacks;
2388
+ for (const entry of chain) {
2389
+ const modelRef = typeof entry === "string" ? entry : entry.model;
2390
+ if (isModelAvailable(modelRef, availableModels)) {
2391
+ return typeof entry === "string" ? { model: entry, fromFallback: true } : {
2392
+ model: entry.model,
2393
+ fromFallback: true,
2394
+ reasoningEffort: entry.reasoningEffort,
2395
+ temperature: entry.temperature
2396
+ };
2397
+ }
2398
+ }
2399
+ return;
2400
+ }
2401
+ function walkBuiltinChain(agentName, availableModels) {
2402
+ const chain = BUILTIN_FALLBACK_CHAINS[agentName];
2403
+ if (!chain)
2404
+ return;
2405
+ for (const entry of chain) {
2406
+ const resolved = resolveChainEntry(entry, availableModels);
2407
+ if (resolved)
2408
+ return resolved;
2409
+ }
2410
+ return;
2411
+ }
2412
+ function resolveAgentModel(agentName, agentConfig, globalFallbacks, availableModels) {
2413
+ const primaryModel = agentConfig?.model;
2414
+ if (!primaryModel) {
2415
+ const perAgentResolved2 = walkFallbackChain(agentConfig?.fallback_models, availableModels);
2416
+ if (perAgentResolved2) {
2417
+ log(` [model-resolver] ${agentName}: resolved \u2192 ${perAgentResolved2.model} (agent fallback)`);
2418
+ return perAgentResolved2;
2419
+ }
2420
+ const builtinResolved2 = walkBuiltinChain(agentName, availableModels);
2421
+ if (builtinResolved2) {
2422
+ log(` [model-resolver] ${agentName}: resolved \u2192 ${builtinResolved2.model} (builtin chain)`);
2423
+ return builtinResolved2;
2424
+ }
2425
+ const globalResolved2 = walkFallbackChain(globalFallbacks, availableModels);
2426
+ if (globalResolved2) {
2427
+ log(` [model-resolver] ${agentName}: resolved \u2192 ${globalResolved2.model} (global fallback)`);
2428
+ return globalResolved2;
2429
+ }
2430
+ return { model: "", fromFallback: false };
2431
+ }
2432
+ if (isModelAvailable(primaryModel, availableModels)) {
2433
+ log(` [model-resolver] ${agentName}: using primary model ${primaryModel}`);
2434
+ return { model: primaryModel, fromFallback: false };
2435
+ }
2436
+ log(` [model-resolver] ${agentName}: primary model ${primaryModel} not available, trying fallbacks...`);
2437
+ const perAgentResolved = walkFallbackChain(agentConfig?.fallback_models, availableModels);
2438
+ if (perAgentResolved) {
2439
+ log(` [model-resolver] ${agentName}: resolved \u2192 ${perAgentResolved.model} (agent fallback)`);
2440
+ return perAgentResolved;
2441
+ }
2442
+ const builtinResolved = walkBuiltinChain(agentName, availableModels);
2443
+ if (builtinResolved) {
2444
+ log(` [model-resolver] ${agentName}: resolved \u2192 ${builtinResolved.model} (builtin chain)`);
2445
+ return builtinResolved;
2446
+ }
2447
+ const globalResolved = walkFallbackChain(globalFallbacks, availableModels);
2448
+ if (globalResolved) {
2449
+ log(` [model-resolver] ${agentName}: resolved \u2192 ${globalResolved.model} (global fallback)`);
2450
+ return globalResolved;
2451
+ }
2452
+ log(` [model-resolver] ${agentName}: no available fallback, using OpenCode default`);
2453
+ return { model: "", fromFallback: false };
2454
+ }
2455
+ function resolveAllAgentModels(pluginConfig, availableModels) {
2456
+ const agentOverrides = pluginConfig.agents;
2457
+ const globalFallbacks = pluginConfig.fallback_models;
2458
+ const agentNames = new Set([
2459
+ ...Object.keys(BUILTIN_FALLBACK_CHAINS),
2460
+ ...agentOverrides ? Object.keys(agentOverrides) : []
2461
+ ]);
2462
+ if (agentNames.size === 0)
2463
+ return;
2464
+ const resolved = {};
2465
+ log("[model-resolver] Resolving agent models with fallback chains...");
2466
+ for (const name of agentNames) {
2467
+ const config = agentOverrides?.[name];
2468
+ const resolution = resolveAgentModel(name, config, globalFallbacks, availableModels);
2469
+ resolved[name] = {
2470
+ ...config,
2471
+ model: resolution.model || undefined,
2472
+ ...resolution.fromFallback && resolution.reasoningEffort !== undefined && config?.reasoningEffort === undefined ? { reasoningEffort: resolution.reasoningEffort } : {},
2473
+ ...resolution.fromFallback && resolution.temperature !== undefined && config?.temperature === undefined ? { temperature: resolution.temperature } : {}
2474
+ };
2475
+ }
2476
+ return resolved;
2477
+ }
2199
2478
  // src/handlers/config-handler/agent-config-handler.ts
2200
2479
  var DEFAULT_AGENT_NAME = "bytes";
2201
2480
  var SUPERSEDED_AGENTS = ["build", "plan"];
2202
- function createBuiltinAgents() {
2203
- const defaultModelHint = "";
2204
- return {
2205
- bytes: createBytesAgent(defaultModelHint),
2206
- explore: createExploreAgent(defaultModelHint),
2207
- oracle: createOracleAgent(defaultModelHint),
2208
- librarian: createLibrarianAgent(defaultModelHint),
2209
- general: createGeneralAgent(defaultModelHint)
2210
- };
2481
+ var BUILTIN_AGENT_FACTORIES = {
2482
+ bytes: createBytesAgent,
2483
+ explore: createExploreAgent,
2484
+ oracle: createOracleAgent,
2485
+ librarian: createLibrarianAgent,
2486
+ general: createGeneralAgent
2487
+ };
2488
+ function createBuiltinAgents(agentOverrides) {
2489
+ const agents = {};
2490
+ for (const [name, factory] of Object.entries(BUILTIN_AGENT_FACTORIES)) {
2491
+ const modelHint = agentOverrides?.[name]?.model ?? "";
2492
+ agents[name] = factory(modelHint);
2493
+ }
2494
+ if (agentOverrides) {
2495
+ applyAgentModelOverrides(agents, agentOverrides);
2496
+ }
2497
+ return agents;
2498
+ }
2499
+ function applyAgentModelOverrides(agents, overrides) {
2500
+ for (const [name, override] of Object.entries(overrides)) {
2501
+ if (!(name in agents))
2502
+ continue;
2503
+ const agent = agents[name];
2504
+ if (override.reasoningEffort !== undefined) {
2505
+ agent.reasoningEffort = override.reasoningEffort;
2506
+ log(` Agent '${name}': reasoningEffort \u2192 ${override.reasoningEffort}`);
2507
+ }
2508
+ if (override.temperature !== undefined) {
2509
+ agent.temperature = override.temperature;
2510
+ log(` Agent '${name}': temperature \u2192 ${override.temperature}`);
2511
+ }
2512
+ }
2211
2513
  }
2212
2514
  function isDisabledAgentEntry(value) {
2213
2515
  return typeof value === "object" && value !== null && "disable" in value && value.disable === true;
@@ -2249,7 +2551,8 @@ function handleAgentConfig(ctx) {
2249
2551
  const { disabled_agents: pluginDisabledAgents = [] } = ctx.pluginConfig;
2250
2552
  const userAgents = ctx.config.agent;
2251
2553
  const userDisabledAgentNames = captureUserDisabledAgents(userAgents);
2252
- const builtinAgents = createBuiltinAgents();
2554
+ const effectiveOverrides = ctx.availableModels.size > 0 ? resolveAllAgentModels(ctx.pluginConfig, ctx.availableModels) ?? ctx.pluginConfig.agents : ctx.pluginConfig.agents;
2555
+ const builtinAgents = createBuiltinAgents(effectiveOverrides);
2253
2556
  const merged = {
2254
2557
  ...builtinAgents
2255
2558
  };
@@ -2397,10 +2700,10 @@ function handleMcpConfig(ctx) {
2397
2700
  }
2398
2701
 
2399
2702
  // src/handlers/config-handler/index.ts
2400
- function handleConfig(pluginConfig) {
2703
+ function handleConfig(pluginConfig, availableModels) {
2401
2704
  return {
2402
2705
  config: async (config) => {
2403
- const configCtx = { config, pluginConfig };
2706
+ const configCtx = { config, pluginConfig, availableModels };
2404
2707
  handleMcpConfig(configCtx);
2405
2708
  handleAgentConfig(configCtx);
2406
2709
  }
@@ -17520,11 +17823,13 @@ function handleTools(pluginConfig, ctx) {
17520
17823
  // src/bootstrap.ts
17521
17824
  async function createOpenCodePlugin({
17522
17825
  input,
17523
- pluginConfig
17826
+ pluginConfig,
17827
+ availableModels
17524
17828
  }) {
17525
17829
  return {
17526
- ...handleConfig(pluginConfig),
17830
+ ...handleConfig(pluginConfig, availableModels),
17527
17831
  ...handleChatHeaders(pluginConfig),
17832
+ ...handleChatParams(pluginConfig),
17528
17833
  tool: handleTools(pluginConfig, input),
17529
17834
  "tool.execute.after": handleToolExecuteAfter(pluginConfig)
17530
17835
  };
@@ -31078,6 +31383,21 @@ var WebsearchConfigSchema = zod_default.object({
31078
31383
  });
31079
31384
 
31080
31385
  // src/config/schema/oc-blackbytes-config.ts
31386
+ var FallbackModelObjectSchema = zod_default.object({
31387
+ model: zod_default.string(),
31388
+ reasoningEffort: zod_default.string().optional(),
31389
+ temperature: zod_default.number().optional()
31390
+ });
31391
+ var FallbackModelsSchema = zod_default.union([
31392
+ zod_default.string(),
31393
+ zod_default.array(zod_default.union([zod_default.string(), FallbackModelObjectSchema]))
31394
+ ]);
31395
+ var AgentModelConfigSchema = zod_default.object({
31396
+ model: zod_default.string().optional(),
31397
+ reasoningEffort: zod_default.string().optional(),
31398
+ temperature: zod_default.number().optional(),
31399
+ fallback_models: FallbackModelsSchema.optional()
31400
+ });
31081
31401
  var OcBlackbytesConfigSchema = zod_default.object({
31082
31402
  $schema: zod_default.string().optional(),
31083
31403
  disabled_mcps: zod_default.array(AnyMcpNameSchema).optional(),
@@ -31089,6 +31409,8 @@ var OcBlackbytesConfigSchema = zod_default.object({
31089
31409
  model_fallback: zod_default.boolean().optional(),
31090
31410
  auto_update: zod_default.boolean().optional(),
31091
31411
  websearch: WebsearchConfigSchema.optional(),
31412
+ agents: zod_default.record(zod_default.string(), AgentModelConfigSchema).optional(),
31413
+ fallback_models: FallbackModelsSchema.optional(),
31092
31414
  _migrations: zod_default.array(zod_default.string()).optional()
31093
31415
  });
31094
31416
  // src/config/loader.ts
@@ -31118,7 +31440,8 @@ function loadPluginConfig(_input) {
31118
31440
  // src/index.ts
31119
31441
  var BlackbytesPlugin = async (ctx) => {
31120
31442
  const pluginConfig = loadPluginConfig(ctx);
31121
- return createOpenCodePlugin({ input: ctx, pluginConfig });
31443
+ const availableModels = pluginConfig.model_fallback ? await discoverAvailableModels(ctx.client) : new Map;
31444
+ return createOpenCodePlugin({ input: ctx, pluginConfig, availableModels });
31122
31445
  };
31123
31446
  var src_default = BlackbytesPlugin;
31124
31447
  export {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oc-blackbytes",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "description": "An OpenCode plugin tailored to streamline my everyday workflow.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",