@arnilo/prism-tools 0.0.0-stage → 0.13.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/CHANGELOG.md +418 -0
- package/LICENSE +21 -0
- package/README.md +44 -2
- package/browser/README.md +98 -0
- package/browser/skills/browser-automation/SKILL.md +14 -0
- package/browser/src/actions/cdp.ts +131 -0
- package/browser/src/actions/dialog.ts +25 -0
- package/browser/src/actions/download_release.ts +22 -0
- package/browser/src/actions/index.ts +37 -0
- package/browser/src/actions/locator.ts +70 -0
- package/browser/src/actions/navigate.ts +23 -0
- package/browser/src/actions/screenshot.ts +27 -0
- package/browser/src/actions/scroll.ts +24 -0
- package/browser/src/actions/select_page.ts +21 -0
- package/browser/src/actions/types.ts +114 -0
- package/browser/src/actions/upload.ts +44 -0
- package/browser/src/actions/wait.ts +45 -0
- package/browser/src/browser.ts +197 -0
- package/browser/src/cdp.ts +178 -0
- package/browser/src/checkpoint.ts +189 -0
- package/browser/src/config.ts +72 -0
- package/browser/src/downloads.ts +253 -0
- package/browser/src/errors.ts +31 -0
- package/browser/src/evaluate.ts +101 -0
- package/browser/src/index.ts +129 -0
- package/browser/src/limits.ts +271 -0
- package/browser/src/manager.ts +730 -0
- package/browser/src/network.ts +263 -0
- package/browser/src/observe.ts +178 -0
- package/browser/src/policy.ts +133 -0
- package/browser/src/screenshot.ts +107 -0
- package/browser/src/shared-sandbox.ts +53 -0
- package/browser/src/snapshot.ts +99 -0
- package/browser/src/targets.ts +165 -0
- package/browser/src/tools.ts +480 -0
- package/browser/src/types.ts +316 -0
- package/browser/src/uploads.ts +174 -0
- package/coding/README.md +337 -0
- package/coding/src/agent/acp-operations.ts +146 -0
- package/coding/src/agent/artifacts.ts +38 -0
- package/coding/src/agent/ask-user-decision.ts +623 -0
- package/coding/src/agent/atomic-write.ts +23 -0
- package/coding/src/agent/bounded-file.ts +21 -0
- package/coding/src/agent/checks.ts +301 -0
- package/coding/src/agent/coding-checkpoint.ts +828 -0
- package/coding/src/agent/delete.ts +237 -0
- package/coding/src/agent/diagnostics-tool.ts +163 -0
- package/coding/src/agent/diagnostics.ts +262 -0
- package/coding/src/agent/edit-diff.ts +382 -0
- package/coding/src/agent/edit.ts +322 -0
- package/coding/src/agent/effects.ts +102 -0
- package/coding/src/agent/env.ts +28 -0
- package/coding/src/agent/execution-policy.ts +89 -0
- package/coding/src/agent/file-mutation-queue.ts +72 -0
- package/coding/src/agent/forge/github.ts +603 -0
- package/coding/src/agent/forge/index.ts +16 -0
- package/coding/src/agent/forge/types.ts +185 -0
- package/coding/src/agent/git-aware-repository.ts +305 -0
- package/coding/src/agent/git-exec.ts +326 -0
- package/coding/src/agent/git-status.ts +177 -0
- package/coding/src/agent/git-tools.ts +360 -0
- package/coding/src/agent/git.ts +1074 -0
- package/coding/src/agent/glob-match.ts +146 -0
- package/coding/src/agent/glob.ts +182 -0
- package/coding/src/agent/goal-verify.ts +385 -0
- package/coding/src/agent/grep-worker.ts +74 -0
- package/coding/src/agent/grep.ts +459 -0
- package/coding/src/agent/identity.ts +48 -0
- package/coding/src/agent/index.ts +676 -0
- package/coding/src/agent/language/client.ts +429 -0
- package/coding/src/agent/language/framing.ts +190 -0
- package/coding/src/agent/language/index.ts +23 -0
- package/coding/src/agent/language/intelligence.ts +682 -0
- package/coding/src/agent/language/types.ts +187 -0
- package/coding/src/agent/lifecycle-events.ts +121 -0
- package/coding/src/agent/lifecycle.ts +166 -0
- package/coding/src/agent/limits.ts +214 -0
- package/coding/src/agent/move.ts +178 -0
- package/coding/src/agent/mutation-path.ts +52 -0
- package/coding/src/agent/output-accumulator.ts +304 -0
- package/coding/src/agent/path-utils.ts +120 -0
- package/coding/src/agent/process/index.ts +58 -0
- package/coding/src/agent/process/recovery.ts +508 -0
- package/coding/src/agent/process/sessions-host.ts +222 -0
- package/coding/src/agent/process/sessions-monitor.ts +141 -0
- package/coding/src/agent/process/sessions-recovery.ts +241 -0
- package/coding/src/agent/process/sessions-spawn.ts +613 -0
- package/coding/src/agent/process/sessions-teardown.ts +281 -0
- package/coding/src/agent/process/sessions.ts +30 -0
- package/coding/src/agent/process/types.ts +358 -0
- package/coding/src/agent/read-path-set.ts +126 -0
- package/coding/src/agent/read.ts +713 -0
- package/coding/src/agent/repository/glob.ts +147 -0
- package/coding/src/agent/repository/indexed-search.ts +480 -0
- package/coding/src/agent/repository/list.ts +131 -0
- package/coding/src/agent/repository/operations.ts +22 -0
- package/coding/src/agent/repository/path.ts +98 -0
- package/coding/src/agent/repository/search.ts +277 -0
- package/coding/src/agent/repository/types.ts +243 -0
- package/coding/src/agent/repository/walk.ts +126 -0
- package/coding/src/agent/repository.ts +12 -0
- package/coding/src/agent/shell-background.ts +264 -0
- package/coding/src/agent/shell-output.ts +75 -0
- package/coding/src/agent/shell.ts +526 -0
- package/coding/src/agent/todo.ts +399 -0
- package/coding/src/agent/truncate.ts +251 -0
- package/coding/src/agent/write.ts +170 -0
- package/coding/src/ask-user-host.ts +48 -0
- package/coding/src/config.ts +278 -0
- package/coding/src/hard-deny.ts +97 -0
- package/coding/src/index.ts +318 -0
- package/coding/src/principal-guard.ts +84 -0
- package/coding/src/sandbox.ts +92 -0
- package/coding/src/security/approval.ts +144 -0
- package/coding/src/security/command-rules.ts +57 -0
- package/coding/src/security/docker-cli.ts +222 -0
- package/coding/src/security/docker-sandbox.ts +1205 -0
- package/coding/src/security/e2b-sandbox.ts +853 -0
- package/coding/src/security/egress/dns-pin.ts +88 -0
- package/coding/src/security/egress/index.ts +32 -0
- package/coding/src/security/egress/limits.ts +57 -0
- package/coding/src/security/egress/policy.ts +137 -0
- package/coding/src/security/egress/proxy.ts +599 -0
- package/coding/src/security/egress/types.ts +30 -0
- package/coding/src/security/index.ts +199 -0
- package/coding/src/security/native-sandbox.ts +639 -0
- package/coding/src/security/path-containment.ts +55 -0
- package/coding/src/security/sandbox-coding-operations.ts +392 -0
- package/coding/src/security/sandbox-fs-operations.ts +644 -0
- package/coding/src/security/sandbox-limits.ts +147 -0
- package/coding/src/security/sandbox-tar.ts +308 -0
- package/coding/src/security/sandbox.ts +179 -0
- package/coding/src/security/semaphore.ts +55 -0
- package/coding/src/targets.ts +110 -0
- package/coding/src/workspace/diff.ts +78 -0
- package/coding/src/workspace/errors.ts +29 -0
- package/coding/src/workspace/index.ts +5 -0
- package/coding/src/workspace/merge.ts +151 -0
- package/coding/src/workspace/routing.ts +76 -0
- package/coding/src/workspace/service.ts +584 -0
- package/computer-use/README.md +105 -0
- package/computer-use/skills/computer-use-linux/SKILL.md +17 -0
- package/computer-use/src/classify.ts +53 -0
- package/computer-use/src/command.ts +29 -0
- package/computer-use/src/config.ts +69 -0
- package/computer-use/src/create.ts +170 -0
- package/computer-use/src/devices.ts +247 -0
- package/computer-use/src/env.ts +28 -0
- package/computer-use/src/index.ts +157 -0
- package/computer-use/src/skill.ts +21 -0
- package/openapi/README.md +97 -0
- package/openapi/src/compile.ts +386 -0
- package/openapi/src/config.ts +73 -0
- package/openapi/src/errors.ts +16 -0
- package/openapi/src/execute.ts +241 -0
- package/openapi/src/index.ts +124 -0
- package/openapi/src/limits.ts +63 -0
- package/openapi/src/specs.ts +86 -0
- package/openapi/src/tools.ts +60 -0
- package/openapi/src/types.ts +61 -0
- package/package.json +171 -4
- package/research/README.md +72 -0
- package/research/skills/research-report/SKILL.md +12 -0
- package/research/skills/research-scoping/SKILL.md +10 -0
- package/research/skills/research-synthesis/SKILL.md +10 -0
- package/research/skills/source-triage/SKILL.md +10 -0
- package/research/src/check.ts +71 -0
- package/research/src/config.ts +41 -0
- package/research/src/index.ts +147 -0
- package/research/src/ledger.ts +157 -0
- package/research/src/urls.ts +21 -0
- package/web/README.md +287 -0
- package/web/skills/web-research/SKILL.md +17 -0
- package/web/src/brave.ts +66 -0
- package/web/src/cache.ts +151 -0
- package/web/src/config.ts +210 -0
- package/web/src/controls.ts +128 -0
- package/web/src/domains.ts +114 -0
- package/web/src/exa.ts +60 -0
- package/web/src/firecrawl.ts +160 -0
- package/web/src/index.ts +387 -0
- package/web/src/kagi.ts +73 -0
- package/web/src/limits.ts +55 -0
- package/web/src/native/convert.ts +84 -0
- package/web/src/native/fetch.ts +226 -0
- package/web/src/normalize.ts +131 -0
- package/web/src/obscura/cdp.ts +201 -0
- package/web/src/obscura/classify.ts +35 -0
- package/web/src/obscura/cli.ts +254 -0
- package/web/src/obscura/errors.ts +8 -0
- package/web/src/obscura/index.ts +59 -0
- package/web/src/obscura/limits.ts +92 -0
- package/web/src/obscura/mcp.ts +104 -0
- package/web/src/obscura/playwright.ts +133 -0
- package/web/src/obscura/process.ts +235 -0
- package/web/src/obscura/search-profile.ts +73 -0
- package/web/src/obscura/types.ts +48 -0
- package/web/src/obscura/web.ts +419 -0
- package/web/src/prompt-fetch.ts +88 -0
- package/web/src/render.ts +75 -0
- package/web/src/searxng.ts +78 -0
- package/web/src/slice.ts +139 -0
- package/web/src/tavily.ts +77 -0
- package/web/src/tools.ts +182 -0
- package/web/src/transport.ts +310 -0
- package/web/src/types.ts +199 -0
- package/work/README.md +77 -0
- package/work/THIRD_PARTY_NOTICES +24 -0
- package/work/docling/ocr.py +150 -0
- package/work/sandbox/Dockerfile +36 -0
- package/work/sandbox/soffice.sh +12 -0
- package/work/src/command.ts +29 -0
- package/work/src/config.ts +138 -0
- package/work/src/connectors/cli.ts +343 -0
- package/work/src/connectors/drafts.ts +647 -0
- package/work/src/connectors/errors.ts +8 -0
- package/work/src/connectors/file-bytes.ts +149 -0
- package/work/src/connectors/google-workspace-http.ts +399 -0
- package/work/src/connectors/google-workspace.ts +465 -0
- package/work/src/connectors/gws-tools.ts +378 -0
- package/work/src/connectors/http.ts +123 -0
- package/work/src/connectors/idempotency.ts +235 -0
- package/work/src/connectors/identity.ts +34 -0
- package/work/src/connectors/index.ts +93 -0
- package/work/src/connectors/limits.ts +50 -0
- package/work/src/connectors/m365-tools.ts +308 -0
- package/work/src/connectors/microsoft365-http.ts +428 -0
- package/work/src/connectors/microsoft365.ts +402 -0
- package/work/src/connectors/normalize.ts +120 -0
- package/work/src/connectors/tool-helpers.ts +398 -0
- package/work/src/connectors/tools.ts +42 -0
- package/work/src/connectors/types.ts +399 -0
- package/work/src/diagrams/canonicalize.ts +166 -0
- package/work/src/diagrams/caps.ts +52 -0
- package/work/src/diagrams/embed.ts +340 -0
- package/work/src/diagrams/errors.ts +76 -0
- package/work/src/diagrams/index.ts +81 -0
- package/work/src/diagrams/messages.ts +150 -0
- package/work/src/diagrams/origin.ts +52 -0
- package/work/src/diagrams/telemetry.ts +34 -0
- package/work/src/diagrams/xml.ts +267 -0
- package/work/src/document-extraction/index.ts +563 -0
- package/work/src/document-reader/ambient.d.ts +2 -0
- package/work/src/document-reader/errors.ts +7 -0
- package/work/src/document-reader/index.ts +181 -0
- package/work/src/document-reader/mistral-ocr.ts +318 -0
- package/work/src/document-reader/parsers.ts +171 -0
- package/work/src/documents/caps.ts +135 -0
- package/work/src/documents/diff.ts +269 -0
- package/work/src/documents/errors.ts +44 -0
- package/work/src/documents/fidelity.ts +98 -0
- package/work/src/documents/generate.ts +96 -0
- package/work/src/documents/hash.ts +8 -0
- package/work/src/documents/index.ts +14 -0
- package/work/src/documents/model-schema.ts +639 -0
- package/work/src/documents/parse.ts +252 -0
- package/work/src/documents/patch-history.ts +81 -0
- package/work/src/documents/patch.ts +476 -0
- package/work/src/documents/preview-html.ts +245 -0
- package/work/src/documents/preview.ts +227 -0
- package/work/src/documents/telemetry.ts +36 -0
- package/work/src/documents/translate/docx.ts +292 -0
- package/work/src/documents/translate/pptx.ts +304 -0
- package/work/src/documents/translate/xlsx.ts +137 -0
- package/work/src/documents/types.ts +188 -0
- package/work/src/index.ts +311 -0
- package/work/src/runtime/worker-pool-entry.ts +102 -0
- package/work/src/runtime/worker-pool.ts +267 -0
- package/work/src/sandbox/composition.ts +212 -0
- package/work/src/sandbox/image.ts +6 -0
- package/work/src/sandbox/index.ts +14 -0
- package/work/src/sheets/caps.ts +68 -0
- package/work/src/sheets/csv.ts +351 -0
- package/work/src/sheets/decimal.ts +388 -0
- package/work/src/sheets/errors.ts +52 -0
- package/work/src/sheets/index.ts +62 -0
- package/work/src/sheets/inference.ts +378 -0
- package/work/src/sheets/telemetry.ts +34 -0
- package/work/src/sheets/types.ts +162 -0
- package/work/src/sheets/xlsx.ts +388 -0
- package/work/src/skills/index.ts +8 -0
- package/work/src/skills/load.ts +43 -0
- package/work/src/tools/filesystem.ts +21 -0
- package/work/src/tools/index.ts +1 -0
- package/work/src/tools/local-filesystem.ts +33 -0
- package/work/src/tools/office.ts +311 -0
- package/work/vendor/hermes-agent/LICENSE +21 -0
- package/work/vendor/hermes-agent/skills/productivity/docx/LICENSE +21 -0
- package/work/vendor/hermes-agent/skills/productivity/docx/SKILL.md +196 -0
- package/work/vendor/hermes-agent/skills/productivity/docx/scripts/docx_comments.py +289 -0
- package/work/vendor/hermes-agent/skills/productivity/docx/scripts/docx_common.py +94 -0
- package/work/vendor/hermes-agent/skills/productivity/docx/scripts/docx_create.py +177 -0
- package/work/vendor/hermes-agent/skills/productivity/docx/scripts/docx_edit.py +250 -0
- package/work/vendor/hermes-agent/skills/productivity/docx/scripts/docx_read.py +149 -0
- package/work/vendor/hermes-agent/skills/productivity/docx/scripts/docx_revisions.py +147 -0
- package/work/vendor/hermes-agent/skills/productivity/docx/scripts/docx_template.py +70 -0
- package/work/vendor/hermes-agent/skills/productivity/docx/scripts/docx_validate.py +156 -0
- package/work/vendor/hermes-agent/skills/productivity/pdf/LICENSE +21 -0
- package/work/vendor/hermes-agent/skills/productivity/pdf/SKILL.md +125 -0
- package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/_raster.py +76 -0
- package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/extract_marker.py +88 -0
- package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/extract_pymupdf.py +101 -0
- package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_create.py +130 -0
- package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_fill_form.py +97 -0
- package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_form_layout.py +168 -0
- package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_make_form.py +145 -0
- package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_merge.py +50 -0
- package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_meta.py +115 -0
- package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_page_image.py +99 -0
- package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_read.py +153 -0
- package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_secure.py +71 -0
- package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_split.py +84 -0
- package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_stamp.py +143 -0
- package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_watermark.py +51 -0
- package/work/vendor/hermes-agent/skills/productivity/powerpoint/LICENSE +21 -0
- package/work/vendor/hermes-agent/skills/productivity/powerpoint/SKILL.md +220 -0
- package/work/vendor/hermes-agent/skills/productivity/powerpoint/scripts/pptx_create.py +214 -0
- package/work/vendor/hermes-agent/skills/productivity/powerpoint/scripts/pptx_edit.py +436 -0
- package/work/vendor/hermes-agent/skills/productivity/powerpoint/scripts/pptx_from_template.py +88 -0
- package/work/vendor/hermes-agent/skills/productivity/powerpoint/scripts/pptx_read.py +131 -0
- package/work/vendor/hermes-agent/skills/productivity/powerpoint/scripts/pptx_render.py +93 -0
- package/work/vendor/hermes-agent/skills/productivity/xlsx/LICENSE +21 -0
- package/work/vendor/hermes-agent/skills/productivity/xlsx/SKILL.md +196 -0
- package/work/vendor/hermes-agent/skills/productivity/xlsx/scripts/csv_to_xlsx.py +104 -0
- package/work/vendor/hermes-agent/skills/productivity/xlsx/scripts/xlsx_create.py +259 -0
- package/work/vendor/hermes-agent/skills/productivity/xlsx/scripts/xlsx_edit.py +263 -0
- package/work/vendor/hermes-agent/skills/productivity/xlsx/scripts/xlsx_read.py +160 -0
- package/work/vendor/hermes-agent/skills/productivity/xlsx/scripts/xlsx_recalc.py +111 -0
- package/work/vendor/hermes-agent/skills/productivity/xlsx/scripts/xlsx_restructure.py +337 -0
- package/work/vendor/hermes-agent/skills/productivity/xlsx/scripts/xlsx_to_csv.py +69 -0
- package/work/vendor-lock.json +11 -0
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
import { type JsonValue, redactSecrets } from "@arnilo/prism";
|
|
2
|
+
import { isLoopbackHostname } from "@arnilo/prism/net";
|
|
3
|
+
import { resolveWebLimits } from "./limits.js";
|
|
4
|
+
import type { ResolvedWebLimits, WebAdapterOptions, WebProvider, WebProviderMetadata } from "./types.js";
|
|
5
|
+
|
|
6
|
+
export class WebToolError extends Error {
|
|
7
|
+
readonly code: string;
|
|
8
|
+
constructor(code: string, message: string) {
|
|
9
|
+
super(message);
|
|
10
|
+
this.name = "WebToolError";
|
|
11
|
+
this.code = code;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* How a request may be retried, chosen by what a provider bills:
|
|
17
|
+
* - `idempotent`: a GET — retry a connection failure and any 429/5xx.
|
|
18
|
+
* - `safe-post`: a POST that creates or bills nothing twice on a 429 retry — retry a connection
|
|
19
|
+
* failure and a 429, never a 5xx (the request may already have been accepted).
|
|
20
|
+
* - `never`: retry nothing, so a paid job is never submitted twice.
|
|
21
|
+
*/
|
|
22
|
+
export type WebRetryMode = "idempotent" | "safe-post" | "never";
|
|
23
|
+
|
|
24
|
+
export interface WebTransport {
|
|
25
|
+
readonly limits: ResolvedWebLimits;
|
|
26
|
+
run<T>(signal: AbortSignal | undefined, operation: (signal: AbortSignal) => Promise<T>): Promise<T>;
|
|
27
|
+
json(
|
|
28
|
+
provider: WebProvider,
|
|
29
|
+
credentialName: string,
|
|
30
|
+
url: URL,
|
|
31
|
+
init: RequestInit,
|
|
32
|
+
signal: AbortSignal,
|
|
33
|
+
options?: { readonly retry?: WebRetryMode },
|
|
34
|
+
): Promise<{ value: unknown; metadata?: WebProviderMetadata }>;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** How a transport authenticates and whether it may reach a plaintext loopback origin. */
|
|
38
|
+
export interface WebTransportOptions {
|
|
39
|
+
/** Accept an `http://` origin only when its hostname is loopback (SearXNG on the same machine). */
|
|
40
|
+
readonly allowLoopback?: boolean;
|
|
41
|
+
/** `bearer` (default), `bot` (`Authorization: Bot <key>`, Kagi v1), or `none` (a keyless backend). */
|
|
42
|
+
readonly auth?: "bearer" | "bot" | "none";
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function createWebTransport(
|
|
46
|
+
provider: WebProvider,
|
|
47
|
+
apiOrigin: string,
|
|
48
|
+
options: WebAdapterOptions,
|
|
49
|
+
transport: WebTransportOptions = {},
|
|
50
|
+
): WebTransport {
|
|
51
|
+
const limits = resolveWebLimits(options.limits),
|
|
52
|
+
origin = new URL(apiOrigin).origin;
|
|
53
|
+
const allowed = (options.allowedOrigins ?? [origin]).map((value) => {
|
|
54
|
+
let parsed: URL;
|
|
55
|
+
try {
|
|
56
|
+
parsed = new URL(value);
|
|
57
|
+
} catch {
|
|
58
|
+
throw new WebToolError("ERR_PRISM_WEB_ORIGIN", "Provider API origin is invalid");
|
|
59
|
+
}
|
|
60
|
+
// Plaintext is allowed only where it cannot leave the machine: an explicitly opted-in loopback origin.
|
|
61
|
+
const loopback = parsed.protocol === "http:" && transport.allowLoopback === true && isLoopbackHostname(parsed.hostname);
|
|
62
|
+
if (
|
|
63
|
+
(!loopback && parsed.protocol !== "https:") ||
|
|
64
|
+
parsed.username ||
|
|
65
|
+
parsed.password ||
|
|
66
|
+
parsed.pathname !== "/" ||
|
|
67
|
+
parsed.search ||
|
|
68
|
+
parsed.hash ||
|
|
69
|
+
value !== parsed.origin
|
|
70
|
+
)
|
|
71
|
+
throw new WebToolError(
|
|
72
|
+
"ERR_PRISM_WEB_ORIGIN",
|
|
73
|
+
"Provider API origins must be exact HTTPS origins (loopback HTTP only with allowLoopback)",
|
|
74
|
+
);
|
|
75
|
+
return parsed.origin;
|
|
76
|
+
});
|
|
77
|
+
if (!allowed.includes(origin)) throw new WebToolError("ERR_PRISM_WEB_ORIGIN", `Required ${provider} API origin is not allowed`);
|
|
78
|
+
let active = 0;
|
|
79
|
+
const waiters: Array<() => void> = [];
|
|
80
|
+
const acquire = async (signal: AbortSignal) => {
|
|
81
|
+
if (active < limits.maxConcurrency) {
|
|
82
|
+
active++;
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
// Past the queue bound a call fails; up to it, a call waits for the next free slot.
|
|
86
|
+
if (waiters.length >= limits.maxQueued) throw new WebToolError("ERR_PRISM_WEB_LIMIT", "Web operation queue is full");
|
|
87
|
+
await new Promise<void>((resolve, reject) => {
|
|
88
|
+
const start = () => {
|
|
89
|
+
signal.removeEventListener("abort", abort);
|
|
90
|
+
active++;
|
|
91
|
+
resolve();
|
|
92
|
+
};
|
|
93
|
+
const abort = () => {
|
|
94
|
+
const i = waiters.indexOf(start);
|
|
95
|
+
if (i >= 0) waiters.splice(i, 1);
|
|
96
|
+
reject(signal.reason);
|
|
97
|
+
};
|
|
98
|
+
waiters.push(start);
|
|
99
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
100
|
+
});
|
|
101
|
+
};
|
|
102
|
+
const release = () => {
|
|
103
|
+
active--;
|
|
104
|
+
waiters.shift()?.();
|
|
105
|
+
};
|
|
106
|
+
const sleep =
|
|
107
|
+
options.sleep ??
|
|
108
|
+
((ms: number, signal: AbortSignal) =>
|
|
109
|
+
new Promise<void>((resolve, reject) => {
|
|
110
|
+
try {
|
|
111
|
+
signal.throwIfAborted();
|
|
112
|
+
} catch (error) {
|
|
113
|
+
reject(error);
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
const timer = setTimeout(done, ms);
|
|
117
|
+
function done() {
|
|
118
|
+
cleanup();
|
|
119
|
+
resolve();
|
|
120
|
+
}
|
|
121
|
+
function abort() {
|
|
122
|
+
clearTimeout(timer);
|
|
123
|
+
cleanup();
|
|
124
|
+
reject(signal.reason);
|
|
125
|
+
}
|
|
126
|
+
function cleanup() {
|
|
127
|
+
signal.removeEventListener("abort", abort);
|
|
128
|
+
}
|
|
129
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
130
|
+
}));
|
|
131
|
+
const fetcher = options.fetch ?? globalThis.fetch;
|
|
132
|
+
return {
|
|
133
|
+
limits,
|
|
134
|
+
async run(signal, operation) {
|
|
135
|
+
const timeout = AbortSignal.timeout(limits.timeoutMs);
|
|
136
|
+
const combined = signal ? AbortSignal.any([signal, timeout]) : timeout;
|
|
137
|
+
let acquired = false;
|
|
138
|
+
try {
|
|
139
|
+
combined.throwIfAborted();
|
|
140
|
+
await acquire(combined);
|
|
141
|
+
acquired = true;
|
|
142
|
+
return await operation(combined);
|
|
143
|
+
} catch (error) {
|
|
144
|
+
if (combined.aborted)
|
|
145
|
+
throw new WebToolError("ERR_PRISM_WEB_ABORTED", signal?.aborted ? "Web operation aborted" : "Web operation timed out");
|
|
146
|
+
throw error;
|
|
147
|
+
} finally {
|
|
148
|
+
if (acquired) release();
|
|
149
|
+
}
|
|
150
|
+
},
|
|
151
|
+
async json(_provider, _credentialName, url, init, signal, request = {}) {
|
|
152
|
+
const retry = request.retry ?? "idempotent";
|
|
153
|
+
const mayRetryStatus = (status: number) =>
|
|
154
|
+
retry === "idempotent" ? status === 429 || status >= 500 : retry === "safe-post" ? status === 429 : false;
|
|
155
|
+
if (url.origin !== origin) throw new WebToolError("ERR_PRISM_WEB_ORIGIN", `${provider} request origin is not allowed`);
|
|
156
|
+
let credential: string | undefined;
|
|
157
|
+
if (transport.auth !== "none") {
|
|
158
|
+
try {
|
|
159
|
+
credential = await options.credentials();
|
|
160
|
+
} catch {
|
|
161
|
+
throw new WebToolError("ERR_PRISM_WEB_CREDENTIAL", `${provider} credential resolution failed`);
|
|
162
|
+
}
|
|
163
|
+
if (!credential) throw new WebToolError("ERR_PRISM_WEB_CREDENTIAL", `${provider} credential is unavailable`);
|
|
164
|
+
}
|
|
165
|
+
const requestBytes = typeof init.body === "string" ? Buffer.byteLength(init.body) : 0;
|
|
166
|
+
if (requestBytes > limits.maxRequestBytes) throw new WebToolError("ERR_PRISM_WEB_LIMIT", "Web request exceeds byte limit");
|
|
167
|
+
for (let attempt = 0; ; attempt++) {
|
|
168
|
+
let response: Response;
|
|
169
|
+
try {
|
|
170
|
+
response = await fetcher(url, {
|
|
171
|
+
...init,
|
|
172
|
+
redirect: "error",
|
|
173
|
+
signal,
|
|
174
|
+
headers: {
|
|
175
|
+
"content-type": "application/json",
|
|
176
|
+
...init.headers,
|
|
177
|
+
...(credential === undefined
|
|
178
|
+
? {}
|
|
179
|
+
: transport.auth === "bot"
|
|
180
|
+
? { authorization: `Bot ${credential}` }
|
|
181
|
+
: provider === "brave"
|
|
182
|
+
? { "x-subscription-token": credential }
|
|
183
|
+
: { authorization: `Bearer ${credential}` }),
|
|
184
|
+
},
|
|
185
|
+
});
|
|
186
|
+
} catch (error) {
|
|
187
|
+
if (signal.aborted) throw error;
|
|
188
|
+
// A connection failure produced no response, so no retry mode rules it out except `never`.
|
|
189
|
+
if (retry === "never" || attempt >= limits.maxRetries)
|
|
190
|
+
throw new WebToolError("ERR_PRISM_WEB_REQUEST", `${provider} request failed`);
|
|
191
|
+
await sleep(Math.min(250 * 2 ** attempt, limits.maxRateLimitDelayMs), signal);
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
const metadata = responseMetadata(response);
|
|
195
|
+
if (mayRetryStatus(response.status) && attempt < limits.maxRetries) {
|
|
196
|
+
await response.body?.cancel();
|
|
197
|
+
await sleep(Math.min(metadata?.rate?.retryAfterMs ?? 250 * 2 ** attempt, limits.maxRateLimitDelayMs), signal);
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
if (!response.ok) {
|
|
201
|
+
await response.body?.cancel();
|
|
202
|
+
throw new WebToolError("ERR_PRISM_WEB_RESPONSE", `${provider} request failed with status ${response.status}`);
|
|
203
|
+
}
|
|
204
|
+
const text = await readBoundedText(response, limits.maxResponseBytes, signal);
|
|
205
|
+
try {
|
|
206
|
+
const value: unknown = redactSecrets(JSON.parse(text), credential === undefined ? [] : [credential]);
|
|
207
|
+
assertBoundedJson(value, limits.maxJsonDepth, limits.maxJsonProperties, limits.maxResponseBytes);
|
|
208
|
+
return { value, metadata };
|
|
209
|
+
} catch (error) {
|
|
210
|
+
if (error instanceof WebToolError) throw error;
|
|
211
|
+
throw new WebToolError("ERR_PRISM_WEB_RESPONSE", `${provider} returned invalid JSON`);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
},
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export function boundedJson<T>(value: T, maxDepth: number, maxProperties: number, maxBytes: number): T {
|
|
219
|
+
let clone: T;
|
|
220
|
+
try {
|
|
221
|
+
// Deliberate JSON round-trip, not structuredClone: normalizes the value to its wire shape (drops undefined keys) before bounds validation.
|
|
222
|
+
clone = JSON.parse(JSON.stringify(value)) as T;
|
|
223
|
+
} catch {
|
|
224
|
+
throw new WebToolError("ERR_PRISM_WEB_JSON", "JSON value is cyclic or unserializable");
|
|
225
|
+
}
|
|
226
|
+
assertBoundedJson(clone, maxDepth, maxProperties, maxBytes);
|
|
227
|
+
return clone;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
export function assertBoundedJson(value: unknown, maxDepth: number, maxProperties: number, maxBytes: number): asserts value is JsonValue {
|
|
231
|
+
let properties = 0,
|
|
232
|
+
bytes: number;
|
|
233
|
+
try {
|
|
234
|
+
bytes = Buffer.byteLength(JSON.stringify(value));
|
|
235
|
+
} catch {
|
|
236
|
+
throw new WebToolError("ERR_PRISM_WEB_JSON", "JSON value is cyclic or unserializable");
|
|
237
|
+
}
|
|
238
|
+
if (bytes > maxBytes) throw new WebToolError("ERR_PRISM_WEB_LIMIT", "JSON value exceeds byte limit");
|
|
239
|
+
const stack: Array<[unknown, number]> = [[value, 0]];
|
|
240
|
+
while (stack.length) {
|
|
241
|
+
const [entry, depth] = stack.pop()!;
|
|
242
|
+
if (depth > maxDepth) throw new WebToolError("ERR_PRISM_WEB_LIMIT", "JSON value exceeds depth limit");
|
|
243
|
+
if (entry === null || typeof entry === "string" || typeof entry === "boolean") continue;
|
|
244
|
+
if (typeof entry === "number") {
|
|
245
|
+
if (!Number.isFinite(entry)) throw new WebToolError("ERR_PRISM_WEB_JSON", "JSON number must be finite");
|
|
246
|
+
continue;
|
|
247
|
+
}
|
|
248
|
+
if (typeof entry !== "object") throw new WebToolError("ERR_PRISM_WEB_JSON", "Value is not JSON");
|
|
249
|
+
const values = Array.isArray(entry) ? entry : Object.values(entry);
|
|
250
|
+
if (!Array.isArray(entry)) {
|
|
251
|
+
for (const key of Object.keys(entry))
|
|
252
|
+
if (["__proto__", "prototype", "constructor"].includes(key))
|
|
253
|
+
throw new WebToolError("ERR_PRISM_WEB_JSON", "JSON contains forbidden key");
|
|
254
|
+
properties += values.length;
|
|
255
|
+
if (properties > maxProperties) throw new WebToolError("ERR_PRISM_WEB_LIMIT", "JSON value exceeds property limit");
|
|
256
|
+
}
|
|
257
|
+
for (const child of values) stack.push([child, depth + 1]);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
async function readBoundedText(response: Response, maxBytes: number, signal: AbortSignal): Promise<string> {
|
|
262
|
+
if (!response.body) return "";
|
|
263
|
+
const reader = response.body.getReader();
|
|
264
|
+
const chunks: Uint8Array[] = [];
|
|
265
|
+
let total = 0;
|
|
266
|
+
try {
|
|
267
|
+
for (;;) {
|
|
268
|
+
signal.throwIfAborted();
|
|
269
|
+
const { done, value } = await reader.read();
|
|
270
|
+
if (done) break;
|
|
271
|
+
total += value.byteLength;
|
|
272
|
+
if (total > maxBytes) throw new WebToolError("ERR_PRISM_WEB_LIMIT", "Web response exceeds byte limit");
|
|
273
|
+
chunks.push(value);
|
|
274
|
+
}
|
|
275
|
+
} finally {
|
|
276
|
+
reader.releaseLock();
|
|
277
|
+
}
|
|
278
|
+
const bytes = new Uint8Array(total);
|
|
279
|
+
let offset = 0;
|
|
280
|
+
for (const chunk of chunks) {
|
|
281
|
+
bytes.set(chunk, offset);
|
|
282
|
+
offset += chunk.byteLength;
|
|
283
|
+
}
|
|
284
|
+
return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
285
|
+
}
|
|
286
|
+
/** `Retry-After` is either delay-seconds or an HTTP date (RFC 9110 §10.2.3); both become a delay from now. */
|
|
287
|
+
function retryAfterDelayMs(header: string | null): number | undefined {
|
|
288
|
+
if (!header) return undefined;
|
|
289
|
+
if (/^\d+(?:\.\d+)?$/u.test(header)) return Number(header) * 1000;
|
|
290
|
+
const at = Date.parse(header);
|
|
291
|
+
return Number.isNaN(at) ? undefined : Math.max(0, at - Date.now());
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function responseMetadata(response: Response): WebProviderMetadata | undefined {
|
|
295
|
+
const number = (name: string) => {
|
|
296
|
+
const value = response.headers.get(name);
|
|
297
|
+
return value && /^\d+$/u.test(value) ? Number(value) : undefined;
|
|
298
|
+
};
|
|
299
|
+
const retryAfterMs = retryAfterDelayMs(response.headers.get("retry-after"));
|
|
300
|
+
const metadata = {
|
|
301
|
+
requestId: response.headers.get("x-request-id") ?? undefined,
|
|
302
|
+
rate: {
|
|
303
|
+
limit: number("x-ratelimit-limit"),
|
|
304
|
+
remaining: number("x-ratelimit-remaining"),
|
|
305
|
+
reset: response.headers.get("x-ratelimit-reset") ?? undefined,
|
|
306
|
+
retryAfterMs,
|
|
307
|
+
},
|
|
308
|
+
};
|
|
309
|
+
return metadata.requestId || Object.values(metadata.rate).some((v) => v !== undefined) ? metadata : undefined;
|
|
310
|
+
}
|
package/web/src/types.ts
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import type { JsonObject, JsonValue, SsrfPolicy, ToolArgumentValidator, ToolDefinition, Usage } from "@arnilo/prism";
|
|
2
|
+
|
|
3
|
+
import type { DomainPolicy } from "./domains.js";
|
|
4
|
+
|
|
5
|
+
export type WebProvider = "brave" | "exa" | "firecrawl" | "native" | "obscura" | "searxng" | "tavily" | "kagi";
|
|
6
|
+
/** The backends that answer `web_search` (the others are fetch/extract only). */
|
|
7
|
+
export type WebSearchProvider = "brave" | "exa" | "searxng" | "tavily" | "kagi";
|
|
8
|
+
export interface WebRateMetadata {
|
|
9
|
+
readonly limit?: number;
|
|
10
|
+
readonly remaining?: number;
|
|
11
|
+
readonly reset?: string;
|
|
12
|
+
readonly retryAfterMs?: number;
|
|
13
|
+
}
|
|
14
|
+
export interface WebProviderMetadata {
|
|
15
|
+
readonly requestId?: string;
|
|
16
|
+
readonly cost?: JsonValue;
|
|
17
|
+
readonly rate?: WebRateMetadata;
|
|
18
|
+
}
|
|
19
|
+
export interface WebCitation {
|
|
20
|
+
readonly citationId: string;
|
|
21
|
+
readonly provider: WebProvider;
|
|
22
|
+
readonly sourceId?: string;
|
|
23
|
+
readonly url: string;
|
|
24
|
+
}
|
|
25
|
+
export interface WebSearchResult extends WebCitation {
|
|
26
|
+
readonly title?: string;
|
|
27
|
+
readonly snippet?: string;
|
|
28
|
+
readonly highlights?: readonly string[];
|
|
29
|
+
readonly publishedAt?: string;
|
|
30
|
+
readonly retrievedAt: string;
|
|
31
|
+
}
|
|
32
|
+
export interface WebSearchResponse {
|
|
33
|
+
readonly provider: WebSearchProvider;
|
|
34
|
+
readonly query: string;
|
|
35
|
+
readonly results: readonly WebSearchResult[];
|
|
36
|
+
/** Results dropped by the host domain policy. */
|
|
37
|
+
readonly filtered?: number;
|
|
38
|
+
readonly metadata?: WebProviderMetadata;
|
|
39
|
+
readonly untrusted: true;
|
|
40
|
+
}
|
|
41
|
+
export interface WebDocument extends WebCitation {
|
|
42
|
+
readonly title?: string;
|
|
43
|
+
readonly markdown: string;
|
|
44
|
+
/** The page was longer than the fetch limit; `markdown` keeps its head only. */
|
|
45
|
+
readonly sourceTruncated?: boolean;
|
|
46
|
+
/** Targets followed, in order; `url` is the final one. Only the `native` backend reports them. */
|
|
47
|
+
readonly redirects?: readonly string[];
|
|
48
|
+
readonly retrievedAt: string;
|
|
49
|
+
/** Served from the session cache; `cachedAgeMs` is how old the stored copy is. Only with `cache` config. */
|
|
50
|
+
readonly cached?: true;
|
|
51
|
+
readonly cachedAgeMs?: number;
|
|
52
|
+
readonly metadata?: WebProviderMetadata;
|
|
53
|
+
readonly untrusted: true;
|
|
54
|
+
}
|
|
55
|
+
export interface WebExtraction {
|
|
56
|
+
readonly provider: "firecrawl";
|
|
57
|
+
readonly urls: readonly string[];
|
|
58
|
+
readonly data: JsonValue;
|
|
59
|
+
readonly retrievedAt: string;
|
|
60
|
+
readonly metadata?: WebProviderMetadata;
|
|
61
|
+
readonly untrusted: true;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface WebLimits {
|
|
65
|
+
readonly maxQueryBytes?: number;
|
|
66
|
+
readonly maxResults?: number;
|
|
67
|
+
readonly maxUrls?: number;
|
|
68
|
+
readonly maxRequestBytes?: number;
|
|
69
|
+
readonly maxResponseBytes?: number;
|
|
70
|
+
readonly maxMarkdownBytes?: number;
|
|
71
|
+
readonly maxExtractBytes?: number;
|
|
72
|
+
readonly maxSchemaBytes?: number;
|
|
73
|
+
readonly maxAggregateBytes?: number;
|
|
74
|
+
readonly maxJsonDepth?: number;
|
|
75
|
+
readonly maxJsonProperties?: number;
|
|
76
|
+
readonly maxRetries?: number;
|
|
77
|
+
readonly maxRateLimitDelayMs?: number;
|
|
78
|
+
readonly maxConcurrency?: number;
|
|
79
|
+
/** Calls waiting for a slot; only past this many does the transport refuse a new one. */
|
|
80
|
+
readonly maxQueued?: number;
|
|
81
|
+
readonly maxPollingAttempts?: number;
|
|
82
|
+
readonly pollingDelayMs?: number;
|
|
83
|
+
readonly timeoutMs?: number;
|
|
84
|
+
}
|
|
85
|
+
export type ResolvedWebLimits = Required<WebLimits>;
|
|
86
|
+
/** A lazily resolved key (the kit's `SecretSource` shape); the package builds it from an `env:`/`keyring:` ref. A literal string does not type-check. */
|
|
87
|
+
export type WebCredentialSource = () => Promise<string | undefined>;
|
|
88
|
+
export interface WebAdapterOptions {
|
|
89
|
+
readonly credentials: WebCredentialSource;
|
|
90
|
+
readonly allowedOrigins?: readonly string[];
|
|
91
|
+
readonly fetch?: typeof globalThis.fetch;
|
|
92
|
+
readonly limits?: WebLimits;
|
|
93
|
+
readonly sleep?: (ms: number, signal: AbortSignal) => Promise<void>;
|
|
94
|
+
}
|
|
95
|
+
export type SearchFreshness = "day" | "week" | "month" | "year";
|
|
96
|
+
/** Structured search controls: the tool schema exposes only what a backend supports. */
|
|
97
|
+
export interface SearchControls {
|
|
98
|
+
readonly freshness?: SearchFreshness;
|
|
99
|
+
readonly includeDomains?: readonly string[];
|
|
100
|
+
readonly excludeDomains?: readonly string[];
|
|
101
|
+
readonly country?: string;
|
|
102
|
+
readonly language?: string;
|
|
103
|
+
readonly offset?: number;
|
|
104
|
+
}
|
|
105
|
+
export type SearchControl = keyof SearchControls;
|
|
106
|
+
/** Narrowed value sets for a control a backend supports only partly (the tool schema follows them). */
|
|
107
|
+
export interface WebSearchControlValues {
|
|
108
|
+
readonly freshness?: readonly SearchFreshness[];
|
|
109
|
+
}
|
|
110
|
+
export interface WebSearchCall extends SearchControls {
|
|
111
|
+
readonly count?: number;
|
|
112
|
+
readonly signal?: AbortSignal;
|
|
113
|
+
}
|
|
114
|
+
export interface WebSearchAdapter {
|
|
115
|
+
readonly provider: WebSearchProvider;
|
|
116
|
+
/** Resolved limits, so `web_search` can state the real `count` maximum. */
|
|
117
|
+
readonly limits?: WebLimits;
|
|
118
|
+
/** One table per adapter, read by the tool builder and by the adapter's own validation. */
|
|
119
|
+
readonly supports: ReadonlySet<SearchControl>;
|
|
120
|
+
/** The values a backend accepts for a supported control, when it accepts only some (e.g. no `week`). */
|
|
121
|
+
readonly controlValues?: WebSearchControlValues;
|
|
122
|
+
search(query: string, options?: WebSearchCall): Promise<WebSearchResponse>;
|
|
123
|
+
}
|
|
124
|
+
/** One fetch request. `sessionId` opts into the per-session cache; `refresh` bypasses a cached page. */
|
|
125
|
+
export interface WebFetchOptions {
|
|
126
|
+
readonly signal?: AbortSignal;
|
|
127
|
+
/** Lowers the Markdown kept (`maxMarkdownBytes` is the ceiling) and flags the truncation. */
|
|
128
|
+
readonly maxBytes?: number;
|
|
129
|
+
/** The session the page belongs to. Without it the cache is not consulted and nothing is stored. */
|
|
130
|
+
readonly sessionId?: string;
|
|
131
|
+
/** Fetch again even when the session has a fresh cached page. */
|
|
132
|
+
readonly refresh?: boolean;
|
|
133
|
+
}
|
|
134
|
+
export interface WebFetchAdapter {
|
|
135
|
+
readonly provider: "firecrawl" | "native";
|
|
136
|
+
fetch(url: string, options?: WebFetchOptions): Promise<WebDocument>;
|
|
137
|
+
}
|
|
138
|
+
export interface WebExtractAdapter {
|
|
139
|
+
readonly provider: "firecrawl";
|
|
140
|
+
extract(urls: readonly string[], options?: { readonly signal?: AbortSignal }): Promise<WebExtraction>;
|
|
141
|
+
}
|
|
142
|
+
export interface FirecrawlTargetPolicy {
|
|
143
|
+
readonly ssrf?: SsrfPolicy;
|
|
144
|
+
readonly validateUrl?: (url: URL, signal?: AbortSignal) => void | Promise<void>;
|
|
145
|
+
}
|
|
146
|
+
export interface FirecrawlExtractOptions extends WebAdapterOptions, FirecrawlTargetPolicy {
|
|
147
|
+
readonly schema: JsonObject;
|
|
148
|
+
readonly validator: ToolArgumentValidator;
|
|
149
|
+
}
|
|
150
|
+
export interface FirecrawlFetchOptions extends WebAdapterOptions, FirecrawlTargetPolicy {}
|
|
151
|
+
/** A page and how long ago it was fetched, as the cache returns it. */
|
|
152
|
+
export interface CachedPage {
|
|
153
|
+
readonly document: WebDocument;
|
|
154
|
+
readonly ageMs: number;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* A session-scoped page cache. `annotated` marks entries with `cached`/`cachedAgeMs` and adds the
|
|
158
|
+
* model's `refresh` argument; the fixed memo Prism keeps without `cache` config is anonymous.
|
|
159
|
+
*/
|
|
160
|
+
export interface FetchCache {
|
|
161
|
+
readonly annotated: boolean;
|
|
162
|
+
/** Keyed by session id plus canonical URL; `undefined` never matches and never stores. */
|
|
163
|
+
read(sessionId: string | undefined, url: string, now?: number): CachedPage | undefined;
|
|
164
|
+
write(sessionId: string | undefined, url: string, document: WebDocument): void;
|
|
165
|
+
/** Drops one session's pages; called from the `session.closed` hook. */
|
|
166
|
+
dropSession(sessionId: string): void;
|
|
167
|
+
}
|
|
168
|
+
/** The answer to a `web_fetch` question, plus what the sub-call cost (outside the run's budget). */
|
|
169
|
+
export interface PromptAnswer {
|
|
170
|
+
readonly answer: string;
|
|
171
|
+
readonly inputChars: number;
|
|
172
|
+
readonly sourceTruncated: boolean;
|
|
173
|
+
readonly usage?: Usage;
|
|
174
|
+
}
|
|
175
|
+
/** Opt-in `fetch.prompt` support: one bounded, tool-free model call over the page. */
|
|
176
|
+
export interface PromptFetch {
|
|
177
|
+
answer(page: WebDocument, question: string, options?: { readonly signal?: AbortSignal }): Promise<PromptAnswer>;
|
|
178
|
+
}
|
|
179
|
+
export interface WebToolsOptions {
|
|
180
|
+
readonly search?: WebSearchAdapter;
|
|
181
|
+
readonly fetch?: WebFetchAdapter;
|
|
182
|
+
readonly extract?: WebExtractAdapter;
|
|
183
|
+
/** Host-owned permission: filters search rows and refuses non-permitted fetch/extract targets. */
|
|
184
|
+
readonly domains?: DomainPolicy;
|
|
185
|
+
/** Per-session page cache; without it `web_fetch` uses the short anonymous paging memo. */
|
|
186
|
+
readonly cache?: FetchCache;
|
|
187
|
+
/** Adds the `web_fetch` `prompt` argument: one model call answers a question from the page. */
|
|
188
|
+
readonly prompt?: PromptFetch;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/** The `web.fetch` service `@arnilo/prism-tools/web` provides when a fetch backend is configured. */
|
|
192
|
+
export interface WebFetchService {
|
|
193
|
+
fetch(url: string, options?: WebFetchOptions): Promise<WebDocument>;
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* @deprecated No replacement: nothing reads it since the 0.13 split. Kept for the deprecation
|
|
197
|
+
* wave and removed at the next breaking cut (docs/_evidence/dead-export-verification-2026-09-03.md).
|
|
198
|
+
*/
|
|
199
|
+
export type WebToolSet = readonly ToolDefinition[];
|
package/work/README.md
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# @arnilo/prism-tools/work
|
|
2
|
+
|
|
3
|
+
First-party work tools for Prism: Office documents, spreadsheets, diagrams, M365/Google connectors, and a bounded document reader, as one package with a `definePackage` entry and the 0.12 family subpaths kept for direct imports.
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
import toolsWork from "@arnilo/prism-tools/work";
|
|
7
|
+
import { createAgent } from "@arnilo/prism";
|
|
8
|
+
|
|
9
|
+
const agent = await createAgent({
|
|
10
|
+
model,
|
|
11
|
+
packages: [toolsWork({ root: process.cwd() })],
|
|
12
|
+
});
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## What it registers
|
|
16
|
+
|
|
17
|
+
| Group | Tools | Effects | Enabled by |
|
|
18
|
+
| --- | --- | --- | --- |
|
|
19
|
+
| Office documents | `office_parse`, `office_import`, `office_diff`, `office_preview` | `read` | `office.enabled` (default `true`) |
|
|
20
|
+
| Office documents | `office_generate`, `office_patch` | `write` | `office.enabled` (default `true`) |
|
|
21
|
+
| Sandbox exec | `work_exec` | `execute` | `work.enabled` (default `true`) plus a host `work.sandbox` service |
|
|
22
|
+
| Connectors | `m365_*` / `gws_*` | `network` (reads) or `network` + `external` (drafts/writes) | a resolvable `connectors.*.credential` ref |
|
|
23
|
+
|
|
24
|
+
`office_parse` / `office_import` return results tagged `trust: "untrusted_external"`, which the kernel
|
|
25
|
+
wraps in its envelope (`docs/tools.md`). `work_exec` runs an argv command inside the host-injected `WorkSandbox`; without that service the package notes why and registers the rest.
|
|
26
|
+
|
|
27
|
+
The bounded document reader is **not** a tool: it is provided as the `work.reader` service so `@arnilo/prism-tools/coding`'s `read` tool can consume it.
|
|
28
|
+
|
|
29
|
+
The vendored Hermes MIT skills (`docx`, `xlsx`, `powerpoint`, `pdf`) are registered by `loadWorkSkills()` from `vendor/hermes-agent/skills/productivity`, each declaring `requiresTools: work_exec` (`WORK_SKILL_REQUIRES_TOOLS`). They teach `python`, `openpyxl`, `python-docx`, `pypdf` and `soffice` run through the sandbox and never name an `office_*` tool, so a request without `work_exec` (no `work.sandbox` service) hides them. The vendored bytes are unchanged and pinned by `vendor-lock.json`; their 9.6-12.5 KB bodies are the one recorded size exception to the package-skill body cap.
|
|
30
|
+
|
|
31
|
+
## Config
|
|
32
|
+
|
|
33
|
+
| Field | Default | Notes |
|
|
34
|
+
| --- | --- | --- |
|
|
35
|
+
| `root` | — | **Required.** Absolute workspace root the file-touching tools stay inside. |
|
|
36
|
+
| `office.enabled` | `true` | `false` registers no `office_*` tool. |
|
|
37
|
+
| `office.maxBytes` | document cap default | OOXML byte cap. |
|
|
38
|
+
| `work.enabled` | `true` | `false` drops `work_exec`. |
|
|
39
|
+
| `reader.formats` | all four | `["pdf", "docx", "xlsx", "pptx"]` subset. A format whose optional peer is missing is dropped with one warn note. |
|
|
40
|
+
| `reader.maxBytes` / `maxPages` / `maxTextBytes` | reader defaults | Hard caps. |
|
|
41
|
+
| `reader.ocr` | off | `{ provider: "mistral", apiKey: "env:MISTRAL_API_KEY" }`; **one external call per page**. |
|
|
42
|
+
| `connectors.microsoft365` | none | `{ credential: "keyring:m365", binary?, configDir, identity, allowedOps? }`. |
|
|
43
|
+
| `connectors.google` | none | `{ credential: "keyring:gws", binary?, configDir, identity, allowedOps? }`. |
|
|
44
|
+
| `env` | `{}` | Non-token env for the sandbox. `M365_*` / `GOOGLE_*` names are refused. |
|
|
45
|
+
|
|
46
|
+
`connectors.*.credential` and `reader.ocr.apiKey` are ref-only (`env:NAME` or `keyring:NAME`) and resolved through `prism.credentials.resolve`; a literal fails config validation and a resolved value is redacted from events, notes, logs, errors, and tool results. A credential that resolves to a JSON object is injected as the connector's token env map; any other value becomes the provider's default access-token variable.
|
|
47
|
+
|
|
48
|
+
## Optional peers
|
|
49
|
+
|
|
50
|
+
| Peer | Unlocks | Missing |
|
|
51
|
+
| --- | --- | --- |
|
|
52
|
+
| `mammoth` | `docx` reader format | that format is dropped with a note |
|
|
53
|
+
| `pdf-parse` | `pdf` reader format | that format is dropped with a note |
|
|
54
|
+
| `@firecrawl/anydoc` | `/document-extraction` local Markdown conversion | `createDocumentExtractor()` throws at call time |
|
|
55
|
+
|
|
56
|
+
## Direct subpath imports
|
|
57
|
+
|
|
58
|
+
The family shares type names (`validateByteCap`, `CellValue`) across libraries, so a single flat barrel cannot re-export them unambiguously. The root entry holds the `definePackage` surface; the libraries stay importable as subpaths:
|
|
59
|
+
|
|
60
|
+
`@arnilo/prism-tools/work/{connectors,connectors/drafts,connectors/google-workspace,connectors/microsoft365,documents,sheets,diagrams,document-extraction,document-reader,sandbox,skills,tools}`
|
|
61
|
+
|
|
62
|
+
## Install
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
bun add @arnilo/prism @arnilo/prism-tools
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Security and cost
|
|
69
|
+
|
|
70
|
+
- File tools stay inside `root`; paths are resolved and contained before any read or write.
|
|
71
|
+
- Connector credentials and the OCR key are ref-only and redacted everywhere after resolution.
|
|
72
|
+
- The worker pool starts lazily and is closed in `onDispose`.
|
|
73
|
+
- OCR is off by default and costs one external Mistral call per page when enabled.
|
|
74
|
+
|
|
75
|
+
## Optional Office peers
|
|
76
|
+
|
|
77
|
+
Install `@office-open/docx@0.14.5`, `@office-open/pptx@0.14.5`, `@office-open/xlsx@0.14.5`, `@office-open/xml@0.14.5`, `ajv` and `fast-xml-parser@5.11.1` for Office tools and OOXML reading. Missing peers produce a status note and disable Office tools; connectors and skills still register. Direct documents, sheets and diagrams library imports require their peers. See the [bundle footprint](../README.md#dependencies-and-footprint).
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
Hermes Agent productivity skills (docx, xlsx, powerpoint, pdf)
|
|
2
|
+
Source: https://github.com/NousResearch/hermes-agent
|
|
3
|
+
Commit: bbaf7af5c83546d19f8060f4097d3bb25cd1a3c3
|
|
4
|
+
License: MIT
|
|
5
|
+
|
|
6
|
+
Copyright (c) 2025 Nous Research
|
|
7
|
+
|
|
8
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
9
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
10
|
+
in the Software without restriction, including without limitation the rights
|
|
11
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
12
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
13
|
+
furnished to do so, subject to the following conditions:
|
|
14
|
+
|
|
15
|
+
The above copyright notice and this permission notice shall be included in all
|
|
16
|
+
copies or substantial portions of the Software.
|
|
17
|
+
|
|
18
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
19
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
20
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
21
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
22
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
23
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
24
|
+
SOFTWARE.
|