oc-blackbytes 0.2.0 → 0.3.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 +64 -4
- package/dist/index.js +142 -10
- 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
|
|
@@ -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
|
```
|
|
@@ -90,6 +100,8 @@ Create `oc-blackbytes.jsonc` in the OpenCode config directory.
|
|
|
90
100
|
| `model_fallback` | `boolean` | `false` | Recognized by the schema for model compatibility workflows. |
|
|
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 (reserved for future use). |
|
|
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 (reserved for future use). |
|
|
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.
|
|
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
|
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 = {
|
|
@@ -2199,15 +2290,38 @@ createOracleAgent.mode = MODE5;
|
|
|
2199
2290
|
// src/handlers/config-handler/agent-config-handler.ts
|
|
2200
2291
|
var DEFAULT_AGENT_NAME = "bytes";
|
|
2201
2292
|
var SUPERSEDED_AGENTS = ["build", "plan"];
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
};
|
|
2293
|
+
var BUILTIN_AGENT_FACTORIES = {
|
|
2294
|
+
bytes: createBytesAgent,
|
|
2295
|
+
explore: createExploreAgent,
|
|
2296
|
+
oracle: createOracleAgent,
|
|
2297
|
+
librarian: createLibrarianAgent,
|
|
2298
|
+
general: createGeneralAgent
|
|
2299
|
+
};
|
|
2300
|
+
function createBuiltinAgents(agentOverrides) {
|
|
2301
|
+
const agents = {};
|
|
2302
|
+
for (const [name, factory] of Object.entries(BUILTIN_AGENT_FACTORIES)) {
|
|
2303
|
+
const modelHint = agentOverrides?.[name]?.model ?? "";
|
|
2304
|
+
agents[name] = factory(modelHint);
|
|
2305
|
+
}
|
|
2306
|
+
if (agentOverrides) {
|
|
2307
|
+
applyAgentModelOverrides(agents, agentOverrides);
|
|
2308
|
+
}
|
|
2309
|
+
return agents;
|
|
2310
|
+
}
|
|
2311
|
+
function applyAgentModelOverrides(agents, overrides) {
|
|
2312
|
+
for (const [name, override] of Object.entries(overrides)) {
|
|
2313
|
+
if (!(name in agents))
|
|
2314
|
+
continue;
|
|
2315
|
+
const agent = agents[name];
|
|
2316
|
+
if (override.reasoningEffort !== undefined) {
|
|
2317
|
+
agent.reasoningEffort = override.reasoningEffort;
|
|
2318
|
+
log(` Agent '${name}': reasoningEffort \u2192 ${override.reasoningEffort}`);
|
|
2319
|
+
}
|
|
2320
|
+
if (override.temperature !== undefined) {
|
|
2321
|
+
agent.temperature = override.temperature;
|
|
2322
|
+
log(` Agent '${name}': temperature \u2192 ${override.temperature}`);
|
|
2323
|
+
}
|
|
2324
|
+
}
|
|
2211
2325
|
}
|
|
2212
2326
|
function isDisabledAgentEntry(value) {
|
|
2213
2327
|
return typeof value === "object" && value !== null && "disable" in value && value.disable === true;
|
|
@@ -2249,7 +2363,7 @@ function handleAgentConfig(ctx) {
|
|
|
2249
2363
|
const { disabled_agents: pluginDisabledAgents = [] } = ctx.pluginConfig;
|
|
2250
2364
|
const userAgents = ctx.config.agent;
|
|
2251
2365
|
const userDisabledAgentNames = captureUserDisabledAgents(userAgents);
|
|
2252
|
-
const builtinAgents = createBuiltinAgents();
|
|
2366
|
+
const builtinAgents = createBuiltinAgents(ctx.pluginConfig.agents);
|
|
2253
2367
|
const merged = {
|
|
2254
2368
|
...builtinAgents
|
|
2255
2369
|
};
|
|
@@ -17525,6 +17639,7 @@ async function createOpenCodePlugin({
|
|
|
17525
17639
|
return {
|
|
17526
17640
|
...handleConfig(pluginConfig),
|
|
17527
17641
|
...handleChatHeaders(pluginConfig),
|
|
17642
|
+
...handleChatParams(pluginConfig),
|
|
17528
17643
|
tool: handleTools(pluginConfig, input),
|
|
17529
17644
|
"tool.execute.after": handleToolExecuteAfter(pluginConfig)
|
|
17530
17645
|
};
|
|
@@ -31078,6 +31193,21 @@ var WebsearchConfigSchema = zod_default.object({
|
|
|
31078
31193
|
});
|
|
31079
31194
|
|
|
31080
31195
|
// src/config/schema/oc-blackbytes-config.ts
|
|
31196
|
+
var FallbackModelObjectSchema = zod_default.object({
|
|
31197
|
+
model: zod_default.string(),
|
|
31198
|
+
reasoningEffort: zod_default.string().optional(),
|
|
31199
|
+
temperature: zod_default.number().optional()
|
|
31200
|
+
});
|
|
31201
|
+
var FallbackModelsSchema = zod_default.union([
|
|
31202
|
+
zod_default.string(),
|
|
31203
|
+
zod_default.array(zod_default.union([zod_default.string(), FallbackModelObjectSchema]))
|
|
31204
|
+
]);
|
|
31205
|
+
var AgentModelConfigSchema = zod_default.object({
|
|
31206
|
+
model: zod_default.string().optional(),
|
|
31207
|
+
reasoningEffort: zod_default.string().optional(),
|
|
31208
|
+
temperature: zod_default.number().optional(),
|
|
31209
|
+
fallback_models: FallbackModelsSchema.optional()
|
|
31210
|
+
});
|
|
31081
31211
|
var OcBlackbytesConfigSchema = zod_default.object({
|
|
31082
31212
|
$schema: zod_default.string().optional(),
|
|
31083
31213
|
disabled_mcps: zod_default.array(AnyMcpNameSchema).optional(),
|
|
@@ -31089,6 +31219,8 @@ var OcBlackbytesConfigSchema = zod_default.object({
|
|
|
31089
31219
|
model_fallback: zod_default.boolean().optional(),
|
|
31090
31220
|
auto_update: zod_default.boolean().optional(),
|
|
31091
31221
|
websearch: WebsearchConfigSchema.optional(),
|
|
31222
|
+
agents: zod_default.record(zod_default.string(), AgentModelConfigSchema).optional(),
|
|
31223
|
+
fallback_models: FallbackModelsSchema.optional(),
|
|
31092
31224
|
_migrations: zod_default.array(zod_default.string()).optional()
|
|
31093
31225
|
});
|
|
31094
31226
|
// src/config/loader.ts
|