clanka 0.7.7 → 0.8.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 +2 -0
- package/dist/Acp.js.map +1 -1
- package/dist/Acp.test.js +62 -0
- package/dist/Acp.test.js.map +1 -1
- package/dist/Agent.d.ts.map +1 -1
- package/dist/Agent.js +10 -0
- package/dist/Agent.js.map +1 -1
- package/dist/Agent.test.js +47 -2
- package/dist/Agent.test.js.map +1 -1
- package/dist/AgentOutput.d.ts +8 -0
- package/dist/AgentOutput.d.ts.map +1 -1
- package/dist/AgentOutput.js +8 -0
- package/dist/AgentOutput.js.map +1 -1
- package/dist/bin/cli.mjs +14 -4
- package/dist/bin/cli.mjs.map +1 -1
- package/package.json +1 -1
- package/src/Acp.test.ts +102 -0
- package/src/Acp.ts +2 -0
- package/src/Agent.test.ts +54 -2
- package/src/Agent.ts +10 -0
- package/src/AgentOutput.ts +8 -0
package/package.json
CHANGED
package/src/Acp.test.ts
CHANGED
|
@@ -154,6 +154,108 @@ describe("Acp", () => {
|
|
|
154
154
|
),
|
|
155
155
|
)
|
|
156
156
|
|
|
157
|
+
it.effect("reports cache usage in the ACP usage extension", () =>
|
|
158
|
+
withStore(
|
|
159
|
+
Effect.scoped(
|
|
160
|
+
Effect.gen(function* () {
|
|
161
|
+
const server = yield* makeServer(() =>
|
|
162
|
+
parts(
|
|
163
|
+
{ type: "text-start", id: "1" },
|
|
164
|
+
{ type: "text-delta", id: "1", delta: "done" },
|
|
165
|
+
{ type: "text-end", id: "1" },
|
|
166
|
+
{
|
|
167
|
+
type: "finish",
|
|
168
|
+
reason: "stop",
|
|
169
|
+
usage: {
|
|
170
|
+
inputTokens: {
|
|
171
|
+
total: 120,
|
|
172
|
+
cacheRead: 30,
|
|
173
|
+
cacheWrite: 6,
|
|
174
|
+
},
|
|
175
|
+
outputTokens: { total: 12 },
|
|
176
|
+
},
|
|
177
|
+
},
|
|
178
|
+
),
|
|
179
|
+
)
|
|
180
|
+
const created = yield* server.request(1, "session/new", {
|
|
181
|
+
cwd: "/tmp",
|
|
182
|
+
})
|
|
183
|
+
const sessionId: string = created.result.sessionId
|
|
184
|
+
|
|
185
|
+
yield* server.request(2, "session/prompt", {
|
|
186
|
+
sessionId,
|
|
187
|
+
prompt: [{ type: "text", text: "hello" }],
|
|
188
|
+
})
|
|
189
|
+
|
|
190
|
+
assert.deepStrictEqual(
|
|
191
|
+
server
|
|
192
|
+
.updates(sessionId)
|
|
193
|
+
.find((update) => update.sessionUpdate === "usage_update"),
|
|
194
|
+
{
|
|
195
|
+
sessionUpdate: "usage_update",
|
|
196
|
+
usage: {
|
|
197
|
+
inputTokens: 120,
|
|
198
|
+
outputTokens: 12,
|
|
199
|
+
cacheRead: 30,
|
|
200
|
+
cacheWrite: 6,
|
|
201
|
+
},
|
|
202
|
+
},
|
|
203
|
+
)
|
|
204
|
+
}),
|
|
205
|
+
),
|
|
206
|
+
),
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
it.effect(
|
|
210
|
+
"reports zero cache usage when the provider omits cache fields",
|
|
211
|
+
() =>
|
|
212
|
+
withStore(
|
|
213
|
+
Effect.scoped(
|
|
214
|
+
Effect.gen(function* () {
|
|
215
|
+
const server = yield* makeServer(() =>
|
|
216
|
+
parts(
|
|
217
|
+
{ type: "text-start", id: "1" },
|
|
218
|
+
{ type: "text-delta", id: "1", delta: "done" },
|
|
219
|
+
{ type: "text-end", id: "1" },
|
|
220
|
+
{
|
|
221
|
+
type: "finish",
|
|
222
|
+
reason: "stop",
|
|
223
|
+
usage: {
|
|
224
|
+
inputTokens: { total: 120 },
|
|
225
|
+
outputTokens: { total: 12 },
|
|
226
|
+
},
|
|
227
|
+
},
|
|
228
|
+
),
|
|
229
|
+
)
|
|
230
|
+
const created = yield* server.request(1, "session/new", {
|
|
231
|
+
cwd: "/tmp",
|
|
232
|
+
})
|
|
233
|
+
const sessionId: string = created.result.sessionId
|
|
234
|
+
|
|
235
|
+
yield* server.request(2, "session/prompt", {
|
|
236
|
+
sessionId,
|
|
237
|
+
prompt: [{ type: "text", text: "hello" }],
|
|
238
|
+
})
|
|
239
|
+
|
|
240
|
+
assert.deepStrictEqual(
|
|
241
|
+
server
|
|
242
|
+
.updates(sessionId)
|
|
243
|
+
.find((update) => update.sessionUpdate === "usage_update"),
|
|
244
|
+
{
|
|
245
|
+
sessionUpdate: "usage_update",
|
|
246
|
+
usage: {
|
|
247
|
+
inputTokens: 120,
|
|
248
|
+
outputTokens: 12,
|
|
249
|
+
cacheRead: 0,
|
|
250
|
+
cacheWrite: 0,
|
|
251
|
+
},
|
|
252
|
+
},
|
|
253
|
+
)
|
|
254
|
+
}),
|
|
255
|
+
),
|
|
256
|
+
),
|
|
257
|
+
)
|
|
258
|
+
|
|
157
259
|
it.effect("applies model and thought-level changes to the next turn", () =>
|
|
158
260
|
withStore(
|
|
159
261
|
Effect.scoped(
|
package/src/Acp.ts
CHANGED
package/src/Agent.test.ts
CHANGED
|
@@ -18,7 +18,7 @@ import type * as Response from "effect/unstable/ai/Response"
|
|
|
18
18
|
import * as ResponseIdTracker from "effect/unstable/ai/ResponseIdTracker"
|
|
19
19
|
import * as Agent from "./Agent.ts"
|
|
20
20
|
import * as AgentExecutor from "./AgentExecutor.ts"
|
|
21
|
-
import
|
|
21
|
+
import * as AgentOutput from "./AgentOutput.ts"
|
|
22
22
|
import * as Compaction from "./Compaction.ts"
|
|
23
23
|
|
|
24
24
|
const capabilities = new AgentExecutor.Capabilities({
|
|
@@ -310,11 +310,19 @@ const toolCall = (id: string, script: string): Response.StreamPartEncoded => ({
|
|
|
310
310
|
const finish = (
|
|
311
311
|
contextTokens: number,
|
|
312
312
|
reason: "stop" | "tool-calls" = "tool-calls",
|
|
313
|
+
cache: {
|
|
314
|
+
readonly read?: number | undefined
|
|
315
|
+
readonly write?: number | undefined
|
|
316
|
+
} = {},
|
|
313
317
|
): Response.StreamPartEncoded => ({
|
|
314
318
|
type: "finish",
|
|
315
319
|
reason,
|
|
316
320
|
usage: {
|
|
317
|
-
inputTokens: {
|
|
321
|
+
inputTokens: {
|
|
322
|
+
total: contextTokens,
|
|
323
|
+
cacheRead: cache.read,
|
|
324
|
+
cacheWrite: cache.write,
|
|
325
|
+
},
|
|
318
326
|
outputTokens: { total: 10 },
|
|
319
327
|
},
|
|
320
328
|
})
|
|
@@ -524,6 +532,50 @@ it.layer(localExecutorLayer, { excludeTestServices: true })(
|
|
|
524
532
|
|
|
525
533
|
// --- tests -------------------------------------------------------------------
|
|
526
534
|
|
|
535
|
+
describe("Agent usage", () => {
|
|
536
|
+
it.effect("accumulates provider cache reads and writes", () =>
|
|
537
|
+
Effect.gen(function* () {
|
|
538
|
+
const { exit, outputs } = yield* runAgentCollect({
|
|
539
|
+
executor: makeExecutor(() => Stream.succeed("done")),
|
|
540
|
+
respond: (_call, index) => {
|
|
541
|
+
switch (index) {
|
|
542
|
+
case 0:
|
|
543
|
+
return Stream.fromIterable([
|
|
544
|
+
toolCall("call-1", "console.log('done')"),
|
|
545
|
+
finish(100, "tool-calls", { read: 25, write: 5 }),
|
|
546
|
+
])
|
|
547
|
+
case 1:
|
|
548
|
+
return Stream.fromIterable([
|
|
549
|
+
...text("complete"),
|
|
550
|
+
finish(60, "stop", { read: 15, write: 3 }),
|
|
551
|
+
])
|
|
552
|
+
default:
|
|
553
|
+
return Stream.die(`unexpected model call #${index}`)
|
|
554
|
+
}
|
|
555
|
+
},
|
|
556
|
+
})
|
|
557
|
+
|
|
558
|
+
assert.deepStrictEqual(exit, Exit.succeed("complete"))
|
|
559
|
+
assert.deepStrictEqual(outputsOfTag(outputs, "Usage"), [
|
|
560
|
+
new AgentOutput.Usage({
|
|
561
|
+
contextTokens: 100,
|
|
562
|
+
inputTokens: 100,
|
|
563
|
+
outputTokens: 10,
|
|
564
|
+
cacheRead: 25,
|
|
565
|
+
cacheWrite: 5,
|
|
566
|
+
}),
|
|
567
|
+
new AgentOutput.Usage({
|
|
568
|
+
contextTokens: 60,
|
|
569
|
+
inputTokens: 160,
|
|
570
|
+
outputTokens: 20,
|
|
571
|
+
cacheRead: 40,
|
|
572
|
+
cacheWrite: 8,
|
|
573
|
+
}),
|
|
574
|
+
])
|
|
575
|
+
}),
|
|
576
|
+
)
|
|
577
|
+
})
|
|
578
|
+
|
|
527
579
|
describe("Agent execute output cap", () => {
|
|
528
580
|
const dump = "DUMP-HEAD " + filler("log", 40_000) + " DUMP-TAIL"
|
|
529
581
|
|
package/src/Agent.ts
CHANGED
|
@@ -211,6 +211,8 @@ ${content}
|
|
|
211
211
|
const output = yield* Queue.make<Output, AgentFinished | AiError.AiError>()
|
|
212
212
|
let inputTokens = 0
|
|
213
213
|
let outputTokens = 0
|
|
214
|
+
let cacheRead = 0
|
|
215
|
+
let cacheWrite = 0
|
|
214
216
|
const prompt = opts.disableHistory ? MutableRef.make(Prompt.empty) : history
|
|
215
217
|
|
|
216
218
|
MutableRef.update(prompt, Prompt.concat(opts.prompt))
|
|
@@ -536,6 +538,12 @@ ${content}
|
|
|
536
538
|
if (usage.outputTokens.total !== undefined) {
|
|
537
539
|
outputTokens += usage.outputTokens.total
|
|
538
540
|
}
|
|
541
|
+
if (usage.inputTokens.cacheRead !== undefined) {
|
|
542
|
+
cacheRead += usage.inputTokens.cacheRead
|
|
543
|
+
}
|
|
544
|
+
if (usage.inputTokens.cacheWrite !== undefined) {
|
|
545
|
+
cacheWrite += usage.inputTokens.cacheWrite
|
|
546
|
+
}
|
|
539
547
|
if (usage.inputTokens.total !== undefined) {
|
|
540
548
|
lastContextTokens = usage.inputTokens.total
|
|
541
549
|
inputTokens += usage.inputTokens.total
|
|
@@ -545,6 +553,8 @@ ${content}
|
|
|
545
553
|
contextTokens: usage.inputTokens.total,
|
|
546
554
|
inputTokens,
|
|
547
555
|
outputTokens,
|
|
556
|
+
cacheRead,
|
|
557
|
+
cacheWrite,
|
|
548
558
|
}),
|
|
549
559
|
})
|
|
550
560
|
}
|
package/src/AgentOutput.ts
CHANGED
|
@@ -87,6 +87,14 @@ export class Usage extends Schema.TaggedClass<Usage>()("Usage", {
|
|
|
87
87
|
contextTokens: Schema.Number,
|
|
88
88
|
inputTokens: Schema.Number,
|
|
89
89
|
outputTokens: Schema.Number,
|
|
90
|
+
/**
|
|
91
|
+
* Cumulative cache reads included in `inputTokens`. Zero is not proof of no cache activity.
|
|
92
|
+
*/
|
|
93
|
+
cacheRead: Schema.Number,
|
|
94
|
+
/**
|
|
95
|
+
* Cumulative cache writes included in `inputTokens`. Zero is not proof of no cache activity.
|
|
96
|
+
*/
|
|
97
|
+
cacheWrite: Schema.Number,
|
|
90
98
|
}) {}
|
|
91
99
|
|
|
92
100
|
/**
|