@omercnet/paseo-omp 0.4.0-next.123.1 → 0.4.0-next.124.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
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/** Release Please keeps this bundle-safe constant aligned with package.json. */
|
|
2
|
-
export const PASEO_OMP_PACKAGE_VERSION = "0.4.0-next.
|
|
2
|
+
export const PASEO_OMP_PACKAGE_VERSION = "0.4.0-next.124.1"; // x-release-please-version
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import type { ProviderEvent } from "@getpaseo/plugin/server/provider";
|
|
3
|
+
import type { OmpRpcEvent } from "./omp-rpc-protocol";
|
|
4
|
+
import type { OmpPublicDataSerializer } from "./security";
|
|
5
|
+
import type { ActiveTurn } from "./session-terminal";
|
|
6
|
+
import type { OmpSessionUsage } from "./session-usage";
|
|
7
|
+
import type { OmpTimelineProjector } from "./timeline-projector";
|
|
8
|
+
|
|
9
|
+
type Emit = (event: ProviderEvent) => void;
|
|
10
|
+
|
|
11
|
+
export interface ActiveCompaction {
|
|
12
|
+
id: string;
|
|
13
|
+
trigger: "auto" | "manual";
|
|
14
|
+
turnId: string;
|
|
15
|
+
generation: number;
|
|
16
|
+
retrying: boolean;
|
|
17
|
+
action?: string;
|
|
18
|
+
preTokens?: number;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export class OmpSessionCompaction {
|
|
22
|
+
private operation: ActiveCompaction | null = null;
|
|
23
|
+
private discardedEnds = 0;
|
|
24
|
+
|
|
25
|
+
constructor(
|
|
26
|
+
private readonly sessionId: string,
|
|
27
|
+
private readonly emit: Emit,
|
|
28
|
+
private readonly projector: OmpTimelineProjector,
|
|
29
|
+
private readonly dataFilter: OmpPublicDataSerializer,
|
|
30
|
+
private readonly usage: OmpSessionUsage,
|
|
31
|
+
) {}
|
|
32
|
+
|
|
33
|
+
get active(): ActiveCompaction | null {
|
|
34
|
+
return this.operation;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
start(turn: ActiveTurn, trigger: "auto" | "manual", action?: string): void {
|
|
38
|
+
if (this.discardedEnds > 0) {
|
|
39
|
+
this.discardedEnds += 1;
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
const active = this.operation;
|
|
43
|
+
if (active && active.trigger === trigger && active.action === action) {
|
|
44
|
+
active.retrying = false;
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
if (active) {
|
|
48
|
+
this.retire("OMP emitted overlapping compactions");
|
|
49
|
+
this.discardedEnds = 2;
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
const operation: ActiveCompaction = {
|
|
53
|
+
id: randomUUID(),
|
|
54
|
+
trigger,
|
|
55
|
+
turnId: turn.turnId,
|
|
56
|
+
generation: turn.generation,
|
|
57
|
+
action,
|
|
58
|
+
retrying: false,
|
|
59
|
+
preTokens: this.usage.latest?.contextWindowUsedTokens,
|
|
60
|
+
};
|
|
61
|
+
this.operation = operation;
|
|
62
|
+
this.projector.flush(true);
|
|
63
|
+
this.emit({
|
|
64
|
+
type: "timeline.item",
|
|
65
|
+
sessionId: this.sessionId,
|
|
66
|
+
item: {
|
|
67
|
+
id: operation.id,
|
|
68
|
+
type: "compaction",
|
|
69
|
+
status: "loading",
|
|
70
|
+
trigger,
|
|
71
|
+
...(operation.preTokens !== undefined ? { preTokens: operation.preTokens } : {}),
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
handleAutoEnd(
|
|
77
|
+
turn: ActiveTurn,
|
|
78
|
+
event: Extract<OmpRpcEvent, { type: "auto_compaction_end" }>,
|
|
79
|
+
): boolean {
|
|
80
|
+
if (this.discardedEnds > 0) {
|
|
81
|
+
this.discardedEnds -= 1;
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
const operation = this.operation;
|
|
85
|
+
if (
|
|
86
|
+
operation?.trigger !== "auto" ||
|
|
87
|
+
operation.turnId !== turn.turnId ||
|
|
88
|
+
operation.generation !== turn.generation
|
|
89
|
+
) {
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
if (event.action !== undefined && operation.action !== event.action) {
|
|
93
|
+
this.retire("OMP emitted overlapping compactions");
|
|
94
|
+
this.discardedEnds = 1;
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
if (event.willRetry) {
|
|
98
|
+
operation.retrying = true;
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
if (operation.retrying) {
|
|
102
|
+
operation.retrying = false;
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
const state = event.aborted
|
|
106
|
+
? "canceled"
|
|
107
|
+
: event.errorMessage
|
|
108
|
+
? "failed"
|
|
109
|
+
: event.skipped
|
|
110
|
+
? "skipped"
|
|
111
|
+
: "completed";
|
|
112
|
+
this.finish(state, {
|
|
113
|
+
tokensBefore: event.result?.tokensBefore ?? event.result?.preTokens,
|
|
114
|
+
message: event.errorMessage,
|
|
115
|
+
});
|
|
116
|
+
return true;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
finish(
|
|
120
|
+
state: "completed" | "failed" | "canceled" | "skipped",
|
|
121
|
+
options: { tokensBefore?: number | null; message?: string } = {},
|
|
122
|
+
): void {
|
|
123
|
+
const operation = this.operation;
|
|
124
|
+
if (!operation) return;
|
|
125
|
+
this.operation = null;
|
|
126
|
+
this.projector.flush(true);
|
|
127
|
+
if (state === "completed") {
|
|
128
|
+
this.usage.completeCompaction();
|
|
129
|
+
}
|
|
130
|
+
if (state !== "completed") {
|
|
131
|
+
const defaultMessage =
|
|
132
|
+
state === "failed"
|
|
133
|
+
? "OMP compaction failed"
|
|
134
|
+
: state === "canceled"
|
|
135
|
+
? "OMP compaction canceled"
|
|
136
|
+
: "OMP compaction skipped";
|
|
137
|
+
this.emit({
|
|
138
|
+
type: "timeline.item",
|
|
139
|
+
sessionId: this.sessionId,
|
|
140
|
+
item: {
|
|
141
|
+
id: operation.id,
|
|
142
|
+
type: "notification",
|
|
143
|
+
level: state === "failed" ? "error" : "info",
|
|
144
|
+
message: this.dataFilter.text(options.message ?? defaultMessage, 4_096),
|
|
145
|
+
},
|
|
146
|
+
});
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
const tokensBefore = options.tokensBefore ?? operation.preTokens;
|
|
150
|
+
this.emit({
|
|
151
|
+
type: "timeline.item",
|
|
152
|
+
sessionId: this.sessionId,
|
|
153
|
+
item: {
|
|
154
|
+
id: operation.id,
|
|
155
|
+
type: "compaction",
|
|
156
|
+
status: "completed",
|
|
157
|
+
trigger: operation.trigger,
|
|
158
|
+
...(tokensBefore !== undefined ? { preTokens: tokensBefore } : {}),
|
|
159
|
+
},
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
retire(message: string): void {
|
|
164
|
+
const operation = this.operation;
|
|
165
|
+
if (!operation) return;
|
|
166
|
+
this.operation = null;
|
|
167
|
+
this.emit({
|
|
168
|
+
type: "timeline.item",
|
|
169
|
+
sessionId: this.sessionId,
|
|
170
|
+
item: {
|
|
171
|
+
id: operation.id,
|
|
172
|
+
type: "compaction",
|
|
173
|
+
status: "completed",
|
|
174
|
+
trigger: operation.trigger,
|
|
175
|
+
},
|
|
176
|
+
});
|
|
177
|
+
this.emit({
|
|
178
|
+
type: "timeline.item",
|
|
179
|
+
sessionId: this.sessionId,
|
|
180
|
+
item: { id: `${operation.id}:error`, type: "error", message },
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
resetDiscardedEnds(): void {
|
|
185
|
+
this.discardedEnds = 0;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import type { ProviderEvent, ProviderUsage } from "@getpaseo/plugin/server/provider";
|
|
2
|
+
import type { OmpRuntimeSession } from "./omp-rpc";
|
|
3
|
+
import type { OmpSessionState, OmpSessionStats } from "./omp-rpc-protocol";
|
|
4
|
+
import type { ActiveTurn } from "./session-terminal";
|
|
5
|
+
import type { OmpTimelineScheduler } from "./timeline-projector";
|
|
6
|
+
|
|
7
|
+
const USAGE_POLL_MS = 1_000;
|
|
8
|
+
|
|
9
|
+
interface UsageContext {
|
|
10
|
+
closed: boolean;
|
|
11
|
+
runtimeDead: boolean;
|
|
12
|
+
generation: number;
|
|
13
|
+
runtime: OmpRuntimeSession;
|
|
14
|
+
activeTurn: ActiveTurn | null;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
type Emit = (event: ProviderEvent) => void;
|
|
18
|
+
type ReadContext = () => UsageContext;
|
|
19
|
+
type IsPollable = (turn: ActiveTurn) => boolean;
|
|
20
|
+
|
|
21
|
+
interface UsageSample {
|
|
22
|
+
turn: ActiveTurn;
|
|
23
|
+
generation: number;
|
|
24
|
+
runtime: OmpRuntimeSession;
|
|
25
|
+
epoch: number;
|
|
26
|
+
sequence: number;
|
|
27
|
+
promise: Promise<OmpSessionState | undefined>;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function usageFrom(
|
|
31
|
+
state: OmpSessionState | undefined,
|
|
32
|
+
stats: OmpSessionStats | undefined,
|
|
33
|
+
): ProviderUsage | undefined {
|
|
34
|
+
if (!state?.contextUsage && !stats) return undefined;
|
|
35
|
+
const stateContext = state?.contextUsage;
|
|
36
|
+
const statsContext = stats?.contextUsage;
|
|
37
|
+
const modelCapacity = state?.model?.contextWindow;
|
|
38
|
+
const inputTokens = stats?.tokens?.input;
|
|
39
|
+
const cachedInputTokens = stats?.tokens?.cacheRead;
|
|
40
|
+
const outputTokens = stats?.tokens?.output;
|
|
41
|
+
const totalCostUsd = stats?.cost;
|
|
42
|
+
const contextTokens =
|
|
43
|
+
typeof stateContext?.tokens === "number"
|
|
44
|
+
? stateContext.tokens
|
|
45
|
+
: typeof statsContext?.tokens === "number"
|
|
46
|
+
? statsContext.tokens
|
|
47
|
+
: undefined;
|
|
48
|
+
const contextWindow =
|
|
49
|
+
typeof stateContext?.contextWindow === "number" && stateContext.contextWindow > 0
|
|
50
|
+
? stateContext.contextWindow
|
|
51
|
+
: typeof statsContext?.contextWindow === "number" && statsContext.contextWindow > 0
|
|
52
|
+
? statsContext.contextWindow
|
|
53
|
+
: typeof modelCapacity === "number" && modelCapacity > 0
|
|
54
|
+
? modelCapacity
|
|
55
|
+
: undefined;
|
|
56
|
+
const usage: ProviderUsage = {
|
|
57
|
+
...(typeof inputTokens === "number" ? { inputTokens } : {}),
|
|
58
|
+
...(typeof cachedInputTokens === "number" ? { cachedInputTokens } : {}),
|
|
59
|
+
...(typeof outputTokens === "number" ? { outputTokens } : {}),
|
|
60
|
+
...(typeof totalCostUsd === "number" ? { totalCostUsd } : {}),
|
|
61
|
+
...(contextTokens !== undefined ? { contextWindowUsedTokens: contextTokens } : {}),
|
|
62
|
+
...(contextWindow !== undefined ? { contextWindowMaxTokens: contextWindow } : {}),
|
|
63
|
+
};
|
|
64
|
+
return Object.keys(usage).length > 0 ? usage : undefined;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export class OmpSessionUsage {
|
|
68
|
+
private epoch = 0;
|
|
69
|
+
private sequence = 0;
|
|
70
|
+
private sample: UsageSample | null = null;
|
|
71
|
+
private lastUsage: ProviderUsage | null = null;
|
|
72
|
+
|
|
73
|
+
constructor(
|
|
74
|
+
private readonly sessionId: string,
|
|
75
|
+
private readonly emit: Emit,
|
|
76
|
+
private readonly scheduler: OmpTimelineScheduler,
|
|
77
|
+
private readonly readContext: ReadContext,
|
|
78
|
+
private readonly isPollable: IsPollable,
|
|
79
|
+
) {}
|
|
80
|
+
|
|
81
|
+
get latest(): ProviderUsage | null {
|
|
82
|
+
return this.lastUsage;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
private ownsSample(turn: ActiveTurn, generation: number, runtime: OmpRuntimeSession): boolean {
|
|
86
|
+
const context = this.readContext();
|
|
87
|
+
return (
|
|
88
|
+
!context.closed &&
|
|
89
|
+
!context.runtimeDead &&
|
|
90
|
+
!turn.terminal &&
|
|
91
|
+
generation === context.generation &&
|
|
92
|
+
runtime === context.runtime &&
|
|
93
|
+
context.activeTurn === turn
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
publishSnapshot(
|
|
98
|
+
turn: ActiveTurn,
|
|
99
|
+
minimumEpoch = this.epoch,
|
|
100
|
+
minimumSequence = turn.usageSampleFloor,
|
|
101
|
+
): Promise<OmpSessionState | undefined> {
|
|
102
|
+
const { generation, runtime } = this.readContext();
|
|
103
|
+
if (!this.ownsSample(turn, generation, runtime)) return Promise.resolve(undefined);
|
|
104
|
+
const current = this.sample;
|
|
105
|
+
if (current) {
|
|
106
|
+
if (
|
|
107
|
+
current.turn === turn &&
|
|
108
|
+
current.generation === generation &&
|
|
109
|
+
current.runtime === runtime &&
|
|
110
|
+
current.epoch >= minimumEpoch &&
|
|
111
|
+
current.sequence >= minimumSequence
|
|
112
|
+
) {
|
|
113
|
+
return current.promise;
|
|
114
|
+
}
|
|
115
|
+
return current.promise.then(() => {
|
|
116
|
+
if (!this.ownsSample(turn, generation, runtime)) return undefined;
|
|
117
|
+
return this.publishSnapshot(turn, minimumEpoch, minimumSequence);
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
const epoch = this.epoch;
|
|
121
|
+
const sequence = ++this.sequence;
|
|
122
|
+
const promise = Promise.allSettled([runtime.getState(), runtime.getSessionStats()]).then(
|
|
123
|
+
([stateResult, statsResult]) => {
|
|
124
|
+
if (
|
|
125
|
+
!this.ownsSample(turn, generation, runtime) ||
|
|
126
|
+
epoch !== this.epoch ||
|
|
127
|
+
epoch < minimumEpoch ||
|
|
128
|
+
sequence < turn.usageSampleFloor ||
|
|
129
|
+
sequence < minimumSequence
|
|
130
|
+
) {
|
|
131
|
+
return undefined;
|
|
132
|
+
}
|
|
133
|
+
const state = stateResult.status === "fulfilled" ? stateResult.value : undefined;
|
|
134
|
+
const stats = statsResult.status === "fulfilled" ? statsResult.value : undefined;
|
|
135
|
+
const usage = usageFrom(state, stats);
|
|
136
|
+
if (usage) {
|
|
137
|
+
this.lastUsage = usage;
|
|
138
|
+
this.emit({
|
|
139
|
+
type: "session.usage",
|
|
140
|
+
sessionId: this.sessionId,
|
|
141
|
+
turnId: turn.turnId,
|
|
142
|
+
usage,
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
return state;
|
|
146
|
+
},
|
|
147
|
+
);
|
|
148
|
+
this.sample = { turn, generation, runtime, epoch, sequence, promise };
|
|
149
|
+
void promise.finally(() => {
|
|
150
|
+
if (this.sample?.promise === promise) this.sample = null;
|
|
151
|
+
});
|
|
152
|
+
return promise;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
async boundedSnapshot(turn: ActiveTurn, timeoutMs: number): Promise<OmpSessionState | undefined> {
|
|
156
|
+
const timeout = Promise.withResolvers<undefined>();
|
|
157
|
+
const timer = this.scheduler.set(() => timeout.resolve(undefined), timeoutMs);
|
|
158
|
+
try {
|
|
159
|
+
return await Promise.race([this.publishSnapshot(turn), timeout.promise]);
|
|
160
|
+
} finally {
|
|
161
|
+
this.scheduler.clear(timer);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
async boundedTerminalState(
|
|
166
|
+
turn: ActiveTurn,
|
|
167
|
+
timeoutMs: number,
|
|
168
|
+
): Promise<OmpSessionState | undefined> {
|
|
169
|
+
const { generation, runtime } = this.readContext();
|
|
170
|
+
const timeout = Promise.withResolvers<undefined>();
|
|
171
|
+
const timer = this.scheduler.set(() => timeout.resolve(undefined), timeoutMs);
|
|
172
|
+
try {
|
|
173
|
+
const state = await Promise.race([runtime.getState(), timeout.promise]);
|
|
174
|
+
return this.ownsSample(turn, generation, runtime) ? state : undefined;
|
|
175
|
+
} catch {
|
|
176
|
+
return undefined;
|
|
177
|
+
} finally {
|
|
178
|
+
this.scheduler.clear(timer);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
schedulePoll(turn: ActiveTurn, delayMs = USAGE_POLL_MS): void {
|
|
183
|
+
if (!this.isPollable(turn) || turn.usagePollTimer !== undefined) return;
|
|
184
|
+
turn.usagePollTimer = this.scheduler.set(() => {
|
|
185
|
+
turn.usagePollTimer = undefined;
|
|
186
|
+
this.poll(turn);
|
|
187
|
+
}, delayMs);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
poll(turn: ActiveTurn): void {
|
|
191
|
+
if (!this.isPollable(turn) || turn.usagePoll) return;
|
|
192
|
+
const poll = this.publishSnapshot(turn).then(() => undefined);
|
|
193
|
+
turn.usagePoll = poll;
|
|
194
|
+
void poll.finally(() => {
|
|
195
|
+
if (turn.usagePoll === poll) turn.usagePoll = undefined;
|
|
196
|
+
this.schedulePoll(turn);
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
stopPoll(turn: ActiveTurn): void {
|
|
201
|
+
if (turn.usagePollTimer === undefined) return;
|
|
202
|
+
this.scheduler.clear(turn.usagePollTimer);
|
|
203
|
+
turn.usagePollTimer = undefined;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
invalidateBeforeNextSample(turn: ActiveTurn): void {
|
|
207
|
+
turn.usageSampleFloor = this.sequence + 1;
|
|
208
|
+
if (this.sample?.turn === turn && this.sample.sequence < turn.usageSampleFloor) {
|
|
209
|
+
this.sample = null;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
completeCompaction(): void {
|
|
214
|
+
this.epoch += 1;
|
|
215
|
+
const staleSample = this.sample;
|
|
216
|
+
if (staleSample && staleSample.epoch < this.epoch) {
|
|
217
|
+
this.sample = null;
|
|
218
|
+
staleSample.turn.usagePoll = undefined;
|
|
219
|
+
}
|
|
220
|
+
this.lastUsage = null;
|
|
221
|
+
}
|
|
222
|
+
clearLatest(): void {
|
|
223
|
+
this.lastUsage = null;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
clearSample(turn: ActiveTurn): void {
|
|
227
|
+
if (this.sample?.turn === turn) this.sample = null;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
reset(): void {
|
|
231
|
+
this.sample = null;
|
|
232
|
+
this.lastUsage = null;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
@@ -7,7 +7,6 @@ import type {
|
|
|
7
7
|
ProviderPermissionResponse,
|
|
8
8
|
ProviderSessionConfig,
|
|
9
9
|
ProviderToolCallDetail,
|
|
10
|
-
ProviderUsage,
|
|
11
10
|
} from "@getpaseo/plugin/server/provider";
|
|
12
11
|
import type {
|
|
13
12
|
OmpOperationalFailure,
|
|
@@ -40,7 +39,6 @@ import type {
|
|
|
40
39
|
OmpModel,
|
|
41
40
|
OmpRpcEvent,
|
|
42
41
|
OmpSessionState,
|
|
43
|
-
OmpSessionStats,
|
|
44
42
|
OmpToolApprovalCancel,
|
|
45
43
|
OmpToolApprovalRequest,
|
|
46
44
|
} from "./omp-rpc-protocol";
|
|
@@ -64,6 +62,7 @@ import {
|
|
|
64
62
|
OmpPublicError,
|
|
65
63
|
utf8Bytes,
|
|
66
64
|
} from "./security";
|
|
65
|
+
import { OmpSessionCompaction } from "./session-compaction";
|
|
67
66
|
import type { OmpSessionDescriptor } from "./session-descriptors";
|
|
68
67
|
import { validateNativeSessionId } from "./session-descriptors";
|
|
69
68
|
import { isNativeTurnActivity, isPassiveUiMethod, isRuntimeConfigEvent } from "./session-events";
|
|
@@ -79,6 +78,7 @@ import {
|
|
|
79
78
|
terminalOutcome,
|
|
80
79
|
unknownTerminalOutcomeError,
|
|
81
80
|
} from "./session-terminal";
|
|
81
|
+
import { OmpSessionUsage } from "./session-usage";
|
|
82
82
|
import { OmpSubsessionProjector } from "./subsessions";
|
|
83
83
|
import {
|
|
84
84
|
defaultOmpTimelineScheduler,
|
|
@@ -295,16 +295,6 @@ function permissionFingerprint(request: OmpQuestionRequest): string {
|
|
|
295
295
|
return createHash("sha256").update(JSON.stringify(request)).digest("base64url");
|
|
296
296
|
}
|
|
297
297
|
|
|
298
|
-
type ActiveCompaction = {
|
|
299
|
-
id: string;
|
|
300
|
-
trigger: "auto" | "manual";
|
|
301
|
-
turnId: string;
|
|
302
|
-
generation: number;
|
|
303
|
-
retrying: boolean;
|
|
304
|
-
action?: string;
|
|
305
|
-
preTokens?: number;
|
|
306
|
-
};
|
|
307
|
-
|
|
308
298
|
function providerError(error: unknown, fallback: string): { message: string } {
|
|
309
299
|
return { message: isOmpPublicError(error) ? error.message : fallback };
|
|
310
300
|
}
|
|
@@ -364,19 +354,8 @@ export class OmpProviderSession {
|
|
|
364
354
|
private configRevision = 0;
|
|
365
355
|
private recoveryUsesNativeConfig = false;
|
|
366
356
|
private activeAbort: PendingAbort | null = null;
|
|
367
|
-
private
|
|
368
|
-
private
|
|
369
|
-
private usageSample: {
|
|
370
|
-
turn: ActiveTurn;
|
|
371
|
-
generation: number;
|
|
372
|
-
runtime: OmpRuntimeSession;
|
|
373
|
-
epoch: number;
|
|
374
|
-
sequence: number;
|
|
375
|
-
promise: Promise<OmpSessionState | undefined>;
|
|
376
|
-
} | null = null;
|
|
377
|
-
private activeCompaction: ActiveCompaction | null = null;
|
|
378
|
-
private discardedCompactionEnds = 0;
|
|
379
|
-
private lastUsage: ProviderUsage | null = null;
|
|
357
|
+
private readonly usage: OmpSessionUsage;
|
|
358
|
+
private readonly compaction: OmpSessionCompaction;
|
|
380
359
|
private revertInFlight = false;
|
|
381
360
|
private runtimeTurnCompleted = false;
|
|
382
361
|
private commandCatalog: OmpAvailableCommand[];
|
|
@@ -420,6 +399,26 @@ export class OmpProviderSession {
|
|
|
420
399
|
this.id = id;
|
|
421
400
|
this.cwd = config.cwd;
|
|
422
401
|
this.scheduler = scheduler;
|
|
402
|
+
this.usage = new OmpSessionUsage(
|
|
403
|
+
id,
|
|
404
|
+
emit,
|
|
405
|
+
scheduler,
|
|
406
|
+
() => ({
|
|
407
|
+
closed: this.closed,
|
|
408
|
+
runtimeDead: this.runtimeDead !== null,
|
|
409
|
+
generation: this.generation,
|
|
410
|
+
runtime: this.runtime,
|
|
411
|
+
activeTurn: this.activeTurn,
|
|
412
|
+
}),
|
|
413
|
+
(turn) =>
|
|
414
|
+
!this.closed &&
|
|
415
|
+
!this.runtimeDead &&
|
|
416
|
+
!turn.terminal &&
|
|
417
|
+
!turn.terminalizing &&
|
|
418
|
+
!turn.agentEndPending &&
|
|
419
|
+
turn.generation === this.generation &&
|
|
420
|
+
this.activeTurn === turn,
|
|
421
|
+
);
|
|
423
422
|
this.dataFilter = new OmpPublicDataSerializer(outputRedactionValues);
|
|
424
423
|
this.nativeModelsByPublicId = nativeModelsByPublicId;
|
|
425
424
|
this.commandCatalog = commandCatalog;
|
|
@@ -433,6 +432,13 @@ export class OmpProviderSession {
|
|
|
433
432
|
capabilities.includes("timeline.plugin"),
|
|
434
433
|
hostTools.labels,
|
|
435
434
|
);
|
|
435
|
+
this.compaction = new OmpSessionCompaction(
|
|
436
|
+
id,
|
|
437
|
+
emit,
|
|
438
|
+
this.projector,
|
|
439
|
+
this.dataFilter,
|
|
440
|
+
this.usage,
|
|
441
|
+
);
|
|
436
442
|
this.subsessions = capabilities.includes("session.subsession")
|
|
437
443
|
? new OmpSubsessionProjector(
|
|
438
444
|
id,
|
|
@@ -774,289 +780,6 @@ export class OmpProviderSession {
|
|
|
774
780
|
});
|
|
775
781
|
}
|
|
776
782
|
|
|
777
|
-
private usageFrom(
|
|
778
|
-
state: OmpSessionState | undefined,
|
|
779
|
-
stats: OmpSessionStats | undefined,
|
|
780
|
-
): ProviderUsage | undefined {
|
|
781
|
-
if (!state?.contextUsage && !stats) return undefined;
|
|
782
|
-
const stateContext = state?.contextUsage;
|
|
783
|
-
const statsContext = stats?.contextUsage;
|
|
784
|
-
const modelCapacity = state?.model?.contextWindow;
|
|
785
|
-
const inputTokens = stats?.tokens?.input;
|
|
786
|
-
const cachedInputTokens = stats?.tokens?.cacheRead;
|
|
787
|
-
const outputTokens = stats?.tokens?.output;
|
|
788
|
-
const totalCostUsd = stats?.cost;
|
|
789
|
-
const contextTokens =
|
|
790
|
-
typeof stateContext?.tokens === "number"
|
|
791
|
-
? stateContext.tokens
|
|
792
|
-
: typeof statsContext?.tokens === "number"
|
|
793
|
-
? statsContext.tokens
|
|
794
|
-
: undefined;
|
|
795
|
-
const contextWindow =
|
|
796
|
-
typeof stateContext?.contextWindow === "number" && stateContext.contextWindow > 0
|
|
797
|
-
? stateContext.contextWindow
|
|
798
|
-
: typeof statsContext?.contextWindow === "number" && statsContext.contextWindow > 0
|
|
799
|
-
? statsContext.contextWindow
|
|
800
|
-
: typeof modelCapacity === "number" && modelCapacity > 0
|
|
801
|
-
? modelCapacity
|
|
802
|
-
: undefined;
|
|
803
|
-
const usage: ProviderUsage = {
|
|
804
|
-
...(typeof inputTokens === "number" ? { inputTokens } : {}),
|
|
805
|
-
...(typeof cachedInputTokens === "number" ? { cachedInputTokens } : {}),
|
|
806
|
-
...(typeof outputTokens === "number" ? { outputTokens } : {}),
|
|
807
|
-
...(typeof totalCostUsd === "number" ? { totalCostUsd } : {}),
|
|
808
|
-
...(contextTokens !== undefined ? { contextWindowUsedTokens: contextTokens } : {}),
|
|
809
|
-
...(contextWindow !== undefined ? { contextWindowMaxTokens: contextWindow } : {}),
|
|
810
|
-
};
|
|
811
|
-
return Object.keys(usage).length > 0 ? usage : undefined;
|
|
812
|
-
}
|
|
813
|
-
|
|
814
|
-
private ownsUsageSample(
|
|
815
|
-
turn: ActiveTurn,
|
|
816
|
-
generation: number,
|
|
817
|
-
runtime: OmpRuntimeSession,
|
|
818
|
-
): boolean {
|
|
819
|
-
return (
|
|
820
|
-
!this.closed &&
|
|
821
|
-
!this.runtimeDead &&
|
|
822
|
-
!turn.terminal &&
|
|
823
|
-
generation === this.generation &&
|
|
824
|
-
runtime === this.runtime &&
|
|
825
|
-
this.activeTurn === turn
|
|
826
|
-
);
|
|
827
|
-
}
|
|
828
|
-
|
|
829
|
-
private publishUsageSnapshot(
|
|
830
|
-
turn: ActiveTurn,
|
|
831
|
-
minimumEpoch = this.usageEpoch,
|
|
832
|
-
minimumSequence = turn.usageSampleFloor,
|
|
833
|
-
): Promise<OmpSessionState | undefined> {
|
|
834
|
-
const generation = turn.generation;
|
|
835
|
-
const runtime = this.runtime;
|
|
836
|
-
if (!this.ownsUsageSample(turn, generation, runtime)) return Promise.resolve(undefined);
|
|
837
|
-
const current = this.usageSample;
|
|
838
|
-
if (current) {
|
|
839
|
-
if (
|
|
840
|
-
current.turn === turn &&
|
|
841
|
-
current.generation === generation &&
|
|
842
|
-
current.runtime === runtime &&
|
|
843
|
-
current.epoch >= minimumEpoch &&
|
|
844
|
-
current.sequence >= minimumSequence
|
|
845
|
-
) {
|
|
846
|
-
return current.promise;
|
|
847
|
-
}
|
|
848
|
-
return current.promise.then(() => {
|
|
849
|
-
if (!this.ownsUsageSample(turn, generation, runtime)) return undefined;
|
|
850
|
-
return this.publishUsageSnapshot(turn, minimumEpoch, minimumSequence);
|
|
851
|
-
});
|
|
852
|
-
}
|
|
853
|
-
const epoch = this.usageEpoch;
|
|
854
|
-
const sequence = ++this.usageSequence;
|
|
855
|
-
const promise = Promise.allSettled([runtime.getState(), runtime.getSessionStats()]).then(
|
|
856
|
-
([stateResult, statsResult]) => {
|
|
857
|
-
if (
|
|
858
|
-
!this.ownsUsageSample(turn, generation, runtime) ||
|
|
859
|
-
epoch !== this.usageEpoch ||
|
|
860
|
-
epoch < minimumEpoch ||
|
|
861
|
-
sequence < turn.usageSampleFloor ||
|
|
862
|
-
sequence < minimumSequence
|
|
863
|
-
) {
|
|
864
|
-
return undefined;
|
|
865
|
-
}
|
|
866
|
-
const state = stateResult.status === "fulfilled" ? stateResult.value : undefined;
|
|
867
|
-
const stats = statsResult.status === "fulfilled" ? statsResult.value : undefined;
|
|
868
|
-
const usage = this.usageFrom(state, stats);
|
|
869
|
-
if (usage) {
|
|
870
|
-
this.lastUsage = usage;
|
|
871
|
-
this.emit({ type: "session.usage", sessionId: this.id, turnId: turn.turnId, usage });
|
|
872
|
-
}
|
|
873
|
-
return state;
|
|
874
|
-
},
|
|
875
|
-
);
|
|
876
|
-
this.usageSample = { turn, generation, runtime, epoch, sequence, promise };
|
|
877
|
-
void promise.finally(() => {
|
|
878
|
-
if (this.usageSample?.promise === promise) this.usageSample = null;
|
|
879
|
-
});
|
|
880
|
-
return promise;
|
|
881
|
-
}
|
|
882
|
-
|
|
883
|
-
private async boundedUsageSnapshot(
|
|
884
|
-
turn: ActiveTurn,
|
|
885
|
-
timeoutMs: number,
|
|
886
|
-
): Promise<OmpSessionState | undefined> {
|
|
887
|
-
const timeout = Promise.withResolvers<undefined>();
|
|
888
|
-
const timer = this.scheduler.set(() => timeout.resolve(undefined), timeoutMs);
|
|
889
|
-
try {
|
|
890
|
-
return await Promise.race([this.publishUsageSnapshot(turn), timeout.promise]);
|
|
891
|
-
} finally {
|
|
892
|
-
this.scheduler.clear(timer);
|
|
893
|
-
}
|
|
894
|
-
}
|
|
895
|
-
|
|
896
|
-
private async boundedTerminalState(
|
|
897
|
-
turn: ActiveTurn,
|
|
898
|
-
timeoutMs: number,
|
|
899
|
-
): Promise<OmpSessionState | undefined> {
|
|
900
|
-
const generation = turn.generation;
|
|
901
|
-
const runtime = this.runtime;
|
|
902
|
-
const timeout = Promise.withResolvers<undefined>();
|
|
903
|
-
const timer = this.scheduler.set(() => timeout.resolve(undefined), timeoutMs);
|
|
904
|
-
try {
|
|
905
|
-
const state = await Promise.race([runtime.getState(), timeout.promise]);
|
|
906
|
-
return this.ownsUsageSample(turn, generation, runtime) ? state : undefined;
|
|
907
|
-
} catch {
|
|
908
|
-
return undefined;
|
|
909
|
-
} finally {
|
|
910
|
-
this.scheduler.clear(timer);
|
|
911
|
-
}
|
|
912
|
-
}
|
|
913
|
-
|
|
914
|
-
private isActiveTurn(turn: ActiveTurn): boolean {
|
|
915
|
-
return (
|
|
916
|
-
!this.closed &&
|
|
917
|
-
!this.runtimeDead &&
|
|
918
|
-
!turn.terminal &&
|
|
919
|
-
!turn.terminalizing &&
|
|
920
|
-
!turn.agentEndPending &&
|
|
921
|
-
turn.generation === this.generation &&
|
|
922
|
-
this.activeTurn === turn
|
|
923
|
-
);
|
|
924
|
-
}
|
|
925
|
-
|
|
926
|
-
private scheduleUsagePoll(turn: ActiveTurn, delayMs = USAGE_POLL_MS): void {
|
|
927
|
-
if (!this.isActiveTurn(turn) || turn.usagePollTimer !== undefined) return;
|
|
928
|
-
turn.usagePollTimer = this.scheduler.set(() => {
|
|
929
|
-
turn.usagePollTimer = undefined;
|
|
930
|
-
this.pollUsage(turn);
|
|
931
|
-
}, delayMs);
|
|
932
|
-
}
|
|
933
|
-
|
|
934
|
-
private pollUsage(turn: ActiveTurn): void {
|
|
935
|
-
if (!this.isActiveTurn(turn) || turn.usagePoll) return;
|
|
936
|
-
const poll = this.publishUsageSnapshot(turn).then(() => undefined);
|
|
937
|
-
turn.usagePoll = poll;
|
|
938
|
-
void poll.finally(() => {
|
|
939
|
-
if (turn.usagePoll === poll) turn.usagePoll = undefined;
|
|
940
|
-
this.scheduleUsagePoll(turn);
|
|
941
|
-
});
|
|
942
|
-
}
|
|
943
|
-
|
|
944
|
-
private stopUsagePoll(turn: ActiveTurn): void {
|
|
945
|
-
if (turn.usagePollTimer === undefined) return;
|
|
946
|
-
this.scheduler.clear(turn.usagePollTimer);
|
|
947
|
-
turn.usagePollTimer = undefined;
|
|
948
|
-
}
|
|
949
|
-
|
|
950
|
-
private startCompaction(turn: ActiveTurn, trigger: "auto" | "manual", action?: string): void {
|
|
951
|
-
if (this.discardedCompactionEnds > 0) {
|
|
952
|
-
this.discardedCompactionEnds += 1;
|
|
953
|
-
return;
|
|
954
|
-
}
|
|
955
|
-
const active = this.activeCompaction;
|
|
956
|
-
if (active && active.trigger === trigger && active.action === action) {
|
|
957
|
-
active.retrying = false;
|
|
958
|
-
return;
|
|
959
|
-
}
|
|
960
|
-
if (active) {
|
|
961
|
-
this.retireCompaction("OMP emitted overlapping compactions");
|
|
962
|
-
this.discardedCompactionEnds = 2;
|
|
963
|
-
return;
|
|
964
|
-
}
|
|
965
|
-
const operation: ActiveCompaction = {
|
|
966
|
-
id: randomUUID(),
|
|
967
|
-
trigger,
|
|
968
|
-
turnId: turn.turnId,
|
|
969
|
-
generation: turn.generation,
|
|
970
|
-
action,
|
|
971
|
-
retrying: false,
|
|
972
|
-
preTokens: this.lastUsage?.contextWindowUsedTokens,
|
|
973
|
-
};
|
|
974
|
-
this.activeCompaction = operation;
|
|
975
|
-
this.projector.flush(true);
|
|
976
|
-
this.emit({
|
|
977
|
-
type: "timeline.item",
|
|
978
|
-
sessionId: this.id,
|
|
979
|
-
item: {
|
|
980
|
-
id: operation.id,
|
|
981
|
-
type: "compaction",
|
|
982
|
-
status: "loading",
|
|
983
|
-
trigger,
|
|
984
|
-
...(operation.preTokens !== undefined ? { preTokens: operation.preTokens } : {}),
|
|
985
|
-
},
|
|
986
|
-
});
|
|
987
|
-
}
|
|
988
|
-
|
|
989
|
-
private finishCompaction(
|
|
990
|
-
state: "completed" | "failed" | "canceled" | "skipped",
|
|
991
|
-
options: { tokensBefore?: number | null; message?: string } = {},
|
|
992
|
-
): void {
|
|
993
|
-
const operation = this.activeCompaction;
|
|
994
|
-
if (!operation) return;
|
|
995
|
-
this.activeCompaction = null;
|
|
996
|
-
this.projector.flush(true);
|
|
997
|
-
if (state === "completed") {
|
|
998
|
-
this.usageEpoch += 1;
|
|
999
|
-
const staleSample = this.usageSample;
|
|
1000
|
-
if (staleSample && staleSample.epoch < this.usageEpoch) {
|
|
1001
|
-
this.usageSample = null;
|
|
1002
|
-
staleSample.turn.usagePoll = undefined;
|
|
1003
|
-
}
|
|
1004
|
-
this.lastUsage = null;
|
|
1005
|
-
}
|
|
1006
|
-
if (state !== "completed") {
|
|
1007
|
-
const defaultMessage =
|
|
1008
|
-
state === "failed"
|
|
1009
|
-
? "OMP compaction failed"
|
|
1010
|
-
: state === "canceled"
|
|
1011
|
-
? "OMP compaction canceled"
|
|
1012
|
-
: "OMP compaction skipped";
|
|
1013
|
-
this.emit({
|
|
1014
|
-
type: "timeline.item",
|
|
1015
|
-
sessionId: this.id,
|
|
1016
|
-
item: {
|
|
1017
|
-
id: operation.id,
|
|
1018
|
-
type: "notification",
|
|
1019
|
-
level: state === "failed" ? "error" : "info",
|
|
1020
|
-
message: this.dataFilter.text(options.message ?? defaultMessage, 4_096),
|
|
1021
|
-
},
|
|
1022
|
-
});
|
|
1023
|
-
return;
|
|
1024
|
-
}
|
|
1025
|
-
const tokensBefore = options.tokensBefore ?? operation.preTokens;
|
|
1026
|
-
this.emit({
|
|
1027
|
-
type: "timeline.item",
|
|
1028
|
-
sessionId: this.id,
|
|
1029
|
-
item: {
|
|
1030
|
-
id: operation.id,
|
|
1031
|
-
type: "compaction",
|
|
1032
|
-
status: "completed",
|
|
1033
|
-
trigger: operation.trigger,
|
|
1034
|
-
...(tokensBefore !== undefined ? { preTokens: tokensBefore } : {}),
|
|
1035
|
-
},
|
|
1036
|
-
});
|
|
1037
|
-
}
|
|
1038
|
-
|
|
1039
|
-
private retireCompaction(message: string): void {
|
|
1040
|
-
const operation = this.activeCompaction;
|
|
1041
|
-
if (!operation) return;
|
|
1042
|
-
this.activeCompaction = null;
|
|
1043
|
-
this.emit({
|
|
1044
|
-
type: "timeline.item",
|
|
1045
|
-
sessionId: this.id,
|
|
1046
|
-
item: {
|
|
1047
|
-
id: operation.id,
|
|
1048
|
-
type: "compaction",
|
|
1049
|
-
status: "completed",
|
|
1050
|
-
trigger: operation.trigger,
|
|
1051
|
-
},
|
|
1052
|
-
});
|
|
1053
|
-
this.emit({
|
|
1054
|
-
type: "timeline.item",
|
|
1055
|
-
sessionId: this.id,
|
|
1056
|
-
item: { id: `${operation.id}:error`, type: "error", message },
|
|
1057
|
-
});
|
|
1058
|
-
}
|
|
1059
|
-
|
|
1060
783
|
private async replayHistory(preferPersistedTranscript = false): Promise<void> {
|
|
1061
784
|
if (!this.runtime.canReplayHistory) {
|
|
1062
785
|
throw new OmpPublicError("OMP session history cannot be replayed safely");
|
|
@@ -1449,8 +1172,8 @@ export class OmpProviderSession {
|
|
|
1449
1172
|
turn.starting = false;
|
|
1450
1173
|
turn.acknowledged = true;
|
|
1451
1174
|
this.startTurn(turn, false);
|
|
1452
|
-
this.
|
|
1453
|
-
this.
|
|
1175
|
+
this.compaction.start(turn, "manual");
|
|
1176
|
+
this.usage.poll(turn);
|
|
1454
1177
|
const bufferedEvents = turn.bufferedEvents.splice(0);
|
|
1455
1178
|
turn.replayingBufferedEvents = true;
|
|
1456
1179
|
for (const event of bufferedEvents) this.handleTurnEvent(turn, event);
|
|
@@ -1541,7 +1264,7 @@ export class OmpProviderSession {
|
|
|
1541
1264
|
else {
|
|
1542
1265
|
turn.steerReady.resolve();
|
|
1543
1266
|
turn.terminal = true;
|
|
1544
|
-
this.
|
|
1267
|
+
this.compaction.finish("failed", { message: "OMP prompt failed" });
|
|
1545
1268
|
this.resolveTurnPermissions(turn.turnId);
|
|
1546
1269
|
this.projector.finishTurn(turn.turnId);
|
|
1547
1270
|
if (this.activeTurn === turn) this.activeTurn = null;
|
|
@@ -1714,7 +1437,7 @@ export class OmpProviderSession {
|
|
|
1714
1437
|
const result = await compaction;
|
|
1715
1438
|
turn.manualCompactionPending = false;
|
|
1716
1439
|
if (this.closed || this.runtimeDead || turn.generation !== this.generation) return;
|
|
1717
|
-
this.
|
|
1440
|
+
this.compaction.finish("completed", { tokensBefore: result.tokensBefore });
|
|
1718
1441
|
await this.finishTurn(turn, turn.interrupted ? "canceled" : "completed");
|
|
1719
1442
|
} catch (error) {
|
|
1720
1443
|
if (this.closed || this.runtimeDead || turn.generation !== this.generation) return;
|
|
@@ -1722,7 +1445,7 @@ export class OmpProviderSession {
|
|
|
1722
1445
|
const raw = providerError(error, "OMP compaction failed");
|
|
1723
1446
|
const failure = { message: this.dataFilter.text(raw.message, 4_096) };
|
|
1724
1447
|
const state = turn.interrupted ? "canceled" : "failed";
|
|
1725
|
-
this.
|
|
1448
|
+
this.compaction.finish(state, { message: failure.message });
|
|
1726
1449
|
await this.finishTurn(turn, state, state === "failed" ? failure : undefined, true, true);
|
|
1727
1450
|
}
|
|
1728
1451
|
}
|
|
@@ -2299,11 +2022,11 @@ export class OmpProviderSession {
|
|
|
2299
2022
|
if (turn.started) await this.finishTurn(turn, "canceled", undefined, true, true);
|
|
2300
2023
|
else {
|
|
2301
2024
|
this.settleUnstartedTurn(turn);
|
|
2302
|
-
this.
|
|
2025
|
+
this.compaction.finish("canceled");
|
|
2303
2026
|
}
|
|
2304
2027
|
}
|
|
2305
2028
|
this.imageMaterializer.clear();
|
|
2306
|
-
this.
|
|
2029
|
+
this.compaction.finish("canceled");
|
|
2307
2030
|
this.resolveAllPermissions(true);
|
|
2308
2031
|
this.closed = true;
|
|
2309
2032
|
this.configRefreshAttempts = 0;
|
|
@@ -2463,7 +2186,7 @@ export class OmpProviderSession {
|
|
|
2463
2186
|
}
|
|
2464
2187
|
if (this.subsessions) await recovered.setSubagentSubscription("events");
|
|
2465
2188
|
this.generation += 1;
|
|
2466
|
-
this.
|
|
2189
|
+
this.usage.clearLatest();
|
|
2467
2190
|
this.runtimeDead = null;
|
|
2468
2191
|
this.runtimeDisposal = null;
|
|
2469
2192
|
unsubscribeBootstrap();
|
|
@@ -2810,43 +2533,13 @@ export class OmpProviderSession {
|
|
|
2810
2533
|
if (event.type === "auto_compaction_start") {
|
|
2811
2534
|
turn.nativeActivity = true;
|
|
2812
2535
|
this.cancelLocalOnlyCompletion(turn);
|
|
2813
|
-
this.
|
|
2536
|
+
this.compaction.start(turn, "auto", event.action);
|
|
2814
2537
|
return;
|
|
2815
2538
|
}
|
|
2816
2539
|
if (event.type === "auto_compaction_end") {
|
|
2817
|
-
if (this.
|
|
2818
|
-
this.
|
|
2819
|
-
return;
|
|
2820
|
-
}
|
|
2821
|
-
const operation = this.activeCompaction;
|
|
2822
|
-
if (
|
|
2823
|
-
operation?.trigger !== "auto" ||
|
|
2824
|
-
operation.turnId !== turn.turnId ||
|
|
2825
|
-
operation.generation !== turn.generation
|
|
2826
|
-
) {
|
|
2827
|
-
return;
|
|
2828
|
-
}
|
|
2829
|
-
if (event.action !== undefined && operation.action !== event.action) {
|
|
2830
|
-
this.retireCompaction("OMP emitted overlapping compactions");
|
|
2831
|
-
this.discardedCompactionEnds = 1;
|
|
2832
|
-
return;
|
|
2833
|
-
}
|
|
2834
|
-
if (event.willRetry) {
|
|
2835
|
-
operation.retrying = true;
|
|
2836
|
-
return;
|
|
2540
|
+
if (this.compaction.handleAutoEnd(turn, event)) {
|
|
2541
|
+
this.usage.schedulePoll(turn, USAGE_REFRESH_MS);
|
|
2837
2542
|
}
|
|
2838
|
-
const state = event.aborted
|
|
2839
|
-
? "canceled"
|
|
2840
|
-
: event.errorMessage
|
|
2841
|
-
? "failed"
|
|
2842
|
-
: event.skipped
|
|
2843
|
-
? "skipped"
|
|
2844
|
-
: "completed";
|
|
2845
|
-
this.finishCompaction(state, {
|
|
2846
|
-
tokensBefore: event.result?.tokensBefore ?? event.result?.preTokens,
|
|
2847
|
-
message: event.errorMessage,
|
|
2848
|
-
});
|
|
2849
|
-
this.scheduleUsagePoll(turn, USAGE_REFRESH_MS);
|
|
2850
2543
|
return;
|
|
2851
2544
|
}
|
|
2852
2545
|
if (event.type === "agent_end" && turn.manualCompactionPending) return;
|
|
@@ -3993,7 +3686,7 @@ export class OmpProviderSession {
|
|
|
3993
3686
|
await Promise.allSettled(turn.userLookups);
|
|
3994
3687
|
this.refreshAmbiguousTerminal(turn);
|
|
3995
3688
|
if (turn.terminal || this.activeTurn !== turn || turn.deferredAgentEnd !== candidate) return;
|
|
3996
|
-
const state = await this.boundedTerminalState(turn, FINAL_USAGE_WAIT_MS);
|
|
3689
|
+
const state = await this.usage.boundedTerminalState(turn, FINAL_USAGE_WAIT_MS);
|
|
3997
3690
|
if (turn.terminal || this.activeTurn !== turn || turn.deferredAgentEnd !== candidate) return;
|
|
3998
3691
|
if (!state) {
|
|
3999
3692
|
turn.operationalTerminalStage = "unresolved";
|
|
@@ -4023,10 +3716,7 @@ export class OmpProviderSession {
|
|
|
4023
3716
|
) {
|
|
4024
3717
|
return;
|
|
4025
3718
|
}
|
|
4026
|
-
|
|
4027
|
-
if (this.usageSample?.turn === turn && this.usageSample.sequence < turn.usageSampleFloor) {
|
|
4028
|
-
this.usageSample = null;
|
|
4029
|
-
}
|
|
3719
|
+
this.usage.invalidateBeforeNextSample(turn);
|
|
4030
3720
|
this.publishPendingUsers(turn);
|
|
4031
3721
|
await this.finishTurn(turn, "completed", undefined, false, false, true);
|
|
4032
3722
|
}
|
|
@@ -4055,7 +3745,7 @@ export class OmpProviderSession {
|
|
|
4055
3745
|
this.beginTerminalization(turn, replacement);
|
|
4056
3746
|
return;
|
|
4057
3747
|
}
|
|
4058
|
-
this.
|
|
3748
|
+
this.usage.poll(turn);
|
|
4059
3749
|
}
|
|
4060
3750
|
|
|
4061
3751
|
private beginTerminalization(turn: ActiveTurn, candidate: TerminalCandidate): void {
|
|
@@ -4069,11 +3759,8 @@ export class OmpProviderSession {
|
|
|
4069
3759
|
}
|
|
4070
3760
|
turn.terminalizing = true;
|
|
4071
3761
|
this.cancelAmbiguousTerminal(turn);
|
|
4072
|
-
|
|
4073
|
-
|
|
4074
|
-
this.usageSample = null;
|
|
4075
|
-
}
|
|
4076
|
-
this.stopUsagePoll(turn);
|
|
3762
|
+
this.usage.invalidateBeforeNextSample(turn);
|
|
3763
|
+
this.usage.stopPoll(turn);
|
|
4077
3764
|
turn.agentEndDeadlineTimer = this.scheduler.set(() => {
|
|
4078
3765
|
turn.agentEndDeadlineTimer = undefined;
|
|
4079
3766
|
if (!turn.agentEndPending || turn.terminal || this.activeTurn !== turn) return;
|
|
@@ -4165,14 +3852,14 @@ export class OmpProviderSession {
|
|
|
4165
3852
|
) {
|
|
4166
3853
|
this.resetAgentEndProbe(turn);
|
|
4167
3854
|
turn.deferredAgentEnd = candidate;
|
|
4168
|
-
this.
|
|
3855
|
+
this.usage.poll(turn);
|
|
4169
3856
|
return;
|
|
4170
3857
|
}
|
|
4171
3858
|
if (turn.interrupted) {
|
|
4172
3859
|
await this.completeAgentEnd(turn, candidate.event);
|
|
4173
3860
|
return;
|
|
4174
3861
|
}
|
|
4175
|
-
const state = await this.boundedTerminalState(turn, FINAL_USAGE_WAIT_MS);
|
|
3862
|
+
const state = await this.usage.boundedTerminalState(turn, FINAL_USAGE_WAIT_MS);
|
|
4176
3863
|
if (!turn.agentEndPending || turn.terminal || this.activeTurn !== turn) return;
|
|
4177
3864
|
if (!turn.interrupted && this.subsessions?.hasActiveChildren()) {
|
|
4178
3865
|
this.resetAgentEndProbe(turn);
|
|
@@ -4185,7 +3872,7 @@ export class OmpProviderSession {
|
|
|
4185
3872
|
) {
|
|
4186
3873
|
this.resetAgentEndProbe(turn);
|
|
4187
3874
|
turn.deferredAgentEnd = candidate;
|
|
4188
|
-
this.
|
|
3875
|
+
this.usage.poll(turn);
|
|
4189
3876
|
return;
|
|
4190
3877
|
}
|
|
4191
3878
|
if (state) {
|
|
@@ -4227,7 +3914,7 @@ export class OmpProviderSession {
|
|
|
4227
3914
|
!turn.interrupted &&
|
|
4228
3915
|
this.activeTurn === turn
|
|
4229
3916
|
) {
|
|
4230
|
-
const state = await this.boundedTerminalState(turn, FINAL_USAGE_WAIT_MS);
|
|
3917
|
+
const state = await this.usage.boundedTerminalState(turn, FINAL_USAGE_WAIT_MS);
|
|
4231
3918
|
if (
|
|
4232
3919
|
turn.terminal ||
|
|
4233
3920
|
this.activeTurn !== turn ||
|
|
@@ -4305,7 +3992,7 @@ export class OmpProviderSession {
|
|
|
4305
3992
|
turn.steerReady.resolve();
|
|
4306
3993
|
turn.starting = false;
|
|
4307
3994
|
turn.terminal = true;
|
|
4308
|
-
this.
|
|
3995
|
+
this.usage.stopPoll(turn);
|
|
4309
3996
|
this.cancelAmbiguousTerminal(turn);
|
|
4310
3997
|
this.resolveTurnPermissions(turn.turnId);
|
|
4311
3998
|
this.projector.finishTurn(turn.turnId);
|
|
@@ -4332,7 +4019,7 @@ export class OmpProviderSession {
|
|
|
4332
4019
|
turn.starting = false;
|
|
4333
4020
|
turn.steerReady.resolve();
|
|
4334
4021
|
this.emit({ type: "session.turn", sessionId: this.id, turnId: turn.turnId, state: "started" });
|
|
4335
|
-
if (pollUsage) this.
|
|
4022
|
+
if (pollUsage) this.usage.poll(turn);
|
|
4336
4023
|
}
|
|
4337
4024
|
private finishTurn(
|
|
4338
4025
|
turn: ActiveTurn,
|
|
@@ -4362,7 +4049,7 @@ export class OmpProviderSession {
|
|
|
4362
4049
|
turn.agentEndPending = false;
|
|
4363
4050
|
this.cancelLocalOnlyCompletion(turn);
|
|
4364
4051
|
this.cancelAmbiguousTerminal(turn);
|
|
4365
|
-
this.
|
|
4052
|
+
this.usage.stopPoll(turn);
|
|
4366
4053
|
if (turn.manualCompactionDeadlineTimer !== undefined) {
|
|
4367
4054
|
this.scheduler.clear(turn.manualCompactionDeadlineTimer);
|
|
4368
4055
|
turn.manualCompactionDeadlineTimer = undefined;
|
|
@@ -4381,7 +4068,7 @@ export class OmpProviderSession {
|
|
|
4381
4068
|
const terminalization = (async () => {
|
|
4382
4069
|
if (!turn.terminalOutcome?.usageSampled && !this.closed && !this.runtimeDead) {
|
|
4383
4070
|
await Promise.race([
|
|
4384
|
-
this.
|
|
4071
|
+
this.usage.boundedSnapshot(turn, FINAL_USAGE_WAIT_MS),
|
|
4385
4072
|
wake.promise.then(() => undefined),
|
|
4386
4073
|
]);
|
|
4387
4074
|
}
|
|
@@ -4408,11 +4095,11 @@ export class OmpProviderSession {
|
|
|
4408
4095
|
}
|
|
4409
4096
|
this.imageMaterializer.clear();
|
|
4410
4097
|
turn.terminal = true;
|
|
4411
|
-
|
|
4412
|
-
if (!preserveCompactions && this.
|
|
4413
|
-
if (outcome.state === "canceled") this.
|
|
4098
|
+
this.usage.clearSample(turn);
|
|
4099
|
+
if (!preserveCompactions && this.compaction.active) {
|
|
4100
|
+
if (outcome.state === "canceled") this.compaction.finish("canceled");
|
|
4414
4101
|
else {
|
|
4415
|
-
this.
|
|
4102
|
+
this.compaction.finish("failed", {
|
|
4416
4103
|
message: outcome.error?.message ?? "OMP compaction ended without a terminal result",
|
|
4417
4104
|
});
|
|
4418
4105
|
}
|
|
@@ -4449,15 +4136,14 @@ export class OmpProviderSession {
|
|
|
4449
4136
|
): void {
|
|
4450
4137
|
this.imageMaterializer.clear();
|
|
4451
4138
|
if (this.closed || this.runtimeDead) return;
|
|
4452
|
-
this.
|
|
4139
|
+
this.compaction.resetDiscardedEnds();
|
|
4453
4140
|
this.resolveAllPermissions();
|
|
4454
4141
|
this.recoveryUsesNativeConfig ||=
|
|
4455
4142
|
this.configRefreshInFlight !== null || this.configRefreshDirty || this.configMutationInFlight;
|
|
4456
4143
|
const turn = this.activeTurn;
|
|
4457
|
-
if (turn) this.
|
|
4458
|
-
this.
|
|
4459
|
-
this.
|
|
4460
|
-
this.lastUsage = null;
|
|
4144
|
+
if (turn) this.usage.stopPoll(turn);
|
|
4145
|
+
this.usage.reset();
|
|
4146
|
+
this.compaction.finish(compactionState, { message });
|
|
4461
4147
|
this.generation += 1;
|
|
4462
4148
|
this.projector.resetRuntimeGeneration("OMP runtime ended during compaction");
|
|
4463
4149
|
this.runtimeDead = message;
|