clanka 0.3.0 → 0.4.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/dist/Acp.d.ts.map +1 -1
- package/dist/Acp.js +3 -0
- package/dist/Acp.js.map +1 -1
- package/dist/Acp.test.js +58 -0
- package/dist/Acp.test.js.map +1 -1
- package/dist/Agent.d.ts.map +1 -1
- package/dist/Agent.js +86 -8
- package/dist/Agent.js.map +1 -1
- package/dist/Agent.test.js +671 -0
- package/dist/Agent.test.js.map +1 -1
- package/dist/AgentExecutor.d.ts +5 -0
- package/dist/AgentExecutor.d.ts.map +1 -1
- package/dist/AgentExecutor.js +22 -0
- package/dist/AgentExecutor.js.map +1 -1
- package/dist/AgentOutput.d.ts +49 -4
- package/dist/AgentOutput.d.ts.map +1 -1
- package/dist/AgentOutput.js +45 -0
- package/dist/AgentOutput.js.map +1 -1
- package/dist/AgentSkills.d.ts +56 -0
- package/dist/AgentSkills.d.ts.map +1 -0
- package/dist/AgentSkills.js +126 -0
- package/dist/AgentSkills.js.map +1 -0
- package/dist/AgentSkills.test.d.ts +2 -0
- package/dist/AgentSkills.test.d.ts.map +1 -0
- package/dist/AgentSkills.test.js +356 -0
- package/dist/AgentSkills.test.js.map +1 -0
- package/dist/Codex.js +1 -1
- package/dist/Codex.js.map +1 -1
- package/dist/Compaction.d.ts +342 -0
- package/dist/Compaction.d.ts.map +1 -0
- package/dist/Compaction.js +566 -0
- package/dist/Compaction.js.map +1 -0
- package/dist/Compaction.test.d.ts +2 -0
- package/dist/Compaction.test.d.ts.map +1 -0
- package/dist/Compaction.test.js +576 -0
- package/dist/Compaction.test.js.map +1 -0
- package/dist/CompactionTransport.test.d.ts +2 -0
- package/dist/CompactionTransport.test.d.ts.map +1 -0
- package/dist/CompactionTransport.test.js +123 -0
- package/dist/CompactionTransport.test.js.map +1 -0
- package/dist/Copilot.d.ts.map +1 -1
- package/dist/Copilot.js +7 -2
- package/dist/Copilot.js.map +1 -1
- package/dist/OutputFormatter.d.ts.map +1 -1
- package/dist/OutputFormatter.js +9 -0
- package/dist/OutputFormatter.js.map +1 -1
- package/dist/cli.js +14 -5
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/Acp.test.ts +85 -0
- package/src/Acp.ts +2 -0
- package/src/Agent.test.ts +994 -0
- package/src/Agent.ts +127 -6
- package/src/AgentExecutor.ts +27 -0
- package/src/AgentOutput.ts +58 -0
- package/src/AgentSkills.test.ts +652 -0
- package/src/AgentSkills.ts +155 -0
- package/src/Codex.ts +3 -1
- package/src/Compaction.test.ts +824 -0
- package/src/Compaction.ts +788 -0
- package/src/CompactionTransport.test.ts +228 -0
- package/src/Copilot.ts +8 -1
- package/src/OutputFormatter.ts +9 -0
- package/src/cli.ts +27 -6
- package/src/index.ts +6 -0
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
import { OpenAiClient } from "@effect/ai-openai"
|
|
2
|
+
import { OpenAiClient as CompatClient } from "@effect/ai-openai-compat"
|
|
3
|
+
import { assert, describe, it } from "@effect/vitest"
|
|
4
|
+
import * as Effect from "effect/Effect"
|
|
5
|
+
import * as Exit from "effect/Exit"
|
|
6
|
+
import * as Layer from "effect/Layer"
|
|
7
|
+
import * as Option from "effect/Option"
|
|
8
|
+
import * as Stream from "effect/Stream"
|
|
9
|
+
import * as AiError from "effect/unstable/ai/AiError"
|
|
10
|
+
import * as LanguageModel from "effect/unstable/ai/LanguageModel"
|
|
11
|
+
import * as Prompt from "effect/unstable/ai/Prompt"
|
|
12
|
+
import * as HttpClient from "effect/unstable/http/HttpClient"
|
|
13
|
+
import type * as HttpClientRequest from "effect/unstable/http/HttpClientRequest"
|
|
14
|
+
import * as HttpClientResponse from "effect/unstable/http/HttpClientResponse"
|
|
15
|
+
import * as Codex from "./Codex.ts"
|
|
16
|
+
import * as Compaction from "./Compaction.ts"
|
|
17
|
+
import * as Copilot from "./Copilot.ts"
|
|
18
|
+
|
|
19
|
+
const user = (text: string) =>
|
|
20
|
+
Prompt.makeMessage("user", {
|
|
21
|
+
content: [Prompt.makePart("text", { text })],
|
|
22
|
+
})
|
|
23
|
+
const history = Prompt.fromMessages([
|
|
24
|
+
user("old context ".repeat(1_000)),
|
|
25
|
+
user("continue"),
|
|
26
|
+
])
|
|
27
|
+
const summaryPrompt = Compaction.summarizerPrompt({
|
|
28
|
+
previousSummary: Option.none(),
|
|
29
|
+
messages: history.content,
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
// Use the real provider model and client. Stop at the transport boundary so no
|
|
33
|
+
// credentials, network access, or permissive scripted LanguageModel are involved.
|
|
34
|
+
const captureHttp = () => {
|
|
35
|
+
const requests: Array<HttpClientRequest.HttpClientRequest> = []
|
|
36
|
+
const client = HttpClient.make((request) =>
|
|
37
|
+
Effect.sync(() => {
|
|
38
|
+
requests.push(request)
|
|
39
|
+
return HttpClientResponse.fromWeb(
|
|
40
|
+
request,
|
|
41
|
+
new Response(
|
|
42
|
+
JSON.stringify({
|
|
43
|
+
error: {
|
|
44
|
+
message: "transport probe",
|
|
45
|
+
type: "invalid_request_error",
|
|
46
|
+
},
|
|
47
|
+
}),
|
|
48
|
+
{ status: 400, headers: { "content-type": "application/json" } },
|
|
49
|
+
),
|
|
50
|
+
)
|
|
51
|
+
}),
|
|
52
|
+
)
|
|
53
|
+
return { requests, layer: Layer.succeed(HttpClient.HttpClient, client) }
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const body = (
|
|
57
|
+
request: HttpClientRequest.HttpClientRequest,
|
|
58
|
+
): Record<string, unknown> => {
|
|
59
|
+
if (request.body._tag !== "Uint8Array")
|
|
60
|
+
throw new Error("expected a JSON request body")
|
|
61
|
+
return JSON.parse(new TextDecoder().decode(request.body.body))
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const compact = Compaction.compact({
|
|
65
|
+
prompt: history,
|
|
66
|
+
reason: "threshold",
|
|
67
|
+
}).pipe(
|
|
68
|
+
Effect.provide(Compaction.CompactionConfig.layer({ keepRecentTokens: 100 })),
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
const streamedSummary = Effect.gen(function* () {
|
|
72
|
+
const ai = yield* LanguageModel.LanguageModel
|
|
73
|
+
const transform = yield* Compaction.SummarizerTransform
|
|
74
|
+
return yield* transform(
|
|
75
|
+
ai.streamText({ prompt: summaryPrompt }).pipe(Stream.runDrain),
|
|
76
|
+
)
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
const socketError = AiError.make({
|
|
80
|
+
module: "test/socket",
|
|
81
|
+
method: "createResponseStream",
|
|
82
|
+
reason: new AiError.InvalidRequestError({ description: "transport probe" }),
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
describe("Compaction provider transport", () => {
|
|
86
|
+
it.effect("sets stream: true on the Codex HTTP summarization request", () =>
|
|
87
|
+
Effect.gen(function* () {
|
|
88
|
+
const http = captureHttp()
|
|
89
|
+
const exit = yield* compact.pipe(
|
|
90
|
+
Effect.provide(
|
|
91
|
+
Codex.model("gpt-6-astra").pipe(
|
|
92
|
+
Layer.provide(
|
|
93
|
+
OpenAiClient.layer({ apiUrl: "https://codex.test" }).pipe(
|
|
94
|
+
Layer.provide(http.layer),
|
|
95
|
+
),
|
|
96
|
+
),
|
|
97
|
+
),
|
|
98
|
+
),
|
|
99
|
+
Effect.exit,
|
|
100
|
+
)
|
|
101
|
+
assert.isTrue(Exit.isFailure(exit))
|
|
102
|
+
assert.strictEqual(http.requests.length, 1)
|
|
103
|
+
assert.strictEqual(http.requests[0]!.url, "https://codex.test/responses")
|
|
104
|
+
const request = body(http.requests[0]!)
|
|
105
|
+
assert.strictEqual(
|
|
106
|
+
request.stream,
|
|
107
|
+
true,
|
|
108
|
+
"Codex rejects non-streaming responses with HTTP 400",
|
|
109
|
+
)
|
|
110
|
+
assert.isFalse("max_output_tokens" in request)
|
|
111
|
+
assert.deepStrictEqual(request.tools ?? [], [])
|
|
112
|
+
}),
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
it.effect(
|
|
116
|
+
"routes compaction through the installed OpenAiSocket rather than HTTP",
|
|
117
|
+
() =>
|
|
118
|
+
Effect.gen(function* () {
|
|
119
|
+
const http = captureHttp()
|
|
120
|
+
const requests: Array<
|
|
121
|
+
Parameters<
|
|
122
|
+
OpenAiClient.OpenAiSocket["Service"]["createResponseStream"]
|
|
123
|
+
>[0]
|
|
124
|
+
> = []
|
|
125
|
+
const exit = yield* compact.pipe(
|
|
126
|
+
Effect.provideService(OpenAiClient.OpenAiSocket, {
|
|
127
|
+
createResponseStream: (request) =>
|
|
128
|
+
Effect.suspend(() => {
|
|
129
|
+
requests.push(request)
|
|
130
|
+
return Effect.fail(socketError)
|
|
131
|
+
}),
|
|
132
|
+
}),
|
|
133
|
+
Effect.provide(
|
|
134
|
+
Codex.model("gpt-6-astra").pipe(
|
|
135
|
+
Layer.provide(
|
|
136
|
+
OpenAiClient.layer({ apiUrl: "https://codex.test" }).pipe(
|
|
137
|
+
Layer.provide(http.layer),
|
|
138
|
+
),
|
|
139
|
+
),
|
|
140
|
+
),
|
|
141
|
+
),
|
|
142
|
+
Effect.exit,
|
|
143
|
+
)
|
|
144
|
+
assert.strictEqual(
|
|
145
|
+
http.requests.length,
|
|
146
|
+
0,
|
|
147
|
+
"generateText bypasses the websocket and must not be used here",
|
|
148
|
+
)
|
|
149
|
+
assert.strictEqual(requests.length, 1)
|
|
150
|
+
assert.deepStrictEqual(exit, Exit.fail(socketError))
|
|
151
|
+
assert.isFalse("max_output_tokens" in requests[0]!)
|
|
152
|
+
assert.deepStrictEqual(requests[0]!.tools ?? [], [])
|
|
153
|
+
}),
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
it.effect(
|
|
157
|
+
"omits the unsupported Codex output-token cap even when streaming is selected",
|
|
158
|
+
() =>
|
|
159
|
+
Effect.gen(function* () {
|
|
160
|
+
const http = captureHttp()
|
|
161
|
+
yield* streamedSummary.pipe(
|
|
162
|
+
Effect.provide(
|
|
163
|
+
Codex.model("gpt-6-astra").pipe(
|
|
164
|
+
Layer.provide(
|
|
165
|
+
OpenAiClient.layer({ apiUrl: "https://codex.test" }).pipe(
|
|
166
|
+
Layer.provide(http.layer),
|
|
167
|
+
),
|
|
168
|
+
),
|
|
169
|
+
),
|
|
170
|
+
),
|
|
171
|
+
Effect.exit,
|
|
172
|
+
)
|
|
173
|
+
assert.strictEqual(http.requests.length, 1)
|
|
174
|
+
const request = body(http.requests[0]!)
|
|
175
|
+
assert.strictEqual(request.stream, true)
|
|
176
|
+
// The live Codex backend rejects this parameter; a 4k cap is unavailable.
|
|
177
|
+
assert.isFalse(
|
|
178
|
+
"max_output_tokens" in request,
|
|
179
|
+
"Codex does not support max_output_tokens",
|
|
180
|
+
)
|
|
181
|
+
assert.deepInclude(request.input, {
|
|
182
|
+
role: "system",
|
|
183
|
+
content: [{ type: "input_text", text: Compaction.summarizerSystem }],
|
|
184
|
+
})
|
|
185
|
+
}),
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
it.effect(
|
|
189
|
+
"retains Copilot's 4k wire-level cap only for the streamed summary",
|
|
190
|
+
() =>
|
|
191
|
+
Effect.gen(function* () {
|
|
192
|
+
const http = captureHttp()
|
|
193
|
+
yield* Effect.gen(function* () {
|
|
194
|
+
yield* streamedSummary.pipe(Effect.exit)
|
|
195
|
+
const ai = yield* LanguageModel.LanguageModel
|
|
196
|
+
yield* ai
|
|
197
|
+
.streamText({ prompt: "continue" })
|
|
198
|
+
.pipe(Stream.runDrain, Effect.exit)
|
|
199
|
+
}).pipe(
|
|
200
|
+
Effect.provide(
|
|
201
|
+
Copilot.model("gpt-4.1").pipe(
|
|
202
|
+
Layer.provide(
|
|
203
|
+
CompatClient.layer({ apiUrl: "https://copilot.test" }).pipe(
|
|
204
|
+
Layer.provide(http.layer),
|
|
205
|
+
),
|
|
206
|
+
),
|
|
207
|
+
),
|
|
208
|
+
),
|
|
209
|
+
)
|
|
210
|
+
assert.strictEqual(http.requests.length, 2)
|
|
211
|
+
assert.strictEqual(
|
|
212
|
+
http.requests[0]!.url,
|
|
213
|
+
"https://copilot.test/chat/completions",
|
|
214
|
+
)
|
|
215
|
+
const summary = body(http.requests[0]!)
|
|
216
|
+
const continuation = body(http.requests[1]!)
|
|
217
|
+
assert.strictEqual(summary.stream, true)
|
|
218
|
+
// The compatibility client translates max_output_tokens to max_tokens.
|
|
219
|
+
assert.strictEqual(summary.max_tokens, 4_000)
|
|
220
|
+
assert.deepStrictEqual(summary.tools ?? [], [])
|
|
221
|
+
assert.isFalse(
|
|
222
|
+
"max_tokens" in continuation,
|
|
223
|
+
"summary override must not leak into the next model call",
|
|
224
|
+
)
|
|
225
|
+
assert.strictEqual(continuation.stream, true)
|
|
226
|
+
}),
|
|
227
|
+
)
|
|
228
|
+
})
|
package/src/Copilot.ts
CHANGED
|
@@ -6,6 +6,7 @@ import * as Layer from "effect/Layer"
|
|
|
6
6
|
import * as Struct from "effect/Struct"
|
|
7
7
|
import { API_URL, GithubCopilotAuth } from "./CopilotAuth.ts"
|
|
8
8
|
import { AgentModelConfig } from "./Agent.ts"
|
|
9
|
+
import * as Compaction from "./Compaction.ts"
|
|
9
10
|
import * as Model from "effect/unstable/ai/Model"
|
|
10
11
|
import type * as LanguageModel from "effect/unstable/ai/LanguageModel"
|
|
11
12
|
|
|
@@ -34,7 +35,7 @@ export const model = (
|
|
|
34
35
|
Model.make(
|
|
35
36
|
"openai",
|
|
36
37
|
model,
|
|
37
|
-
Layer.
|
|
38
|
+
Layer.mergeAll(
|
|
38
39
|
OpenAiLanguageModel.layer({
|
|
39
40
|
model,
|
|
40
41
|
config: Struct.omit(options ?? {}, ["systemPromptTransform"]),
|
|
@@ -42,5 +43,11 @@ export const model = (
|
|
|
42
43
|
AgentModelConfig.layer({
|
|
43
44
|
systemPromptTransform: options?.systemPromptTransform,
|
|
44
45
|
}),
|
|
46
|
+
// Cap compaction summaries; Copilot honours max_output_tokens.
|
|
47
|
+
Layer.succeed(Compaction.SummarizerTransform, (effect) =>
|
|
48
|
+
OpenAiLanguageModel.withConfigOverride(effect, {
|
|
49
|
+
max_output_tokens: Compaction.summarizerMaxOutputTokens,
|
|
50
|
+
}),
|
|
51
|
+
),
|
|
45
52
|
),
|
|
46
53
|
)
|
package/src/OutputFormatter.ts
CHANGED
|
@@ -98,6 +98,15 @@ ${output.summary}\n\n`
|
|
|
98
98
|
case "Usage": {
|
|
99
99
|
return `${prefix}${chalkInfoHeading(`${infoIcon} Usage:`)} ${numberFormat.format(output.contextTokens)} context / ${numberFormat.format(output.inputTokens)} input / ${numberFormat.format(output.outputTokens)} output\n\n`
|
|
100
100
|
}
|
|
101
|
+
case "ExecuteOutputCapped": {
|
|
102
|
+
return `${prefix}${chalkInfoHeading(`${infoIcon} Output capped:`)} ${numberFormat.format(output.charsBefore)} chars reduced to ${numberFormat.format(output.charsAfter)}\n\n`
|
|
103
|
+
}
|
|
104
|
+
case "CompactionStarted": {
|
|
105
|
+
return `${prefix}${chalkInfoHeading(`${infoIcon} Compacting context (${output.reason})...`)}\n\n`
|
|
106
|
+
}
|
|
107
|
+
case "CompactionEnded": {
|
|
108
|
+
return `${prefix}${chalkInfoHeading(`${infoIcon} Context compacted (${output.reason}):`)} ~${numberFormat.format(output.tokensBefore)} tokens to ~${numberFormat.format(output.tokensAfter)}\n\n`
|
|
109
|
+
}
|
|
101
110
|
}
|
|
102
111
|
}),
|
|
103
112
|
Stream.catchIf(
|
package/src/cli.ts
CHANGED
|
@@ -12,6 +12,7 @@ import * as AgentExecutor from "./AgentExecutor.ts"
|
|
|
12
12
|
import * as Codex from "./Codex.ts"
|
|
13
13
|
import * as Copilot from "./Copilot.ts"
|
|
14
14
|
import * as Agent from "./Agent.ts"
|
|
15
|
+
import * as Compaction from "./Compaction.ts"
|
|
15
16
|
import * as Stream from "effect/Stream"
|
|
16
17
|
import * as OutputFormatter from "./OutputFormatter.ts"
|
|
17
18
|
import * as Stdio from "effect/Stdio"
|
|
@@ -28,7 +29,7 @@ import * as Option from "effect/Option"
|
|
|
28
29
|
import { OpenAiClient, OpenAiEmbeddingModel } from "@effect/ai-openai"
|
|
29
30
|
import { DeviceCodeHandler } from "./index.ts"
|
|
30
31
|
|
|
31
|
-
const version = "0.0
|
|
32
|
+
const version = "1.0.0"
|
|
32
33
|
|
|
33
34
|
type Provider = "openai" | "copilot"
|
|
34
35
|
|
|
@@ -122,6 +123,16 @@ const prompt = Flag.String("prompt").pipe(
|
|
|
122
123
|
Flag.optional,
|
|
123
124
|
)
|
|
124
125
|
|
|
126
|
+
// Kill switch: `--no-compaction` or CLANKA_COMPACTION=false.
|
|
127
|
+
const compaction = Flag.Boolean("compaction").pipe(
|
|
128
|
+
Flag.withDescription(
|
|
129
|
+
"Auto-compact the conversation history when it nears the context window. Disable with --no-compaction or CLANKA_COMPACTION=false; the execute output cap stays on.",
|
|
130
|
+
),
|
|
131
|
+
Flag.withFallbackConfig(
|
|
132
|
+
Config.Boolean("CLANKA_COMPACTION").pipe(Config.withDefault(true)),
|
|
133
|
+
),
|
|
134
|
+
)
|
|
135
|
+
|
|
125
136
|
const Kvs = Layer.unwrap(
|
|
126
137
|
Effect.gen(function* () {
|
|
127
138
|
const path = yield* Path.Path
|
|
@@ -182,7 +193,7 @@ const acpModel = Flag.String("model").pipe(
|
|
|
182
193
|
Flag.withDefault("openai/gpt-6-astra/medium"),
|
|
183
194
|
)
|
|
184
195
|
|
|
185
|
-
const acp = Command.make("acp", { model: acpModel }).pipe(
|
|
196
|
+
const acp = Command.make("acp", { model: acpModel, compaction }).pipe(
|
|
186
197
|
Command.withDescription(
|
|
187
198
|
"Run as an Agent Client Protocol (ACP) server over stdio",
|
|
188
199
|
),
|
|
@@ -200,16 +211,23 @@ const acp = Command.make("acp", { model: acpModel }).pipe(
|
|
|
200
211
|
),
|
|
201
212
|
}),
|
|
202
213
|
),
|
|
203
|
-
Command.provide(
|
|
214
|
+
Command.provide(({ compaction }) =>
|
|
204
215
|
Layer.mergeAll(
|
|
205
216
|
Agent.ConversationMode.layer(true),
|
|
206
217
|
Layer.succeed(Logger.LogToStderr, true),
|
|
207
218
|
DeviceCodeHandler.layerLog,
|
|
219
|
+
Compaction.CompactionConfig.layer({ enabled: compaction }),
|
|
208
220
|
),
|
|
209
221
|
),
|
|
210
222
|
)
|
|
211
223
|
|
|
212
|
-
Command.make("clanka", {
|
|
224
|
+
Command.make("clanka", {
|
|
225
|
+
provider,
|
|
226
|
+
model,
|
|
227
|
+
semantic,
|
|
228
|
+
prompt,
|
|
229
|
+
compaction,
|
|
230
|
+
}).pipe(
|
|
213
231
|
Command.withHandler(
|
|
214
232
|
Effect.fnUntraced(function* ({
|
|
215
233
|
provider,
|
|
@@ -264,8 +282,11 @@ Command.make("clanka", { provider, model, semantic, prompt }).pipe(
|
|
|
264
282
|
)
|
|
265
283
|
}),
|
|
266
284
|
),
|
|
267
|
-
Command.provide(({ prompt }) =>
|
|
268
|
-
|
|
285
|
+
Command.provide(({ prompt, compaction }) =>
|
|
286
|
+
Layer.mergeAll(
|
|
287
|
+
Agent.ConversationMode.layer(Option.isNone(prompt)),
|
|
288
|
+
Compaction.CompactionConfig.layer({ enabled: compaction }),
|
|
289
|
+
),
|
|
269
290
|
),
|
|
270
291
|
Command.withSubcommands([acp]),
|
|
271
292
|
Command.run({
|
package/src/index.ts
CHANGED
|
@@ -12,6 +12,7 @@ export * as Agent from "./Agent.ts"
|
|
|
12
12
|
* @since 1.0.0
|
|
13
13
|
*/
|
|
14
14
|
export * as AgentExecutor from "./AgentExecutor.ts"
|
|
15
|
+
export * as AgentSkills from "./AgentSkills.ts"
|
|
15
16
|
|
|
16
17
|
/**
|
|
17
18
|
* @since 1.0.0
|
|
@@ -33,6 +34,11 @@ export * as Codex from "./Codex.ts"
|
|
|
33
34
|
*/
|
|
34
35
|
export * as CodeChunker from "./CodeChunker.ts"
|
|
35
36
|
|
|
37
|
+
/**
|
|
38
|
+
* @since 1.0.0
|
|
39
|
+
*/
|
|
40
|
+
export * as Compaction from "./Compaction.ts"
|
|
41
|
+
|
|
36
42
|
/**
|
|
37
43
|
* @since 1.0.0
|
|
38
44
|
*/
|