@agimon-ai/po-web-search 0.0.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.
@@ -0,0 +1,337 @@
1
+ import { Type } from "typebox";
2
+ //#region src/constants/webRun.ts
3
+ const WEB_RUN_NAME = "web_run";
4
+ const WEB_RUN_DESCRIPTION = `Tool for accessing the internet.
5
+
6
+
7
+ ---
8
+
9
+ ## Examples of different commands available in this tool
10
+
11
+ Examples of different commands available in this tool:
12
+ * \`search_query\`: {"search_query": [{"q": "What is the capital of France?"}, {"q": "What is the capital of belgium?"}]}. Searches the internet for a given query (and optionally with a domain or recency filter)
13
+ * \`image_query\`: {"image_query":[{"q": "waterfalls"}]}.
14
+ * \`open\`: {"open": [{"ref_id": "turn0search0"}, {"ref_id": "https://www.openai.com", "lineno": 120}]}
15
+ * \`click\`: {"click": [{"ref_id": "turn0fetch3", "id": 17}]}
16
+ * \`find\`: {"find": [{"ref_id": "turn0fetch3", "pattern": "Annie Case"}]}
17
+ * \`screenshot\`: {"screenshot": [{"ref_id": "turn1view0", "pageno": 0}, {"ref_id": "turn1view0", "pageno": 3}]}
18
+ * \`finance\`: {"finance":[{"ticker":"AMD","type":"equity","market":"USA"}]}, {"finance":[{"ticker":"BTC","type":"crypto","market":""}]}
19
+ * \`weather\`: {"weather":[{"location":"San Francisco, CA"}]}
20
+ * \`sports\`: {"sports":[{"fn":"standings","league":"nfl"}, {"fn":"schedule","league":"nba","team":"GSW","date_from":"2025-02-24"}]}
21
+ * \`time\`: {"time":[{"utc_offset":"+03:00"}]}
22
+
23
+ ---
24
+
25
+ ## Usage hints
26
+ To use this tool efficiently:
27
+ * Use multiple commands and queries in one call to get more results faster; e.g. {"search_query": [{"q": "bitcoin news"}], "finance":[{"ticker":"BTC","type":"crypto","market":""}], "find": [{"ref_id": "turn0search0", "pattern": "Annie Case"}, {"ref_id": "turn0search1", "pattern": "John Smith"}]}
28
+ * Use "response_length" to control the number of results returned by this tool, omit it if you intend to pass "short" in
29
+ * Only write required parameters; do not write empty lists or nulls where they could be omitted.
30
+ * \`search_query\` must have length at most 4 in each call. If it has length > 3, response_length must be medium or long
31
+ * If you find yourself in a situation where you accidentally call the \`web_run\` tool, it's best just to send an empty query: {"search_query": [{"q": ""}]}.
32
+
33
+ ---
34
+
35
+ ## Decision boundary
36
+
37
+ If the user makes an explicit request to search the internet, find latest information, look up, etc (or to not do so), you must obey their request.
38
+ When you make an assumption, always consider whether it is temporally stable; i.e. whether there's even a small (>10%) chance it has changed. If it is unstable, you must verify with browsing the internet for verification.
39
+
40
+ <situations_where_you_must_browse_the_internet>
41
+ Below is a list of scenarios where browsing the internet MUST be used. PAY CLOSE ATTENTION: you MUST browse the internet in these cases. If you're unsure or on the fence, you MUST bias towards browsing the internet.
42
+ - The information could have changed recently: for example news; prices; laws; schedules; product specs; sports scores; economic indicators; political/public/company figures (e.g. the question relates to 'the president of country A' or 'the CEO of company B', which might change over time); rules; regulations; standards; software libraries that could be updated; exchange rates; recommendations (i.e., recommendations about various topics or things might be informed by what currently exists / is popular / is safe / is unsafe / is in the zeitgeist / etc.); and many many many more categories -- again, if you're on the fence, you MUST browse the internet!
43
+ - For news queries, prioritize more recent events, ensuring you compare publish dates and the date that the event happened.
44
+ - The user is seeking recommendations that could lead them to spend substantial time or money -- researching products, restaurants, travel plans, etc.
45
+ - The user wants (or would benefit from) direct quotes, links, or precise source attribution.
46
+ - A specific page, paper, dataset, PDF, or site is referenced and you haven't been given its contents.
47
+ - You're unsure about a fact, the topic is niche or emerging, or you suspect there's at least a 10% chance you will incorrectly recall it
48
+ - High-stakes accuracy matters (medical, legal, financial guidance). For these you generally should search by default because this information is highly temporally unstable
49
+ - The user explicitly says to search, browse, verify, or look it up.
50
+ </situations_where_you_must_browse_the_internet>
51
+
52
+ ---
53
+
54
+ ## Citations
55
+
56
+ Results from \`web_run\` include internal reference IDs such as \`turn2search5\`. Use
57
+ those reference IDs only in calls to \`web_run\`; do not expose them in the final
58
+ response.
59
+
60
+ Cite sources in the final response using Markdown links:
61
+
62
+ - Cite a single source as \`[descriptive source title](https://example.com/page)\`.
63
+ - Cite multiple sources with separate Markdown links, for example
64
+ \`[first source](https://example.com/one), [second source](https://example.com/two)\`.
65
+ - Link directly to the page that supports the claim. Do not link to search result
66
+ pages or use bare URLs.
67
+
68
+ Formatting of citations:
69
+
70
+ - Place each citation as near as possible to the claim it supports, normally at
71
+ the end of the sentence or paragraph and after punctuation.
72
+ - Do not place citations inside code fences.
73
+ - Do not put citations on a line by themselves or collect all citations at the
74
+ end of the response.
75
+
76
+ If you browse the internet, cite statements supported by web sources. Each cited
77
+ source must directly support the associated claim. Prefer primary and
78
+ authoritative sources, and use sources from different domains when the response
79
+ benefits from multiple perspectives.
80
+
81
+ ---
82
+
83
+ ## Special cases
84
+ If these conflict with any other instructions, these should take precedence.
85
+
86
+ <special_cases>
87
+ - When the user asks for information about how to use OpenAI products, (ChatGPT, the OpenAI API, etc.), you should check the code in local env and only browse as fallback, when you browse restrict your sources to official OpenAI websites using the domains filter, unless otherwise requested.
88
+ - When using search to answer technical questions, you must only rely on primary sources (research papers, official documentation, etc.)
89
+ - Clearly indicate when you are making an inference from sources.
90
+ </special_cases>
91
+
92
+ ---
93
+
94
+ ## Word limits
95
+ Responses may not excessively quote or draw on a specific source. There are several limits here:
96
+ - **Limit on verbatim quotes:**
97
+ - You may not quote more than 25 words verbatim from any single non-lyrical source, unless the source is reddit.
98
+ - For song lyrics, verbatim quotes must be limited to at most 10 words.
99
+ - Long quotes from reddit are allowed, as long as you indicate that those are direct quotes via a markdown blockquote starting with ">", copy verbatim, and link the source.
100
+ - **Word limits:**
101
+ - Each webpage source in the sources has a word limit label formatted like "[wordlim N]", in which N is the maximum number of words in the whole response that are attributed to that source. If omitted, the word limit is 200 words.
102
+ - Non-contiguous words derived from a given source must be counted to the word limit.
103
+ - The summarization limit N is a maximum for each source.
104
+ - When using multiple sources, their summarization limits add together. However, each article used must be relevant to the response.
105
+ - **Copyright compliance:**
106
+ - You must avoid providing full articles, long verbatim passages, or extensive direct quotes due to copyright concerns.
107
+ - If the user asked for a verbatim quote, the response should provide a short compliant excerpt and then answer with paraphrases and summaries.
108
+ - Again, this limit does not apply to reddit content, as long as it's appropriately indicated that those are direct quotes and you link to the source.
109
+
110
+ ---
111
+
112
+ ## Errors
113
+
114
+ If \`web_run\` reports a sign-in, permission, or quota error, tell the user and do not call it again this turn.
115
+ `;
116
+ /** Session environment variable selecting `cached`, `indexed` or `live` access. */
117
+ const WEB_SEARCH_ACCESS_ENV = "PO_WEB_SEARCH_ACCESS";
118
+ const WEB_SEARCH_LOGIN_MESSAGE = "Web search needs a ChatGPT sign-in. Sign in to ChatGPT for Po features under Settings > Providers, or with /login.";
119
+ //#endregion
120
+ //#region src/schemas/webRun.ts
121
+ const MAX_TEXT = 2048;
122
+ const MAX_OPERATIONS = 10;
123
+ const text = (description) => Type.String({
124
+ maxLength: MAX_TEXT,
125
+ description
126
+ });
127
+ const count = (description) => Type.Integer({
128
+ minimum: 0,
129
+ description
130
+ });
131
+ const closed = (properties) => Type.Object(properties, { additionalProperties: false });
132
+ const operations = (item, description, maxItems = MAX_OPERATIONS) => Type.Optional(Type.Array(item, {
133
+ maxItems,
134
+ description
135
+ }));
136
+ const oneOf = (values, description) => Type.Union(values.map((value) => Type.Literal(value)), { description });
137
+ const SearchQuery = closed({
138
+ q: text("Search query."),
139
+ recency: Type.Optional(count("Whether to filter by recency, as a number of recent days.")),
140
+ domains: Type.Optional(Type.Array(Type.String({ maxLength: 253 }), {
141
+ maxItems: 20,
142
+ description: "Whether to filter by a specific list of domains."
143
+ }))
144
+ });
145
+ const WebRunParamsSchema = Type.Object({
146
+ search_query: operations(SearchQuery, "Query the internet search engine for a given list of queries.", 4),
147
+ image_query: operations(SearchQuery, "Query the image search engine for a given list of queries.", 4),
148
+ open: operations(closed({
149
+ ref_id: text("Reference id or URL to open."),
150
+ lineno: Type.Optional(count("Line number to position the page at."))
151
+ }), "Open pages by reference id or URL."),
152
+ click: operations(closed({
153
+ ref_id: text("Reference id containing the numbered link."),
154
+ id: count("Numbered link id to open.")
155
+ }), "Open links from previously opened pages."),
156
+ find: operations(closed({
157
+ ref_id: text("Reference id or URL to search within."),
158
+ pattern: text("Text pattern to find.")
159
+ }), "Find text patterns in pages."),
160
+ screenshot: operations(closed({
161
+ ref_id: text("Reference id or URL to screenshot."),
162
+ pageno: count("Zero-indexed PDF page number.")
163
+ }), "Take screenshots of PDF pages."),
164
+ finance: operations(closed({
165
+ ticker: text("Ticker symbol to look up."),
166
+ type: oneOf([
167
+ "equity",
168
+ "fund",
169
+ "crypto",
170
+ "index"
171
+ ], "Asset type to look up."),
172
+ market: Type.Optional(text("ISO 3166-1 alpha-3 country code, \"OTC\", or \"\" for cryptocurrency."))
173
+ }), "Look up prices for the given stock symbols."),
174
+ weather: operations(closed({
175
+ location: text("Location in \"Country, Area, City\" format."),
176
+ start: Type.Optional(text("Start date in YYYY-MM-DD format. Defaults to today.")),
177
+ duration: Type.Optional(count("Number of days to return. Defaults to 7."))
178
+ }), "Look up weather forecasts."),
179
+ sports: operations(closed({
180
+ tool: Type.Optional(oneOf(["sports"], "Tool name for sports requests.")),
181
+ fn: oneOf(["schedule", "standings"], "Sports function to call."),
182
+ league: oneOf([
183
+ "nba",
184
+ "wnba",
185
+ "nfl",
186
+ "nhl",
187
+ "mlb",
188
+ "epl",
189
+ "ncaamb",
190
+ "ncaawb",
191
+ "ipl"
192
+ ], "League to look up."),
193
+ team: Type.Optional(text("Team to look up, using the common 3 or 4 letter alias used in broadcasts.")),
194
+ opponent: Type.Optional(text("Opponent to use with `team` when narrowing the lookup.")),
195
+ date_from: Type.Optional(text("Start date in YYYY-MM-DD format.")),
196
+ date_to: Type.Optional(text("End date in YYYY-MM-DD format.")),
197
+ num_games: Type.Optional(count("Number of games to return.")),
198
+ locale: Type.Optional(text("Locale for the lookup."))
199
+ }), "Look up sports schedules and standings."),
200
+ time: operations(closed({ utc_offset: text("UTC offset formatted like \"+03:00\".") }), "Get time for the given UTC offsets."),
201
+ response_length: Type.Optional(oneOf([
202
+ "short",
203
+ "medium",
204
+ "long"
205
+ ], "Set the length of the response to be returned."))
206
+ }, {
207
+ additionalProperties: false,
208
+ minProperties: 1
209
+ });
210
+ //#endregion
211
+ //#region src/services/recentConversation/index.ts
212
+ /** About Codex's 1,000-token assistant budget. */
213
+ const ASSISTANT_CHARACTERS = 4e3;
214
+ const USER_CHARACTERS = 8e3;
215
+ /** The host accepts at most 16 conversation items: two user messages plus this many assistant messages. */
216
+ const ASSISTANT_MESSAGES = 14;
217
+ function textParts(content) {
218
+ if (typeof content === "string") return [content];
219
+ if (!Array.isArray(content)) return [];
220
+ return content.flatMap((part) => typeof part === "object" && part !== null && part.type === "text" && typeof part.text === "string" ? [part.text] : []);
221
+ }
222
+ /**
223
+ * The previous user text message, the assistant text that followed it (shared budget, oldest first),
224
+ * and the latest user text message. Tool traffic, thinking, images and custom messages are never included.
225
+ */
226
+ function recentConversation(messages) {
227
+ const visible = [];
228
+ for (const message of messages) {
229
+ if (typeof message !== "object" || message === null) continue;
230
+ const { role, content } = message;
231
+ if (role !== "user" && role !== "assistant") continue;
232
+ const text = textParts(content).join("\n");
233
+ if (text.trim()) visible.push({
234
+ role,
235
+ text
236
+ });
237
+ }
238
+ const latest = visible.findLastIndex((message) => message.role === "user");
239
+ if (latest < 0) return void 0;
240
+ const userIndexes = visible.flatMap((message, index) => message.role === "user" && index <= latest ? [index] : []);
241
+ const tail = visible.slice(userIndexes.at(-2) ?? latest, latest + 1);
242
+ let budget = ASSISTANT_CHARACTERS;
243
+ let assistants = 0;
244
+ return tail.flatMap((message) => {
245
+ if (message.role === "user") return [{
246
+ role: "user",
247
+ text: message.text.slice(0, USER_CHARACTERS)
248
+ }];
249
+ if (budget === 0 || assistants === ASSISTANT_MESSAGES) return [];
250
+ const text = message.text.slice(0, budget);
251
+ budget -= text.length;
252
+ assistants += 1;
253
+ return [{
254
+ role: "assistant",
255
+ text
256
+ }];
257
+ });
258
+ }
259
+ //#endregion
260
+ //#region src/services/webRunTool/index.ts
261
+ const ACCESS_MODES = [
262
+ "cached",
263
+ "indexed",
264
+ "live"
265
+ ];
266
+ const MAX_SOURCES = 20;
267
+ const MAX_URL = 2048;
268
+ const MAX_TITLE = 300;
269
+ function accessMode(environment) {
270
+ const value = environment["PO_WEB_SEARCH_ACCESS"]?.trim() || "cached";
271
+ const access = ACCESS_MODES.find((mode) => mode === value);
272
+ if (!access) throw new Error(`${WEB_SEARCH_ACCESS_ENV} must be cached, indexed, or live.`);
273
+ return access;
274
+ }
275
+ /** Only the excerpt sent to the same OpenAI account the chat already uses; other providers send commands only. */
276
+ async function conversationFor(context) {
277
+ if (context.model?.provider !== "openai-codex" || !context.session.contextMessages) return void 0;
278
+ try {
279
+ return recentConversation(await context.session.contextMessages());
280
+ } catch {
281
+ return;
282
+ }
283
+ }
284
+ function sources(results) {
285
+ const safe = [];
286
+ for (const result of results ?? []) {
287
+ if (safe.length === MAX_SOURCES) break;
288
+ if (typeof result.url !== "string" || result.url.length > MAX_URL || !URL.canParse(result.url)) continue;
289
+ const url = new URL(result.url);
290
+ if (url.protocol !== "https:" && url.protocol !== "http:") continue;
291
+ const title = typeof result.title === "string" ? result.title.trim().slice(0, MAX_TITLE) : "";
292
+ safe.push(title ? {
293
+ url: url.href,
294
+ title
295
+ } : { url: url.href });
296
+ }
297
+ return safe;
298
+ }
299
+ function createWebRunTool() {
300
+ return {
301
+ name: WEB_RUN_NAME,
302
+ label: WEB_RUN_NAME,
303
+ description: WEB_RUN_DESCRIPTION,
304
+ parameters: WebRunParamsSchema,
305
+ executionMode: "parallel",
306
+ async execute(_toolCallId, params, signal, _onUpdate, context) {
307
+ signal?.throwIfAborted();
308
+ const capability = context.webSearch;
309
+ if (!capability?.available()) throw new Error(WEB_SEARCH_LOGIN_MESSAGE);
310
+ const access = accessMode(context.environment);
311
+ const conversation = await conversationFor(context);
312
+ signal?.throwIfAborted();
313
+ const result = await capability.search({
314
+ commands: params,
315
+ access,
316
+ ...conversation ? { conversation } : {},
317
+ ...signal ? { signal } : {}
318
+ });
319
+ const details = {
320
+ access,
321
+ sharedConversation: conversation !== void 0,
322
+ sources: sources(result.results)
323
+ };
324
+ return {
325
+ content: [{
326
+ type: "text",
327
+ text: result.output
328
+ }],
329
+ details
330
+ };
331
+ }
332
+ };
333
+ }
334
+ //#endregion
335
+ export { createWebRunTool as t };
336
+
337
+ //# sourceMappingURL=webRunTool-BkYexFJr.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webRunTool-BkYexFJr.mjs","names":[],"sources":["../src/constants/webRun.ts","../src/schemas/webRun.ts","../src/services/recentConversation/index.ts","../src/services/webRunTool/index.ts"],"sourcesContent":["// Copyright 2025 OpenAI\n// SPDX-License-Identifier: Apache-2.0\n// Modified for Po from codex-rs ext/web-search/web_run_description.md: `web.run` renamed to `web_run`, error guidance added.\n\nexport const WEB_RUN_NAME = 'web_run';\nexport const WEB_RUN_DESCRIPTION = `Tool for accessing the internet.\n\n\n---\n\n## Examples of different commands available in this tool\n\nExamples of different commands available in this tool:\n* \\`search_query\\`: {\"search_query\": [{\"q\": \"What is the capital of France?\"}, {\"q\": \"What is the capital of belgium?\"}]}. Searches the internet for a given query (and optionally with a domain or recency filter)\n* \\`image_query\\`: {\"image_query\":[{\"q\": \"waterfalls\"}]}.\n* \\`open\\`: {\"open\": [{\"ref_id\": \"turn0search0\"}, {\"ref_id\": \"https://www.openai.com\", \"lineno\": 120}]}\n* \\`click\\`: {\"click\": [{\"ref_id\": \"turn0fetch3\", \"id\": 17}]}\n* \\`find\\`: {\"find\": [{\"ref_id\": \"turn0fetch3\", \"pattern\": \"Annie Case\"}]}\n* \\`screenshot\\`: {\"screenshot\": [{\"ref_id\": \"turn1view0\", \"pageno\": 0}, {\"ref_id\": \"turn1view0\", \"pageno\": 3}]}\n* \\`finance\\`: {\"finance\":[{\"ticker\":\"AMD\",\"type\":\"equity\",\"market\":\"USA\"}]}, {\"finance\":[{\"ticker\":\"BTC\",\"type\":\"crypto\",\"market\":\"\"}]}\n* \\`weather\\`: {\"weather\":[{\"location\":\"San Francisco, CA\"}]}\n* \\`sports\\`: {\"sports\":[{\"fn\":\"standings\",\"league\":\"nfl\"}, {\"fn\":\"schedule\",\"league\":\"nba\",\"team\":\"GSW\",\"date_from\":\"2025-02-24\"}]}\n* \\`time\\`: {\"time\":[{\"utc_offset\":\"+03:00\"}]}\n\n---\n\n## Usage hints\nTo use this tool efficiently:\n* Use multiple commands and queries in one call to get more results faster; e.g. {\"search_query\": [{\"q\": \"bitcoin news\"}], \"finance\":[{\"ticker\":\"BTC\",\"type\":\"crypto\",\"market\":\"\"}], \"find\": [{\"ref_id\": \"turn0search0\", \"pattern\": \"Annie Case\"}, {\"ref_id\": \"turn0search1\", \"pattern\": \"John Smith\"}]}\n* Use \"response_length\" to control the number of results returned by this tool, omit it if you intend to pass \"short\" in\n* Only write required parameters; do not write empty lists or nulls where they could be omitted.\n* \\`search_query\\` must have length at most 4 in each call. If it has length > 3, response_length must be medium or long\n* If you find yourself in a situation where you accidentally call the \\`web_run\\` tool, it's best just to send an empty query: {\"search_query\": [{\"q\": \"\"}]}.\n\n---\n\n## Decision boundary\n\nIf the user makes an explicit request to search the internet, find latest information, look up, etc (or to not do so), you must obey their request.\nWhen you make an assumption, always consider whether it is temporally stable; i.e. whether there's even a small (>10%) chance it has changed. If it is unstable, you must verify with browsing the internet for verification.\n\n<situations_where_you_must_browse_the_internet>\nBelow is a list of scenarios where browsing the internet MUST be used. PAY CLOSE ATTENTION: you MUST browse the internet in these cases. If you're unsure or on the fence, you MUST bias towards browsing the internet.\n- The information could have changed recently: for example news; prices; laws; schedules; product specs; sports scores; economic indicators; political/public/company figures (e.g. the question relates to 'the president of country A' or 'the CEO of company B', which might change over time); rules; regulations; standards; software libraries that could be updated; exchange rates; recommendations (i.e., recommendations about various topics or things might be informed by what currently exists / is popular / is safe / is unsafe / is in the zeitgeist / etc.); and many many many more categories -- again, if you're on the fence, you MUST browse the internet!\n - For news queries, prioritize more recent events, ensuring you compare publish dates and the date that the event happened.\n- The user is seeking recommendations that could lead them to spend substantial time or money -- researching products, restaurants, travel plans, etc.\n- The user wants (or would benefit from) direct quotes, links, or precise source attribution.\n- A specific page, paper, dataset, PDF, or site is referenced and you haven't been given its contents.\n- You're unsure about a fact, the topic is niche or emerging, or you suspect there's at least a 10% chance you will incorrectly recall it\n- High-stakes accuracy matters (medical, legal, financial guidance). For these you generally should search by default because this information is highly temporally unstable\n- The user explicitly says to search, browse, verify, or look it up.\n</situations_where_you_must_browse_the_internet>\n\n---\n\n## Citations\n\nResults from \\`web_run\\` include internal reference IDs such as \\`turn2search5\\`. Use\nthose reference IDs only in calls to \\`web_run\\`; do not expose them in the final\nresponse.\n\nCite sources in the final response using Markdown links:\n\n- Cite a single source as \\`[descriptive source title](https://example.com/page)\\`.\n- Cite multiple sources with separate Markdown links, for example\n \\`[first source](https://example.com/one), [second source](https://example.com/two)\\`.\n- Link directly to the page that supports the claim. Do not link to search result\n pages or use bare URLs.\n\nFormatting of citations:\n\n- Place each citation as near as possible to the claim it supports, normally at\n the end of the sentence or paragraph and after punctuation.\n- Do not place citations inside code fences.\n- Do not put citations on a line by themselves or collect all citations at the\n end of the response.\n\nIf you browse the internet, cite statements supported by web sources. Each cited\nsource must directly support the associated claim. Prefer primary and\nauthoritative sources, and use sources from different domains when the response\nbenefits from multiple perspectives.\n\n---\n\n## Special cases\nIf these conflict with any other instructions, these should take precedence.\n\n<special_cases>\n- When the user asks for information about how to use OpenAI products, (ChatGPT, the OpenAI API, etc.), you should check the code in local env and only browse as fallback, when you browse restrict your sources to official OpenAI websites using the domains filter, unless otherwise requested.\n- When using search to answer technical questions, you must only rely on primary sources (research papers, official documentation, etc.)\n- Clearly indicate when you are making an inference from sources.\n</special_cases>\n\n---\n\n## Word limits\nResponses may not excessively quote or draw on a specific source. There are several limits here:\n- **Limit on verbatim quotes:**\n - You may not quote more than 25 words verbatim from any single non-lyrical source, unless the source is reddit.\n - For song lyrics, verbatim quotes must be limited to at most 10 words.\n - Long quotes from reddit are allowed, as long as you indicate that those are direct quotes via a markdown blockquote starting with \">\", copy verbatim, and link the source.\n- **Word limits:**\n - Each webpage source in the sources has a word limit label formatted like \"[wordlim N]\", in which N is the maximum number of words in the whole response that are attributed to that source. If omitted, the word limit is 200 words.\n - Non-contiguous words derived from a given source must be counted to the word limit.\n - The summarization limit N is a maximum for each source.\n - When using multiple sources, their summarization limits add together. However, each article used must be relevant to the response.\n- **Copyright compliance:**\n - You must avoid providing full articles, long verbatim passages, or extensive direct quotes due to copyright concerns.\n - If the user asked for a verbatim quote, the response should provide a short compliant excerpt and then answer with paraphrases and summaries.\n - Again, this limit does not apply to reddit content, as long as it's appropriately indicated that those are direct quotes and you link to the source.\n\n---\n\n## Errors\n\nIf \\`web_run\\` reports a sign-in, permission, or quota error, tell the user and do not call it again this turn.\n`;\n/** Session environment variable selecting `cached`, `indexed` or `live` access. */\nexport const WEB_SEARCH_ACCESS_ENV = 'PO_WEB_SEARCH_ACCESS';\nexport const DEFAULT_WEB_SEARCH_ACCESS = 'cached';\nexport const WEB_SEARCH_LOGIN_MESSAGE =\n 'Web search needs a ChatGPT sign-in. Sign in to ChatGPT for Po features under Settings > Providers, or with /login.';\n","// Copyright 2025 OpenAI\n// SPDX-License-Identifier: Apache-2.0\n// Modified for Po from codex-rs codex-api/src/search.rs SearchCommands: closed objects and size bounds added.\n\nimport { type Static, type TSchema, Type } from 'typebox';\n\nconst MAX_TEXT = 2048;\nconst MAX_OPERATIONS = 10;\n\nconst text = (description: string) => Type.String({ maxLength: MAX_TEXT, description });\nconst count = (description: string) => Type.Integer({ minimum: 0, description });\nconst closed = <T extends Record<string, TSchema>>(properties: T) =>\n Type.Object(properties, { additionalProperties: false });\nconst operations = <T extends TSchema>(item: T, description: string, maxItems = MAX_OPERATIONS) =>\n Type.Optional(Type.Array(item, { maxItems, description }));\nconst oneOf = <const T extends readonly string[]>(values: T, description: string) =>\n Type.Union(\n values.map((value) => Type.Literal(value)),\n { description },\n );\n\nconst SearchQuery = closed({\n q: text('Search query.'),\n recency: Type.Optional(count('Whether to filter by recency, as a number of recent days.')),\n domains: Type.Optional(\n Type.Array(Type.String({ maxLength: 253 }), {\n maxItems: 20,\n description: 'Whether to filter by a specific list of domains.',\n }),\n ),\n});\n\nexport const WebRunParamsSchema = Type.Object(\n {\n search_query: operations(SearchQuery, 'Query the internet search engine for a given list of queries.', 4),\n image_query: operations(SearchQuery, 'Query the image search engine for a given list of queries.', 4),\n open: operations(\n closed({\n ref_id: text('Reference id or URL to open.'),\n lineno: Type.Optional(count('Line number to position the page at.')),\n }),\n 'Open pages by reference id or URL.',\n ),\n click: operations(\n closed({\n ref_id: text('Reference id containing the numbered link.'),\n id: count('Numbered link id to open.'),\n }),\n 'Open links from previously opened pages.',\n ),\n find: operations(\n closed({ ref_id: text('Reference id or URL to search within.'), pattern: text('Text pattern to find.') }),\n 'Find text patterns in pages.',\n ),\n screenshot: operations(\n closed({ ref_id: text('Reference id or URL to screenshot.'), pageno: count('Zero-indexed PDF page number.') }),\n 'Take screenshots of PDF pages.',\n ),\n finance: operations(\n closed({\n ticker: text('Ticker symbol to look up.'),\n type: oneOf(['equity', 'fund', 'crypto', 'index'], 'Asset type to look up.'),\n market: Type.Optional(text('ISO 3166-1 alpha-3 country code, \"OTC\", or \"\" for cryptocurrency.')),\n }),\n 'Look up prices for the given stock symbols.',\n ),\n weather: operations(\n closed({\n location: text('Location in \"Country, Area, City\" format.'),\n start: Type.Optional(text('Start date in YYYY-MM-DD format. Defaults to today.')),\n duration: Type.Optional(count('Number of days to return. Defaults to 7.')),\n }),\n 'Look up weather forecasts.',\n ),\n sports: operations(\n closed({\n tool: Type.Optional(oneOf(['sports'], 'Tool name for sports requests.')),\n fn: oneOf(['schedule', 'standings'], 'Sports function to call.'),\n league: oneOf(['nba', 'wnba', 'nfl', 'nhl', 'mlb', 'epl', 'ncaamb', 'ncaawb', 'ipl'], 'League to look up.'),\n team: Type.Optional(text('Team to look up, using the common 3 or 4 letter alias used in broadcasts.')),\n opponent: Type.Optional(text('Opponent to use with `team` when narrowing the lookup.')),\n date_from: Type.Optional(text('Start date in YYYY-MM-DD format.')),\n date_to: Type.Optional(text('End date in YYYY-MM-DD format.')),\n num_games: Type.Optional(count('Number of games to return.')),\n locale: Type.Optional(text('Locale for the lookup.')),\n }),\n 'Look up sports schedules and standings.',\n ),\n time: operations(\n closed({ utc_offset: text('UTC offset formatted like \"+03:00\".') }),\n 'Get time for the given UTC offsets.',\n ),\n response_length: Type.Optional(\n oneOf(['short', 'medium', 'long'], 'Set the length of the response to be returned.'),\n ),\n },\n { additionalProperties: false, minProperties: 1 },\n);\n\nexport type WebRunParams = Static<typeof WebRunParamsSchema>;\n","// Copyright 2025 OpenAI\n// SPDX-License-Identifier: Apache-2.0\n// Modified for Po from codex-rs ext/web-search/src/history.rs and tools/src/response_history.rs:\n// reads Pi messages, drops custom (injected) messages, and budgets characters instead of tokens.\n\n/** About Codex's 1,000-token assistant budget. */\nconst ASSISTANT_CHARACTERS = 4000;\nconst USER_CHARACTERS = 8000;\nconst USER_TURNS = 2;\n/** The host accepts at most 16 conversation items: two user messages plus this many assistant messages. */\nconst ASSISTANT_MESSAGES = 14;\n\ninterface ConversationMessage {\n role: 'user' | 'assistant';\n text: string;\n}\n\nfunction textParts(content: unknown): string[] {\n if (typeof content === 'string') return [content];\n if (!Array.isArray(content)) return [];\n return content.flatMap((part: unknown) =>\n typeof part === 'object' &&\n part !== null &&\n (part as { type?: unknown }).type === 'text' &&\n typeof (part as { text?: unknown }).text === 'string'\n ? [(part as { text: string }).text]\n : [],\n );\n}\n\n/**\n * The previous user text message, the assistant text that followed it (shared budget, oldest first),\n * and the latest user text message. Tool traffic, thinking, images and custom messages are never included.\n */\nexport function recentConversation(messages: readonly unknown[]): ConversationMessage[] | undefined {\n const visible: ConversationMessage[] = [];\n for (const message of messages) {\n if (typeof message !== 'object' || message === null) continue;\n const { role, content } = message as { role?: unknown; content?: unknown };\n if (role !== 'user' && role !== 'assistant') continue;\n const text = textParts(content).join('\\n');\n if (text.trim()) visible.push({ role, text });\n }\n const latest = visible.findLastIndex((message) => message.role === 'user');\n if (latest < 0) return undefined;\n const userIndexes = visible.flatMap((message, index) => (message.role === 'user' && index <= latest ? [index] : []));\n const tail = visible.slice(userIndexes.at(-USER_TURNS) ?? latest, latest + 1);\n let budget = ASSISTANT_CHARACTERS;\n let assistants = 0;\n return tail.flatMap<ConversationMessage>((message) => {\n if (message.role === 'user') return [{ role: 'user' as const, text: message.text.slice(0, USER_CHARACTERS) }];\n if (budget === 0 || assistants === ASSISTANT_MESSAGES) return [];\n const text = message.text.slice(0, budget);\n budget -= text.length;\n assistants += 1;\n return [{ role: 'assistant' as const, text }];\n });\n}\n","import type { DoomHeadlessExecutionContext, DoomHeadlessTool } from '@agimon-ai/po-core/headless';\n\nimport {\n DEFAULT_WEB_SEARCH_ACCESS,\n WEB_RUN_DESCRIPTION,\n WEB_RUN_NAME,\n WEB_SEARCH_ACCESS_ENV,\n WEB_SEARCH_LOGIN_MESSAGE,\n} from '../../constants/webRun';\nimport { WebRunParamsSchema } from '../../schemas/webRun';\nimport { recentConversation } from '../recentConversation';\nimport type { WebRunDetails, WebRunSource, WebSearchAccess } from './type';\n\nconst ACCESS_MODES: readonly WebSearchAccess[] = ['cached', 'indexed', 'live'];\nconst MAX_SOURCES = 20;\nconst MAX_URL = 2048;\nconst MAX_TITLE = 300;\n\nfunction accessMode(environment: Readonly<Record<string, string | undefined>>): WebSearchAccess {\n const value = environment[WEB_SEARCH_ACCESS_ENV]?.trim() || DEFAULT_WEB_SEARCH_ACCESS;\n const access = ACCESS_MODES.find((mode) => mode === value);\n if (!access) throw new Error(`${WEB_SEARCH_ACCESS_ENV} must be cached, indexed, or live.`);\n return access;\n}\n\n/** Only the excerpt sent to the same OpenAI account the chat already uses; other providers send commands only. */\nasync function conversationFor(context: DoomHeadlessExecutionContext) {\n if (context.model?.provider !== 'openai-codex' || !context.session.contextMessages) return undefined;\n try {\n return recentConversation(await context.session.contextMessages());\n } catch {\n // Documented fallback: search still runs, without conversation context, when history is unreadable.\n return undefined;\n }\n}\n\nfunction sources(results: readonly Record<string, unknown>[] | undefined): WebRunSource[] {\n const safe: WebRunSource[] = [];\n for (const result of results ?? []) {\n if (safe.length === MAX_SOURCES) break;\n if (typeof result.url !== 'string' || result.url.length > MAX_URL || !URL.canParse(result.url)) continue;\n const url = new URL(result.url);\n if (url.protocol !== 'https:' && url.protocol !== 'http:') continue;\n const title = typeof result.title === 'string' ? result.title.trim().slice(0, MAX_TITLE) : '';\n safe.push(title ? { url: url.href, title } : { url: url.href });\n }\n return safe;\n}\n\nexport function createWebRunTool(): DoomHeadlessTool<typeof WebRunParamsSchema> {\n return {\n name: WEB_RUN_NAME,\n label: WEB_RUN_NAME,\n description: WEB_RUN_DESCRIPTION,\n parameters: WebRunParamsSchema,\n executionMode: 'parallel',\n async execute(_toolCallId, params, signal, _onUpdate, context) {\n signal?.throwIfAborted();\n const capability = context.webSearch;\n if (!capability?.available()) throw new Error(WEB_SEARCH_LOGIN_MESSAGE);\n const access = accessMode(context.environment);\n const conversation = await conversationFor(context);\n signal?.throwIfAborted();\n const result = await capability.search({\n commands: params,\n access,\n ...(conversation ? { conversation } : {}),\n ...(signal ? { signal } : {}),\n });\n const details: WebRunDetails = {\n access,\n sharedConversation: conversation !== undefined,\n sources: sources(result.results),\n };\n return { content: [{ type: 'text', text: result.output }], details };\n },\n };\n}\n"],"mappings":"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"}