@treeseed/sdk 0.1.1
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 +565 -0
- package/dist/cli-tools.js +44 -0
- package/dist/content-store.js +237 -0
- package/dist/d1-store.js +549 -0
- package/dist/frontmatter.js +33 -0
- package/dist/git-runtime.js +67 -0
- package/dist/index.js +12 -0
- package/dist/model-registry.js +164 -0
- package/dist/runtime.js +36 -0
- package/dist/scripts/.ts-run-1775616845195-odh4xzphk3l.js +22 -0
- package/dist/scripts/.ts-run-1775616848931-9386s6kwrl.js +126 -0
- package/dist/scripts/assert-release-tag-version.d.ts +1 -0
- package/dist/scripts/assert-release-tag-version.js +23 -0
- package/dist/scripts/build-dist.d.ts +1 -0
- package/dist/scripts/build-dist.js +114 -0
- package/dist/scripts/package-tools.d.ts +15 -0
- package/dist/scripts/package-tools.js +76 -0
- package/dist/scripts/publish-package.d.ts +1 -0
- package/dist/scripts/publish-package.js +20 -0
- package/dist/scripts/release-verify.d.ts +1 -0
- package/dist/scripts/release-verify.js +49 -0
- package/dist/scripts/run-ts.js +45 -0
- package/dist/scripts/test-smoke.d.ts +1 -0
- package/dist/scripts/test-smoke.js +77 -0
- package/dist/sdk-filters.js +77 -0
- package/dist/sdk-types.js +24 -0
- package/dist/sdk.js +232 -0
- package/dist/src/cli-tools.d.ts +3 -0
- package/dist/src/content-store.d.ts +24 -0
- package/dist/src/d1-store.d.ts +108 -0
- package/dist/src/frontmatter.d.ts +6 -0
- package/dist/src/git-runtime.d.ts +16 -0
- package/dist/src/index.d.ts +6 -0
- package/dist/src/model-registry.d.ts +4 -0
- package/dist/src/runtime.d.ts +1 -0
- package/dist/src/sdk-filters.d.ts +4 -0
- package/dist/src/sdk-types.d.ts +285 -0
- package/dist/src/sdk.d.ts +109 -0
- package/dist/src/stores/cursor-store.d.ts +10 -0
- package/dist/src/stores/envelopes.d.ts +116 -0
- package/dist/src/stores/helpers.d.ts +12 -0
- package/dist/src/stores/lease-store.d.ts +18 -0
- package/dist/src/stores/message-store.d.ts +12 -0
- package/dist/src/stores/run-store.d.ts +10 -0
- package/dist/src/stores/subscription-store.d.ts +9 -0
- package/dist/src/types/agents.d.ts +100 -0
- package/dist/src/types/cloudflare.d.ts +32 -0
- package/dist/src/wrangler-d1.d.ts +25 -0
- package/dist/stores/cursor-store.js +158 -0
- package/dist/stores/envelopes.js +219 -0
- package/dist/stores/helpers.js +42 -0
- package/dist/stores/lease-store.js +183 -0
- package/dist/stores/message-store.js +249 -0
- package/dist/stores/run-store.js +166 -0
- package/dist/stores/subscription-store.js +171 -0
- package/dist/test/test-fixture.d.ts +1 -0
- package/dist/test/utils/envelopes.test.d.ts +1 -0
- package/dist/test/utils/sdk.test.d.ts +1 -0
- package/dist/types/agents.js +40 -0
- package/dist/types/cloudflare.js +0 -0
- package/dist/vitest.config.d.ts +2 -0
- package/dist/wrangler-d1.js +84 -0
- package/package.json +130 -0
package/README.md
ADDED
|
@@ -0,0 +1,565 @@
|
|
|
1
|
+
# `@treeseed/sdk`
|
|
2
|
+
|
|
3
|
+
`@treeseed/sdk` is the standalone TreeSeed SDK for content-backed and D1-backed object models.
|
|
4
|
+
|
|
5
|
+
It exposes the public model and storage surface used by TreeSeed agents and supporting tooling:
|
|
6
|
+
|
|
7
|
+
- content-backed access for pages, notes, questions, objectives, people, agents, books, and knowledge
|
|
8
|
+
- D1-backed access for subscriptions, messages, agent runs, cursors, and content leases
|
|
9
|
+
- stable query and mutation APIs for `get`, `read`, `search`, `follow`, `pick`, `create`, and `update`
|
|
10
|
+
|
|
11
|
+
## Consumer Contract
|
|
12
|
+
|
|
13
|
+
- Node `>=20`
|
|
14
|
+
- ESM package
|
|
15
|
+
- install from npm as a normal package dependency
|
|
16
|
+
- import from the package root or documented subpath exports
|
|
17
|
+
|
|
18
|
+
Install:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm install @treeseed/sdk
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Example:
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
import { AgentSdk } from '@treeseed/sdk';
|
|
28
|
+
|
|
29
|
+
const sdk = new AgentSdk();
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Using The SDK In Applications
|
|
33
|
+
|
|
34
|
+
`AgentSdk` is the main application entrypoint. It routes each request to either the content-backed store or the D1-backed store based on the model you ask for, and it always returns a JSON envelope with:
|
|
35
|
+
|
|
36
|
+
- `ok`
|
|
37
|
+
- `model`
|
|
38
|
+
- `operation`
|
|
39
|
+
- `payload`
|
|
40
|
+
- optional `meta`
|
|
41
|
+
|
|
42
|
+
For most application code, the working pattern is:
|
|
43
|
+
|
|
44
|
+
1. create one SDK instance for your process, request handler, worker, or job
|
|
45
|
+
2. call `get`, `read`, `search`, `follow`, `pick`, `create`, or `update`
|
|
46
|
+
3. read the typed `payload` from the returned envelope
|
|
47
|
+
|
|
48
|
+
### Initialize An SDK Instance
|
|
49
|
+
|
|
50
|
+
Use the default constructor when you want in-memory D1 behavior and a content root resolved from your environment or working directory:
|
|
51
|
+
|
|
52
|
+
```ts
|
|
53
|
+
import { AgentSdk } from '@treeseed/sdk';
|
|
54
|
+
|
|
55
|
+
const sdk = new AgentSdk({
|
|
56
|
+
repoRoot: '/absolute/path/to/your-site',
|
|
57
|
+
});
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Use `createLocal()` when you want a local Wrangler-backed D1 database:
|
|
61
|
+
|
|
62
|
+
```ts
|
|
63
|
+
import { AgentSdk } from '@treeseed/sdk';
|
|
64
|
+
|
|
65
|
+
const sdk = AgentSdk.createLocal({
|
|
66
|
+
repoRoot: '/absolute/path/to/your-site',
|
|
67
|
+
databaseName: 'treeseed-local',
|
|
68
|
+
persistTo: '.wrangler/state/v3/d1',
|
|
69
|
+
});
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Use `MemoryAgentDatabase` explicitly in tests or scripts when you want a fully in-memory setup:
|
|
73
|
+
|
|
74
|
+
```ts
|
|
75
|
+
import { AgentSdk } from '@treeseed/sdk';
|
|
76
|
+
import { MemoryAgentDatabase } from '@treeseed/sdk/d1-store';
|
|
77
|
+
|
|
78
|
+
const sdk = new AgentSdk({
|
|
79
|
+
repoRoot: '/absolute/path/to/your-site',
|
|
80
|
+
database: new MemoryAgentDatabase(),
|
|
81
|
+
});
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Read A Single Record
|
|
85
|
+
|
|
86
|
+
Use `get()` when you want one record by `id`, `slug`, or `key`.
|
|
87
|
+
|
|
88
|
+
```ts
|
|
89
|
+
const response = await sdk.get({
|
|
90
|
+
model: 'knowledge',
|
|
91
|
+
slug: 'guides/getting-started/1-introduction',
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
if (response.payload) {
|
|
95
|
+
console.log(response.payload.title);
|
|
96
|
+
console.log(response.payload.body);
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Use `read()` when you want the same lookup behavior but want the returned envelope to say `operation: 'read'`.
|
|
101
|
+
|
|
102
|
+
```ts
|
|
103
|
+
const response = await sdk.read({
|
|
104
|
+
model: 'page',
|
|
105
|
+
slug: 'getting-started',
|
|
106
|
+
});
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Search Across A Model
|
|
110
|
+
|
|
111
|
+
Use `search()` to list and filter records from a model.
|
|
112
|
+
|
|
113
|
+
```ts
|
|
114
|
+
const response = await sdk.search({
|
|
115
|
+
model: 'knowledge',
|
|
116
|
+
filters: [
|
|
117
|
+
{ field: 'title', op: 'contains', value: 'TreeSeed' },
|
|
118
|
+
{ field: 'tags', op: 'contains', value: 'onboarding' },
|
|
119
|
+
],
|
|
120
|
+
sort: [{ field: 'updated', direction: 'desc' }],
|
|
121
|
+
limit: 10,
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
console.log(response.meta?.count);
|
|
125
|
+
console.log(response.payload.map((item) => item.slug));
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
`search()` is the main method for application reads such as:
|
|
129
|
+
|
|
130
|
+
- listing recent notes
|
|
131
|
+
- finding objectives by status
|
|
132
|
+
- finding people by role or affiliation
|
|
133
|
+
- finding queued D1 messages by type or status
|
|
134
|
+
|
|
135
|
+
### Follow Changes Since A Timestamp
|
|
136
|
+
|
|
137
|
+
Use `follow()` when your application wants records changed since a known point in time.
|
|
138
|
+
|
|
139
|
+
```ts
|
|
140
|
+
const response = await sdk.follow({
|
|
141
|
+
model: 'knowledge',
|
|
142
|
+
since: '2026-04-07T00:00:00.000Z',
|
|
143
|
+
filters: [{ field: 'tags', op: 'contains', value: 'treeseed' }],
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
for (const item of response.payload.items) {
|
|
147
|
+
console.log(item.slug);
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
The payload shape is:
|
|
152
|
+
|
|
153
|
+
```ts
|
|
154
|
+
{
|
|
155
|
+
items: [...],
|
|
156
|
+
since: '...'
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### Pick Work Items
|
|
161
|
+
|
|
162
|
+
Use `pick()` when you want the SDK to choose one item from a model for a worker.
|
|
163
|
+
|
|
164
|
+
For content-backed models, `pick()` tries to claim a content lease and returns both the selected item and a `leaseToken` when a claim succeeds.
|
|
165
|
+
|
|
166
|
+
```ts
|
|
167
|
+
const response = await sdk.pick({
|
|
168
|
+
model: 'objective',
|
|
169
|
+
workerId: 'planner-1',
|
|
170
|
+
leaseSeconds: 300,
|
|
171
|
+
filters: [{ field: 'status', op: 'eq', value: 'in progress' }],
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
if (response.payload.item) {
|
|
175
|
+
console.log(response.payload.item.slug);
|
|
176
|
+
console.log(response.payload.leaseToken);
|
|
177
|
+
}
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
For `message`, `pick()` routes to queue claiming behavior in the D1 layer.
|
|
181
|
+
|
|
182
|
+
### Create Content Or D1 Records
|
|
183
|
+
|
|
184
|
+
Use `create()` for models that support creation.
|
|
185
|
+
|
|
186
|
+
For content-backed models, pass frontmatter-like fields in `data`. The SDK writes the document and returns the created item plus git metadata.
|
|
187
|
+
|
|
188
|
+
```ts
|
|
189
|
+
const response = await sdk.create({
|
|
190
|
+
model: 'note',
|
|
191
|
+
actor: 'app-server',
|
|
192
|
+
data: {
|
|
193
|
+
slug: 'operating-a-small-treeseed',
|
|
194
|
+
title: 'Operating a Small TreeSeed',
|
|
195
|
+
status: 'live',
|
|
196
|
+
author: 'TreeSeed Team',
|
|
197
|
+
tags: ['operations', 'treeseed'],
|
|
198
|
+
body: 'Keep the content model simple and the workflows visible.',
|
|
199
|
+
},
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
console.log(response.payload.item.slug);
|
|
203
|
+
console.log(response.payload.git);
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
For D1-backed models, `create()` delegates to the relevant store and returns the created entity.
|
|
207
|
+
|
|
208
|
+
### Update Existing Records
|
|
209
|
+
|
|
210
|
+
Use `update()` when you want to modify an existing content-backed or D1-backed record.
|
|
211
|
+
|
|
212
|
+
```ts
|
|
213
|
+
const response = await sdk.update({
|
|
214
|
+
model: 'objective',
|
|
215
|
+
slug: 'make-the-sample-site-easy-to-operate',
|
|
216
|
+
actor: 'app-server',
|
|
217
|
+
data: {
|
|
218
|
+
status: 'live',
|
|
219
|
+
body: 'The objective is now complete and documented.',
|
|
220
|
+
},
|
|
221
|
+
});
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
For content-backed models, `update()` returns the updated item and git metadata. For D1-backed models, it returns the updated row or `null` when no matching record exists.
|
|
225
|
+
|
|
226
|
+
### Work With Messages
|
|
227
|
+
|
|
228
|
+
The SDK exposes dedicated queue helpers in addition to generic model access.
|
|
229
|
+
|
|
230
|
+
Create a message:
|
|
231
|
+
|
|
232
|
+
```ts
|
|
233
|
+
const created = await sdk.createMessage({
|
|
234
|
+
type: 'guidance_ready',
|
|
235
|
+
actor: 'guide-agent',
|
|
236
|
+
payload: {
|
|
237
|
+
slug: 'guides/getting-started/1-introduction',
|
|
238
|
+
},
|
|
239
|
+
relatedModel: 'knowledge',
|
|
240
|
+
relatedId: 'guides/getting-started/1-introduction',
|
|
241
|
+
priority: 5,
|
|
242
|
+
maxAttempts: 3,
|
|
243
|
+
});
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
Claim a message:
|
|
247
|
+
|
|
248
|
+
```ts
|
|
249
|
+
const claimed = await sdk.claimMessage({
|
|
250
|
+
workerId: 'worker-1',
|
|
251
|
+
messageTypes: ['guidance_ready'],
|
|
252
|
+
leaseSeconds: 300,
|
|
253
|
+
});
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
Acknowledge a message:
|
|
257
|
+
|
|
258
|
+
```ts
|
|
259
|
+
await sdk.ackMessage({
|
|
260
|
+
id: 1,
|
|
261
|
+
status: 'completed',
|
|
262
|
+
});
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
### Record Agent Runs, Cursors, And Leases
|
|
266
|
+
|
|
267
|
+
Record a run:
|
|
268
|
+
|
|
269
|
+
```ts
|
|
270
|
+
await sdk.recordRun({
|
|
271
|
+
run: {
|
|
272
|
+
runId: 'run-123',
|
|
273
|
+
agentSlug: 'guide-agent',
|
|
274
|
+
status: 'completed',
|
|
275
|
+
triggerSource: 'message',
|
|
276
|
+
startedAt: '2026-04-07T00:00:00.000Z',
|
|
277
|
+
finishedAt: '2026-04-07T00:05:00.000Z',
|
|
278
|
+
},
|
|
279
|
+
});
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
Read and update a cursor:
|
|
283
|
+
|
|
284
|
+
```ts
|
|
285
|
+
const cursor = await sdk.getCursor({
|
|
286
|
+
agentSlug: 'guide-agent',
|
|
287
|
+
cursorKey: 'knowledge-sync',
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
await sdk.upsertCursor({
|
|
291
|
+
agentSlug: 'guide-agent',
|
|
292
|
+
cursorKey: 'knowledge-sync',
|
|
293
|
+
cursorValue: '2026-04-07T00:00:00.000Z',
|
|
294
|
+
});
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
Release one lease or all leases:
|
|
298
|
+
|
|
299
|
+
```ts
|
|
300
|
+
await sdk.releaseLease({
|
|
301
|
+
model: 'objective',
|
|
302
|
+
itemKey: 'make-the-sample-site-easy-to-operate',
|
|
303
|
+
leaseToken: 'lease-token',
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
await sdk.releaseAllLeases();
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
### How TreeSeed Uses Agent Runs, Cursors, And Leases
|
|
310
|
+
|
|
311
|
+
These three concepts are the operational state layer for TreeSeed's agent runtime. They are not general content models like `page` or `knowledge`. Instead, they let TreeSeed coordinate ongoing agent work safely and make that work inspectable after the fact.
|
|
312
|
+
|
|
313
|
+
#### Agent Runs
|
|
314
|
+
|
|
315
|
+
An `agent_run` is the execution trace for one agent invocation.
|
|
316
|
+
|
|
317
|
+
TreeSeed records a run when the agent kernel starts an agent, and records it again when the run finishes or fails. In practice, that means a run captures:
|
|
318
|
+
|
|
319
|
+
- which agent ran
|
|
320
|
+
- what triggered it
|
|
321
|
+
- the current status such as `running`, `completed`, `failed`, or `waiting`
|
|
322
|
+
- which message or item was selected
|
|
323
|
+
- summary or error output
|
|
324
|
+
- optional branch, commit, PR, and changed-path metadata
|
|
325
|
+
- start and finish timestamps
|
|
326
|
+
|
|
327
|
+
In the TreeSeed agent runtime, [`agent/src/agents/kernel/agent-kernel.ts`](/home/adrian/Projects/treeseed/agent/src/agents/kernel/agent-kernel.ts) calls `sdk.recordRun()` at the beginning of execution and again after the handler returns. That gives TreeSeed a durable per-run audit trail for:
|
|
328
|
+
|
|
329
|
+
- debugging agent behavior
|
|
330
|
+
- understanding why an agent did or did not run
|
|
331
|
+
- inspecting outputs from planner, reviewer, notifier, and similar handlers
|
|
332
|
+
- connecting downstream events back to the run that produced them
|
|
333
|
+
|
|
334
|
+
Conceptually, `agent_run` is the answer to: "What happened during this agent invocation?"
|
|
335
|
+
|
|
336
|
+
#### Agent Cursors
|
|
337
|
+
|
|
338
|
+
An `agent_cursor` is a tiny per-agent checkpoint. It stores one named progress marker as:
|
|
339
|
+
|
|
340
|
+
- `agentSlug`
|
|
341
|
+
- `cursorKey`
|
|
342
|
+
- `cursorValue`
|
|
343
|
+
|
|
344
|
+
TreeSeed uses cursors to remember where an agent last left off, so the next cycle can resume from the correct point instead of starting over.
|
|
345
|
+
|
|
346
|
+
In the runtime, cursors are used in a few concrete ways:
|
|
347
|
+
|
|
348
|
+
- the agent kernel writes `last_run_at` after a successful run
|
|
349
|
+
- follow triggers read a cursor like `follow:model-a,model-b` to know which timestamp to compare against
|
|
350
|
+
- the sample planner agent writes `last_priority_run_at`
|
|
351
|
+
- the sample notifier agent reads and updates `last_notified_at` so it only announces new activity
|
|
352
|
+
|
|
353
|
+
You can see that usage in:
|
|
354
|
+
|
|
355
|
+
- [`agent/src/agents/kernel/agent-kernel.ts`](/home/adrian/Projects/treeseed/agent/src/agents/kernel/agent-kernel.ts)
|
|
356
|
+
- [`agent/src/agents/kernel/trigger-resolver.ts`](/home/adrian/Projects/treeseed/agent/src/agents/kernel/trigger-resolver.ts)
|
|
357
|
+
- [`core/fixture/src/agents/planner.ts`](/home/adrian/Projects/treeseed/core/fixture/src/agents/planner.ts)
|
|
358
|
+
- [`core/fixture/src/agents/notifier.ts`](/home/adrian/Projects/treeseed/core/fixture/src/agents/notifier.ts)
|
|
359
|
+
|
|
360
|
+
Conceptually, `agent_cursor` is the answer to: "Where should this agent continue from next time?"
|
|
361
|
+
|
|
362
|
+
#### Content Leases
|
|
363
|
+
|
|
364
|
+
A `content_lease` is a short-lived claim on one content item. TreeSeed uses leases to avoid two workers picking and mutating the same item at the same time.
|
|
365
|
+
|
|
366
|
+
When `pick()` runs against a content-backed model, the SDK does not just choose the "best" item. It also tries to claim a lease in the database. If another worker already holds a live lease for that item, the claim fails and the picker moves on.
|
|
367
|
+
|
|
368
|
+
Each lease stores:
|
|
369
|
+
|
|
370
|
+
- the model
|
|
371
|
+
- the item key
|
|
372
|
+
- who claimed it
|
|
373
|
+
- when it was claimed
|
|
374
|
+
- when the lease expires
|
|
375
|
+
- a lease token
|
|
376
|
+
|
|
377
|
+
This is how TreeSeed prevents duplicate work during autonomous or parallel agent execution, especially for content-backed tasks like selecting the next note, question, or objective to act on.
|
|
378
|
+
|
|
379
|
+
In the SDK, the lease flow is wired through:
|
|
380
|
+
|
|
381
|
+
- content selection in [`sdk/src/content-store.ts`](/home/adrian/Projects/treeseed/sdk/src/content-store.ts)
|
|
382
|
+
- D1 lease persistence in [`sdk/src/stores/lease-store.ts`](/home/adrian/Projects/treeseed/sdk/src/stores/lease-store.ts)
|
|
383
|
+
- runtime cleanup through `releaseLease()` and `releaseAllLeases()`
|
|
384
|
+
|
|
385
|
+
The agent kernel exposes that cleanup path as `releaseLeases()` so TreeSeed operators can clear stale claims when needed.
|
|
386
|
+
|
|
387
|
+
Conceptually, `content_lease` is the answer to: "Who currently owns this piece of work, and when does that claim expire?"
|
|
388
|
+
|
|
389
|
+
#### How They Work Together
|
|
390
|
+
|
|
391
|
+
In TreeSeed, these records solve different parts of the same runtime problem:
|
|
392
|
+
|
|
393
|
+
- `agent_run` records what happened
|
|
394
|
+
- `agent_cursor` records where to resume
|
|
395
|
+
- `content_lease` records who currently owns a piece of work
|
|
396
|
+
|
|
397
|
+
Together, they make the agent system:
|
|
398
|
+
|
|
399
|
+
- inspectable, because each run leaves a trace
|
|
400
|
+
- incremental, because agents can continue from saved cursors
|
|
401
|
+
- concurrency-safe, because content picking is guarded by leases
|
|
402
|
+
|
|
403
|
+
That combination is what lets TreeSeed move from one-off scripts toward a manageable long-running agent runtime.
|
|
404
|
+
|
|
405
|
+
### Discover Agent Specs
|
|
406
|
+
|
|
407
|
+
When your application stores agent definitions in content, use `listRawAgentSpecs()` or `listAgentSpecs()`.
|
|
408
|
+
|
|
409
|
+
```ts
|
|
410
|
+
const specs = await sdk.listAgentSpecs({ enabled: true });
|
|
411
|
+
|
|
412
|
+
for (const spec of specs) {
|
|
413
|
+
console.log(spec.slug, spec.handler);
|
|
414
|
+
}
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
Use `listRawAgentSpecs()` when you want the underlying content entries. Use `listAgentSpecs()` when you want normalized runtime specs.
|
|
418
|
+
|
|
419
|
+
### Restrict Access With `ScopedAgentSdk`
|
|
420
|
+
|
|
421
|
+
Use `scopeForAgent()` when you want application code to enforce an agent’s declared permissions before executing SDK operations.
|
|
422
|
+
|
|
423
|
+
```ts
|
|
424
|
+
const scoped = sdk.scopeForAgent({
|
|
425
|
+
slug: 'guide-agent',
|
|
426
|
+
permissions: [
|
|
427
|
+
{ model: 'knowledge', operations: ['get', 'read', 'search'] },
|
|
428
|
+
{ model: 'message', operations: ['create'] },
|
|
429
|
+
],
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
await scoped.search({
|
|
433
|
+
model: 'knowledge',
|
|
434
|
+
filters: [{ field: 'tags', op: 'contains', value: 'treeseed' }],
|
|
435
|
+
});
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
`ScopedAgentSdk` automatically injects the agent slug as `actor` for `create()`, `update()`, and `createMessage()`, and throws when the requested operation is not allowed.
|
|
439
|
+
|
|
440
|
+
### Model And Filter Notes
|
|
441
|
+
|
|
442
|
+
The SDK resolves model aliases for you. For example, `docs` maps to `knowledge` and `people` maps to `person`.
|
|
443
|
+
|
|
444
|
+
Common request fields:
|
|
445
|
+
|
|
446
|
+
- `model`: the canonical model or an accepted alias
|
|
447
|
+
- `filters`: array of `{ field, op, value }`
|
|
448
|
+
- `sort`: array of `{ field, direction }`
|
|
449
|
+
- `limit`: max number of returned items
|
|
450
|
+
|
|
451
|
+
Common filter operators include:
|
|
452
|
+
|
|
453
|
+
- `eq`
|
|
454
|
+
- `in`
|
|
455
|
+
- `contains`
|
|
456
|
+
- `prefix`
|
|
457
|
+
- `gt`
|
|
458
|
+
- `gte`
|
|
459
|
+
- `lt`
|
|
460
|
+
- `lte`
|
|
461
|
+
- `updated_since`
|
|
462
|
+
- `related_to`
|
|
463
|
+
|
|
464
|
+
### Envelope Pattern
|
|
465
|
+
|
|
466
|
+
Every top-level SDK call returns a consistent envelope:
|
|
467
|
+
|
|
468
|
+
```ts
|
|
469
|
+
const response = await sdk.search({ model: 'person', limit: 1 });
|
|
470
|
+
|
|
471
|
+
response.ok; // true
|
|
472
|
+
response.model; // 'person'
|
|
473
|
+
response.operation; // 'search'
|
|
474
|
+
response.payload; // typed result
|
|
475
|
+
response.meta; // optional metadata
|
|
476
|
+
```
|
|
477
|
+
|
|
478
|
+
That envelope shape makes it straightforward to use the SDK in API handlers, background jobs, CLIs, and agent runtimes without introducing a second application-specific response format.
|
|
479
|
+
|
|
480
|
+
## Public Surface
|
|
481
|
+
|
|
482
|
+
The package root exports the main SDK class, model registry helpers, CLI option helpers, and shared SDK types.
|
|
483
|
+
|
|
484
|
+
The package also exposes focused subpaths including:
|
|
485
|
+
|
|
486
|
+
- `@treeseed/sdk/sdk`
|
|
487
|
+
- `@treeseed/sdk/content-store`
|
|
488
|
+
- `@treeseed/sdk/d1-store`
|
|
489
|
+
- `@treeseed/sdk/frontmatter`
|
|
490
|
+
- `@treeseed/sdk/git-runtime`
|
|
491
|
+
- `@treeseed/sdk/models`
|
|
492
|
+
- `@treeseed/sdk/sdk-filters`
|
|
493
|
+
- `@treeseed/sdk/cli-tools`
|
|
494
|
+
- `@treeseed/sdk/types`
|
|
495
|
+
- `@treeseed/sdk/types/agents`
|
|
496
|
+
- `@treeseed/sdk/types/cloudflare`
|
|
497
|
+
- `@treeseed/sdk/wrangler-d1`
|
|
498
|
+
- `@treeseed/sdk/stores/*`
|
|
499
|
+
|
|
500
|
+
## Content Root Resolution
|
|
501
|
+
|
|
502
|
+
Content-backed operations need a repository root that contains `src/content`.
|
|
503
|
+
|
|
504
|
+
`AgentSdk` resolves that root in this order:
|
|
505
|
+
|
|
506
|
+
1. the explicit `repoRoot` option
|
|
507
|
+
2. `TREESEED_SDK_CONTENT_ROOT`
|
|
508
|
+
3. `TREESEED_SDK_REPO_ROOT`
|
|
509
|
+
4. auto-detection from the current working directory
|
|
510
|
+
|
|
511
|
+
For package-local tests and fixture-driven development, the SDK also recognizes a package fixture root containing `fixture/src/content`.
|
|
512
|
+
|
|
513
|
+
Example with an explicit root:
|
|
514
|
+
|
|
515
|
+
```ts
|
|
516
|
+
import { AgentSdk } from '@treeseed/sdk';
|
|
517
|
+
|
|
518
|
+
const sdk = new AgentSdk({
|
|
519
|
+
repoRoot: '/absolute/path/to/site-or-fixture-root',
|
|
520
|
+
});
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
## Local Development
|
|
524
|
+
|
|
525
|
+
```bash
|
|
526
|
+
npm ci
|
|
527
|
+
npm run build
|
|
528
|
+
npm test
|
|
529
|
+
npm run test:smoke
|
|
530
|
+
npm run verify
|
|
531
|
+
```
|
|
532
|
+
|
|
533
|
+
What each command does:
|
|
534
|
+
|
|
535
|
+
- `npm run build`: builds `dist/`
|
|
536
|
+
- `npm test`: runs unit tests
|
|
537
|
+
- `npm run test:smoke`: packs the SDK tarball and verifies a clean import from the packed install
|
|
538
|
+
- `npm run verify`: runs the release verification path used by CI
|
|
539
|
+
|
|
540
|
+
## Sample Fixture Site
|
|
541
|
+
|
|
542
|
+
The canonical SDK sample fixture lives at `../fixtures/fixture-sdk-sample-site/template` in the TreeSeed workspace.
|
|
543
|
+
|
|
544
|
+
The SDK package also keeps a mirrored local fixture at `sdk/fixture` so the standalone SDK repository can run its own tests and CI without depending on the larger workspace layout.
|
|
545
|
+
|
|
546
|
+
It serves three purposes at once:
|
|
547
|
+
|
|
548
|
+
- a small documentation surface about working with TreeSeed
|
|
549
|
+
- the default local test ground for content-backed SDK behavior
|
|
550
|
+
- a concrete example of a valid `repoRoot` for `AgentSdk`
|
|
551
|
+
|
|
552
|
+
You can point the SDK at it directly:
|
|
553
|
+
|
|
554
|
+
```ts
|
|
555
|
+
import path from 'node:path';
|
|
556
|
+
import { AgentSdk } from '@treeseed/sdk';
|
|
557
|
+
|
|
558
|
+
const sdk = new AgentSdk({
|
|
559
|
+
repoRoot: path.resolve('../fixtures/fixture-sdk-sample-site/template'),
|
|
560
|
+
});
|
|
561
|
+
```
|
|
562
|
+
|
|
563
|
+
The fixture includes representative entries for pages, notes, questions, objectives, books, knowledge, people, and agents so local queries behave like a small real site instead of a synthetic stub.
|
|
564
|
+
|
|
565
|
+
In the full TreeSeed workspace, tests prefer the workspace fixture under `fixtures/`. In the standalone SDK repository, tests fall back to `sdk/fixture`.
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AGENT_CLI_ALLOW_TOOLS
|
|
3
|
+
} from "./types/agents.js";
|
|
4
|
+
const ALLOWED_TOOL_SET = new Set(AGENT_CLI_ALLOW_TOOLS);
|
|
5
|
+
function normalizeStringArray(value, field) {
|
|
6
|
+
if (value === void 0 || value === null) {
|
|
7
|
+
return [];
|
|
8
|
+
}
|
|
9
|
+
if (!Array.isArray(value) || value.some((entry) => typeof entry !== "string")) {
|
|
10
|
+
throw new Error(`Invalid agent cli.${field}: expected an array of strings.`);
|
|
11
|
+
}
|
|
12
|
+
return value.map(String);
|
|
13
|
+
}
|
|
14
|
+
function normalizeAgentCliOptions(input) {
|
|
15
|
+
if (input === void 0 || input === null) {
|
|
16
|
+
return {
|
|
17
|
+
allowTools: [],
|
|
18
|
+
additionalArgs: []
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
if (typeof input !== "object" || Array.isArray(input)) {
|
|
22
|
+
throw new Error("Invalid agent cli config: expected an object.");
|
|
23
|
+
}
|
|
24
|
+
const cli = input;
|
|
25
|
+
const rawAllowTools = normalizeStringArray(cli.allowTools, "allowTools");
|
|
26
|
+
const invalidTools = rawAllowTools.filter((tool) => !ALLOWED_TOOL_SET.has(tool));
|
|
27
|
+
if (invalidTools.length) {
|
|
28
|
+
throw new Error(
|
|
29
|
+
`Invalid agent cli.allowTools entries: ${invalidTools.join(", ")}. Allowed tools: ${AGENT_CLI_ALLOW_TOOLS.join(", ")}.`
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
return {
|
|
33
|
+
model: typeof cli.model === "string" ? cli.model : void 0,
|
|
34
|
+
allowTools: [...new Set(rawAllowTools)],
|
|
35
|
+
additionalArgs: normalizeStringArray(cli.additionalArgs, "additionalArgs")
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
function buildCopilotAllowToolArgs(allowTools = []) {
|
|
39
|
+
return (allowTools ?? []).flatMap((tool) => ["--allow-tool", tool]);
|
|
40
|
+
}
|
|
41
|
+
export {
|
|
42
|
+
buildCopilotAllowToolArgs,
|
|
43
|
+
normalizeAgentCliOptions
|
|
44
|
+
};
|