| 1 | import { isWorkspaceImagePreviewPath } from "@t3tools/shared/filePreview"; |
| 2 | import { COMPUTER_USE_TOOL_SOURCE, dynamicToolSurface } from "@t3tools/shared/toolActivity"; |
| 3 | import type { |
| 4 | ToolActivityIcon, |
| 5 | ToolActivityNativeAppReference, |
| 6 | ToolActivitySource, |
| 7 | ToolActivitySurface, |
| 8 | } from "@t3tools/contracts"; |
| 9 | |
| 10 | export interface ExtractedToolActivityPresentation { |
| 11 | readonly viewedImagePath?: string; |
| 12 | readonly toolSurface?: ToolActivitySurface; |
| 13 | readonly toolIcon?: ToolActivityIcon; |
| 14 | readonly toolSource?: ToolActivitySource; |
| 15 | } |
| 16 | |
| 17 | function asRecord(value: unknown): Record<string, unknown> | undefined { |
| 18 | return value !== null && typeof value === "object" && !Array.isArray(value) |
| 19 | ? (value as Record<string, unknown>) |
| 20 | : undefined; |
| 21 | } |
| 22 | |
| 23 | function trimmedString(value: unknown, maxLength: number): string | undefined { |
| 24 | if (typeof value !== "string") return undefined; |
| 25 | const trimmed = value.trim(); |
| 26 | return trimmed && trimmed.length <= maxLength ? trimmed : undefined; |
| 27 | } |
| 28 | |
| 29 | function imageUrl(value: unknown): string | undefined { |
| 30 | const raw = trimmedString(value, 4096); |
| 31 | if (!raw) return undefined; |
| 32 | try { |
| 33 | const url = new URL(raw); |
| 34 | return url.protocol === "http:" || url.protocol === "https:" || url.protocol === "data:" |
| 35 | ? url.href |
| 36 | : undefined; |
| 37 | } catch { |
| 38 | return undefined; |
| 39 | } |
| 40 | } |
| 41 | |
| 42 | function pageUrl(value: unknown): string | undefined { |
| 43 | const raw = trimmedString(value, 4096); |
| 44 | if (!raw) return undefined; |
| 45 | try { |
| 46 | const url = new URL(raw); |
| 47 | return url.protocol === "http:" || url.protocol === "https:" ? url.href : undefined; |
| 48 | } catch { |
| 49 | return undefined; |
| 50 | } |
| 51 | } |
| 52 | |
| 53 | function nativeAppReference(value: unknown): ToolActivityNativeAppReference | undefined { |
| 54 | const app = asRecord(value); |
| 55 | const appId = trimmedString(app?.appId, 512); |
| 56 | if (app?._tag === "app-id" && appId && /^[A-Za-z0-9._-]+$/u.test(appId)) { |
| 57 | return { _tag: "app-id", appId }; |
| 58 | } |
| 59 | const displayName = trimmedString(app?.displayName, 160); |
| 60 | if (app?._tag === "display-name" && displayName) { |
| 61 | return { _tag: "display-name", displayName }; |
| 62 | } |
| 63 | return undefined; |
| 64 | } |
| 65 | |
| 66 | function activityIcon(value: unknown): ToolActivityIcon | undefined { |
| 67 | const icon = asRecord(value); |
| 68 | if (icon?._tag === "website") { |
| 69 | const resolvedPageUrl = pageUrl(icon.pageUrl); |
| 70 | const faviconUrl = imageUrl(icon.faviconUrl); |
| 71 | const faviconUrlDark = imageUrl(icon.faviconUrlDark); |
| 72 | if (resolvedPageUrl) { |
| 73 | return { |
| 74 | _tag: "website", |
| 75 | pageUrl: resolvedPageUrl, |
| 76 | ...(faviconUrl ? { faviconUrl } : {}), |
| 77 | ...(faviconUrlDark ? { faviconUrlDark } : {}), |
| 78 | }; |
| 79 | } |
| 80 | } |
| 81 | if (icon?._tag === "native-app") { |
| 82 | const app = nativeAppReference(icon.app); |
| 83 | if (app) return { _tag: "native-app", app }; |
| 84 | } |
| 85 | if (icon?._tag === "themed-logo") { |
| 86 | const logoUrl = imageUrl(icon.logoUrl); |
| 87 | const logoUrlDark = imageUrl(icon.logoUrlDark); |
| 88 | if (logoUrl) { |
| 89 | return { |
| 90 | _tag: "themed-logo", |
| 91 | logoUrl, |
| 92 | ...(logoUrlDark ? { logoUrlDark } : {}), |
| 93 | }; |
| 94 | } |
| 95 | } |
| 96 | return undefined; |
| 97 | } |
| 98 | |
| 99 | function activitySource(value: unknown): ToolActivitySource | undefined { |
| 100 | const source = asRecord(value); |
| 101 | const key = trimmedString(source?.key, 512); |
| 102 | const name = trimmedString(source?.name, 160); |
| 103 | const kind = source?.kind; |
| 104 | if (!key || !name || (kind !== "browser" && kind !== "computer" && kind !== "integration")) { |
| 105 | return undefined; |
| 106 | } |
| 107 | const icon = activityIcon(source?.icon); |
| 108 | return { key, name, kind, ...(icon ? { icon } : {}) }; |
| 109 | } |
| 110 | |
| 111 | export function extractToolActivityPresentation( |
| 112 | payloadValue: unknown, |
| 113 | ): ExtractedToolActivityPresentation { |
| 114 | const payload = asRecord(payloadValue); |
| 115 | const retainedSource = activitySource(payload?.toolSource); |
| 116 | const toolSurface = |
| 117 | payload?.toolSurface === "browser" || payload?.toolSurface === "computer" |
| 118 | ? payload.toolSurface |
| 119 | : retainedSource?.kind === "computer" || retainedSource?.kind === "browser" |
| 120 | ? retainedSource.kind |
| 121 | : payload?.type === "dynamic_tool" |
| 122 | ? dynamicToolSurface( |
| 123 | typeof payload.toolName === "string" ? payload.toolName : undefined, |
| 124 | payload.input, |
| 125 | ) |
| 126 | : undefined; |
| 127 | const toolSource = |
| 128 | toolSurface === "computer" && retainedSource?.kind !== "computer" |
| 129 | ? COMPUTER_USE_TOOL_SOURCE |
| 130 | : retainedSource; |
| 131 | const toolIcon = activityIcon(payload?.toolIcon); |
| 132 | const viewedImagePath = trimmedString(payload?.viewedImagePath, 4096); |
| 133 | return { |
| 134 | ...(toolSurface ? { toolSurface } : {}), |
| 135 | ...(toolIcon ? { toolIcon } : {}), |
| 136 | ...(toolSource ? { toolSource } : {}), |
| 137 | ...(viewedImagePath && |
| 138 | !/[\r\n]/.test(viewedImagePath) && |
| 139 | isWorkspaceImagePreviewPath(viewedImagePath) |
| 140 | ? { viewedImagePath } |
| 141 | : {}), |
| 142 | }; |
| 143 | } |