@schlessera/brain-ui-server 0.18.0 → 0.20.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/README.md +5 -1
- package/data/model-prices.json +63 -0
- package/dist/activity/digest.d.ts +29 -0
- package/dist/activity/digest.d.ts.map +1 -0
- package/dist/activity/digest.js +89 -0
- package/dist/activity/digest.js.map +1 -0
- package/dist/activity/notify.d.ts +62 -0
- package/dist/activity/notify.d.ts.map +1 -0
- package/dist/activity/notify.js +193 -0
- package/dist/activity/notify.js.map +1 -0
- package/dist/activity/push-sender.d.ts +63 -0
- package/dist/activity/push-sender.d.ts.map +1 -0
- package/dist/activity/push-sender.js +131 -0
- package/dist/activity/push-sender.js.map +1 -0
- package/dist/activity/query.d.ts +16 -0
- package/dist/activity/query.d.ts.map +1 -0
- package/dist/activity/query.js +155 -0
- package/dist/activity/query.js.map +1 -0
- package/dist/activity/recorder.d.ts +53 -0
- package/dist/activity/recorder.d.ts.map +1 -0
- package/dist/activity/recorder.js +238 -0
- package/dist/activity/recorder.js.map +1 -0
- package/dist/activity/runtime.d.ts +37 -0
- package/dist/activity/runtime.d.ts.map +1 -0
- package/dist/activity/runtime.js +127 -0
- package/dist/activity/runtime.js.map +1 -0
- package/dist/activity/span-sink.d.ts +22 -0
- package/dist/activity/span-sink.d.ts.map +1 -0
- package/dist/activity/span-sink.js +138 -0
- package/dist/activity/span-sink.js.map +1 -0
- package/dist/activity/store.d.ts +256 -0
- package/dist/activity/store.d.ts.map +1 -0
- package/dist/activity/store.js +657 -0
- package/dist/activity/store.js.map +1 -0
- package/dist/activity/stream.d.ts +49 -0
- package/dist/activity/stream.d.ts.map +1 -0
- package/dist/activity/stream.js +353 -0
- package/dist/activity/stream.js.map +1 -0
- package/dist/agent/backend.d.ts +13 -4
- package/dist/agent/backend.d.ts.map +1 -1
- package/dist/agent/backend.js +68 -5
- package/dist/agent/backend.js.map +1 -1
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js +33 -2
- package/dist/app.js.map +1 -1
- package/dist/config/env.d.ts +36 -0
- package/dist/config/env.d.ts.map +1 -1
- package/dist/config/env.js +66 -2
- package/dist/config/env.js.map +1 -1
- package/dist/cron/scheduler.d.ts.map +1 -1
- package/dist/cron/scheduler.js +20 -5
- package/dist/cron/scheduler.js.map +1 -1
- package/dist/db/client.d.ts.map +1 -1
- package/dist/db/client.js +32 -6
- package/dist/db/client.js.map +1 -1
- package/dist/db/settings.d.ts +15 -0
- package/dist/db/settings.d.ts.map +1 -1
- package/dist/db/settings.js +43 -0
- package/dist/db/settings.js.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -1
- package/dist/pricing/model-pricing.d.ts +61 -0
- package/dist/pricing/model-pricing.d.ts.map +1 -0
- package/dist/pricing/model-pricing.js +379 -0
- package/dist/pricing/model-pricing.js.map +1 -0
- package/dist/routes/activity.d.ts +20 -0
- package/dist/routes/activity.d.ts.map +1 -0
- package/dist/routes/activity.js +308 -0
- package/dist/routes/activity.js.map +1 -0
- package/dist/routes/models.d.ts +6 -0
- package/dist/routes/models.d.ts.map +1 -1
- package/dist/routes/models.js +38 -3
- package/dist/routes/models.js.map +1 -1
- package/dist/routes/push.d.ts +6 -0
- package/dist/routes/push.d.ts.map +1 -0
- package/dist/routes/push.js +73 -0
- package/dist/routes/push.js.map +1 -0
- package/dist/routes/sessions.d.ts.map +1 -1
- package/dist/routes/sessions.js +20 -4
- package/dist/routes/sessions.js.map +1 -1
- package/dist/ws/bridge.d.ts +2 -1
- package/dist/ws/bridge.d.ts.map +1 -1
- package/dist/ws/bridge.js +27 -2
- package/dist/ws/bridge.js.map +1 -1
- package/dist/ws/connection.d.ts.map +1 -1
- package/dist/ws/connection.js +9 -1
- package/dist/ws/connection.js.map +1 -1
- package/dist/ws/dispatch.d.ts.map +1 -1
- package/dist/ws/dispatch.js +11 -0
- package/dist/ws/dispatch.js.map +1 -1
- package/dist/ws/host.d.ts +16 -0
- package/dist/ws/host.d.ts.map +1 -1
- package/dist/ws/host.js +2 -0
- package/dist/ws/host.js.map +1 -1
- package/dist/ws/run-session.d.ts.map +1 -1
- package/dist/ws/run-session.js +55 -1
- package/dist/ws/run-session.js.map +1 -1
- package/migrations/007_activity.sql +114 -0
- package/migrations/008_push_subscriptions.sql +24 -0
- package/migrations/009_activity_followups.sql +22 -0
- package/migrations/010_effective_cost.sql +21 -0
- package/package.json +6 -3
- package/src/activity/digest.ts +120 -0
- package/src/activity/notify.ts +307 -0
- package/src/activity/push-sender.ts +215 -0
- package/src/activity/query.ts +187 -0
- package/src/activity/recorder.ts +313 -0
- package/src/activity/runtime.ts +169 -0
- package/src/activity/span-sink.ts +152 -0
- package/src/activity/store.ts +1069 -0
- package/src/activity/stream.ts +396 -0
- package/src/agent/backend.ts +91 -10
- package/src/app.ts +35 -2
- package/src/config/env.ts +90 -2
- package/src/cron/scheduler.ts +28 -8
- package/src/db/client.ts +31 -6
- package/src/db/settings.ts +56 -0
- package/src/index.ts +23 -0
- package/src/pricing/model-pricing.ts +497 -0
- package/src/routes/activity.ts +371 -0
- package/src/routes/models.ts +57 -6
- package/src/routes/push.ts +90 -0
- package/src/routes/sessions.ts +24 -4
- package/src/ws/bridge.ts +29 -2
- package/src/ws/connection.ts +9 -1
- package/src/ws/dispatch.ts +13 -0
- package/src/ws/host.ts +18 -0
- package/src/ws/run-session.ts +71 -2
package/src/ws/dispatch.ts
CHANGED
|
@@ -177,6 +177,19 @@ export async function handleClientMessage(
|
|
|
177
177
|
break;
|
|
178
178
|
}
|
|
179
179
|
|
|
180
|
+
case "activity_subscribe": {
|
|
181
|
+
// View-scoped opt-in: without a subscription this connection never
|
|
182
|
+
// receives an activity frame. No turn correlation — subscriptions are
|
|
183
|
+
// connection state, not turn state.
|
|
184
|
+
host.activity?.stream.handleSubscribe(ws, msg);
|
|
185
|
+
break;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
case "activity_unsubscribe": {
|
|
189
|
+
host.activity?.stream.handleUnsubscribe(ws, msg);
|
|
190
|
+
break;
|
|
191
|
+
}
|
|
192
|
+
|
|
180
193
|
case "session_resume": {
|
|
181
194
|
host.sendMessage(ws, {
|
|
182
195
|
type: "session_info",
|
package/src/ws/host.ts
CHANGED
|
@@ -5,6 +5,16 @@ import type { SessionCatalog } from "./session-catalog.js";
|
|
|
5
5
|
import type { BackendRegistry } from "../agent/backend.js";
|
|
6
6
|
import { createSilentObservability, type Observability } from "../observability/index.js";
|
|
7
7
|
import { FrameRateLimiter } from "./rate-limit.js";
|
|
8
|
+
import type { ActivityStore } from "../activity/store.js";
|
|
9
|
+
import type { ActivityStream } from "../activity/stream.js";
|
|
10
|
+
|
|
11
|
+
/** The activity record and its live stream, when the host records activity. */
|
|
12
|
+
export interface ActivityRuntime {
|
|
13
|
+
store: ActivityStore;
|
|
14
|
+
stream: ActivityStream;
|
|
15
|
+
/** Read seam for the agent-facing query tool (bridge.queryActivity). */
|
|
16
|
+
query?: (query: import("@schlessera/brain-ui-sdk/server").ActivityQuery) => Record<string, unknown>;
|
|
17
|
+
}
|
|
8
18
|
|
|
9
19
|
/** Host-side turn timeout. The backend no longer times out — the host owns it. */
|
|
10
20
|
const DEFAULT_TIMEOUT_MS = 10 * 60 * 1000; // 10 minutes
|
|
@@ -65,6 +75,12 @@ export interface WsHostOptions {
|
|
|
65
75
|
* fronting the socket with its own limiter wants.
|
|
66
76
|
*/
|
|
67
77
|
wsRate?: { ratePerSecond: number; burst: number };
|
|
78
|
+
/**
|
|
79
|
+
* Activity recording (span store + live stream). Optional: a host without
|
|
80
|
+
* one records nothing and never sends activity frames — which is also what
|
|
81
|
+
* most existing tests want.
|
|
82
|
+
*/
|
|
83
|
+
activity?: ActivityRuntime;
|
|
68
84
|
}
|
|
69
85
|
|
|
70
86
|
/** Identity of one turn, as it appears on a log record. */
|
|
@@ -102,6 +118,7 @@ export class WsHost {
|
|
|
102
118
|
maxConcurrentSessions: () => number;
|
|
103
119
|
readonly observability: Observability;
|
|
104
120
|
readonly wsRate: { ratePerSecond: number; burst: number } | null;
|
|
121
|
+
readonly activity: ActivityRuntime | null;
|
|
105
122
|
/** Scoped instruments, resolved once — `[ws]` is the existing log prefix. */
|
|
106
123
|
readonly log: ReturnType<Observability["logger"]>;
|
|
107
124
|
private readonly framesDropped: ReturnType<
|
|
@@ -130,6 +147,7 @@ export class WsHost {
|
|
|
130
147
|
this.observability = options.observability ?? createSilentObservability();
|
|
131
148
|
this.wsRate =
|
|
132
149
|
options.wsRate && options.wsRate.ratePerSecond > 0 ? options.wsRate : null;
|
|
150
|
+
this.activity = options.activity ?? null;
|
|
133
151
|
this.log = this.observability.logger("ws");
|
|
134
152
|
const meter = this.observability.meter("ws");
|
|
135
153
|
this.framesDropped = meter.createCounter("ws.frames.dropped", {
|
package/src/ws/run-session.ts
CHANGED
|
@@ -1,7 +1,13 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
BillingMode,
|
|
3
|
+
ChatImageAttachment,
|
|
4
|
+
ClientEnvironment,
|
|
5
|
+
} from "@schlessera/brain-ui-sdk/protocol";
|
|
6
|
+
import type { BackendRegistry } from "../agent/backend.js";
|
|
2
7
|
import type { WSContext } from "./clients.js";
|
|
3
8
|
import { withSessionId, withTurnScope } from "./frames.js";
|
|
4
9
|
import { makeBridge, emitTurnError } from "./bridge.js";
|
|
10
|
+
import { createTurnRecorder, type TurnRecorder } from "../activity/recorder.js";
|
|
5
11
|
import { resolveTurnTarget } from "./routing.js";
|
|
6
12
|
import type { QueuedFollowUp, RunningTurn } from "./turns.js";
|
|
7
13
|
import { queuedBytes, queuedFollowUpBytes } from "./turns.js";
|
|
@@ -114,7 +120,26 @@ export async function runSession(
|
|
|
114
120
|
}, host.turnTimeoutMs);
|
|
115
121
|
turn.timeoutHandle = timeoutHandle;
|
|
116
122
|
|
|
117
|
-
|
|
123
|
+
// One recorder per turn identity: a queued follow-up re-mints turnId
|
|
124
|
+
// and gets its own run in the activity record. The recorder is handed
|
|
125
|
+
// the RESOLVED profile — `profileId` here is post pin-drop fallback and
|
|
126
|
+
// carries any session_info re-pin forward between queued turns — plus
|
|
127
|
+
// its billing mode, so the run's root span classifies what actually ran,
|
|
128
|
+
// never what was requested.
|
|
129
|
+
const billing = host.activity
|
|
130
|
+
? await resolveRunBilling(host.registry, backend.id, profileId)
|
|
131
|
+
: undefined;
|
|
132
|
+
const recorder: TurnRecorder | undefined = host.activity
|
|
133
|
+
? createTurnRecorder(
|
|
134
|
+
{
|
|
135
|
+
store: host.activity.store,
|
|
136
|
+
onWrite: () => host.activity!.stream.pump(),
|
|
137
|
+
log: host.log,
|
|
138
|
+
},
|
|
139
|
+
{ turnId: turn.turnId, sessionId: turn.sessionId, ...billing }
|
|
140
|
+
)
|
|
141
|
+
: undefined;
|
|
142
|
+
const bridge = makeBridge(host, turn, text, backend.id, recorder);
|
|
118
143
|
const startedAt = Date.now();
|
|
119
144
|
host.reportTurnStarted(turn);
|
|
120
145
|
try {
|
|
@@ -139,8 +164,22 @@ export async function runSession(
|
|
|
139
164
|
} else {
|
|
140
165
|
host.reportTurnCompleted(turn, Date.now() - startedAt);
|
|
141
166
|
}
|
|
167
|
+
// Terminal precedence: cancellation and timeout are host-owned facts;
|
|
168
|
+
// otherwise the recorder refines from the buffered result frame.
|
|
169
|
+
recorder?.finish(
|
|
170
|
+
turn.cancelled
|
|
171
|
+
? "cancelled"
|
|
172
|
+
: abortController.signal.aborted
|
|
173
|
+
? "timeout"
|
|
174
|
+
: turn.lastResult === "error"
|
|
175
|
+
? "error"
|
|
176
|
+
: "success"
|
|
177
|
+
);
|
|
142
178
|
} catch (err) {
|
|
143
179
|
emitTurnError(host, turn, err);
|
|
180
|
+
recorder?.finish(
|
|
181
|
+
turn.cancelled ? "cancelled" : abortController.signal.aborted ? "timeout" : "error"
|
|
182
|
+
);
|
|
144
183
|
} finally {
|
|
145
184
|
clearTimeout(timeoutHandle);
|
|
146
185
|
}
|
|
@@ -167,6 +206,36 @@ export async function runSession(
|
|
|
167
206
|
}
|
|
168
207
|
}
|
|
169
208
|
|
|
209
|
+
/**
|
|
210
|
+
* Profile identity + billing mode for a turn's root span. When the slot has no
|
|
211
|
+
* resolved profile id (a resumed session whose pin was dropped), the backend
|
|
212
|
+
* runs its default profile — the backend's first roster entry — so THAT
|
|
213
|
+
* profile's identity and billing are recorded, not the dead pin's. Hidden
|
|
214
|
+
* profiles are included: a session pinned to a profile the user later hid
|
|
215
|
+
* still runs on it and must classify as it. Never throws: an unreadable
|
|
216
|
+
* roster records no billing attrs, and the rollup falls back to env
|
|
217
|
+
* classification.
|
|
218
|
+
*/
|
|
219
|
+
async function resolveRunBilling(
|
|
220
|
+
registry: BackendRegistry,
|
|
221
|
+
backendId: string,
|
|
222
|
+
profileId: string | undefined
|
|
223
|
+
): Promise<{ profileId: string; billingMode?: BillingMode } | undefined> {
|
|
224
|
+
try {
|
|
225
|
+
const providers = await registry.listAllProviders({ includeHidden: true });
|
|
226
|
+
const resolved = profileId
|
|
227
|
+
? providers.find((provider) => provider.id === profileId)
|
|
228
|
+
: providers.find((provider) => provider.backendId === backendId);
|
|
229
|
+
if (!resolved) return undefined;
|
|
230
|
+
return {
|
|
231
|
+
profileId: resolved.id,
|
|
232
|
+
...(resolved.billingMode ? { billingMode: resolved.billingMode } : {}),
|
|
233
|
+
};
|
|
234
|
+
} catch {
|
|
235
|
+
return undefined;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
170
239
|
/** Queue sizes are only ever reported to a human, so one decimal of MB is plenty. */
|
|
171
240
|
function formatMb(bytes: number): string {
|
|
172
241
|
return `${(bytes / 1024 / 1024).toFixed(1)} MB`;
|