@agentuity/runtime 0.1.30 → 0.1.32
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/dist/_standalone.d.ts.map +1 -1
- package/dist/_standalone.js +3 -0
- package/dist/_standalone.js.map +1 -1
- package/dist/_waituntil.d.ts +15 -1
- package/dist/_waituntil.d.ts.map +1 -1
- package/dist/_waituntil.js +78 -19
- package/dist/_waituntil.js.map +1 -1
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +20 -5
- package/dist/agent.js.map +1 -1
- package/dist/middleware.d.ts.map +1 -1
- package/dist/middleware.js +207 -29
- package/dist/middleware.js.map +1 -1
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +19 -1
- package/dist/router.js.map +1 -1
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +1 -0
- package/dist/session.js.map +1 -1
- package/package.json +7 -7
- package/src/_standalone.ts +3 -0
- package/src/_waituntil.ts +82 -18
- package/src/agent.ts +19 -5
- package/src/middleware.ts +251 -40
- package/src/router.ts +28 -1
- package/src/session.ts +1 -0
package/src/middleware.ts
CHANGED
|
@@ -29,6 +29,7 @@ import * as runtimeConfig from './_config';
|
|
|
29
29
|
import { getSessionEventProvider } from './_services';
|
|
30
30
|
import { internal } from './logger/internal';
|
|
31
31
|
import { STREAM_DONE_PROMISE_KEY, IS_STREAMING_RESPONSE_KEY } from './handlers/sse';
|
|
32
|
+
import { loadBuildMetadata } from './_metadata';
|
|
32
33
|
|
|
33
34
|
const SESSION_HEADER = 'x-session-id';
|
|
34
35
|
const THREAD_HEADER = 'x-thread-id';
|
|
@@ -302,6 +303,8 @@ export function createOtelMiddleware() {
|
|
|
302
303
|
},
|
|
303
304
|
},
|
|
304
305
|
async (span): Promise<void> => {
|
|
306
|
+
// Track request duration from the SDK's perspective
|
|
307
|
+
const requestStartTime = performance.now();
|
|
305
308
|
const sctx = span.spanContext();
|
|
306
309
|
const sessionId = sctx?.traceId ? `sess_${sctx.traceId}` : generateId('sess');
|
|
307
310
|
|
|
@@ -321,6 +324,7 @@ export function createOtelMiddleware() {
|
|
|
321
324
|
|
|
322
325
|
if (projectId) traceState = traceState.set('pid', projectId);
|
|
323
326
|
if (orgId) traceState = traceState.set('oid', orgId);
|
|
327
|
+
if (deploymentId) traceState = traceState.set('did', deploymentId);
|
|
324
328
|
if (isDevMode) traceState = traceState.set('d', '1');
|
|
325
329
|
|
|
326
330
|
// Update the active context with the new trace state
|
|
@@ -353,8 +357,40 @@ export function createOtelMiddleware() {
|
|
|
353
357
|
const sessionEventProvider = getSessionEventProvider();
|
|
354
358
|
if (sessionEventProvider) {
|
|
355
359
|
try {
|
|
356
|
-
//
|
|
357
|
-
|
|
360
|
+
// Look up routeId from build metadata by matching method and path
|
|
361
|
+
// We need to do this here because the router wrapper hasn't run yet
|
|
362
|
+
const metadata = loadBuildMetadata();
|
|
363
|
+
const methodUpper = c.req.method.toUpperCase();
|
|
364
|
+
|
|
365
|
+
// Normalize paths: trim trailing slashes for consistent matching
|
|
366
|
+
const normalizePath = (p: string) => {
|
|
367
|
+
const decoded = decodeURIComponent(p);
|
|
368
|
+
return decoded.endsWith('/') && decoded.length > 1 ? decoded.slice(0, -1) : decoded;
|
|
369
|
+
};
|
|
370
|
+
const requestPath = normalizePath(c.req.path);
|
|
371
|
+
|
|
372
|
+
// Helper to check if requestPath ends with routePath at a segment boundary
|
|
373
|
+
// e.g., "/api/translate" matches "/translate" but "/api/translate-v2" does not
|
|
374
|
+
const matchesAtSegmentBoundary = (reqPath: string, routePath: string) => {
|
|
375
|
+
if (reqPath === routePath) return true;
|
|
376
|
+
if (!reqPath.endsWith(routePath)) return false;
|
|
377
|
+
// Check that the character before the match is a path separator
|
|
378
|
+
const charBeforeMatch = reqPath[reqPath.length - routePath.length - 1];
|
|
379
|
+
return charBeforeMatch === '/';
|
|
380
|
+
};
|
|
381
|
+
|
|
382
|
+
// Try matching by exact normalized path first
|
|
383
|
+
let route = metadata?.routes?.find(
|
|
384
|
+
(r) => r.method.toUpperCase() === methodUpper && normalizePath(r.path) === requestPath
|
|
385
|
+
);
|
|
386
|
+
// Fall back to segment-boundary matching (handles /api/translate matching /translate)
|
|
387
|
+
if (!route) {
|
|
388
|
+
route = metadata?.routes?.find(
|
|
389
|
+
(r) => r.method.toUpperCase() === methodUpper && matchesAtSegmentBoundary(requestPath, normalizePath(r.path))
|
|
390
|
+
);
|
|
391
|
+
}
|
|
392
|
+
const routeId = route?.id || '';
|
|
393
|
+
|
|
358
394
|
await sessionEventProvider.start({
|
|
359
395
|
id: sessionId,
|
|
360
396
|
threadId: thread.id,
|
|
@@ -418,14 +454,27 @@ export function createOtelMiddleware() {
|
|
|
418
454
|
}
|
|
419
455
|
};
|
|
420
456
|
|
|
457
|
+
// Track state for finalization
|
|
458
|
+
let responseStatus = 200;
|
|
459
|
+
let errorMessage: string | undefined;
|
|
460
|
+
let handlerDurationMs = 0;
|
|
461
|
+
// Track whether span should be ended in finally block (false for streaming - ended in waitUntil)
|
|
462
|
+
let shouldEndSpanInFinally = true;
|
|
463
|
+
|
|
421
464
|
try {
|
|
465
|
+
internal.info('[request] %s %s - handler starting (session: %s)', method, url.pathname, sessionId);
|
|
466
|
+
|
|
422
467
|
await next();
|
|
423
468
|
|
|
469
|
+
// Capture timing immediately after next() returns - this is when the handler completed
|
|
470
|
+
// This is the HTTP response time we want to report (excludes waitUntil/finalization)
|
|
471
|
+
handlerDurationMs = performance.now() - requestStartTime;
|
|
472
|
+
|
|
473
|
+
internal.info('[request] %s %s - handler completed in %sms (session: %s)', method, url.pathname, handlerDurationMs.toFixed(2), sessionId);
|
|
474
|
+
|
|
424
475
|
// Check if this is a streaming response that needs deferred finalization
|
|
425
476
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
426
|
-
const streamDone = (c as any).get(STREAM_DONE_PROMISE_KEY) as
|
|
427
|
-
| Promise<void>
|
|
428
|
-
| undefined;
|
|
477
|
+
const streamDone = (c as any).get(STREAM_DONE_PROMISE_KEY) as Promise<void> | undefined;
|
|
429
478
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
430
479
|
const isStreaming = Boolean((c as any).get(IS_STREAMING_RESPONSE_KEY));
|
|
431
480
|
|
|
@@ -433,38 +482,15 @@ export function createOtelMiddleware() {
|
|
|
433
482
|
// or if the response status indicates an error
|
|
434
483
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
435
484
|
const honoError = (c as any).error as Error | undefined;
|
|
436
|
-
|
|
485
|
+
responseStatus = c.res?.status ?? 200;
|
|
437
486
|
const isError = honoError || responseStatus >= 500;
|
|
438
487
|
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
// This ensures thread state changes made during streaming are persisted
|
|
442
|
-
internal.info(
|
|
443
|
-
'[session] deferring session/thread save until streaming completes (session %s)',
|
|
444
|
-
sessionId
|
|
445
|
-
);
|
|
488
|
+
internal.info('[request] %s %s - status: %d, streaming: %s, error: %s (session: %s)',
|
|
489
|
+
method, url.pathname, responseStatus, isStreaming, isError, sessionId);
|
|
446
490
|
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
internal.info(
|
|
451
|
-
'[session] stream completed, now saving session/thread (session %s)',
|
|
452
|
-
sessionId
|
|
453
|
-
);
|
|
454
|
-
} catch (ex) {
|
|
455
|
-
// Stream ended with an error/abort; still try to persist the latest state
|
|
456
|
-
internal.info(
|
|
457
|
-
'[session] stream ended with error, still saving state: %s',
|
|
458
|
-
ex
|
|
459
|
-
);
|
|
460
|
-
}
|
|
461
|
-
await finalizeSession();
|
|
462
|
-
});
|
|
463
|
-
|
|
464
|
-
span.setStatus({ code: SpanStatusCode.OK });
|
|
465
|
-
} else if (isError) {
|
|
466
|
-
// Hono caught an error or response is 5xx - report as error
|
|
467
|
-
const errorMessage = honoError
|
|
491
|
+
if (isError) {
|
|
492
|
+
// Capture error message for finalization
|
|
493
|
+
errorMessage = honoError
|
|
468
494
|
? (honoError.stack ?? honoError.message)
|
|
469
495
|
: `HTTP ${responseStatus}`;
|
|
470
496
|
span.setStatus({
|
|
@@ -474,26 +500,203 @@ export function createOtelMiddleware() {
|
|
|
474
500
|
if (honoError) {
|
|
475
501
|
span.recordException(honoError);
|
|
476
502
|
}
|
|
477
|
-
await finalizeSession(responseStatus, errorMessage);
|
|
478
503
|
} else {
|
|
479
|
-
// Non-streaming success: save session/thread synchronously
|
|
480
|
-
await finalizeSession();
|
|
481
504
|
span.setStatus({ code: SpanStatusCode.OK });
|
|
482
505
|
}
|
|
506
|
+
|
|
507
|
+
// For streaming responses, defer everything until stream completes
|
|
508
|
+
if (isStreaming && streamDone) {
|
|
509
|
+
internal.info('[request] %s %s - streaming response, deferring finalization (session: %s)',
|
|
510
|
+
method, url.pathname, sessionId);
|
|
511
|
+
|
|
512
|
+
// For streaming, we end the span inside waitUntil after setting attributes
|
|
513
|
+
shouldEndSpanInFinally = false;
|
|
514
|
+
|
|
515
|
+
// Capture pending promises BEFORE adding finalization waitUntil to avoid deadlock
|
|
516
|
+
const pendingPromises = handler.getPendingSnapshot();
|
|
517
|
+
const hasPendingTasks = pendingPromises.length > 0;
|
|
518
|
+
|
|
519
|
+
if (hasPendingTasks) {
|
|
520
|
+
internal.info('[request] %s %s - %d pending waitUntil tasks to wait for after stream (session: %s)',
|
|
521
|
+
method, url.pathname, pendingPromises.length, sessionId);
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
// Capture values needed for span attributes (responseStatus already captured above)
|
|
525
|
+
const capturedResponseStatus = responseStatus;
|
|
526
|
+
const capturedErrorMessage = errorMessage;
|
|
527
|
+
|
|
528
|
+
// Use waitUntil to handle stream completion and finalization
|
|
529
|
+
// This runs AFTER the response is sent to the client
|
|
530
|
+
// Note: We intentionally do NOT use noSpan here - the waitUntil span helps
|
|
531
|
+
// track the streaming finalization work in telemetry
|
|
532
|
+
handler.waitUntil(async () => {
|
|
533
|
+
// Track if stream ended with error so we can update finalization status
|
|
534
|
+
let streamError: unknown = undefined;
|
|
535
|
+
|
|
536
|
+
try {
|
|
537
|
+
await streamDone;
|
|
538
|
+
internal.info('[request] %s %s - stream completed (session: %s)', method, url.pathname, sessionId);
|
|
539
|
+
} catch (ex) {
|
|
540
|
+
streamError = ex;
|
|
541
|
+
internal.info('[request] %s %s - stream ended with error: %s (session: %s)',
|
|
542
|
+
method, url.pathname, ex, sessionId);
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
// Record duration now that stream is complete - set attributes BEFORE ending span
|
|
546
|
+
const streamDurationMs = performance.now() - requestStartTime;
|
|
547
|
+
const durationNs = Math.round(streamDurationMs * 1_000_000);
|
|
548
|
+
internal.info('[request] %s %s - recording stream duration: %sms (session: %s)',
|
|
549
|
+
method, url.pathname, streamDurationMs.toFixed(2), sessionId);
|
|
550
|
+
|
|
551
|
+
// Determine final status - use stream error if present
|
|
552
|
+
const finalStatus = streamError ? 500 : capturedResponseStatus;
|
|
553
|
+
const finalErrorMessage = streamError
|
|
554
|
+
? (streamError instanceof Error ? (streamError.stack ?? streamError.message) : String(streamError))
|
|
555
|
+
: capturedErrorMessage;
|
|
556
|
+
|
|
557
|
+
try {
|
|
558
|
+
// Wait for pending tasks (evals, etc.) captured BEFORE this waitUntil was added
|
|
559
|
+
if (hasPendingTasks) {
|
|
560
|
+
internal.info('[request] %s %s - waiting for %d pending waitUntil tasks (session: %s)',
|
|
561
|
+
method, url.pathname, pendingPromises.length, sessionId);
|
|
562
|
+
const logger = c.get('logger');
|
|
563
|
+
await handler.waitForPromises(pendingPromises, logger, sessionId);
|
|
564
|
+
internal.info('[request] %s %s - all waitUntil tasks complete (session: %s)', method, url.pathname, sessionId);
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
// Finalize session after stream completes and evals finish
|
|
568
|
+
await finalizeSession(finalStatus >= 500 ? finalStatus : undefined, finalErrorMessage);
|
|
569
|
+
internal.info('[request] %s %s - stream session finalization complete (session: %s)', method, url.pathname, sessionId);
|
|
570
|
+
} finally {
|
|
571
|
+
// Set span attributes and end span AFTER all work is done
|
|
572
|
+
span.setAttribute('@agentuity/request.duration', durationNs);
|
|
573
|
+
span.setAttribute('http.status_code', finalStatus);
|
|
574
|
+
|
|
575
|
+
// Set span status based on whether there was an error
|
|
576
|
+
if (streamError) {
|
|
577
|
+
span.setStatus({
|
|
578
|
+
code: SpanStatusCode.ERROR,
|
|
579
|
+
message: finalErrorMessage ?? 'Stream ended with error',
|
|
580
|
+
});
|
|
581
|
+
if (streamError instanceof Error) {
|
|
582
|
+
span.recordException(streamError);
|
|
583
|
+
}
|
|
584
|
+
} else {
|
|
585
|
+
span.setStatus({ code: SpanStatusCode.OK });
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
span.end();
|
|
589
|
+
internal.info('[request] %s %s - stream span ended (session: %s)', method, url.pathname, sessionId);
|
|
590
|
+
// Note: We don't call waitUntilAll() here because this waitUntil callback
|
|
591
|
+
// IS the final cleanup task. Calling waitUntilAll() would deadlock since
|
|
592
|
+
// it would wait for this very promise to complete.
|
|
593
|
+
}
|
|
594
|
+
});
|
|
595
|
+
} else {
|
|
596
|
+
// Non-streaming: record duration immediately
|
|
597
|
+
const durationNs = Math.round(handlerDurationMs * 1_000_000);
|
|
598
|
+
internal.info('[request] %s %s - recording duration: %sms (%dns) (session: %s)',
|
|
599
|
+
method, url.pathname, handlerDurationMs.toFixed(2), durationNs, sessionId);
|
|
600
|
+
span.setAttribute('@agentuity/request.duration', durationNs);
|
|
601
|
+
span.setAttribute('http.status_code', responseStatus);
|
|
602
|
+
|
|
603
|
+
// Capture pending promises BEFORE adding finalization waitUntil to avoid deadlock.
|
|
604
|
+
// If we called waitUntilAll inside waitUntil, it would wait for itself.
|
|
605
|
+
const pendingPromises = handler.getPendingSnapshot();
|
|
606
|
+
const hasPendingTasks = pendingPromises.length > 0;
|
|
607
|
+
|
|
608
|
+
if (hasPendingTasks) {
|
|
609
|
+
internal.info('[request] %s %s - %d pending waitUntil tasks to wait for (session: %s)',
|
|
610
|
+
method, url.pathname, pendingPromises.length, sessionId);
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
// Capture values for use in waitUntil callback
|
|
614
|
+
const capturedResponseStatus = responseStatus;
|
|
615
|
+
const capturedErrorMessage = errorMessage;
|
|
616
|
+
|
|
617
|
+
// Defer session finalization to run AFTER response is sent
|
|
618
|
+
// Use noSpan: true since finalizeSession creates its own Session End span
|
|
619
|
+
handler.waitUntil(async () => {
|
|
620
|
+
// Wait for the snapshot of pending tasks (evals, etc.) captured BEFORE this waitUntil was added
|
|
621
|
+
if (hasPendingTasks) {
|
|
622
|
+
internal.info('[request] %s %s - waiting for %d pending waitUntil tasks (session: %s)',
|
|
623
|
+
method, url.pathname, pendingPromises.length, sessionId);
|
|
624
|
+
const logger = c.get('logger');
|
|
625
|
+
await handler.waitForPromises(pendingPromises, logger, sessionId);
|
|
626
|
+
internal.info('[request] %s %s - all waitUntil tasks complete (session: %s)', method, url.pathname, sessionId);
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
// Finalize session - this is the actual work
|
|
630
|
+
internal.info('[request] %s %s - starting session finalization (session: %s)', method, url.pathname, sessionId);
|
|
631
|
+
try {
|
|
632
|
+
await finalizeSession(capturedResponseStatus >= 500 ? capturedResponseStatus : undefined, capturedErrorMessage);
|
|
633
|
+
internal.info('[request] %s %s - session finalization complete (session: %s)', method, url.pathname, sessionId);
|
|
634
|
+
} catch (ex) {
|
|
635
|
+
internal.error('[request] %s %s - session finalization failed: %s (session: %s)',
|
|
636
|
+
method, url.pathname, ex, sessionId);
|
|
637
|
+
}
|
|
638
|
+
// Note: We don't call waitUntilAll() here because this waitUntil callback
|
|
639
|
+
// IS the final cleanup task. Calling waitUntilAll() would deadlock since
|
|
640
|
+
// it would wait for this very promise to complete.
|
|
641
|
+
}, { noSpan: true });
|
|
642
|
+
}
|
|
483
643
|
} catch (ex) {
|
|
644
|
+
// Record request metrics even on exceptions (500 status)
|
|
645
|
+
const exceptionDurationMs = performance.now() - requestStartTime;
|
|
646
|
+
const durationNs = Math.round(exceptionDurationMs * 1_000_000);
|
|
647
|
+
internal.info('[request] %s %s - recording exception duration: %sms (session: %s)',
|
|
648
|
+
method, url.pathname, exceptionDurationMs.toFixed(2), sessionId);
|
|
649
|
+
span.setAttribute('@agentuity/request.duration', durationNs);
|
|
650
|
+
span.setAttribute('http.status_code', 500);
|
|
651
|
+
|
|
484
652
|
if (ex instanceof Error) {
|
|
485
653
|
span.recordException(ex);
|
|
486
654
|
}
|
|
487
|
-
|
|
655
|
+
errorMessage = ex instanceof Error ? (ex.stack ?? ex.message) : String(ex);
|
|
656
|
+
responseStatus = 500;
|
|
488
657
|
span.setStatus({
|
|
489
658
|
code: SpanStatusCode.ERROR,
|
|
490
659
|
message: ex instanceof Error ? ex.message : String(ex),
|
|
491
660
|
});
|
|
492
661
|
|
|
493
|
-
|
|
662
|
+
// Capture error message for use in waitUntil callback
|
|
663
|
+
const capturedErrorMessage = errorMessage;
|
|
664
|
+
|
|
665
|
+
// Capture pending promises BEFORE adding finalization waitUntil to avoid deadlock
|
|
666
|
+
const pendingPromises = handler.getPendingSnapshot();
|
|
667
|
+
const hasPendingTasks = pendingPromises.length > 0;
|
|
668
|
+
|
|
669
|
+
if (hasPendingTasks) {
|
|
670
|
+
internal.info('[request] %s %s - %d pending waitUntil tasks to wait for after error (session: %s)',
|
|
671
|
+
method, url.pathname, pendingPromises.length, sessionId);
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
// Still defer finalization even on error
|
|
675
|
+
// Use noSpan: true since finalizeSession creates its own Session End span
|
|
676
|
+
handler.waitUntil(async () => {
|
|
677
|
+
// Wait for pending tasks (evals, etc.) captured BEFORE this waitUntil was added
|
|
678
|
+
if (hasPendingTasks) {
|
|
679
|
+
internal.info('[request] %s %s - waiting for %d pending waitUntil tasks (session: %s)',
|
|
680
|
+
method, url.pathname, pendingPromises.length, sessionId);
|
|
681
|
+
const logger = c.get('logger');
|
|
682
|
+
await handler.waitForPromises(pendingPromises, logger, sessionId);
|
|
683
|
+
internal.info('[request] %s %s - all waitUntil tasks complete (session: %s)', method, url.pathname, sessionId);
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
try {
|
|
687
|
+
await finalizeSession(500, capturedErrorMessage);
|
|
688
|
+
} catch (finalizeEx) {
|
|
689
|
+
internal.error('[request] %s %s - error session finalization failed: %s (session: %s)',
|
|
690
|
+
method, url.pathname, finalizeEx, sessionId);
|
|
691
|
+
}
|
|
692
|
+
// Note: We don't call waitUntilAll() here because this waitUntil callback
|
|
693
|
+
// IS the final cleanup task. Calling waitUntilAll() would deadlock since
|
|
694
|
+
// it would wait for this very promise to complete.
|
|
695
|
+
}, { noSpan: true });
|
|
494
696
|
|
|
495
697
|
throw ex;
|
|
496
698
|
} finally {
|
|
699
|
+
// Set response headers - this is the only thing that should block the response
|
|
497
700
|
const headers: Record<string, string> = {};
|
|
498
701
|
propagation.inject(context.active(), headers);
|
|
499
702
|
for (const key of Object.keys(headers)) {
|
|
@@ -501,7 +704,15 @@ export function createOtelMiddleware() {
|
|
|
501
704
|
}
|
|
502
705
|
const traceId = sctx?.traceId || sessionId.replace(/^sess_/, '');
|
|
503
706
|
c.header(SESSION_HEADER, `sess_${traceId}`);
|
|
504
|
-
|
|
707
|
+
|
|
708
|
+
internal.info('[request] %s %s - response ready, duration: %sms (session: %s)',
|
|
709
|
+
method, url.pathname, handlerDurationMs.toFixed(2), sessionId);
|
|
710
|
+
|
|
711
|
+
// Only end span here for non-streaming responses
|
|
712
|
+
// For streaming, span is ended in the waitUntil callback after setting duration attributes
|
|
713
|
+
if (shouldEndSpanInFinally) {
|
|
714
|
+
span.end();
|
|
715
|
+
}
|
|
505
716
|
}
|
|
506
717
|
}
|
|
507
718
|
);
|
package/src/router.ts
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { type Context, Hono, type Schema, type Env as HonoEnv } from 'hono';
|
|
3
3
|
import { returnResponse } from './_util';
|
|
4
4
|
import type { Env } from './app';
|
|
5
|
+
import { loadBuildMetadata } from './_metadata';
|
|
5
6
|
|
|
6
7
|
// Re-export both Env types
|
|
7
8
|
export type { Env };
|
|
@@ -163,8 +164,34 @@ export const createRouter = <E extends Env = Env, S extends Schema = Schema>():
|
|
|
163
164
|
return _originalInvoker(path, ...args);
|
|
164
165
|
}
|
|
165
166
|
|
|
166
|
-
// Wrap the handler to add our response conversion
|
|
167
|
+
// Wrap the handler to add our response conversion and set routeId
|
|
167
168
|
const wrapper = async (c: Context): Promise<Response> => {
|
|
169
|
+
// Look up the route ID from build metadata by matching method and path
|
|
170
|
+
// Try both the registered path and the actual request path (which may include base path)
|
|
171
|
+
const metadata = loadBuildMetadata();
|
|
172
|
+
const methodUpper = method.toUpperCase();
|
|
173
|
+
const requestPath = c.req.routePath || c.req.path;
|
|
174
|
+
|
|
175
|
+
// Try matching by registered path first, then by request path, then by path ending
|
|
176
|
+
let route = metadata?.routes?.find(
|
|
177
|
+
(r) => r.method.toUpperCase() === methodUpper && r.path === path
|
|
178
|
+
);
|
|
179
|
+
if (!route) {
|
|
180
|
+
route = metadata?.routes?.find(
|
|
181
|
+
(r) => r.method.toUpperCase() === methodUpper && r.path === requestPath
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
if (!route) {
|
|
185
|
+
// Try matching by path ending (handles /api/translate matching /translate)
|
|
186
|
+
route = metadata?.routes?.find(
|
|
187
|
+
(r) => r.method.toUpperCase() === methodUpper && r.path.endsWith(path)
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (route?.id) {
|
|
192
|
+
(c as any).set('routeId', route.id);
|
|
193
|
+
}
|
|
194
|
+
|
|
168
195
|
let result = handler(c);
|
|
169
196
|
if (result instanceof Promise) result = await result;
|
|
170
197
|
// If handler returns a Response, return it unchanged
|
package/src/session.ts
CHANGED
|
@@ -1408,6 +1408,7 @@ export class ThreadWebSocketClient {
|
|
|
1408
1408
|
this.ws = new WebSocket(this.wsUrl);
|
|
1409
1409
|
|
|
1410
1410
|
this.ws.addEventListener('open', () => {
|
|
1411
|
+
internal.info('WebSocket connected');
|
|
1411
1412
|
// Send authentication (do NOT clear timeout yet - wait for auth response)
|
|
1412
1413
|
this.ws?.send(JSON.stringify({ authorization: this.apiKey }));
|
|
1413
1414
|
});
|