@omercnet/paseo-omp 0.4.0-next.122.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 +1 -1
- package/server/package-version.ts +1 -1
- package/server/provider/session-compaction.ts +187 -0
- package/server/provider/session-events.ts +42 -0
- package/server/provider/session-terminal.ts +307 -0
- package/server/provider/session-usage.ts +234 -0
- package/server/provider/session.ts +76 -721
|
@@ -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
|
+
}
|