@tangle-network/agent-interface 0.14.0 → 0.16.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.
|
@@ -29,6 +29,20 @@ export declare function harnessSupportsModel(harness: HarnessType, modelId: stri
|
|
|
29
29
|
/** The harness to adopt for a model whose provider is vendor-locked (`anthropic` → `claude-code`,
|
|
30
30
|
* `openai` → `codex`, `moonshot` → `kimi-code`); `null` when any router-backed harness will do. */
|
|
31
31
|
export declare function preferredHarnessForModel(modelId: string): HarnessType | null;
|
|
32
|
+
/**
|
|
33
|
+
* Keep `modelId` when the harness can run it; otherwise return the harness's best compatible id from
|
|
34
|
+
* `candidateIds` (preferred patterns in order, highest version within a pattern). When nothing in the
|
|
35
|
+
* candidate list fits, the original id is returned unchanged so the caller sees the incompatibility
|
|
36
|
+
* instead of a silent wrong substitution. `candidateIds` are canonical ("provider/model") ids — the
|
|
37
|
+
* caller maps its own catalog shape down to ids, keeping this layer catalog-agnostic.
|
|
38
|
+
*/
|
|
39
|
+
export declare function snapModelToHarness(harness: HarnessType, modelId: string, candidateIds: readonly string[]): string;
|
|
40
|
+
/**
|
|
41
|
+
* Keep the harness when it can run `modelId`; otherwise return the model's native harness
|
|
42
|
+
* (anthropic → claude-code, openai → codex, moonshot → kimi-code), falling back to the router-backed
|
|
43
|
+
* `opencode` for everything else.
|
|
44
|
+
*/
|
|
45
|
+
export declare function snapHarnessToModel(harness: HarnessType, modelId: string): HarnessType;
|
|
32
46
|
/** The reasoning efforts a harness can express, independent of model — `none` up to its ceiling. */
|
|
33
47
|
export declare function harnessReasoningEfforts(harness: HarnessType): readonly ReasoningEffort[];
|
|
34
48
|
/** What the caller knows about a model's own reasoning capability (from a model catalog). */
|
|
@@ -44,3 +58,9 @@ export interface ModelReasoningCapability {
|
|
|
44
58
|
* lower ceiling caps the list there. Pass `model` from your catalog; omit it for the harness-only set.
|
|
45
59
|
*/
|
|
46
60
|
export declare function reasoningEffortsFor(harness: HarnessType, model?: ModelReasoningCapability | null): readonly ReasoningEffort[];
|
|
61
|
+
/** Whether the harness's runner honors a per-turn MODEL override (vs. picking the model itself). */
|
|
62
|
+
export declare function harnessHonorsModel(harness: HarnessType): boolean;
|
|
63
|
+
/** Whether the harness's runner honors a reasoning-EFFORT override (vs. dropping it). */
|
|
64
|
+
export declare function harnessHonorsEffort(harness: HarnessType): boolean;
|
|
65
|
+
/** Whether the harness honors BOTH chat selectors — i.e. the model and effort pickers are live. */
|
|
66
|
+
export declare function harnessHonorsSelectors(harness: HarnessType): boolean;
|
|
@@ -29,10 +29,13 @@ export const reasoningLadder = [
|
|
|
29
29
|
* Provider prefixes a harness is vendor-locked to (canonical-id prefix, e.g. `anthropic`, `openai`).
|
|
30
30
|
* A harness with no entry is router-backed: it runs any model. Keyed by the BASE runner — aliases
|
|
31
31
|
* (`claude`/`claudish`/`kimi`) resolve through `canonicalizeHarness` first.
|
|
32
|
+
*
|
|
33
|
+
* `nanoclaw` is deliberately absent despite the "claw" name: its runner routes every provider through
|
|
34
|
+
* the Tangle router (canonical model id straight to the gateway), so it is router-backed like
|
|
35
|
+
* `opencode` — not Anthropic-locked.
|
|
32
36
|
*/
|
|
33
37
|
const harnessProviderLock = {
|
|
34
38
|
"claude-code": ["anthropic"],
|
|
35
|
-
nanoclaw: ["anthropic"],
|
|
36
39
|
codex: ["openai"],
|
|
37
40
|
"kimi-code": ["moonshot"],
|
|
38
41
|
};
|
|
@@ -68,19 +71,69 @@ export function preferredHarnessForModel(modelId) {
|
|
|
68
71
|
}
|
|
69
72
|
return null;
|
|
70
73
|
}
|
|
74
|
+
// ── Harness ↔ model snapping (catalog-aware) ─────────────────────────────────
|
|
75
|
+
/**
|
|
76
|
+
* Per-harness ranking patterns for {@link snapModelToHarness}, best first; within one pattern the
|
|
77
|
+
* highest version wins (numeric-aware). Only vendor-locked harnesses need an entry — a router-backed
|
|
78
|
+
* harness never snaps (it runs the model as-is). Keyed by the BASE runner (aliases canonicalized).
|
|
79
|
+
*/
|
|
80
|
+
const harnessPreferredModelPatterns = {
|
|
81
|
+
"claude-code": [
|
|
82
|
+
/^anthropic\/claude-opus-[\d.-]+$/,
|
|
83
|
+
/^anthropic\/claude-sonnet-[\d.-]+$/,
|
|
84
|
+
/^anthropic\//,
|
|
85
|
+
],
|
|
86
|
+
codex: [/^openai\/gpt-\d+(\.\d+)?$/, /^openai\/gpt/, /^openai\//],
|
|
87
|
+
"kimi-code": [/^moonshot\//],
|
|
88
|
+
};
|
|
89
|
+
const numericDesc = new Intl.Collator(undefined, {
|
|
90
|
+
numeric: true,
|
|
91
|
+
sensitivity: "base",
|
|
92
|
+
});
|
|
93
|
+
/**
|
|
94
|
+
* Keep `modelId` when the harness can run it; otherwise return the harness's best compatible id from
|
|
95
|
+
* `candidateIds` (preferred patterns in order, highest version within a pattern). When nothing in the
|
|
96
|
+
* candidate list fits, the original id is returned unchanged so the caller sees the incompatibility
|
|
97
|
+
* instead of a silent wrong substitution. `candidateIds` are canonical ("provider/model") ids — the
|
|
98
|
+
* caller maps its own catalog shape down to ids, keeping this layer catalog-agnostic.
|
|
99
|
+
*/
|
|
100
|
+
export function snapModelToHarness(harness, modelId, candidateIds) {
|
|
101
|
+
if (harnessSupportsModel(harness, modelId))
|
|
102
|
+
return modelId;
|
|
103
|
+
const patterns = harnessPreferredModelPatterns[canonicalizeHarness(harness)] ?? [];
|
|
104
|
+
for (const pattern of patterns) {
|
|
105
|
+
const matches = candidateIds
|
|
106
|
+
.filter((id) => pattern.test(id))
|
|
107
|
+
.sort((a, b) => numericDesc.compare(b, a));
|
|
108
|
+
if (matches.length > 0)
|
|
109
|
+
return matches[0];
|
|
110
|
+
}
|
|
111
|
+
return candidateIds.find((id) => harnessSupportsModel(harness, id)) ?? modelId;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Keep the harness when it can run `modelId`; otherwise return the model's native harness
|
|
115
|
+
* (anthropic → claude-code, openai → codex, moonshot → kimi-code), falling back to the router-backed
|
|
116
|
+
* `opencode` for everything else.
|
|
117
|
+
*/
|
|
118
|
+
export function snapHarnessToModel(harness, modelId) {
|
|
119
|
+
if (harnessSupportsModel(harness, modelId))
|
|
120
|
+
return harness;
|
|
121
|
+
return preferredHarnessForModel(modelId) ?? "opencode";
|
|
122
|
+
}
|
|
71
123
|
// ── Reasoning-effort support ──────────────────────────────────────────────────
|
|
72
124
|
/**
|
|
73
125
|
* The highest reasoning effort a harness's runtime can express (its native clamp ceiling). Grounded
|
|
74
126
|
* in cli-bridge: codex's `model_reasoning_effort` caps at `high` (xhigh/ultracode clamp down); kimi's
|
|
75
127
|
* `--thinking` is binary, so `high` is its "on"; claude-code carries the full range; `cli-base` has
|
|
76
|
-
* no agent and thus no thinking
|
|
128
|
+
* no agent and thus no thinking; `nanoclaw`'s runner sends no thinking flag, so it expresses only
|
|
129
|
+
* `none`. Router/model-driven harnesses default to the full range.
|
|
77
130
|
*/
|
|
78
131
|
const harnessReasoningCeiling = {
|
|
79
132
|
"cli-base": "none",
|
|
80
133
|
codex: "high",
|
|
81
134
|
"kimi-code": "high",
|
|
82
135
|
"claude-code": "ultracode",
|
|
83
|
-
nanoclaw: "
|
|
136
|
+
nanoclaw: "none",
|
|
84
137
|
};
|
|
85
138
|
/** The reasoning efforts a harness can express, independent of model — `none` up to its ceiling. */
|
|
86
139
|
export function harnessReasoningEfforts(harness) {
|
|
@@ -103,3 +156,40 @@ export function reasoningEffortsFor(harness, model) {
|
|
|
103
156
|
}
|
|
104
157
|
return efforts;
|
|
105
158
|
}
|
|
159
|
+
// ── Per-turn selector support (does the harness honor the chat pickers?) ──────
|
|
160
|
+
/**
|
|
161
|
+
* Harnesses whose runner DROPS a per-turn selector — grounded in the cli-bridge adapter audit, NOT a
|
|
162
|
+
* guess. Most harnesses honor both selectors, so only the exceptions are listed; a harness absent from
|
|
163
|
+
* a set honors that selector. Keyed by the BASE runner (aliases canonicalized).
|
|
164
|
+
*
|
|
165
|
+
* - model dropped: `amp` (own agent picks the model), `openclaw` (dispatcher routes by its own
|
|
166
|
+
* config), `nanoclaw` (socket-bridge runner is config/env-driven).
|
|
167
|
+
* - effort dropped: `amp` and `factory-droids`/`hermes`/`nanoclaw` (no thinking flag is plumbed to
|
|
168
|
+
* the underlying CLI).
|
|
169
|
+
*
|
|
170
|
+
* This is distinct from {@link reasoningEffortsFor} (which levels a harness can EXPRESS): a picker uses
|
|
171
|
+
* these to trim or mark harnesses up front, so a user's model/effort choice is never silently ignored.
|
|
172
|
+
*/
|
|
173
|
+
const harnessIgnoresModel = new Set([
|
|
174
|
+
"amp",
|
|
175
|
+
"openclaw",
|
|
176
|
+
"nanoclaw",
|
|
177
|
+
]);
|
|
178
|
+
const harnessIgnoresEffort = new Set([
|
|
179
|
+
"amp",
|
|
180
|
+
"factory-droids",
|
|
181
|
+
"hermes",
|
|
182
|
+
"nanoclaw",
|
|
183
|
+
]);
|
|
184
|
+
/** Whether the harness's runner honors a per-turn MODEL override (vs. picking the model itself). */
|
|
185
|
+
export function harnessHonorsModel(harness) {
|
|
186
|
+
return !harnessIgnoresModel.has(canonicalizeHarness(harness));
|
|
187
|
+
}
|
|
188
|
+
/** Whether the harness's runner honors a reasoning-EFFORT override (vs. dropping it). */
|
|
189
|
+
export function harnessHonorsEffort(harness) {
|
|
190
|
+
return !harnessIgnoresEffort.has(canonicalizeHarness(harness));
|
|
191
|
+
}
|
|
192
|
+
/** Whether the harness honors BOTH chat selectors — i.e. the model and effort pickers are live. */
|
|
193
|
+
export function harnessHonorsSelectors(harness) {
|
|
194
|
+
return harnessHonorsModel(harness) && harnessHonorsEffort(harness);
|
|
195
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tangle-network/agent-interface",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -36,11 +36,14 @@
|
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
38
|
"@types/node": "25.6.0",
|
|
39
|
-
"typescript": "^6.0.3"
|
|
39
|
+
"typescript": "^6.0.3",
|
|
40
|
+
"vitest": "^4.1.5"
|
|
40
41
|
},
|
|
41
42
|
"scripts": {
|
|
42
43
|
"build": "tsc -p tsconfig.json",
|
|
43
44
|
"check-types": "tsc --noEmit",
|
|
44
|
-
"clean": "rm -rf dist"
|
|
45
|
+
"clean": "rm -rf dist",
|
|
46
|
+
"test": "vitest run",
|
|
47
|
+
"test:watch": "vitest"
|
|
45
48
|
}
|
|
46
49
|
}
|