@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.
Files changed (63) hide show
  1. package/README.md +565 -0
  2. package/dist/cli-tools.js +44 -0
  3. package/dist/content-store.js +237 -0
  4. package/dist/d1-store.js +549 -0
  5. package/dist/frontmatter.js +33 -0
  6. package/dist/git-runtime.js +67 -0
  7. package/dist/index.js +12 -0
  8. package/dist/model-registry.js +164 -0
  9. package/dist/runtime.js +36 -0
  10. package/dist/scripts/.ts-run-1775616845195-odh4xzphk3l.js +22 -0
  11. package/dist/scripts/.ts-run-1775616848931-9386s6kwrl.js +126 -0
  12. package/dist/scripts/assert-release-tag-version.d.ts +1 -0
  13. package/dist/scripts/assert-release-tag-version.js +23 -0
  14. package/dist/scripts/build-dist.d.ts +1 -0
  15. package/dist/scripts/build-dist.js +114 -0
  16. package/dist/scripts/package-tools.d.ts +15 -0
  17. package/dist/scripts/package-tools.js +76 -0
  18. package/dist/scripts/publish-package.d.ts +1 -0
  19. package/dist/scripts/publish-package.js +20 -0
  20. package/dist/scripts/release-verify.d.ts +1 -0
  21. package/dist/scripts/release-verify.js +49 -0
  22. package/dist/scripts/run-ts.js +45 -0
  23. package/dist/scripts/test-smoke.d.ts +1 -0
  24. package/dist/scripts/test-smoke.js +77 -0
  25. package/dist/sdk-filters.js +77 -0
  26. package/dist/sdk-types.js +24 -0
  27. package/dist/sdk.js +232 -0
  28. package/dist/src/cli-tools.d.ts +3 -0
  29. package/dist/src/content-store.d.ts +24 -0
  30. package/dist/src/d1-store.d.ts +108 -0
  31. package/dist/src/frontmatter.d.ts +6 -0
  32. package/dist/src/git-runtime.d.ts +16 -0
  33. package/dist/src/index.d.ts +6 -0
  34. package/dist/src/model-registry.d.ts +4 -0
  35. package/dist/src/runtime.d.ts +1 -0
  36. package/dist/src/sdk-filters.d.ts +4 -0
  37. package/dist/src/sdk-types.d.ts +285 -0
  38. package/dist/src/sdk.d.ts +109 -0
  39. package/dist/src/stores/cursor-store.d.ts +10 -0
  40. package/dist/src/stores/envelopes.d.ts +116 -0
  41. package/dist/src/stores/helpers.d.ts +12 -0
  42. package/dist/src/stores/lease-store.d.ts +18 -0
  43. package/dist/src/stores/message-store.d.ts +12 -0
  44. package/dist/src/stores/run-store.d.ts +10 -0
  45. package/dist/src/stores/subscription-store.d.ts +9 -0
  46. package/dist/src/types/agents.d.ts +100 -0
  47. package/dist/src/types/cloudflare.d.ts +32 -0
  48. package/dist/src/wrangler-d1.d.ts +25 -0
  49. package/dist/stores/cursor-store.js +158 -0
  50. package/dist/stores/envelopes.js +219 -0
  51. package/dist/stores/helpers.js +42 -0
  52. package/dist/stores/lease-store.js +183 -0
  53. package/dist/stores/message-store.js +249 -0
  54. package/dist/stores/run-store.js +166 -0
  55. package/dist/stores/subscription-store.js +171 -0
  56. package/dist/test/test-fixture.d.ts +1 -0
  57. package/dist/test/utils/envelopes.test.d.ts +1 -0
  58. package/dist/test/utils/sdk.test.d.ts +1 -0
  59. package/dist/types/agents.js +40 -0
  60. package/dist/types/cloudflare.js +0 -0
  61. package/dist/vitest.config.d.ts +2 -0
  62. package/dist/wrangler-d1.js +84 -0
  63. package/package.json +130 -0
@@ -0,0 +1,549 @@
1
+ import crypto from "node:crypto";
2
+ import { applyFilters, applySort } from "./sdk-filters.js";
3
+ import { CursorStore } from "./stores/cursor-store.js";
4
+ import { LeaseStore } from "./stores/lease-store.js";
5
+ import { MessageStore } from "./stores/message-store.js";
6
+ import { RunStore } from "./stores/run-store.js";
7
+ import { SubscriptionStore } from "./stores/subscription-store.js";
8
+ function nowIso() {
9
+ return (/* @__PURE__ */ new Date()).toISOString();
10
+ }
11
+ function nextLeaseToken() {
12
+ return crypto.randomUUID();
13
+ }
14
+ function filterSinceField(model) {
15
+ switch (model) {
16
+ case "message":
17
+ case "subscription":
18
+ return "updated_at";
19
+ case "agent_run":
20
+ return "startedAt";
21
+ case "agent_cursor":
22
+ return "updatedAt";
23
+ case "content_lease":
24
+ return "leaseExpiresAt";
25
+ default:
26
+ return "updatedAt";
27
+ }
28
+ }
29
+ class MemoryAgentDatabase {
30
+ subscriptions = /* @__PURE__ */ new Map();
31
+ messages = /* @__PURE__ */ new Map();
32
+ runs = /* @__PURE__ */ new Map();
33
+ contentLeases = /* @__PURE__ */ new Map();
34
+ cursors = /* @__PURE__ */ new Map();
35
+ messageId = 0;
36
+ constructor(seed) {
37
+ for (const item of seed?.subscriptions ?? []) {
38
+ this.subscriptions.set(String(item.id ?? item.email), item);
39
+ }
40
+ for (const message of seed?.messages ?? []) {
41
+ this.messages.set(message.id, message);
42
+ this.messageId = Math.max(this.messageId, message.id);
43
+ }
44
+ for (const run of seed?.runs ?? []) {
45
+ this.runs.set(run.runId, run);
46
+ }
47
+ for (const cursor of seed?.cursors ?? []) {
48
+ this.cursors.set(`${cursor.agentSlug}:${cursor.cursorKey}`, cursor.cursorValue);
49
+ }
50
+ for (const lease of seed?.leases ?? []) {
51
+ this.contentLeases.set(`${lease.model}:${lease.itemKey}`, lease);
52
+ }
53
+ }
54
+ rowsForModel(model) {
55
+ if (model === "subscription") {
56
+ return [...this.subscriptions.values()];
57
+ }
58
+ if (model === "message") {
59
+ return [...this.messages.values()];
60
+ }
61
+ if (model === "agent_run") {
62
+ return [...this.runs.values()];
63
+ }
64
+ if (model === "agent_cursor") {
65
+ return [...this.cursors.entries()].map(([key, value]) => {
66
+ const [agentSlug, cursorKey] = key.split(":", 2);
67
+ return {
68
+ agentSlug,
69
+ cursorKey,
70
+ cursorValue: value,
71
+ updatedAt: null
72
+ };
73
+ });
74
+ }
75
+ if (model === "content_lease") {
76
+ return [...this.contentLeases.values()].map((lease) => ({
77
+ model: lease.model,
78
+ itemKey: lease.itemKey,
79
+ claimedBy: lease.claimedBy,
80
+ claimedAt: lease.claimedAt,
81
+ leaseExpiresAt: lease.leaseExpiresAt,
82
+ token: lease.token
83
+ }));
84
+ }
85
+ throw new Error(`Unsupported D1 model "${model}".`);
86
+ }
87
+ async get(request) {
88
+ const key = String(request.id ?? request.slug ?? request.key ?? "");
89
+ if (request.model === "agent_cursor") {
90
+ if (!key) {
91
+ return null;
92
+ }
93
+ const [agentSlug, cursorKey] = key.split(":", 2);
94
+ const value = this.cursors.get(`${agentSlug}:${cursorKey}`);
95
+ return value ? {
96
+ agentSlug,
97
+ cursorKey,
98
+ cursorValue: value,
99
+ updatedAt: null
100
+ } : null;
101
+ }
102
+ if (request.model === "content_lease") {
103
+ const lease = this.contentLeases.get(key);
104
+ return lease ? {
105
+ model: lease.model,
106
+ itemKey: lease.itemKey,
107
+ claimedBy: lease.claimedBy,
108
+ claimedAt: lease.claimedAt,
109
+ leaseExpiresAt: lease.leaseExpiresAt,
110
+ token: lease.token
111
+ } : null;
112
+ }
113
+ return this.rowsForModel(request.model).find(
114
+ (row) => [row.id, row.email, row.runId].map((value) => String(value ?? "")).includes(key)
115
+ ) ?? null;
116
+ }
117
+ async search(request) {
118
+ const filtered = applyFilters(this.rowsForModel(request.model), request.filters);
119
+ const sorted = applySort(filtered, request.sort);
120
+ return sorted.slice(0, request.limit ?? sorted.length);
121
+ }
122
+ async follow(request) {
123
+ const filters = [
124
+ ...request.filters ?? [],
125
+ {
126
+ field: filterSinceField(request.model),
127
+ op: "updated_since",
128
+ value: request.since
129
+ }
130
+ ];
131
+ return {
132
+ items: await this.search({
133
+ model: request.model,
134
+ filters
135
+ }),
136
+ since: request.since
137
+ };
138
+ }
139
+ async pick(request) {
140
+ if (request.model === "message") {
141
+ const item = await this.claimMessage({
142
+ workerId: request.workerId,
143
+ messageTypes: request.filters?.filter((filter) => filter.field === "type" && filter.op === "in").flatMap((filter) => Array.isArray(filter.value) ? filter.value.map(String) : []),
144
+ leaseSeconds: request.leaseSeconds
145
+ });
146
+ return {
147
+ item,
148
+ leaseToken: item ? nextLeaseToken() : null
149
+ };
150
+ }
151
+ if (request.model === "content_lease") {
152
+ const item = (await this.search({
153
+ model: request.model,
154
+ filters: request.filters,
155
+ sort: [{ field: "leaseExpiresAt", direction: "desc" }],
156
+ limit: 1
157
+ }))[0];
158
+ return {
159
+ item: item ?? null,
160
+ leaseToken: item ? String(item.token) : null
161
+ };
162
+ }
163
+ const items = await this.search({
164
+ model: request.model,
165
+ filters: request.filters,
166
+ sort: [{ field: filterSinceField(request.model), direction: "desc" }]
167
+ });
168
+ return {
169
+ item: items[0] ?? null,
170
+ leaseToken: null
171
+ };
172
+ }
173
+ async create(request) {
174
+ switch (request.model) {
175
+ case "message":
176
+ return await this.createMessage({
177
+ type: String(request.data.type ?? "message.created"),
178
+ payload: request.data.payload ?? request.data,
179
+ relatedModel: typeof request.data.relatedModel === "string" ? request.data.relatedModel : null,
180
+ relatedId: typeof request.data.relatedId === "string" ? request.data.relatedId : null,
181
+ priority: Number(request.data.priority ?? 0),
182
+ maxAttempts: Number(request.data.maxAttempts ?? 3),
183
+ actor: request.actor
184
+ });
185
+ case "subscription": {
186
+ const record = {
187
+ id: this.subscriptions.size + 1,
188
+ email: String(request.data.email ?? ""),
189
+ name: request.data.name ? String(request.data.name) : null,
190
+ status: String(request.data.status ?? "active"),
191
+ source: String(request.data.source ?? "sdk"),
192
+ consent_at: String(request.data.consent_at ?? nowIso()),
193
+ created_at: String(request.data.created_at ?? nowIso()),
194
+ updated_at: String(request.data.updated_at ?? nowIso()),
195
+ ip_hash: String(request.data.ip_hash ?? "")
196
+ };
197
+ this.subscriptions.set(String(record.id), record);
198
+ return record;
199
+ }
200
+ case "agent_run":
201
+ return this.recordRun({ run: request.data });
202
+ case "agent_cursor": {
203
+ const agentSlug = String(request.data.agentSlug ?? "");
204
+ const cursorKey = String(request.data.cursorKey ?? "");
205
+ const cursorValue = String(request.data.cursorValue ?? "");
206
+ this.cursors.set(`${agentSlug}:${cursorKey}`, cursorValue);
207
+ return {
208
+ agentSlug,
209
+ cursorKey,
210
+ cursorValue,
211
+ updatedAt: nowIso()
212
+ };
213
+ }
214
+ case "content_lease": {
215
+ const token = await this.tryClaimContentLease({
216
+ model: String(request.data.model ?? ""),
217
+ itemKey: String(request.data.itemKey ?? ""),
218
+ claimedBy: String(request.data.claimedBy ?? request.actor),
219
+ leaseSeconds: Number(request.data.leaseSeconds ?? 300)
220
+ });
221
+ const lease = this.contentLeases.get(`${request.data.model}:${request.data.itemKey}`);
222
+ return {
223
+ model: String(request.data.model ?? ""),
224
+ itemKey: String(request.data.itemKey ?? ""),
225
+ claimedBy: String(request.data.claimedBy ?? request.actor),
226
+ claimedAt: String(lease?.claimedAt ?? nowIso()),
227
+ leaseExpiresAt: String(lease?.leaseExpiresAt ?? nowIso()),
228
+ token: String(token ?? lease?.token ?? "")
229
+ };
230
+ }
231
+ default:
232
+ throw new Error(`Unsupported D1 create model "${request.model}".`);
233
+ }
234
+ }
235
+ async update(request) {
236
+ switch (request.model) {
237
+ case "message": {
238
+ const current = this.messages.get(Number(request.id ?? request.key ?? request.data.id ?? 0));
239
+ if (!current) {
240
+ return null;
241
+ }
242
+ const next = {
243
+ ...current,
244
+ ...request.data,
245
+ updatedAt: nowIso()
246
+ };
247
+ this.messages.set(next.id, next);
248
+ return next;
249
+ }
250
+ case "subscription": {
251
+ const key = String(request.id ?? request.key ?? request.data.email ?? "");
252
+ const current = await this.get({ model: "subscription", key });
253
+ if (!current) {
254
+ return null;
255
+ }
256
+ const next = {
257
+ ...current,
258
+ ...request.data,
259
+ updated_at: nowIso()
260
+ };
261
+ this.subscriptions.set(String(next.id ?? next.email), next);
262
+ return next;
263
+ }
264
+ case "agent_run":
265
+ return this.recordRun({ run: { ...request.data, runId: request.id ?? request.key ?? request.data.runId } });
266
+ case "agent_cursor":
267
+ return this.create({
268
+ model: "agent_cursor",
269
+ data: request.data,
270
+ actor: request.actor
271
+ });
272
+ case "content_lease":
273
+ return this.create({
274
+ model: "content_lease",
275
+ data: request.data,
276
+ actor: request.actor
277
+ });
278
+ default:
279
+ throw new Error(`Unsupported D1 update model "${request.model}".`);
280
+ }
281
+ }
282
+ async claimMessage(request) {
283
+ const pending = [...this.messages.values()].filter(
284
+ (message) => (message.status === "pending" || message.status === "failed") && new Date(message.availableAt).valueOf() <= Date.now() && (!request.messageTypes?.length || request.messageTypes.includes(message.type))
285
+ ).sort((left, right) => right.priority - left.priority || left.availableAt.localeCompare(right.availableAt))[0];
286
+ if (!pending) {
287
+ return null;
288
+ }
289
+ const claimedAt = nowIso();
290
+ const next = {
291
+ ...pending,
292
+ status: "claimed",
293
+ claimedBy: request.workerId,
294
+ claimedAt,
295
+ leaseExpiresAt: new Date(Date.now() + request.leaseSeconds * 1e3).toISOString(),
296
+ attempts: pending.attempts + 1,
297
+ updatedAt: claimedAt
298
+ };
299
+ this.messages.set(next.id, next);
300
+ return next;
301
+ }
302
+ async ackMessage(request) {
303
+ const current = this.messages.get(request.id);
304
+ if (!current) {
305
+ return;
306
+ }
307
+ this.messages.set(request.id, {
308
+ ...current,
309
+ status: request.status,
310
+ updatedAt: nowIso()
311
+ });
312
+ }
313
+ async createMessage(request) {
314
+ this.messageId += 1;
315
+ const record = {
316
+ id: this.messageId,
317
+ type: request.type,
318
+ status: "pending",
319
+ payloadJson: JSON.stringify(request.payload),
320
+ relatedModel: request.relatedModel ?? null,
321
+ relatedId: request.relatedId ?? null,
322
+ priority: request.priority ?? 0,
323
+ availableAt: nowIso(),
324
+ claimedBy: null,
325
+ claimedAt: null,
326
+ leaseExpiresAt: null,
327
+ attempts: 0,
328
+ maxAttempts: request.maxAttempts ?? 3,
329
+ createdAt: nowIso(),
330
+ updatedAt: nowIso()
331
+ };
332
+ this.messages.set(record.id, record);
333
+ return record;
334
+ }
335
+ async recordRun(request) {
336
+ const run = request.run;
337
+ this.runs.set(String(run.runId), run);
338
+ return run;
339
+ }
340
+ async getCursor(request) {
341
+ return this.cursors.get(`${request.agentSlug}:${request.cursorKey}`) ?? null;
342
+ }
343
+ async upsertCursor(request) {
344
+ this.cursors.set(`${request.agentSlug}:${request.cursorKey}`, request.cursorValue);
345
+ }
346
+ async releaseLease(request) {
347
+ this.contentLeases.delete(`${request.model}:${request.itemKey}`);
348
+ }
349
+ async tryClaimContentLease(input) {
350
+ const key = `${input.model}:${input.itemKey}`;
351
+ const existing = this.contentLeases.get(key);
352
+ if (existing && new Date(existing.leaseExpiresAt).valueOf() > Date.now()) {
353
+ return null;
354
+ }
355
+ const token = nextLeaseToken();
356
+ this.contentLeases.set(key, {
357
+ model: input.model,
358
+ itemKey: input.itemKey,
359
+ claimedBy: input.claimedBy,
360
+ claimedAt: nowIso(),
361
+ leaseExpiresAt: new Date(Date.now() + input.leaseSeconds * 1e3).toISOString(),
362
+ token
363
+ });
364
+ return token;
365
+ }
366
+ async releaseAllLeases() {
367
+ const count = this.contentLeases.size;
368
+ this.contentLeases.clear();
369
+ return count;
370
+ }
371
+ inspectRuns() {
372
+ return [...this.runs.values()];
373
+ }
374
+ inspectLeases() {
375
+ return [...this.contentLeases.values()];
376
+ }
377
+ }
378
+ class CloudflareD1AgentDatabase {
379
+ constructor(db) {
380
+ this.db = db;
381
+ this.subscriptions = new SubscriptionStore(db);
382
+ this.messages = new MessageStore(db);
383
+ this.runs = new RunStore(db);
384
+ this.cursors = new CursorStore(db);
385
+ this.leases = new LeaseStore(db);
386
+ }
387
+ db;
388
+ subscriptions;
389
+ messages;
390
+ runs;
391
+ cursors;
392
+ leases;
393
+ async get(request) {
394
+ if (request.model === "subscription") {
395
+ return this.subscriptions.getByKey(String(request.id ?? request.slug ?? request.key ?? ""));
396
+ }
397
+ if (request.model === "message") {
398
+ return this.messages.getById(Number(request.id ?? request.slug ?? request.key ?? 0));
399
+ }
400
+ if (request.model === "agent_run") {
401
+ return this.runs.getByKey(String(request.id ?? request.key ?? request.slug ?? ""));
402
+ }
403
+ if (request.model === "agent_cursor") {
404
+ return this.cursors.getByKey(String(request.id ?? request.key ?? request.slug ?? ""));
405
+ }
406
+ if (request.model === "content_lease") {
407
+ return this.leases.getByKey(String(request.id ?? request.key ?? request.slug ?? ""));
408
+ }
409
+ throw new Error(`Unsupported D1 get model "${request.model}".`);
410
+ }
411
+ async search(request) {
412
+ if (request.model === "subscription") {
413
+ return this.subscriptions.search(request);
414
+ }
415
+ if (request.model === "message") {
416
+ return this.messages.search(request);
417
+ }
418
+ if (request.model === "agent_run") {
419
+ return this.runs.search(request);
420
+ }
421
+ if (request.model === "agent_cursor") {
422
+ return this.cursors.search(request);
423
+ }
424
+ if (request.model === "content_lease") {
425
+ return this.leases.search(request);
426
+ }
427
+ throw new Error(`Unsupported D1 search model "${request.model}".`);
428
+ }
429
+ async follow(request) {
430
+ const field = request.model === "subscription" || request.model === "message" ? "updated_at" : request.model === "agent_run" ? "started_at" : request.model === "agent_cursor" ? "updated_at" : "lease_expires_at";
431
+ return this.search({
432
+ model: request.model,
433
+ filters: [
434
+ ...request.filters ?? [],
435
+ { field, op: "updated_since", value: request.since }
436
+ ]
437
+ }).then((items) => ({ items, since: request.since }));
438
+ }
439
+ async pick(request) {
440
+ if (request.model === "message") {
441
+ const claimed = await this.claimMessage({
442
+ workerId: request.workerId,
443
+ messageTypes: request.filters?.filter((filter) => filter.field === "type" && filter.op === "in").flatMap((filter) => Array.isArray(filter.value) ? filter.value.map(String) : []),
444
+ leaseSeconds: request.leaseSeconds
445
+ });
446
+ return {
447
+ item: claimed,
448
+ leaseToken: claimed ? nextLeaseToken() : null
449
+ };
450
+ }
451
+ if (request.model === "content_lease") {
452
+ const items = await this.leases.search({
453
+ model: "content_lease",
454
+ filters: request.filters,
455
+ sort: [{ field: "lease_expires_at", direction: "desc" }],
456
+ limit: 1
457
+ });
458
+ return {
459
+ item: items[0] ?? null,
460
+ leaseToken: items[0]?.token ?? null
461
+ };
462
+ }
463
+ return {
464
+ item: null,
465
+ leaseToken: null
466
+ };
467
+ }
468
+ async create(request) {
469
+ if (request.model === "message") {
470
+ return await this.createMessage({
471
+ type: String(request.data.type ?? "message.created"),
472
+ payload: request.data.payload ?? request.data,
473
+ relatedModel: typeof request.data.relatedModel === "string" ? request.data.relatedModel : null,
474
+ relatedId: typeof request.data.relatedId === "string" ? request.data.relatedId : null,
475
+ priority: Number(request.data.priority ?? 0),
476
+ maxAttempts: Number(request.data.maxAttempts ?? 3),
477
+ actor: request.actor
478
+ });
479
+ }
480
+ if (request.model === "subscription") {
481
+ return await this.subscriptions.create(request);
482
+ }
483
+ if (request.model === "agent_run") {
484
+ return await this.runs.record({ run: request.data });
485
+ }
486
+ if (request.model === "agent_cursor") {
487
+ return await this.cursors.update({
488
+ ...request,
489
+ model: "agent_cursor"
490
+ });
491
+ }
492
+ if (request.model === "content_lease") {
493
+ return await this.leases.update({
494
+ ...request,
495
+ model: "content_lease"
496
+ });
497
+ }
498
+ throw new Error(`Unsupported D1 create model "${request.model}".`);
499
+ }
500
+ async update(request) {
501
+ if (request.model === "message") {
502
+ return this.messages.update(request);
503
+ }
504
+ if (request.model === "subscription") {
505
+ return this.subscriptions.update(request);
506
+ }
507
+ if (request.model === "agent_run") {
508
+ return this.runs.update(request);
509
+ }
510
+ if (request.model === "agent_cursor") {
511
+ return this.cursors.update(request);
512
+ }
513
+ if (request.model === "content_lease") {
514
+ return this.leases.update(request);
515
+ }
516
+ throw new Error(`Unsupported D1 update model "${request.model}".`);
517
+ }
518
+ claimMessage(request) {
519
+ return this.messages.claim(request);
520
+ }
521
+ ackMessage(request) {
522
+ return this.messages.ack(request);
523
+ }
524
+ createMessage(request) {
525
+ return this.messages.create(request);
526
+ }
527
+ recordRun(request) {
528
+ return this.runs.record(request);
529
+ }
530
+ getCursor(request) {
531
+ return this.cursors.get(request);
532
+ }
533
+ upsertCursor(request) {
534
+ return this.cursors.upsert(request);
535
+ }
536
+ releaseLease(request) {
537
+ return this.leases.release(request);
538
+ }
539
+ tryClaimContentLease(input) {
540
+ return this.leases.tryClaim(input);
541
+ }
542
+ releaseAllLeases() {
543
+ return this.leases.releaseAll();
544
+ }
545
+ }
546
+ export {
547
+ CloudflareD1AgentDatabase,
548
+ MemoryAgentDatabase
549
+ };
@@ -0,0 +1,33 @@
1
+ import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
2
+ function parseFrontmatterDocument(source) {
3
+ if (!source.startsWith("---\n")) {
4
+ return {
5
+ frontmatter: {},
6
+ body: source
7
+ };
8
+ }
9
+ const delimiterIndex = source.indexOf("\n---\n", 4);
10
+ if (delimiterIndex < 0) {
11
+ return {
12
+ frontmatter: {},
13
+ body: source
14
+ };
15
+ }
16
+ const yamlSource = source.slice(4, delimiterIndex);
17
+ const body = source.slice(delimiterIndex + 5);
18
+ const parsed = parseYaml(yamlSource);
19
+ return {
20
+ frontmatter: parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {},
21
+ body
22
+ };
23
+ }
24
+ function serializeFrontmatterDocument(frontmatter, body) {
25
+ return `---
26
+ ${stringifyYaml(frontmatter).trimEnd()}
27
+ ---
28
+ ${body.replace(/^\n*/, "")}`;
29
+ }
30
+ export {
31
+ parseFrontmatterDocument,
32
+ serializeFrontmatterDocument
33
+ };
@@ -0,0 +1,67 @@
1
+ import { execFile } from "node:child_process";
2
+ import path from "node:path";
3
+ import { promisify } from "node:util";
4
+ const execFileAsync = promisify(execFile);
5
+ class GitRuntime {
6
+ constructor(repoRoot, disabled = false) {
7
+ this.repoRoot = repoRoot;
8
+ this.disabled = disabled;
9
+ }
10
+ repoRoot;
11
+ disabled;
12
+ async currentBranch() {
13
+ const { stdout } = await execFileAsync("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
14
+ cwd: this.repoRoot
15
+ });
16
+ return stdout.trim();
17
+ }
18
+ async ensureWorktree(branchName) {
19
+ const worktreePath = path.join(this.repoRoot, ".agent-worktrees", branchName);
20
+ if (this.disabled) {
21
+ return worktreePath;
22
+ }
23
+ try {
24
+ await execFileAsync("git", ["worktree", "list", "--porcelain"], {
25
+ cwd: this.repoRoot
26
+ }).then(async ({ stdout }) => {
27
+ if (stdout.includes(`worktree ${worktreePath}`)) {
28
+ await execFileAsync("git", ["switch", branchName], { cwd: worktreePath });
29
+ return;
30
+ }
31
+ await execFileAsync("git", ["worktree", "add", "-B", branchName, worktreePath, "HEAD"], {
32
+ cwd: this.repoRoot
33
+ });
34
+ });
35
+ } catch {
36
+ await execFileAsync("git", ["worktree", "add", "-B", branchName, worktreePath, "HEAD"], {
37
+ cwd: this.repoRoot
38
+ });
39
+ }
40
+ return worktreePath;
41
+ }
42
+ async commitFileChange(filePath, branchName, commitMessage) {
43
+ return this.commitFileChanges([filePath], branchName, commitMessage);
44
+ }
45
+ async commitFileChanges(filePaths, branchName, commitMessage) {
46
+ const worktreePath = await this.ensureWorktree(branchName);
47
+ if (this.disabled) {
48
+ return { branchName, commitMessage, worktreePath, commitSha: null, changedPaths: filePaths };
49
+ }
50
+ const relativeFilePaths = filePaths.map((filePath) => path.relative(worktreePath, filePath));
51
+ await execFileAsync("git", ["add", ...relativeFilePaths], { cwd: worktreePath });
52
+ try {
53
+ await execFileAsync("git", ["commit", "-m", commitMessage], { cwd: worktreePath });
54
+ } catch (error) {
55
+ const message = error && typeof error === "object" && "stderr" in error ? String(error.stderr ?? "") : "";
56
+ if (!message.includes("nothing to commit")) {
57
+ throw error;
58
+ }
59
+ }
60
+ const { stdout } = await execFileAsync("git", ["rev-parse", "HEAD"], { cwd: worktreePath });
61
+ const commitSha = stdout.trim();
62
+ return { branchName, commitMessage, worktreePath, commitSha, changedPaths: filePaths };
63
+ }
64
+ }
65
+ export {
66
+ GitRuntime
67
+ };
package/dist/index.js ADDED
@@ -0,0 +1,12 @@
1
+ import { AgentSdk, ScopedAgentSdk } from "./sdk.js";
2
+ import { MODEL_REGISTRY, buildModelRegistry, resolveModelDefinition } from "./model-registry.js";
3
+ import { normalizeAgentCliOptions, buildCopilotAllowToolArgs } from "./cli-tools.js";
4
+ export {
5
+ AgentSdk,
6
+ MODEL_REGISTRY,
7
+ ScopedAgentSdk,
8
+ buildCopilotAllowToolArgs,
9
+ buildModelRegistry,
10
+ normalizeAgentCliOptions,
11
+ resolveModelDefinition
12
+ };