compressed-context 0.1.0 → 0.1.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/package.json +64 -64
- package/src/compression/active-summary-cache.ts +50 -50
- package/src/compression/adopt.ts +57 -57
- package/src/compression/auto.ts +372 -372
- package/src/compression/babel.ts +85 -85
- package/src/compression/block.ts +25 -25
- package/src/compression/cache-history.ts +75 -75
- package/src/compression/compaction.ts +189 -189
- package/src/compression/decompress-tool.ts +74 -74
- package/src/compression/decompress.ts +292 -292
- package/src/compression/deleted-sessions.ts +24 -24
- package/src/compression/distill.ts +220 -220
- package/src/compression/events.ts +275 -275
- package/src/compression/markdown.ts +53 -53
- package/src/compression/normalize.ts +114 -114
- package/src/compression/render.ts +70 -70
- package/src/compression/schema.ts +84 -84
- package/src/compression/select.ts +123 -123
- package/src/compression/session-message.ts +112 -112
- package/src/compression/settings-rpc.ts +111 -111
- package/src/compression/settings.ts +175 -175
- package/src/compression/state.ts +248 -248
- package/src/compression/summarize.ts +595 -595
- package/src/compression/summary.ts +73 -73
- package/src/compression/tool.ts +142 -142
- package/src/compression/transcript-store.ts +258 -258
- package/src/compression/transcript.ts +63 -63
- package/src/compression/validate.ts +81 -81
- package/src/i18n/en.ts +93 -93
- package/src/i18n/index.ts +28 -28
- package/src/i18n/types.ts +89 -89
- package/src/i18n/zh.ts +93 -93
- package/src/index.ts +108 -108
- package/src/shared/blocks.ts +17 -17
- package/src/shared/cache.ts +199 -199
- package/src/shared/format.ts +23 -23
- package/src/shared/media.ts +184 -184
- package/src/shared/parse.ts +25 -25
- package/src/shared/parts.ts +72 -72
- package/src/shared/rpc.ts +145 -145
- package/src/shared/scan.ts +32 -32
- package/src/shared/settings.ts +471 -471
- package/src/shared/stats.ts +142 -142
- package/src/shared/tokens.ts +259 -259
- package/src/tui/chart/cache-chart.ts +237 -237
- package/src/tui/chart/color.ts +58 -58
- package/src/tui/index.tsx +111 -111
- package/src/tui/panel/cache.tsx +203 -203
- package/src/tui/panel/data.ts +105 -105
- package/src/tui/panel/index.tsx +176 -176
- package/src/tui/panel/summaries.tsx +192 -192
- package/src/tui/settings/dialogs.tsx +215 -215
- package/src/tui/settings/options.ts +238 -238
- package/src/tui/settings.tsx +418 -418
- package/src/tui/types.ts +4 -4
package/package.json
CHANGED
|
@@ -1,64 +1,64 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "compressed-context",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "OpenCode 上下文管理插件:按需压缩、自包含摘要",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"license": "MIT",
|
|
7
|
-
"author": "1cyberlangke1",
|
|
8
|
-
"homepage": "https://github.com/1cyberlangke1/compressed-context#readme",
|
|
9
|
-
"repository": {
|
|
10
|
-
"type": "git",
|
|
11
|
-
"url": "git+https://github.com/1cyberlangke1/compressed-context.git"
|
|
12
|
-
},
|
|
13
|
-
"bugs": {
|
|
14
|
-
"url": "https://github.com/1cyberlangke1/compressed-context/issues"
|
|
15
|
-
},
|
|
16
|
-
"keywords": [
|
|
17
|
-
"opencode",
|
|
18
|
-
"plugin",
|
|
19
|
-
"context",
|
|
20
|
-
"compression",
|
|
21
|
-
"summarization",
|
|
22
|
-
"llm"
|
|
23
|
-
],
|
|
24
|
-
"exports": {
|
|
25
|
-
".": "./src/index.ts",
|
|
26
|
-
"./tui": "./src/tui/index.tsx"
|
|
27
|
-
},
|
|
28
|
-
"files": [
|
|
29
|
-
"src"
|
|
30
|
-
],
|
|
31
|
-
"peerDependencies": {
|
|
32
|
-
"@opencode/plugin": "^2.0.32",
|
|
33
|
-
"@opentui/core": ">=0.5.17",
|
|
34
|
-
"@opentui/solid": ">=0.5.17",
|
|
35
|
-
"solid-js": ">=1.9.0"
|
|
36
|
-
},
|
|
37
|
-
"scripts": {
|
|
38
|
-
"typecheck": "tsc --noEmit",
|
|
39
|
-
"lint": "eslint .",
|
|
40
|
-
"check": "pnpm typecheck && pnpm lint && pnpm test && pnpm test:tui",
|
|
41
|
-
"test": "vitest run",
|
|
42
|
-
"test:tui": "cd tui-test && bun test",
|
|
43
|
-
"test:e2e:nudge": "bun scripts/e2e-nudge.mjs",
|
|
44
|
-
"test:e2e:tool-loop": "bun scripts/e2e-tool-loop.mjs",
|
|
45
|
-
"test:watch": "vitest"
|
|
46
|
-
},
|
|
47
|
-
"devDependencies": {
|
|
48
|
-
"@eslint/js": "^10.0.1",
|
|
49
|
-
"@opencode/plugin": "^2.0.32",
|
|
50
|
-
"@opencode/theme": "2.0.32",
|
|
51
|
-
"@opentui/core": "0.5.17",
|
|
52
|
-
"@opentui/solid": "0.5.17",
|
|
53
|
-
"@types/node": "^26.6.5",
|
|
54
|
-
"eslint": "^10.13.0",
|
|
55
|
-
"solid-js": "1.9.15",
|
|
56
|
-
"typescript": "^6.0.3",
|
|
57
|
-
"typescript-eslint": "^8.71.2",
|
|
58
|
-
"vitest": "^5.0.5"
|
|
59
|
-
},
|
|
60
|
-
"dependencies": {
|
|
61
|
-
"effect": "4.0.1",
|
|
62
|
-
"jsonrepair": "^3.15.0"
|
|
63
|
-
}
|
|
64
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "compressed-context",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "OpenCode 上下文管理插件:按需压缩、自包含摘要",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": "1cyberlangke1",
|
|
8
|
+
"homepage": "https://github.com/1cyberlangke1/compressed-context#readme",
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/1cyberlangke1/compressed-context.git"
|
|
12
|
+
},
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/1cyberlangke1/compressed-context/issues"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"opencode",
|
|
18
|
+
"plugin",
|
|
19
|
+
"context",
|
|
20
|
+
"compression",
|
|
21
|
+
"summarization",
|
|
22
|
+
"llm"
|
|
23
|
+
],
|
|
24
|
+
"exports": {
|
|
25
|
+
".": "./src/index.ts",
|
|
26
|
+
"./tui": "./src/tui/index.tsx"
|
|
27
|
+
},
|
|
28
|
+
"files": [
|
|
29
|
+
"src"
|
|
30
|
+
],
|
|
31
|
+
"peerDependencies": {
|
|
32
|
+
"@opencode/plugin": "^2.0.32",
|
|
33
|
+
"@opentui/core": ">=0.5.17",
|
|
34
|
+
"@opentui/solid": ">=0.5.17",
|
|
35
|
+
"solid-js": ">=1.9.0"
|
|
36
|
+
},
|
|
37
|
+
"scripts": {
|
|
38
|
+
"typecheck": "tsc --noEmit",
|
|
39
|
+
"lint": "eslint .",
|
|
40
|
+
"check": "pnpm typecheck && pnpm lint && pnpm test && pnpm test:tui",
|
|
41
|
+
"test": "vitest run",
|
|
42
|
+
"test:tui": "cd tui-test && bun test",
|
|
43
|
+
"test:e2e:nudge": "bun scripts/e2e-nudge.mjs",
|
|
44
|
+
"test:e2e:tool-loop": "bun scripts/e2e-tool-loop.mjs",
|
|
45
|
+
"test:watch": "vitest"
|
|
46
|
+
},
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"@eslint/js": "^10.0.1",
|
|
49
|
+
"@opencode/plugin": "^2.0.32",
|
|
50
|
+
"@opencode/theme": "2.0.32",
|
|
51
|
+
"@opentui/core": "0.5.17",
|
|
52
|
+
"@opentui/solid": "0.5.17",
|
|
53
|
+
"@types/node": "^26.6.5",
|
|
54
|
+
"eslint": "^10.13.0",
|
|
55
|
+
"solid-js": "1.9.15",
|
|
56
|
+
"typescript": "^6.0.3",
|
|
57
|
+
"typescript-eslint": "^8.71.2",
|
|
58
|
+
"vitest": "^5.0.5"
|
|
59
|
+
},
|
|
60
|
+
"dependencies": {
|
|
61
|
+
"effect": "4.0.1",
|
|
62
|
+
"jsonrepair": "^3.15.0"
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -1,50 +1,50 @@
|
|
|
1
|
-
import { renderAll, type RenderOptions } from "./render.ts"
|
|
2
|
-
import type { State } from "./state.ts"
|
|
3
|
-
|
|
4
|
-
interface Entry {
|
|
5
|
-
readonly revision: string
|
|
6
|
-
readonly text: string
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
const LIMIT = 32
|
|
10
|
-
const cache = new Map<string, Entry>()
|
|
11
|
-
|
|
12
|
-
/** 输入:状态 + 渲染选项;输出:只由块身份决定的 revision。 */
|
|
13
|
-
function revisionOf(state: State, options: RenderOptions): string {
|
|
14
|
-
return JSON.stringify({
|
|
15
|
-
blocks: state.blocks.map((block) => [
|
|
16
|
-
block.id,
|
|
17
|
-
block.tier,
|
|
18
|
-
block.order,
|
|
19
|
-
block.at,
|
|
20
|
-
block.sourceBlockIds ?? [],
|
|
21
|
-
]),
|
|
22
|
-
pending: state.pending?.id ?? null,
|
|
23
|
-
options,
|
|
24
|
-
})
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/** 输入:sessionID + 状态 + 渲染选项;输出:活跃摘要文本。 */
|
|
28
|
-
export function renderActiveSummary(
|
|
29
|
-
sessionID: string,
|
|
30
|
-
state: State,
|
|
31
|
-
options: RenderOptions,
|
|
32
|
-
): string {
|
|
33
|
-
const revision = revisionOf(state, options)
|
|
34
|
-
const hit = cache.get(sessionID)
|
|
35
|
-
if (hit?.revision === revision) return hit.text
|
|
36
|
-
const text = renderAll(state.blocks, state.pending, options)
|
|
37
|
-
cache.delete(sessionID)
|
|
38
|
-
cache.set(sessionID, { revision, text })
|
|
39
|
-
while (cache.size > LIMIT) {
|
|
40
|
-
const oldest = cache.keys().next().value
|
|
41
|
-
if (oldest === undefined) break
|
|
42
|
-
cache.delete(oldest)
|
|
43
|
-
}
|
|
44
|
-
return text
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/** 输入:sessionID;输出:无;预期:会话删除时丢掉活跃摘要缓存。 */
|
|
48
|
-
export function clearActiveSummary(sessionID: string): void {
|
|
49
|
-
cache.delete(sessionID)
|
|
50
|
-
}
|
|
1
|
+
import { renderAll, type RenderOptions } from "./render.ts"
|
|
2
|
+
import type { State } from "./state.ts"
|
|
3
|
+
|
|
4
|
+
interface Entry {
|
|
5
|
+
readonly revision: string
|
|
6
|
+
readonly text: string
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const LIMIT = 32
|
|
10
|
+
const cache = new Map<string, Entry>()
|
|
11
|
+
|
|
12
|
+
/** 输入:状态 + 渲染选项;输出:只由块身份决定的 revision。 */
|
|
13
|
+
function revisionOf(state: State, options: RenderOptions): string {
|
|
14
|
+
return JSON.stringify({
|
|
15
|
+
blocks: state.blocks.map((block) => [
|
|
16
|
+
block.id,
|
|
17
|
+
block.tier,
|
|
18
|
+
block.order,
|
|
19
|
+
block.at,
|
|
20
|
+
block.sourceBlockIds ?? [],
|
|
21
|
+
]),
|
|
22
|
+
pending: state.pending?.id ?? null,
|
|
23
|
+
options,
|
|
24
|
+
})
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** 输入:sessionID + 状态 + 渲染选项;输出:活跃摘要文本。 */
|
|
28
|
+
export function renderActiveSummary(
|
|
29
|
+
sessionID: string,
|
|
30
|
+
state: State,
|
|
31
|
+
options: RenderOptions,
|
|
32
|
+
): string {
|
|
33
|
+
const revision = revisionOf(state, options)
|
|
34
|
+
const hit = cache.get(sessionID)
|
|
35
|
+
if (hit?.revision === revision) return hit.text
|
|
36
|
+
const text = renderAll(state.blocks, state.pending, options)
|
|
37
|
+
cache.delete(sessionID)
|
|
38
|
+
cache.set(sessionID, { revision, text })
|
|
39
|
+
while (cache.size > LIMIT) {
|
|
40
|
+
const oldest = cache.keys().next().value
|
|
41
|
+
if (oldest === undefined) break
|
|
42
|
+
cache.delete(oldest)
|
|
43
|
+
}
|
|
44
|
+
return text
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** 输入:sessionID;输出:无;预期:会话删除时丢掉活跃摘要缓存。 */
|
|
48
|
+
export function clearActiveSummary(sessionID: string): void {
|
|
49
|
+
cache.delete(sessionID)
|
|
50
|
+
}
|
package/src/compression/adopt.ts
CHANGED
|
@@ -1,57 +1,57 @@
|
|
|
1
|
-
import type { Plugin } from "@opencode/plugin"
|
|
2
|
-
|
|
3
|
-
import { createT1Block } from "./block.ts"
|
|
4
|
-
import { toMarkdownSummary } from "./markdown.ts"
|
|
5
|
-
import { isCheckpoint, messageText, type MessageLike } from "./select.ts"
|
|
6
|
-
import { loadState, saveState } from "./state.ts"
|
|
7
|
-
|
|
8
|
-
/** 原生 checkpoint 里抽出的摘要与 recent 上下文。 */
|
|
9
|
-
export interface NativeCheckpoint {
|
|
10
|
-
readonly summary: string
|
|
11
|
-
readonly recent?: string
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* 输入:OpenCode checkpoint 文本。
|
|
16
|
-
* 输出:`<summary>` / `<recent-context>` 内容;不是 checkpoint 时 undefined。
|
|
17
|
-
* 预期:只做只读解析,不改原文。
|
|
18
|
-
*/
|
|
19
|
-
export function extractCheckpoint(text: string): NativeCheckpoint | undefined {
|
|
20
|
-
if (!text.trimStart().startsWith("<conversation-checkpoint>")) return undefined
|
|
21
|
-
const summary = /<summary>\n?([\s\S]*?)\n?<\/summary>/.exec(text)?.[1]?.trim()
|
|
22
|
-
if (summary === undefined || summary.length === 0) return undefined
|
|
23
|
-
const recent = /<recent-context>\n?([\s\S]*?)\n?<\/recent-context>/.exec(text)?.[1]?.trim()
|
|
24
|
-
return recent === undefined || recent.length === 0 ? { summary } : { summary, recent }
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* 输入:插件上下文 + sessionID + 当前请求消息。
|
|
29
|
-
* 输出:是否收养了原生摘要。
|
|
30
|
-
* 预期:仅当插件还没有任何块时,把 OpenCode 自己的 checkpoint 摘要当作 b1:T1;
|
|
31
|
-
* 它没有原始 transcript,fromId/toId 指向 checkpoint 消息本身。
|
|
32
|
-
*/
|
|
33
|
-
export async function adoptNativeCheckpoint(
|
|
34
|
-
ctx: Plugin.Context,
|
|
35
|
-
sessionID: string,
|
|
36
|
-
messages: readonly MessageLike[],
|
|
37
|
-
): Promise<boolean> {
|
|
38
|
-
const state = await loadState(ctx.storage, sessionID)
|
|
39
|
-
if (state.blocks.length > 0 || state.pending !== undefined || state.terminal !== undefined) return false
|
|
40
|
-
|
|
41
|
-
const checkpoint = messages.findLast((message) => isCheckpoint(message))
|
|
42
|
-
if (checkpoint === undefined) return false
|
|
43
|
-
const parsed = extractCheckpoint(messageText(checkpoint))
|
|
44
|
-
if (parsed === undefined) return false
|
|
45
|
-
|
|
46
|
-
const summary = toMarkdownSummary(parsed.summary)
|
|
47
|
-
const block = createT1Block({
|
|
48
|
-
blocks: [],
|
|
49
|
-
summary,
|
|
50
|
-
// 原生摘要没有原始 transcript;空范围让 decompress full 返回 unavailable,而不是伪造原文。
|
|
51
|
-
fromId: "",
|
|
52
|
-
toId: "",
|
|
53
|
-
sourceChars: parsed.summary.length,
|
|
54
|
-
})
|
|
55
|
-
await saveState(ctx.storage, sessionID, { blocks: [block], attempts: 0 })
|
|
56
|
-
return true
|
|
57
|
-
}
|
|
1
|
+
import type { Plugin } from "@opencode/plugin"
|
|
2
|
+
|
|
3
|
+
import { createT1Block } from "./block.ts"
|
|
4
|
+
import { toMarkdownSummary } from "./markdown.ts"
|
|
5
|
+
import { isCheckpoint, messageText, type MessageLike } from "./select.ts"
|
|
6
|
+
import { loadState, saveState } from "./state.ts"
|
|
7
|
+
|
|
8
|
+
/** 原生 checkpoint 里抽出的摘要与 recent 上下文。 */
|
|
9
|
+
export interface NativeCheckpoint {
|
|
10
|
+
readonly summary: string
|
|
11
|
+
readonly recent?: string
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* 输入:OpenCode checkpoint 文本。
|
|
16
|
+
* 输出:`<summary>` / `<recent-context>` 内容;不是 checkpoint 时 undefined。
|
|
17
|
+
* 预期:只做只读解析,不改原文。
|
|
18
|
+
*/
|
|
19
|
+
export function extractCheckpoint(text: string): NativeCheckpoint | undefined {
|
|
20
|
+
if (!text.trimStart().startsWith("<conversation-checkpoint>")) return undefined
|
|
21
|
+
const summary = /<summary>\n?([\s\S]*?)\n?<\/summary>/.exec(text)?.[1]?.trim()
|
|
22
|
+
if (summary === undefined || summary.length === 0) return undefined
|
|
23
|
+
const recent = /<recent-context>\n?([\s\S]*?)\n?<\/recent-context>/.exec(text)?.[1]?.trim()
|
|
24
|
+
return recent === undefined || recent.length === 0 ? { summary } : { summary, recent }
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* 输入:插件上下文 + sessionID + 当前请求消息。
|
|
29
|
+
* 输出:是否收养了原生摘要。
|
|
30
|
+
* 预期:仅当插件还没有任何块时,把 OpenCode 自己的 checkpoint 摘要当作 b1:T1;
|
|
31
|
+
* 它没有原始 transcript,fromId/toId 指向 checkpoint 消息本身。
|
|
32
|
+
*/
|
|
33
|
+
export async function adoptNativeCheckpoint(
|
|
34
|
+
ctx: Plugin.Context,
|
|
35
|
+
sessionID: string,
|
|
36
|
+
messages: readonly MessageLike[],
|
|
37
|
+
): Promise<boolean> {
|
|
38
|
+
const state = await loadState(ctx.storage, sessionID)
|
|
39
|
+
if (state.blocks.length > 0 || state.pending !== undefined || state.terminal !== undefined) return false
|
|
40
|
+
|
|
41
|
+
const checkpoint = messages.findLast((message) => isCheckpoint(message))
|
|
42
|
+
if (checkpoint === undefined) return false
|
|
43
|
+
const parsed = extractCheckpoint(messageText(checkpoint))
|
|
44
|
+
if (parsed === undefined) return false
|
|
45
|
+
|
|
46
|
+
const summary = toMarkdownSummary(parsed.summary)
|
|
47
|
+
const block = createT1Block({
|
|
48
|
+
blocks: [],
|
|
49
|
+
summary,
|
|
50
|
+
// 原生摘要没有原始 transcript;空范围让 decompress full 返回 unavailable,而不是伪造原文。
|
|
51
|
+
fromId: "",
|
|
52
|
+
toId: "",
|
|
53
|
+
sourceChars: parsed.summary.length,
|
|
54
|
+
})
|
|
55
|
+
await saveState(ctx.storage, sessionID, { blocks: [block], attempts: 0 })
|
|
56
|
+
return true
|
|
57
|
+
}
|