@khalilgharbaoui/opencode-claude-code-plugin 0.6.2 → 0.7.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 CHANGED
@@ -319,7 +319,7 @@ Set `permissionMode: "plan"` to forward `--permission-mode plan` to Claude. The
319
319
  opencode has no native structured ask-question executor to proxy through (unlike `Bash`/`Task`), so the plugin handles `AskUserQuestion` specially:
320
320
 
321
321
  1. **It renders the full question.** The tool's payload — every question, header, option label, and option description — is emitted as readable markdown into the assistant stream so the user actually sees the choices (same approach as `ExitPlanMode`).
322
- 2. **It is never auto-allowed at the CLI gate.** Allowing it would let the headless Claude CLI resolve its own question (no TTY → fabricated/empty answer) and proceed on a guess. `controlRequestBehaviorForTool` hard-denies `AskUserQuestion` and returns a message telling the model to wait for the operator's answer — or, if the run is non-interactive, to proceed with the single most reasonable option and state its assumption rather than stall.
322
+ 2. **It is never auto-allowed at the CLI gate.** Allowing it would let the headless Claude CLI resolve its own question (no TTY → fabricated/empty answer) and proceed on a guess. `controlRequestBehaviorForTool` hard-denies `AskUserQuestion` and returns a message telling the model to **stop and wait for the operator's answer**end the turn, call no further tools, and never self-answer. (Before v0.7.0 this message also offered an "if the run is non-interactive, proceed with a reasonable guess" fallback. The model could not reliably tell interactive opencode from a headless run and routinely took it, so questions appeared to be skipped — [issue #8](https://github.com/khalilgharbaoui/opencode-claude-code-plugin/issues/8). For genuinely unattended runs, use the `controlRequestToolBehaviors` override below instead.)
323
323
 
324
324
  This hard-deny sits **below** `controlRequestToolBehaviors` in precedence but **above** the global `controlRequestBehavior`. So:
325
325
 
package/dist/index.d.ts CHANGED
@@ -542,9 +542,15 @@ interface ClaudeCodeProvider {
542
542
  languageModel(modelId: string): LanguageModelV3;
543
543
  }
544
544
  declare function createClaudeCode(settings?: ClaudeCodeProviderSettings): ClaudeCodeProvider;
545
+ /**
546
+ * Build models in OpenCode's config schema format (flat properties like
547
+ * `temperature`, `reasoning`, `cost.cache_read`, `modalities`, etc.)
548
+ * so the config-path provider loader parses them correctly.
549
+ */
550
+ declare function configModelsForProvider(providerModels: OpenCodeProvider["models"], providerID: string, modelSuffix?: string): Record<string, Record<string, unknown>>;
545
551
  declare const _default: {
546
552
  id: string;
547
553
  server: OpenCodePlugin;
548
554
  };
549
555
 
550
- export { type ClaudeCodeConfig, ClaudeCodeLanguageModel, type ClaudeCodeProvider, type ClaudeCodeProviderSettings, type ClaudeStreamMessage, type OpenCodeHooks, type OpenCodeModel, type OpenCodePlugin, bridgeOpencodeMcp, createClaudeCode, _default as default, defaultModels };
556
+ export { type ClaudeCodeConfig, ClaudeCodeLanguageModel, type ClaudeCodeProvider, type ClaudeCodeProviderSettings, type ClaudeStreamMessage, type OpenCodeHooks, type OpenCodeModel, type OpenCodePlugin, bridgeOpencodeMcp, configModelsForProvider, createClaudeCode, _default as default, defaultModels };
package/dist/index.js CHANGED
@@ -2018,6 +2018,11 @@ function isAskUserQuestionTool(name) {
2018
2018
  const n = name.toLowerCase();
2019
2019
  return n === "askuserquestion" || n === "ask_user_question";
2020
2020
  }
2021
+ var ASK_USER_QUESTION_DENY_MESSAGE = "Your question and its options have already been presented to the operator verbatim. Stop now: end your turn without calling any more tools and without answering the question yourself. Wait for the operator's reply, which arrives as the next user message. Do not guess, assume, or proceed on their behalf.";
2022
+ function denyMessageForTool(toolName, configuredDenyMessage) {
2023
+ if (isAskUserQuestionTool(toolName)) return ASK_USER_QUESTION_DENY_MESSAGE;
2024
+ return configuredDenyMessage ?? `Denied by opencode-claude-code policy for tool ${toolName}`;
2025
+ }
2021
2026
  function formatAskUserQuestion(input) {
2022
2027
  const anyInput = input;
2023
2028
  const questions = Array.isArray(anyInput?.questions) ? anyInput.questions : [];
@@ -2460,7 +2465,10 @@ var ClaudeCodeLanguageModel = class {
2460
2465
  toolName
2461
2466
  });
2462
2467
  } else {
2463
- const denyMessage = isAskUserQuestionTool(toolName) ? "Your question and its options have already been presented to the operator in full. Prefer to stop here and wait for their answer in the next message \u2014 do not silently guess. But if this is an automated or otherwise non-interactive run where no operator will reply, do not stall: proceed with the single most reasonable option and state, in one line, the assumption you made so it can be corrected later." : this.config.controlRequestDenyMessage ?? `Denied by opencode-claude-code policy for tool ${toolName}`;
2468
+ const denyMessage = denyMessageForTool(
2469
+ toolName,
2470
+ this.config.controlRequestDenyMessage
2471
+ );
2464
2472
  this.writeControlResponse(proc, requestId, {
2465
2473
  behavior: "deny",
2466
2474
  message: denyMessage,
@@ -4559,22 +4567,6 @@ function cleanProviderOptions(options = {}) {
4559
4567
  delete result.accounts;
4560
4568
  return result;
4561
4569
  }
4562
- function mergeDefaultVariants(models = {}) {
4563
- const result = { ...models };
4564
- for (const [id, model] of Object.entries(defaultModels)) {
4565
- if (!model.variants) continue;
4566
- const existing = result[id] && typeof result[id] === "object" ? result[id] : {};
4567
- const variants = existing.variants && typeof existing.variants === "object" ? existing.variants : {};
4568
- result[id] = {
4569
- ...existing,
4570
- variants: {
4571
- ...model.variants,
4572
- ...variants
4573
- }
4574
- };
4575
- }
4576
- return result;
4577
- }
4578
4570
  function defaultModelsForProvider(providerModels, providerID = PROVIDER_ID2, modelSuffix) {
4579
4571
  const models = Object.fromEntries(
4580
4572
  Object.entries(defaultModels).map(([id, model]) => {
@@ -4611,6 +4603,7 @@ function configModelsForProvider(providerModels, providerID, modelSuffix) {
4611
4603
  for (const [id, model] of Object.entries(defaultModels)) {
4612
4604
  const modelId = modelSuffix ? `${id}@${modelSuffix}` : id;
4613
4605
  const existing = providerModels[id] ?? providerModels[modelId];
4606
+ const existingVariants = existing && typeof existing.variants === "object" ? existing.variants ?? {} : {};
4614
4607
  const full = {
4615
4608
  ...model,
4616
4609
  id: modelId,
@@ -4620,6 +4613,10 @@ function configModelsForProvider(providerModels, providerID, modelSuffix) {
4620
4613
  id: modelId,
4621
4614
  npm: existing?.api?.npm ?? model.api.npm,
4622
4615
  url: existing?.api?.url ?? model.api.url
4616
+ },
4617
+ variants: {
4618
+ ...model.variants ?? {},
4619
+ ...existingVariants
4623
4620
  }
4624
4621
  };
4625
4622
  models[modelId] = toConfigModel(full);
@@ -4648,8 +4645,10 @@ async function providerConfig(existing, providerID = PROVIDER_ID2, optionDefault
4648
4645
  options: {
4649
4646
  ...mergedOptions,
4650
4647
  ...runtime
4651
- },
4652
- models: mergeDefaultVariants(existing?.models)
4648
+ }
4649
+ // models is intentionally omitted: both callers overwrite it with
4650
+ // configModelsForProvider(), which emits the flat config schema
4651
+ // opencode's config-path loader parses (and merges user variants).
4653
4652
  };
4654
4653
  }
4655
4654
  async function expandAccountProviders(config) {
@@ -4717,7 +4716,11 @@ var server = async (input) => {
4717
4716
  const existing = config.provider[PROVIDER_ID2];
4718
4717
  config.provider[PROVIDER_ID2] = {
4719
4718
  ...existing,
4720
- ...await providerConfig(existing)
4719
+ ...await providerConfig(existing),
4720
+ models: configModelsForProvider(
4721
+ existing?.models ?? {},
4722
+ PROVIDER_ID2
4723
+ )
4721
4724
  };
4722
4725
  log.notice("registered claude-code provider", {
4723
4726
  id: PROVIDER_ID2,
@@ -4768,6 +4771,7 @@ var index_default = {
4768
4771
  export {
4769
4772
  ClaudeCodeLanguageModel,
4770
4773
  bridgeOpencodeMcp,
4774
+ configModelsForProvider,
4771
4775
  createClaudeCode,
4772
4776
  index_default as default,
4773
4777
  defaultModels