@botbotgo/agent-harness 0.0.8 → 0.0.10
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 +269 -0
- package/dist/config/orchestra.yaml +3 -5
- package/dist/contracts/types.d.ts +4 -1
- package/dist/extensions.js +5 -5
- package/dist/resource/builtins.d.ts +1 -0
- package/dist/resource/builtins.js +1 -0
- package/dist/resource/resource-impl.d.ts +30 -0
- package/dist/resource/resource-impl.js +250 -0
- package/dist/resource/resource.d.ts +1 -0
- package/dist/resource/resource.js +1 -0
- package/dist/{vendor → resource}/sources.d.ts +3 -0
- package/dist/{vendor → resource}/sources.js +53 -0
- package/dist/runtime/agent-runtime-adapter.js +3 -2
- package/dist/runtime/file-checkpoint-saver.js +17 -2
- package/dist/runtime/harness.js +3 -3
- package/dist/runtime/support/vector-stores.js +1 -1
- package/dist/workspace/compile.js +44 -19
- package/dist/workspace/object-loader.d.ts +5 -0
- package/dist/workspace/object-loader.js +180 -36
- package/dist/workspace/resource-compilers.js +1 -0
- package/dist/workspace/support/discovery.js +19 -14
- package/dist/workspace/support/source-collectors.js +2 -2
- package/package.json +27 -5
- package/dist/vendor/builtins.d.ts +0 -24
- package/dist/vendor/builtins.js +0 -118
package/README.md
ADDED
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
# @botbotgo/agent-harness
|
|
2
|
+
|
|
3
|
+
`@botbotgo/agent-harness` is a TypeScript framework for running local agent workspaces with declarative config, reusable tools, reusable skills, and configurable host-agent routing.
|
|
4
|
+
|
|
5
|
+
It is designed for two common use cases:
|
|
6
|
+
|
|
7
|
+
- build a reusable agent runtime package and publish it to npm
|
|
8
|
+
- build an application workspace that ships its own agents, tools, skills, and model config
|
|
9
|
+
|
|
10
|
+
The public API stays intentionally small:
|
|
11
|
+
|
|
12
|
+
- `createAgentHarness(...)`
|
|
13
|
+
- `run(...)`
|
|
14
|
+
- `subscribe(...)`
|
|
15
|
+
- `getThread(...)`
|
|
16
|
+
- `stop(...)`
|
|
17
|
+
|
|
18
|
+
## Product Overview
|
|
19
|
+
|
|
20
|
+
Agent Harness loads a workspace from disk, compiles its config into runnable agent bindings, and executes requests through either a lightweight LangChain v1 path or a DeepAgent path.
|
|
21
|
+
|
|
22
|
+
Out of the box, the framework supports:
|
|
23
|
+
|
|
24
|
+
- workspace loading from a directory root
|
|
25
|
+
- declarative `Model`, `EmbeddingModel`, `VectorStore`, `LangChainAgent`, `DeepAgent`, and `Runtime` objects
|
|
26
|
+
- host-agent routing between a direct path and an orchestration path
|
|
27
|
+
- tool loading from resource packages and external sources
|
|
28
|
+
- skill discovery from filesystem roots
|
|
29
|
+
- subagent discovery from filesystem roots
|
|
30
|
+
- thread persistence, run history, and resumable state
|
|
31
|
+
|
|
32
|
+
The default package config in this repo shows the intended model:
|
|
33
|
+
|
|
34
|
+
- `direct`: low-latency host for simple one-step requests
|
|
35
|
+
- `orchestra`: default host for multi-step work, tools, skills, and delegation
|
|
36
|
+
|
|
37
|
+
## Quick Start
|
|
38
|
+
|
|
39
|
+
Install the package:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
npm install @botbotgo/agent-harness
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Create a workspace with at least:
|
|
46
|
+
|
|
47
|
+
```text
|
|
48
|
+
your-workspace/
|
|
49
|
+
AGENTS.md
|
|
50
|
+
config/
|
|
51
|
+
model.yaml
|
|
52
|
+
runtime.yaml
|
|
53
|
+
direct.yaml
|
|
54
|
+
orchestra.yaml
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Minimal usage:
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
import { createAgentHarness, run, stop } from "@botbotgo/agent-harness";
|
|
61
|
+
|
|
62
|
+
const harness = await createAgentHarness("/absolute/path/to/your-workspace");
|
|
63
|
+
|
|
64
|
+
try {
|
|
65
|
+
const result = await run(harness, {
|
|
66
|
+
agentId: "direct",
|
|
67
|
+
input: "Summarize what this workspace is for.",
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
console.log(result.output);
|
|
71
|
+
} finally {
|
|
72
|
+
await stop(harness);
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
If you want the framework to choose the host agent automatically, pass `agentId: "auto"` when your workspace has runtime routing configured.
|
|
77
|
+
|
|
78
|
+
## How To Use
|
|
79
|
+
|
|
80
|
+
There are two common ways to use the framework.
|
|
81
|
+
|
|
82
|
+
### 1. Use It As A Library
|
|
83
|
+
|
|
84
|
+
Load a workspace from disk and run requests through the public API.
|
|
85
|
+
|
|
86
|
+
```ts
|
|
87
|
+
import { createAgentHarness, getThread, run, subscribe, stop } from "@botbotgo/agent-harness";
|
|
88
|
+
|
|
89
|
+
const harness = await createAgentHarness("/absolute/path/to/workspace");
|
|
90
|
+
|
|
91
|
+
const unsubscribe = subscribe(harness, (event) => {
|
|
92
|
+
console.log(event.eventType, event.payload);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
try {
|
|
96
|
+
const firstRun = await run(harness, {
|
|
97
|
+
agentId: "orchestra",
|
|
98
|
+
input: "Inspect the workspace and explain the available agents.",
|
|
99
|
+
listeners: {
|
|
100
|
+
onChunk(chunk) {
|
|
101
|
+
process.stdout.write(chunk);
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
const thread = await getThread(harness, firstRun.threadId);
|
|
107
|
+
console.log(thread?.messages.at(-1)?.content);
|
|
108
|
+
} finally {
|
|
109
|
+
unsubscribe();
|
|
110
|
+
await stop(harness);
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### 2. Use It Inside An App Workspace
|
|
115
|
+
|
|
116
|
+
The example app in [`examples/stock-research-app`](/Users/boqiang.liang/900-project/agent-harness3/examples/stock-research-app/README.md) is the reference shape for an application workspace. It keeps the framework package separate from app-specific agents, tools, and skills.
|
|
117
|
+
|
|
118
|
+
Run the example:
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
cd examples/stock-research-app
|
|
122
|
+
npm install
|
|
123
|
+
npm run start -- "Investigate NVDA and produce a balanced stock research brief."
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## How To Configure
|
|
127
|
+
|
|
128
|
+
Agent Harness is workspace-first. The runtime is assembled from files on disk rather than from a large constructor API.
|
|
129
|
+
|
|
130
|
+
### Core Files
|
|
131
|
+
|
|
132
|
+
- `AGENTS.md`: durable instructions and operating rules loaded into agent memory where configured
|
|
133
|
+
- `config/model.yaml`: default chat model
|
|
134
|
+
- `config/runtime.yaml`: workspace-wide runtime defaults such as `runRoot` and host routing
|
|
135
|
+
- `config/direct.yaml`: lightweight direct-response host agent
|
|
136
|
+
- `config/orchestra.yaml`: default orchestration host agent
|
|
137
|
+
- `config/embedding-model.yaml`: embeddings preset for retrieval flows
|
|
138
|
+
- `config/vector-store.yaml`: vector store preset for retrieval flows
|
|
139
|
+
|
|
140
|
+
### Minimal Model Config
|
|
141
|
+
|
|
142
|
+
```yaml
|
|
143
|
+
apiVersion: agent-harness/v1alpha1
|
|
144
|
+
kind: Model
|
|
145
|
+
metadata:
|
|
146
|
+
name: default
|
|
147
|
+
spec:
|
|
148
|
+
provider: ollama
|
|
149
|
+
model: gpt-oss:latest
|
|
150
|
+
init:
|
|
151
|
+
baseUrl: http://localhost:11434
|
|
152
|
+
temperature: 0.2
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### Runtime Routing
|
|
156
|
+
|
|
157
|
+
`config/runtime.yaml` controls shared runtime behavior. In this repo it defines:
|
|
158
|
+
|
|
159
|
+
- `runRoot`: where thread state, run artifacts, approvals, and indexes are stored
|
|
160
|
+
- `routing.systemPrompt`: how the harness chooses between the primary and secondary host agents when `agentId: "auto"` is used
|
|
161
|
+
|
|
162
|
+
### Agent Config
|
|
163
|
+
|
|
164
|
+
Agent objects are declarative YAML files. The package currently supports:
|
|
165
|
+
|
|
166
|
+
- `LangChainAgent`
|
|
167
|
+
- `DeepAgent`
|
|
168
|
+
|
|
169
|
+
Typical fields include:
|
|
170
|
+
|
|
171
|
+
- `metadata.name`
|
|
172
|
+
- `metadata.description`
|
|
173
|
+
- `spec.modelRef`
|
|
174
|
+
- `spec.systemPrompt`
|
|
175
|
+
- `spec.checkpointer`
|
|
176
|
+
- `spec.memory`
|
|
177
|
+
- `spec.store`
|
|
178
|
+
- `spec.backend`
|
|
179
|
+
|
|
180
|
+
Use `LangChainAgent` for a fast direct path. Use `DeepAgent` when you need richer orchestration, tool-heavy execution, memory backends, and delegation.
|
|
181
|
+
|
|
182
|
+
## How To Extend
|
|
183
|
+
|
|
184
|
+
The extension model is filesystem-based. You extend the harness by adding new config objects, new discovery roots, or new resource packages.
|
|
185
|
+
|
|
186
|
+
### Add More Agents
|
|
187
|
+
|
|
188
|
+
Subagents can be discovered from configured roots. The discovery layer supports:
|
|
189
|
+
|
|
190
|
+
- local filesystem paths
|
|
191
|
+
- external resource sources
|
|
192
|
+
- builtin discovery paths
|
|
193
|
+
|
|
194
|
+
The harness scans YAML files under the discovered agent roots and adds them to the workspace graph.
|
|
195
|
+
|
|
196
|
+
### Add Skills
|
|
197
|
+
|
|
198
|
+
Skills are discovered from roots that contain either:
|
|
199
|
+
|
|
200
|
+
- a direct `SKILL.md`
|
|
201
|
+
- child directories where each directory contains its own `SKILL.md`
|
|
202
|
+
|
|
203
|
+
A practical layout looks like this:
|
|
204
|
+
|
|
205
|
+
```text
|
|
206
|
+
your-workspace/
|
|
207
|
+
resources/
|
|
208
|
+
skills/
|
|
209
|
+
code-review/
|
|
210
|
+
SKILL.md
|
|
211
|
+
release-check/
|
|
212
|
+
SKILL.md
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### Add Tools
|
|
216
|
+
|
|
217
|
+
Tools can come from:
|
|
218
|
+
|
|
219
|
+
- resource packages
|
|
220
|
+
- external sources
|
|
221
|
+
- builtin resources
|
|
222
|
+
- declarative tool objects that bundle or reference other tools
|
|
223
|
+
|
|
224
|
+
The example application demonstrates a clean pattern: keep app-specific tools under `resources/tools/` and keep one tool per module.
|
|
225
|
+
|
|
226
|
+
### Add Retrieval
|
|
227
|
+
|
|
228
|
+
If your workspace needs RAG-style behavior:
|
|
229
|
+
|
|
230
|
+
1. add an `EmbeddingModel`
|
|
231
|
+
2. add a `VectorStore`
|
|
232
|
+
3. point retrieval-oriented tools at those refs
|
|
233
|
+
|
|
234
|
+
This repo already includes `config/embedding-model.yaml` and `config/vector-store.yaml` as the default pattern.
|
|
235
|
+
|
|
236
|
+
### Extend The Runtime In Code
|
|
237
|
+
|
|
238
|
+
The public API also accepts a prebuilt `WorkspaceBundle`, which lets you compile or inject workspace data yourself before creating the harness. That path is useful when you need tighter control in tests or in a higher-level product.
|
|
239
|
+
|
|
240
|
+
## Suggested Workspace Layout
|
|
241
|
+
|
|
242
|
+
```text
|
|
243
|
+
your-workspace/
|
|
244
|
+
AGENTS.md
|
|
245
|
+
config/
|
|
246
|
+
model.yaml
|
|
247
|
+
runtime.yaml
|
|
248
|
+
direct.yaml
|
|
249
|
+
orchestra.yaml
|
|
250
|
+
embedding-model.yaml
|
|
251
|
+
vector-store.yaml
|
|
252
|
+
resources/
|
|
253
|
+
agents/
|
|
254
|
+
skills/
|
|
255
|
+
tools/
|
|
256
|
+
.agent/
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
## Development
|
|
260
|
+
|
|
261
|
+
Build and test this package locally:
|
|
262
|
+
|
|
263
|
+
```bash
|
|
264
|
+
npm run build
|
|
265
|
+
npm run check
|
|
266
|
+
npm test
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
The example workspace under [`examples/stock-research-app`](/Users/boqiang.liang/900-project/agent-harness3/examples/stock-research-app/README.md) is the fastest way to understand how an app should package its own agents, tools, and skills around this framework.
|
|
@@ -39,7 +39,7 @@ spec:
|
|
|
39
39
|
path: store.json
|
|
40
40
|
# DeepAgents aligned feature: backend config passed into `createDeepAgent({ backend })`.
|
|
41
41
|
# This directly defines the backend topology for this agent:
|
|
42
|
-
# -
|
|
42
|
+
# - workspace execution uses a lightweight VFS sandbox
|
|
43
43
|
# - long-term memory under `/memories/*` uses `StoreBackend`
|
|
44
44
|
# - `CompositeBackend` composes those backend instances together
|
|
45
45
|
# The harness also injects a persistent file-backed store and a file-backed checkpointer so that
|
|
@@ -48,11 +48,9 @@ spec:
|
|
|
48
48
|
backend:
|
|
49
49
|
kind: CompositeBackend
|
|
50
50
|
state:
|
|
51
|
-
# Available state backend `kind` options today: `StateBackend`, `LocalShellBackend`.
|
|
52
|
-
kind:
|
|
53
|
-
inheritEnv: true
|
|
51
|
+
# Available state backend `kind` options today: `StateBackend`, `LocalShellBackend`, `VfsSandbox`.
|
|
52
|
+
kind: VfsSandbox
|
|
54
53
|
timeout: 600
|
|
55
|
-
maxOutputBytes: 200000
|
|
56
54
|
routes:
|
|
57
55
|
/memories/:
|
|
58
56
|
# Available route backend `kind` options today: `StoreBackend`.
|
|
@@ -57,6 +57,7 @@ export type ParsedToolObject = {
|
|
|
57
57
|
type: string;
|
|
58
58
|
name: string;
|
|
59
59
|
description: string;
|
|
60
|
+
implementationName?: string;
|
|
60
61
|
config?: Record<string, unknown>;
|
|
61
62
|
inputSchemaRef?: string;
|
|
62
63
|
backendOperation?: string;
|
|
@@ -158,7 +159,8 @@ export type CompiledAgentBinding = {
|
|
|
158
159
|
};
|
|
159
160
|
export type WorkspaceBundle = {
|
|
160
161
|
workspaceRoot: string;
|
|
161
|
-
|
|
162
|
+
resourceSources: string[];
|
|
163
|
+
builtinSources?: string[];
|
|
162
164
|
refs: Map<string, WorkspaceObject | ParsedAgentObject>;
|
|
163
165
|
models: Map<string, ParsedModelObject>;
|
|
164
166
|
embeddings: Map<string, ParsedEmbeddingModelObject>;
|
|
@@ -169,6 +171,7 @@ export type WorkspaceBundle = {
|
|
|
169
171
|
};
|
|
170
172
|
export type WorkspaceLoadOptions = {
|
|
171
173
|
overlayRoots?: string[];
|
|
174
|
+
resourceSources?: string[];
|
|
172
175
|
builtinSources?: string[];
|
|
173
176
|
};
|
|
174
177
|
export type ThreadSummary = {
|
package/dist/extensions.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
-
import {
|
|
3
|
-
import { isExternalSourceLocator,
|
|
2
|
+
import { defaultResourceSkillsRoot } from "./resource/resource.js";
|
|
3
|
+
import { isExternalSourceLocator, resolveExternalResourcePath } from "./resource/sources.js";
|
|
4
4
|
const toolKindAdapters = new Map();
|
|
5
5
|
const skillSourceResolvers = new Map();
|
|
6
6
|
const skillInheritancePolicies = new Map();
|
|
@@ -217,7 +217,7 @@ registerSkillSourceResolver({
|
|
|
217
217
|
}
|
|
218
218
|
const candidate = String(skill.sourcePathRef);
|
|
219
219
|
if (isExternalSourceLocator(candidate)) {
|
|
220
|
-
return [
|
|
220
|
+
return [resolveExternalResourcePath(candidate, workspaceRoot)];
|
|
221
221
|
}
|
|
222
222
|
return [candidate.startsWith("/") ? candidate : `${workspaceRoot}/${candidate}`];
|
|
223
223
|
},
|
|
@@ -225,7 +225,7 @@ registerSkillSourceResolver({
|
|
|
225
225
|
registerSkillSourceResolver({
|
|
226
226
|
kind: "builtin",
|
|
227
227
|
resolve({ skill }) {
|
|
228
|
-
return [path.join(
|
|
228
|
+
return [path.join(defaultResourceSkillsRoot(), skill.sourcePathRef)];
|
|
229
229
|
},
|
|
230
230
|
});
|
|
231
231
|
registerSkillInheritancePolicy({
|
|
@@ -244,7 +244,7 @@ registerSkillPackagingConvention({
|
|
|
244
244
|
resolve({ workspaceRoot, skill }) {
|
|
245
245
|
const candidate = String(skill.sourcePathRef);
|
|
246
246
|
if (isExternalSourceLocator(candidate)) {
|
|
247
|
-
return [
|
|
247
|
+
return [resolveExternalResourcePath(candidate, workspaceRoot)];
|
|
248
248
|
}
|
|
249
249
|
return [candidate.startsWith("/") ? candidate : `${workspaceRoot}/${candidate}`];
|
|
250
250
|
},
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { builtinConfigRoot, builtinSkillsRoot, createBuiltinBackendResolver, createBuiltinToolResolver, ensureBuiltinSources, listBuiltinTools, listBuiltinToolsForSource, resolveLocalBuiltinsEntry, } from "./resource.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { builtinConfigRoot, builtinSkillsRoot, createBuiltinBackendResolver, createBuiltinToolResolver, ensureBuiltinSources, listBuiltinTools, listBuiltinToolsForSource, resolveLocalBuiltinsEntry, } from "./resource.js";
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { RuntimeAdapterOptions, WorkspaceBundle } from "../contracts/types.js";
|
|
2
|
+
export declare function resolveLocalBuiltinsEntry(currentResourceDir: string, resolveInstalledEntry?: () => string | null): string;
|
|
3
|
+
export type ResourceToolInfo = {
|
|
4
|
+
toolPath: string;
|
|
5
|
+
backendOperation: string;
|
|
6
|
+
name: string;
|
|
7
|
+
description: string;
|
|
8
|
+
hitl?: {
|
|
9
|
+
enabled: boolean;
|
|
10
|
+
allow: Array<"approve" | "edit" | "reject">;
|
|
11
|
+
};
|
|
12
|
+
};
|
|
13
|
+
export declare function ensureResourceSources(sources?: string[], workspaceRoot?: string): Promise<void>;
|
|
14
|
+
export declare function defaultResourceSkillsRoot(): string;
|
|
15
|
+
export declare function defaultResourceConfigRoot(): string;
|
|
16
|
+
export declare function listResourceTools(sources?: string[], workspaceRoot?: string): Promise<ResourceToolInfo[]>;
|
|
17
|
+
export declare function listResourceToolsForSource(source: string, workspaceRoot?: string): Promise<ResourceToolInfo[]>;
|
|
18
|
+
export declare function createResourceBackendResolver(workspace: WorkspaceBundle): NonNullable<RuntimeAdapterOptions["backendResolver"]>;
|
|
19
|
+
export declare function createResourceToolResolver(workspace: WorkspaceBundle, options?: {
|
|
20
|
+
getStore?: (_binding?: WorkspaceBundle["bindings"] extends Map<any, infer T> ? T : never) => unknown;
|
|
21
|
+
getEmbeddingModel?: (embeddingModelRef?: string, _binding?: WorkspaceBundle["bindings"] extends Map<any, infer T> ? T : never) => Promise<unknown>;
|
|
22
|
+
getVectorStore?: (vectorStoreRef?: string, _binding?: WorkspaceBundle["bindings"] extends Map<any, infer T> ? T : never) => Promise<unknown>;
|
|
23
|
+
}): NonNullable<RuntimeAdapterOptions["toolResolver"]>;
|
|
24
|
+
export declare const ensureBuiltinSources: typeof ensureResourceSources;
|
|
25
|
+
export declare const builtinSkillsRoot: typeof defaultResourceSkillsRoot;
|
|
26
|
+
export declare const builtinConfigRoot: typeof defaultResourceConfigRoot;
|
|
27
|
+
export declare const listBuiltinTools: typeof listResourceTools;
|
|
28
|
+
export declare const listBuiltinToolsForSource: typeof listResourceToolsForSource;
|
|
29
|
+
export declare const createBuiltinBackendResolver: typeof createResourceBackendResolver;
|
|
30
|
+
export declare const createBuiltinToolResolver: typeof createResourceToolResolver;
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { stat } from "node:fs/promises";
|
|
5
|
+
import { readFile } from "node:fs/promises";
|
|
6
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
7
|
+
import { ensureExternalResourceSource, ensureExternalSource, isExternalSourceLocator, parseExternalSourceLocator } from "./sources.js";
|
|
8
|
+
const resourceDir = path.dirname(fileURLToPath(import.meta.url));
|
|
9
|
+
const require = createRequire(import.meta.url);
|
|
10
|
+
function installedBuiltinsEntry() {
|
|
11
|
+
try {
|
|
12
|
+
return require.resolve("@botbotgo/agent-harness-builtin");
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
export function resolveLocalBuiltinsEntry(currentResourceDir, resolveInstalledEntry = installedBuiltinsEntry) {
|
|
19
|
+
const candidates = [
|
|
20
|
+
path.resolve(currentResourceDir, "../../../builtins/dist/src/index.js"),
|
|
21
|
+
path.resolve(currentResourceDir, "../../../agent-harness/packages/builtins/dist/src/index.js"),
|
|
22
|
+
path.resolve(currentResourceDir, "../../../builtins/src/index.ts"),
|
|
23
|
+
path.resolve(currentResourceDir, "../../../agent-harness/packages/builtins/src/index.ts"),
|
|
24
|
+
resolveInstalledEntry(),
|
|
25
|
+
].filter((candidate) => typeof candidate === "string" && candidate.length > 0);
|
|
26
|
+
for (const candidate of candidates) {
|
|
27
|
+
if (existsSync(candidate)) {
|
|
28
|
+
return candidate;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return candidates.at(-1) ?? path.resolve(currentResourceDir, "../../../agent-harness/packages/builtins/dist/src/index.js");
|
|
32
|
+
}
|
|
33
|
+
const builtinsEntry = resolveLocalBuiltinsEntry(resourceDir);
|
|
34
|
+
async function loadLocalResource(entry) {
|
|
35
|
+
if (!existsSync(entry)) {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
const imported = await import(pathToFileURL(entry).href);
|
|
39
|
+
return (imported.default ?? imported);
|
|
40
|
+
}
|
|
41
|
+
const localResource = await loadLocalResource(builtinsEntry);
|
|
42
|
+
function listProviderTools(provider) {
|
|
43
|
+
const rawTools = provider?.listResourceTools?.() ?? provider?.listBuiltinTools?.() ?? [];
|
|
44
|
+
return rawTools.map((tool) => ({
|
|
45
|
+
...tool,
|
|
46
|
+
toolPath: tool.toolPath ?? tool.builtinPath ?? "",
|
|
47
|
+
}));
|
|
48
|
+
}
|
|
49
|
+
function createProviderToolResolver(provider, workspace, options) {
|
|
50
|
+
return (provider?.createResourceToolResolver?.(workspace, options) ??
|
|
51
|
+
provider?.createBuiltinToolResolver?.(workspace, options));
|
|
52
|
+
}
|
|
53
|
+
function createProviderBackendResolver(provider, workspace) {
|
|
54
|
+
return (provider?.createResourceBackendResolver?.(workspace) ??
|
|
55
|
+
provider?.createBuiltinBackendResolver?.(workspace));
|
|
56
|
+
}
|
|
57
|
+
function requireLocalResource(feature) {
|
|
58
|
+
if (localResource) {
|
|
59
|
+
return localResource;
|
|
60
|
+
}
|
|
61
|
+
throw new Error(`agent-harness optional tool bundle support is unavailable for ${feature}. Install the matching provider package or provide a local sibling checkout.`);
|
|
62
|
+
}
|
|
63
|
+
const remoteResourceCache = new Map();
|
|
64
|
+
async function findPackageRoot(startPath) {
|
|
65
|
+
let current = path.dirname(startPath);
|
|
66
|
+
for (;;) {
|
|
67
|
+
try {
|
|
68
|
+
await stat(path.join(current, "package.json"));
|
|
69
|
+
return current;
|
|
70
|
+
}
|
|
71
|
+
catch { }
|
|
72
|
+
const parent = path.dirname(current);
|
|
73
|
+
if (parent === current) {
|
|
74
|
+
return path.dirname(startPath);
|
|
75
|
+
}
|
|
76
|
+
current = parent;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
const functionToolModuleCache = new Map();
|
|
80
|
+
async function loadFunctionToolModule(tool) {
|
|
81
|
+
const cacheKey = `${tool.sourcePath}:${tool.implementationName ?? tool.id}`;
|
|
82
|
+
const cached = functionToolModuleCache.get(cacheKey);
|
|
83
|
+
if (cached) {
|
|
84
|
+
return cached;
|
|
85
|
+
}
|
|
86
|
+
const loading = (async () => {
|
|
87
|
+
const imported = await import(pathToFileURL(tool.sourcePath).href);
|
|
88
|
+
const implementationName = tool.implementationName ?? tool.id;
|
|
89
|
+
const invoke = imported[implementationName];
|
|
90
|
+
const schema = imported[`${implementationName}Schema`];
|
|
91
|
+
if (typeof invoke !== "function") {
|
|
92
|
+
throw new Error(`Tool module ${tool.sourcePath} must export function ${implementationName}.`);
|
|
93
|
+
}
|
|
94
|
+
if (!schema?.parse) {
|
|
95
|
+
throw new Error(`Tool module ${tool.sourcePath} must export ${implementationName}Schema as a Zod schema.`);
|
|
96
|
+
}
|
|
97
|
+
return { invoke, schema };
|
|
98
|
+
})();
|
|
99
|
+
functionToolModuleCache.set(cacheKey, loading);
|
|
100
|
+
return loading;
|
|
101
|
+
}
|
|
102
|
+
function createFunctionToolResolver(workspace) {
|
|
103
|
+
const functionTools = new Map(Array.from(workspace.tools.values())
|
|
104
|
+
.filter((tool) => tool.type === "function" && tool.sourcePath.endsWith(".mjs"))
|
|
105
|
+
.map((tool) => [tool.id, tool]));
|
|
106
|
+
return (toolIds) => toolIds.flatMap((toolId) => {
|
|
107
|
+
const tool = functionTools.get(toolId);
|
|
108
|
+
if (!tool) {
|
|
109
|
+
return [];
|
|
110
|
+
}
|
|
111
|
+
return [
|
|
112
|
+
{
|
|
113
|
+
name: tool.name,
|
|
114
|
+
description: tool.description,
|
|
115
|
+
async invoke(input) {
|
|
116
|
+
const loaded = await loadFunctionToolModule(tool);
|
|
117
|
+
const parsedInput = loaded.schema.parse(input ?? {});
|
|
118
|
+
const toolPackageRoot = await findPackageRoot(tool.sourcePath);
|
|
119
|
+
return loaded.invoke(parsedInput, {
|
|
120
|
+
appRoot: workspace.workspaceRoot,
|
|
121
|
+
toolId: tool.id,
|
|
122
|
+
toolPath: tool.sourcePath,
|
|
123
|
+
toolPackageRoot,
|
|
124
|
+
});
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
];
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
function resolvePackageEntry(packageRoot, pkg) {
|
|
131
|
+
const exportsField = pkg.exports;
|
|
132
|
+
if (typeof exportsField === "string") {
|
|
133
|
+
return path.resolve(packageRoot, exportsField);
|
|
134
|
+
}
|
|
135
|
+
if (exportsField && typeof exportsField === "object" && "." in exportsField) {
|
|
136
|
+
const rootExport = exportsField["."];
|
|
137
|
+
if (typeof rootExport === "string") {
|
|
138
|
+
return path.resolve(packageRoot, rootExport);
|
|
139
|
+
}
|
|
140
|
+
if (rootExport && typeof rootExport === "object") {
|
|
141
|
+
const importEntry = rootExport.import ?? rootExport.default;
|
|
142
|
+
if (typeof importEntry === "string") {
|
|
143
|
+
return path.resolve(packageRoot, importEntry);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
if (typeof pkg.module === "string") {
|
|
148
|
+
return path.resolve(packageRoot, pkg.module);
|
|
149
|
+
}
|
|
150
|
+
if (typeof pkg.main === "string") {
|
|
151
|
+
return path.resolve(packageRoot, pkg.main);
|
|
152
|
+
}
|
|
153
|
+
return path.resolve(packageRoot, "index.js");
|
|
154
|
+
}
|
|
155
|
+
async function loadRemoteResource(source, workspaceRoot) {
|
|
156
|
+
const cached = remoteResourceCache.get(source);
|
|
157
|
+
if (cached !== undefined) {
|
|
158
|
+
return cached;
|
|
159
|
+
}
|
|
160
|
+
if (!isExternalSourceLocator(source)) {
|
|
161
|
+
throw new Error(`Unsupported resource source ${source}. Use npm:, tgz:, or file:.`);
|
|
162
|
+
}
|
|
163
|
+
const parsed = parseExternalSourceLocator(source);
|
|
164
|
+
if (parsed.subpath) {
|
|
165
|
+
throw new Error(`Resource source ${source} must point at a package root, not a subpath`);
|
|
166
|
+
}
|
|
167
|
+
await ensureExternalResourceSource(source, workspaceRoot);
|
|
168
|
+
const packageRoot = await ensureExternalSource(source, workspaceRoot);
|
|
169
|
+
const pkg = JSON.parse(await readFile(path.join(packageRoot, "package.json"), "utf8"));
|
|
170
|
+
const entry = resolvePackageEntry(packageRoot, pkg);
|
|
171
|
+
if (!existsSync(entry)) {
|
|
172
|
+
remoteResourceCache.set(source, null);
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
const imported = await import(pathToFileURL(entry).href);
|
|
176
|
+
const provider = (imported.default ?? imported);
|
|
177
|
+
if (listProviderTools(provider).length === 0 &&
|
|
178
|
+
!provider.listResourceTools &&
|
|
179
|
+
!provider.listBuiltinTools) {
|
|
180
|
+
remoteResourceCache.set(source, null);
|
|
181
|
+
return null;
|
|
182
|
+
}
|
|
183
|
+
if (!provider.createResourceToolResolver && !provider.createBuiltinToolResolver) {
|
|
184
|
+
remoteResourceCache.set(source, null);
|
|
185
|
+
return null;
|
|
186
|
+
}
|
|
187
|
+
const typedProvider = provider;
|
|
188
|
+
remoteResourceCache.set(source, typedProvider);
|
|
189
|
+
return typedProvider;
|
|
190
|
+
}
|
|
191
|
+
export async function ensureResourceSources(sources = [], workspaceRoot = process.cwd()) {
|
|
192
|
+
await Promise.all(sources.map((source) => loadRemoteResource(source, workspaceRoot)));
|
|
193
|
+
}
|
|
194
|
+
export function defaultResourceSkillsRoot() {
|
|
195
|
+
const provider = requireLocalResource("default resource skill resolution");
|
|
196
|
+
return provider.defaultResourceSkillsRoot?.() ?? provider.builtinSkillsRoot?.() ?? "";
|
|
197
|
+
}
|
|
198
|
+
export function defaultResourceConfigRoot() {
|
|
199
|
+
const provider = requireLocalResource("default resource config resolution");
|
|
200
|
+
return provider.defaultResourceConfigRoot?.() ?? provider.builtinConfigRoot?.() ?? provider.builtinDefaultsRoot?.() ?? "";
|
|
201
|
+
}
|
|
202
|
+
export async function listResourceTools(sources = [], workspaceRoot = process.cwd()) {
|
|
203
|
+
await ensureResourceSources(sources, workspaceRoot);
|
|
204
|
+
const deduped = new Map();
|
|
205
|
+
for (const tool of listProviderTools(localResource)) {
|
|
206
|
+
deduped.set(tool.toolPath, tool);
|
|
207
|
+
}
|
|
208
|
+
for (const source of sources) {
|
|
209
|
+
for (const tool of listProviderTools(remoteResourceCache.get(source) ?? undefined)) {
|
|
210
|
+
deduped.set(tool.toolPath, tool);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
return Array.from(deduped.values());
|
|
214
|
+
}
|
|
215
|
+
export async function listResourceToolsForSource(source, workspaceRoot = process.cwd()) {
|
|
216
|
+
await ensureResourceSources([source], workspaceRoot);
|
|
217
|
+
return listProviderTools(remoteResourceCache.get(source) ?? undefined);
|
|
218
|
+
}
|
|
219
|
+
export function createResourceBackendResolver(workspace) {
|
|
220
|
+
const localResolver = createProviderBackendResolver(localResource, workspace);
|
|
221
|
+
return (binding) => localResolver?.(binding) ?? [];
|
|
222
|
+
}
|
|
223
|
+
export function createResourceToolResolver(workspace, options = {}) {
|
|
224
|
+
const functionResolver = createFunctionToolResolver(workspace);
|
|
225
|
+
const localResolver = createProviderToolResolver(localResource, workspace, options);
|
|
226
|
+
const remoteResolvers = (workspace.resourceSources ?? workspace.builtinSources ?? [])
|
|
227
|
+
.map((source) => remoteResourceCache.get(source))
|
|
228
|
+
.filter((provider) => Boolean(provider))
|
|
229
|
+
.map((provider) => createProviderToolResolver(provider, workspace, options))
|
|
230
|
+
.filter((resolver) => Boolean(resolver));
|
|
231
|
+
return (toolIds, binding) => {
|
|
232
|
+
const resolved = [
|
|
233
|
+
...functionResolver(toolIds, binding),
|
|
234
|
+
...(localResolver?.(toolIds, binding) ?? []),
|
|
235
|
+
...remoteResolvers.flatMap((resolver) => resolver(toolIds, binding)),
|
|
236
|
+
];
|
|
237
|
+
const deduped = new Map();
|
|
238
|
+
for (const tool of resolved) {
|
|
239
|
+
deduped.set(String(tool.name), tool);
|
|
240
|
+
}
|
|
241
|
+
return Array.from(deduped.values());
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
export const ensureBuiltinSources = ensureResourceSources;
|
|
245
|
+
export const builtinSkillsRoot = defaultResourceSkillsRoot;
|
|
246
|
+
export const builtinConfigRoot = defaultResourceConfigRoot;
|
|
247
|
+
export const listBuiltinTools = listResourceTools;
|
|
248
|
+
export const listBuiltinToolsForSource = listResourceToolsForSource;
|
|
249
|
+
export const createBuiltinBackendResolver = createResourceBackendResolver;
|
|
250
|
+
export const createBuiltinToolResolver = createResourceToolResolver;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { type ResourceToolInfo, ensureResourceSources, defaultResourceSkillsRoot, defaultResourceConfigRoot, listResourceTools, listResourceToolsForSource, createResourceBackendResolver, createResourceToolResolver, ensureBuiltinSources, builtinSkillsRoot, builtinConfigRoot, listBuiltinTools, listBuiltinToolsForSource, createBuiltinBackendResolver, createBuiltinToolResolver, resolveLocalBuiltinsEntry, } from "./resource-impl.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { ensureResourceSources, defaultResourceSkillsRoot, defaultResourceConfigRoot, listResourceTools, listResourceToolsForSource, createResourceBackendResolver, createResourceToolResolver, ensureBuiltinSources, builtinSkillsRoot, builtinConfigRoot, listBuiltinTools, listBuiltinToolsForSource, createBuiltinBackendResolver, createBuiltinToolResolver, resolveLocalBuiltinsEntry, } from "./resource-impl.js";
|
|
@@ -3,10 +3,13 @@ type ParsedLocator = {
|
|
|
3
3
|
spec: string;
|
|
4
4
|
subpath?: string;
|
|
5
5
|
};
|
|
6
|
+
export declare function resolveResourcePackageRoot(root: string): string | null;
|
|
6
7
|
export declare function isExternalSourceLocator(value: unknown): boolean;
|
|
7
8
|
export declare function parseExternalSourceLocator(locator: string): ParsedLocator;
|
|
8
9
|
export declare function ensureExternalSource(locator: string, workspaceRoot: string): Promise<string>;
|
|
10
|
+
export declare function ensureExternalResourceSource(locator: string, workspaceRoot: string): Promise<string>;
|
|
9
11
|
export declare function ensureExternalSources(locators: string[], workspaceRoot: string): Promise<void>;
|
|
10
12
|
export declare function resolveExternalSourcePath(locator: string, workspaceRoot: string): string;
|
|
13
|
+
export declare function resolveExternalResourcePath(locator: string, workspaceRoot: string): string;
|
|
11
14
|
export declare function isDirectoryPath(candidate: string): boolean;
|
|
12
15
|
export {};
|