@fungi.computer/shiitake 0.0.0-stage → 0.1.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/LICENSE +21 -0
- package/README.md +454 -2
- package/dist/agar-extension.d.ts +9 -0
- package/dist/agar-extension.js +38 -0
- package/dist/agent-control.d.ts +77 -0
- package/dist/agent-control.js +434 -0
- package/dist/client-entry.d.ts +211 -0
- package/dist/client-entry.js +101 -0
- package/dist/client-wire.d.ts +199 -0
- package/dist/client-wire.js +271 -0
- package/dist/client.d.ts +439 -0
- package/dist/client.js +4 -0
- package/dist/cloudflare.d.ts +220 -0
- package/dist/cloudflare.js +531 -0
- package/dist/effect.d.ts +42 -0
- package/dist/effect.js +20 -0
- package/dist/errors.d.ts +50 -0
- package/dist/errors.js +81 -0
- package/dist/extension-client.d.ts +38 -0
- package/dist/extension-client.js +56 -0
- package/dist/extension-contract.d.ts +73 -0
- package/dist/extension-contract.js +11 -0
- package/dist/extensions.d.ts +589 -0
- package/dist/extensions.js +263 -0
- package/dist/fleet.d.ts +313 -0
- package/dist/fleet.js +540 -0
- package/dist/hosted.d.ts +133 -0
- package/dist/hosted.js +265 -0
- package/dist/http-routes.d.ts +772 -0
- package/dist/http-routes.js +242 -0
- package/dist/http.d.ts +1 -0
- package/dist/http.js +1 -0
- package/dist/index.d.ts +35 -0
- package/dist/index.js +13 -0
- package/dist/internal/accepted-run-executor.d.ts +23 -0
- package/dist/internal/accepted-run-executor.js +129 -0
- package/dist/internal/admission-door.d.ts +75 -0
- package/dist/internal/admission-door.js +105 -0
- package/dist/internal/agent-close.d.ts +4 -0
- package/dist/internal/agent-close.js +42 -0
- package/dist/internal/browser-sessions.d.ts +31 -0
- package/dist/internal/browser-sessions.js +173 -0
- package/dist/internal/client-boundary.d.ts +262 -0
- package/dist/internal/client-boundary.js +202 -0
- package/dist/internal/compaction/compaction.d.ts +54 -0
- package/dist/internal/compaction/compaction.js +291 -0
- package/dist/internal/compaction/session-compaction.d.ts +69 -0
- package/dist/internal/compaction/session-compaction.js +330 -0
- package/dist/internal/compaction/settings.d.ts +11 -0
- package/dist/internal/compaction/settings.js +40 -0
- package/dist/internal/compaction/utils.d.ts +45 -0
- package/dist/internal/compaction/utils.js +319 -0
- package/dist/internal/contracts.d.ts +235 -0
- package/dist/internal/contracts.js +1 -0
- package/dist/internal/coordinator.d.ts +18 -0
- package/dist/internal/coordinator.js +362 -0
- package/dist/internal/document-codec.d.ts +11 -0
- package/dist/internal/document-codec.js +39 -0
- package/dist/internal/effect-client.d.ts +13 -0
- package/dist/internal/effect-client.js +102 -0
- package/dist/internal/effect-contracts.d.ts +236 -0
- package/dist/internal/effect-contracts.js +2 -0
- package/dist/internal/extension-command-registry.d.ts +11 -0
- package/dist/internal/extension-command-registry.js +29 -0
- package/dist/internal/extension-command-tools.d.ts +17 -0
- package/dist/internal/extension-command-tools.js +50 -0
- package/dist/internal/extension-context.d.ts +25 -0
- package/dist/internal/extension-context.js +35 -0
- package/dist/internal/extension-definitions.d.ts +5 -0
- package/dist/internal/extension-definitions.js +6 -0
- package/dist/internal/extension-hook.d.ts +25 -0
- package/dist/internal/extension-hook.js +46 -0
- package/dist/internal/extension-roster.d.ts +23 -0
- package/dist/internal/extension-roster.js +228 -0
- package/dist/internal/extension-runtime.d.ts +63 -0
- package/dist/internal/extension-runtime.js +1004 -0
- package/dist/internal/failure.d.ts +2 -0
- package/dist/internal/failure.js +9 -0
- package/dist/internal/host-meta.d.ts +41 -0
- package/dist/internal/host-meta.js +39 -0
- package/dist/internal/json-value.d.ts +4 -0
- package/dist/internal/json-value.js +3 -0
- package/dist/internal/model-acquisition.d.ts +21 -0
- package/dist/internal/model-acquisition.js +98 -0
- package/dist/internal/model-preference.d.ts +21 -0
- package/dist/internal/model-preference.js +20 -0
- package/dist/internal/observation-kernel.d.ts +28 -0
- package/dist/internal/observation-kernel.js +169 -0
- package/dist/internal/promise-facade.d.ts +18 -0
- package/dist/internal/promise-facade.js +172 -0
- package/dist/internal/run-scope.d.ts +27 -0
- package/dist/internal/run-scope.js +214 -0
- package/dist/internal/runtime-coordinator.d.ts +34 -0
- package/dist/internal/runtime-coordinator.js +233 -0
- package/dist/internal/runtime-core.d.ts +19 -0
- package/dist/internal/runtime-core.js +39 -0
- package/dist/internal/runtime-reconciliation.d.ts +18 -0
- package/dist/internal/runtime-reconciliation.js +162 -0
- package/dist/internal/session-context.d.ts +11 -0
- package/dist/internal/session-context.js +67 -0
- package/dist/internal/session-status.d.ts +10 -0
- package/dist/internal/session-status.js +52 -0
- package/dist/internal/woodstock/agent-reply.d.ts +25 -0
- package/dist/internal/woodstock/agent-reply.js +67 -0
- package/dist/internal/woodstock/client-projection.d.ts +29 -0
- package/dist/internal/woodstock/client-projection.js +512 -0
- package/dist/internal/woodstock/compaction-activity.d.ts +17 -0
- package/dist/internal/woodstock/compaction-activity.js +32 -0
- package/dist/internal/woodstock/cursor.d.ts +9 -0
- package/dist/internal/woodstock/cursor.js +44 -0
- package/dist/internal/woodstock/extension-store.d.ts +72 -0
- package/dist/internal/woodstock/extension-store.js +262 -0
- package/dist/internal/woodstock/fleet-store.d.ts +36 -0
- package/dist/internal/woodstock/fleet-store.js +100 -0
- package/dist/internal/woodstock/identities.d.ts +6 -0
- package/dist/internal/woodstock/identities.js +11 -0
- package/dist/internal/woodstock/index.d.ts +170 -0
- package/dist/internal/woodstock/index.js +546 -0
- package/dist/internal/woodstock/progress.d.ts +32 -0
- package/dist/internal/woodstock/progress.js +89 -0
- package/dist/internal/woodstock/run-recovery.d.ts +8 -0
- package/dist/internal/woodstock/run-recovery.js +95 -0
- package/dist/internal/woodstock/session-attention.d.ts +16 -0
- package/dist/internal/woodstock/session-attention.js +88 -0
- package/dist/internal/woodstock/session-documents.d.ts +18 -0
- package/dist/internal/woodstock/session-documents.js +51 -0
- package/dist/internal/woodstock/session-entry.d.ts +478 -0
- package/dist/internal/woodstock/session-entry.js +484 -0
- package/dist/internal/woodstock/session-grants.d.ts +24 -0
- package/dist/internal/woodstock/session-grants.js +84 -0
- package/dist/internal/woodstock/session-inbox.d.ts +56 -0
- package/dist/internal/woodstock/session-inbox.js +307 -0
- package/dist/internal/woodstock/session-store.d.ts +432 -0
- package/dist/internal/woodstock/session-store.js +2006 -0
- package/dist/internal/woodstock/storage-admission.d.ts +14 -0
- package/dist/internal/woodstock/storage-admission.js +44 -0
- package/dist/model-preference.d.ts +20 -0
- package/dist/model-preference.js +30 -0
- package/dist/node.d.ts +64 -0
- package/dist/node.js +256 -0
- package/dist/remote-client.d.ts +11 -0
- package/dist/remote-client.js +298 -0
- package/dist/session-attention.d.ts +35 -0
- package/dist/session-attention.js +22 -0
- package/dist/session-documents.d.ts +28 -0
- package/dist/session-documents.js +1 -0
- package/dist/testing.d.ts +54 -0
- package/dist/testing.js +215 -0
- package/docs/api/README.md +26 -0
- package/docs/api/agar-extension.md +40 -0
- package/docs/api/agent-control.md +151 -0
- package/docs/api/client-entry.md +344 -0
- package/docs/api/client.md +1112 -0
- package/docs/api/cloudflare.md +500 -0
- package/docs/api/effect.md +884 -0
- package/docs/api/extensions.md +2003 -0
- package/docs/api/fleet.md +734 -0
- package/docs/api/hosted.md +382 -0
- package/docs/api/http.md +111 -0
- package/docs/api/index.md +1116 -0
- package/docs/api/model-preference.md +151 -0
- package/docs/api/node.md +142 -0
- package/docs/api/testing.md +250 -0
- package/package.json +130 -3
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import * as Result from "effect/Result";
|
|
2
|
+
import * as Schema from "effect/Schema";
|
|
3
|
+
import { ShiitakeError } from "../errors.js";
|
|
4
|
+
const encoder = new TextEncoder();
|
|
5
|
+
function boundedText(maximumBytes) {
|
|
6
|
+
return Schema.String.check(Schema.isMinLength(1), Schema.isMaxLength(maximumBytes), Schema.makeFilter((value) => encoder.encode(value).byteLength <= maximumBytes));
|
|
7
|
+
}
|
|
8
|
+
const boundedId = boundedText(256);
|
|
9
|
+
/**
|
|
10
|
+
* The one prompt-text grammar. Human, mail, host and extension prompts are all
|
|
11
|
+
* admitted through the accepted-command door under this bound. Text is
|
|
12
|
+
* stored exactly as sent: no decoder trims it.
|
|
13
|
+
*/
|
|
14
|
+
const PromptTextSchema = boundedText(256 * 1024);
|
|
15
|
+
/** Bounded image payload admitted and persisted by the Session owner. */
|
|
16
|
+
export const PromptImageSchema = Schema.Struct({
|
|
17
|
+
type: Schema.Literal("image"),
|
|
18
|
+
data: Schema.String.check(Schema.isMinLength(1), Schema.isMaxLength(1024 * 1024), Schema.isBase64()),
|
|
19
|
+
mimeType: Schema.String.check(Schema.makeFilter((value) => ["image/png", "image/jpeg", "image/gif", "image/webp"].includes(value))),
|
|
20
|
+
});
|
|
21
|
+
const PromptImagesSchema = Schema.Array(PromptImageSchema).check(Schema.isMinLength(1), Schema.isMaxLength(8));
|
|
22
|
+
const PromptDeliverySchema = Schema.Literals(["steer", "followUp"]);
|
|
23
|
+
const SessionIdSchema = boundedId;
|
|
24
|
+
const decodeSessionPageCursorValue = Schema.decodeUnknownResult(boundedText(512));
|
|
25
|
+
const SessionCreationRequestSchema = Schema.Struct({ requestId: boundedId });
|
|
26
|
+
const PromptCommandSchema = Schema.Struct({
|
|
27
|
+
delivery: PromptDeliverySchema,
|
|
28
|
+
requestId: boundedId,
|
|
29
|
+
type: Schema.Literal("prompt"),
|
|
30
|
+
text: Schema.String.check(Schema.isMaxLength(256 * 1024), Schema.makeFilter((value) => encoder.encode(value).byteLength <= 256 * 1024)),
|
|
31
|
+
images: Schema.optionalKey(PromptImagesSchema),
|
|
32
|
+
}).check(Schema.makeFilter((value) => (value.text.length > 0 || (value.images?.length ?? 0) > 0) &&
|
|
33
|
+
encoder.encode(value.text).byteLength +
|
|
34
|
+
(value.images ?? []).reduce((bytes, image) => bytes + image.data.length, 0) <=
|
|
35
|
+
1024 * 1024));
|
|
36
|
+
const AbortCommandSchema = Schema.Struct({ type: Schema.Literal("abort") });
|
|
37
|
+
const CancelCommandSchema = Schema.Struct({
|
|
38
|
+
commandId: boundedId,
|
|
39
|
+
onlyIfPending: Schema.optional(Schema.Boolean),
|
|
40
|
+
type: Schema.Literal("cancel"),
|
|
41
|
+
});
|
|
42
|
+
/**
|
|
43
|
+
* One human-to-human line recorded into an idle Session outside any run, for
|
|
44
|
+
* example during a live takeover: `operator` is a person answering for the
|
|
45
|
+
* Agent, `party` is the other side's message while the Agent is held. It is
|
|
46
|
+
* context for later runs, is idempotent by request id and starts no run.
|
|
47
|
+
*/
|
|
48
|
+
const OperatorCommandSchema = Schema.Struct({
|
|
49
|
+
type: Schema.Literal("operator"),
|
|
50
|
+
requestId: boundedId,
|
|
51
|
+
author: Schema.Literals(["operator", "party"]),
|
|
52
|
+
text: PromptTextSchema,
|
|
53
|
+
});
|
|
54
|
+
const BranchCommandSchema = Schema.Struct({
|
|
55
|
+
type: Schema.Literal("branch"),
|
|
56
|
+
entryId: boundedId,
|
|
57
|
+
});
|
|
58
|
+
const ForkCommandSchema = Schema.Struct({
|
|
59
|
+
type: Schema.Literal("fork"),
|
|
60
|
+
entryId: boundedId,
|
|
61
|
+
});
|
|
62
|
+
/**
|
|
63
|
+
* Compact the Session's context now. It is accepted Session work, queued
|
|
64
|
+
* behind any running work and idempotent by `requestId`; it runs no model
|
|
65
|
+
* turn beyond the summary and never enters model context or a title.
|
|
66
|
+
*/
|
|
67
|
+
/** Bounded focus text a manual compaction passes to its summary. */
|
|
68
|
+
export const CompactionInstructionsSchema = Schema.String.check(Schema.isMinLength(1), Schema.isMaxLength(4_096));
|
|
69
|
+
const CompactCommandSchema = Schema.Struct({
|
|
70
|
+
type: Schema.Literal("compact"),
|
|
71
|
+
requestId: boundedId,
|
|
72
|
+
/** Focus for the summary, appended as Pi's "Additional focus". */
|
|
73
|
+
instructions: Schema.optionalKey(CompactionInstructionsSchema),
|
|
74
|
+
});
|
|
75
|
+
/** Work the accepted-command door admits: a prompt or a compaction request. */
|
|
76
|
+
const AcceptedCommandSchema = Schema.Union([
|
|
77
|
+
PromptCommandSchema,
|
|
78
|
+
CompactCommandSchema,
|
|
79
|
+
]);
|
|
80
|
+
/**
|
|
81
|
+
* The longest Session title, in Unicode code points; the derived first-prompt
|
|
82
|
+
* title uses the same bound.
|
|
83
|
+
*/
|
|
84
|
+
export const sessionTitleLimit = 120;
|
|
85
|
+
/** One user-set Session title: trimmed, single-line and bounded. */
|
|
86
|
+
export const SessionTitleSchema = Schema.String.check(Schema.makeFilter((value) => value.length > 0 &&
|
|
87
|
+
value.trim() === value &&
|
|
88
|
+
!/\p{Cc}/u.test(value) &&
|
|
89
|
+
Array.from(value).length <= sessionTitleLimit));
|
|
90
|
+
/**
|
|
91
|
+
* Set or clear the user's Session title. It is display metadata only: it
|
|
92
|
+
* overrides the first-prompt title everywhere the title shows, and `null`
|
|
93
|
+
* restores the derived title. Session identity and history are unchanged.
|
|
94
|
+
*/
|
|
95
|
+
const TitleCommandSchema = Schema.Struct({
|
|
96
|
+
type: Schema.Literal("title"),
|
|
97
|
+
title: Schema.NullOr(SessionTitleSchema),
|
|
98
|
+
});
|
|
99
|
+
const SessionCommandSchema = Schema.Union([
|
|
100
|
+
PromptCommandSchema,
|
|
101
|
+
AbortCommandSchema,
|
|
102
|
+
CancelCommandSchema,
|
|
103
|
+
BranchCommandSchema,
|
|
104
|
+
ForkCommandSchema,
|
|
105
|
+
OperatorCommandSchema,
|
|
106
|
+
CompactCommandSchema,
|
|
107
|
+
TitleCommandSchema,
|
|
108
|
+
]);
|
|
109
|
+
const SessionCommandRequestSchema = Schema.Struct({
|
|
110
|
+
command: SessionCommandSchema,
|
|
111
|
+
});
|
|
112
|
+
const ReadOptionsSchema = Schema.Struct({
|
|
113
|
+
includeSandboxFailures: Schema.optional(Schema.Boolean),
|
|
114
|
+
limit: Schema.optional(Schema.Int.check(Schema.isBetween({ minimum: 1, maximum: 200 }))),
|
|
115
|
+
before: Schema.optional(boundedText(512)),
|
|
116
|
+
});
|
|
117
|
+
const ReadQuerySchema = Schema.Struct({
|
|
118
|
+
includeSandboxFailures: Schema.optional(Schema.Literal("true")),
|
|
119
|
+
limit: Schema.optional(Schema.NumberFromString.check(Schema.isInt(), Schema.isBetween({ minimum: 1, maximum: 200 }))),
|
|
120
|
+
before: Schema.optional(boundedText(512)),
|
|
121
|
+
});
|
|
122
|
+
const SubscribeOptionsSchema = Schema.Struct({
|
|
123
|
+
includeSandboxFailures: Schema.optional(Schema.Boolean),
|
|
124
|
+
after: Schema.optional(Schema.String),
|
|
125
|
+
});
|
|
126
|
+
const EventsQuerySchema = Schema.Struct({
|
|
127
|
+
includeSandboxFailures: Schema.optional(Schema.Literal("true")),
|
|
128
|
+
mode: Schema.optional(Schema.Literal("subscribe")),
|
|
129
|
+
after: Schema.optional(Schema.String),
|
|
130
|
+
limit: Schema.optional(Schema.NumberFromString.check(Schema.isInt(), Schema.isBetween({ minimum: 1, maximum: 200 }))),
|
|
131
|
+
});
|
|
132
|
+
const closed = { onExcessProperty: "error" };
|
|
133
|
+
const decodeSessionIdValue = Schema.decodeUnknownResult(SessionIdSchema, closed);
|
|
134
|
+
export const decodeClientSessionCreationRequest = Schema.decodeUnknownResult(SessionCreationRequestSchema, closed);
|
|
135
|
+
/** The accepted-command door's one decoder for queued Session work. */
|
|
136
|
+
export const decodeClientAcceptedCommand = Schema.decodeUnknownResult(AcceptedCommandSchema, closed);
|
|
137
|
+
/** Decode one owner-issued identity (command, extension or cursor). */
|
|
138
|
+
export const decodeClientIdentity = Schema.decodeUnknownResult(boundedId);
|
|
139
|
+
export const decodeClientSessionCommand = Schema.decodeUnknownResult(SessionCommandSchema, closed);
|
|
140
|
+
export const decodeClientSessionCommandRequest = Schema.decodeUnknownResult(SessionCommandRequestSchema, closed);
|
|
141
|
+
const decodeReadOptionsValue = Schema.decodeUnknownResult(Schema.Union([ReadOptionsSchema, Schema.Undefined]), closed);
|
|
142
|
+
export const decodeClientReadQuery = Schema.decodeUnknownResult(ReadQuerySchema, closed);
|
|
143
|
+
const decodeSubscribeOptionsValue = Schema.decodeUnknownResult(SubscribeOptionsSchema, closed);
|
|
144
|
+
export const decodeClientEventsQuery = Schema.decodeUnknownResult(EventsQuerySchema, closed);
|
|
145
|
+
/** Decode one ordinary Session-id value without exposing input detail. */
|
|
146
|
+
export function decodeClientSessionId(value) {
|
|
147
|
+
const decoded = decodeSessionIdValue(value);
|
|
148
|
+
if (decoded._tag === "Failure")
|
|
149
|
+
throw new ShiitakeError("contract", "invalid", "Session id is invalid");
|
|
150
|
+
return decoded.success;
|
|
151
|
+
}
|
|
152
|
+
/** Decode one opaque session-page cursor; its owner decodes the position. */
|
|
153
|
+
export function decodeClientPageCursor(value) {
|
|
154
|
+
const decoded = decodeSessionPageCursorValue(value);
|
|
155
|
+
if (decoded._tag === "Failure")
|
|
156
|
+
throw new ShiitakeError("contract", "invalid", "Session cursor is invalid");
|
|
157
|
+
return decoded.success;
|
|
158
|
+
}
|
|
159
|
+
/** Decode the exact public Session creation request. */
|
|
160
|
+
export function decodeClientSessionCreationRequestId(value) {
|
|
161
|
+
const decoded = decodeClientSessionCreationRequest(value);
|
|
162
|
+
if (decoded._tag === "Failure")
|
|
163
|
+
throw new ShiitakeError("contract", "invalid", "Session creation request is invalid");
|
|
164
|
+
return decoded.success.requestId;
|
|
165
|
+
}
|
|
166
|
+
/** Decode and normalize the exact public C1 read option bag. */
|
|
167
|
+
export function decodeClientReadOptions(value) {
|
|
168
|
+
const decoded = decodeReadOptionsValue(value);
|
|
169
|
+
if (decoded._tag === "Failure")
|
|
170
|
+
throw new ShiitakeError("contract", "invalid", "Read options are invalid");
|
|
171
|
+
const read = decoded.success ?? {};
|
|
172
|
+
let options = { limit: read.limit ?? 200 };
|
|
173
|
+
if (read.before !== undefined)
|
|
174
|
+
options = { ...options, before: read.before };
|
|
175
|
+
if (read.includeSandboxFailures !== undefined)
|
|
176
|
+
options = {
|
|
177
|
+
...options,
|
|
178
|
+
includeSandboxFailures: read.includeSandboxFailures,
|
|
179
|
+
};
|
|
180
|
+
return Object.freeze(options);
|
|
181
|
+
}
|
|
182
|
+
/** Decode the exact public C2 subscribe option bag before cursor classification. */
|
|
183
|
+
export function decodeClientSubscribeOptions(value) {
|
|
184
|
+
const decoded = decodeSubscribeOptionsValue(value);
|
|
185
|
+
if (decoded._tag === "Failure")
|
|
186
|
+
throw new ShiitakeError("contract", "invalid", "Subscribe options are invalid");
|
|
187
|
+
let options = {};
|
|
188
|
+
if (decoded.success.after !== undefined)
|
|
189
|
+
options = { ...options, after: decoded.success.after };
|
|
190
|
+
if (decoded.success.includeSandboxFailures !== undefined)
|
|
191
|
+
options = {
|
|
192
|
+
...options,
|
|
193
|
+
includeSandboxFailures: decoded.success.includeSandboxFailures,
|
|
194
|
+
};
|
|
195
|
+
return Object.freeze(options);
|
|
196
|
+
}
|
|
197
|
+
/** Take one decoder result, or reject with a sanitized contract error. */
|
|
198
|
+
export function requireDecoded(decoded, message) {
|
|
199
|
+
if (Result.isFailure(decoded))
|
|
200
|
+
throw new ShiitakeError("contract", "invalid", message);
|
|
201
|
+
return decoded.success;
|
|
202
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pi v0.84.4 semantic compaction, adapted only at Woodstock's entry boundary.
|
|
3
|
+
* The retained preparation/result vocabulary is intentionally visible here;
|
|
4
|
+
* Shiitake supplies native Message values and its selected model stream.
|
|
5
|
+
*/
|
|
6
|
+
import type { Api, Model, Usage } from "@earendil-works/pi-ai";
|
|
7
|
+
import type { MuleStreamFunction } from "@fungi.computer/mule/effect";
|
|
8
|
+
import * as Effect from "effect/Effect";
|
|
9
|
+
import type { BorrowedCompactionViewV0 } from "../../extensions.js";
|
|
10
|
+
import { ShiitakeError } from "../../errors.js";
|
|
11
|
+
import { createFileOps, type CompactionSettings } from "./utils.js";
|
|
12
|
+
export type CompactionSourceEntry = Readonly<{
|
|
13
|
+
id: string;
|
|
14
|
+
kind: "message";
|
|
15
|
+
message: import("@earendil-works/pi-ai").Message;
|
|
16
|
+
}> | Readonly<{
|
|
17
|
+
id: string;
|
|
18
|
+
kind: "compaction";
|
|
19
|
+
timestamp: number;
|
|
20
|
+
firstKeptEntryId: string;
|
|
21
|
+
summary: string;
|
|
22
|
+
usage: Usage;
|
|
23
|
+
details?: Readonly<{
|
|
24
|
+
readFiles: readonly string[];
|
|
25
|
+
modifiedFiles: readonly string[];
|
|
26
|
+
}>;
|
|
27
|
+
}>;
|
|
28
|
+
export type CompactionPreparation = Readonly<{
|
|
29
|
+
firstKeptEntryId: string;
|
|
30
|
+
messagesToSummarize: readonly import("@earendil-works/pi-ai").Message[];
|
|
31
|
+
turnPrefixMessages: readonly import("@earendil-works/pi-ai").Message[];
|
|
32
|
+
isSplitTurn: boolean;
|
|
33
|
+
tokensBefore: number;
|
|
34
|
+
previousSummary?: string;
|
|
35
|
+
fileOps: ReturnType<typeof createFileOps>;
|
|
36
|
+
settings: CompactionSettings;
|
|
37
|
+
}>;
|
|
38
|
+
export type CompactionResult = Readonly<{
|
|
39
|
+
summary: string;
|
|
40
|
+
firstKeptEntryId: string;
|
|
41
|
+
tokensBefore: number;
|
|
42
|
+
usage: Usage;
|
|
43
|
+
details: Readonly<{
|
|
44
|
+
readFiles: readonly string[];
|
|
45
|
+
modifiedFiles: readonly string[];
|
|
46
|
+
}>;
|
|
47
|
+
}>;
|
|
48
|
+
/** Mechanically retained Pi preparation, using only context-visible Woodstock entries. */
|
|
49
|
+
export declare function prepareCompaction(entries: readonly CompactionSourceEntry[], settings: CompactionSettings): CompactionPreparation | undefined;
|
|
50
|
+
/** Generate Pi's history and optional split-turn summaries, in order. */
|
|
51
|
+
export declare function compact(preparation: CompactionPreparation, model: Model<Api>, streamFn?: MuleStreamFunction,
|
|
52
|
+
/** A manual compaction's focus, appended to the history summary prompt. */
|
|
53
|
+
instructions?: string): Effect.Effect<CompactionResult, ShiitakeError>;
|
|
54
|
+
export declare function borrowedView(preparation: CompactionPreparation, result: CompactionResult, contextRevision: string): BorrowedCompactionViewV0;
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
// Leaf helpers, not Pi's root entry, which also evaluates its validators.
|
|
2
|
+
import { retryAssistantCall } from "@earendil-works/pi-ai/utils/retry";
|
|
3
|
+
import { contentText } from "@earendil-works/pi-ai/utils/text";
|
|
4
|
+
import { normalizeContext } from "@earendil-works/pi-ai/utils/transcript";
|
|
5
|
+
import { uuidv7 } from "@earendil-works/pi-ai/utils/uuid";
|
|
6
|
+
import * as Effect from "effect/Effect";
|
|
7
|
+
import { ShiitakeError } from "../../errors.js";
|
|
8
|
+
import { computeFileLists, createFileOps, extractFileOpsFromMessage, formatFileOperations, serializeConversation, SUMMARIZATION_PROMPT, SUMMARIZATION_SYSTEM_PROMPT, TURN_PREFIX_SUMMARIZATION_PROMPT, UPDATE_SUMMARIZATION_PROMPT, compactionSummaryMessage, estimateContextTokens, estimateTokens, summaryFailure, } from "./utils.js";
|
|
9
|
+
/** A summary is a model call: its bounded provider message is shown. */
|
|
10
|
+
function failure(message) {
|
|
11
|
+
return new ShiitakeError("model", "rejected", message);
|
|
12
|
+
}
|
|
13
|
+
function combineUsage(first, second) {
|
|
14
|
+
const combined = {
|
|
15
|
+
input: first.input + second.input,
|
|
16
|
+
output: first.output + second.output,
|
|
17
|
+
cacheRead: first.cacheRead + second.cacheRead,
|
|
18
|
+
cacheWrite: first.cacheWrite + second.cacheWrite,
|
|
19
|
+
totalTokens: first.totalTokens + second.totalTokens,
|
|
20
|
+
cost: {
|
|
21
|
+
input: first.cost.input + second.cost.input,
|
|
22
|
+
output: first.cost.output + second.cost.output,
|
|
23
|
+
cacheRead: first.cost.cacheRead + second.cost.cacheRead,
|
|
24
|
+
cacheWrite: first.cost.cacheWrite + second.cost.cacheWrite,
|
|
25
|
+
total: first.cost.total + second.cost.total,
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
if (first.cacheWrite1h !== undefined || second.cacheWrite1h !== undefined)
|
|
29
|
+
combined.cacheWrite1h =
|
|
30
|
+
(first.cacheWrite1h ?? 0) + (second.cacheWrite1h ?? 0);
|
|
31
|
+
if (first.reasoning !== undefined || second.reasoning !== undefined)
|
|
32
|
+
combined.reasoning = (first.reasoning ?? 0) + (second.reasoning ?? 0);
|
|
33
|
+
return combined;
|
|
34
|
+
}
|
|
35
|
+
function contextMessages(entries) {
|
|
36
|
+
let latest;
|
|
37
|
+
for (const entry of entries) {
|
|
38
|
+
if (entry.kind === "compaction")
|
|
39
|
+
latest = entry;
|
|
40
|
+
}
|
|
41
|
+
const latestCompaction = latest;
|
|
42
|
+
const keptIndex = latestCompaction === undefined
|
|
43
|
+
? 0
|
|
44
|
+
: Math.max(0, entries.findIndex(({ id }) => id === latestCompaction.firstKeptEntryId));
|
|
45
|
+
const messages = entries.flatMap((entry, index) => entry.kind === "message" && index >= keptIndex ? [entry.message] : []);
|
|
46
|
+
if (latestCompaction === undefined)
|
|
47
|
+
return messages;
|
|
48
|
+
return [
|
|
49
|
+
compactionSummaryMessage(latestCompaction.summary, latestCompaction.timestamp),
|
|
50
|
+
...messages,
|
|
51
|
+
];
|
|
52
|
+
}
|
|
53
|
+
function extractFileOperations(messages, entries, previousIndex) {
|
|
54
|
+
const fileOps = createFileOps();
|
|
55
|
+
const previous = entries[previousIndex];
|
|
56
|
+
if (previous?.kind === "compaction" && previous.details !== undefined) {
|
|
57
|
+
for (const file of previous.details.readFiles)
|
|
58
|
+
fileOps.read.add(file);
|
|
59
|
+
for (const file of previous.details.modifiedFiles)
|
|
60
|
+
fileOps.edited.add(file);
|
|
61
|
+
}
|
|
62
|
+
for (const message of messages)
|
|
63
|
+
extractFileOpsFromMessage(message, fileOps);
|
|
64
|
+
return fileOps;
|
|
65
|
+
}
|
|
66
|
+
function isCutPoint(message) {
|
|
67
|
+
return message.role !== "toolResult";
|
|
68
|
+
}
|
|
69
|
+
function selectCutIndex(entries, startIndex, endIndex, keepRecentTokens) {
|
|
70
|
+
const points = entries.flatMap((entry, index) => entry.kind === "message" &&
|
|
71
|
+
index >= startIndex &&
|
|
72
|
+
index < endIndex &&
|
|
73
|
+
isCutPoint(entry.message)
|
|
74
|
+
? [index]
|
|
75
|
+
: []);
|
|
76
|
+
const firstPoint = points[0];
|
|
77
|
+
if (firstPoint === undefined)
|
|
78
|
+
return undefined;
|
|
79
|
+
let accumulated = 0;
|
|
80
|
+
let cut = firstPoint;
|
|
81
|
+
for (let index = endIndex - 1; index >= startIndex; index -= 1) {
|
|
82
|
+
const entry = entries[index];
|
|
83
|
+
if (entry?.kind !== "message")
|
|
84
|
+
continue;
|
|
85
|
+
accumulated += estimateTokens(entry.message);
|
|
86
|
+
if (accumulated >= keepRecentTokens) {
|
|
87
|
+
// Pi 0.99.2: when trailing tool results alone exceed the budget, keep
|
|
88
|
+
// their tool call (the last cut point), never fall back to the start.
|
|
89
|
+
cut =
|
|
90
|
+
points.find((candidate) => candidate >= index) ?? points.at(-1) ?? cut;
|
|
91
|
+
break;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return cut;
|
|
95
|
+
}
|
|
96
|
+
function classifyCut(entries, startIndex, cut) {
|
|
97
|
+
const cutEntry = entries[cut];
|
|
98
|
+
const startsTurn = cutEntry?.kind === "message" && cutEntry.message.role === "user";
|
|
99
|
+
let turnStartIndex = -1;
|
|
100
|
+
if (!startsTurn) {
|
|
101
|
+
for (let index = cut; index >= startIndex; index -= 1) {
|
|
102
|
+
const entry = entries[index];
|
|
103
|
+
if (entry?.kind === "message" && entry.message.role === "user") {
|
|
104
|
+
turnStartIndex = index;
|
|
105
|
+
break;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return {
|
|
110
|
+
isSplitTurn: !startsTurn && turnStartIndex >= 0,
|
|
111
|
+
turnStartIndex,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
function findCutPoint(entries, startIndex, endIndex, keepRecentTokens) {
|
|
115
|
+
const cut = selectCutIndex(entries, startIndex, endIndex, keepRecentTokens);
|
|
116
|
+
if (cut === undefined)
|
|
117
|
+
return {
|
|
118
|
+
firstKeptEntryIndex: startIndex,
|
|
119
|
+
isSplitTurn: false,
|
|
120
|
+
turnStartIndex: -1,
|
|
121
|
+
};
|
|
122
|
+
return { firstKeptEntryIndex: cut, ...classifyCut(entries, startIndex, cut) };
|
|
123
|
+
}
|
|
124
|
+
/** Mechanically retained Pi preparation, using only context-visible Woodstock entries. */
|
|
125
|
+
export function prepareCompaction(entries, settings) {
|
|
126
|
+
const last = entries.at(-1);
|
|
127
|
+
if (last?.kind === "compaction")
|
|
128
|
+
return undefined;
|
|
129
|
+
let previousSummary;
|
|
130
|
+
let previousIndex = -1;
|
|
131
|
+
let boundaryStart = 0;
|
|
132
|
+
for (let index = entries.length - 1; index >= 0; index -= 1) {
|
|
133
|
+
const entry = entries[index];
|
|
134
|
+
if (entry?.kind === "compaction") {
|
|
135
|
+
previousSummary = entry.summary;
|
|
136
|
+
previousIndex = index;
|
|
137
|
+
const kept = entries.findIndex((candidate) => candidate.id === entry.firstKeptEntryId);
|
|
138
|
+
boundaryStart = kept >= 0 ? kept : index + 1;
|
|
139
|
+
break;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
const visible = entries.flatMap((entry, index) => entry.kind === "message" && index >= boundaryStart ? [entry] : []);
|
|
143
|
+
const tokensBefore = estimateContextTokens(contextMessages(entries)).tokens;
|
|
144
|
+
const cut = findCutPoint(visible, 0, visible.length, settings.keepRecentTokens);
|
|
145
|
+
const firstKept = visible[cut.firstKeptEntryIndex];
|
|
146
|
+
if (firstKept?.kind !== "message")
|
|
147
|
+
return undefined;
|
|
148
|
+
const historyEnd = cut.isSplitTurn
|
|
149
|
+
? cut.turnStartIndex
|
|
150
|
+
: cut.firstKeptEntryIndex;
|
|
151
|
+
const messagesToSummarize = Object.freeze(visible.slice(0, historyEnd).map(({ message }) => message));
|
|
152
|
+
const turnPrefixMessages = Object.freeze(cut.isSplitTurn
|
|
153
|
+
? visible
|
|
154
|
+
.slice(cut.turnStartIndex, cut.firstKeptEntryIndex)
|
|
155
|
+
.map(({ message }) => message)
|
|
156
|
+
: []);
|
|
157
|
+
if (messagesToSummarize.length === 0 && turnPrefixMessages.length === 0)
|
|
158
|
+
return undefined;
|
|
159
|
+
const fileOps = extractFileOperations(messagesToSummarize, entries, previousIndex);
|
|
160
|
+
for (const message of turnPrefixMessages)
|
|
161
|
+
extractFileOpsFromMessage(message, fileOps);
|
|
162
|
+
const base = {
|
|
163
|
+
firstKeptEntryId: firstKept.id,
|
|
164
|
+
messagesToSummarize,
|
|
165
|
+
turnPrefixMessages,
|
|
166
|
+
isSplitTurn: cut.isSplitTurn,
|
|
167
|
+
tokensBefore,
|
|
168
|
+
fileOps,
|
|
169
|
+
settings,
|
|
170
|
+
};
|
|
171
|
+
return previousSummary === undefined ? base : { ...base, previousSummary };
|
|
172
|
+
}
|
|
173
|
+
// Pi v0.84.4's compaction retry defaults; retryAssistantCall owns the retry policy.
|
|
174
|
+
const RETRY_POLICY = {
|
|
175
|
+
enabled: true,
|
|
176
|
+
maxRetries: 3,
|
|
177
|
+
baseDelayMs: 2000,
|
|
178
|
+
};
|
|
179
|
+
function completeSummarization(model, context, options, streamFn) {
|
|
180
|
+
const requestOptions = {
|
|
181
|
+
...options,
|
|
182
|
+
cacheRetention: "none",
|
|
183
|
+
sessionId: options.sessionId ?? uuidv7(),
|
|
184
|
+
};
|
|
185
|
+
return retryAssistantCall(() => streamFn
|
|
186
|
+
? Promise.resolve(streamFn(model, normalizeContext(context), requestOptions)).then((stream) => stream.result())
|
|
187
|
+
: import("@earendil-works/pi-ai/compat").then(({ completeSimple }) => completeSimple(model, context, requestOptions)), RETRY_POLICY, requestOptions.signal);
|
|
188
|
+
}
|
|
189
|
+
function buildContext(promptText) {
|
|
190
|
+
return {
|
|
191
|
+
systemPrompt: SUMMARIZATION_SYSTEM_PROMPT,
|
|
192
|
+
messages: [
|
|
193
|
+
{
|
|
194
|
+
role: "user",
|
|
195
|
+
content: [{ type: "text", text: promptText }],
|
|
196
|
+
timestamp: Date.now(),
|
|
197
|
+
},
|
|
198
|
+
],
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
function historyPrompt(preparation, hasPreviousSummary, instructions) {
|
|
202
|
+
const previousSummary = preparation.previousSummary ?? "";
|
|
203
|
+
const basePrompt = hasPreviousSummary
|
|
204
|
+
? UPDATE_SUMMARIZATION_PROMPT
|
|
205
|
+
: SUMMARIZATION_PROMPT;
|
|
206
|
+
const focused = instructions === undefined
|
|
207
|
+
? basePrompt
|
|
208
|
+
: `${basePrompt}\n\nAdditional focus: ${instructions}`;
|
|
209
|
+
return `<conversation>\n${serializeConversation(preparation.messagesToSummarize)}\n</conversation>\n\n${hasPreviousSummary ? `<previous-summary>\n${previousSummary}\n</previous-summary>\n\n` : ""}${focused}`;
|
|
210
|
+
}
|
|
211
|
+
function prefixPrompt(preparation) {
|
|
212
|
+
return `# Conversation\n${serializeConversation(preparation.turnPrefixMessages)}\n\n# Instructions\n${TURN_PREFIX_SUMMARIZATION_PROMPT}`;
|
|
213
|
+
}
|
|
214
|
+
async function summarize(model, maxTokens, signal, prompt, label, streamFn) {
|
|
215
|
+
const options = signal === undefined ? { maxTokens } : { maxTokens, signal };
|
|
216
|
+
const response = await completeSummarization(model, buildContext(prompt), options, streamFn);
|
|
217
|
+
const failureMessage = summaryFailure(response, label);
|
|
218
|
+
if (failureMessage !== undefined)
|
|
219
|
+
throw new Error(failureMessage);
|
|
220
|
+
return response;
|
|
221
|
+
}
|
|
222
|
+
function historySummary(preparation, model, maxTokens, signal, instructions, streamFn) {
|
|
223
|
+
if (preparation.messagesToSummarize.length === 0)
|
|
224
|
+
return Promise.resolve(undefined);
|
|
225
|
+
const hasPreviousSummary = preparation.previousSummary !== undefined &&
|
|
226
|
+
preparation.previousSummary.length > 0;
|
|
227
|
+
return summarize(model, maxTokens, signal, historyPrompt(preparation, hasPreviousSummary, instructions), "Summarization", streamFn);
|
|
228
|
+
}
|
|
229
|
+
function prefixSummary(preparation, model, signal, streamFn) {
|
|
230
|
+
if (!preparation.isSplitTurn || preparation.turnPrefixMessages.length === 0)
|
|
231
|
+
return Promise.resolve(undefined);
|
|
232
|
+
return summarize(model, Math.min(Math.floor(0.5 * preparation.settings.reserveTokens), model.maxTokens > 0 ? model.maxTokens : Number.POSITIVE_INFINITY), signal, prefixPrompt(preparation), "Turn prefix summarization", streamFn);
|
|
233
|
+
}
|
|
234
|
+
function composeCompactionResult(preparation, historyResult, turnPrefixResult) {
|
|
235
|
+
let summary;
|
|
236
|
+
let usage;
|
|
237
|
+
if (preparation.isSplitTurn) {
|
|
238
|
+
if (turnPrefixResult === undefined)
|
|
239
|
+
throw new Error("Split-turn compaction did not produce a prefix summary");
|
|
240
|
+
// With no new history to summarize, the previous summary still stands.
|
|
241
|
+
const historyText = historyResult === undefined
|
|
242
|
+
? (preparation.previousSummary ?? "No prior history.")
|
|
243
|
+
: contentText(historyResult.content);
|
|
244
|
+
summary = `${historyText}\n\n---\n\n**Turn Context (split turn):**\n\n${contentText(turnPrefixResult.content)}`;
|
|
245
|
+
usage =
|
|
246
|
+
historyResult === undefined
|
|
247
|
+
? turnPrefixResult.usage
|
|
248
|
+
: combineUsage(historyResult.usage, turnPrefixResult.usage);
|
|
249
|
+
}
|
|
250
|
+
else {
|
|
251
|
+
if (historyResult === undefined)
|
|
252
|
+
throw new Error("Compaction did not produce a history summary");
|
|
253
|
+
summary = contentText(historyResult.content);
|
|
254
|
+
usage = historyResult.usage;
|
|
255
|
+
}
|
|
256
|
+
const files = computeFileLists(preparation.fileOps);
|
|
257
|
+
return {
|
|
258
|
+
summary: summary + formatFileOperations(files.readFiles, files.modifiedFiles),
|
|
259
|
+
firstKeptEntryId: preparation.firstKeptEntryId,
|
|
260
|
+
tokensBefore: preparation.tokensBefore,
|
|
261
|
+
usage,
|
|
262
|
+
details: files,
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
async function compactPromise(preparation, model, signal, instructions, streamFn) {
|
|
266
|
+
const maxTokens = Math.min(Math.floor(0.8 * preparation.settings.reserveTokens), model.maxTokens > 0 ? model.maxTokens : Number.POSITIVE_INFINITY);
|
|
267
|
+
// Pi runs the two summaries one after the other: in order, and never two
|
|
268
|
+
// concurrent calls against a single-concurrency provider (Pi #5536).
|
|
269
|
+
const historyResult = await historySummary(preparation, model, maxTokens, signal, instructions, streamFn);
|
|
270
|
+
const turnPrefixResult = preparation.isSplitTurn && preparation.turnPrefixMessages.length > 0
|
|
271
|
+
? await prefixSummary(preparation, model, signal, streamFn)
|
|
272
|
+
: undefined;
|
|
273
|
+
return composeCompactionResult(preparation, historyResult, turnPrefixResult);
|
|
274
|
+
}
|
|
275
|
+
/** Generate Pi's history and optional split-turn summaries, in order. */
|
|
276
|
+
export function compact(preparation, model, streamFn,
|
|
277
|
+
/** A manual compaction's focus, appended to the history summary prompt. */
|
|
278
|
+
instructions) {
|
|
279
|
+
return Effect.tryPromise({
|
|
280
|
+
try: (effectSignal) => compactPromise(preparation, model, effectSignal, instructions, streamFn),
|
|
281
|
+
catch: (error) => failure(error instanceof Error ? error.message : String(error)),
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
export function borrowedView(preparation, result, contextRevision) {
|
|
285
|
+
return {
|
|
286
|
+
messagesToSummarize: preparation.messagesToSummarize,
|
|
287
|
+
turnPrefixMessages: preparation.turnPrefixMessages,
|
|
288
|
+
result,
|
|
289
|
+
contextRevision,
|
|
290
|
+
};
|
|
291
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { type Message, type Model, type Api, type MuleRunOutcome, type MuleStreamFunction } from "@fungi.computer/mule/effect";
|
|
2
|
+
import * as Effect from "effect/Effect";
|
|
3
|
+
import type { CompactionHandoffV0 } from "../../extensions.js";
|
|
4
|
+
import { ShiitakeError } from "../../errors.js";
|
|
5
|
+
import { type CompactionSettings } from "./utils.js";
|
|
6
|
+
import { type StoredSessionRow } from "../woodstock/index.js";
|
|
7
|
+
import type { SqliteOwner } from "../contracts.js";
|
|
8
|
+
declare const commitFailures: {
|
|
9
|
+
readonly handoff: "The compaction handoff failed; the conversation context is unchanged";
|
|
10
|
+
readonly conflict: "The Session changed during compaction; the conversation context is unchanged";
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* A commit that stopped before its CAS append took effect. The phase is
|
|
14
|
+
* owner-closed, so its diagnostic is safe to record and show.
|
|
15
|
+
*/
|
|
16
|
+
export declare class CompactionCommitError extends ShiitakeError {
|
|
17
|
+
readonly phase: keyof typeof commitFailures;
|
|
18
|
+
constructor(phase: keyof typeof commitFailures);
|
|
19
|
+
get diagnostic(): string;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Replace the context before a running work's next assistant request, or
|
|
23
|
+
* keep it (`undefined`). Run scope hands this to Mule's between-turn point.
|
|
24
|
+
*/
|
|
25
|
+
export type BetweenTurns = (context: readonly Message[], newMessageCount: number) => Effect.Effect<readonly Message[] | undefined, ShiitakeError>;
|
|
26
|
+
export type SessionCompactionInput = Readonly<{
|
|
27
|
+
store: SqliteOwner;
|
|
28
|
+
sessionId: string;
|
|
29
|
+
workId: string;
|
|
30
|
+
model: Model<Api>;
|
|
31
|
+
stream?: MuleStreamFunction | undefined;
|
|
32
|
+
handoff?: CompactionHandoffV0 | undefined;
|
|
33
|
+
/** This model's resolved budgets and the global automatic switch. */
|
|
34
|
+
settings: CompactionSettings;
|
|
35
|
+
path: readonly StoredSessionRow[];
|
|
36
|
+
messages: readonly Message[];
|
|
37
|
+
/** Finish the durable assistant/tool boundary before compacting it. */
|
|
38
|
+
resuming?: boolean;
|
|
39
|
+
run: (messages: readonly Message[], betweenTurns: BetweenTurns) => Effect.Effect<MuleRunOutcome, ShiitakeError>;
|
|
40
|
+
}>;
|
|
41
|
+
/**
|
|
42
|
+
* The compaction owner's context estimate: Pi's usage-plus-trailing count,
|
|
43
|
+
* excluding provider usage that belongs before Woodstock's latest boundary.
|
|
44
|
+
*
|
|
45
|
+
* A provider's reported usage measures the context it actually read, so it
|
|
46
|
+
* is authoritative only while nothing has reshaped that context since. Once
|
|
47
|
+
* a compaction lands, every kept message's own usage figure is stale: it
|
|
48
|
+
* priced the pre-compaction history, not the summary that replaced it. The
|
|
49
|
+
* one safe fallback is a fresh chars/4 estimate of every message still in
|
|
50
|
+
* view, the same method `tokensAfterCompaction` uses when it records the
|
|
51
|
+
* marker's own `tokensAfter` at commit time; trailing-after-usage alone
|
|
52
|
+
* would undercount (even to zero, when the kept tail's own reply is the
|
|
53
|
+
* newest message and nothing followed it).
|
|
54
|
+
*/
|
|
55
|
+
export declare function estimateThresholdContextTokens(path: readonly StoredSessionRow[], messages: readonly Message[]): number;
|
|
56
|
+
/** Compose the retained pre-run, between-turn and post-run compaction phases. */
|
|
57
|
+
export declare function runWithCompaction(input: SessionCompactionInput): Effect.Effect<MuleRunOutcome, ShiitakeError>;
|
|
58
|
+
/** What a manual compaction did; a failure is the Effect's error. */
|
|
59
|
+
export type ManualCompactionOutcome = "compacted" | "nothing_to_compact";
|
|
60
|
+
/**
|
|
61
|
+
* Compact one Session now, as accepted work: the same commit the automatic
|
|
62
|
+
* triggers use, with `resumesRun: false`, the user's optional focus and no
|
|
63
|
+
* model turn. "Nothing to compact" and a failure are each recorded as a
|
|
64
|
+
* notice. A retried claim of the same work never compacts twice.
|
|
65
|
+
*/
|
|
66
|
+
export declare function compactSession(input: Pick<SessionCompactionInput, "store" | "sessionId" | "workId" | "model" | "stream" | "handoff" | "settings" | "path"> & Readonly<{
|
|
67
|
+
instructions?: string | undefined;
|
|
68
|
+
}>): Effect.Effect<ManualCompactionOutcome, ShiitakeError>;
|
|
69
|
+
export {};
|