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.
Files changed (3) hide show
  1. package/README.md +64 -4
  2. package/dist/index.js +142 -10
  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
@@ -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
- 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
- };
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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oc-blackbytes",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "An OpenCode plugin tailored to streamline my everyday workflow.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",