@chroxify/eunoe 1.2.1 → 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.
- package/README.md +143 -5
- package/dist/core/index.js +2235 -0
- package/dist/types/context/content.d.ts +31 -0
- package/dist/types/context/fold/evidence.d.ts +20 -0
- package/dist/types/context/fold/index.d.ts +11 -0
- package/dist/types/context/fold/strategies.d.ts +20 -0
- package/dist/types/context/fold/turns.d.ts +14 -0
- package/dist/types/context/prompts.d.ts +24 -0
- package/dist/types/context/recall/index.d.ts +44 -0
- package/dist/types/context/recall/rerank.d.ts +3 -0
- package/dist/types/core/compaction.d.ts +32 -0
- package/dist/types/core/config.d.ts +33 -0
- package/dist/types/core/estimate.d.ts +9 -0
- package/dist/types/core/fingerprint.d.ts +10 -0
- package/dist/types/core/fold.d.ts +18 -0
- package/dist/types/core/forward.d.ts +24 -0
- package/dist/types/core/handoff.d.ts +10 -0
- package/dist/types/core/index.d.ts +14 -0
- package/dist/types/core/recall.d.ts +8 -0
- package/dist/types/core/rewrite.d.ts +63 -0
- package/dist/types/core/state.d.ts +22 -0
- package/dist/types/core/subagents.d.ts +10 -0
- package/dist/types/core/transcript.d.ts +9 -0
- package/dist/types/core/usage.d.ts +7 -0
- package/dist/types/core/window.d.ts +5 -0
- package/dist/types/transcript/format.d.ts +16 -0
- package/dist/types/transcript/parse.d.ts +45 -0
- package/package.json +15 -2
- package/src/cli/commands/mode.ts +2 -1
- package/src/cli/commands/serve.ts +2 -0
- package/src/cli/commands/status.ts +1 -1
- package/src/cli/proxy/stats.ts +1 -1
- package/src/cli/replay/index.ts +3 -3
- package/src/cli/sessions/index.ts +2 -2
- package/src/config/index.ts +4 -27
- package/src/context/content.ts +64 -1
- package/src/context/fold/strategies.ts +55 -10
- package/src/context/fold/turns.ts +22 -2
- package/src/context/prompts.ts +4 -3
- package/src/context/recall/index.ts +259 -35
- package/src/context/recall/rerank.ts +0 -8
- package/src/{proxy → core}/compaction.ts +74 -24
- package/src/core/config.ts +31 -0
- package/src/core/fold.ts +69 -0
- package/src/core/forward.ts +192 -0
- package/src/{proxy → core}/handoff.ts +23 -23
- package/src/core/index.ts +14 -0
- package/src/{proxy → core}/recall.ts +18 -20
- package/src/core/rewrite.ts +189 -0
- package/src/core/state.ts +40 -0
- package/src/core/subagents.ts +28 -0
- package/src/core/transcript.ts +44 -0
- package/src/{proxy → core}/usage.ts +34 -1
- package/src/core/window.ts +39 -0
- package/src/proxy/rewrite.ts +22 -139
- package/src/proxy/server.ts +24 -137
- package/src/proxy/sessions.ts +1 -1
- package/src/proxy/sources.ts +35 -0
- package/src/proxy/state.ts +32 -21
- package/src/proxy/warm.ts +68 -0
- package/src/proxy/window.ts +3 -33
- package/src/transcript/entries.ts +114 -0
- package/src/transcript/format.ts +1 -2
- package/src/transcript/messages.ts +14 -11
- package/src/transcript/render-worker.ts +24 -0
- package/src/transcript/render.ts +12 -3
- package/src/transcript/search.ts +46 -5
- package/src/transcript/subagents.ts +55 -34
- /package/src/{proxy → core}/estimate.ts +0 -0
- /package/src/{proxy → core}/fingerprint.ts +0 -0
package/README.md
CHANGED
|
@@ -407,9 +407,10 @@ Recall works out of the box, but it gets noticeably better with
|
|
|
407
407
|
[Jev](https://typesafe.ai) reordering the candidates. To turn that on, put a
|
|
408
408
|
Typesafe API key in `~/.eunoe/typesafe-key` or in the `TYPESAFE_API_KEY`
|
|
409
409
|
environment variable, and `eunoe status` will show the reranker as active.
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
410
|
+
The cost is time: the first request of each new message waits for Jev, about
|
|
411
|
+
300 ms and at most four seconds, after which the recall falls back to the
|
|
412
|
+
plain keyword order. Every other request is unaffected. To switch it off, run
|
|
413
|
+
`eunoe settings set rerank off`.
|
|
413
414
|
|
|
414
415
|
### Logs
|
|
415
416
|
|
|
@@ -429,6 +430,142 @@ reads, and every token a fold rewrote is charged back at the cache-write price.
|
|
|
429
430
|
deletes sessions that have been idle for 30 days unless `cleanupPeriodDays` in
|
|
430
431
|
its `settings.json` says otherwise.
|
|
431
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
|
+
|
|
432
569
|
## What it doesn't do
|
|
433
570
|
|
|
434
571
|
- **It is a proxy, which makes it a single point of failure.** If it isn't
|
|
@@ -441,8 +578,9 @@ its `settings.json` says otherwise.
|
|
|
441
578
|
502 that Claude Code retries, never a hang. Sessions that were already
|
|
442
579
|
running keep the old address until restarted, so they wait for the proxy
|
|
443
580
|
rather than falling back.
|
|
444
|
-
- **
|
|
445
|
-
Anthropic API clients pass through unchanged and get nothing out of
|
|
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.
|
|
446
584
|
- **It can't recover what Claude Code already compacted,** so turn it on
|
|
447
585
|
before a long session rather than after.
|
|
448
586
|
- **Recall is keyword search.** Ask about "the deploy that failed" and it
|