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.
- package/README.md +68 -7
- package/dist/index.js +338 -15
- 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
|
|
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` |
|
|
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/ #
|
|
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
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
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
|
|
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
|
-
|
|
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 {
|