@pellux/goodvibes-daemon-sdk 0.18.3 → 0.30.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 +13 -7
- package/dist/api-router.d.ts +8 -1
- package/dist/api-router.d.ts.map +1 -1
- package/dist/api-router.js +12 -3
- package/dist/artifact-upload.d.ts +26 -0
- package/dist/artifact-upload.d.ts.map +1 -0
- package/dist/artifact-upload.js +535 -0
- package/dist/automation.d.ts +2 -2
- package/dist/automation.d.ts.map +1 -1
- package/dist/channel-route-types.d.ts.map +1 -1
- package/dist/channel-routes.d.ts +2 -2
- package/dist/channel-routes.d.ts.map +1 -1
- package/dist/channel-routes.js +28 -3
- package/dist/context.d.ts +151 -51
- package/dist/context.d.ts.map +1 -1
- package/dist/control-routes.d.ts +2 -2
- package/dist/control-routes.d.ts.map +1 -1
- package/dist/error-response.d.ts.map +1 -1
- package/dist/error-response.js +172 -12
- package/dist/http-policy.d.ts +1 -1
- package/dist/http-policy.d.ts.map +1 -1
- package/dist/http-policy.js +0 -1
- package/dist/index.d.ts +8 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/integration-route-types.d.ts +3 -1
- package/dist/integration-route-types.d.ts.map +1 -1
- package/dist/integration-routes.d.ts +2 -2
- package/dist/integration-routes.d.ts.map +1 -1
- package/dist/integration-routes.js +3 -0
- package/dist/knowledge-refinement-routes.d.ts +4 -0
- package/dist/knowledge-refinement-routes.d.ts.map +1 -0
- package/dist/knowledge-refinement-routes.js +58 -0
- package/dist/knowledge-route-types.d.ts +11 -1
- package/dist/knowledge-route-types.d.ts.map +1 -1
- package/dist/knowledge-routes.d.ts +2 -2
- package/dist/knowledge-routes.d.ts.map +1 -1
- package/dist/knowledge-routes.js +156 -13
- package/dist/media-route-types.d.ts +10 -0
- package/dist/media-route-types.d.ts.map +1 -1
- package/dist/media-routes.d.ts +2 -2
- package/dist/media-routes.d.ts.map +1 -1
- package/dist/media-routes.js +134 -15
- package/dist/operator.d.ts +2 -2
- package/dist/operator.d.ts.map +1 -1
- package/dist/operator.js +49 -15
- package/dist/otlp-protobuf.d.ts +3 -0
- package/dist/otlp-protobuf.d.ts.map +1 -0
- package/dist/otlp-protobuf.js +977 -0
- package/dist/remote-routes.d.ts +2 -2
- package/dist/remote-routes.d.ts.map +1 -1
- package/dist/remote-routes.js +146 -13
- package/dist/remote.d.ts +2 -2
- package/dist/remote.d.ts.map +1 -1
- package/dist/route-helpers.d.ts +8 -0
- package/dist/route-helpers.d.ts.map +1 -1
- package/dist/route-helpers.js +24 -0
- package/dist/runtime-automation-routes.d.ts +2 -2
- package/dist/runtime-automation-routes.d.ts.map +1 -1
- package/dist/runtime-automation-routes.js +4 -1
- package/dist/runtime-route-types.d.ts +46 -3
- package/dist/runtime-route-types.d.ts.map +1 -1
- package/dist/runtime-routes.d.ts +2 -2
- package/dist/runtime-routes.d.ts.map +1 -1
- package/dist/runtime-routes.js +1 -0
- package/dist/runtime-session-routes.d.ts +13 -3
- package/dist/runtime-session-routes.d.ts.map +1 -1
- package/dist/runtime-session-routes.js +102 -10
- package/dist/sessions.d.ts +2 -2
- package/dist/sessions.d.ts.map +1 -1
- package/dist/sessions.js +3 -0
- package/dist/system-route-types.d.ts +19 -0
- package/dist/system-route-types.d.ts.map +1 -1
- package/dist/system-routes.d.ts +2 -2
- package/dist/system-routes.d.ts.map +1 -1
- package/dist/system-routes.js +18 -0
- package/dist/tasks.d.ts +2 -2
- package/dist/tasks.d.ts.map +1 -1
- package/dist/telemetry-routes.d.ts +25 -2
- package/dist/telemetry-routes.d.ts.map +1 -1
- package/dist/telemetry-routes.js +131 -15
- package/package.json +128 -5
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { readBoundedPositiveInteger } from './route-helpers.js';
|
|
3
|
+
const DEFAULT_LIST_LIMIT = 100;
|
|
4
|
+
const MAX_LIST_LIMIT = 500;
|
|
5
|
+
function readBoundedLimit(url, key = 'limit') {
|
|
6
|
+
return readBoundedPositiveInteger(url.searchParams.get(key), DEFAULT_LIST_LIMIT, MAX_LIST_LIMIT);
|
|
7
|
+
}
|
|
1
8
|
export function createDaemonRuntimeSessionRouteHandlers(context) {
|
|
2
9
|
return {
|
|
3
10
|
createSharedSession: async (request) => handleCreateSharedSession(context, request),
|
|
@@ -14,6 +21,7 @@ export function createDaemonRuntimeSessionRouteHandlers(context) {
|
|
|
14
21
|
getRuntimeTask: (taskId) => handleGetRuntimeTask(context, taskId),
|
|
15
22
|
runtimeTaskAction: (taskId, action, request) => handleRuntimeTaskAction(context, taskId, action, request),
|
|
16
23
|
getTaskStatus: (agentId) => handleGetTaskStatus(context, agentId),
|
|
24
|
+
getSharedSessionEvents: (sessionId, request) => handleGetSharedSessionEvents(context, sessionId, request),
|
|
17
25
|
};
|
|
18
26
|
}
|
|
19
27
|
async function handleCreateSharedSession(context, req) {
|
|
@@ -171,7 +179,7 @@ async function handleGetSharedSessionMessages(context, sessionId, url) {
|
|
|
171
179
|
if (!session) {
|
|
172
180
|
return Response.json({ error: 'Unknown shared session' }, { status: 404 });
|
|
173
181
|
}
|
|
174
|
-
const limit =
|
|
182
|
+
const limit = readBoundedLimit(url);
|
|
175
183
|
return Response.json({
|
|
176
184
|
session,
|
|
177
185
|
messages: context.sessionBroker.getMessages(sessionId, limit),
|
|
@@ -183,25 +191,100 @@ async function handleGetSharedSessionInputs(context, sessionId, url) {
|
|
|
183
191
|
if (!session) {
|
|
184
192
|
return Response.json({ error: 'Unknown shared session' }, { status: 404 });
|
|
185
193
|
}
|
|
186
|
-
const limit =
|
|
194
|
+
const limit = readBoundedLimit(url);
|
|
187
195
|
return Response.json({
|
|
188
196
|
session,
|
|
189
197
|
inputs: context.sessionBroker.getInputs(sessionId, limit),
|
|
190
198
|
});
|
|
191
199
|
}
|
|
200
|
+
/**
|
|
201
|
+
* Handle POST /api/sessions/:sessionId/messages.
|
|
202
|
+
*
|
|
203
|
+
* Accepts `{body}` in the request payload and returns 400 when it is absent or empty.
|
|
204
|
+
*/
|
|
192
205
|
async function handlePostSharedSessionMessage(context, sessionId, req) {
|
|
193
206
|
const body = await context.parseJsonBody(req);
|
|
194
207
|
if (body instanceof Response)
|
|
195
208
|
return body;
|
|
209
|
+
// Validate kind field. Ordinary session messages default to conversation
|
|
210
|
+
// routing; callers must explicitly opt into kind='task' for agent/WRFC work.
|
|
211
|
+
const kind = body.kind === undefined ? 'message' : body.kind;
|
|
212
|
+
if (kind !== 'task' && kind !== 'message' && kind !== 'followup') {
|
|
213
|
+
return Response.json({ error: `Invalid kind '${String(kind)}'. Accepted values: 'task' | 'message' | 'followup'`, code: 'INVALID_KIND' }, { status: 400 });
|
|
214
|
+
}
|
|
196
215
|
const message = readSharedSessionMessageBody(body);
|
|
197
216
|
if (!message) {
|
|
198
217
|
return Response.json({ error: 'Missing shared session message body' }, { status: 400 });
|
|
199
218
|
}
|
|
219
|
+
// kind='followup' — always queues/spawns a follow-up turn via followUpMessage()
|
|
220
|
+
if (kind === 'followup') {
|
|
221
|
+
const followUpSubmission = await context.sessionBroker.followUpMessage(buildSharedSessionMessageInput(sessionId, body, message));
|
|
222
|
+
return await respondToSessionSubmission(context, req, followUpSubmission, message, `/api/sessions/${sessionId}/messages`, 'DaemonServer.handlePostSharedSessionMessage.followup', {
|
|
223
|
+
context: `shared-session:${followUpSubmission.session.id}`,
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
// kind='message' — companion main-chat send. Persist + publish COMPANION_MESSAGE_RECEIVED
|
|
227
|
+
// on the runtime bus; TUI's bootstrap-core subscriber delegates to
|
|
228
|
+
// orchestrator.handleUserInput() which fires a real LLM turn (same entry as TUI input
|
|
229
|
+
// box). Turn events stream to both TUI and companion over SSE automatically.
|
|
230
|
+
// Deliberately short-circuits BEFORE sessionBroker.submitMessage() to avoid the
|
|
231
|
+
// buildContinuationTask WRFC engineer-chain spawn path.
|
|
232
|
+
if (kind === 'message') {
|
|
233
|
+
const session = context.sessionBroker.getSession(sessionId);
|
|
234
|
+
if (!session) {
|
|
235
|
+
return Response.json({ error: 'Unknown shared session', code: 'SESSION_NOT_FOUND' }, { status: 404 });
|
|
236
|
+
}
|
|
237
|
+
if (session.status === 'closed') {
|
|
238
|
+
return Response.json({ error: 'Session is closed', code: 'SESSION_CLOSED' }, { status: 409 });
|
|
239
|
+
}
|
|
240
|
+
const messageId = randomUUID();
|
|
241
|
+
const timestamp = Date.now();
|
|
242
|
+
// Persist the companion message to the session log so GET /api/sessions/:id/messages
|
|
243
|
+
// returns it and the TUI can render it.
|
|
244
|
+
await context.sessionBroker.appendCompanionMessage(sessionId, {
|
|
245
|
+
messageId,
|
|
246
|
+
body: message,
|
|
247
|
+
source: 'companion-followup',
|
|
248
|
+
timestamp,
|
|
249
|
+
});
|
|
250
|
+
// Also notify the TUI's in-process subscriber via the conversation follow-up event.
|
|
251
|
+
context.publishConversationFollowup(sessionId, {
|
|
252
|
+
messageId,
|
|
253
|
+
body: message,
|
|
254
|
+
source: 'companion-followup',
|
|
255
|
+
timestamp,
|
|
256
|
+
});
|
|
257
|
+
// Return immediately. appendCompanionMessage persists the message and
|
|
258
|
+
// publishConversationFollowup emits COMPANION_MESSAGE_RECEIVED on the runtime bus.
|
|
259
|
+
// The TUI's bootstrap-core.ts COMPANION_MESSAGE_RECEIVED subscriber delegates to
|
|
260
|
+
// orchestrator.handleUserInput(), which adds the user message to the conversation
|
|
261
|
+
// view and fires a real LLM turn whose events stream to both TUI and companion SSE.
|
|
262
|
+
// The { routedTo: 'conversation' } shape signals the companion app that the message
|
|
263
|
+
// was received and persisted (isConversationRouteResult check).
|
|
264
|
+
return context.recordApiResponse(req, `/api/sessions/${sessionId}/messages`, Response.json({
|
|
265
|
+
messageId,
|
|
266
|
+
routedTo: 'conversation',
|
|
267
|
+
sessionId,
|
|
268
|
+
}, { status: 202 }));
|
|
269
|
+
}
|
|
200
270
|
const submission = await context.sessionBroker.submitMessage(buildSharedSessionMessageInput(sessionId, body, message));
|
|
201
271
|
return await respondToSessionSubmission(context, req, submission, message, `/api/sessions/${sessionId}/messages`, 'DaemonServer.handlePostSharedSessionMessage', {
|
|
202
272
|
context: `shared-session:${submission.session.id}`,
|
|
203
273
|
});
|
|
204
274
|
}
|
|
275
|
+
/**
|
|
276
|
+
* Handle GET /api/sessions/:id/events — creates a session-scoped SSE stream
|
|
277
|
+
* for the companion app to receive turn events (STREAM_DELTA, TURN_COMPLETED,
|
|
278
|
+
* etc.) and agent events from the shared session.
|
|
279
|
+
*/
|
|
280
|
+
async function handleGetSharedSessionEvents(context, sessionId, req) {
|
|
281
|
+
await context.sessionBroker.start();
|
|
282
|
+
const session = context.sessionBroker.getSession(sessionId);
|
|
283
|
+
if (!session) {
|
|
284
|
+
return Response.json({ error: 'Unknown shared session', code: 'SESSION_NOT_FOUND' }, { status: 404 });
|
|
285
|
+
}
|
|
286
|
+
return context.openSessionEventStream(req, sessionId);
|
|
287
|
+
}
|
|
205
288
|
async function handlePostSharedSessionSteer(context, sessionId, req) {
|
|
206
289
|
const body = await context.parseJsonBody(req);
|
|
207
290
|
if (body instanceof Response)
|
|
@@ -237,6 +320,12 @@ async function handleCancelSharedSessionInput(context, sessionId, inputId) {
|
|
|
237
320
|
if (!input) {
|
|
238
321
|
return Response.json({ error: 'Unknown shared session input' }, { status: 404 });
|
|
239
322
|
}
|
|
323
|
+
// F17: cancelInput returns the entry unchanged when state is not 'queued'.
|
|
324
|
+
// Return 409 so callers know the cancel was a no-op (e.g. already spawned).
|
|
325
|
+
const inputRecord = input;
|
|
326
|
+
if (inputRecord.state !== 'queued' && inputRecord.state !== 'cancelled') {
|
|
327
|
+
return Response.json({ error: `Cannot cancel input in state '${inputRecord.state}'`, code: 'CANCEL_NOT_ALLOWED', input }, { status: 409 });
|
|
328
|
+
}
|
|
240
329
|
return Response.json({ input });
|
|
241
330
|
}
|
|
242
331
|
function handleGetRuntimeTask(context, taskId) {
|
|
@@ -246,14 +335,17 @@ function handleGetRuntimeTask(context, taskId) {
|
|
|
246
335
|
}
|
|
247
336
|
return Response.json({ task });
|
|
248
337
|
}
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
338
|
+
/**
|
|
339
|
+
* Extract the message body from an incoming POST body.
|
|
340
|
+
*
|
|
341
|
+
* F13 normalization: accepts the canonical 'body' field.
|
|
342
|
+
* The caller must check for an empty result and return 400.
|
|
343
|
+
*
|
|
344
|
+
* @param body - Parsed JSON body from the request.
|
|
345
|
+
* @returns Trimmed message string, or '' if none of the accepted fields are present.
|
|
346
|
+
*/
|
|
347
|
+
export function readSharedSessionMessageBody(body) {
|
|
348
|
+
return typeof body.body === 'string' ? body.body.trim() : '';
|
|
257
349
|
}
|
|
258
350
|
function buildSharedSessionMessageInput(sessionId, body, message) {
|
|
259
351
|
return {
|
package/dist/sessions.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
export declare function dispatchSessionRoutes(req: Request, handlers:
|
|
1
|
+
import type { DaemonSessionRouteHandlers } from './context.js';
|
|
2
|
+
export declare function dispatchSessionRoutes(req: Request, handlers: DaemonSessionRouteHandlers): Promise<Response | null>;
|
|
3
3
|
//# sourceMappingURL=sessions.d.ts.map
|
package/dist/sessions.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sessions.d.ts","sourceRoot":"","sources":["../src/sessions.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"sessions.d.ts","sourceRoot":"","sources":["../src/sessions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,cAAc,CAAC;AAE/D,wBAAsB,qBAAqB,CACzC,GAAG,EAAE,OAAO,EACZ,QAAQ,EAAE,0BAA0B,GACnC,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CAwC1B"}
|
package/dist/sessions.js
CHANGED
|
@@ -33,5 +33,8 @@ export async function dispatchSessionRoutes(req, handlers) {
|
|
|
33
33
|
if (sharedSessionCancelInputMatch && method === 'POST') {
|
|
34
34
|
return handlers.cancelSharedSessionInput(sharedSessionCancelInputMatch[1], sharedSessionCancelInputMatch[2]);
|
|
35
35
|
}
|
|
36
|
+
const sharedSessionEventsMatch = pathname.match(/^\/api\/sessions\/([^/]+)\/events$/);
|
|
37
|
+
if (sharedSessionEventsMatch && method === 'GET')
|
|
38
|
+
return handlers.getSharedSessionEvents(sharedSessionEventsMatch[1], req);
|
|
36
39
|
return null;
|
|
37
40
|
}
|
|
@@ -102,6 +102,23 @@ export interface ApprovalBrokerLike {
|
|
|
102
102
|
readonly note?: string;
|
|
103
103
|
}): Promise<unknown | null>;
|
|
104
104
|
}
|
|
105
|
+
export interface WorkspaceSwapManagerLike {
|
|
106
|
+
getCurrentWorkingDir(): string;
|
|
107
|
+
requestSwap(newWorkingDir: string): Promise<{
|
|
108
|
+
ok: true;
|
|
109
|
+
previous: string;
|
|
110
|
+
current: string;
|
|
111
|
+
} | {
|
|
112
|
+
ok: false;
|
|
113
|
+
code: 'WORKSPACE_BUSY';
|
|
114
|
+
reason: string;
|
|
115
|
+
retryAfter: number;
|
|
116
|
+
} | {
|
|
117
|
+
ok: false;
|
|
118
|
+
code: 'INVALID_PATH';
|
|
119
|
+
reason: string;
|
|
120
|
+
}>;
|
|
121
|
+
}
|
|
105
122
|
export interface DaemonSystemRouteContext {
|
|
106
123
|
readonly approvalBroker: ApprovalBrokerLike;
|
|
107
124
|
readonly configManager: ConfigManagerLike;
|
|
@@ -119,6 +136,8 @@ export interface DaemonSystemRouteContext {
|
|
|
119
136
|
roles: readonly string[];
|
|
120
137
|
} | null;
|
|
121
138
|
readonly routeBindings: RouteBindingManagerLike;
|
|
139
|
+
/** Manages runtime.workingDir swaps. Null when workspace swapping is not available. */
|
|
140
|
+
readonly swapManager: WorkspaceSwapManagerLike | null;
|
|
122
141
|
readonly watcherRegistry: WatcherRegistryLike;
|
|
123
142
|
}
|
|
124
143
|
//# sourceMappingURL=system-route-types.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"system-route-types.d.ts","sourceRoot":"","sources":["../src/system-route-types.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"system-route-types.d.ts","sourceRoot":"","sources":["../src/system-route-types.ts"],"names":[],"mappings":"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"}
|
package/dist/system-routes.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { DaemonSystemRouteHandlers } from './context.js';
|
|
2
2
|
import type { DaemonSystemRouteContext } from './system-route-types.js';
|
|
3
|
-
export declare function createDaemonSystemRouteHandlers(context: DaemonSystemRouteContext, request: Request):
|
|
3
|
+
export declare function createDaemonSystemRouteHandlers(context: DaemonSystemRouteContext, request: Request): DaemonSystemRouteHandlers;
|
|
4
4
|
//# sourceMappingURL=system-routes.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"system-routes.d.ts","sourceRoot":"","sources":["../src/system-routes.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"system-routes.d.ts","sourceRoot":"","sources":["../src/system-routes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,cAAc,CAAC;AAG9D,OAAO,KAAK,EAMV,wBAAwB,EAEzB,MAAM,yBAAyB,CAAC;AAEjC,wBAAgB,+BAA+B,CAC7C,OAAO,EAAE,wBAAwB,EACjC,OAAO,EAAE,OAAO,GACf,yBAAyB,CA6K3B"}
|
package/dist/system-routes.js
CHANGED
|
@@ -157,6 +157,24 @@ export function createDaemonSystemRouteHandlers(context, request) {
|
|
|
157
157
|
if (!key || typeof key !== 'string') {
|
|
158
158
|
return Response.json({ error: 'Missing or invalid key' }, { status: 400 });
|
|
159
159
|
}
|
|
160
|
+
// Special-case: runtime.workingDir is handled by the workspace swap manager.
|
|
161
|
+
if (key === 'runtime.workingDir') {
|
|
162
|
+
if (!context.swapManager) {
|
|
163
|
+
return Response.json({ error: 'Workspace swapping is not available in this daemon configuration.' }, { status: 400 });
|
|
164
|
+
}
|
|
165
|
+
if (typeof value !== 'string' || !value.trim()) {
|
|
166
|
+
return Response.json({ error: 'runtime.workingDir value must be a non-empty string path.', code: 'INVALID_PATH' }, { status: 400 });
|
|
167
|
+
}
|
|
168
|
+
const result = await context.swapManager.requestSwap(value);
|
|
169
|
+
if (result.ok) {
|
|
170
|
+
return Response.json({ success: true, key, value: result.current, previous: result.previous });
|
|
171
|
+
}
|
|
172
|
+
if (result.code === 'WORKSPACE_BUSY') {
|
|
173
|
+
return Response.json({ error: result.reason, code: 'WORKSPACE_BUSY', retryAfter: result.retryAfter }, { status: 409 });
|
|
174
|
+
}
|
|
175
|
+
// INVALID_PATH
|
|
176
|
+
return Response.json({ error: result.reason, code: 'INVALID_PATH' }, { status: 400 });
|
|
177
|
+
}
|
|
160
178
|
if (!context.isValidConfigKey(key)) {
|
|
161
179
|
return Response.json({ error: 'Invalid config key' }, { status: 400 });
|
|
162
180
|
}
|
package/dist/tasks.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
export declare function dispatchTaskRoutes(req: Request, handlers:
|
|
1
|
+
import type { DaemonTaskRouteHandlers } from './context.js';
|
|
2
|
+
export declare function dispatchTaskRoutes(req: Request, handlers: DaemonTaskRouteHandlers): Promise<Response | null>;
|
|
3
3
|
//# sourceMappingURL=tasks.d.ts.map
|
package/dist/tasks.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tasks.d.ts","sourceRoot":"","sources":["../src/tasks.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"tasks.d.ts","sourceRoot":"","sources":["../src/tasks.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,cAAc,CAAC;AAE5D,wBAAsB,kBAAkB,CACtC,GAAG,EAAE,OAAO,EACZ,QAAQ,EAAE,uBAAuB,GAChC,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CAqB1B"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { DaemonTelemetryRouteHandlers } from './context.js';
|
|
2
2
|
import { type AuthenticatedPrincipal } from './http-policy.js';
|
|
3
3
|
import type { RuntimeEventDomain } from '@pellux/goodvibes-contracts';
|
|
4
4
|
type TelemetrySeverity = 'debug' | 'info' | 'warn' | 'error';
|
|
@@ -35,10 +35,33 @@ interface TelemetryApiLike {
|
|
|
35
35
|
buildOtlpLogDocument(filter: TelemetryFilter, view: TelemetryViewMode): unknown;
|
|
36
36
|
buildOtlpMetricDocument(): unknown;
|
|
37
37
|
}
|
|
38
|
+
/**
|
|
39
|
+
* Ingest sink — receives parsed OTLP records forwarded by the POST receivers.
|
|
40
|
+
* When null (no ingest sink wired), the route still accepts and acknowledges
|
|
41
|
+
* payloads (to keep client exporters happy) but discards the data.
|
|
42
|
+
*/
|
|
43
|
+
interface TelemetryIngestSink {
|
|
44
|
+
/** Ingest a batch of log records from an OTLP ExportLogsServiceRequest. */
|
|
45
|
+
ingestLogs(payload: Record<string, unknown>): void;
|
|
46
|
+
/** Ingest a batch of trace spans from an OTLP ExportTraceServiceRequest. */
|
|
47
|
+
ingestTraces(payload: Record<string, unknown>): void;
|
|
48
|
+
/** Ingest a batch of metric data points from an OTLP ExportMetricsServiceRequest. */
|
|
49
|
+
ingestMetrics(payload: Record<string, unknown>): void;
|
|
50
|
+
}
|
|
38
51
|
interface TelemetryRouteContext {
|
|
39
52
|
readonly telemetryApi: TelemetryApiLike | null;
|
|
40
53
|
readonly resolveAuthenticatedPrincipal: (req: Request) => AuthenticatedPrincipal | null;
|
|
54
|
+
/**
|
|
55
|
+
* Sink for OTLP POST receivers. Must be provided by the caller.
|
|
56
|
+
* In production `DaemonHttpRouter` this is the `TelemetryApiService` instance
|
|
57
|
+
* which stores ingested records in its bounded event buffer (default 500
|
|
58
|
+
* records) and makes them observable via GET /api/v1/telemetry/events.
|
|
59
|
+
* Pass `null` only in test/stub contexts where ingestion is intentionally
|
|
60
|
+
* a no-op — the receivers still return 200 to keep OTLP exporters happy but
|
|
61
|
+
* discard the payload.
|
|
62
|
+
*/
|
|
63
|
+
readonly ingestSink: TelemetryIngestSink | null;
|
|
41
64
|
}
|
|
42
|
-
export declare function createDaemonTelemetryRouteHandlers(context: TelemetryRouteContext):
|
|
65
|
+
export declare function createDaemonTelemetryRouteHandlers(context: TelemetryRouteContext): DaemonTelemetryRouteHandlers;
|
|
43
66
|
export {};
|
|
44
67
|
//# sourceMappingURL=telemetry-routes.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"telemetry-routes.d.ts","sourceRoot":"","sources":["../src/telemetry-routes.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"telemetry-routes.d.ts","sourceRoot":"","sources":["../src/telemetry-routes.ts"],"names":[],"mappings":"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"}
|
package/dist/telemetry-routes.js
CHANGED
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
import { buildMissingScopeBody } from './http-policy.js';
|
|
2
|
-
|
|
2
|
+
import { decodeOtlpProtobuf } from './otlp-protobuf.js';
|
|
3
|
+
import { DaemonErrorCategory } from '@pellux/goodvibes-errors';
|
|
4
|
+
import { readBoundedPositiveInteger, readOptionalBoundedInteger } from './route-helpers.js';
|
|
5
|
+
const MAX_TELEMETRY_TIMESTAMP_MS = Date.UTC(2100, 0, 1);
|
|
6
|
+
function parseLimit(value) {
|
|
3
7
|
if (value === null || value.trim().length === 0)
|
|
4
8
|
return undefined;
|
|
5
|
-
|
|
6
|
-
|
|
9
|
+
return readBoundedPositiveInteger(value, 100, 1_000);
|
|
10
|
+
}
|
|
11
|
+
function parseTimestampMillis(value) {
|
|
12
|
+
return readOptionalBoundedInteger(value, 0, MAX_TELEMETRY_TIMESTAMP_MS);
|
|
7
13
|
}
|
|
8
14
|
function parseCsv(value) {
|
|
9
15
|
if (!value)
|
|
@@ -23,27 +29,101 @@ function parseView(value) {
|
|
|
23
29
|
return value === 'safe' || value === 'raw' ? value : undefined;
|
|
24
30
|
}
|
|
25
31
|
function buildFilter(url) {
|
|
32
|
+
const limit = parseLimit(url.searchParams.get('limit'));
|
|
33
|
+
const since = parseTimestampMillis(url.searchParams.get('since'));
|
|
34
|
+
const until = parseTimestampMillis(url.searchParams.get('until'));
|
|
35
|
+
const domains = parseCsv(url.searchParams.get('domains'));
|
|
36
|
+
const eventTypes = parseCsv(url.searchParams.get('types'));
|
|
37
|
+
const severity = parseSeverity(url.searchParams.get('severity'));
|
|
38
|
+
const view = parseView(url.searchParams.get('view'));
|
|
26
39
|
return {
|
|
27
|
-
...(
|
|
28
|
-
...(
|
|
29
|
-
...(
|
|
30
|
-
...(
|
|
31
|
-
...(
|
|
32
|
-
...(
|
|
40
|
+
...(limit !== undefined ? { limit } : {}),
|
|
41
|
+
...(since !== undefined ? { since } : {}),
|
|
42
|
+
...(until !== undefined ? { until } : {}),
|
|
43
|
+
...(domains ? { domains } : {}),
|
|
44
|
+
...(eventTypes ? { eventTypes } : {}),
|
|
45
|
+
...(severity ? { severity } : {}),
|
|
33
46
|
...(url.searchParams.get('traceId') ? { traceId: url.searchParams.get('traceId') ?? undefined } : {}),
|
|
34
47
|
...(url.searchParams.get('sessionId') ? { sessionId: url.searchParams.get('sessionId') ?? undefined } : {}),
|
|
35
48
|
...(url.searchParams.get('turnId') ? { turnId: url.searchParams.get('turnId') ?? undefined } : {}),
|
|
36
49
|
...(url.searchParams.get('agentId') ? { agentId: url.searchParams.get('agentId') ?? undefined } : {}),
|
|
37
50
|
...(url.searchParams.get('taskId') ? { taskId: url.searchParams.get('taskId') ?? undefined } : {}),
|
|
38
51
|
...(url.searchParams.get('cursor') ? { cursor: url.searchParams.get('cursor') ?? undefined } : {}),
|
|
39
|
-
...(
|
|
52
|
+
...(view ? { view } : {}),
|
|
40
53
|
};
|
|
41
54
|
}
|
|
55
|
+
// ---------------------------------------------------------------------------
|
|
56
|
+
// OTLP ingest helpers
|
|
57
|
+
// ---------------------------------------------------------------------------
|
|
58
|
+
/** Max ingest payload (4 MiB) — reject larger bodies with 413 */
|
|
59
|
+
const OTLP_INGEST_MAX_BODY_BYTES = 4 * 1024 * 1024;
|
|
60
|
+
/** Accepted content-types for OTLP/HTTP ingest. */
|
|
61
|
+
const OTLP_JSON_CONTENT_TYPE = 'application/json';
|
|
62
|
+
const OTLP_PROTOBUF_CONTENT_TYPES = new Set(['application/x-protobuf', 'application/protobuf']);
|
|
63
|
+
const OTLP_PARTIAL_SUCCESS_KEYS = {
|
|
64
|
+
logs: 'partialSuccess',
|
|
65
|
+
traces: 'partialSuccess',
|
|
66
|
+
metrics: 'partialSuccess',
|
|
67
|
+
};
|
|
68
|
+
/**
|
|
69
|
+
* Validate and parse an OTLP HTTP ingest request body.
|
|
70
|
+
* Returns a parsed JSON Record on success, or a Response (error) on failure.
|
|
71
|
+
*
|
|
72
|
+
* Protocol: OTLP/HTTP spec §4.2 — supports JSON and binary protobuf service
|
|
73
|
+
* requests for logs, traces, and metrics.
|
|
74
|
+
*/
|
|
75
|
+
async function parseOtlpBody(req, kind) {
|
|
76
|
+
const contentType = (req.headers.get('content-type') ?? '').toLowerCase().split(';')[0].trim();
|
|
77
|
+
const acceptsJson = contentType === OTLP_JSON_CONTENT_TYPE;
|
|
78
|
+
const acceptsProtobuf = OTLP_PROTOBUF_CONTENT_TYPES.has(contentType);
|
|
79
|
+
if (!acceptsJson && !acceptsProtobuf) {
|
|
80
|
+
return Response.json({
|
|
81
|
+
error: `Unsupported Content-Type '${contentType}' for OTLP ingest`,
|
|
82
|
+
code: 'UNSUPPORTED_MEDIA_TYPE',
|
|
83
|
+
category: DaemonErrorCategory.BAD_REQUEST,
|
|
84
|
+
hint: `Use '${OTLP_JSON_CONTENT_TYPE}' or 'application/x-protobuf'.`,
|
|
85
|
+
}, { status: 415 });
|
|
86
|
+
}
|
|
87
|
+
const raw = await req.arrayBuffer();
|
|
88
|
+
if (raw.byteLength > OTLP_INGEST_MAX_BODY_BYTES) {
|
|
89
|
+
return Response.json({
|
|
90
|
+
error: `OTLP ingest payload too large (${raw.byteLength} > ${OTLP_INGEST_MAX_BODY_BYTES} bytes)`,
|
|
91
|
+
code: 'PAYLOAD_TOO_LARGE',
|
|
92
|
+
category: DaemonErrorCategory.BAD_REQUEST,
|
|
93
|
+
}, { status: 413 });
|
|
94
|
+
}
|
|
95
|
+
if (acceptsProtobuf) {
|
|
96
|
+
try {
|
|
97
|
+
return decodeOtlpProtobuf(kind, new Uint8Array(raw));
|
|
98
|
+
}
|
|
99
|
+
catch (error) {
|
|
100
|
+
void error;
|
|
101
|
+
return Response.json({ error: 'OTLP ingest body is not valid protobuf', code: 'INVALID_PAYLOAD', category: DaemonErrorCategory.BAD_REQUEST }, { status: 400 });
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
try {
|
|
105
|
+
const text = new TextDecoder().decode(raw);
|
|
106
|
+
const parsed = JSON.parse(text);
|
|
107
|
+
if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
|
|
108
|
+
return Response.json({ error: 'OTLP ingest body must be a JSON object', code: 'INVALID_PAYLOAD', category: DaemonErrorCategory.BAD_REQUEST }, { status: 400 });
|
|
109
|
+
}
|
|
110
|
+
return parsed;
|
|
111
|
+
}
|
|
112
|
+
catch (error) {
|
|
113
|
+
void error;
|
|
114
|
+
return Response.json({ error: 'OTLP ingest body is not valid JSON', code: 'INVALID_PAYLOAD', category: DaemonErrorCategory.BAD_REQUEST }, { status: 400 });
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
function otlpIngestSuccess(kind) {
|
|
118
|
+
// Per OTLP/HTTP spec: respond with ExportXxxServiceResponse shape.
|
|
119
|
+
// partialSuccess omitted when empty (all records accepted).
|
|
120
|
+
return Response.json({ [OTLP_PARTIAL_SUCCESS_KEYS[kind]]: {} });
|
|
121
|
+
}
|
|
42
122
|
function unavailable() {
|
|
43
123
|
return Response.json({
|
|
44
124
|
error: 'Telemetry API unavailable',
|
|
45
125
|
code: 'TELEMETRY_UNAVAILABLE',
|
|
46
|
-
category:
|
|
126
|
+
category: DaemonErrorCategory.SERVICE,
|
|
47
127
|
source: 'runtime',
|
|
48
128
|
recoverable: true,
|
|
49
129
|
hint: 'Start the daemon runtime and ensure the runtime store is available before reading telemetry.',
|
|
@@ -54,7 +134,7 @@ function invalidCursor(error) {
|
|
|
54
134
|
return Response.json({
|
|
55
135
|
error: error instanceof Error ? error.message : 'Invalid telemetry cursor',
|
|
56
136
|
code: 'INVALID_CURSOR',
|
|
57
|
-
category:
|
|
137
|
+
category: DaemonErrorCategory.BAD_REQUEST,
|
|
58
138
|
source: 'runtime',
|
|
59
139
|
recoverable: false,
|
|
60
140
|
hint: 'Use the nextCursor returned by the previous telemetry page, or omit cursor to start from the newest records.',
|
|
@@ -67,7 +147,7 @@ function authenticateTelemetryRequest(context, req, requestedView) {
|
|
|
67
147
|
return Response.json({
|
|
68
148
|
error: 'Authentication required for telemetry access',
|
|
69
149
|
code: 'AUTH_REQUIRED',
|
|
70
|
-
category:
|
|
150
|
+
category: DaemonErrorCategory.AUTHENTICATION,
|
|
71
151
|
source: 'runtime',
|
|
72
152
|
recoverable: false,
|
|
73
153
|
hint: 'Authenticate with the operator shared token or an authenticated user session before calling telemetry APIs.',
|
|
@@ -79,7 +159,7 @@ function authenticateTelemetryRequest(context, req, requestedView) {
|
|
|
79
159
|
return Response.json({
|
|
80
160
|
error: missingRead.error,
|
|
81
161
|
code: 'MISSING_SCOPE',
|
|
82
|
-
category:
|
|
162
|
+
category: DaemonErrorCategory.AUTHORIZATION,
|
|
83
163
|
source: 'permission',
|
|
84
164
|
recoverable: false,
|
|
85
165
|
hint: 'Use a token or session with the read:telemetry scope, or elevate to an admin/shared-token session.',
|
|
@@ -92,7 +172,7 @@ function authenticateTelemetryRequest(context, req, requestedView) {
|
|
|
92
172
|
return Response.json({
|
|
93
173
|
error: 'Raw telemetry view requires elevated telemetry scope',
|
|
94
174
|
code: 'MISSING_SCOPE',
|
|
95
|
-
category:
|
|
175
|
+
category: DaemonErrorCategory.AUTHORIZATION,
|
|
96
176
|
source: 'permission',
|
|
97
177
|
recoverable: false,
|
|
98
178
|
hint: 'Use an admin/shared-token session or a token granted read:telemetry-sensitive to access raw telemetry payloads.',
|
|
@@ -223,5 +303,41 @@ export function createDaemonTelemetryRouteHandlers(context) {
|
|
|
223
303
|
return access;
|
|
224
304
|
return Response.json(context.telemetryApi.buildOtlpMetricDocument());
|
|
225
305
|
},
|
|
306
|
+
// -------------------------------------------------------------------------
|
|
307
|
+
// OTLP POST ingest receivers
|
|
308
|
+
// -------------------------------------------------------------------------
|
|
309
|
+
postTelemetryOtlpLogs: async (req) => {
|
|
310
|
+
const auth = context.resolveAuthenticatedPrincipal(req);
|
|
311
|
+
if (!auth) {
|
|
312
|
+
return Response.json({ error: 'Authentication required for OTLP ingest', code: 'AUTH_REQUIRED', category: DaemonErrorCategory.AUTHENTICATION, status: 401 }, { status: 401 });
|
|
313
|
+
}
|
|
314
|
+
const bodyOrErr = await parseOtlpBody(req, 'logs');
|
|
315
|
+
if (bodyOrErr instanceof Response)
|
|
316
|
+
return bodyOrErr;
|
|
317
|
+
context.ingestSink?.ingestLogs(bodyOrErr);
|
|
318
|
+
return otlpIngestSuccess('logs');
|
|
319
|
+
},
|
|
320
|
+
postTelemetryOtlpTraces: async (req) => {
|
|
321
|
+
const auth = context.resolveAuthenticatedPrincipal(req);
|
|
322
|
+
if (!auth) {
|
|
323
|
+
return Response.json({ error: 'Authentication required for OTLP ingest', code: 'AUTH_REQUIRED', category: DaemonErrorCategory.AUTHENTICATION, status: 401 }, { status: 401 });
|
|
324
|
+
}
|
|
325
|
+
const bodyOrErr = await parseOtlpBody(req, 'traces');
|
|
326
|
+
if (bodyOrErr instanceof Response)
|
|
327
|
+
return bodyOrErr;
|
|
328
|
+
context.ingestSink?.ingestTraces(bodyOrErr);
|
|
329
|
+
return otlpIngestSuccess('traces');
|
|
330
|
+
},
|
|
331
|
+
postTelemetryOtlpMetrics: async (req) => {
|
|
332
|
+
const auth = context.resolveAuthenticatedPrincipal(req);
|
|
333
|
+
if (!auth) {
|
|
334
|
+
return Response.json({ error: 'Authentication required for OTLP ingest', code: 'AUTH_REQUIRED', category: DaemonErrorCategory.AUTHENTICATION, status: 401 }, { status: 401 });
|
|
335
|
+
}
|
|
336
|
+
const bodyOrErr = await parseOtlpBody(req, 'metrics');
|
|
337
|
+
if (bodyOrErr instanceof Response)
|
|
338
|
+
return bodyOrErr;
|
|
339
|
+
context.ingestSink?.ingestMetrics(bodyOrErr);
|
|
340
|
+
return otlpIngestSuccess('metrics');
|
|
341
|
+
},
|
|
226
342
|
};
|
|
227
343
|
}
|