@chroxify/eunoe 1.2.2 → 1.3.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 (63) hide show
  1. package/README.md +139 -2
  2. package/dist/core/index.js +2235 -0
  3. package/dist/types/context/content.d.ts +31 -0
  4. package/dist/types/context/fold/evidence.d.ts +20 -0
  5. package/dist/types/context/fold/index.d.ts +11 -0
  6. package/dist/types/context/fold/strategies.d.ts +20 -0
  7. package/dist/types/context/fold/turns.d.ts +14 -0
  8. package/dist/types/context/prompts.d.ts +24 -0
  9. package/dist/types/context/recall/index.d.ts +44 -0
  10. package/dist/types/context/recall/rerank.d.ts +3 -0
  11. package/dist/types/core/compaction.d.ts +32 -0
  12. package/dist/types/core/config.d.ts +33 -0
  13. package/dist/types/core/estimate.d.ts +9 -0
  14. package/dist/types/core/fingerprint.d.ts +10 -0
  15. package/dist/types/core/fold.d.ts +18 -0
  16. package/dist/types/core/forward.d.ts +24 -0
  17. package/dist/types/core/handoff.d.ts +10 -0
  18. package/dist/types/core/index.d.ts +14 -0
  19. package/dist/types/core/recall.d.ts +8 -0
  20. package/dist/types/core/rewrite.d.ts +63 -0
  21. package/dist/types/core/state.d.ts +22 -0
  22. package/dist/types/core/subagents.d.ts +10 -0
  23. package/dist/types/core/transcript.d.ts +9 -0
  24. package/dist/types/core/usage.d.ts +7 -0
  25. package/dist/types/core/window.d.ts +5 -0
  26. package/dist/types/transcript/format.d.ts +16 -0
  27. package/dist/types/transcript/parse.d.ts +45 -0
  28. package/package.json +15 -2
  29. package/src/cli/commands/mode.ts +2 -1
  30. package/src/cli/commands/status.ts +1 -1
  31. package/src/cli/proxy/stats.ts +1 -1
  32. package/src/cli/replay/index.ts +3 -3
  33. package/src/cli/sessions/index.ts +2 -2
  34. package/src/config/index.ts +4 -27
  35. package/src/context/content.ts +64 -1
  36. package/src/context/fold/strategies.ts +55 -10
  37. package/src/context/fold/turns.ts +22 -2
  38. package/src/context/prompts.ts +4 -3
  39. package/src/context/recall/index.ts +2 -3
  40. package/src/context/recall/rerank.ts +0 -8
  41. package/src/{proxy → core}/compaction.ts +56 -24
  42. package/src/core/config.ts +31 -0
  43. package/src/core/fold.ts +69 -0
  44. package/src/core/forward.ts +192 -0
  45. package/src/{proxy → core}/handoff.ts +23 -23
  46. package/src/core/index.ts +14 -0
  47. package/src/{proxy → core}/recall.ts +18 -20
  48. package/src/core/rewrite.ts +189 -0
  49. package/src/core/state.ts +40 -0
  50. package/src/core/subagents.ts +28 -0
  51. package/src/core/transcript.ts +44 -0
  52. package/src/{proxy → core}/usage.ts +34 -1
  53. package/src/core/window.ts +39 -0
  54. package/src/proxy/rewrite.ts +22 -139
  55. package/src/proxy/server.ts +21 -159
  56. package/src/proxy/sessions.ts +1 -1
  57. package/src/proxy/sources.ts +35 -0
  58. package/src/proxy/state.ts +2 -20
  59. package/src/proxy/window.ts +3 -33
  60. package/src/transcript/format.ts +1 -2
  61. package/src/transcript/subagents.ts +3 -3
  62. /package/src/{proxy → core}/estimate.ts +0 -0
  63. /package/src/{proxy → core}/fingerprint.ts +0 -0
package/README.md CHANGED
@@ -430,6 +430,142 @@ reads, and every token a fold rewrote is charged back at the cache-write price.
430
430
  deletes sessions that have been idle for 30 days unless `cleanupPeriodDays` in
431
431
  its `settings.json` says otherwise.
432
432
 
433
+ ## Embedding
434
+
435
+ The proxy is one host of a small library. Anything that sends Anthropic
436
+ Messages requests can use the same folding, recall and estimates: a
437
+ forwarding server, an agent loop of your own, a Cloudflare Durable Object,
438
+ another proxy. The core reads no files, no environment and no global
439
+ config, and it runs in Node, Bun, Deno and Workers.
440
+
441
+ ```sh
442
+ npm install @chroxify/eunoe
443
+ ```
444
+
445
+ ```ts
446
+ import { CORE_DEFAULTS, memoryStore, rewrite } from "@chroxify/eunoe"
447
+
448
+ const store = memoryStore()
449
+ const { body, note } = rewrite({
450
+ body: request, // the Messages request you were about to send
451
+ threadId: conversationId, // anything stable per conversation
452
+ config: { ...CORE_DEFAULTS, mode: "compact" },
453
+ store,
454
+ window: 200_000, // the model's context window
455
+ mayCompact: true, // false for count_tokens and other side calls
456
+ })
457
+ // send `body` instead of `request`; `note` says what happened
458
+ ```
459
+
460
+ `rewrite` is a pure function of the request and the thread's saved state,
461
+ so the same history always produces the same bytes and the prompt cache
462
+ holds. Everything it needs from outside comes in through three small
463
+ pieces:
464
+
465
+ | | |
466
+ |---|---|
467
+ | `ThreadStore` | `read(key)` and `save(key, state)`, synchronous. `memoryStore()` keeps state in memory; give it your own to persist across restarts. |
468
+ | `ThreadSources` | optional: `readHistory()` for the whole conversation when you send a shortened one, `findHandoff` and `readHandoff` for `{ee:continue:…}`, `guide` and `transcriptDir` when the agent can search a transcript. |
469
+ | `Reranker` | optional, for `rewriteAsync`: reorders recall candidates. `jevReranker(model, key)` is the one the proxy uses. |
470
+
471
+ In a Durable Object, SQLite-backed storage is synchronous, so the store is
472
+ two lines:
473
+
474
+ ```ts
475
+ const store = {
476
+ read: (key) => this.ctx.storage.kv.get(key) ?? {},
477
+ save: (key, state) => this.ctx.storage.kv.put(key, state),
478
+ }
479
+ ```
480
+
481
+ ### Folding at a boundary you know
482
+
483
+ A host often knows better than any token count when a step is over. After a
484
+ planning step, fold every earlier turn and keep only the plan whole:
485
+
486
+ ```ts
487
+ import { foldThread, pinTurns } from "@chroxify/eunoe"
488
+
489
+ messages.push({ role: "user", content: "Build step one." })
490
+ foldThread({ store, threadId, messages }, { at: "latest", pin: [planTurn] })
491
+ ```
492
+
493
+ Turns are numbered as `note.cutTurn` numbers them, from 0 for the first
494
+ prompt, and the boundary must be a turn that already exists, so fold once
495
+ the next step's prompt is in the list. Every turn before the boundary is
496
+ folded to the user's message, the evidence and the reply. Pinned turns are
497
+ sent whole, with their tool calls, and stay whole even when a later
498
+ automatic cut drops the turns around them. `pinTurns(target, [n], false)`
499
+ releases one. A host fold applies in every mode, `off` included, and its
500
+ output stays byte-stable from one request to the next.
501
+
502
+ ### The note
503
+
504
+ Every call returns a note: `action` (`pass`, `full` or `compact`),
505
+ `cutTurn`, `folded`, `omitted`, `pinned`, `estimate` in tokens, `fit` when
506
+ the request had to be trimmed to fit (`recut`, `bare` or `over`), and
507
+ `stuck` when even the cut leaves it over its target. `stuck` gives the
508
+ tokens in each part (`fixed`, `head`, `folded`, `live`) so the host can
509
+ act: start a fresh conversation with a handoff, or fold at its own
510
+ boundary. While a thread is stuck, eunoe holds what it cleared still
511
+ instead of clearing a little more on every request, so the cache keeps
512
+ hitting.
513
+
514
+ ### The whole proxy as one call
515
+
516
+ `createForwarder` is the proxy's request loop as a fetch handler: it
517
+ rewrites each `/v1/messages` request, forwards everything to the API,
518
+ streams the answer back, learns the thread's real token ratio from the
519
+ answer's `usage`, retries once with a corrected size after a "prompt is
520
+ too long", retries a stalled `count_tokens`, and looks the context window
521
+ up from the Models API. `eunoe serve` is this call with a file-backed
522
+ store, Claude Code's session files as sources and a request log.
523
+
524
+ ```ts
525
+ import { CORE_DEFAULTS, createForwarder, memoryStore } from "@chroxify/eunoe"
526
+
527
+ const forward = createForwarder({ store: memoryStore(), config: CORE_DEFAULTS })
528
+ Bun.serve({ port: 8787, fetch: forward }) // or Deno.serve, a Worker's fetch, …
529
+ ```
530
+
531
+ Threads are told apart by an `x-thread-id` header, or Claude Code's own
532
+ session id; pass `threadIdOf`, `sources`, `reranker`, `window` or `record`
533
+ to change any part, or `prepare` to take the rewrite over entirely. A host
534
+ that calls the API itself gets the same calibration with
535
+ `recordUsage(store, threadKey, sentBody, usage)` and
536
+ `recordOverflow(store, threadKey, sentBody, errorText)`.
537
+
538
+ | export | |
539
+ |---|---|
540
+ | `rewrite`, `rewriteAsync` | rewrite one request for one thread; the async one takes a reranker |
541
+ | `foldThread`, `pinTurns` | fold at your own boundary, keep chosen turns whole |
542
+ | `createForwarder` | the full proxy loop as a `(Request) => Promise<Response>` |
543
+ | `recordUsage`, `recordOverflow` | feed what the API said back into the thread's estimates |
544
+ | `createWindowLookup` | a model's context window from the Models API, cached |
545
+ | `memoryStore`, `threadKeyOf` | in-memory state, and the key a thread is saved under |
546
+ | `listSubagentRecall` | recall over what your own subagents printed |
547
+ | `renderTranscriptFiles`, `transcriptGuide` | the searchable per-turn transcript from your own messages, and the text that points the agent at it |
548
+ | `jevReranker`, `continueToken`, `watchUsage`, `claudeCodeSessionId` | the pieces the proxy uses, for hosts that want them |
549
+
550
+ What stays in the proxy is what only makes sense next to Claude Code on
551
+ your machine: reading its session files and subagent runs, finding earlier
552
+ sessions for `{ee:continue:…}`, rendering the markdown transcript, the
553
+ config file and the CLI.
554
+
555
+ Every export, with its signature, what it does and an example, is in
556
+ [`docs/api.md`](docs/api.md), which starts with where each kind of state and
557
+ data lives. [`examples/`](examples) runs as it is. [`tour.ts`](examples/tour.ts) walks
558
+ through every feature on real inputs and prints what each did: folding,
559
+ the full cut, evidence, recall, recall from host history, reranking,
560
+ handoffs, host folds and pins, `stuck`, calibration, custom storage, the
561
+ forwarder and transcripts. The others are hosts: a
562
+ [Durable Object](examples/durable-object.ts) per thread on its own storage,
563
+ tested in real `workerd`, a
564
+ [forwarding server](examples/forwarding-server.ts) on `node:http` with a
565
+ `/fold` endpoint, a [custom agent loop](examples/agent-loop.ts) that folds
566
+ at each step and keeps its plan pinned, and a
567
+ [Claude Code proxy](examples/proxy.ts) with a file-backed store.
568
+
433
569
  ## What it doesn't do
434
570
 
435
571
  - **It is a proxy, which makes it a single point of failure.** If it isn't
@@ -442,8 +578,9 @@ its `settings.json` says otherwise.
442
578
  502 that Claude Code retries, never a hang. Sessions that were already
443
579
  running keep the old address until restarted, so they wait for the proxy
444
580
  rather than falling back.
445
- - **It only understands Claude Code's shape of conversation.** Other
446
- Anthropic API clients pass through unchanged and get nothing out of it.
581
+ - **The proxy only understands Claude Code's shape of conversation.** Other
582
+ Anthropic API clients pass through it unchanged and get nothing out of
583
+ it; they can [embed the library](#embedding) instead.
447
584
  - **It can't recover what Claude Code already compacted,** so turn it on
448
585
  before a long session rather than after.
449
586
  - **Recall is keyword search.** Ask about "the deploy that failed" and it