返回 presentation-ai
image-search.ts
根目录 / src / lib / presentation / image-search.ts
1 export const PRESENTATION_IMAGE_SEARCH_TOOL_NAME = "search_presentation_images";
2
3 export interface PresentationImageSearchResultItem {
4 url: string;
5 description: string;
6 sourceTitle?: string;
7 sourceUrl?: string;
8 }
9
10 export interface PresentationImageSearchResult {
11 query: string;
12 results: PresentationImageSearchResultItem[];
13 }
14
15 function isRecord(value: unknown): value is Record<string, unknown> {
16 return typeof value === "object" && value !== null && !Array.isArray(value);
17 }
18
19 function normalizeUrl(value: unknown): string | null {
20 if (typeof value !== "string" || value.trim().length === 0) {
21 return null;
22 }
23
24 try {
25 const parsed = new URL(value);
26 if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
27 return null;
28 }
29
30 return parsed.toString();
31 } catch {
32 return null;
33 }
34 }
35
36 function normalizeDescription(value: unknown): string {
37 return typeof value === "string" && value.trim().length > 0
38 ? value.trim()
39 : "Image result";
40 }
41
42 function normalizeOptionalText(value: unknown): string | undefined {
43 return typeof value === "string" && value.trim().length > 0
44 ? value.trim()
45 : undefined;
46 }
47
48 export function parsePresentationImageSearchResults(
49 value: unknown,
50 ): PresentationImageSearchResultItem[] {
51 if (!Array.isArray(value)) {
52 return [];
53 }
54
55 const seenUrls = new Set<string>();
56
57 return value.reduce<PresentationImageSearchResultItem[]>((acc, item) => {
58 if (!isRecord(item)) {
59 return acc;
60 }
61
62 const url = normalizeUrl(item.url);
63 if (!url || seenUrls.has(url)) {
64 return acc;
65 }
66
67 seenUrls.add(url);
68 acc.push({
69 url,
70 description: normalizeDescription(item.description),
71 sourceTitle: normalizeOptionalText(item.sourceTitle),
72 sourceUrl: normalizeOptionalText(item.sourceUrl),
73 });
74 return acc;
75 }, []);
76 }
77
78 export function parsePresentationImageSearchPayload(
79 value: unknown,
80 ): PresentationImageSearchResult | null {
81 const parsedValue =
82 typeof value === "string"
83 ? (() => {
84 try {
85 return JSON.parse(value) as unknown;
86 } catch {
87 return null;
88 }
89 })()
90 : value;
91
92 if (!isRecord(parsedValue)) {
93 return null;
94 }
95
96 const query =
97 typeof parsedValue.query === "string" && parsedValue.query.trim().length > 0
98 ? parsedValue.query.trim()
99 : null;
100
101 if (!query) {
102 return null;
103 }
104
105 return {
106 query,
107 results: parsePresentationImageSearchResults(parsedValue.results),
108 };
109 }
110
110 lines TYPESCRIPT