loop-sdk 0.1.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.
Files changed (62) hide show
  1. package/README.md +591 -0
  2. package/dist/agent.d.ts +31 -0
  3. package/dist/agent.d.ts.map +1 -0
  4. package/dist/agent.js +48 -0
  5. package/dist/agent.js.map +1 -0
  6. package/dist/checkpoint.d.ts +16 -0
  7. package/dist/checkpoint.d.ts.map +1 -0
  8. package/dist/checkpoint.js +20 -0
  9. package/dist/checkpoint.js.map +1 -0
  10. package/dist/claude-cli.d.ts +35 -0
  11. package/dist/claude-cli.d.ts.map +1 -0
  12. package/dist/claude-cli.js +135 -0
  13. package/dist/claude-cli.js.map +1 -0
  14. package/dist/context.d.ts +53 -0
  15. package/dist/context.d.ts.map +1 -0
  16. package/dist/context.js +95 -0
  17. package/dist/context.js.map +1 -0
  18. package/dist/events.d.ts +111 -0
  19. package/dist/events.d.ts.map +1 -0
  20. package/dist/events.js +53 -0
  21. package/dist/events.js.map +1 -0
  22. package/dist/flow.d.ts +36 -0
  23. package/dist/flow.d.ts.map +1 -0
  24. package/dist/flow.js +62 -0
  25. package/dist/flow.js.map +1 -0
  26. package/dist/index.d.ts +26 -0
  27. package/dist/index.d.ts.map +1 -0
  28. package/dist/index.js +14 -0
  29. package/dist/index.js.map +1 -0
  30. package/dist/logger.d.ts +37 -0
  31. package/dist/logger.d.ts.map +1 -0
  32. package/dist/logger.js +64 -0
  33. package/dist/logger.js.map +1 -0
  34. package/dist/loop.d.ts +199 -0
  35. package/dist/loop.d.ts.map +1 -0
  36. package/dist/loop.js +403 -0
  37. package/dist/loop.js.map +1 -0
  38. package/dist/loopfile.d.ts +82 -0
  39. package/dist/loopfile.d.ts.map +1 -0
  40. package/dist/loopfile.js +235 -0
  41. package/dist/loopfile.js.map +1 -0
  42. package/dist/mcp/server.d.ts +26 -0
  43. package/dist/mcp/server.d.ts.map +1 -0
  44. package/dist/mcp/server.js +160 -0
  45. package/dist/mcp/server.js.map +1 -0
  46. package/dist/notify.d.ts +43 -0
  47. package/dist/notify.d.ts.map +1 -0
  48. package/dist/notify.js +68 -0
  49. package/dist/notify.js.map +1 -0
  50. package/dist/plugins/retry.d.ts +41 -0
  51. package/dist/plugins/retry.d.ts.map +1 -0
  52. package/dist/plugins/retry.js +53 -0
  53. package/dist/plugins/retry.js.map +1 -0
  54. package/dist/providers/playwright.d.ts +38 -0
  55. package/dist/providers/playwright.d.ts.map +1 -0
  56. package/dist/providers/playwright.js +155 -0
  57. package/dist/providers/playwright.js.map +1 -0
  58. package/dist/session.d.ts +43 -0
  59. package/dist/session.d.ts.map +1 -0
  60. package/dist/session.js +26 -0
  61. package/dist/session.js.map +1 -0
  62. package/package.json +78 -0
package/README.md ADDED
@@ -0,0 +1,591 @@
1
+ # loop-sdk
2
+
3
+ Framework for building long-running agentic loops. Steps can be browser actions, AI agent calls, data operations, or anything else — composed into named, resumable sequences with structured logging, checkpointing, events, and a plugin system.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install loop-sdk
9
+ npm install @ai-sdk/anthropic # or @ai-sdk/openai, @ai-sdk/google, etc.
10
+ ```
11
+
12
+ ## Quick start
13
+
14
+ ```js
15
+ import { Loop } from 'loop-sdk'
16
+ import { claudeCli } from 'loop-sdk'
17
+
18
+ const loop = new Loop('research')
19
+
20
+ loop.step('gather', async (ctx) => {
21
+ const result = await claudeCli(ctx, 'List the top 5 JS frameworks in 2026.')
22
+ ctx.set('frameworks', result.text)
23
+ })
24
+
25
+ loop.step('summarize', async (ctx) => {
26
+ const list = ctx.get('frameworks')
27
+ await claudeCli(ctx, `Summarize this list in one sentence: ${list}`)
28
+ })
29
+
30
+ await loop.run({ session: null })
31
+ ```
32
+
33
+ ---
34
+
35
+ ## Concepts
36
+
37
+ ### Loop
38
+
39
+ A named sequence of steps. Steps are plain async functions that receive a `ctx` (Context).
40
+
41
+ ```js
42
+ const loop = new Loop('my-loop')
43
+
44
+ loop.step('step-name', async (ctx) => {
45
+ // do work
46
+ }, {
47
+ retries: 3, // retry this step up to 3 times on failure
48
+ retryDelay: 1000, // wait 1s between retries
49
+ retryBackoff: 'exponential', // 'flat' | 'linear' | 'exponential'
50
+ skipOnError: false, // skip this step instead of failing the loop
51
+ onError: async (err, ctx) => {
52
+ // fallback: return a value to use as the step result, or re-throw to fail
53
+ },
54
+ })
55
+
56
+ await loop.run({ session })
57
+ ```
58
+
59
+ **`loop.run(opts)`** options:
60
+
61
+ | Option | Type | Description |
62
+ |--------|------|-------------|
63
+ | `session` | `Session \| null` | The session to run against. |
64
+ | `vars` | `object` | Initial variables available as `ctx.vars`. |
65
+ | `logDir` | `string` | Directory to write a JSON run log. |
66
+ | `startAt` | `number` | Skip steps before this 1-based index. |
67
+ | `stopAt` | `number` | Stop after this 1-based index. |
68
+ | `signal` | `AbortSignal` | External abort signal to cancel the run. |
69
+ | `checkpointFile` | `string` | Path to write checkpoints after each step. |
70
+ | `resumeFrom` | `string` | Checkpoint file to resume from (skips completed steps). |
71
+ | `keepCheckpointOnSuccess` | `boolean` | Keep the checkpoint file after a successful run (default: `false`). |
72
+ | `onError` | `(err, ctx, failedStep) => Promise<void>` | Called when the loop fails. |
73
+
74
+ Returns a run log object: `{ loop, session, status, steps, startedAt, finishedAt }`.
75
+
76
+ ---
77
+
78
+ ### Parallel steps
79
+
80
+ Run multiple step functions concurrently within a single named step:
81
+
82
+ ```js
83
+ loop.parallel('fetch-all', [
84
+ async (ctx) => { ctx.set('a', await fetch('https://a.com')) },
85
+ async (ctx) => { ctx.set('b', await fetch('https://b.com')) },
86
+ async (ctx) => { ctx.set('c', await fetch('https://c.com')) },
87
+ ])
88
+ ```
89
+
90
+ All functions in a parallel group share the same context and run via `Promise.all`.
91
+
92
+ ---
93
+
94
+ ### Background execution
95
+
96
+ `runBackground()` starts the loop in the background and immediately returns a `RunHandle`.
97
+
98
+ ```js
99
+ const handle = loop.runBackground({ session })
100
+
101
+ console.log(handle.id) // unique run ID
102
+ console.log(handle.status) // 'running' | 'completed' | 'failed' | 'cancelled' | 'paused'
103
+
104
+ const log = await handle.wait() // resolves when the loop finishes
105
+ handle.cancel() // cancel the loop
106
+ handle.pause() // pause after the current step
107
+ const newHandle = handle.resume() // resume from where it paused
108
+ ```
109
+
110
+ Checkpoints are auto-written under `.loop/<id>.checkpoint.json` during background runs. Pause and resume use the checkpoint file automatically.
111
+
112
+ **Run multiple loops in parallel:**
113
+
114
+ ```js
115
+ const handles = await Loop.runAll([
116
+ { loop: loopA, opts: { session } },
117
+ { loop: loopB, opts: { session } },
118
+ { loop: loopC, opts: { session } },
119
+ ])
120
+ ```
121
+
122
+ ---
123
+
124
+ ### Checkpointing
125
+
126
+ Checkpoints save progress after each step so a loop can be resumed after a crash or pause.
127
+
128
+ ```js
129
+ await loop.run({
130
+ session,
131
+ checkpointFile: '.loop/my-run.checkpoint.json',
132
+ })
133
+
134
+ // Later, resume from where it left off:
135
+ await loop.run({
136
+ session,
137
+ resumeFrom: '.loop/my-run.checkpoint.json',
138
+ })
139
+ ```
140
+
141
+ Helpers:
142
+
143
+ ```js
144
+ import { checkpointExists, deleteCheckpoint } from 'loop-sdk'
145
+
146
+ if (await checkpointExists('.loop/my-run.checkpoint.json')) {
147
+ // resume
148
+ }
149
+ await deleteCheckpoint('.loop/my-run.checkpoint.json')
150
+ ```
151
+
152
+ Checkpoint files are auto-deleted on successful completion unless `keepCheckpointOnSuccess: true`.
153
+
154
+ ---
155
+
156
+ ### Events
157
+
158
+ Loops emit typed events throughout their lifecycle. Listen with `loop.on()` / `loop.off()`.
159
+
160
+ ```js
161
+ loop.on('loop:start', ({ totalSteps }) => {
162
+ console.log(`Starting with ${totalSteps} steps`)
163
+ })
164
+
165
+ loop.on('loop:complete', ({ status, durationMs, stepsCompleted }) => {
166
+ console.log(`Finished: ${status} in ${durationMs}ms`)
167
+ })
168
+
169
+ loop.on('step:start', ({ step, index }) => { })
170
+ loop.on('step:complete', ({ step, durationMs }) => { })
171
+ loop.on('step:error', ({ step, error, attempt }) => { })
172
+ loop.on('step:skip', ({ step, reason }) => { }) // reason: 'checkpoint' | 'range' | 'error'
173
+ loop.on('step:retry', ({ step, attempt, delay }) => { })
174
+ loop.on('checkpoint:saved', ({ file, completedSteps }) => { })
175
+ ```
176
+
177
+ **Custom events** — emit from inside a step and listen anywhere:
178
+
179
+ ```js
180
+ loop.step('draft', async (ctx) => {
181
+ ctx.set('draft', '...')
182
+ ctx.emit('draft:ready', { preview: ctx.get('draft') })
183
+ })
184
+
185
+ loop.on('draft:ready', async ({ preview }) => {
186
+ // approve, notify, update UI, etc.
187
+ })
188
+ ```
189
+
190
+ ---
191
+
192
+ ### Context
193
+
194
+ Passed to every step. Carries shared state, per-iteration variables, and browser shortcuts.
195
+
196
+ ```js
197
+ // State — shared across all steps in a run
198
+ ctx.set('key', value)
199
+ ctx.get('key')
200
+ ctx.has('key')
201
+ ctx.snapshot() // plain object of all state
202
+
203
+ // Variables — injected per-iteration by each()
204
+ ctx.vars.item
205
+ ctx.vars.subtype
206
+
207
+ // Emit a custom event
208
+ ctx.emit('my:event', { data: 123 })
209
+
210
+ // Logging
211
+ ctx.log('message', { optional: 'data' })
212
+
213
+ // Browser shortcuts (delegate to ctx.session)
214
+ ctx.navigate(url)
215
+ ctx.click({ selector, text, x, y })
216
+ ctx.type(text)
217
+ ctx.key('Enter')
218
+ ctx.scroll({ deltaY: 300 })
219
+ ctx.screenshot() // returns Buffer
220
+
221
+ // Direct session access
222
+ ctx.session.mcp('browser_evaluate', { function: '() => document.title' })
223
+ ```
224
+
225
+ ---
226
+
227
+ ### Session
228
+
229
+ Abstract interface that all browser providers implement. Extend it to add your own:
230
+
231
+ ```js
232
+ import { Session } from 'loop-sdk'
233
+
234
+ export class MySession extends Session {
235
+ async navigate(url) { /* ... */ }
236
+ async click(opts) { /* ... */ }
237
+ async type(text) { /* ... */ }
238
+ async key(key) { /* ... */ }
239
+ async scroll(opts) { /* ... */ }
240
+ async screenshot() { /* returns Buffer */ }
241
+ async destroy() { /* ... */ }
242
+
243
+ // Optional: expose an MCP URL so agent() / claudeCli() gets browser tool access
244
+ get mcpUrl() { return `http://my-daemon/sessions/${this.id}/mcp` }
245
+ }
246
+ ```
247
+
248
+ ---
249
+
250
+ ### agent()
251
+
252
+ Run any AI model as a loop step. Uses the [Vercel AI SDK](https://sdk.vercel.ai) so every `@ai-sdk/*` provider works.
253
+
254
+ ```js
255
+ import { agent } from 'loop-sdk'
256
+ import { anthropic } from '@ai-sdk/anthropic'
257
+
258
+ loop.step('summarize', async (ctx) => {
259
+ const result = await agent(ctx, 'Summarize the main content of this page.', {
260
+ model: anthropic('claude-opus-4-8'),
261
+ screenshot: true, // attach a screenshot before calling the model
262
+ maxSteps: 50,
263
+ system: 'You are a research assistant.',
264
+ })
265
+ ctx.set('summary', result.text)
266
+ })
267
+ ```
268
+
269
+ If `ctx.session.mcpUrl` is set, the model automatically gets the session's browser tools via MCP.
270
+
271
+ ---
272
+
273
+ ### claudeCli()
274
+
275
+ Spawn the `claude` CLI subprocess (`claude -p`) as a loop step. Use this when you want Claude Code's built-in tool-use loop, retry logic, and MCP permission model.
276
+
277
+ Requires `claude` to be installed and on PATH.
278
+
279
+ ```js
280
+ import { claudeCli } from 'loop-sdk'
281
+
282
+ loop.step('fill', async (ctx) => {
283
+ const result = await claudeCli(ctx, 'Fill out the visible form fields with test data.', {
284
+ screenshot: true,
285
+ model: 'claude-opus-4-8',
286
+ timeout: 240_000,
287
+ })
288
+ ctx.set('output', result.text)
289
+ })
290
+ ```
291
+
292
+ **`agent()` vs `claudeCli()`:**
293
+
294
+ | | `agent()` | `claudeCli()` |
295
+ |-|-----------|---------------|
296
+ | Provider | Any `@ai-sdk/*` model | Claude CLI only |
297
+ | Tool-use loop | Managed by Vercel AI SDK | Managed by Claude Code CLI |
298
+ | Best for | Multi-provider, programmatic control | Delegating full control to Claude |
299
+
300
+ ---
301
+
302
+ ### each()
303
+
304
+ Iterate over a list of items. Each item gets its own forked context with per-item vars.
305
+
306
+ ```js
307
+ import { each } from 'loop-sdk'
308
+
309
+ loop.step('process-pages', async (ctx) => {
310
+ const pages = ['https://a.com', 'https://b.com', 'https://c.com']
311
+
312
+ await each(ctx, pages, async (ctx) => {
313
+ await ctx.navigate(ctx.vars.item)
314
+ await claudeCli(ctx, 'Summarize this page.')
315
+ }, { continueOnError: true })
316
+ })
317
+ ```
318
+
319
+ Items can carry subtypes:
320
+
321
+ ```js
322
+ const items = [
323
+ { type: 'Category A', subtypes: ['Sub 1', 'Sub 2'] },
324
+ 'Category B',
325
+ ]
326
+
327
+ await each(ctx, items, async (ctx) => {
328
+ console.log(ctx.vars.item, ctx.vars.subtype)
329
+ // → 'Category A', 'Sub 1'
330
+ // → 'Category A', 'Sub 2'
331
+ // → 'Category B', undefined
332
+ })
333
+ ```
334
+
335
+ ---
336
+
337
+ ### sub()
338
+
339
+ Run a `Loop` as a sub-step, sharing the current session and state.
340
+
341
+ ```js
342
+ import { sub } from 'loop-sdk'
343
+
344
+ const loginLoop = new Loop('login')
345
+ loginLoop.step('authenticate', async (ctx) => {
346
+ await claudeCli(ctx, `Log in as ${ctx.vars.username}.`)
347
+ })
348
+
349
+ loop.step('login', async (ctx) => {
350
+ await sub(ctx, loginLoop, { username: 'admin' })
351
+ })
352
+ ```
353
+
354
+ State written inside the sub-loop is visible in the parent loop (shared `ctx` state Map).
355
+
356
+ ---
357
+
358
+ ### Plugins
359
+
360
+ Plugins hook into the loop lifecycle. Return `true` from `onStepError` to retry the failed step.
361
+
362
+ ```js
363
+ const crashRecovery = {
364
+ name: 'crash-recovery',
365
+ hooks: {
366
+ onStepError: async (err, step, ctx) => {
367
+ if (!err.message.includes('context was lost')) return false
368
+ await ctx.session.recreate()
369
+ return true // retry the step
370
+ },
371
+ },
372
+ }
373
+
374
+ loop.use(crashRecovery)
375
+ ```
376
+
377
+ #### RetryPlugin
378
+
379
+ Built-in plugin for automatic step retries with backoff:
380
+
381
+ ```js
382
+ import { RetryPlugin } from 'loop-sdk'
383
+
384
+ loop.use(RetryPlugin({
385
+ attempts: 3,
386
+ delay: 500,
387
+ backoff: 'exponential', // 'flat' | 'linear' | 'exponential'
388
+ retryIf: (err) => !err.message.includes('auth'), // optional filter
389
+ }))
390
+ ```
391
+
392
+ ---
393
+
394
+ ### macOS Notifications
395
+
396
+ Send native macOS notifications from a loop:
397
+
398
+ ```js
399
+ import { notify, notifyOn } from 'loop-sdk'
400
+
401
+ // One-off notification
402
+ notify('Step completed!', { title: 'My Loop', sound: true })
403
+
404
+ // Auto-wire lifecycle notifications to a loop
405
+ notifyOn(loop, {
406
+ onStart: true,
407
+ onComplete: true,
408
+ onError: true,
409
+ onStepError: false,
410
+ title: 'My Loop',
411
+ sound: true,
412
+ })
413
+ ```
414
+
415
+ No-op on non-macOS platforms.
416
+
417
+ ---
418
+
419
+ ## .loop files
420
+
421
+ `.loop` files let you define loops in a simple text format. An AI agent can generate them via MCP; your app parses and runs them.
422
+
423
+ **Format:**
424
+
425
+ ```
426
+ ---
427
+ name: my-loop
428
+ description: Does something useful
429
+ ---
430
+
431
+ ## step-one
432
+
433
+ action: claudeCli
434
+ prompt: Write a haiku about the ocean.
435
+
436
+ ## step-two
437
+
438
+ action: log
439
+ message: Done! Output was {{step-one}}
440
+ ```
441
+
442
+ **Supported actions:**
443
+
444
+ | Action | Description |
445
+ |--------|-------------|
446
+ | `claudeCli` | Run `claude -p` with `prompt`. |
447
+ | `navigate` | Navigate to `url`. |
448
+ | `screenshot` | Take a screenshot. |
449
+ | `log` | Print `message` to stdout. |
450
+ | `parallel` | Run multiple steps concurrently (nested `steps` list). |
451
+ | `sub` | Run another `.loop` file (`file` path). |
452
+ | `each` | Iterate over `items` (array, context key, or `claudeCli` prompt) and run `steps` or `file` per item. |
453
+
454
+ **`{{step-name}}` interpolation** — reference the output of any previous step by name.
455
+
456
+ **Running a `.loop` file:**
457
+
458
+ ```js
459
+ import { loadLoop, runFile, runFileBackground } from 'loop-sdk'
460
+
461
+ // Run synchronously (returns run log)
462
+ await runFile('./my-loop.loop', { session: null })
463
+
464
+ // Run in the background (returns RunHandle)
465
+ const handle = await runFileBackground('./my-loop.loop', { session: null })
466
+ await handle.wait()
467
+
468
+ // Parse and build a Loop instance manually
469
+ const loop = await loadLoop('./my-loop.loop')
470
+ await loop.run({ session: null })
471
+ ```
472
+
473
+ ---
474
+
475
+ ## MCP server
476
+
477
+ The `loop-mcp` server lets AI agents create and manage `.loop` files via MCP tools.
478
+
479
+ **Start it:**
480
+
481
+ ```bash
482
+ npx loop-mcp
483
+ # or
484
+ npm run mcp
485
+ ```
486
+
487
+ **Available tools:**
488
+
489
+ | Tool | Description |
490
+ |------|-------------|
491
+ | `write_loop` | Write (or overwrite) a `.loop` file. Validates before writing. |
492
+ | `read_loop` | Read the contents of a `.loop` file. |
493
+ | `list_loops` | List all `.loop` files with step summaries. |
494
+ | `validate_loop` | Validate a `.loop` file without writing it. |
495
+
496
+ Loop files are stored in the directory set by `LOOP_DIR` (default: `.loop`).
497
+
498
+ **Claude Desktop config:**
499
+
500
+ ```json
501
+ {
502
+ "mcpServers": {
503
+ "loop": {
504
+ "command": "npx",
505
+ "args": ["loop-mcp"]
506
+ }
507
+ }
508
+ }
509
+ ```
510
+
511
+ ---
512
+
513
+ ## PlaywrightSession
514
+
515
+ The built-in browser provider. Wraps the aria-playwright daemon.
516
+
517
+ ```js
518
+ import { PlaywrightSession } from 'loop-sdk/playwright'
519
+
520
+ const session = new PlaywrightSession('session-id', {
521
+ daemon: 'http://localhost:4848', // default
522
+ })
523
+
524
+ await session.ensure() // create if it doesn't exist
525
+ await session.recreate() // destroy + re-create (after a crash)
526
+ await session.destroy() // tear down
527
+
528
+ // Extra methods:
529
+ await session.evaluate('() => document.title')
530
+ await session.currentUrl()
531
+ await session.mcp('browser_snapshot', {})
532
+ ```
533
+
534
+ ---
535
+
536
+ ## Run log
537
+
538
+ When `logDir` is passed to `loop.run()`, a JSON log is written incrementally:
539
+
540
+ ```json
541
+ {
542
+ "loop": "my-loop",
543
+ "session": "my-session",
544
+ "status": "completed",
545
+ "startedAt": "2026-06-28T10:00:00.000Z",
546
+ "finishedAt": "2026-06-28T10:01:23.456Z",
547
+ "steps": [
548
+ { "name": "gather", "status": "ok" },
549
+ { "name": "summarize", "status": "ok" }
550
+ ]
551
+ }
552
+ ```
553
+
554
+ Possible statuses: `completed`, `failed`, `running` (if the process was killed mid-run), `cancelled`.
555
+
556
+ ---
557
+
558
+ ## Writing a custom provider
559
+
560
+ Any class that extends `Session` works as a provider:
561
+
562
+ ```js
563
+ import { Session } from 'loop-sdk'
564
+ import puppeteer from 'puppeteer'
565
+
566
+ export class PuppeteerSession extends Session {
567
+ constructor(id) {
568
+ super(id)
569
+ this._browser = null
570
+ this._page = null
571
+ }
572
+
573
+ async ensure() {
574
+ this._browser = await puppeteer.launch()
575
+ this._page = await this._browser.newPage()
576
+ }
577
+
578
+ async navigate(url) { await this._page.goto(url) }
579
+ async click({ selector }) { await this._page.click(selector) }
580
+ async type(text) { await this._page.keyboard.type(text) }
581
+ async key(key) { await this._page.keyboard.press(key) }
582
+ async scroll({ deltaY }) { await this._page.evaluate(dy => window.scrollBy(0, dy), deltaY) }
583
+ async screenshot() { return this._page.screenshot({ type: 'jpeg' }) }
584
+ async destroy() { await this._browser?.close() }
585
+ }
586
+
587
+ const session = new PuppeteerSession('puppeteer-session')
588
+ await session.ensure()
589
+ await loop.run({ session })
590
+ await session.destroy()
591
+ ```
@@ -0,0 +1,31 @@
1
+ import type { LanguageModel } from 'ai';
2
+ import type { Context } from './context.js';
3
+ export interface AgentOptions {
4
+ model: LanguageModel;
5
+ system?: string;
6
+ maxSteps?: number;
7
+ /** Attach a screenshot of the current browser state before calling the model. */
8
+ screenshot?: boolean;
9
+ }
10
+ export interface AgentResult {
11
+ ok: true;
12
+ text: string;
13
+ usage?: {
14
+ totalTokens?: number;
15
+ promptTokens?: number;
16
+ completionTokens?: number;
17
+ };
18
+ steps?: unknown[];
19
+ }
20
+ /**
21
+ * agent — run any AI model as a loop step via the Vercel AI SDK.
22
+ *
23
+ * Pass any @ai-sdk/* model — provider switching is one argument change.
24
+ * If ctx.session.mcpUrl is set, the model gets live browser tool access via MCP.
25
+ *
26
+ * @example
27
+ * import { anthropic } from '@ai-sdk/anthropic'
28
+ * await agent(ctx, 'Summarize the page.', { model: anthropic('claude-opus-4-8') })
29
+ */
30
+ export declare function agent(ctx: Context, prompt: string, opts: AgentOptions): Promise<AgentResult>;
31
+ //# sourceMappingURL=agent.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../src/agent.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,IAAI,CAAA;AACvC,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AAE3C,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,aAAa,CAAA;IACpB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,iFAAiF;IACjF,UAAU,CAAC,EAAE,OAAO,CAAA;CACrB;AAED,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,IAAI,CAAA;IACR,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,CAAC,EAAE;QAAE,WAAW,CAAC,EAAE,MAAM,CAAC;QAAC,YAAY,CAAC,EAAE,MAAM,CAAC;QAAC,gBAAgB,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IAClF,KAAK,CAAC,EAAE,OAAO,EAAE,CAAA;CAClB;AAED;;;;;;;;;GASG;AACH,wBAAsB,KAAK,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,YAAY,GAAG,OAAO,CAAC,WAAW,CAAC,CA0ClG"}
package/dist/agent.js ADDED
@@ -0,0 +1,48 @@
1
+ import { generateText, experimental_createMCPClient } from 'ai';
2
+ /**
3
+ * agent — run any AI model as a loop step via the Vercel AI SDK.
4
+ *
5
+ * Pass any @ai-sdk/* model — provider switching is one argument change.
6
+ * If ctx.session.mcpUrl is set, the model gets live browser tool access via MCP.
7
+ *
8
+ * @example
9
+ * import { anthropic } from '@ai-sdk/anthropic'
10
+ * await agent(ctx, 'Summarize the page.', { model: anthropic('claude-opus-4-8') })
11
+ */
12
+ export async function agent(ctx, prompt, opts) {
13
+ const { model, system, maxSteps = 50, screenshot = false } = opts;
14
+ let userContent = prompt;
15
+ if (screenshot && ctx.session?.screenshot) {
16
+ const imgBytes = await ctx.session.screenshot();
17
+ userContent = [
18
+ { type: 'text', text: prompt },
19
+ { type: 'image', image: imgBytes, mimeType: 'image/jpeg' },
20
+ ];
21
+ }
22
+ let tools;
23
+ let mcpClient = null;
24
+ if (ctx.session?.mcpUrl) {
25
+ mcpClient = await experimental_createMCPClient({
26
+ transport: { type: 'sse', url: ctx.session.mcpUrl },
27
+ });
28
+ tools = await mcpClient.tools();
29
+ }
30
+ try {
31
+ const result = await generateText({
32
+ model,
33
+ system,
34
+ tools,
35
+ maxSteps,
36
+ messages: [{ role: 'user', content: userContent }],
37
+ });
38
+ ctx.log(`agent: ${result.usage?.totalTokens ?? '?'} tokens`, {
39
+ steps: result.steps?.length,
40
+ finishReason: result.finishReason,
41
+ });
42
+ return { ok: true, text: result.text, usage: result.usage, steps: result.steps };
43
+ }
44
+ finally {
45
+ await mcpClient?.close().catch(() => { });
46
+ }
47
+ }
48
+ //# sourceMappingURL=agent.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent.js","sourceRoot":"","sources":["../src/agent.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,4BAA4B,EAAE,MAAM,IAAI,CAAA;AAmB/D;;;;;;;;;GASG;AACH,MAAM,CAAC,KAAK,UAAU,KAAK,CAAC,GAAY,EAAE,MAAc,EAAE,IAAkB;IAC1E,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,GAAG,EAAE,EAAE,UAAU,GAAG,KAAK,EAAE,GAAG,IAAI,CAAA;IAGjE,IAAI,WAAW,GAAmB,MAAM,CAAA;IAExC,IAAI,UAAU,IAAI,GAAG,CAAC,OAAO,EAAE,UAAU,EAAE,CAAC;QAC1C,MAAM,QAAQ,GAAG,MAAM,GAAG,CAAC,OAAO,CAAC,UAAU,EAAE,CAAA;QAC/C,WAAW,GAAG;YACZ,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE;YAC9B,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,YAAY,EAAE;SAC3D,CAAA;IACH,CAAC;IAED,IAAI,KAAyG,CAAA;IAC7G,IAAI,SAAS,GAAoE,IAAI,CAAA;IAErF,IAAI,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,CAAC;QACxB,SAAS,GAAG,MAAM,4BAA4B,CAAC;YAC7C,SAAS,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE,GAAG,CAAC,OAAO,CAAC,MAAM,EAAE;SACpD,CAAC,CAAA;QACF,KAAK,GAAG,MAAM,SAAS,CAAC,KAAK,EAAE,CAAA;IACjC,CAAC;IAED,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,YAAY,CAAC;YAChC,KAAK;YACL,MAAM;YACN,KAAK;YACL,QAAQ;YACR,QAAQ,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,WAAW,EAAE,CAAC;SACnD,CAAC,CAAA;QAEF,GAAG,CAAC,GAAG,CAAC,UAAU,MAAM,CAAC,KAAK,EAAE,WAAW,IAAI,GAAG,SAAS,EAAE;YAC3D,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,MAAM;YAC3B,YAAY,EAAE,MAAM,CAAC,YAAY;SAClC,CAAC,CAAA;QAEF,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,CAAA;IAClF,CAAC;YAAS,CAAC;QACT,MAAM,SAAS,EAAE,KAAK,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAA;IAC1C,CAAC;AACH,CAAC"}
@@ -0,0 +1,16 @@
1
+ export interface Checkpoint {
2
+ loop: string;
3
+ session: string;
4
+ savedAt: string;
5
+ /** Names of every step that completed successfully before this checkpoint. */
6
+ completedSteps: string[];
7
+ /** Index of the last completed step (0-based). -1 if no steps completed yet. */
8
+ lastCompletedIndex: number;
9
+ /** Full ctx.snapshot() at the time of the checkpoint. */
10
+ state: Record<string, unknown>;
11
+ }
12
+ export declare function writeCheckpoint(file: string, data: Checkpoint): void;
13
+ export declare function readCheckpoint(file: string): Checkpoint;
14
+ export declare function checkpointExists(file: string): boolean;
15
+ export declare function deleteCheckpoint(file: string): void;
16
+ //# sourceMappingURL=checkpoint.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"checkpoint.d.ts","sourceRoot":"","sources":["../src/checkpoint.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,CAAA;IACf,OAAO,EAAE,MAAM,CAAA;IACf,8EAA8E;IAC9E,cAAc,EAAE,MAAM,EAAE,CAAA;IACxB,gFAAgF;IAChF,kBAAkB,EAAE,MAAM,CAAA;IAC1B,yDAAyD;IACzD,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAC/B;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,GAAG,IAAI,CAGpE;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,CAGvD;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEtD;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAEnD"}