@tangle-network/agent-interface 0.13.0 → 0.15.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 +37 -1
- package/dist/environment-provider.d.ts +190 -0
- package/dist/environment-provider.js +1 -0
- package/dist/harness-capabilities.d.ts +14 -0
- package/dist/harness-capabilities.js +56 -3
- package/dist/index.d.ts +1 -0
- package/package.json +11 -3
package/README.md
CHANGED
|
@@ -16,7 +16,13 @@ pnpm add @tangle-network/agent-interface
|
|
|
16
16
|
## Usage
|
|
17
17
|
|
|
18
18
|
```ts
|
|
19
|
-
import type {
|
|
19
|
+
import type {
|
|
20
|
+
AgentEnvironmentProvider,
|
|
21
|
+
} from "@tangle-network/agent-interface/environment-provider";
|
|
22
|
+
import type {
|
|
23
|
+
BackendCapabilities,
|
|
24
|
+
ProviderCapabilities,
|
|
25
|
+
} from "@tangle-network/agent-interface";
|
|
20
26
|
|
|
21
27
|
const caps: ProviderCapabilities = {
|
|
22
28
|
supportsVision: true,
|
|
@@ -24,6 +30,36 @@ const caps: ProviderCapabilities = {
|
|
|
24
30
|
supportsToolCalls: true,
|
|
25
31
|
supportsComputerUse: false,
|
|
26
32
|
};
|
|
33
|
+
|
|
34
|
+
const provider: AgentEnvironmentProvider = {
|
|
35
|
+
name: "example",
|
|
36
|
+
capabilities: () => ({
|
|
37
|
+
profile: {
|
|
38
|
+
namedProfiles: false,
|
|
39
|
+
systemPrompt: true,
|
|
40
|
+
instructions: true,
|
|
41
|
+
tools: true,
|
|
42
|
+
permissions: true,
|
|
43
|
+
mcp: true,
|
|
44
|
+
subagents: false,
|
|
45
|
+
resources: { files: true, instructions: true, tools: true },
|
|
46
|
+
hooks: false,
|
|
47
|
+
modes: false,
|
|
48
|
+
runtimeUpdate: false,
|
|
49
|
+
validation: true,
|
|
50
|
+
},
|
|
51
|
+
streaming: { live: true, replay: false, detach: false, turnIdempotency: false },
|
|
52
|
+
sessions: { continue: false, list: false, messages: false },
|
|
53
|
+
workspace: { read: true, write: true, exec: true, git: false, upload: false, download: false },
|
|
54
|
+
branching: { checkpoint: false, fork: false },
|
|
55
|
+
placement: false,
|
|
56
|
+
usage: true,
|
|
57
|
+
confidential: false,
|
|
58
|
+
}),
|
|
59
|
+
create: async () => {
|
|
60
|
+
throw new Error("implement provider create()");
|
|
61
|
+
},
|
|
62
|
+
};
|
|
27
63
|
```
|
|
28
64
|
|
|
29
65
|
## License
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import type { AgentProfile, AgentProfileCapabilities, AgentProfileValidationResult } from "./agent-profile.js";
|
|
2
|
+
import type { InputPart, StreamEvent, TokenUsage } from "./index.js";
|
|
3
|
+
/** Portable profile reference: inline profile or provider catalog id. */
|
|
4
|
+
export type AgentProfileRef = AgentProfile | string;
|
|
5
|
+
export type AgentEnvironmentStatus = "pending" | "provisioning" | "running" | "stopped" | "failed" | "expired" | "unknown";
|
|
6
|
+
export type AgentSessionStatus = AgentEnvironmentStatus | "completed" | "cancelled";
|
|
7
|
+
export interface WorkspaceRequest {
|
|
8
|
+
/** Provider-specific environment/template id, for example "universal". */
|
|
9
|
+
environment?: string;
|
|
10
|
+
/** Container image or image alias when the provider supports image-backed workspaces. */
|
|
11
|
+
image?: string;
|
|
12
|
+
/** Repository to clone or mount before the agent runs. */
|
|
13
|
+
repoUrl?: string;
|
|
14
|
+
/** Git ref for {@link repoUrl}. */
|
|
15
|
+
gitRef?: string;
|
|
16
|
+
/** Initial working directory inside the environment. */
|
|
17
|
+
cwd?: string;
|
|
18
|
+
/** Opaque provider-native workspace fields. */
|
|
19
|
+
providerOptions?: Record<string, unknown>;
|
|
20
|
+
}
|
|
21
|
+
export interface ResourceRequest {
|
|
22
|
+
cpu?: number;
|
|
23
|
+
memoryMb?: number;
|
|
24
|
+
diskMb?: number;
|
|
25
|
+
gpu?: string;
|
|
26
|
+
providerOptions?: Record<string, unknown>;
|
|
27
|
+
}
|
|
28
|
+
export interface AgentEnvironmentQuery {
|
|
29
|
+
name?: string;
|
|
30
|
+
metadata?: Record<string, unknown>;
|
|
31
|
+
providerOptions?: Record<string, unknown>;
|
|
32
|
+
}
|
|
33
|
+
export interface AgentEnvironmentSummary {
|
|
34
|
+
id: string;
|
|
35
|
+
provider: string;
|
|
36
|
+
name?: string;
|
|
37
|
+
status?: AgentEnvironmentStatus;
|
|
38
|
+
metadata?: Record<string, unknown>;
|
|
39
|
+
}
|
|
40
|
+
export interface ExecRequest {
|
|
41
|
+
cwd?: string;
|
|
42
|
+
env?: Record<string, string>;
|
|
43
|
+
timeoutMs?: number;
|
|
44
|
+
signal?: AbortSignal;
|
|
45
|
+
}
|
|
46
|
+
export interface ExecResult {
|
|
47
|
+
exitCode: number;
|
|
48
|
+
stdout: string;
|
|
49
|
+
stderr: string;
|
|
50
|
+
}
|
|
51
|
+
export interface CheckpointRequest {
|
|
52
|
+
name?: string;
|
|
53
|
+
metadata?: Record<string, unknown>;
|
|
54
|
+
}
|
|
55
|
+
export interface CheckpointRef {
|
|
56
|
+
id: string;
|
|
57
|
+
provider?: string;
|
|
58
|
+
metadata?: Record<string, unknown>;
|
|
59
|
+
}
|
|
60
|
+
export interface ForkRequest {
|
|
61
|
+
name?: string;
|
|
62
|
+
metadata?: Record<string, unknown>;
|
|
63
|
+
}
|
|
64
|
+
export interface PlacementInfo {
|
|
65
|
+
kind: "local" | "sandbox" | "fleet" | "provider";
|
|
66
|
+
sandboxId?: string;
|
|
67
|
+
fleetId?: string;
|
|
68
|
+
machineId?: string;
|
|
69
|
+
region?: string;
|
|
70
|
+
providerMetadata?: Record<string, unknown>;
|
|
71
|
+
}
|
|
72
|
+
export interface AgentTurnInput {
|
|
73
|
+
prompt?: string;
|
|
74
|
+
parts?: InputPart[];
|
|
75
|
+
sessionId?: string;
|
|
76
|
+
model?: string;
|
|
77
|
+
timeoutMs?: number;
|
|
78
|
+
executionId?: string;
|
|
79
|
+
lastEventId?: string;
|
|
80
|
+
turnId?: string;
|
|
81
|
+
detach?: boolean;
|
|
82
|
+
context?: Record<string, unknown>;
|
|
83
|
+
signal?: AbortSignal;
|
|
84
|
+
providerOptions?: Record<string, unknown>;
|
|
85
|
+
}
|
|
86
|
+
export interface AgentTurnResult {
|
|
87
|
+
text: string;
|
|
88
|
+
success: boolean;
|
|
89
|
+
error?: string;
|
|
90
|
+
sessionId?: string;
|
|
91
|
+
usage?: TokenUsage;
|
|
92
|
+
metadata?: Record<string, unknown>;
|
|
93
|
+
events?: AgentEnvironmentEvent[];
|
|
94
|
+
}
|
|
95
|
+
export interface AgentSessionRef {
|
|
96
|
+
id: string;
|
|
97
|
+
provider?: string;
|
|
98
|
+
metadata?: Record<string, unknown>;
|
|
99
|
+
}
|
|
100
|
+
export interface AgentEnvironmentEvent {
|
|
101
|
+
type: string;
|
|
102
|
+
data: Record<string, unknown>;
|
|
103
|
+
id?: string;
|
|
104
|
+
normalized?: StreamEvent;
|
|
105
|
+
usage?: TokenUsage;
|
|
106
|
+
providerEvent?: unknown;
|
|
107
|
+
}
|
|
108
|
+
export interface AgentSession {
|
|
109
|
+
readonly id: string;
|
|
110
|
+
status(): Promise<AgentSessionStatus | null>;
|
|
111
|
+
events(options?: {
|
|
112
|
+
since?: string;
|
|
113
|
+
signal?: AbortSignal;
|
|
114
|
+
}): AsyncIterable<AgentEnvironmentEvent>;
|
|
115
|
+
result(): Promise<AgentTurnResult>;
|
|
116
|
+
prompt(input: AgentTurnInput): Promise<AgentTurnResult>;
|
|
117
|
+
cancel(): Promise<void>;
|
|
118
|
+
}
|
|
119
|
+
export interface AgentEnvironment {
|
|
120
|
+
readonly id: string;
|
|
121
|
+
readonly provider: string;
|
|
122
|
+
readonly name?: string;
|
|
123
|
+
status(): Promise<AgentEnvironmentStatus>;
|
|
124
|
+
stream(input: AgentTurnInput): AsyncIterable<AgentEnvironmentEvent>;
|
|
125
|
+
dispatch?(input: AgentTurnInput): Promise<AgentSessionRef>;
|
|
126
|
+
session?(id: string): AgentSession;
|
|
127
|
+
read?(path: string, options?: {
|
|
128
|
+
sessionId?: string;
|
|
129
|
+
}): Promise<string>;
|
|
130
|
+
write?(path: string, content: string, options?: {
|
|
131
|
+
sessionId?: string;
|
|
132
|
+
}): Promise<void>;
|
|
133
|
+
exec?(command: string, options?: ExecRequest): Promise<ExecResult>;
|
|
134
|
+
checkpoint?(options?: CheckpointRequest): Promise<CheckpointRef>;
|
|
135
|
+
fork?(checkpoint: CheckpointRef, options?: ForkRequest): Promise<AgentEnvironment>;
|
|
136
|
+
placement?(): Promise<PlacementInfo>;
|
|
137
|
+
refresh?(): Promise<void>;
|
|
138
|
+
destroy?(): Promise<void>;
|
|
139
|
+
}
|
|
140
|
+
export interface AgentEnvironmentCapabilities {
|
|
141
|
+
profile: AgentProfileCapabilities;
|
|
142
|
+
streaming: {
|
|
143
|
+
live: boolean;
|
|
144
|
+
replay: boolean;
|
|
145
|
+
detach: boolean;
|
|
146
|
+
turnIdempotency: boolean;
|
|
147
|
+
};
|
|
148
|
+
sessions: {
|
|
149
|
+
continue: boolean;
|
|
150
|
+
list: boolean;
|
|
151
|
+
messages: boolean;
|
|
152
|
+
};
|
|
153
|
+
workspace: {
|
|
154
|
+
read: boolean;
|
|
155
|
+
write: boolean;
|
|
156
|
+
exec: boolean;
|
|
157
|
+
git: boolean;
|
|
158
|
+
upload: boolean;
|
|
159
|
+
download: boolean;
|
|
160
|
+
};
|
|
161
|
+
branching: {
|
|
162
|
+
checkpoint: boolean;
|
|
163
|
+
fork: boolean;
|
|
164
|
+
};
|
|
165
|
+
placement: boolean;
|
|
166
|
+
usage: boolean;
|
|
167
|
+
confidential: boolean;
|
|
168
|
+
}
|
|
169
|
+
export interface CreateAgentEnvironmentInput {
|
|
170
|
+
profile: AgentProfileRef;
|
|
171
|
+
/** Agent backend inside the provider, for example "opencode" or "codex". */
|
|
172
|
+
backend?: string;
|
|
173
|
+
workspace?: WorkspaceRequest;
|
|
174
|
+
resources?: ResourceRequest;
|
|
175
|
+
env?: Record<string, string>;
|
|
176
|
+
secrets?: string[] | Record<string, string>;
|
|
177
|
+
metadata?: Record<string, unknown>;
|
|
178
|
+
name?: string;
|
|
179
|
+
idempotencyKey?: string;
|
|
180
|
+
signal?: AbortSignal;
|
|
181
|
+
providerOptions?: Record<string, unknown>;
|
|
182
|
+
}
|
|
183
|
+
export interface AgentEnvironmentProvider {
|
|
184
|
+
readonly name: string;
|
|
185
|
+
capabilities(): AgentEnvironmentCapabilities | Promise<AgentEnvironmentCapabilities>;
|
|
186
|
+
validateProfile?(profile: AgentProfileRef): AgentProfileValidationResult | Promise<AgentProfileValidationResult>;
|
|
187
|
+
create(input: CreateAgentEnvironmentInput): Promise<AgentEnvironment>;
|
|
188
|
+
get?(id: string): Promise<AgentEnvironment | null>;
|
|
189
|
+
list?(query?: AgentEnvironmentQuery): Promise<AgentEnvironmentSummary[]>;
|
|
190
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -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). */
|
|
@@ -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) {
|
package/dist/index.d.ts
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* This package defines the contract between the sidecar and provider implementations.
|
|
6
6
|
*/
|
|
7
7
|
import type { InteractionRequest, InteractionResponse } from "./interaction.js";
|
|
8
|
+
export type * from "./environment-provider.js";
|
|
8
9
|
export type BackendCapabilities = {
|
|
9
10
|
streaming: boolean;
|
|
10
11
|
toolUse: boolean;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tangle-network/agent-interface",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -10,6 +10,11 @@
|
|
|
10
10
|
"import": "./dist/index.js",
|
|
11
11
|
"types": "./dist/index.d.ts",
|
|
12
12
|
"default": "./dist/index.js"
|
|
13
|
+
},
|
|
14
|
+
"./environment-provider": {
|
|
15
|
+
"import": "./dist/environment-provider.js",
|
|
16
|
+
"types": "./dist/environment-provider.d.ts",
|
|
17
|
+
"default": "./dist/environment-provider.js"
|
|
13
18
|
}
|
|
14
19
|
},
|
|
15
20
|
"repository": {
|
|
@@ -31,11 +36,14 @@
|
|
|
31
36
|
},
|
|
32
37
|
"devDependencies": {
|
|
33
38
|
"@types/node": "25.6.0",
|
|
34
|
-
"typescript": "^6.0.3"
|
|
39
|
+
"typescript": "^6.0.3",
|
|
40
|
+
"vitest": "^4.1.5"
|
|
35
41
|
},
|
|
36
42
|
"scripts": {
|
|
37
43
|
"build": "tsc -p tsconfig.json",
|
|
38
44
|
"check-types": "tsc --noEmit",
|
|
39
|
-
"clean": "rm -rf dist"
|
|
45
|
+
"clean": "rm -rf dist",
|
|
46
|
+
"test": "vitest run",
|
|
47
|
+
"test:watch": "vitest"
|
|
40
48
|
}
|
|
41
49
|
}
|