teapot-coding-agent 0.29.10 → 0.29.11

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.
@@ -308,6 +308,7 @@ export class Agent {
308
308
  // effort field is then never sent, which is the safe default
309
309
  reasoningEffort: "",
310
310
  supportedParameters: [],
311
+ reasoningEffortOptIn: false,
311
312
  maxConsecutiveToolErrors: 5,
312
313
  maxTurnsPerRound: 200,
313
314
  maxAuditRejects: 3, // #128: bound the reject loop
@@ -1262,9 +1263,10 @@ export class Agent {
1262
1263
  stats: { ...this.stats, costUsd: this.modelPricing ? this.stats.costUsd : undefined },
1263
1264
  model: this.opts.llm.model,
1264
1265
  provider: this.opts.provider,
1265
- // #47: the effort in force, and whether this model can take one at all
1266
+ // #47/#150: the effort in force, and whether this model can take one at all
1266
1267
  ...(this.opts.reasoningEffort ? { reasoningEffort: this.opts.reasoningEffort } : {}),
1267
- effortSupported: (this.opts.supportedParameters ?? []).includes("reasoning_effort"),
1268
+ effortSupported: (this.opts.supportedParameters ?? []).includes("reasoning_effort") ||
1269
+ this.opts.reasoningEffortOptIn === true,
1268
1270
  sessionDir: this.opts.sessionDir,
1269
1271
  ctx: {
1270
1272
  usedTokens: this.lastUsage?.input ?? this.estimateTokens(),
package/dist/agent/llm.js CHANGED
@@ -8,13 +8,25 @@
8
8
  import OpenAI from "openai";
9
9
  const clients = new WeakMap();
10
10
  /**
11
- * Reasoning effort for a request body (#47).
11
+ * Reasoning effort for a request body (#47, #150).
12
12
  *
13
- * Kept local and deliberately conservative: the setting is only honoured when
14
- * the endpoint is OpenRouter, the value is one OpenRouter documents, and the
15
- * catalogue says this model advertises `reasoning_effort`. Support is far from
16
- * universal (verified against the live /models endpoint: 196 of 462 models),
17
- * so an unsent field is much safer than a wrong one.
13
+ * MEASURED against the live endpoints in use (one probe per provider, `max_tokens:
14
+ * 3` with `reasoning_effort: "low"`):
15
+ *
16
+ * openrouter — documented, catalogue advertises the field
17
+ * commandcode-goat — 200 OK and the reply carried `reasoning_content`
18
+ * google — 400 "Thinking level is not supported for this model"
19
+ * orcarouter / opencode / experientiallabs — refused before reaching the field
20
+ *
21
+ * So "OpenRouter only" was too narrow (#150: `commandcode-goat` offers no options
22
+ * at all), but "send it everywhere" would break Google, which REJECTS the field
23
+ * with a 400 rather than ignoring it. The gate therefore stays opt-in:
24
+ *
25
+ * 1. the configured value must be a level the API documents, AND
26
+ * 2. EITHER the endpoint's catalogue advertises `reasoning_effort` (OpenRouter
27
+ * and compatible gateways), OR the provider is explicitly marked
28
+ * `reasoningEffort: true` in the config for an endpoint that accepts the field
29
+ * but publishes no catalogue capability list (#150).
18
30
  *
19
31
  * Implemented here rather than via model-meta to avoid an import cycle —
20
32
  * model-meta already imports providerHeaders from this file.
@@ -23,11 +35,19 @@ export function effortForRequest(cfg, supported = cfg.supportedParameters) {
23
35
  const e = cfg.reasoningEffort;
24
36
  if (!e || !REASONING_EFFORTS.includes(e))
25
37
  return {};
26
- if (!isOpenRouterEndpoint(cfg.baseUrl))
27
- return {};
28
- if (!supported?.includes("reasoning_effort"))
29
- return {};
30
- return { reasoning_effort: e };
38
+ // the catalogue is a capability claim from a GATEWAY, so trust it only where the
39
+ // gateway format is the one that carries it. MEASURED: of the endpoints in use,
40
+ // only OpenRouter-family ones publish `supported_parameters` at all — google,
41
+ // commandcode, orcarouter and opencode all return 0 models with the field, so
42
+ // this cannot silently widen the blast radius.
43
+ if (supported?.includes("reasoning_effort") && isOpenRouterEndpoint(cfg.baseUrl)) {
44
+ return { reasoning_effort: e };
45
+ }
46
+ // #150: an endpoint that ACCEPTS the field but publishes no capability list needs
47
+ // the provider to say so explicitly (provider.reasoningEffort: true in config).
48
+ if (cfg.reasoningEffortOptIn === true)
49
+ return { reasoning_effort: e };
50
+ return {};
31
51
  }
32
52
  function isOpenRouterEndpoint(baseUrl) {
33
53
  try {
@@ -11,6 +11,15 @@ export const ProviderSchema = z.object({
11
11
  baseUrl: z.union([z.string().url("baseUrl must be a valid URL"), z.literal("")]).optional(),
12
12
  apiKey: z.string().optional(),
13
13
  model: z.string().optional(),
14
+ /**
15
+ * Does this endpoint accept `reasoning_effort`? (#150)
16
+ *
17
+ * Needed for gateways that ACCEPT the field but publish no capability list in
18
+ * their /models response, so the gateway-style catalogue check cannot see it.
19
+ * Off by default because some endpoints (Google) reject the field with a 400
20
+ * rather than ignoring it — an unset field is safer than a wrong one.
21
+ */
22
+ reasoningEffort: z.boolean().optional(),
14
23
  });
15
24
  export const AgentEntrySchema = z.object({
16
25
  id: z
package/dist/master.js CHANGED
@@ -804,6 +804,9 @@ export class Master {
804
804
  ...(modelMeta?.supportedParameters
805
805
  ? { supportedParameters: modelMeta.supportedParameters }
806
806
  : {}),
807
+ // #150: the provider opts in explicitly when its catalogue does not carry
808
+ // capability metadata, so the gate above cannot see the support
809
+ ...(prov?.reasoningEffort ? { reasoningEffortOptIn: true } : {}),
807
810
  });
808
811
  // pricing metadata (USD/token) for the runtime cost estimate — best
809
812
  // effort: providers without pricing in /models simply show no cost
@@ -1662,6 +1665,10 @@ When this task is done (or truly blocked), call finish() with a summary for @${p
1662
1665
  cfgEntry.reasoningEffort = opts.opts.reasoningEffort || undefined;
1663
1666
  }
1664
1667
  opts.opts.supportedParameters = meta?.supportedParameters ?? [];
1668
+ // #150: keep the explicit provider opt-in in step with the (possibly new)
1669
+ // provider, so switching to an opted-in gateway enables the dropdown at once
1670
+ const nowProv = this.config.providers?.[effectiveProvider];
1671
+ opts.opts.reasoningEffortOptIn = nowProv?.reasoningEffort === true;
1665
1672
  if (!llm.model)
1666
1673
  throw new Error("no model resolved (pass model or set one on the provider)");
1667
1674
  if (!llm.baseUrl)
@@ -749,6 +749,9 @@ export function buildApp(master) {
749
749
  baseUrl: p.baseUrl ?? "",
750
750
  apiKey: masked ? prev[name]?.apiKey : p.apiKey,
751
751
  ...(p.model ? { model: p.model } : {}),
752
+ // #150: the opt-in must survive a config PUT, or saving anything else
753
+ // would silently disable the effort dropdown for that provider
754
+ ...(p.reasoningEffort ? { reasoningEffort: true } : {}),
752
755
  };
753
756
  }
754
757
  master.updateConfig({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "teapot-coding-agent",
3
- "version": "0.29.10",
3
+ "version": "0.29.11",
4
4
  "description": "A lightweight, always-on multi-agent harness for AI coding agents",
5
5
  "type": "module",
6
6
  "license": "AGPL-3.0-or-later",