@chroxify/eunoe 1.2.2 → 1.3.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 +150 -6
- package/dist/core/index.js +2354 -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 +16 -0
- package/dist/types/context/prompts.d.ts +24 -0
- package/dist/types/context/recall/index.d.ts +47 -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 +34 -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 +9 -0
- package/dist/types/core/rewrite.d.ts +63 -0
- package/dist/types/core/state.d.ts +25 -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 +8 -0
- package/dist/types/core/window.d.ts +8 -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/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 +63 -10
- package/src/context/fold/turns.ts +34 -2
- package/src/context/prompts.ts +4 -3
- package/src/context/recall/index.ts +8 -4
- package/src/context/recall/rerank.ts +0 -8
- package/src/{proxy → core}/compaction.ts +85 -28
- package/src/core/config.ts +31 -0
- package/src/core/fold.ts +71 -0
- package/src/core/forward.ts +194 -0
- package/src/{proxy → core}/handoff.ts +23 -23
- package/src/core/index.ts +14 -0
- package/src/core/recall.ts +80 -0
- package/src/core/rewrite.ts +201 -0
- package/src/core/state.ts +50 -0
- package/src/core/subagents.ts +28 -0
- package/src/core/transcript.ts +44 -0
- package/src/{proxy → core}/usage.ts +43 -1
- package/src/core/window.ts +69 -0
- package/src/proxy/rewrite.ts +22 -139
- package/src/proxy/server.ts +21 -159
- package/src/proxy/sessions.ts +1 -1
- package/src/proxy/sources.ts +35 -0
- package/src/proxy/state.ts +2 -20
- package/src/proxy/window.ts +3 -33
- package/src/transcript/format.ts +1 -2
- package/src/transcript/subagents.ts +3 -3
- package/src/proxy/recall.ts +0 -58
- /package/src/{proxy → core}/estimate.ts +0 -0
- /package/src/{proxy → core}/fingerprint.ts +0 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { type Config, claudeConfigDirs } from "../config"
|
|
2
|
+
import type { Message } from "../context/content"
|
|
3
|
+
import { searchGuide } from "../context/prompts"
|
|
4
|
+
import type { ThreadSources } from "../core/rewrite"
|
|
5
|
+
import { findSessionByPrefix } from "../transcript/locate"
|
|
6
|
+
import { sessionMessages } from "../transcript/messages"
|
|
7
|
+
import type { TranscriptRef } from "../transcript/search"
|
|
8
|
+
import { subagentChunks } from "../transcript/subagents"
|
|
9
|
+
import { titleOf } from "./sessions"
|
|
10
|
+
|
|
11
|
+
function readSession(file: string): Message[] {
|
|
12
|
+
try {
|
|
13
|
+
return sessionMessages(file)
|
|
14
|
+
} catch {
|
|
15
|
+
return []
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function sessionSources(transcript: TranscriptRef | null, config: Config, sessionId: string | null): ThreadSources {
|
|
20
|
+
const handoffs = {
|
|
21
|
+
findHandoff: (id: string) => {
|
|
22
|
+
const file = findSessionByPrefix(id, claudeConfigDirs(config), sessionId ?? undefined)
|
|
23
|
+
return file ? { label: titleOf(file) ?? id, source: file } : null
|
|
24
|
+
},
|
|
25
|
+
readHandoff: readSession,
|
|
26
|
+
}
|
|
27
|
+
if (!transcript) return handoffs
|
|
28
|
+
return {
|
|
29
|
+
...handoffs,
|
|
30
|
+
guide: searchGuide(transcript),
|
|
31
|
+
transcriptDir: transcript.mode !== "jsonl" ? transcript.dir : null,
|
|
32
|
+
readHistory: () => sessionMessages(transcript.jsonl),
|
|
33
|
+
listSubagentChunks: () => subagentChunks(transcript.jsonl),
|
|
34
|
+
}
|
|
35
|
+
}
|
package/src/proxy/state.ts
CHANGED
|
@@ -1,21 +1,7 @@
|
|
|
1
1
|
import { mkdirSync, readFileSync, statSync, writeFileSync } from "node:fs"
|
|
2
2
|
import path from "node:path"
|
|
3
3
|
import { paths } from "../config/paths"
|
|
4
|
-
|
|
5
|
-
export interface Handoff {
|
|
6
|
-
label: string
|
|
7
|
-
source: string
|
|
8
|
-
text: string
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export interface ThreadState {
|
|
12
|
-
handoffs?: Record<string, Handoff | null>
|
|
13
|
-
cut?: string
|
|
14
|
-
keep?: number
|
|
15
|
-
cleared?: number
|
|
16
|
-
ratio?: number
|
|
17
|
-
recalls?: Record<string, string>
|
|
18
|
-
}
|
|
4
|
+
import type { ThreadState, ThreadStore } from "../core/state"
|
|
19
5
|
|
|
20
6
|
function stateFile(key: string) {
|
|
21
7
|
return path.join(paths.threads, `${key.replace(/[^a-zA-Z0-9_-]/g, "_")}.json`)
|
|
@@ -63,8 +49,4 @@ export function saveState(key: string, state: ThreadState) {
|
|
|
63
49
|
if (version !== null) remember(file, version, structuredClone(state))
|
|
64
50
|
}
|
|
65
51
|
|
|
66
|
-
export
|
|
67
|
-
delete state.cut
|
|
68
|
-
delete state.keep
|
|
69
|
-
delete state.cleared
|
|
70
|
-
}
|
|
52
|
+
export const fileStore: ThreadStore = { read: readState, save: saveState }
|
package/src/proxy/window.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"
|
|
2
2
|
import { EUNOE_DIR, paths } from "../config/paths"
|
|
3
|
+
import { createWindowLookup } from "../core/window"
|
|
3
4
|
|
|
4
5
|
function readCache(): Map<string, number> {
|
|
5
6
|
try {
|
|
@@ -9,40 +10,9 @@ function readCache(): Map<string, number> {
|
|
|
9
10
|
}
|
|
10
11
|
}
|
|
11
12
|
|
|
12
|
-
|
|
13
|
-
const lookups = new Map<string, Promise<number | null>>()
|
|
14
|
-
|
|
15
|
-
function lookupHeaders(requestHeaders: Headers): Headers {
|
|
16
|
-
const headers = new Headers()
|
|
17
|
-
for (const name of ["authorization", "x-api-key", "anthropic-version", "anthropic-beta"]) {
|
|
18
|
-
const value = requestHeaders.get(name)
|
|
19
|
-
if (value) headers.set(name, value)
|
|
20
|
-
}
|
|
21
|
-
if (!headers.has("anthropic-version")) headers.set("anthropic-version", "2023-06-01")
|
|
22
|
-
return headers
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
async function lookup(id: string, requestHeaders: Headers, upstream: string): Promise<number | null> {
|
|
26
|
-
const response = await fetch(`${upstream}/v1/models/${encodeURIComponent(id)}`, { headers: lookupHeaders(requestHeaders), signal: AbortSignal.timeout(5_000) })
|
|
27
|
-
if (!response.ok) return null
|
|
28
|
-
const info = (await response.json()) as { max_input_tokens?: number }
|
|
29
|
-
if (typeof info.max_input_tokens !== "number" || info.max_input_tokens <= 0) return null
|
|
30
|
-
windows.set(id, info.max_input_tokens)
|
|
13
|
+
function saveCache(windows: Map<string, number>) {
|
|
31
14
|
mkdirSync(EUNOE_DIR, { recursive: true })
|
|
32
15
|
writeFileSync(paths.models, JSON.stringify(Object.fromEntries(windows), null, 2))
|
|
33
|
-
return info.max_input_tokens
|
|
34
16
|
}
|
|
35
17
|
|
|
36
|
-
export
|
|
37
|
-
const id = String(model ?? "").replace(/\[[^\]]*\]$/, "")
|
|
38
|
-
if (!id) return Promise.resolve(null)
|
|
39
|
-
const known = windows.get(id)
|
|
40
|
-
if (known) return Promise.resolve(known)
|
|
41
|
-
const inFlight = lookups.get(id)
|
|
42
|
-
if (inFlight) return inFlight
|
|
43
|
-
const pending = lookup(id, requestHeaders, upstream)
|
|
44
|
-
.catch(() => null)
|
|
45
|
-
.finally(() => lookups.delete(id))
|
|
46
|
-
lookups.set(id, pending)
|
|
47
|
-
return pending
|
|
48
|
-
}
|
|
18
|
+
export const resolveWindow = createWindowLookup({ known: readCache(), onLearn: saveCache })
|
package/src/transcript/format.ts
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { clip } from "../context/content"
|
|
2
2
|
import type { ToolPart, TranscriptFormat, Turn } from "./parse"
|
|
3
|
-
import type { SubagentRun } from "./subagents"
|
|
4
3
|
|
|
5
4
|
export interface SubagentRecord {
|
|
6
|
-
run:
|
|
5
|
+
run: { description: string }
|
|
7
6
|
turns: Turn[]
|
|
8
7
|
parentTurn: number
|
|
9
8
|
file: string
|
|
@@ -2,7 +2,8 @@ import { readFileSync, statSync } from "node:fs"
|
|
|
2
2
|
import path from "node:path"
|
|
3
3
|
import { blocks, clip, contentBlocks, type Message } from "../context/content"
|
|
4
4
|
import { splitTurns } from "../context/fold/turns"
|
|
5
|
-
import {
|
|
5
|
+
import type { RecallChunk } from "../context/recall"
|
|
6
|
+
import { subagentRecall } from "../core/subagents"
|
|
6
7
|
import { readSessionEntries } from "./entries"
|
|
7
8
|
import { listEntries } from "./locate"
|
|
8
9
|
import { sessionMessages } from "./messages"
|
|
@@ -163,8 +164,7 @@ export function subagentChunks(sessionFile: string): RecallChunk[] {
|
|
|
163
164
|
const parentTurn = launchTurn(run, launches)
|
|
164
165
|
const hit = chunkCache.get(key)
|
|
165
166
|
if (hit && hit.mtimeMs === run.mtimeMs && hit.parentTurn === parentTurn) return hit.chunks
|
|
166
|
-
const
|
|
167
|
-
const chunks = recallChunks([...subagentMessages(run), { role: "user", content: "" }], new Map()).map((chunk) => ({ ...chunk, from, turn: parentTurn }))
|
|
167
|
+
const chunks = subagentRecall(subagentMessages(run), run.description, parentTurn)
|
|
168
168
|
chunkCache.set(key, { mtimeMs: run.mtimeMs, parentTurn, chunks })
|
|
169
169
|
return chunks
|
|
170
170
|
})
|
package/src/proxy/recall.ts
DELETED
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
import { type Body, blocks, resultText } from "../context/content"
|
|
2
|
-
import { livePrompt, type RecallChunk, type Reranker, recallChunks, recallFor, recallForWith, withRecall } from "../context/recall"
|
|
3
|
-
import { sessionMessages } from "../transcript/messages"
|
|
4
|
-
import { subagentChunks } from "../transcript/subagents"
|
|
5
|
-
import { messageFingerprint } from "./fingerprint"
|
|
6
|
-
import { activeHandoffs, handoffChunks } from "./handoff"
|
|
7
|
-
import type { Rewrite, RewriteInput } from "./rewrite"
|
|
8
|
-
import { readState, saveState } from "./state"
|
|
9
|
-
|
|
10
|
-
export const wantsRecall = (input: RewriteInput) => (input.config.eval?.recall ?? true) && input.config.mode !== "off"
|
|
11
|
-
|
|
12
|
-
export const activeRecalls = (recalls: Record<string, string> = {}) => Object.fromEntries(Object.entries(recalls).filter(([, text]) => text))
|
|
13
|
-
|
|
14
|
-
const touched = (input: RewriteInput, result: Rewrite) => result.threadKey !== null && !(result.body === input.body && result.note.action === "pass")
|
|
15
|
-
|
|
16
|
-
function droppedChunks(input: RewriteInput, result: Rewrite): RecallChunk[] {
|
|
17
|
-
const transcript = input.transcript?.jsonl ?? null
|
|
18
|
-
const kept = new Map<string, string>()
|
|
19
|
-
for (const message of result.body.messages) for (const b of blocks(message)) if (b.type === "tool_result") kept.set(b.tool_use_id, resultText(b))
|
|
20
|
-
const own = recallChunks(input.body.messages, kept)
|
|
21
|
-
const session = own.length > 0 || !transcript ? own : recallChunks([...sessionMessages(transcript), { role: "user", content: "" }], kept)
|
|
22
|
-
const withSubagents = transcript ? [...session, ...subagentChunks(transcript)] : session
|
|
23
|
-
const handoffs = result.threadKey ? activeHandoffs(readState(result.threadKey).handoffs) : {}
|
|
24
|
-
return [...handoffChunks(handoffs), ...withSubagents]
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function recallTarget(original: Body, result: Rewrite): { key: string; text: string } | null {
|
|
28
|
-
const live = livePrompt(original.messages)
|
|
29
|
-
return live && result.threadKey ? { key: messageFingerprint(original.messages[live.index]), text: live.text } : null
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export function applyRecall(input: RewriteInput, result: Rewrite): Rewrite {
|
|
33
|
-
if (!touched(input, result) || !result.threadKey) return result
|
|
34
|
-
const state = readState(result.threadKey)
|
|
35
|
-
state.recalls ??= {}
|
|
36
|
-
const target = recallTarget(input.body, result)
|
|
37
|
-
let note = result.note
|
|
38
|
-
if (target && !(target.key in state.recalls)) {
|
|
39
|
-
state.recalls[target.key] = recallFor(target.text, droppedChunks(input, result), input.config.eval?.recallOptions) ?? ""
|
|
40
|
-
saveState(result.threadKey, state)
|
|
41
|
-
note = { ...note, recalled: state.recalls[target.key].length }
|
|
42
|
-
}
|
|
43
|
-
const messages = withRecall(result.body.messages, messageFingerprint, activeRecalls(state.recalls))
|
|
44
|
-
return messages === result.body.messages ? { ...result, note } : { ...result, body: { ...result.body, messages }, note }
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
export async function prefetchFor(input: RewriteInput, result: Rewrite, rerank: Reranker): Promise<number | null> {
|
|
48
|
-
if (!touched(input, result) || !result.threadKey) return null
|
|
49
|
-
const target = recallTarget(input.body, result)
|
|
50
|
-
if (!target) return null
|
|
51
|
-
const state = readState(result.threadKey)
|
|
52
|
-
state.recalls ??= {}
|
|
53
|
-
if (target.key in state.recalls) return null
|
|
54
|
-
const options = { candidates: input.config.eval?.rerank?.candidates, ...input.config.eval?.recallOptions }
|
|
55
|
-
state.recalls[target.key] = (await recallForWith(target.text, droppedChunks(input, result), rerank, options)) ?? ""
|
|
56
|
-
saveState(result.threadKey, state)
|
|
57
|
-
return state.recalls[target.key].length
|
|
58
|
-
}
|
|
File without changes
|
|
File without changes
|