@oai404iao/pi-codex-web-search 0.1.0-alpha.1
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/LICENSE +28 -0
- package/LICENSES/Apache-2.0.txt +201 -0
- package/LICENSES/OpenAI-Codex-NOTICE.txt +6 -0
- package/README.md +26 -0
- package/THIRD_PARTY_NOTICES.md +17 -0
- package/package.json +80 -0
- package/provenance/openai-codex-eb9dceba-reserved-tools.json +140 -0
- package/src/index.ts +23 -0
- package/src/tools/web-search/activity.ts +195 -0
- package/src/tools/web-search/capture.ts +44 -0
- package/src/tools/web-search/render.ts +48 -0
- package/src/tools/web-search/schema.ts +171 -0
- package/src/tools/web-search.ts +389 -0
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { glyphs } from "@oai404iao/pi-codex-runtime/internal/glyphs";
|
|
2
|
+
import { type StreamEventShape } from "@oai404iao/pi-codex-runtime/internal/providers/openai-codex/types";
|
|
3
|
+
|
|
4
|
+
export const WEB_SEARCH_ACTIVITY_MESSAGE_TYPE = "codex-web-search-activity";
|
|
5
|
+
|
|
6
|
+
export interface SurfacedWebSearch {
|
|
7
|
+
callId: string;
|
|
8
|
+
status?: string;
|
|
9
|
+
completed?: boolean;
|
|
10
|
+
actionType?: string;
|
|
11
|
+
query?: string;
|
|
12
|
+
queries: string[];
|
|
13
|
+
url?: string;
|
|
14
|
+
pattern?: string;
|
|
15
|
+
sources: Array<{ title?: string; url: string }>;
|
|
16
|
+
responseItem?: Record<string, unknown>;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function extractWebSearch(
|
|
20
|
+
item: StreamEventShape["item"],
|
|
21
|
+
options?: { completed?: boolean },
|
|
22
|
+
): SurfacedWebSearch | undefined {
|
|
23
|
+
if (!item || item.type !== "web_search_call") return undefined;
|
|
24
|
+
const callId = typeof item.id === "string" ? item.id : typeof item.call_id === "string" ? item.call_id : undefined;
|
|
25
|
+
if (!callId) return undefined;
|
|
26
|
+
|
|
27
|
+
const action = typeof item.action === "object" && item.action !== null ? (item.action as Record<string, unknown>) : undefined;
|
|
28
|
+
const actionType = typeof action?.type === "string" ? action.type : undefined;
|
|
29
|
+
const query = typeof action?.query === "string" ? action.query : typeof item.query === "string" ? item.query : undefined;
|
|
30
|
+
const queries = [
|
|
31
|
+
...(Array.isArray(action?.queries) ? action.queries : []),
|
|
32
|
+
...(Array.isArray(item.queries) ? item.queries : []),
|
|
33
|
+
].filter((value): value is string => typeof value === "string" && value.trim().length > 0);
|
|
34
|
+
const url = typeof action?.url === "string" && action.url.trim()
|
|
35
|
+
? action.url.trim()
|
|
36
|
+
: typeof item.url === "string" && item.url.trim()
|
|
37
|
+
? item.url.trim()
|
|
38
|
+
: undefined;
|
|
39
|
+
const pattern = typeof action?.pattern === "string" && action.pattern.trim() ? action.pattern.trim() : undefined;
|
|
40
|
+
|
|
41
|
+
const asRecordArray = (value: unknown): Record<string, unknown>[] => Array.isArray(value)
|
|
42
|
+
? value
|
|
43
|
+
.map((entry) => typeof entry === "object" && entry !== null ? entry as Record<string, unknown> : undefined)
|
|
44
|
+
.filter((entry): entry is Record<string, unknown> => !!entry)
|
|
45
|
+
: [];
|
|
46
|
+
const sourceCandidates = [
|
|
47
|
+
...asRecordArray(action?.sources),
|
|
48
|
+
...asRecordArray(action?.results),
|
|
49
|
+
...asRecordArray(item.results),
|
|
50
|
+
];
|
|
51
|
+
if (typeof item.url === "string") sourceCandidates.push(item as Record<string, unknown>);
|
|
52
|
+
|
|
53
|
+
const seenUrls = new Set<string>();
|
|
54
|
+
const sources: Array<{ title?: string; url: string }> = [];
|
|
55
|
+
for (const source of sourceCandidates) {
|
|
56
|
+
const url = typeof source.url === "string" && source.url.trim() ? source.url.trim() : undefined;
|
|
57
|
+
if (!url || seenUrls.has(url)) continue;
|
|
58
|
+
seenUrls.add(url);
|
|
59
|
+
const title = typeof source.title === "string" && source.title.trim() ? source.title.trim() : undefined;
|
|
60
|
+
sources.push({ ...(title ? { title } : {}), url });
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return {
|
|
64
|
+
callId,
|
|
65
|
+
...(typeof item.status === "string" ? { status: item.status } : {}),
|
|
66
|
+
...(options?.completed !== undefined ? { completed: options.completed } : {}),
|
|
67
|
+
...(actionType ? { actionType } : {}),
|
|
68
|
+
...(query ? { query } : {}),
|
|
69
|
+
queries,
|
|
70
|
+
...(url ? { url } : {}),
|
|
71
|
+
...(pattern ? { pattern } : {}),
|
|
72
|
+
sources,
|
|
73
|
+
...(options?.completed ? { responseItem: item as Record<string, unknown> } : {}),
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function extractWebSearchProgress(event: StreamEventShape): SurfacedWebSearch | undefined {
|
|
78
|
+
const status = event.type === "response.web_search_call.in_progress"
|
|
79
|
+
? "in_progress"
|
|
80
|
+
: event.type === "response.web_search_call.searching"
|
|
81
|
+
? "searching"
|
|
82
|
+
: event.type === "response.web_search_call.completed"
|
|
83
|
+
? "completed"
|
|
84
|
+
: undefined;
|
|
85
|
+
if (!status || typeof event.item_id !== "string" || !event.item_id) return undefined;
|
|
86
|
+
return {
|
|
87
|
+
callId: event.item_id,
|
|
88
|
+
status,
|
|
89
|
+
completed: status === "completed",
|
|
90
|
+
queries: [],
|
|
91
|
+
sources: [],
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function mergeWebSearchActivity(
|
|
96
|
+
previous: SurfacedWebSearch | undefined,
|
|
97
|
+
next: SurfacedWebSearch,
|
|
98
|
+
): SurfacedWebSearch {
|
|
99
|
+
if (!previous) return next;
|
|
100
|
+
const seenUrls = new Set<string>();
|
|
101
|
+
const sources = [...next.sources, ...previous.sources].filter((source) => {
|
|
102
|
+
if (seenUrls.has(source.url)) return false;
|
|
103
|
+
seenUrls.add(source.url);
|
|
104
|
+
return true;
|
|
105
|
+
});
|
|
106
|
+
const completed = Boolean(previous.completed || next.completed);
|
|
107
|
+
return {
|
|
108
|
+
callId: next.callId,
|
|
109
|
+
status: previous.completed && !next.completed ? previous.status : (next.status ?? previous.status),
|
|
110
|
+
completed,
|
|
111
|
+
actionType: next.actionType ?? previous.actionType,
|
|
112
|
+
query: next.query ?? previous.query,
|
|
113
|
+
queries: next.queries.length > 0 ? next.queries : previous.queries,
|
|
114
|
+
url: next.url ?? previous.url,
|
|
115
|
+
pattern: next.pattern ?? previous.pattern,
|
|
116
|
+
sources,
|
|
117
|
+
responseItem: next.responseItem ?? previous.responseItem,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function webSearchActivityDetail(search: SurfacedWebSearch): string {
|
|
122
|
+
if (search.actionType === "open_page") return search.url ?? "";
|
|
123
|
+
if (search.actionType === "find_in_page") {
|
|
124
|
+
if (search.pattern && search.url) return `'${search.pattern}' in ${search.url}`;
|
|
125
|
+
if (search.pattern) return `'${search.pattern}'`;
|
|
126
|
+
return search.url ?? "";
|
|
127
|
+
}
|
|
128
|
+
const query = search.query?.trim();
|
|
129
|
+
if (query) return query;
|
|
130
|
+
const first = search.queries[0]?.trim() ?? "";
|
|
131
|
+
return search.queries.length > 1 && first ? `${first} ...` : first;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export function webSearchActivityHosts(search: SurfacedWebSearch): string[] {
|
|
135
|
+
const seen = new Set<string>();
|
|
136
|
+
const hosts: string[] = [];
|
|
137
|
+
for (const source of search.sources) {
|
|
138
|
+
try {
|
|
139
|
+
const host = new URL(source.url).hostname.replace(/^www\./i, "");
|
|
140
|
+
const key = host.toLowerCase();
|
|
141
|
+
if (!host || seen.has(key)) continue;
|
|
142
|
+
seen.add(key);
|
|
143
|
+
hosts.push(host);
|
|
144
|
+
} catch {
|
|
145
|
+
// Sources without a valid URL do not produce a host tag.
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return hosts;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export function buildWebSearchStatusText(search: SurfacedWebSearch): string {
|
|
152
|
+
const completed = search.completed ?? search.status === "completed";
|
|
153
|
+
const detail = webSearchActivityDetail(search);
|
|
154
|
+
if (completed) return `Searched the web${detail ? ` for ${detail}` : ""}`;
|
|
155
|
+
return `Searching the web${detail ? ` ${detail}` : ""}`;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export function buildWebSearchInlineText(search: SurfacedWebSearch, cwd?: string): string {
|
|
159
|
+
const completed = search.completed ?? search.status === "completed";
|
|
160
|
+
const header = completed ? "Searched the web" : "Searching the web";
|
|
161
|
+
const detail = webSearchActivityDetail(search);
|
|
162
|
+
const separator = detail ? (completed ? " for " : " ") : "";
|
|
163
|
+
return `${glyphs(cwd).bullet}**${header}**${separator}${detail}`;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export function buildWebSearchActivityMessage(searches: SurfacedWebSearch[]): string {
|
|
167
|
+
const sections = searches.map((search, index) => {
|
|
168
|
+
const heading = searches.length > 1
|
|
169
|
+
? `${index + 1}. ${buildWebSearchStatusText(search)}`
|
|
170
|
+
: buildWebSearchStatusText(search);
|
|
171
|
+
const lines = [heading, `Call: ${search.callId}${search.status ? ` (${search.status})` : ""}`];
|
|
172
|
+
const queries = search.queries.length > 0 ? search.queries : search.query ? [search.query] : [];
|
|
173
|
+
if (queries.length > 0) {
|
|
174
|
+
lines.push(`Query: ${queries.join(" | ")}`);
|
|
175
|
+
}
|
|
176
|
+
if (search.sources.length > 0) {
|
|
177
|
+
lines.push("Sources:");
|
|
178
|
+
for (const source of search.sources.slice(0, 8)) {
|
|
179
|
+
lines.push(`- ${source.title ? `${source.title}: ` : ""}${source.url}`);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return lines.join("\n");
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
return sections.join("\n\n");
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export function buildWebSearchSummaryText(searches: SurfacedWebSearch[]): string {
|
|
189
|
+
if (searches.length === 0) return "Web search";
|
|
190
|
+
if (searches.length === 1) return buildWebSearchStatusText(searches[0]!);
|
|
191
|
+
const completed = searches.filter((search) => search.completed ?? search.status === "completed").length;
|
|
192
|
+
if (completed === searches.length) return `Searched the web ${searches.length} times`;
|
|
193
|
+
if (completed === 0) return `Searching the web (${searches.length} calls)`;
|
|
194
|
+
return `Web search activity (${completed}/${searches.length} completed)`;
|
|
195
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { AssistantMessage } from "@earendil-works/pi-ai/compat";
|
|
2
|
+
import type { ProviderEventContext } from "@oai404iao/pi-codex-runtime/internal/providers/openai-codex/stream-effects";
|
|
3
|
+
import type { StreamEventShape } from "@oai404iao/pi-codex-runtime/internal/providers/openai-codex/types";
|
|
4
|
+
import { encodeWebSearchActivityTextSignature } from "@oai404iao/pi-codex-runtime/internal/providers/responses/signatures";
|
|
5
|
+
import {
|
|
6
|
+
buildWebSearchInlineText, extractWebSearch, extractWebSearchProgress,
|
|
7
|
+
mergeWebSearchActivity, type SurfacedWebSearch,
|
|
8
|
+
} from "./activity.js";
|
|
9
|
+
|
|
10
|
+
export function createWebSearchCapture({ cwd, output, stream }: ProviderEventContext) {
|
|
11
|
+
type TextBlock = Extract<AssistantMessage["content"][number], { type: "text" }>;
|
|
12
|
+
const states = new Map<string, { search: SurfacedWebSearch; block: TextBlock; contentIndex: number }>();
|
|
13
|
+
const updateActivity = (search: SurfacedWebSearch) => {
|
|
14
|
+
const existing = states.get(search.callId);
|
|
15
|
+
const merged = mergeWebSearchActivity(existing?.search, search);
|
|
16
|
+
const text = buildWebSearchInlineText(merged, cwd);
|
|
17
|
+
const textSignature = encodeWebSearchActivityTextSignature(merged.callId, merged.responseItem);
|
|
18
|
+
if (existing) {
|
|
19
|
+
existing.search = merged;
|
|
20
|
+
existing.block.text = text;
|
|
21
|
+
existing.block.textSignature = textSignature;
|
|
22
|
+
stream.push({ type: "text_delta", contentIndex: existing.contentIndex, delta: "", partial: output });
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
const block: TextBlock = { type: "text", text: "", textSignature };
|
|
26
|
+
output.content.push(block);
|
|
27
|
+
const contentIndex = output.content.length - 1;
|
|
28
|
+
states.set(search.callId, { search: merged, block, contentIndex });
|
|
29
|
+
stream.push({ type: "text_start", contentIndex, partial: output });
|
|
30
|
+
block.text = text;
|
|
31
|
+
stream.push({ type: "text_delta", contentIndex, delta: text, partial: output });
|
|
32
|
+
};
|
|
33
|
+
return (event: StreamEventShape): void => {
|
|
34
|
+
if (
|
|
35
|
+
(event.type === "response.output_item.added" || event.type === "response.output_item.done")
|
|
36
|
+
&& event.item?.type === "web_search_call"
|
|
37
|
+
) {
|
|
38
|
+
const search = extractWebSearch(event.item, { completed: event.type === "response.output_item.done" });
|
|
39
|
+
if (search) updateActivity(search);
|
|
40
|
+
}
|
|
41
|
+
const progress = extractWebSearchProgress(event);
|
|
42
|
+
if (progress) updateActivity(progress);
|
|
43
|
+
};
|
|
44
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { Container, Text } from "@earendil-works/pi-tui";
|
|
3
|
+
import { glyphs } from "@oai404iao/pi-codex-runtime/internal/glyphs";
|
|
4
|
+
import { themeBold, themeFg } from "@oai404iao/pi-codex-runtime/internal/utils/theme";
|
|
5
|
+
import { WEB_SEARCH_ACTIVITY_MESSAGE_TYPE, buildWebSearchSummaryText, webSearchActivityDetail, webSearchActivityHosts, type SurfacedWebSearch } from "./activity.js";
|
|
6
|
+
|
|
7
|
+
export function registerWebSearchActivityRenderer(pi: ExtensionAPI): void {
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
pi.registerMessageRenderer<{ searches?: SurfacedWebSearch[] }>(WEB_SEARCH_ACTIVITY_MESSAGE_TYPE, (message, options, theme) => {
|
|
11
|
+
const searches = message.details?.searches ?? [];
|
|
12
|
+
const container = new Container();
|
|
13
|
+
if (searches.length > 0) {
|
|
14
|
+
searches.forEach((search, index) => {
|
|
15
|
+
const completed = search.completed ?? search.status === "completed";
|
|
16
|
+
const header = completed ? "Searched the web" : "Searching the web";
|
|
17
|
+
const detail = webSearchActivityDetail(search);
|
|
18
|
+
const separator = detail ? (completed ? " for " : " ") : "";
|
|
19
|
+
const bullet = themeFg(theme, completed ? "muted" : "accent", glyphs().bullet);
|
|
20
|
+
const lines = [
|
|
21
|
+
`${bullet}${themeFg(theme, "text", themeBold(theme, header))}${themeFg(theme, "dim", `${separator}${detail}`)}`,
|
|
22
|
+
];
|
|
23
|
+
const hosts = webSearchActivityHosts(search);
|
|
24
|
+
if (hosts.length > 0) {
|
|
25
|
+
const shown = hosts.slice(0, 8);
|
|
26
|
+
const hostLine = shown.map((host) => themeFg(theme, "accent", host));
|
|
27
|
+
if (hosts.length > shown.length) {
|
|
28
|
+
hostLine.push(themeFg(theme, "dim", `+${hosts.length - shown.length}`));
|
|
29
|
+
}
|
|
30
|
+
lines.push(` ${hostLine.join(themeFg(theme, "dim", glyphs().dot))}`);
|
|
31
|
+
}
|
|
32
|
+
container.addChild(new Text(`${index > 0 ? "\n" : ""}${lines.join("\n")}`, 0, 0));
|
|
33
|
+
});
|
|
34
|
+
} else {
|
|
35
|
+
container.addChild(new Text(themeFg(theme, "text", themeBold(theme, buildWebSearchSummaryText(searches))), 0, 0));
|
|
36
|
+
}
|
|
37
|
+
if (options.expanded) {
|
|
38
|
+
const content = typeof message.content === "string"
|
|
39
|
+
? message.content
|
|
40
|
+
: message.content
|
|
41
|
+
.filter((item) => item.type === "text")
|
|
42
|
+
.map((item) => item.text)
|
|
43
|
+
.join("\n");
|
|
44
|
+
container.addChild(new Text(`\n${themeFg(theme, "dim", content)}`, 0, 0));
|
|
45
|
+
}
|
|
46
|
+
return container;
|
|
47
|
+
});
|
|
48
|
+
}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
export interface SearchQuery {
|
|
2
|
+
q: string;
|
|
3
|
+
recency?: number;
|
|
4
|
+
domains?: string[];
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export interface WebSearchInput {
|
|
8
|
+
search_query?: SearchQuery[];
|
|
9
|
+
image_query?: SearchQuery[];
|
|
10
|
+
open?: Array<{ ref_id: string; lineno?: number }>;
|
|
11
|
+
click?: Array<{ ref_id: string; id: number }>;
|
|
12
|
+
find?: Array<{ ref_id: string; pattern: string }>;
|
|
13
|
+
screenshot?: Array<{ ref_id: string; pageno: number }>;
|
|
14
|
+
finance?: Array<{
|
|
15
|
+
ticker: string;
|
|
16
|
+
type: "equity" | "fund" | "crypto" | "index";
|
|
17
|
+
market?: string;
|
|
18
|
+
}>;
|
|
19
|
+
weather?: Array<{ location: string; start?: string; duration?: number }>;
|
|
20
|
+
sports?: Array<{
|
|
21
|
+
tool?: "sports";
|
|
22
|
+
fn: "schedule" | "standings";
|
|
23
|
+
league: "nba" | "wnba" | "nfl" | "nhl" | "mlb" | "epl" | "ncaamb" | "ncaawb" | "ipl";
|
|
24
|
+
team?: string;
|
|
25
|
+
opponent?: string;
|
|
26
|
+
date_from?: string;
|
|
27
|
+
date_to?: string;
|
|
28
|
+
num_games?: number;
|
|
29
|
+
locale?: string;
|
|
30
|
+
}>;
|
|
31
|
+
time?: Array<{ utc_offset: string }>;
|
|
32
|
+
response_length?: "short" | "medium" | "long";
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const searchQuerySchema = {
|
|
36
|
+
type: "object",
|
|
37
|
+
additionalProperties: false,
|
|
38
|
+
required: ["q"],
|
|
39
|
+
properties: {
|
|
40
|
+
q: { type: "string", minLength: 1, description: "Search query." },
|
|
41
|
+
recency: { type: "integer", minimum: 0, description: "Restrict results to this many recent days." },
|
|
42
|
+
domains: { type: "array", items: { type: "string", minLength: 1 }, description: "Restrict results to these domains." },
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export const webSearchToolSchema = {
|
|
47
|
+
type: "object",
|
|
48
|
+
additionalProperties: false,
|
|
49
|
+
properties: {
|
|
50
|
+
search_query: {
|
|
51
|
+
type: "array",
|
|
52
|
+
maxItems: 4,
|
|
53
|
+
items: searchQuerySchema,
|
|
54
|
+
description: "Query the internet search engine.",
|
|
55
|
+
},
|
|
56
|
+
image_query: {
|
|
57
|
+
type: "array",
|
|
58
|
+
maxItems: 2,
|
|
59
|
+
items: searchQuerySchema,
|
|
60
|
+
description: "Query the image search engine.",
|
|
61
|
+
},
|
|
62
|
+
open: {
|
|
63
|
+
type: "array",
|
|
64
|
+
items: {
|
|
65
|
+
type: "object",
|
|
66
|
+
additionalProperties: false,
|
|
67
|
+
required: ["ref_id"],
|
|
68
|
+
properties: {
|
|
69
|
+
ref_id: { type: "string" },
|
|
70
|
+
lineno: { type: "integer", minimum: 0 },
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
click: {
|
|
75
|
+
type: "array",
|
|
76
|
+
items: {
|
|
77
|
+
type: "object",
|
|
78
|
+
additionalProperties: false,
|
|
79
|
+
required: ["ref_id", "id"],
|
|
80
|
+
properties: {
|
|
81
|
+
ref_id: { type: "string" },
|
|
82
|
+
id: { type: "integer", minimum: 0 },
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
find: {
|
|
87
|
+
type: "array",
|
|
88
|
+
items: {
|
|
89
|
+
type: "object",
|
|
90
|
+
additionalProperties: false,
|
|
91
|
+
required: ["ref_id", "pattern"],
|
|
92
|
+
properties: {
|
|
93
|
+
ref_id: { type: "string" },
|
|
94
|
+
pattern: { type: "string" },
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
screenshot: {
|
|
99
|
+
type: "array",
|
|
100
|
+
items: {
|
|
101
|
+
type: "object",
|
|
102
|
+
additionalProperties: false,
|
|
103
|
+
required: ["ref_id", "pageno"],
|
|
104
|
+
properties: {
|
|
105
|
+
ref_id: { type: "string" },
|
|
106
|
+
pageno: { type: "integer", minimum: 0 },
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
finance: {
|
|
111
|
+
type: "array",
|
|
112
|
+
items: {
|
|
113
|
+
type: "object",
|
|
114
|
+
additionalProperties: false,
|
|
115
|
+
required: ["ticker", "type"],
|
|
116
|
+
properties: {
|
|
117
|
+
ticker: { type: "string" },
|
|
118
|
+
type: { type: "string", enum: ["equity", "fund", "crypto", "index"] },
|
|
119
|
+
market: { type: "string" },
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
weather: {
|
|
124
|
+
type: "array",
|
|
125
|
+
items: {
|
|
126
|
+
type: "object",
|
|
127
|
+
additionalProperties: false,
|
|
128
|
+
required: ["location"],
|
|
129
|
+
properties: {
|
|
130
|
+
location: { type: "string" },
|
|
131
|
+
start: { type: "string" },
|
|
132
|
+
duration: { type: "integer", minimum: 1 },
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
sports: {
|
|
137
|
+
type: "array",
|
|
138
|
+
items: {
|
|
139
|
+
type: "object",
|
|
140
|
+
additionalProperties: false,
|
|
141
|
+
required: ["fn", "league"],
|
|
142
|
+
properties: {
|
|
143
|
+
tool: { type: "string", enum: ["sports"] },
|
|
144
|
+
fn: { type: "string", enum: ["schedule", "standings"] },
|
|
145
|
+
league: { type: "string", enum: ["nba", "wnba", "nfl", "nhl", "mlb", "epl", "ncaamb", "ncaawb", "ipl"] },
|
|
146
|
+
team: { type: "string" },
|
|
147
|
+
opponent: { type: "string" },
|
|
148
|
+
date_from: { type: "string" },
|
|
149
|
+
date_to: { type: "string" },
|
|
150
|
+
num_games: { type: "integer", minimum: 1 },
|
|
151
|
+
locale: { type: "string" },
|
|
152
|
+
},
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
time: {
|
|
156
|
+
type: "array",
|
|
157
|
+
items: {
|
|
158
|
+
type: "object",
|
|
159
|
+
additionalProperties: false,
|
|
160
|
+
required: ["utc_offset"],
|
|
161
|
+
properties: {
|
|
162
|
+
utc_offset: { type: "string" },
|
|
163
|
+
},
|
|
164
|
+
},
|
|
165
|
+
},
|
|
166
|
+
response_length: {
|
|
167
|
+
type: "string",
|
|
168
|
+
enum: ["short", "medium", "long"],
|
|
169
|
+
},
|
|
170
|
+
},
|
|
171
|
+
};
|