@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.
Files changed (331) hide show
  1. package/CHANGELOG.md +418 -0
  2. package/LICENSE +21 -0
  3. package/README.md +44 -2
  4. package/browser/README.md +98 -0
  5. package/browser/skills/browser-automation/SKILL.md +14 -0
  6. package/browser/src/actions/cdp.ts +131 -0
  7. package/browser/src/actions/dialog.ts +25 -0
  8. package/browser/src/actions/download_release.ts +22 -0
  9. package/browser/src/actions/index.ts +37 -0
  10. package/browser/src/actions/locator.ts +70 -0
  11. package/browser/src/actions/navigate.ts +23 -0
  12. package/browser/src/actions/screenshot.ts +27 -0
  13. package/browser/src/actions/scroll.ts +24 -0
  14. package/browser/src/actions/select_page.ts +21 -0
  15. package/browser/src/actions/types.ts +114 -0
  16. package/browser/src/actions/upload.ts +44 -0
  17. package/browser/src/actions/wait.ts +45 -0
  18. package/browser/src/browser.ts +197 -0
  19. package/browser/src/cdp.ts +178 -0
  20. package/browser/src/checkpoint.ts +189 -0
  21. package/browser/src/config.ts +72 -0
  22. package/browser/src/downloads.ts +253 -0
  23. package/browser/src/errors.ts +31 -0
  24. package/browser/src/evaluate.ts +101 -0
  25. package/browser/src/index.ts +129 -0
  26. package/browser/src/limits.ts +271 -0
  27. package/browser/src/manager.ts +730 -0
  28. package/browser/src/network.ts +263 -0
  29. package/browser/src/observe.ts +178 -0
  30. package/browser/src/policy.ts +133 -0
  31. package/browser/src/screenshot.ts +107 -0
  32. package/browser/src/shared-sandbox.ts +53 -0
  33. package/browser/src/snapshot.ts +99 -0
  34. package/browser/src/targets.ts +165 -0
  35. package/browser/src/tools.ts +480 -0
  36. package/browser/src/types.ts +316 -0
  37. package/browser/src/uploads.ts +174 -0
  38. package/coding/README.md +337 -0
  39. package/coding/src/agent/acp-operations.ts +146 -0
  40. package/coding/src/agent/artifacts.ts +38 -0
  41. package/coding/src/agent/ask-user-decision.ts +623 -0
  42. package/coding/src/agent/atomic-write.ts +23 -0
  43. package/coding/src/agent/bounded-file.ts +21 -0
  44. package/coding/src/agent/checks.ts +301 -0
  45. package/coding/src/agent/coding-checkpoint.ts +828 -0
  46. package/coding/src/agent/delete.ts +237 -0
  47. package/coding/src/agent/diagnostics-tool.ts +163 -0
  48. package/coding/src/agent/diagnostics.ts +262 -0
  49. package/coding/src/agent/edit-diff.ts +382 -0
  50. package/coding/src/agent/edit.ts +322 -0
  51. package/coding/src/agent/effects.ts +102 -0
  52. package/coding/src/agent/env.ts +28 -0
  53. package/coding/src/agent/execution-policy.ts +89 -0
  54. package/coding/src/agent/file-mutation-queue.ts +72 -0
  55. package/coding/src/agent/forge/github.ts +603 -0
  56. package/coding/src/agent/forge/index.ts +16 -0
  57. package/coding/src/agent/forge/types.ts +185 -0
  58. package/coding/src/agent/git-aware-repository.ts +305 -0
  59. package/coding/src/agent/git-exec.ts +326 -0
  60. package/coding/src/agent/git-status.ts +177 -0
  61. package/coding/src/agent/git-tools.ts +360 -0
  62. package/coding/src/agent/git.ts +1074 -0
  63. package/coding/src/agent/glob-match.ts +146 -0
  64. package/coding/src/agent/glob.ts +182 -0
  65. package/coding/src/agent/goal-verify.ts +385 -0
  66. package/coding/src/agent/grep-worker.ts +74 -0
  67. package/coding/src/agent/grep.ts +459 -0
  68. package/coding/src/agent/identity.ts +48 -0
  69. package/coding/src/agent/index.ts +676 -0
  70. package/coding/src/agent/language/client.ts +429 -0
  71. package/coding/src/agent/language/framing.ts +190 -0
  72. package/coding/src/agent/language/index.ts +23 -0
  73. package/coding/src/agent/language/intelligence.ts +682 -0
  74. package/coding/src/agent/language/types.ts +187 -0
  75. package/coding/src/agent/lifecycle-events.ts +121 -0
  76. package/coding/src/agent/lifecycle.ts +166 -0
  77. package/coding/src/agent/limits.ts +214 -0
  78. package/coding/src/agent/move.ts +178 -0
  79. package/coding/src/agent/mutation-path.ts +52 -0
  80. package/coding/src/agent/output-accumulator.ts +304 -0
  81. package/coding/src/agent/path-utils.ts +120 -0
  82. package/coding/src/agent/process/index.ts +58 -0
  83. package/coding/src/agent/process/recovery.ts +508 -0
  84. package/coding/src/agent/process/sessions-host.ts +222 -0
  85. package/coding/src/agent/process/sessions-monitor.ts +141 -0
  86. package/coding/src/agent/process/sessions-recovery.ts +241 -0
  87. package/coding/src/agent/process/sessions-spawn.ts +613 -0
  88. package/coding/src/agent/process/sessions-teardown.ts +281 -0
  89. package/coding/src/agent/process/sessions.ts +30 -0
  90. package/coding/src/agent/process/types.ts +358 -0
  91. package/coding/src/agent/read-path-set.ts +126 -0
  92. package/coding/src/agent/read.ts +713 -0
  93. package/coding/src/agent/repository/glob.ts +147 -0
  94. package/coding/src/agent/repository/indexed-search.ts +480 -0
  95. package/coding/src/agent/repository/list.ts +131 -0
  96. package/coding/src/agent/repository/operations.ts +22 -0
  97. package/coding/src/agent/repository/path.ts +98 -0
  98. package/coding/src/agent/repository/search.ts +277 -0
  99. package/coding/src/agent/repository/types.ts +243 -0
  100. package/coding/src/agent/repository/walk.ts +126 -0
  101. package/coding/src/agent/repository.ts +12 -0
  102. package/coding/src/agent/shell-background.ts +264 -0
  103. package/coding/src/agent/shell-output.ts +75 -0
  104. package/coding/src/agent/shell.ts +526 -0
  105. package/coding/src/agent/todo.ts +399 -0
  106. package/coding/src/agent/truncate.ts +251 -0
  107. package/coding/src/agent/write.ts +170 -0
  108. package/coding/src/ask-user-host.ts +48 -0
  109. package/coding/src/config.ts +278 -0
  110. package/coding/src/hard-deny.ts +97 -0
  111. package/coding/src/index.ts +318 -0
  112. package/coding/src/principal-guard.ts +84 -0
  113. package/coding/src/sandbox.ts +92 -0
  114. package/coding/src/security/approval.ts +144 -0
  115. package/coding/src/security/command-rules.ts +57 -0
  116. package/coding/src/security/docker-cli.ts +222 -0
  117. package/coding/src/security/docker-sandbox.ts +1205 -0
  118. package/coding/src/security/e2b-sandbox.ts +853 -0
  119. package/coding/src/security/egress/dns-pin.ts +88 -0
  120. package/coding/src/security/egress/index.ts +32 -0
  121. package/coding/src/security/egress/limits.ts +57 -0
  122. package/coding/src/security/egress/policy.ts +137 -0
  123. package/coding/src/security/egress/proxy.ts +599 -0
  124. package/coding/src/security/egress/types.ts +30 -0
  125. package/coding/src/security/index.ts +199 -0
  126. package/coding/src/security/native-sandbox.ts +639 -0
  127. package/coding/src/security/path-containment.ts +55 -0
  128. package/coding/src/security/sandbox-coding-operations.ts +392 -0
  129. package/coding/src/security/sandbox-fs-operations.ts +644 -0
  130. package/coding/src/security/sandbox-limits.ts +147 -0
  131. package/coding/src/security/sandbox-tar.ts +308 -0
  132. package/coding/src/security/sandbox.ts +179 -0
  133. package/coding/src/security/semaphore.ts +55 -0
  134. package/coding/src/targets.ts +110 -0
  135. package/coding/src/workspace/diff.ts +78 -0
  136. package/coding/src/workspace/errors.ts +29 -0
  137. package/coding/src/workspace/index.ts +5 -0
  138. package/coding/src/workspace/merge.ts +151 -0
  139. package/coding/src/workspace/routing.ts +76 -0
  140. package/coding/src/workspace/service.ts +584 -0
  141. package/computer-use/README.md +105 -0
  142. package/computer-use/skills/computer-use-linux/SKILL.md +17 -0
  143. package/computer-use/src/classify.ts +53 -0
  144. package/computer-use/src/command.ts +29 -0
  145. package/computer-use/src/config.ts +69 -0
  146. package/computer-use/src/create.ts +170 -0
  147. package/computer-use/src/devices.ts +247 -0
  148. package/computer-use/src/env.ts +28 -0
  149. package/computer-use/src/index.ts +157 -0
  150. package/computer-use/src/skill.ts +21 -0
  151. package/openapi/README.md +97 -0
  152. package/openapi/src/compile.ts +386 -0
  153. package/openapi/src/config.ts +73 -0
  154. package/openapi/src/errors.ts +16 -0
  155. package/openapi/src/execute.ts +241 -0
  156. package/openapi/src/index.ts +124 -0
  157. package/openapi/src/limits.ts +63 -0
  158. package/openapi/src/specs.ts +86 -0
  159. package/openapi/src/tools.ts +60 -0
  160. package/openapi/src/types.ts +61 -0
  161. package/package.json +171 -4
  162. package/research/README.md +72 -0
  163. package/research/skills/research-report/SKILL.md +12 -0
  164. package/research/skills/research-scoping/SKILL.md +10 -0
  165. package/research/skills/research-synthesis/SKILL.md +10 -0
  166. package/research/skills/source-triage/SKILL.md +10 -0
  167. package/research/src/check.ts +71 -0
  168. package/research/src/config.ts +41 -0
  169. package/research/src/index.ts +147 -0
  170. package/research/src/ledger.ts +157 -0
  171. package/research/src/urls.ts +21 -0
  172. package/web/README.md +287 -0
  173. package/web/skills/web-research/SKILL.md +17 -0
  174. package/web/src/brave.ts +66 -0
  175. package/web/src/cache.ts +151 -0
  176. package/web/src/config.ts +210 -0
  177. package/web/src/controls.ts +128 -0
  178. package/web/src/domains.ts +114 -0
  179. package/web/src/exa.ts +60 -0
  180. package/web/src/firecrawl.ts +160 -0
  181. package/web/src/index.ts +387 -0
  182. package/web/src/kagi.ts +73 -0
  183. package/web/src/limits.ts +55 -0
  184. package/web/src/native/convert.ts +84 -0
  185. package/web/src/native/fetch.ts +226 -0
  186. package/web/src/normalize.ts +131 -0
  187. package/web/src/obscura/cdp.ts +201 -0
  188. package/web/src/obscura/classify.ts +35 -0
  189. package/web/src/obscura/cli.ts +254 -0
  190. package/web/src/obscura/errors.ts +8 -0
  191. package/web/src/obscura/index.ts +59 -0
  192. package/web/src/obscura/limits.ts +92 -0
  193. package/web/src/obscura/mcp.ts +104 -0
  194. package/web/src/obscura/playwright.ts +133 -0
  195. package/web/src/obscura/process.ts +235 -0
  196. package/web/src/obscura/search-profile.ts +73 -0
  197. package/web/src/obscura/types.ts +48 -0
  198. package/web/src/obscura/web.ts +419 -0
  199. package/web/src/prompt-fetch.ts +88 -0
  200. package/web/src/render.ts +75 -0
  201. package/web/src/searxng.ts +78 -0
  202. package/web/src/slice.ts +139 -0
  203. package/web/src/tavily.ts +77 -0
  204. package/web/src/tools.ts +182 -0
  205. package/web/src/transport.ts +310 -0
  206. package/web/src/types.ts +199 -0
  207. package/work/README.md +77 -0
  208. package/work/THIRD_PARTY_NOTICES +24 -0
  209. package/work/docling/ocr.py +150 -0
  210. package/work/sandbox/Dockerfile +36 -0
  211. package/work/sandbox/soffice.sh +12 -0
  212. package/work/src/command.ts +29 -0
  213. package/work/src/config.ts +138 -0
  214. package/work/src/connectors/cli.ts +343 -0
  215. package/work/src/connectors/drafts.ts +647 -0
  216. package/work/src/connectors/errors.ts +8 -0
  217. package/work/src/connectors/file-bytes.ts +149 -0
  218. package/work/src/connectors/google-workspace-http.ts +399 -0
  219. package/work/src/connectors/google-workspace.ts +465 -0
  220. package/work/src/connectors/gws-tools.ts +378 -0
  221. package/work/src/connectors/http.ts +123 -0
  222. package/work/src/connectors/idempotency.ts +235 -0
  223. package/work/src/connectors/identity.ts +34 -0
  224. package/work/src/connectors/index.ts +93 -0
  225. package/work/src/connectors/limits.ts +50 -0
  226. package/work/src/connectors/m365-tools.ts +308 -0
  227. package/work/src/connectors/microsoft365-http.ts +428 -0
  228. package/work/src/connectors/microsoft365.ts +402 -0
  229. package/work/src/connectors/normalize.ts +120 -0
  230. package/work/src/connectors/tool-helpers.ts +398 -0
  231. package/work/src/connectors/tools.ts +42 -0
  232. package/work/src/connectors/types.ts +399 -0
  233. package/work/src/diagrams/canonicalize.ts +166 -0
  234. package/work/src/diagrams/caps.ts +52 -0
  235. package/work/src/diagrams/embed.ts +340 -0
  236. package/work/src/diagrams/errors.ts +76 -0
  237. package/work/src/diagrams/index.ts +81 -0
  238. package/work/src/diagrams/messages.ts +150 -0
  239. package/work/src/diagrams/origin.ts +52 -0
  240. package/work/src/diagrams/telemetry.ts +34 -0
  241. package/work/src/diagrams/xml.ts +267 -0
  242. package/work/src/document-extraction/index.ts +563 -0
  243. package/work/src/document-reader/ambient.d.ts +2 -0
  244. package/work/src/document-reader/errors.ts +7 -0
  245. package/work/src/document-reader/index.ts +181 -0
  246. package/work/src/document-reader/mistral-ocr.ts +318 -0
  247. package/work/src/document-reader/parsers.ts +171 -0
  248. package/work/src/documents/caps.ts +135 -0
  249. package/work/src/documents/diff.ts +269 -0
  250. package/work/src/documents/errors.ts +44 -0
  251. package/work/src/documents/fidelity.ts +98 -0
  252. package/work/src/documents/generate.ts +96 -0
  253. package/work/src/documents/hash.ts +8 -0
  254. package/work/src/documents/index.ts +14 -0
  255. package/work/src/documents/model-schema.ts +639 -0
  256. package/work/src/documents/parse.ts +252 -0
  257. package/work/src/documents/patch-history.ts +81 -0
  258. package/work/src/documents/patch.ts +476 -0
  259. package/work/src/documents/preview-html.ts +245 -0
  260. package/work/src/documents/preview.ts +227 -0
  261. package/work/src/documents/telemetry.ts +36 -0
  262. package/work/src/documents/translate/docx.ts +292 -0
  263. package/work/src/documents/translate/pptx.ts +304 -0
  264. package/work/src/documents/translate/xlsx.ts +137 -0
  265. package/work/src/documents/types.ts +188 -0
  266. package/work/src/index.ts +311 -0
  267. package/work/src/runtime/worker-pool-entry.ts +102 -0
  268. package/work/src/runtime/worker-pool.ts +267 -0
  269. package/work/src/sandbox/composition.ts +212 -0
  270. package/work/src/sandbox/image.ts +6 -0
  271. package/work/src/sandbox/index.ts +14 -0
  272. package/work/src/sheets/caps.ts +68 -0
  273. package/work/src/sheets/csv.ts +351 -0
  274. package/work/src/sheets/decimal.ts +388 -0
  275. package/work/src/sheets/errors.ts +52 -0
  276. package/work/src/sheets/index.ts +62 -0
  277. package/work/src/sheets/inference.ts +378 -0
  278. package/work/src/sheets/telemetry.ts +34 -0
  279. package/work/src/sheets/types.ts +162 -0
  280. package/work/src/sheets/xlsx.ts +388 -0
  281. package/work/src/skills/index.ts +8 -0
  282. package/work/src/skills/load.ts +43 -0
  283. package/work/src/tools/filesystem.ts +21 -0
  284. package/work/src/tools/index.ts +1 -0
  285. package/work/src/tools/local-filesystem.ts +33 -0
  286. package/work/src/tools/office.ts +311 -0
  287. package/work/vendor/hermes-agent/LICENSE +21 -0
  288. package/work/vendor/hermes-agent/skills/productivity/docx/LICENSE +21 -0
  289. package/work/vendor/hermes-agent/skills/productivity/docx/SKILL.md +196 -0
  290. package/work/vendor/hermes-agent/skills/productivity/docx/scripts/docx_comments.py +289 -0
  291. package/work/vendor/hermes-agent/skills/productivity/docx/scripts/docx_common.py +94 -0
  292. package/work/vendor/hermes-agent/skills/productivity/docx/scripts/docx_create.py +177 -0
  293. package/work/vendor/hermes-agent/skills/productivity/docx/scripts/docx_edit.py +250 -0
  294. package/work/vendor/hermes-agent/skills/productivity/docx/scripts/docx_read.py +149 -0
  295. package/work/vendor/hermes-agent/skills/productivity/docx/scripts/docx_revisions.py +147 -0
  296. package/work/vendor/hermes-agent/skills/productivity/docx/scripts/docx_template.py +70 -0
  297. package/work/vendor/hermes-agent/skills/productivity/docx/scripts/docx_validate.py +156 -0
  298. package/work/vendor/hermes-agent/skills/productivity/pdf/LICENSE +21 -0
  299. package/work/vendor/hermes-agent/skills/productivity/pdf/SKILL.md +125 -0
  300. package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/_raster.py +76 -0
  301. package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/extract_marker.py +88 -0
  302. package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/extract_pymupdf.py +101 -0
  303. package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_create.py +130 -0
  304. package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_fill_form.py +97 -0
  305. package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_form_layout.py +168 -0
  306. package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_make_form.py +145 -0
  307. package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_merge.py +50 -0
  308. package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_meta.py +115 -0
  309. package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_page_image.py +99 -0
  310. package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_read.py +153 -0
  311. package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_secure.py +71 -0
  312. package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_split.py +84 -0
  313. package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_stamp.py +143 -0
  314. package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_watermark.py +51 -0
  315. package/work/vendor/hermes-agent/skills/productivity/powerpoint/LICENSE +21 -0
  316. package/work/vendor/hermes-agent/skills/productivity/powerpoint/SKILL.md +220 -0
  317. package/work/vendor/hermes-agent/skills/productivity/powerpoint/scripts/pptx_create.py +214 -0
  318. package/work/vendor/hermes-agent/skills/productivity/powerpoint/scripts/pptx_edit.py +436 -0
  319. package/work/vendor/hermes-agent/skills/productivity/powerpoint/scripts/pptx_from_template.py +88 -0
  320. package/work/vendor/hermes-agent/skills/productivity/powerpoint/scripts/pptx_read.py +131 -0
  321. package/work/vendor/hermes-agent/skills/productivity/powerpoint/scripts/pptx_render.py +93 -0
  322. package/work/vendor/hermes-agent/skills/productivity/xlsx/LICENSE +21 -0
  323. package/work/vendor/hermes-agent/skills/productivity/xlsx/SKILL.md +196 -0
  324. package/work/vendor/hermes-agent/skills/productivity/xlsx/scripts/csv_to_xlsx.py +104 -0
  325. package/work/vendor/hermes-agent/skills/productivity/xlsx/scripts/xlsx_create.py +259 -0
  326. package/work/vendor/hermes-agent/skills/productivity/xlsx/scripts/xlsx_edit.py +263 -0
  327. package/work/vendor/hermes-agent/skills/productivity/xlsx/scripts/xlsx_read.py +160 -0
  328. package/work/vendor/hermes-agent/skills/productivity/xlsx/scripts/xlsx_recalc.py +111 -0
  329. package/work/vendor/hermes-agent/skills/productivity/xlsx/scripts/xlsx_restructure.py +337 -0
  330. package/work/vendor/hermes-agent/skills/productivity/xlsx/scripts/xlsx_to_csv.py +69 -0
  331. package/work/vendor-lock.json +11 -0
@@ -0,0 +1,149 @@
1
+ import { createHash } from "node:crypto";
2
+ import { createReadStream } from "node:fs";
3
+ import { basename, isAbsolute } from "node:path";
4
+ import type { JsonObject } from "@arnilo/prism";
5
+ import type { ArtifactBodyRef, ArtifactBodyStore } from "@arnilo/prism/artifacts";
6
+ import { WorkToolError } from "./errors.js";
7
+
8
+ export interface UploadBytes {
9
+ readonly bytes: Buffer;
10
+ readonly contentHash: string;
11
+ readonly name: string;
12
+ }
13
+
14
+ export function artifactRef(value: unknown): ArtifactBodyRef {
15
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
16
+ throw new WorkToolError("ERR_PRISM_WORK_INPUT", "artifact must be an artifact body reference");
17
+ }
18
+ const row = value as Record<string, unknown>;
19
+ const string = (key: string, max = 2_048): string => {
20
+ const field = row[key];
21
+ if (typeof field !== "string" || !field || Buffer.byteLength(field, "utf8") > max) {
22
+ throw new WorkToolError("ERR_PRISM_WORK_INPUT", `artifact.${key} must be a bounded non-empty string`);
23
+ }
24
+ return field;
25
+ };
26
+ const size = row.size;
27
+ const version = row.version;
28
+ if (
29
+ typeof size !== "number" ||
30
+ !Number.isSafeInteger(size) ||
31
+ size < 0 ||
32
+ typeof version !== "number" ||
33
+ !Number.isSafeInteger(version) ||
34
+ version < 1
35
+ ) {
36
+ throw new WorkToolError("ERR_PRISM_WORK_INPUT", "artifact size and version must be non-negative integers");
37
+ }
38
+ const hash = string("hash", 64).toLowerCase();
39
+ if (!/^[0-9a-f]{64}$/.test(hash)) throw new WorkToolError("ERR_PRISM_WORK_INPUT", "artifact.hash must be a SHA-256 hex digest");
40
+ const accountId = row.accountId;
41
+ const userId = row.userId;
42
+ if (accountId !== undefined && (typeof accountId !== "string" || !accountId))
43
+ throw new WorkToolError("ERR_PRISM_WORK_INPUT", "artifact.accountId must be a string");
44
+ if (userId !== undefined && (typeof userId !== "string" || !userId))
45
+ throw new WorkToolError("ERR_PRISM_WORK_INPUT", "artifact.userId must be a string");
46
+ return {
47
+ tenantId: string("tenantId", 512),
48
+ artifactId: string("artifactId", 512),
49
+ threadId: string("threadId", 512),
50
+ version,
51
+ mime: string("mime", 256),
52
+ size,
53
+ hash,
54
+ ...(accountId === undefined ? {} : { accountId }),
55
+ ...(userId === undefined ? {} : { userId }),
56
+ };
57
+ }
58
+
59
+ export async function readUploadBytes(
60
+ args: JsonObject,
61
+ bodies: ArtifactBodyStore | undefined,
62
+ maxBytes: number,
63
+ signal?: AbortSignal,
64
+ ): Promise<UploadBytes> {
65
+ const artifact = args.artifact;
66
+ const filePath = args.filePath;
67
+ if ((artifact === undefined) === (filePath === undefined)) {
68
+ throw new WorkToolError("ERR_PRISM_WORK_INPUT", "provide exactly one of artifact or filePath");
69
+ }
70
+ if (artifact !== undefined) {
71
+ if (!bodies) throw new WorkToolError("ERR_PRISM_WORK_INPUT", "artifact requires a configured artifact body store");
72
+ const ref = artifactRef(artifact);
73
+ if (ref.size > maxBytes) throw new WorkToolError("ERR_PRISM_WORK_LIMIT", "File exceeds byte limit");
74
+ const bytes = await readStream(await bodies.get(ref, { signal }), maxBytes, signal);
75
+ return { bytes, contentHash: sha256(bytes), name: requiredString(args, "name") };
76
+ }
77
+ if (typeof filePath !== "string" || !filePath) throw new WorkToolError("ERR_PRISM_WORK_INPUT", "filePath must be a non-empty string");
78
+ if (isRemoteUrl(filePath)) throw new WorkToolError("ERR_PRISM_WORK_INPUT", "filePath must be a host-local path");
79
+ const bytes = await readPath(filePath, maxBytes, signal);
80
+ return { bytes, contentHash: sha256(bytes), name: basename(filePath) };
81
+ }
82
+
83
+ export function assertContentHash(args: JsonObject, actual: string): void {
84
+ const expected = args.contentHash;
85
+ if (expected !== undefined && (typeof expected !== "string" || !/^[0-9a-f]{64}$/.test(expected) || expected !== actual)) {
86
+ throw new WorkToolError("ERR_PRISM_WORK_DRAFT_DIGEST", "File content changed after draft approval");
87
+ }
88
+ }
89
+
90
+ export function sha256(bytes: Uint8Array): string {
91
+ return createHash("sha256").update(bytes).digest("hex");
92
+ }
93
+
94
+ async function readPath(path: string, maxBytes: number, signal?: AbortSignal): Promise<Buffer> {
95
+ try {
96
+ return await readChunks(createReadStream(path), maxBytes, signal);
97
+ } catch (error) {
98
+ if (error instanceof WorkToolError) throw error;
99
+ throw new WorkToolError("ERR_PRISM_WORK_INPUT", "filePath could not be read from the host");
100
+ }
101
+ }
102
+
103
+ async function readStream(stream: ReadableStream<Uint8Array>, maxBytes: number, signal?: AbortSignal): Promise<Buffer> {
104
+ const reader = stream.getReader();
105
+ const chunks: Buffer[] = [];
106
+ let total = 0;
107
+ try {
108
+ for (;;) {
109
+ signal?.throwIfAborted();
110
+ const { done, value } = await reader.read();
111
+ if (done) return Buffer.concat(chunks, total);
112
+ total += value.byteLength;
113
+ if (total > maxBytes) {
114
+ await reader.cancel().catch(() => undefined);
115
+ throw new WorkToolError("ERR_PRISM_WORK_LIMIT", "File exceeds byte limit");
116
+ }
117
+ chunks.push(Buffer.from(value));
118
+ }
119
+ } finally {
120
+ reader.releaseLock();
121
+ }
122
+ }
123
+
124
+ async function readChunks(stream: AsyncIterable<Uint8Array>, maxBytes: number, signal?: AbortSignal): Promise<Buffer> {
125
+ const chunks: Buffer[] = [];
126
+ let total = 0;
127
+ for await (const chunk of stream) {
128
+ signal?.throwIfAborted();
129
+ total += chunk.byteLength;
130
+ if (total > maxBytes) throw new WorkToolError("ERR_PRISM_WORK_LIMIT", "File exceeds byte limit");
131
+ chunks.push(Buffer.from(chunk));
132
+ }
133
+ return Buffer.concat(chunks, total);
134
+ }
135
+
136
+ function requiredString(args: JsonObject, key: string): string {
137
+ const value = args[key];
138
+ if (typeof value !== "string" || !value) throw new WorkToolError("ERR_PRISM_WORK_INPUT", `${key} must be a non-empty string`);
139
+ return value;
140
+ }
141
+
142
+ function isRemoteUrl(value: string): boolean {
143
+ try {
144
+ new URL(value);
145
+ return !isAbsolute(value);
146
+ } catch {
147
+ return false;
148
+ }
149
+ }
@@ -0,0 +1,399 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { type JsonObject } from "@arnilo/prism";
3
+ import { type ArtifactBodyStore } from "@arnilo/prism/artifacts";
4
+ import type { CheckpointStore } from "@arnilo/prism-extensions/workflows";
5
+ import { createCheckpointWorkDraftStore, createMemoryWorkDraftStore } from "./drafts.js";
6
+ import { WorkToolError } from "./errors.js";
7
+ import { assertContentHash, readUploadBytes } from "./file-bytes.js";
8
+ import { DEFAULT_GWS_OPS, googleWorkspaceUpdate } from "./google-workspace.js";
9
+ import { createWorkHttpClient } from "./http.js";
10
+ import { type AgentIdentity, assertIdentityActive } from "./identity.js";
11
+ import { resolveWorkLimits } from "./limits.js";
12
+ import type { GoogleWorkspaceAdapter, GoogleWorkspaceOp, WorkDraftStore, WorkLimits, WorkTokenProvider } from "./types.js";
13
+
14
+ const GOOGLE_API_ORIGINS = [
15
+ "https://docs.googleapis.com",
16
+ "https://gmail.googleapis.com",
17
+ "https://sheets.googleapis.com",
18
+ "https://slides.googleapis.com",
19
+ "https://www.googleapis.com",
20
+ "https://tasks.googleapis.com",
21
+ ];
22
+ const GOOGLE_DOC_MIME_TYPES = {
23
+ "docs.create": "application/vnd.google-apps.document",
24
+ "sheets.create": "application/vnd.google-apps.spreadsheet",
25
+ "slides.create": "application/vnd.google-apps.presentation",
26
+ } as const;
27
+
28
+ export interface GoogleWorkspaceHttpAdapterOptions {
29
+ readonly identity: AgentIdentity;
30
+ readonly tokenProvider: WorkTokenProvider;
31
+ readonly accessEnvVar: string;
32
+ readonly allowedOps?: readonly GoogleWorkspaceOp[];
33
+ readonly limits?: WorkLimits;
34
+ readonly fetch?: typeof globalThis.fetch;
35
+ readonly draftStore?: WorkDraftStore;
36
+ readonly checkpoints?: CheckpointStore;
37
+ readonly bodies?: ArtifactBodyStore;
38
+ readonly ephemeralDrafts?: boolean;
39
+ }
40
+
41
+ /** Google Workspace REST adapter with pinned default fetch and host-owned OAuth tokens. */
42
+ export function createGoogleWorkspaceHttpAdapter(options: GoogleWorkspaceHttpAdapterOptions): GoogleWorkspaceAdapter {
43
+ if (!options || typeof options.tokenProvider?.tokenEnv !== "function") {
44
+ throw new WorkToolError("ERR_PRISM_WORK_CREDENTIAL", "Google Workspace HTTP adapter requires a tokenProvider");
45
+ }
46
+ if (typeof options.accessEnvVar !== "string" || !/^[A-Za-z_][A-Za-z0-9_]*$/.test(options.accessEnvVar)) {
47
+ throw new WorkToolError("ERR_PRISM_WORK_INPUT", "accessEnvVar must be an environment variable name");
48
+ }
49
+ if (options.fetch !== undefined && typeof options.fetch !== "function") {
50
+ throw new WorkToolError("ERR_PRISM_WORK_INPUT", "fetch must be a function");
51
+ }
52
+ assertIdentityActive(options.identity);
53
+ const limits = resolveWorkLimits(options.limits);
54
+ const allowedOps = new Set<GoogleWorkspaceOp>(options.allowedOps ?? [...DEFAULT_GWS_OPS, "file.get"]);
55
+ const request = createWorkHttpClient({
56
+ identity: options.identity,
57
+ tokenProvider: options.tokenProvider,
58
+ accessEnvVar: options.accessEnvVar,
59
+ allowedOrigins: GOOGLE_API_ORIGINS,
60
+ limits,
61
+ ...(options.fetch === undefined ? {} : { fetch: options.fetch }),
62
+ });
63
+ const draftStore: WorkDraftStore =
64
+ options.draftStore ??
65
+ (options.checkpoints
66
+ ? createCheckpointWorkDraftStore({ checkpoints: options.checkpoints, bodies: options.bodies, limits: options.limits })
67
+ : createMemoryWorkDraftStore({ ephemeral: options.ephemeralDrafts ?? true }));
68
+ let readyVersion: string | undefined;
69
+
70
+ const assertAllowed = (op: GoogleWorkspaceOp) => {
71
+ if (!allowedOps.has(op)) throw new WorkToolError("ERR_PRISM_WORK_CAPABILITY", `Operation ${op} not allowed for this identity`);
72
+ };
73
+ const ensureReady = async (signal?: AbortSignal): Promise<string> => {
74
+ if (readyVersion) return readyVersion;
75
+ assertAllowed("version");
76
+ const profile = asRecord(await request({ url: new URL("https://gmail.googleapis.com/gmail/v1/users/me/profile"), signal }));
77
+ readyVersion = string(profile?.emailAddress) ?? "google-workspace";
78
+ return readyVersion;
79
+ };
80
+
81
+ return {
82
+ provider: "google-workspace",
83
+ identity: options.identity,
84
+ allowedOps,
85
+ draftStore,
86
+ ensureReady,
87
+ async runOp(op, args, signal) {
88
+ assertAllowed(op);
89
+ switch (op) {
90
+ case "version":
91
+ return ensureReady(signal);
92
+ case "mail.list":
93
+ return request({ url: gmailUrl("messages", args, limits.maxItemsPerPage), signal });
94
+ case "mail.get":
95
+ return request({ url: gmailUrl(`messages/${encodeURIComponent(reqString(args, "id"))}`, args), signal });
96
+ case "mail.send":
97
+ return request({
98
+ url: new URL("https://gmail.googleapis.com/gmail/v1/users/me/messages/send"),
99
+ method: "POST",
100
+ headers: { "content-type": "application/json" },
101
+ body: JSON.stringify({ raw: rfc822(args) }),
102
+ signal,
103
+ });
104
+ case "calendar.list":
105
+ return request({ url: calendarUrl("events", args, limits.maxItemsPerPage), signal });
106
+ case "calendar.add":
107
+ return request({
108
+ url: calendarUrl("events", args),
109
+ method: "POST",
110
+ headers: { "content-type": "application/json" },
111
+ body: JSON.stringify({
112
+ summary: reqString(args, "summary"),
113
+ start: { dateTime: reqString(args, "start") },
114
+ end: { dateTime: reqString(args, "end") },
115
+ }),
116
+ signal,
117
+ });
118
+ case "file.list":
119
+ return listFiles(request, args, limits, signal);
120
+ case "file.get":
121
+ return request({
122
+ url: new URL(`https://www.googleapis.com/drive/v3/files/${encodeURIComponent(reqString(args, "id"))}?alt=media`),
123
+ responseType: "bytes",
124
+ maxResponseBytes: limits.maxFileBytes,
125
+ signal,
126
+ });
127
+ case "file.add":
128
+ return uploadFile(request, args, limits, options.bodies, signal);
129
+ case "file.share":
130
+ return createDrivePermission(request, args, signal);
131
+ case "task.list":
132
+ return request({ url: tasksUrl("tasks", args, limits.maxItemsPerPage), signal });
133
+ case "task.add":
134
+ return request({
135
+ url: tasksUrl("tasks", args),
136
+ method: "POST",
137
+ headers: { "content-type": "application/json" },
138
+ body: JSON.stringify({ title: reqString(args, "title") }),
139
+ signal,
140
+ });
141
+ case "task.complete":
142
+ return request({
143
+ url: tasksUrl(`tasks/${encodeURIComponent(reqString(args, "id"))}`, args),
144
+ method: "PATCH",
145
+ headers: { "content-type": "application/json" },
146
+ body: JSON.stringify({ status: "completed" }),
147
+ signal,
148
+ });
149
+ case "docs.create":
150
+ case "sheets.create":
151
+ case "slides.create":
152
+ return request({
153
+ url: new URL("https://www.googleapis.com/drive/v3/files"),
154
+ method: "POST",
155
+ headers: { "content-type": "application/json" },
156
+ body: JSON.stringify({ name: reqString(args, "title"), mimeType: GOOGLE_DOC_MIME_TYPES[op] }),
157
+ signal,
158
+ });
159
+ case "docs.update":
160
+ case "sheets.update":
161
+ case "slides.update": {
162
+ const update = googleWorkspaceUpdate(op, args);
163
+ return request({
164
+ url: googleWorkspaceUpdateUrl(op, update.resourceId, args),
165
+ method: op === "sheets.update" ? "PUT" : "POST",
166
+ headers: { "content-type": "application/json" },
167
+ body: JSON.stringify(update.body),
168
+ signal,
169
+ });
170
+ }
171
+ default: {
172
+ const _exhaustive: never = op;
173
+ throw new WorkToolError("ERR_PRISM_WORK_OP", `Unknown op ${_exhaustive}`);
174
+ }
175
+ }
176
+ },
177
+ createDraft(op, payload, draftOpts) {
178
+ assertAllowed(op);
179
+ return draftStore.createDraft({
180
+ draftId: draftOpts?.draftId,
181
+ provider: "google-workspace",
182
+ op,
183
+ identity: options.identity,
184
+ payload,
185
+ policyRevision: draftOpts?.policyRevision,
186
+ });
187
+ },
188
+ getDraft(draftId) {
189
+ return draftStore.getDraft({ draftId, identity: options.identity, provider: "google-workspace" });
190
+ },
191
+ updateDraft(draftId, payload, draftOpts) {
192
+ return draftStore.updateDraft({
193
+ draftId,
194
+ identity: options.identity,
195
+ payload,
196
+ expectedRevision: draftOpts?.expectedRevision,
197
+ expectedConcurrencyToken: draftOpts?.concurrencyToken,
198
+ });
199
+ },
200
+ approveDraft(approval) {
201
+ return draftStore.approveDraft({ draftId: approval.draftId, identity: options.identity, approval });
202
+ },
203
+ markDraft(draftId, status, concurrencyToken) {
204
+ return draftStore.markDraft({ draftId, identity: options.identity, status, concurrencyToken });
205
+ },
206
+ };
207
+ }
208
+
209
+ function googleWorkspaceUpdateUrl(op: "docs.update" | "sheets.update" | "slides.update", resourceId: string, args: JsonObject): URL {
210
+ switch (op) {
211
+ case "docs.update":
212
+ return new URL(`https://docs.googleapis.com/v1/documents/${encodeURIComponent(resourceId)}:batchUpdate`);
213
+ case "sheets.update": {
214
+ const url = new URL(
215
+ `https://sheets.googleapis.com/v4/spreadsheets/${encodeURIComponent(resourceId)}/values/${encodeURIComponent(reqString(args, "range"))}`,
216
+ );
217
+ url.searchParams.set("valueInputOption", "RAW");
218
+ return url;
219
+ }
220
+ case "slides.update":
221
+ return new URL(`https://slides.googleapis.com/v1/presentations/${encodeURIComponent(resourceId)}:batchUpdate`);
222
+ }
223
+ }
224
+
225
+ function gmailUrl(path: string, args: JsonObject, maxItems?: number): URL {
226
+ const userId = optString(args, "userId") ?? "me";
227
+ const url = new URL(`https://gmail.googleapis.com/gmail/v1/users/${encodeURIComponent(userId)}/${path}`);
228
+ setQuery(url, "q", optString(args, "q"));
229
+ setQuery(url, "maxResults", boundedPageSize(args, "maxResults", maxItems));
230
+ return url;
231
+ }
232
+
233
+ function calendarUrl(path: string, args: JsonObject, maxItems?: number): URL {
234
+ const calendarId = optString(args, "calendarId") ?? "primary";
235
+ const url = new URL(`https://www.googleapis.com/calendar/v3/calendars/${encodeURIComponent(calendarId)}/${path}`);
236
+ setQuery(url, "timeMin", optString(args, "timeMin"));
237
+ setQuery(url, "timeMax", optString(args, "timeMax"));
238
+ setQuery(url, "maxResults", boundedPageSize(args, "maxResults", maxItems));
239
+ return url;
240
+ }
241
+
242
+ function tasksUrl(path: string, args: JsonObject, maxItems?: number): URL {
243
+ const tasklist = optString(args, "tasklist") ?? "@default";
244
+ const url = new URL(`https://tasks.googleapis.com/tasks/v1/lists/${encodeURIComponent(tasklist)}/${path}`);
245
+ setQuery(url, "maxResults", boundedPageSize(args, "maxResults", maxItems));
246
+ return url;
247
+ }
248
+
249
+ async function listFiles(
250
+ request: ReturnType<typeof createWorkHttpClient>,
251
+ args: JsonObject,
252
+ limits: ReturnType<typeof resolveWorkLimits>,
253
+ signal?: AbortSignal,
254
+ ) {
255
+ const pageAll = args.pageAll === true || args.pageAll === "true";
256
+ const pages: unknown[] = [];
257
+ let pageToken: string | undefined;
258
+ let aggregate = 0;
259
+ do {
260
+ const url = new URL("https://www.googleapis.com/drive/v3/files");
261
+ setQuery(url, "q", optString(args, "q"));
262
+ setQuery(url, "pageSize", boundedInteger(args, "pageSize", limits.maxItemsPerPage) ?? String(Math.min(10, limits.maxItemsPerPage)));
263
+ setQuery(url, "pageToken", pageToken);
264
+ const page = await request({ url, signal });
265
+ const files = array(asRecord(page)?.files);
266
+ if (files.length > limits.maxItemsPerPage) throw new WorkToolError("ERR_PRISM_WORK_LIMIT", "Drive page exceeds item limit");
267
+ aggregate += files.length;
268
+ if (aggregate > limits.maxAggregateItems) throw new WorkToolError("ERR_PRISM_WORK_LIMIT", "Drive result exceeds aggregate item limit");
269
+ if (!pageAll) return page;
270
+ pages.push(page);
271
+ pageToken = string(asRecord(page)?.nextPageToken);
272
+ if (pageToken && pages.length >= limits.maxPaginationPages) {
273
+ throw new WorkToolError("ERR_PRISM_WORK_LIMIT", "Drive pagination exceeds page limit");
274
+ }
275
+ } while (pageToken);
276
+ return pages;
277
+ }
278
+
279
+ async function uploadFile(
280
+ request: ReturnType<typeof createWorkHttpClient>,
281
+ args: JsonObject,
282
+ limits: ReturnType<typeof resolveWorkLimits>,
283
+ bodies: ArtifactBodyStore | undefined,
284
+ signal?: AbortSignal,
285
+ ): Promise<unknown> {
286
+ const source = await readUploadBytes(args, bodies, limits.maxFileBytes, signal);
287
+ assertContentHash(args, source.contentHash);
288
+ const parentId = optString(args, "parentId");
289
+ const multipart = multipartUpload(
290
+ { name: reqString(args, "name"), ...(parentId === undefined ? {} : { parents: [parentId] }) },
291
+ source.bytes,
292
+ );
293
+ return request({
294
+ url: new URL("https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart"),
295
+ method: "POST",
296
+ headers: { "content-type": multipart.contentType },
297
+ body: multipart.body,
298
+ signal,
299
+ });
300
+ }
301
+
302
+ async function createDrivePermission(
303
+ request: ReturnType<typeof createWorkHttpClient>,
304
+ args: JsonObject,
305
+ signal?: AbortSignal,
306
+ ): Promise<unknown> {
307
+ const type = reqString(args, "type");
308
+ if (type === "anyone") throw new WorkToolError("ERR_PRISM_WORK_POLICY", "Anonymous share denied");
309
+ if (type !== "domain" && type !== "user") throw new WorkToolError("ERR_PRISM_WORK_INPUT", "type must be domain or user");
310
+ const role = optString(args, "role") ?? "reader";
311
+ if (role !== "reader" && role !== "writer" && role !== "commenter") {
312
+ throw new WorkToolError("ERR_PRISM_WORK_INPUT", "role must be reader, writer, or commenter");
313
+ }
314
+ const body =
315
+ type === "domain" ? { type, role, domain: reqString(args, "domain") } : { type, role, emailAddress: reqString(args, "emailAddress") };
316
+ return request({
317
+ url: new URL(`https://www.googleapis.com/drive/v3/files/${encodeURIComponent(reqString(args, "fileId"))}/permissions`),
318
+ method: "POST",
319
+ headers: { "content-type": "application/json" },
320
+ body: JSON.stringify(body),
321
+ signal,
322
+ });
323
+ }
324
+
325
+ function rfc822(args: JsonObject): string {
326
+ const headers = [
327
+ ["To", reqString(args, "to")],
328
+ ["Subject", reqString(args, "subject")],
329
+ ["Cc", optString(args, "cc")],
330
+ ["Bcc", optString(args, "bcc")],
331
+ ["From", optString(args, "from")],
332
+ ] as const;
333
+ for (const [, value] of headers) {
334
+ if (value?.includes("\r") || value?.includes("\n"))
335
+ throw new WorkToolError("ERR_PRISM_WORK_INPUT", "Mail headers cannot contain newlines");
336
+ }
337
+ const message = [
338
+ ...headers.filter(([, value]) => value !== undefined).map(([name, value]) => `${name}: ${value}`),
339
+ "MIME-Version: 1.0",
340
+ "Content-Type: text/plain; charset=UTF-8",
341
+ "",
342
+ reqString(args, "body"),
343
+ ].join("\r\n");
344
+ return Buffer.from(message, "utf8").toString("base64url");
345
+ }
346
+
347
+ function multipartUpload(metadata: Record<string, unknown>, file: Buffer): { body: Buffer; contentType: string } {
348
+ const boundary = `prism-${randomUUID()}`;
349
+ const body = Buffer.concat([
350
+ Buffer.from(`--${boundary}\r\nContent-Type: application/json; charset=UTF-8\r\n\r\n${JSON.stringify(metadata)}\r\n`, "utf8"),
351
+ Buffer.from(`--${boundary}\r\nContent-Type: application/octet-stream\r\n\r\n`, "utf8"),
352
+ file,
353
+ Buffer.from(`\r\n--${boundary}--\r\n`, "utf8"),
354
+ ]);
355
+ return { body, contentType: `multipart/related; boundary=${boundary}` };
356
+ }
357
+
358
+ function reqString(args: JsonObject, key: string): string {
359
+ const value = args[key];
360
+ if (typeof value !== "string" || !value) throw new WorkToolError("ERR_PRISM_WORK_INPUT", `${key} must be a non-empty string`);
361
+ return value;
362
+ }
363
+
364
+ function optString(args: JsonObject, key: string): string | undefined {
365
+ const value = args[key];
366
+ if (value === undefined) return undefined;
367
+ if (typeof value !== "string" || !value) throw new WorkToolError("ERR_PRISM_WORK_INPUT", `${key} must be a non-empty string`);
368
+ return value;
369
+ }
370
+
371
+ function boundedInteger(args: JsonObject, key: string, maximum: number | undefined): string | undefined {
372
+ const value = optString(args, key);
373
+ if (value === undefined) return undefined;
374
+ const number = Number(value);
375
+ if (!Number.isSafeInteger(number) || number < 1 || (maximum !== undefined && number > maximum)) {
376
+ throw new WorkToolError("ERR_PRISM_WORK_INPUT", `${key} must be an integer from 1 to ${maximum}`);
377
+ }
378
+ return String(number);
379
+ }
380
+
381
+ function boundedPageSize(args: JsonObject, key: string, maximum: number | undefined): string | undefined {
382
+ return boundedInteger(args, key, maximum) ?? (maximum === undefined ? undefined : String(maximum));
383
+ }
384
+
385
+ function setQuery(url: URL, name: string, value: string | undefined): void {
386
+ if (value !== undefined) url.searchParams.set(name, value);
387
+ }
388
+
389
+ function asRecord(value: unknown): Record<string, unknown> | undefined {
390
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? (value as Record<string, unknown>) : undefined;
391
+ }
392
+
393
+ function array(value: unknown): readonly unknown[] {
394
+ return Array.isArray(value) ? value : [];
395
+ }
396
+
397
+ function string(value: unknown): string | undefined {
398
+ return typeof value === "string" && value ? value : undefined;
399
+ }