@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.
Files changed (64) hide show
  1. package/README.md +150 -6
  2. package/dist/core/index.js +2354 -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 +16 -0
  8. package/dist/types/context/prompts.d.ts +24 -0
  9. package/dist/types/context/recall/index.d.ts +47 -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 +34 -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 +9 -0
  20. package/dist/types/core/rewrite.d.ts +63 -0
  21. package/dist/types/core/state.d.ts +25 -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 +8 -0
  25. package/dist/types/core/window.d.ts +8 -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 +63 -10
  37. package/src/context/fold/turns.ts +34 -2
  38. package/src/context/prompts.ts +4 -3
  39. package/src/context/recall/index.ts +8 -4
  40. package/src/context/recall/rerank.ts +0 -8
  41. package/src/{proxy → core}/compaction.ts +85 -28
  42. package/src/core/config.ts +31 -0
  43. package/src/core/fold.ts +71 -0
  44. package/src/core/forward.ts +194 -0
  45. package/src/{proxy → core}/handoff.ts +23 -23
  46. package/src/core/index.ts +14 -0
  47. package/src/core/recall.ts +80 -0
  48. package/src/core/rewrite.ts +201 -0
  49. package/src/core/state.ts +50 -0
  50. package/src/core/subagents.ts +28 -0
  51. package/src/core/transcript.ts +44 -0
  52. package/src/{proxy → core}/usage.ts +43 -1
  53. package/src/core/window.ts +69 -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/recall.ts +0 -58
  63. /package/src/{proxy → core}/estimate.ts +0 -0
  64. /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
+ }
@@ -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 function resetCut(state: ThreadState) {
67
- delete state.cut
68
- delete state.keep
69
- delete state.cleared
70
- }
52
+ export const fileStore: ThreadStore = { read: readState, save: saveState }
@@ -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
- const windows = readCache()
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 function resolveWindow(model: unknown, requestHeaders: Headers, upstream: string): Promise<number | null> {
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 })
@@ -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: SubagentRun
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 { type RecallChunk, recallChunks } from "../context/recall"
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 from = `subagent "${run.description}"`
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
  })
@@ -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