@tangle-network/agent-interface 0.8.0 → 0.9.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.
@@ -0,0 +1,46 @@
1
+ import type { ReasoningEffort } from "./agent-profile.js";
2
+ import { type HarnessType } from "./harness.js";
3
+ /**
4
+ * The unified harness capability layer — the single source of truth for:
5
+ * 1. harness ↔ model compatibility (which models a harness can run), and
6
+ * 2. reasoning-effort support (which thinking levels a harness/model expresses).
7
+ *
8
+ * Both are facets of the same question — "what can this (harness, model) pair actually do" — and
9
+ * apply to BOTH harness-backed systems (vendor-locked CLIs like claude-code/codex/kimi) AND
10
+ * router-backed systems (opencode, cli-base: any model the router serves). Lifted here so the
11
+ * cli-bridge backends, the sandbox UI pickers, and the router all read one truth instead of each
12
+ * hand-rolling a divergent copy.
13
+ *
14
+ * Grounded in cli-bridge's real backend clamps (codex caps at `high`, kimi is binary on/off, claude
15
+ * carries the full range, cli-base has no agent) — NOT a guessed matrix. The per-MODEL reasoning
16
+ * capability (does this specific model reason at all) is dynamic catalog data the caller supplies.
17
+ */
18
+ /** low → high. `none` = thinking off; `ultracode` = max (claude-code mode). */
19
+ export declare const reasoningLadder: readonly ReasoningEffort[];
20
+ /** Provider prefix of a canonical model id (`anthropic/claude-…` → `anthropic`), or null. */
21
+ export declare function modelProvider(modelId: string): string | null;
22
+ /** The providers a harness is locked to, or `null` when it is router-backed (any model). */
23
+ export declare function harnessProviders(harness: HarnessType): readonly string[] | null;
24
+ /**
25
+ * Whether a harness can run a model. Router-backed harnesses (no provider lock) accept anything;
26
+ * a model id with no provider prefix (a sentinel like `default`) is treated as compatible too.
27
+ */
28
+ export declare function harnessSupportsModel(harness: HarnessType, modelId: string): boolean;
29
+ /** The harness to adopt for a model whose provider is vendor-locked (`anthropic` → `claude-code`,
30
+ * `openai` → `codex`, `moonshot` → `kimi-code`); `null` when any router-backed harness will do. */
31
+ export declare function preferredHarnessForModel(modelId: string): HarnessType | null;
32
+ /** The reasoning efforts a harness can express, independent of model — `none` up to its ceiling. */
33
+ export declare function harnessReasoningEfforts(harness: HarnessType): readonly ReasoningEffort[];
34
+ /** What the caller knows about a model's own reasoning capability (from a model catalog). */
35
+ export interface ModelReasoningCapability {
36
+ /** Does the model reason at all? `false` → only `none` is offered, on any harness. */
37
+ supportsReasoning?: boolean;
38
+ /** The model's own ceiling, if narrower than the harness's. */
39
+ maxEffort?: ReasoningEffort;
40
+ }
41
+ /**
42
+ * The effective reasoning efforts for a (harness, model) pair: the harness clamp, further narrowed by
43
+ * the model's own capability. A model that doesn't reason collapses to `['none']`; a model with a
44
+ * lower ceiling caps the list there. Pass `model` from your catalog; omit it for the harness-only set.
45
+ */
46
+ export declare function reasoningEffortsFor(harness: HarnessType, model?: ModelReasoningCapability | null): readonly ReasoningEffort[];
@@ -0,0 +1,105 @@
1
+ import { canonicalizeHarness } from "./harness.js";
2
+ /**
3
+ * The unified harness capability layer — the single source of truth for:
4
+ * 1. harness ↔ model compatibility (which models a harness can run), and
5
+ * 2. reasoning-effort support (which thinking levels a harness/model expresses).
6
+ *
7
+ * Both are facets of the same question — "what can this (harness, model) pair actually do" — and
8
+ * apply to BOTH harness-backed systems (vendor-locked CLIs like claude-code/codex/kimi) AND
9
+ * router-backed systems (opencode, cli-base: any model the router serves). Lifted here so the
10
+ * cli-bridge backends, the sandbox UI pickers, and the router all read one truth instead of each
11
+ * hand-rolling a divergent copy.
12
+ *
13
+ * Grounded in cli-bridge's real backend clamps (codex caps at `high`, kimi is binary on/off, claude
14
+ * carries the full range, cli-base has no agent) — NOT a guessed matrix. The per-MODEL reasoning
15
+ * capability (does this specific model reason at all) is dynamic catalog data the caller supplies.
16
+ */
17
+ /** low → high. `none` = thinking off; `ultracode` = max (claude-code mode). */
18
+ export const reasoningLadder = [
19
+ "none",
20
+ "minimal",
21
+ "low",
22
+ "medium",
23
+ "high",
24
+ "xhigh",
25
+ "ultracode",
26
+ ];
27
+ // ── Harness ↔ model compatibility ────────────────────────────────────────────
28
+ /**
29
+ * Provider prefixes a harness is vendor-locked to (canonical-id prefix, e.g. `anthropic`, `openai`).
30
+ * A harness with no entry is router-backed: it runs any model. Keyed by the BASE runner — aliases
31
+ * (`claude`/`claudish`/`kimi`) resolve through `canonicalizeHarness` first.
32
+ */
33
+ const harnessProviderLock = {
34
+ "claude-code": ["anthropic"],
35
+ nanoclaw: ["anthropic"],
36
+ codex: ["openai"],
37
+ "kimi-code": ["moonshot"],
38
+ };
39
+ /** Provider prefix of a canonical model id (`anthropic/claude-…` → `anthropic`), or null. */
40
+ export function modelProvider(modelId) {
41
+ const slash = modelId.indexOf("/");
42
+ return slash > 0 ? modelId.slice(0, slash) : null;
43
+ }
44
+ /** The providers a harness is locked to, or `null` when it is router-backed (any model). */
45
+ export function harnessProviders(harness) {
46
+ return harnessProviderLock[canonicalizeHarness(harness)] ?? null;
47
+ }
48
+ /**
49
+ * Whether a harness can run a model. Router-backed harnesses (no provider lock) accept anything;
50
+ * a model id with no provider prefix (a sentinel like `default`) is treated as compatible too.
51
+ */
52
+ export function harnessSupportsModel(harness, modelId) {
53
+ const providers = harnessProviders(harness);
54
+ if (!providers)
55
+ return true;
56
+ const provider = modelProvider(modelId);
57
+ return provider === null || providers.includes(provider);
58
+ }
59
+ /** The harness to adopt for a model whose provider is vendor-locked (`anthropic` → `claude-code`,
60
+ * `openai` → `codex`, `moonshot` → `kimi-code`); `null` when any router-backed harness will do. */
61
+ export function preferredHarnessForModel(modelId) {
62
+ const provider = modelProvider(modelId);
63
+ if (!provider)
64
+ return null;
65
+ for (const [harness, providers] of Object.entries(harnessProviderLock)) {
66
+ if (providers?.includes(provider))
67
+ return harness;
68
+ }
69
+ return null;
70
+ }
71
+ // ── Reasoning-effort support ──────────────────────────────────────────────────
72
+ /**
73
+ * The highest reasoning effort a harness's runtime can express (its native clamp ceiling). Grounded
74
+ * in cli-bridge: codex's `model_reasoning_effort` caps at `high` (xhigh/ultracode clamp down); kimi's
75
+ * `--thinking` is binary, so `high` is its "on"; claude-code carries the full range; `cli-base` has
76
+ * no agent and thus no thinking. Router/model-driven harnesses default to the full range.
77
+ */
78
+ const harnessReasoningCeiling = {
79
+ "cli-base": "none",
80
+ codex: "high",
81
+ "kimi-code": "high",
82
+ "claude-code": "ultracode",
83
+ nanoclaw: "ultracode",
84
+ };
85
+ /** The reasoning efforts a harness can express, independent of model — `none` up to its ceiling. */
86
+ export function harnessReasoningEfforts(harness) {
87
+ const ceiling = harnessReasoningCeiling[canonicalizeHarness(harness)] ?? "ultracode";
88
+ return reasoningLadder.slice(0, reasoningLadder.indexOf(ceiling) + 1);
89
+ }
90
+ /**
91
+ * The effective reasoning efforts for a (harness, model) pair: the harness clamp, further narrowed by
92
+ * the model's own capability. A model that doesn't reason collapses to `['none']`; a model with a
93
+ * lower ceiling caps the list there. Pass `model` from your catalog; omit it for the harness-only set.
94
+ */
95
+ export function reasoningEffortsFor(harness, model) {
96
+ if (model?.supportsReasoning === false)
97
+ return ["none"];
98
+ let efforts = harnessReasoningEfforts(harness);
99
+ if (model?.maxEffort) {
100
+ const cap = reasoningLadder.indexOf(model.maxEffort);
101
+ if (cap >= 0)
102
+ efforts = efforts.filter((e) => reasoningLadder.indexOf(e) <= cap);
103
+ }
104
+ return efforts;
105
+ }
package/dist/index.d.ts CHANGED
@@ -580,4 +580,5 @@ export interface SdkProviderAdapter {
580
580
  }
581
581
  export * from "./agent-profile.js";
582
582
  export * from "./harness.js";
583
+ export * from "./harness-capabilities.js";
583
584
  export * from "./profile-schema.js";
package/dist/index.js CHANGED
@@ -124,4 +124,5 @@ export function resolveNativeWebTools(tools) {
124
124
  }
125
125
  export * from "./agent-profile.js";
126
126
  export * from "./harness.js";
127
+ export * from "./harness-capabilities.js";
127
128
  export * from "./profile-schema.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tangle-network/agent-interface",
3
- "version": "0.8.0",
3
+ "version": "0.9.0",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",