@ykurilov/agent-chat-web-tools 0.0.0-stage → 0.56.0

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 @@
1
+ {"version":3,"sources":["../src/labels.ts"],"sourcesContent":["/** Подписи веб-инструментов в ленте чата. Без кода сервисов: файл безопасно подключать в браузерной сборке. */\nexport const webToolTitles: Record<string, string> = {\n web_search: \"Поиск в интернете\",\n read_page: \"Чтение страницы\",\n page_screenshot: \"Скриншот страницы\",\n};\n\n/** Те же подписи по-английски. */\nexport const englishWebToolTitles: Record<string, string> = {\n web_search: \"Web search\",\n read_page: \"Reading a page\",\n page_screenshot: \"Page screenshot\",\n};\n\n/** Понятные фразы веб-инструментов для простого вида чата (`stepPhrases` у `AgentChat`): «идёт» и «готово». */\nexport const webToolStepPhrases: Record<string, { running: string; done: string }> = {\n web_search: { running: \"Ищу в интернете\", done: \"Поискал в интернете\" },\n read_page: { running: \"Читаю страницу\", done: \"Прочитал страницу\" },\n page_screenshot: { running: \"Делаю снимок страницы\", done: \"Сделал снимок страницы\" },\n};\n\n/** Те же фразы по-английски. */\nexport const englishWebToolStepPhrases: Record<string, { running: string; done: string }> = {\n web_search: { running: \"Searching the web\", done: \"Searched the web\" },\n read_page: { running: \"Reading a page\", done: \"Read a page\" },\n page_screenshot: { running: \"Taking a page screenshot\", done: \"Took a page screenshot\" },\n};\n\ninterface ToolLabels {\n toolRunning(toolName: string): string;\n toolDone(toolName: string): string;\n toolError(toolName: string): string;\n}\n\nfunction titleOf(titles: Record<string, string>, toolName: string): string {\n return Object.hasOwn(titles, toolName) ? (titles[toolName] ?? toolName) : toolName;\n}\n\n/** Надписи чата, в которых веб-инструменты названы по-русски (или по словарю `titles`, например `englishWebToolTitles`); остальное не меняется. */\nexport function withWebToolTitles<Labels extends ToolLabels>(labels: Labels, titles: Record<string, string> = webToolTitles): Labels {\n return {\n ...labels,\n toolRunning: (toolName: string) => labels.toolRunning(titleOf(titles, toolName)),\n toolDone: (toolName: string) => labels.toolDone(titleOf(titles, toolName)),\n toolError: (toolName: string) => labels.toolError(titleOf(titles, toolName)),\n };\n}\n"],"mappings":";AACO,IAAM,gBAAwC;AAAA,EACnD,YAAY;AAAA,EACZ,WAAW;AAAA,EACX,iBAAiB;AACnB;AAGO,IAAM,uBAA+C;AAAA,EAC1D,YAAY;AAAA,EACZ,WAAW;AAAA,EACX,iBAAiB;AACnB;AAGO,IAAM,qBAAwE;AAAA,EACnF,YAAY,EAAE,SAAS,oFAAmB,MAAM,2GAAsB;AAAA,EACtE,WAAW,EAAE,SAAS,mFAAkB,MAAM,oGAAoB;AAAA,EAClE,iBAAiB,EAAE,SAAS,wHAAyB,MAAM,6HAAyB;AACtF;AAGO,IAAM,4BAA+E;AAAA,EAC1F,YAAY,EAAE,SAAS,qBAAqB,MAAM,mBAAmB;AAAA,EACrE,WAAW,EAAE,SAAS,kBAAkB,MAAM,cAAc;AAAA,EAC5D,iBAAiB,EAAE,SAAS,4BAA4B,MAAM,yBAAyB;AACzF;AAQA,SAAS,QAAQ,QAAgC,UAA0B;AACzE,SAAO,OAAO,OAAO,QAAQ,QAAQ,IAAK,OAAO,QAAQ,KAAK,WAAY;AAC5E;AAGO,SAAS,kBAA6C,QAAgB,SAAiC,eAAuB;AACnI,SAAO;AAAA,IACL,GAAG;AAAA,IACH,aAAa,CAAC,aAAqB,OAAO,YAAY,QAAQ,QAAQ,QAAQ,CAAC;AAAA,IAC/E,UAAU,CAAC,aAAqB,OAAO,SAAS,QAAQ,QAAQ,QAAQ,CAAC;AAAA,IACzE,WAAW,CAAC,aAAqB,OAAO,UAAU,QAAQ,QAAQ,QAAQ,CAAC;AAAA,EAC7E;AACF;","names":[]}
@@ -0,0 +1,55 @@
1
+ import type { ToolSet } from "ai";
2
+ import type { ChatStorage } from "@ykurilov/agent-chat-core";
3
+ import type { HostResolver } from "./page-address.js";
4
+ import type { PageReaderService, PageScreenshotService, WebSearchService } from "./services.js";
5
+ export interface CreateWebToolsOptions {
6
+ search: WebSearchService;
7
+ reader: PageReaderService;
8
+ /** Сколько результатов возвращает поиск. */
9
+ searchResultLimit?: number;
10
+ /** Сколько знаков страницы отдаётся модели. */
11
+ pageCharacterLimit?: number;
12
+ /** Служба снимков. Без неё инструмента `page_screenshot` нет. */
13
+ screenshots?: PageScreenshotService;
14
+ /** Для скачивания снимка при сборке запроса к модели. */
15
+ fetch?: typeof globalThis.fetch;
16
+ /** Как разрешать имена при скачивании снимка. По умолчанию системный DNS; заменяется в тестах. */
17
+ resolveHost?: HostResolver;
18
+ /**
19
+ * Траты служб в лимит расходов: удачный вызов и вызов, оборванный после отправки запроса, пишутся расходом `web_tools`.
20
+ * Без него вызовы не учитываются.
21
+ */
22
+ spending?: WebToolsSpending;
23
+ }
24
+ /** Учёт трат веб-служб: запись идёт в то же хранилище, по которому считает лимит расходов. */
25
+ export interface WebToolsSpending {
26
+ storage: Pick<ChatStorage, "insertUsageRecord">;
27
+ /**
28
+ * Доллары за один удачный вызов. У Firecrawl поиск стоит 2 кредита за 10 результатов, страница и снимок — 1 кредит;
29
+ * цена кредита — по вашему тарифу. Инструмент без цены не учитывается.
30
+ */
31
+ pricePerCallUsd: {
32
+ web_search: number;
33
+ read_page: number;
34
+ page_screenshot?: number;
35
+ };
36
+ /** Служба в записи расхода; по умолчанию `firecrawl`. */
37
+ providerId?: string;
38
+ logError?: (message: string) => void;
39
+ now?: () => number;
40
+ }
41
+ /** Выбранная модель: то, что ядро передаёт функции-набору инструментов. */
42
+ export interface WebToolsModel {
43
+ /** `toolResultImageProviders` — профиль модели: через каких провайдеров она видит картинку в результате инструмента. */
44
+ profile: {
45
+ acceptsImages: boolean;
46
+ acceptsImagesByProvider?: Readonly<Record<string, boolean>>;
47
+ toolResultImageProviders?: readonly string[];
48
+ };
49
+ providerId: string;
50
+ /** Чей ход и в каком чате: на них пишется расход (`spending`). Ядро передаёт их само. */
51
+ ownerId?: string;
52
+ chatId?: string;
53
+ }
54
+ /** Набор веб-инструментов для `agent.tools` ядра: `tools: (model) => ({ ...appTools, ...webTools(model) })`. */
55
+ export declare function createWebTools(options: CreateWebToolsOptions): (model: WebToolsModel) => ToolSet;
@@ -0,0 +1,3 @@
1
+ import type { HostResolver } from "./page-address.js";
2
+ /** Все адреса имени по системному DNS. */
3
+ export declare const dnsHostResolver: HostResolver;
@@ -0,0 +1,17 @@
1
+ import type { PageReaderService, PageScreenshotService, WebSearchService } from "../services.js";
2
+ export interface FirecrawlOptions {
3
+ apiKey: string;
4
+ /** Адрес сервиса или прокси перед ним; по умолчанию https://api.firecrawl.dev. */
5
+ baseUrl?: string;
6
+ /** Дополнительные заголовки каждого запроса, например токен прокси. */
7
+ headers?: Record<string, string>;
8
+ fetch?: typeof globalThis.fetch;
9
+ /** Предел ожидания ответа, мс. По умолчанию 30 000 для поиска и 60 000 для чтения и снимка. */
10
+ timeoutMs?: number;
11
+ }
12
+ export interface FirecrawlServices {
13
+ search: WebSearchService;
14
+ reader: PageReaderService;
15
+ screenshots: PageScreenshotService;
16
+ }
17
+ export declare function createFirecrawlServices(options: FirecrawlOptions): FirecrawlServices;
@@ -0,0 +1,13 @@
1
+ import type { DownloadedImage } from "./screenshot-download.js";
2
+ export interface ImageCacheOptions {
3
+ maxEntries: number;
4
+ maxBytes: number;
5
+ ttlMs: number;
6
+ now?: () => number;
7
+ }
8
+ export interface ImageCache {
9
+ get(key: string): DownloadedImage | undefined;
10
+ set(key: string, image: DownloadedImage): void;
11
+ }
12
+ /** Кэш картинок в памяти: ограничен числом, общим объёмом и временем жизни; первой уходит давно не нужная запись. */
13
+ export declare function createImageCache({ maxEntries, maxBytes, ttlMs, now }: ImageCacheOptions): ImageCache;
@@ -0,0 +1,11 @@
1
+ export { createWebTools } from "./create-web-tools.js";
2
+ export type { CreateWebToolsOptions, WebToolsModel, WebToolsSpending } from "./create-web-tools.js";
3
+ export { createPersonLinkTool, PERSON_LINK_TOOL, PERSON_LINKS_PER_CHAT } from "./tools/person-link-view.js";
4
+ export type { PersonLinkModel, PersonLinkOptions, PersonLinkTools } from "./tools/person-link-view.js";
5
+ export { webToolsInstructions } from "./instructions.js";
6
+ export { assertPublicWebAddress, assertResolvesPublic, isHttpAddress, isPrivateAddress } from "./page-address.js";
7
+ export type { HostResolver } from "./page-address.js";
8
+ export { dnsHostResolver } from "./dns-host-resolver.js";
9
+ export { WebToolError } from "./web-tool-error.js";
10
+ export type { WebToolErrorKind } from "./web-tool-error.js";
11
+ export type { PageContent, PageReaderService, PageScreenshotService, SearchHit, WebSearchService } from "./services.js";
@@ -0,0 +1,2 @@
1
+ /** Дополнение к инструкции агента: как пользоваться веб-инструментами. Текст постоянный (кэш модели). */
2
+ export declare const webToolsInstructions: string;
@@ -0,0 +1,22 @@
1
+ /** Подписи веб-инструментов в ленте чата. Без кода сервисов: файл безопасно подключать в браузерной сборке. */
2
+ export declare const webToolTitles: Record<string, string>;
3
+ /** Те же подписи по-английски. */
4
+ export declare const englishWebToolTitles: Record<string, string>;
5
+ /** Понятные фразы веб-инструментов для простого вида чата (`stepPhrases` у `AgentChat`): «идёт» и «готово». */
6
+ export declare const webToolStepPhrases: Record<string, {
7
+ running: string;
8
+ done: string;
9
+ }>;
10
+ /** Те же фразы по-английски. */
11
+ export declare const englishWebToolStepPhrases: Record<string, {
12
+ running: string;
13
+ done: string;
14
+ }>;
15
+ interface ToolLabels {
16
+ toolRunning(toolName: string): string;
17
+ toolDone(toolName: string): string;
18
+ toolError(toolName: string): string;
19
+ }
20
+ /** Надписи чата, в которых веб-инструменты названы по-русски (или по словарю `titles`, например `englishWebToolTitles`); остальное не меняется. */
21
+ export declare function withWebToolTitles<Labels extends ToolLabels>(labels: Labels, titles?: Record<string, string>): Labels;
22
+ export {};
@@ -0,0 +1,14 @@
1
+ /** Определяет адреса имени. В приложении это DNS; в тестах подменяется. */
2
+ export type HostResolver = (hostname: string) => Promise<string[]>;
3
+ /** Адрес http или https: единственные схемы, которые пускают в результаты и в скачивание. */
4
+ export declare function isHttpAddress(address: string): boolean;
5
+ /** Пропускает только публичные адреса http и https: сервис чтения не должен ходить во внутреннюю сеть. */
6
+ export declare function assertPublicWebAddress(address: string): URL;
7
+ /**
8
+ * Проверка по DNS для тех, кто ходит по адресу сам: имя разрешается, и если хоть один адрес частный
9
+ * (или имя не разрешилось), оно отклоняется. Адрес-число уже проверен `assertPublicWebAddress`.
10
+ * Между проверкой и запросом остаётся короткое окно: fetch разрешает имя ещё раз.
11
+ */
12
+ export declare function assertResolvesPublic(url: URL, resolveHost: HostResolver): Promise<void>;
13
+ /** Адрес из ответа DNS (без скобок). Всё, что не удалось разобрать, считается частным. */
14
+ export declare function isPrivateAddress(address: string): boolean;
@@ -0,0 +1,5 @@
1
+ import type { PageContent, SearchHit } from "./services.js";
2
+ /** Результат поиска: нумерованный список, заголовок ссылкой, ниже выдержка. Один текст для модели и для человека. */
3
+ export declare function formatSearchResults(query: string, hits: SearchHit[]): string;
4
+ /** Страница: заголовок, источник и текст; длинный текст режется на границе абзаца с пометкой. */
5
+ export declare function formatPage(page: PageContent, characterLimit: number): string;
@@ -0,0 +1,19 @@
1
+ import type { HostResolver } from "./page-address.js";
2
+ export declare const screenshotDownloadTimeoutMs = 20000;
3
+ export declare const screenshotMaxBytes: number;
4
+ export interface DownloadedImage {
5
+ bytes: Uint8Array;
6
+ mediaType: string;
7
+ }
8
+ export declare const screenshotMaxRedirects = 3;
9
+ export interface ScreenshotDownloadOptions {
10
+ /** Разрешает имя в адреса для проверки: наш сервер сам ходит по этому адресу. */
11
+ resolveHost: HostResolver;
12
+ }
13
+ /**
14
+ * Скачивает снимок по адресу из записи истории. Возвращает `undefined`, если снимка больше нет:
15
+ * ссылка устарела, это не картинка, она слишком велика или ведёт не туда. Переходы выполняются вручную,
16
+ * не больше трёх, и каждый проходит ту же проверку, что и первый адрес: только http(s), не локальный адрес,
17
+ * имя не указывает во внутреннюю сеть.
18
+ */
19
+ export declare function downloadScreenshot(address: string, fetchImplementation: typeof globalThis.fetch, { resolveHost }: ScreenshotDownloadOptions): Promise<DownloadedImage | undefined>;
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Вызов сервиса из инструмента. Ошибки своей реализации сервиса (не `WebToolError`) наружу не идут:
3
+ * в их тексте могут оказаться ключ или адрес. Остановка хода проходит как есть.
4
+ */
5
+ export declare function callService<Result>(signal: AbortSignal | undefined, call: () => Promise<Result>): Promise<Result>;
@@ -0,0 +1,29 @@
1
+ /** Что нужно инструментам от сервисов. Реализация заменяется без правки инструментов (Firecrawl, Exa и другие). */
2
+ export interface SearchHit {
3
+ title: string;
4
+ url: string;
5
+ snippet: string;
6
+ }
7
+ export interface WebSearchService {
8
+ search(query: string, options: {
9
+ limit: number;
10
+ signal?: AbortSignal;
11
+ }): Promise<SearchHit[]>;
12
+ }
13
+ export interface PageContent {
14
+ url: string;
15
+ title: string;
16
+ markdown: string;
17
+ }
18
+ export interface PageReaderService {
19
+ readPage(url: string, options: {
20
+ signal?: AbortSignal;
21
+ }): Promise<PageContent>;
22
+ }
23
+ export interface PageScreenshotService {
24
+ takeScreenshot(url: string, options: {
25
+ signal?: AbortSignal;
26
+ }): Promise<{
27
+ imageUrl: string;
28
+ }>;
29
+ }
@@ -0,0 +1,15 @@
1
+ import type { Tool } from "ai";
2
+ import type { HostResolver } from "../page-address.js";
3
+ import type { PageScreenshotService } from "../services.js";
4
+ export interface ScreenshotRecord {
5
+ pageUrl: string;
6
+ screenshotUrl: string;
7
+ }
8
+ /**
9
+ * В историю попадают только два адреса (запись маленькая). Картинку модель получает при сборке каждого запроса:
10
+ * снимок скачивается заново, а если ссылка устарела, вместо картинки уходит текст.
11
+ * Описание и схема неизменны при любых настройках и службах: от них зависит кэш модели.
12
+ */
13
+ export declare function pageScreenshotTool(screenshots: PageScreenshotService, fetchImplementation: typeof globalThis.fetch, resolveHost: HostResolver): Tool<{
14
+ url: string;
15
+ }, ScreenshotRecord>;
@@ -0,0 +1,41 @@
1
+ import type { ChatStorage } from "@ykurilov/agent-chat-core";
2
+ import type { ToolSet } from "ai";
3
+ import type { WebToolsModel } from "../create-web-tools.js";
4
+ import type { HostResolver } from "../page-address.js";
5
+ import type { PageReaderService, PageScreenshotService } from "../services.js";
6
+ /** Имя инструмента: его же ждёт правило сценария (`flows/prompt/rules/link-views.ts`). */
7
+ export declare const PERSON_LINK_TOOL = "person_link_view";
8
+ /** Сколько раз за разговор агент смотрит ссылки человека. */
9
+ export declare const PERSON_LINKS_PER_CHAT = 3;
10
+ export interface PersonLinkOptions {
11
+ /** Для чтения слов человека и счёта просмотров; в него же пишется расход `web_tools`. */
12
+ storage: Pick<ChatStorage, "listMessages" | "listBranchSelections" | "insertUsageRecord">;
13
+ /** Снимок страницы: его видят модели, которым провайдер отдаёт картинку в результате инструмента. */
14
+ screenshots?: PageScreenshotService;
15
+ /** Текст страницы: для моделей без картинок. Нет ни службы снимков для модели, ни чтения — инструмента нет. */
16
+ reader?: PageReaderService;
17
+ /** Сколько просмотров за разговор; по умолчанию три. */
18
+ maxPerChat?: number;
19
+ /** Домены приложений: голое имя от человека («sveta-nails») открывается на них («sveta-nails.ykapp.ru»). */
20
+ appDomains?: readonly string[];
21
+ /** Доллары за один просмотр (кредит службы); пишется расходом `web_tools`. */
22
+ pricePerViewUsd?: number;
23
+ fetch?: typeof globalThis.fetch;
24
+ resolveHost?: HostResolver;
25
+ logError?: (message: string) => void;
26
+ }
27
+ /** Ход, для которого собирается набор: чат обязателен, `turnId` отделяет вызовы идущего хода. */
28
+ export type PersonLinkModel = WebToolsModel & {
29
+ turnId?: string;
30
+ };
31
+ /** Набор для `agent.tools`: `{ person_link_view }` или пустой. `appDomains` вызова дополняют настройку. */
32
+ export type PersonLinkTools = (model: PersonLinkModel, call?: {
33
+ appDomains?: readonly string[];
34
+ }) => ToolSet;
35
+ /**
36
+ * Инструмент «посмотреть ссылку собеседника»: только адрес из реплик человека (или короткое имя на домене приложений),
37
+ * не больше `maxPerChat` просмотров за разговор. Описание и схема одни для всех моделей: от них зависит кэш.
38
+ */
39
+ export declare function createPersonLinkTool(options: PersonLinkOptions): PersonLinkTools;
40
+ /** Один вид для сравнения: без протокола, «www», хвостовой косой черты, якоря и знаков препинания в конце. */
41
+ export declare function normalizeAddress(raw: string): string;
@@ -0,0 +1,6 @@
1
+ import type { Tool } from "ai";
2
+ import type { PageReaderService } from "../services.js";
3
+ /** Описание и схема неизменны при любых настройках и службах: от них зависит кэш модели. */
4
+ export declare function readPageTool(reader: PageReaderService, characterLimit: number): Tool<{
5
+ url: string;
6
+ }, string>;
@@ -0,0 +1,6 @@
1
+ import type { Tool } from "ai";
2
+ import type { WebSearchService } from "../services.js";
3
+ /** Описание и схема неизменны при любых настройках и службах: от них зависит кэш модели. */
4
+ export declare function webSearchTool(search: WebSearchService, resultLimit: number): Tool<{
5
+ query: string;
6
+ }, string>;
@@ -0,0 +1,18 @@
1
+ export type WebToolErrorKind = "bad_address" | "bad_query" | "invalid_key" | "insufficient_funds" | "rate_limited" | "unavailable" | "timeout" | "bad_response";
2
+ /**
3
+ * Ошибка веб-инструмента. Текст по-русски показывается и модели, и человеку в строке вызова,
4
+ * поэтому в нём нет ключа, адреса сервиса и заголовков.
5
+ */
6
+ export declare class WebToolError extends Error {
7
+ readonly kind: WebToolErrorKind;
8
+ constructor(kind: WebToolErrorKind, message: string);
9
+ }
10
+ export declare const serviceFailureMessages: {
11
+ readonly invalid_key: "Сервис веб-инструментов отклонил ключ доступа.";
12
+ readonly insufficient_funds: "На счёте сервиса веб-инструментов закончились средства.";
13
+ readonly rate_limited: "Превышен лимит запросов сервиса веб-инструментов, попробуйте позже.";
14
+ readonly unavailable: "Сервис веб-инструментов сейчас недоступен, попробуйте позже.";
15
+ readonly timeout: "Сервис веб-инструментов не ответил вовремя.";
16
+ readonly bad_response: "Сервис веб-инструментов вернул неожиданный ответ.";
17
+ };
18
+ export declare function serviceFailure(kind: keyof typeof serviceFailureMessages): WebToolError;
package/package.json CHANGED
@@ -1,6 +1,50 @@
1
1
  {
2
2
  "name": "@ykurilov/agent-chat-web-tools",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.56.0",
4
+ "license": "MIT",
5
+ "type": "module",
6
+ "sideEffects": false,
7
+ "files": [
8
+ "dist",
9
+ "README.md",
10
+ "CHANGELOG.md"
11
+ ],
12
+ "exports": {
13
+ ".": {
14
+ "@ykurilov/agent-chat-source": "./src/index.ts",
15
+ "types": "./dist/types/index.d.ts",
16
+ "import": "./dist/index.js"
17
+ },
18
+ "./firecrawl": {
19
+ "@ykurilov/agent-chat-source": "./src/firecrawl/firecrawl-services.ts",
20
+ "types": "./dist/types/firecrawl/firecrawl-services.d.ts",
21
+ "import": "./dist/firecrawl.js"
22
+ },
23
+ "./labels": {
24
+ "@ykurilov/agent-chat-source": "./src/labels.ts",
25
+ "types": "./dist/types/labels.d.ts",
26
+ "import": "./dist/labels.js"
27
+ }
28
+ },
29
+ "engines": {
30
+ "node": ">=22"
31
+ },
32
+ "publishConfig": {
33
+ "registry": "https://registry.npmjs.org",
34
+ "access": "public"
35
+ },
36
+ "repository": {
37
+ "type": "git",
38
+ "url": "git+https://github.com/ykurilov/agent-chat-sdk.git",
39
+ "directory": "packages/web-tools"
40
+ },
41
+ "dependencies": {
42
+ "ai": "7.0.122",
43
+ "zod": "^4.6.5",
44
+ "@ykurilov/agent-chat-core": "0.56.0"
45
+ },
46
+ "scripts": {
47
+ "build": "node ../../scripts/build-package.mjs",
48
+ "typecheck": "tsc -p tsconfig.json"
49
+ }
6
50
  }