(sessionId: string, orgUUID: string, accessToken: string, onProgress?: TeleportProgressCallback, sessionData?: SessionResource)
| 563 | * @returns TeleportRemoteResponse with session logs as Message[] |
| 564 | */ |
| 565 | export async function teleportFromSessionsAPI(sessionId: string, orgUUID: string, accessToken: string, onProgress?: TeleportProgressCallback, sessionData?: SessionResource): Promise<TeleportRemoteResponse> { |
| 566 | const startTime = Date.now(); |
| 567 | try { |
| 568 | // Fetch session logs via session ingress |
| 569 | logForDebugging(`[teleport] Starting fetch for session: ${sessionId}`); |
| 570 | onProgress?.('fetching_logs'); |
| 571 | const logsStartTime = Date.now(); |
| 572 | // Try CCR v2 first (GetTeleportEvents — server dispatches Spanner/ |
| 573 | // threadstore). Fall back to session-ingress if it returns null |
| 574 | // (endpoint not yet deployed, or transient error). Once session-ingress |
| 575 | // is gone, the fallback becomes a no-op — getSessionLogsViaOAuth will |
| 576 | // return null too and we fail with "Failed to fetch session logs". |
| 577 | let logs = await getTeleportEvents(sessionId, accessToken, orgUUID); |
| 578 | if (logs === null) { |
| 579 | logForDebugging('[teleport] v2 endpoint returned null, trying session-ingress'); |
| 580 | logs = await getSessionLogsViaOAuth(sessionId, accessToken, orgUUID); |
| 581 | } |
| 582 | logForDebugging(`[teleport] Session logs fetched in ${Date.now() - logsStartTime}ms`); |
| 583 | if (logs === null) { |
| 584 | throw new Error('Failed to fetch session logs'); |
| 585 | } |
| 586 | |
| 587 | // Filter to get only transcript messages, excluding sidechain messages |
| 588 | const filterStartTime = Date.now(); |
| 589 | const messages = logs.filter(entry => isTranscriptMessage(entry) && !entry.isSidechain) as Message[]; |
| 590 | logForDebugging(`[teleport] Filtered ${logs.length} entries to ${messages.length} messages in ${Date.now() - filterStartTime}ms`); |
| 591 | |
| 592 | // Extract branch info from session data |
| 593 | onProgress?.('fetching_branch'); |
| 594 | const branch = sessionData ? getBranchFromSession(sessionData) : undefined; |
| 595 | if (branch) { |
| 596 | logForDebugging(`[teleport] Found branch: ${branch}`); |
| 597 | } |
| 598 | logForDebugging(`[teleport] Total teleportFromSessionsAPI time: ${Date.now() - startTime}ms`); |
| 599 | return { |
| 600 | log: messages, |
| 601 | branch |
| 602 | }; |
| 603 | } catch (error) { |
| 604 | const err = toError(error); |
| 605 | |
| 606 | // Handle 404 specifically |
| 607 | if (axios.isAxiosError(error) && error.response?.status === 404) { |
| 608 | logEvent('tengu_teleport_error_session_not_found_404', { |
| 609 | sessionId: sessionId as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS |
| 610 | }); |
| 611 | throw new TeleportOperationError(`${sessionId} not found.`, `${sessionId} not found.\n${chalk.dim('Run /status in Claude Code to check your account.')}`); |
| 612 | } |
| 613 | logError(err); |
| 614 | throw new Error(`Failed to fetch session from Sessions API: ${err.message}`); |
| 615 | } |
| 616 | } |
| 617 | |
| 618 | /** |
| 619 | * Response type for polling remote session events (uses SDK events format) |
no test coverage detected