@try-works/dsh-model-as-tool 0.1.3 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (260) hide show
  1. package/bin/dsh-tool-mode-credentials.mjs +20 -0
  2. package/bin/dsh-tool-mode.mjs +202 -0
  3. package/catalog/desert-ant-models.json +693 -693
  4. package/client/client.model-tools.js +614 -0
  5. package/client/index.js +1717 -0
  6. package/lib/adapters/index.d.ts.map +1 -1
  7. package/lib/adapters/index.js +4 -4
  8. package/lib/bindings.d.ts.map +1 -1
  9. package/lib/bindings.js +26 -25
  10. package/lib/capability.d.ts.map +1 -1
  11. package/lib/capability.js +17 -0
  12. package/lib/capture-path.d.ts +97 -0
  13. package/lib/capture-path.d.ts.map +1 -0
  14. package/lib/capture-path.js +152 -0
  15. package/lib/diagnostics.d.ts +166 -0
  16. package/lib/diagnostics.d.ts.map +1 -0
  17. package/lib/diagnostics.js +135 -0
  18. package/lib/extension-dependencies.d.ts +325 -0
  19. package/lib/extension-dependencies.d.ts.map +1 -0
  20. package/lib/extension-dependencies.js +858 -0
  21. package/lib/extension-dispatch.d.ts +117 -0
  22. package/lib/extension-dispatch.d.ts.map +1 -0
  23. package/lib/extension-dispatch.js +124 -0
  24. package/lib/extension-registrar.d.ts +100 -0
  25. package/lib/extension-registrar.d.ts.map +1 -0
  26. package/lib/extension-registrar.js +134 -0
  27. package/lib/extensions-api.d.ts +221 -0
  28. package/lib/extensions-api.d.ts.map +1 -0
  29. package/lib/extensions-api.js +17 -0
  30. package/lib/extensions-remote.d.ts +54 -0
  31. package/lib/extensions-remote.d.ts.map +1 -0
  32. package/lib/extensions-remote.js +66 -0
  33. package/lib/extensions-service.d.ts +367 -0
  34. package/lib/extensions-service.d.ts.map +1 -0
  35. package/lib/extensions-service.js +1527 -0
  36. package/lib/harness-llm.d.ts +138 -0
  37. package/lib/harness-llm.d.ts.map +1 -0
  38. package/lib/harness-llm.js +218 -0
  39. package/lib/harness-serving.d.ts +95 -0
  40. package/lib/harness-serving.d.ts.map +1 -0
  41. package/lib/harness-serving.js +157 -0
  42. package/lib/headless.d.ts +97 -0
  43. package/lib/headless.d.ts.map +1 -0
  44. package/lib/headless.js +349 -0
  45. package/lib/http-acquisition.d.ts +27 -0
  46. package/lib/http-acquisition.d.ts.map +1 -0
  47. package/lib/http-acquisition.js +136 -0
  48. package/lib/index.d.ts +271 -7
  49. package/lib/index.d.ts.map +1 -1
  50. package/lib/index.js +2644 -70
  51. package/lib/invocation-accounting.d.ts +169 -0
  52. package/lib/invocation-accounting.d.ts.map +1 -0
  53. package/lib/invocation-accounting.js +148 -0
  54. package/lib/legacy-migration.d.ts +95 -0
  55. package/lib/legacy-migration.d.ts.map +1 -0
  56. package/lib/legacy-migration.js +429 -0
  57. package/lib/managed-workers.d.ts +151 -0
  58. package/lib/managed-workers.d.ts.map +1 -0
  59. package/lib/managed-workers.js +249 -0
  60. package/lib/mcp/client-grant.d.ts +74 -0
  61. package/lib/mcp/client-grant.d.ts.map +1 -0
  62. package/lib/mcp/client-grant.js +60 -0
  63. package/lib/mcp/credential-cli.d.ts +38 -0
  64. package/lib/mcp/credential-cli.d.ts.map +1 -0
  65. package/lib/mcp/credential-cli.js +148 -0
  66. package/lib/mcp/credential-store.d.ts +143 -0
  67. package/lib/mcp/credential-store.d.ts.map +1 -0
  68. package/lib/mcp/credential-store.js +454 -0
  69. package/lib/mcp/host-binding.d.ts +25 -0
  70. package/lib/mcp/host-binding.d.ts.map +1 -1
  71. package/lib/mcp/host-binding.js +107 -14
  72. package/lib/mcp/http.d.ts +68 -18
  73. package/lib/mcp/http.d.ts.map +1 -1
  74. package/lib/mcp/http.js +390 -62
  75. package/lib/mcp/oauth.d.ts +149 -0
  76. package/lib/mcp/oauth.d.ts.map +1 -0
  77. package/lib/mcp/oauth.js +396 -0
  78. package/lib/mcp/protocol.d.ts +46 -7
  79. package/lib/mcp/protocol.d.ts.map +1 -1
  80. package/lib/mcp/protocol.js +63 -16
  81. package/lib/mcp/rate-limit.d.ts +68 -0
  82. package/lib/mcp/rate-limit.d.ts.map +1 -0
  83. package/lib/mcp/rate-limit.js +114 -0
  84. package/lib/mcp/sdk-v2.d.ts +17 -0
  85. package/lib/mcp/sdk-v2.d.ts.map +1 -0
  86. package/lib/mcp/sdk-v2.js +181 -0
  87. package/lib/mcp/server.d.ts +11 -1
  88. package/lib/mcp/server.d.ts.map +1 -1
  89. package/lib/mcp/server.js +80 -13
  90. package/lib/mcp/stdio.d.ts.map +1 -1
  91. package/lib/mcp/stdio.js +9 -1
  92. package/lib/mcp/subscriptions.d.ts +43 -1
  93. package/lib/mcp/subscriptions.d.ts.map +1 -1
  94. package/lib/mcp/subscriptions.js +90 -9
  95. package/lib/model-qualification.d.ts +192 -0
  96. package/lib/model-qualification.d.ts.map +1 -0
  97. package/lib/model-qualification.js +178 -0
  98. package/lib/planning.d.ts +246 -0
  99. package/lib/planning.d.ts.map +1 -0
  100. package/lib/planning.js +457 -0
  101. package/lib/prepared-assets.d.ts +178 -0
  102. package/lib/prepared-assets.d.ts.map +1 -0
  103. package/lib/prepared-assets.js +286 -0
  104. package/lib/provider-bridge.d.ts +37 -0
  105. package/lib/provider-bridge.d.ts.map +1 -1
  106. package/lib/provider-bridge.js +189 -12
  107. package/lib/ptc-authority.d.ts +140 -0
  108. package/lib/ptc-authority.d.ts.map +1 -0
  109. package/lib/ptc-authority.js +216 -0
  110. package/lib/ptc-runtime.d.ts +156 -0
  111. package/lib/ptc-runtime.d.ts.map +1 -0
  112. package/lib/ptc-runtime.js +191 -0
  113. package/lib/publication.d.ts +2 -1
  114. package/lib/publication.d.ts.map +1 -1
  115. package/lib/publication.js +13 -5
  116. package/lib/receipt.d.ts.map +1 -1
  117. package/lib/receipt.js +8 -0
  118. package/lib/title-proposal.d.ts +194 -0
  119. package/lib/title-proposal.d.ts.map +1 -0
  120. package/lib/title-proposal.js +244 -0
  121. package/lib/tool-mode-run.d.ts +235 -0
  122. package/lib/tool-mode-run.d.ts.map +1 -0
  123. package/lib/tool-mode-run.js +283 -0
  124. package/lib/types.d.ts +3 -1
  125. package/lib/types.d.ts.map +1 -1
  126. package/lib/update-surface.d.ts +79 -0
  127. package/lib/update-surface.d.ts.map +1 -0
  128. package/lib/update-surface.js +224 -0
  129. package/node_modules/@try-works/tool-mode-contracts/lib/contracts/standard.d.ts +38 -0
  130. package/node_modules/@try-works/tool-mode-contracts/lib/contracts/standard.d.ts.map +1 -0
  131. package/node_modules/@try-works/tool-mode-contracts/lib/contracts/standard.js +445 -0
  132. package/node_modules/@try-works/tool-mode-contracts/lib/contracts/text-embed.d.ts +14 -0
  133. package/node_modules/@try-works/tool-mode-contracts/lib/contracts/text-embed.d.ts.map +1 -0
  134. package/node_modules/@try-works/tool-mode-contracts/lib/contracts/text-embed.js +63 -0
  135. package/node_modules/@try-works/tool-mode-contracts/lib/contracts/text-rerank.d.ts +14 -0
  136. package/node_modules/@try-works/tool-mode-contracts/lib/contracts/text-rerank.d.ts.map +1 -0
  137. package/node_modules/@try-works/tool-mode-contracts/lib/contracts/text-rerank.js +76 -0
  138. package/node_modules/@try-works/tool-mode-contracts/lib/dsh.d.ts.map +1 -1
  139. package/node_modules/@try-works/tool-mode-contracts/lib/dsh.js +69 -55
  140. package/node_modules/@try-works/tool-mode-contracts/lib/index.d.ts +4 -1
  141. package/node_modules/@try-works/tool-mode-contracts/lib/index.d.ts.map +1 -1
  142. package/node_modules/@try-works/tool-mode-contracts/lib/index.js +26 -2
  143. package/node_modules/@try-works/tool-mode-contracts/lib/registry.d.ts +28 -2
  144. package/node_modules/@try-works/tool-mode-contracts/lib/registry.d.ts.map +1 -1
  145. package/node_modules/@try-works/tool-mode-contracts/lib/registry.js +93 -10
  146. package/node_modules/@try-works/tool-mode-contracts/package.json +0 -1
  147. package/node_modules/@try-works/tool-mode-extension-sdk/bin/dsh-tool-mode.mjs +20 -0
  148. package/node_modules/@try-works/tool-mode-extension-sdk/lib/cli.d.ts +80 -0
  149. package/node_modules/@try-works/tool-mode-extension-sdk/lib/cli.d.ts.map +1 -0
  150. package/node_modules/@try-works/tool-mode-extension-sdk/lib/cli.js +533 -0
  151. package/node_modules/@try-works/tool-mode-extension-sdk/lib/declarative-recipes.d.ts +83 -0
  152. package/node_modules/@try-works/tool-mode-extension-sdk/lib/declarative-recipes.d.ts.map +1 -0
  153. package/node_modules/@try-works/tool-mode-extension-sdk/lib/declarative-recipes.js +125 -0
  154. package/node_modules/@try-works/tool-mode-extension-sdk/lib/discovery.d.ts +70 -0
  155. package/node_modules/@try-works/tool-mode-extension-sdk/lib/discovery.d.ts.map +1 -0
  156. package/node_modules/@try-works/tool-mode-extension-sdk/lib/discovery.js +201 -0
  157. package/node_modules/@try-works/tool-mode-extension-sdk/lib/errors.d.ts +57 -0
  158. package/node_modules/@try-works/tool-mode-extension-sdk/lib/errors.d.ts.map +1 -0
  159. package/node_modules/@try-works/tool-mode-extension-sdk/lib/errors.js +83 -0
  160. package/node_modules/@try-works/tool-mode-extension-sdk/lib/index.d.ts +32 -0
  161. package/node_modules/@try-works/tool-mode-extension-sdk/lib/index.d.ts.map +1 -0
  162. package/node_modules/@try-works/tool-mode-extension-sdk/lib/index.js +32 -0
  163. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/archive.d.ts +86 -0
  164. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/archive.d.ts.map +1 -0
  165. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/archive.js +191 -0
  166. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/dependencies.d.ts +126 -0
  167. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/dependencies.d.ts.map +1 -0
  168. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/dependencies.js +297 -0
  169. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/lifecycle.d.ts +216 -0
  170. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/lifecycle.d.ts.map +1 -0
  171. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/lifecycle.js +800 -0
  172. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/migration.d.ts +210 -0
  173. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/migration.d.ts.map +1 -0
  174. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/migration.js +368 -0
  175. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/runtime-link.d.ts +63 -0
  176. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/runtime-link.d.ts.map +1 -0
  177. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/runtime-link.js +149 -0
  178. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/source-directory.d.ts +35 -0
  179. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/source-directory.d.ts.map +1 -0
  180. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/source-directory.js +179 -0
  181. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/staged.d.ts +179 -0
  182. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/staged.d.ts.map +1 -0
  183. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/staged.js +235 -0
  184. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/store.d.ts +367 -0
  185. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/store.d.ts.map +1 -0
  186. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/store.js +807 -0
  187. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/tar-gzip-source.d.ts +44 -0
  188. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/tar-gzip-source.d.ts.map +1 -0
  189. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/tar-gzip-source.js +243 -0
  190. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/update-flow.d.ts +185 -0
  191. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/update-flow.d.ts.map +1 -0
  192. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/update-flow.js +504 -0
  193. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/zip-source.d.ts +17 -0
  194. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/zip-source.d.ts.map +1 -0
  195. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/zip-source.js +80 -0
  196. package/node_modules/@try-works/tool-mode-extension-sdk/lib/load.d.ts +52 -0
  197. package/node_modules/@try-works/tool-mode-extension-sdk/lib/load.d.ts.map +1 -0
  198. package/node_modules/@try-works/tool-mode-extension-sdk/lib/load.js +102 -0
  199. package/node_modules/@try-works/tool-mode-extension-sdk/lib/manifest.d.ts +171 -0
  200. package/node_modules/@try-works/tool-mode-extension-sdk/lib/manifest.d.ts.map +1 -0
  201. package/node_modules/@try-works/tool-mode-extension-sdk/lib/manifest.js +271 -0
  202. package/node_modules/@try-works/tool-mode-extension-sdk/lib/runtime.d.ts +242 -0
  203. package/node_modules/@try-works/tool-mode-extension-sdk/lib/runtime.d.ts.map +1 -0
  204. package/node_modules/@try-works/tool-mode-extension-sdk/lib/runtime.js +58 -0
  205. package/node_modules/@try-works/tool-mode-extension-sdk/lib/state.d.ts +118 -0
  206. package/node_modules/@try-works/tool-mode-extension-sdk/lib/state.d.ts.map +1 -0
  207. package/node_modules/@try-works/tool-mode-extension-sdk/lib/state.js +113 -0
  208. package/node_modules/@try-works/tool-mode-extension-sdk/lib/worker/worker-child.d.ts +2 -0
  209. package/node_modules/@try-works/tool-mode-extension-sdk/lib/worker/worker-child.d.ts.map +1 -0
  210. package/node_modules/@try-works/tool-mode-extension-sdk/lib/worker/worker-child.js +351 -0
  211. package/node_modules/@try-works/tool-mode-extension-sdk/lib/worker.d.ts +150 -0
  212. package/node_modules/@try-works/tool-mode-extension-sdk/lib/worker.d.ts.map +1 -0
  213. package/node_modules/@try-works/tool-mode-extension-sdk/lib/worker.js +670 -0
  214. package/node_modules/@try-works/tool-mode-extension-sdk/package.json +53 -0
  215. package/node_modules/@try-works/tool-mode-extension-sdk/schema/extension-manifest-1.1.0.schema.json +253 -0
  216. package/node_modules/@try-works/tool-mode-recipes/bin/tool-mode-recipe.mjs +15 -0
  217. package/node_modules/@try-works/tool-mode-recipes/lib/broker.d.ts +427 -0
  218. package/node_modules/@try-works/tool-mode-recipes/lib/broker.d.ts.map +1 -0
  219. package/node_modules/@try-works/tool-mode-recipes/lib/broker.js +592 -0
  220. package/node_modules/@try-works/tool-mode-recipes/lib/cli.d.ts +60 -0
  221. package/node_modules/@try-works/tool-mode-recipes/lib/cli.d.ts.map +1 -0
  222. package/node_modules/@try-works/tool-mode-recipes/lib/cli.js +936 -0
  223. package/node_modules/@try-works/tool-mode-recipes/lib/dsh-adapter.d.ts +237 -0
  224. package/node_modules/@try-works/tool-mode-recipes/lib/dsh-adapter.d.ts.map +1 -0
  225. package/node_modules/@try-works/tool-mode-recipes/lib/dsh-adapter.js +200 -0
  226. package/node_modules/@try-works/tool-mode-recipes/lib/fixture-provider.d.ts +85 -0
  227. package/node_modules/@try-works/tool-mode-recipes/lib/fixture-provider.d.ts.map +1 -0
  228. package/node_modules/@try-works/tool-mode-recipes/lib/fixture-provider.js +176 -0
  229. package/node_modules/@try-works/tool-mode-recipes/lib/images.d.ts +105 -0
  230. package/node_modules/@try-works/tool-mode-recipes/lib/images.d.ts.map +1 -0
  231. package/node_modules/@try-works/tool-mode-recipes/lib/images.js +129 -0
  232. package/node_modules/@try-works/tool-mode-recipes/lib/index.d.ts +25 -0
  233. package/node_modules/@try-works/tool-mode-recipes/lib/index.d.ts.map +1 -0
  234. package/node_modules/@try-works/tool-mode-recipes/lib/index.js +25 -0
  235. package/node_modules/@try-works/tool-mode-recipes/lib/recipe-format.d.ts +325 -0
  236. package/node_modules/@try-works/tool-mode-recipes/lib/recipe-format.d.ts.map +1 -0
  237. package/node_modules/@try-works/tool-mode-recipes/lib/recipe-format.js +1310 -0
  238. package/node_modules/@try-works/tool-mode-recipes/lib/templates.d.ts +51 -0
  239. package/node_modules/@try-works/tool-mode-recipes/lib/templates.d.ts.map +1 -0
  240. package/node_modules/@try-works/tool-mode-recipes/lib/templates.js +310 -0
  241. package/node_modules/@try-works/tool-mode-recipes/lib/titles.d.ts +227 -0
  242. package/node_modules/@try-works/tool-mode-recipes/lib/titles.d.ts.map +1 -0
  243. package/node_modules/@try-works/tool-mode-recipes/lib/titles.js +407 -0
  244. package/node_modules/@try-works/tool-mode-recipes/package.json +56 -0
  245. package/node_modules/@try-works/tool-mode-recipes/recipes/describe-image.json +38 -0
  246. package/node_modules/@try-works/tool-mode-recipes/recipes/text-title.json +45 -0
  247. package/node_modules/@try-works/tool-mode-recipes/schema/harness-model-recipe.schema.json +273 -0
  248. package/package.json +43 -15
  249. package/remote/spec.mjs +462 -0
  250. package/remote/typert.host.mjs +14 -0
  251. package/remote/typert.remote-client.mjs +16 -0
  252. package/lib/adapters/tongue.d.ts +0 -31
  253. package/lib/adapters/tongue.d.ts.map +0 -1
  254. package/lib/adapters/tongue.js +0 -93
  255. package/lib/adapters/voz.d.ts +0 -28
  256. package/lib/adapters/voz.d.ts.map +0 -1
  257. package/lib/adapters/voz.js +0 -102
  258. /package/{lib → node_modules/@try-works/tool-mode-extension-sdk/lib}/licensing.d.ts +0 -0
  259. /package/{lib → node_modules/@try-works/tool-mode-extension-sdk/lib}/licensing.d.ts.map +0 -0
  260. /package/{lib → node_modules/@try-works/tool-mode-extension-sdk/lib}/licensing.js +0 -0
@@ -0,0 +1,858 @@
1
+ /**
2
+ * First-party dependency graphs for developer-mode extension installs (EXT-09).
3
+ *
4
+ * Installing a first-party package from its checkout must satisfy the SAME
5
+ * discipline the SDK applies to a registry install: every declared runtime
6
+ * dependency is resolved to an exact version, verified by content, and
7
+ * materialized INSIDE the extension's own immutable installation directory. The
8
+ * SDK owns that discipline (`installDependencyGraph`); this module supplies the
9
+ * host half it deliberately leaves injected:
10
+ *
11
+ * - the RESOLVER: the exact version of a declared dependency is the one already
12
+ * installed in the host's own tree, found by walking `node_modules` upward from
13
+ * the source checkout exactly the way Node would. No registry request is made,
14
+ * no version is guessed, and a dependency that is not installed refuses the
15
+ * install instead of being skipped.
16
+ * - the ARCHIVE: the resolved package directory is turned into a deterministic
17
+ * zip whose SHA-512 is the integrity the SDK verifies before extraction. The
18
+ * archive is an ordinary package archive — `node_modules` is excluded because
19
+ * the SDK resolves the transitive graph itself, and the package's declared
20
+ * install-time scripts are left in place so the SDK's own refusal (EXT-10)
21
+ * decides whether the dependency is admissible.
22
+ *
23
+ * Two properties matter for truthfulness:
24
+ *
25
+ * - **The host's node_modules is READ, never written.** The only write target is
26
+ * the SDK's staging directory, which becomes the extension's private
27
+ * `node_modules`; the resolver asserts this by construction (it has no write
28
+ * path outside the archive bytes).
29
+ * - **A declaration is never silently dropped.** Every name the extension
30
+ * declares is either materialized or the whole install fails with a named
31
+ * `DEPENDENCY_UNSATISFIED` reason.
32
+ *
33
+ * INTEGRITY NOTE. The SDK's dependency lock records the SHA-512 of the archive
34
+ * bytes it verified (`verifyIntegrity`), which is what this module computes. The
35
+ * directory's own identity is additionally checked with the SDK's
36
+ * `digestInstalledContent` before and after the archive is built, so a checkout
37
+ * that changes while it is being archived refuses rather than shipping bytes that
38
+ * no longer match what was resolved.
39
+ *
40
+ * @module @try-works/dsh-model-as-tool/extension-dependencies
41
+ */
42
+ import { createHash } from 'node:crypto';
43
+ import { existsSync, lstatSync, readFileSync, readdirSync } from 'node:fs';
44
+ import { dirname, isAbsolute, join, relative, resolve } from 'node:path';
45
+ import { fileURLToPath } from 'node:url';
46
+ import AdmZip from 'adm-zip';
47
+ import { createZipArchiveSource, DEFAULT_ARCHIVE_LIMITS, digestInstalledContent, ManagementError, refuseInstallScripts, } from '@try-works/tool-mode-extension-sdk';
48
+ /** A package identity: one optional scope segment, then one name segment. */
49
+ const PACKAGE_ID = /^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/i;
50
+ /** A message from an arbitrary thrown value. */
51
+ function messageOf(error) {
52
+ return error instanceof Error ? error.message : String(error);
53
+ }
54
+ /**
55
+ * The dependency archive byte cap.
56
+ *
57
+ * The SDK's `installDependencyGraph` calls `fetch` with a 256 MiB bound and
58
+ * requires the source to enforce it, so this module refuses an archive it could
59
+ * not truthfully hand over under that contract.
60
+ */
61
+ export const MAX_DEPENDENCY_ARCHIVE_BYTES = 256 * 1024 * 1024;
62
+ /**
63
+ * Refuse an identity that cannot safely become a path component.
64
+ * @param name - the declared dependency name.
65
+ * @throws ManagementError `DEPENDENCY_UNSATISFIED` for an unusable name.
66
+ */
67
+ function assertUsableName(name) {
68
+ if (!PACKAGE_ID.test(name)) {
69
+ throw new ManagementError('DEPENDENCY_UNSATISFIED', 'install', `${JSON.stringify(name)} is not a package name; a dependency declaration must be a name like @scope/name`, 'Correct the dependency name in the extension package.json.');
70
+ }
71
+ }
72
+ /** Read one installed package's identity, or undefined when it is not a package. */
73
+ function readInstalledPackage(directory, expectedName) {
74
+ let parsed;
75
+ try {
76
+ parsed = JSON.parse(readFileSync(join(directory, 'package.json'), 'utf8'));
77
+ }
78
+ catch {
79
+ return undefined;
80
+ }
81
+ if (typeof parsed !== 'object' || parsed === null)
82
+ return undefined;
83
+ // SAFETY: the value came from JSON.parse and the object guard above; every field
84
+ // is re-checked (name equality, string version) before it reaches the caller, so
85
+ // an unexpected shape is treated as "not this package" rather than trusted.
86
+ const recorded = parsed;
87
+ const name = recorded.name;
88
+ const version = recorded.version;
89
+ if (name !== expectedName)
90
+ return undefined;
91
+ if (typeof version !== 'string' || version.length === 0)
92
+ return undefined;
93
+ return { name: expectedName, version, directory };
94
+ }
95
+ /**
96
+ * Look for a package in the `node_modules` directory of a starting point and every
97
+ * ancestor, exactly the order Node's own resolver applies.
98
+ * @param start - a directory inside the requesting tree.
99
+ * @param name - the dependency name.
100
+ * @returns the installed package, or undefined when no ancestor holds it.
101
+ */
102
+ function walkNodeModules(start, name) {
103
+ let current = resolve(start);
104
+ for (let depth = 0; depth < 64; depth += 1) {
105
+ const candidate = join(current, 'node_modules', ...name.split('/'));
106
+ if (existsSync(join(candidate, 'package.json'))) {
107
+ const found = readInstalledPackage(candidate, name);
108
+ if (found !== undefined)
109
+ return found;
110
+ }
111
+ const parent = dirname(current);
112
+ if (parent === current)
113
+ return undefined;
114
+ current = parent;
115
+ }
116
+ return undefined;
117
+ }
118
+ /** The host's own module directory: the fallback tree for host-installed dependencies. */
119
+ function hostModuleDirectory() {
120
+ return dirname(fileURLToPath(import.meta.url));
121
+ }
122
+ /**
123
+ * Resolve one declared dependency from the host's installed tree.
124
+ *
125
+ * The source checkout's own ancestor chain is searched first (the tree whose
126
+ * `package.json` declared the dependency), then the host's own module chain, then
127
+ * any explicitly configured roots. The first installed package whose own
128
+ * `package.json` name matches wins; nothing is downloaded and no version is
129
+ * inferred.
130
+ * @param search - where to look.
131
+ * @param name - the declared dependency name.
132
+ * @param range - the declared spec, used only in the refusal message.
133
+ * @returns the installed dependency.
134
+ * @throws ManagementError `DEPENDENCY_UNSATISFIED` when it is not installed.
135
+ */
136
+ export function resolveFirstPartyDependency(search, name, range) {
137
+ assertUsableName(name);
138
+ const origins = [resolve(search.sourcePackageDir), hostModuleDirectory()];
139
+ for (const origin of origins) {
140
+ const found = walkNodeModules(origin, name);
141
+ if (found !== undefined)
142
+ return found;
143
+ }
144
+ for (const root of search.extraSearchRoots ?? []) {
145
+ const candidate = join(resolve(root), 'node_modules', ...name.split('/'));
146
+ if (existsSync(join(candidate, 'package.json'))) {
147
+ const found = readInstalledPackage(candidate, name);
148
+ if (found !== undefined)
149
+ return found;
150
+ }
151
+ }
152
+ throw new ManagementError('DEPENDENCY_UNSATISFIED', 'install', `${name} (declared as ${JSON.stringify(range)}) is not installed anywhere in the host's own dependency tree, `
153
+ + 'so it cannot be materialized into the extension', 'Install the dependency for the host first (or bundle it with the extension); the installer never downloads '
154
+ + 'dependencies for a source-directory install and never silently drops a declared one.');
155
+ }
156
+ /**
157
+ * Collect a package directory's files as archive entries.
158
+ *
159
+ * `node_modules` is skipped (the SDK resolves the transitive graph through this
160
+ * same resolver), and any symbolic link or junction inside the tree is REFUSED
161
+ * rather than followed: a link is how a package reaches bytes outside the content
162
+ * that was resolved.
163
+ * @param root - the package directory.
164
+ * @param limits - the byte bounds.
165
+ * @returns the selected files, in sorted walk order.
166
+ */
167
+ function collectPackageFiles(root, limits) {
168
+ const files = [];
169
+ let totalBytes = 0;
170
+ const walk = (current) => {
171
+ for (const entry of readdirSync(current, { withFileTypes: true }).sort((left, right) => left.name.localeCompare(right.name))) {
172
+ const absolute = join(current, entry.name);
173
+ if (entry.isSymbolicLink()) {
174
+ throw new ManagementError('DEPENDENCY_UNSATISFIED', 'install', `the installed dependency contains a symbolic link at ${absolute}; a link makes the archived content differ from the content that was resolved`, 'Repair the installed dependency so the tree contains plain files, then retry.');
175
+ }
176
+ if (entry.isDirectory()) {
177
+ if (entry.name === 'node_modules')
178
+ continue;
179
+ walk(absolute);
180
+ continue;
181
+ }
182
+ if (!entry.isFile()) {
183
+ throw new ManagementError('DEPENDENCY_UNSATISFIED', 'install', `the installed dependency contains ${absolute}, which is neither a file nor a directory`, 'Repair the installed dependency, then retry.');
184
+ }
185
+ const size = lstatSync(absolute).size;
186
+ if (size > limits.maxEntryBytes) {
187
+ throw new ManagementError('DEPENDENCY_UNSATISFIED', 'install', `the dependency file ${absolute} is ${size} bytes, over the ${limits.maxEntryBytes}-byte per-file bound`, 'Use a dependency whose individual files fit the archive bounds, or install it outside the extension.');
188
+ }
189
+ totalBytes += size;
190
+ if (totalBytes > limits.maxSourceBytes) {
191
+ throw new ManagementError('DEPENDENCY_UNSATISFIED', 'install', `the dependency tree at ${root} is over the ${limits.maxSourceBytes}-byte archive bound`, 'Use a dependency whose installed tree fits the bounds, or install it outside the extension.');
192
+ }
193
+ files.push({ relative: relativePath(root, absolute), absolute, size });
194
+ }
195
+ };
196
+ walk(root);
197
+ if (files.length === 0) {
198
+ throw new ManagementError('DEPENDENCY_UNSATISFIED', 'install', `the installed dependency at ${root} contains no files`, 'Repair the installed dependency, then retry.');
199
+ }
200
+ return files;
201
+ }
202
+ /** A path relative to the package root, using `/` separators for the archive. */
203
+ function relativePath(root, absolute) {
204
+ return relative(resolve(root), absolute).split('\\').join('/');
205
+ }
206
+ /** Whether a directory is the root or lives underneath it. */
207
+ function isUnder(root, candidate) {
208
+ const rootPath = resolve(root);
209
+ const candidatePath = resolve(candidate);
210
+ if (candidatePath === rootPath)
211
+ return true;
212
+ const relativePathFromRoot = relative(rootPath, candidatePath);
213
+ return relativePathFromRoot.length > 0 && !relativePathFromRoot.startsWith('..') && !isAbsolute(relativePathFromRoot);
214
+ }
215
+ /**
216
+ * Build a deterministic zip of one installed dependency directory.
217
+ *
218
+ * The result is the archive the SDK's dependency graph fetches and verifies. Its
219
+ * SHA-512 is the integrity handed to the graph; the directory's own
220
+ * `digestInstalledContent` is checked before and after the build so a concurrent
221
+ * change refuses.
222
+ * @param dependency - the resolved installed dependency.
223
+ * @param limits - the byte bounds.
224
+ * @returns the archive bytes, their integrity, and the directory digest.
225
+ * @throws ManagementError `DEPENDENCY_UNSATISFIED` for any refusal.
226
+ */
227
+ export function buildDependencyArchive(dependency, limits) {
228
+ const files = collectPackageFiles(dependency.directory, limits);
229
+ // The same content-digest discipline the SDK applies to a directory install:
230
+ // the tree's identity is checked around the build, so the archive cannot be
231
+ // assembled from a tree that changed while it was being read.
232
+ let before;
233
+ try {
234
+ before = digestInstalledContent(dependency.directory);
235
+ }
236
+ catch (error) {
237
+ throw new ManagementError('DEPENDENCY_UNSATISFIED', 'install', `the installed dependency ${dependency.name}@${dependency.version} could not be digested: ${error instanceof Error ? error.message : String(error)}`, 'Repair the installed dependency so every file is readable, then retry.');
238
+ }
239
+ const zip = new AdmZip();
240
+ for (const file of files)
241
+ zip.addFile(file.relative, readFileSync(file.absolute));
242
+ const bytes = new Uint8Array(zip.toBuffer());
243
+ if (bytes.byteLength > limits.maxArchiveBytes) {
244
+ throw new ManagementError('DEPENDENCY_UNSATISFIED', 'install', `the dependency ${dependency.name}@${dependency.version} archives to ${bytes.byteLength} bytes, over the ${limits.maxArchiveBytes}-byte dependency bound`, 'Use a dependency whose archive fits the installer bound, or install it outside the extension.');
245
+ }
246
+ const after = digestInstalledContent(dependency.directory);
247
+ if (before !== after) {
248
+ throw new ManagementError('DEPENDENCY_UNSATISFIED', 'install', `the installed dependency ${dependency.name}@${dependency.version} changed while it was being archived`, 'Make sure nothing is installing into the host tree, then retry.');
249
+ }
250
+ return {
251
+ bytes,
252
+ integrity: createHash('sha512').update(bytes).digest('hex'),
253
+ contentDigest: after,
254
+ };
255
+ }
256
+ /**
257
+ * Decide how this host supplies one dependency, applying the operator gate.
258
+ *
259
+ * With the preinstalled gate on, the resolved directory is returned as-is (the SDK
260
+ * copies it and records provenance; scripts are not run and are NOT stripped).
261
+ * With the gate off, the bytes arm applies: the SDK's EXT-10 refusal is consulted
262
+ * BEFORE any archive is built, so a script-declaring dependency refuses cheaply
263
+ * with the SDK's exact named error, then the archive is built and its SHA-512 is
264
+ * returned as the integrity the SDK will verify.
265
+ * @param options - the search tree and gate.
266
+ * @param limits - the archive bounds used by the bytes arm.
267
+ * @param name - the declared dependency name.
268
+ * @param range - the declared dependency spec, for refusals.
269
+ * @returns the supply decision.
270
+ * @throws ManagementError `DEPENDENCY_UNSATISFIED` for every refusal.
271
+ */
272
+ function resolveDependency(options, limits, name, range) {
273
+ const dependency = resolveFirstPartyDependency(options, name, range);
274
+ if (options.allowPreinstalled === true) {
275
+ const root = options.preinstalledRoot;
276
+ if (root === undefined || !isUnder(root, dependency.directory)) {
277
+ throw new ManagementError('DEPENDENCY_UNSATISFIED', 'install', `${dependency.name}@${dependency.version} is installed at ${dependency.directory}, outside the operator-configured preinstalled root ${root ?? '(none configured)'}; preinstalled content may only be copied from inside that root`, 'Configure the preinstalled-dependency root so it contains the host tree this dependency was installed into.');
278
+ }
279
+ return { kind: 'preinstalled', dependency, provenance: 'preinstalled' };
280
+ }
281
+ // The SDK's own EXT-10 gate runs before any bytes are built: a dependency that
282
+ // declares an install-time script refuses here with the SDK's exact named error
283
+ // instead of after a full archive/extract cycle. The SDK checks again after
284
+ // extraction; this copy is the cheap, identical refusal.
285
+ refuseInstallScripts(dependency.directory, dependency.name, dependency.version);
286
+ const built = buildDependencyArchive(dependency, limits);
287
+ return { kind: 'registry-bytes', dependency, integrity: built.integrity, bytes: built.bytes };
288
+ }
289
+ /**
290
+ * Create the first-party dependency graph supplied to the SDK's directory installer.
291
+ *
292
+ * Resolution and archiving happen lazily, one dependency at a time: `resolve`
293
+ * builds the exact bytes the SDK will verify and `fetch` hands back those same
294
+ * bytes, so the integrity the lock records is the integrity of the served archive
295
+ * rather than a value guessed from metadata. Only the most recently built archive
296
+ * is retained, which matches the SDK's resolve-then-fetch sequence and bounds
297
+ * memory to one dependency.
298
+ *
299
+ * The preinstalled-content gate is OPT-IN and OFF by default, matching the SDK:
300
+ * without it every dependency is served as verified bytes and EXT-10 applies. With
301
+ * it, the SDK requires `preinstalledRoot`, copies the resolved directory into the
302
+ * extension's own `node_modules`, and records `provenance: 'preinstalled'`.
303
+ * @param options - the search tree, bounds, gate and forwarded graph limits.
304
+ * @returns the install-dependency graph plus its resolver and archive halves.
305
+ * @throws ManagementError `DEPENDENCY_UNSATISFIED` when the gate has no root.
306
+ */
307
+ export function createFirstPartyDependencyGraph(options) {
308
+ const limits = {
309
+ maxSourceBytes: options.maxSourceBytes ?? DEFAULT_ARCHIVE_LIMITS.maxTotalBytes,
310
+ maxEntryBytes: options.maxEntryBytes ?? DEFAULT_ARCHIVE_LIMITS.maxEntryBytes,
311
+ maxArchiveBytes: options.maxArchiveBytes ?? MAX_DEPENDENCY_ARCHIVE_BYTES,
312
+ };
313
+ let preinstalledGate;
314
+ if (options.allowPreinstalled === true) {
315
+ const root = options.preinstalledRoot;
316
+ if (root === undefined || root.trim().length === 0) {
317
+ throw new ManagementError('DEPENDENCY_UNSATISFIED', 'install', 'the preinstalled-dependency gate is enabled but no operator-configured preinstalled root was supplied', 'Configure the root directory that contains the host dependency tree, or leave the gate off.');
318
+ }
319
+ preinstalledGate = { allowPreinstalled: true, preinstalledRoot: resolve(root) };
320
+ }
321
+ /** The archive built by the most recent resolve, keyed by name@version. */
322
+ let latest;
323
+ const keyOf = (name, version) => `${name}@${version}`;
324
+ const resolver = {
325
+ async resolve(name, range) {
326
+ const supply = resolveDependency(options, limits, name, range);
327
+ const key = keyOf(supply.dependency.name, supply.dependency.version);
328
+ if (supply.kind === 'preinstalled') {
329
+ return {
330
+ name: supply.dependency.name,
331
+ version: supply.dependency.version,
332
+ dir: supply.dependency.directory,
333
+ provenance: 'preinstalled',
334
+ };
335
+ }
336
+ latest = { key, bytes: supply.bytes };
337
+ return {
338
+ name: supply.dependency.name,
339
+ version: supply.dependency.version,
340
+ tarball: { kind: 'local', requested: key },
341
+ integrity: supply.integrity,
342
+ };
343
+ },
344
+ };
345
+ const archive = createZipArchiveSource(async (source) => {
346
+ if (latest === undefined || latest.key !== source.requested) {
347
+ throw new ManagementError('DEPENDENCY_UNSATISFIED', 'install', `the dependency archive ${source.requested} was not built by the resolver, so it cannot be served`, 'Retry the install; the graph builds each archive immediately before the SDK fetches it.');
348
+ }
349
+ if (latest.bytes.byteLength > MAX_DEPENDENCY_ARCHIVE_BYTES) {
350
+ throw new ManagementError('DEPENDENCY_UNSATISFIED', 'install', `the dependency archive ${source.requested} is ${latest.bytes.byteLength} bytes, over the ${MAX_DEPENDENCY_ARCHIVE_BYTES}-byte fetch bound`, 'Use a dependency whose archive fits the installer bound.');
351
+ }
352
+ return latest.bytes;
353
+ });
354
+ /** The optional graph fields, added only when configured (no empty-object spreads). */
355
+ const optionalGraphFields = {};
356
+ if (preinstalledGate !== undefined) {
357
+ optionalGraphFields.allowPreinstalled = preinstalledGate.allowPreinstalled;
358
+ optionalGraphFields.preinstalledRoot = preinstalledGate.preinstalledRoot;
359
+ }
360
+ if (options.maxDepth !== undefined)
361
+ optionalGraphFields.maxDepth = options.maxDepth;
362
+ if (options.maxPackages !== undefined)
363
+ optionalGraphFields.maxPackages = options.maxPackages;
364
+ const graph = {
365
+ resolver,
366
+ archive,
367
+ ...optionalGraphFields,
368
+ };
369
+ return { graph, resolver, archive };
370
+ }
371
+ /** Re-export for callers that want the size bound wording. */
372
+ export { DEFAULT_ARCHIVE_LIMITS };
373
+ // ============================================================================
374
+ // npm registry installs (task-104)
375
+ // ============================================================================
376
+ /** The default npm registry. */
377
+ export const DEFAULT_REGISTRY_URL = 'https://registry.npmjs.org/';
378
+ /** Metadata response cap (a packument is JSON; 16 MiB is generous for one package). */
379
+ const MAX_REGISTRY_METADATA_BYTES = 16 * 1024 * 1024;
380
+ /** A named registry failure. */
381
+ export class RegistryError extends Error {
382
+ name = 'RegistryError';
383
+ code;
384
+ /** Whether a directory fallback may be considered under `auto`. */
385
+ transport;
386
+ constructor(code, message, options) {
387
+ super(message, options?.cause === undefined ? undefined : { cause: options.cause });
388
+ this.code = code;
389
+ this.transport = options?.transport === true;
390
+ }
391
+ }
392
+ /** Normalize a registry base URL, or '' when unconfigured. */
393
+ function normalizeRegistryUrl(url) {
394
+ const trimmed = url.trim();
395
+ if (trimmed.length === 0)
396
+ return '';
397
+ return trimmed.endsWith('/') ? trimmed.slice(0, -1) : trimmed;
398
+ }
399
+ /** Encode a package name for the registry path (`@scope%2fname`). */
400
+ function encodePackageName(name) {
401
+ // npm encodes scoped names' slash as %2f and leaves the leading @ readable.
402
+ return name.startsWith('@') ? name.replace('/', '%2f') : encodeURIComponent(name);
403
+ }
404
+ /** Read a response body with a hard byte cap enforced BEFORE allocation grows unbounded. */
405
+ async function readBoundedBody(response, maxBytes) {
406
+ const declared = Number(response.headers.get('content-length') ?? Number.NaN);
407
+ if (Number.isFinite(declared) && declared > maxBytes) {
408
+ await response.body?.cancel();
409
+ throw new RegistryError('registry-tarball-too-large', `the response declares ${declared} bytes, over the ${maxBytes}-byte cap`);
410
+ }
411
+ if (response.body === null) {
412
+ const buffer = new Uint8Array(await response.arrayBuffer());
413
+ if (buffer.byteLength > maxBytes)
414
+ throw new RegistryError('registry-tarball-too-large', `the response is ${buffer.byteLength} bytes, over the ${maxBytes}-byte cap`);
415
+ return buffer;
416
+ }
417
+ const reader = response.body.getReader();
418
+ const chunks = [];
419
+ let total = 0;
420
+ for (;;) {
421
+ const { done, value } = await reader.read();
422
+ if (done)
423
+ break;
424
+ total += value.byteLength;
425
+ if (total > maxBytes) {
426
+ await reader.cancel();
427
+ throw new RegistryError('registry-tarball-too-large', `the response is over the ${maxBytes}-byte cap`);
428
+ }
429
+ chunks.push(value);
430
+ }
431
+ const joined = new Uint8Array(total);
432
+ let offset = 0;
433
+ for (const chunk of chunks) {
434
+ joined.set(chunk, offset);
435
+ offset += chunk.byteLength;
436
+ }
437
+ return joined;
438
+ }
439
+ /** Parse `1.2.3`, `v1.2.3`, `1.2.3-rc.1`, tolerating a build suffix. */
440
+ function parseVersion(text) {
441
+ const match = /^v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/.exec(text.trim());
442
+ if (match === null)
443
+ return undefined;
444
+ const prerelease = match[4] === undefined
445
+ ? []
446
+ : match[4].split('.').map(part => (/^\d+$/.test(part) ? Number(part) : part));
447
+ return {
448
+ major: Number(match[1]),
449
+ minor: Number(match[2]),
450
+ patch: Number(match[3]),
451
+ prerelease,
452
+ };
453
+ }
454
+ /** Compare two parsed versions by semver precedence. */
455
+ function compareVersions(left, right) {
456
+ for (const key of ['major', 'minor', 'patch']) {
457
+ if (left[key] !== right[key])
458
+ return left[key] < right[key] ? -1 : 1;
459
+ }
460
+ if (left.prerelease.length === 0 && right.prerelease.length === 0)
461
+ return 0;
462
+ if (left.prerelease.length === 0)
463
+ return 1;
464
+ if (right.prerelease.length === 0)
465
+ return -1;
466
+ for (let index = 0; index < Math.max(left.prerelease.length, right.prerelease.length); index += 1) {
467
+ const leftPart = left.prerelease[index];
468
+ const rightPart = right.prerelease[index];
469
+ if (leftPart === undefined)
470
+ return -1;
471
+ if (rightPart === undefined)
472
+ return 1;
473
+ if (leftPart === rightPart)
474
+ continue;
475
+ if (typeof leftPart === 'number' && typeof rightPart === 'number')
476
+ return leftPart < rightPart ? -1 : 1;
477
+ if (typeof leftPart === 'number')
478
+ return -1;
479
+ if (typeof rightPart === 'number')
480
+ return 1;
481
+ return leftPart < rightPart ? -1 : 1;
482
+ }
483
+ return 0;
484
+ }
485
+ /** Build a comparator at an exact position, or undefined when the version is not numeric. */
486
+ function comparatorAt(operator, major, minor = 0, patch = 0) {
487
+ return { operator, version: { major, minor, patch, prerelease: [] } };
488
+ }
489
+ /** Parse the numeric parts of a range token; `undefined` marks a wildcard part. */
490
+ function parsePartial(text) {
491
+ const parts = text.split('.');
492
+ if (parts.length > 3)
493
+ return undefined;
494
+ const numbers = [];
495
+ for (const part of parts) {
496
+ if (part === '' || part === 'x' || part === 'X' || part === '*') {
497
+ numbers.push(undefined);
498
+ continue;
499
+ }
500
+ if (!/^\d+$/.test(part))
501
+ return undefined;
502
+ numbers.push(Number(part));
503
+ }
504
+ return { major: numbers[0], minor: numbers[1], patch: numbers[2] };
505
+ }
506
+ /** Expand one range token into comparators, or undefined when it cannot be parsed. */
507
+ function expandToken(token) {
508
+ const match = /^(>=|<=|>|<|=|\^|~)?(.*)$/.exec(token);
509
+ if (match === null)
510
+ return undefined;
511
+ // SAFETY: the only strings the capture group can contain are the three prefix
512
+ // characters matched by the pattern, listed in the union below.
513
+ const operator = (match[1] ?? '');
514
+ const rest = match[2] ?? '';
515
+ if (rest === '' || rest === '*' || rest === 'x' || rest === 'X' || rest === 'latest') {
516
+ return operator === '' || operator === '=' ? [] : undefined;
517
+ }
518
+ const partial = parsePartial(rest);
519
+ if (partial === undefined)
520
+ return undefined;
521
+ const { major, minor, patch } = partial;
522
+ if (major === undefined)
523
+ return []; // wildcard major
524
+ if (operator === '^') {
525
+ if (minor === undefined || (minor === 0 && patch === undefined)) {
526
+ return major === 0
527
+ ? [comparatorAt('>=', 0, 0, 0), comparatorAt('<', 0, 1, 0)]
528
+ : [comparatorAt('>=', major, 0, 0), comparatorAt('<', major + 1, 0, 0)];
529
+ }
530
+ if (patch === undefined) {
531
+ return major === 0
532
+ ? [comparatorAt('>=', 0, minor, 0), comparatorAt('<', 0, minor + 1, 0)]
533
+ : [comparatorAt('>=', major, minor, 0), comparatorAt('<', major + 1, 0, 0)];
534
+ }
535
+ const lower = comparatorAt('>=', major, minor, patch);
536
+ if (major > 0)
537
+ return [lower, comparatorAt('<', major + 1, 0, 0)];
538
+ if (minor > 0)
539
+ return [lower, comparatorAt('<', 0, minor + 1, 0)];
540
+ return [lower, comparatorAt('<', 0, 0, patch + 1)];
541
+ }
542
+ if (operator === '~') {
543
+ if (minor === undefined)
544
+ return [comparatorAt('>=', major, 0, 0), comparatorAt('<', major + 1, 0, 0)];
545
+ return [comparatorAt('>=', major, minor, patch ?? 0), comparatorAt('<', major, minor + 1, 0)];
546
+ }
547
+ if (operator === '' || operator === '=') {
548
+ if (patch !== undefined)
549
+ return [comparatorAt('=', major, minor ?? 0, patch)];
550
+ if (minor !== undefined)
551
+ return [comparatorAt('>=', major, minor, 0), comparatorAt('<', major, minor + 1, 0)];
552
+ return [comparatorAt('>=', major, 0, 0), comparatorAt('<', major + 1, 0, 0)];
553
+ }
554
+ if (patch !== undefined)
555
+ return [comparatorAt(operator, major, minor ?? 0, patch)];
556
+ if (minor !== undefined) {
557
+ if (operator === '>')
558
+ return [comparatorAt('>=', major, minor + 1, 0)];
559
+ if (operator === '<=')
560
+ return [comparatorAt('<', major, minor + 1, 0)];
561
+ return [comparatorAt(operator, major, minor, 0)];
562
+ }
563
+ if (operator === '>')
564
+ return [comparatorAt('>=', major + 1, 0, 0)];
565
+ if (operator === '<=')
566
+ return [comparatorAt('<', major + 1, 0, 0)];
567
+ return [comparatorAt(operator, major, 0, 0)];
568
+ }
569
+ /** Expand one `||` alternative of a range into comparators. */
570
+ function expandRange(alternative) {
571
+ const hyphen = /^\s*(\S+)\s+-\s+(\S+)\s*$/.exec(alternative);
572
+ if (hyphen !== null) {
573
+ const lower = parsePartial(hyphen[1] ?? '');
574
+ const upper = parsePartial(hyphen[2] ?? '');
575
+ if (lower === undefined || upper === undefined || lower.major === undefined || upper.major === undefined)
576
+ return undefined;
577
+ const comparators = [
578
+ comparatorAt('>=', lower.major, lower.minor ?? 0, lower.patch ?? 0),
579
+ ];
580
+ if (upper.patch !== undefined)
581
+ comparators.push(comparatorAt('<=', upper.major, upper.minor ?? 0, upper.patch));
582
+ else if (upper.minor !== undefined)
583
+ comparators.push(comparatorAt('<', upper.major, upper.minor + 1, 0));
584
+ else
585
+ comparators.push(comparatorAt('<', upper.major + 1, 0, 0));
586
+ return comparators;
587
+ }
588
+ const comparators = [];
589
+ for (const token of alternative.split(/\s+/).filter(part => part.length > 0)) {
590
+ const expanded = expandToken(token);
591
+ if (expanded === undefined)
592
+ return undefined;
593
+ comparators.push(...expanded);
594
+ }
595
+ return comparators;
596
+ }
597
+ /**
598
+ * Whether one published version satisfies a semver range.
599
+ *
600
+ * Supported: exact versions, `^`, `~`, x-ranges (`1.x`, `1.2`), comparison
601
+ * operators (`>=`, `>`, `<=`, `<`, `=`), hyphen ranges, `||`, and `*`. NOT
602
+ * supported (refused rather than guessed): prerelease-only ranges and npm's
603
+ * `tag:` protocol. A prerelease version only matches a range that itself names a
604
+ * prerelease.
605
+ * @param versionText - the published version.
606
+ * @param rangeText - the requested range.
607
+ * @returns whether the version satisfies the range.
608
+ */
609
+ export function satisfiesRegistryRange(versionText, rangeText) {
610
+ const version = parseVersion(versionText);
611
+ if (version === undefined)
612
+ return false;
613
+ if (version.prerelease.length > 0 && !/\d+\.\d+\.\d+-/.test(rangeText))
614
+ return false;
615
+ const alternatives = rangeText.split('||').map(part => part.trim()).filter(part => part.length > 0);
616
+ const effective = alternatives.length === 0 ? ['*'] : alternatives;
617
+ return effective.some(alternative => {
618
+ if (alternative === '*' || alternative === 'x' || alternative === 'X' || alternative === 'latest')
619
+ return true;
620
+ const comparators = expandRange(alternative);
621
+ if (comparators === undefined)
622
+ return false;
623
+ return comparators.every(comparator => {
624
+ const comparison = compareVersions(version, comparator.version);
625
+ switch (comparator.operator) {
626
+ case '=': return comparison === 0;
627
+ case '>': return comparison > 0;
628
+ case '>=': return comparison >= 0;
629
+ case '<': return comparison < 0;
630
+ case '<=': return comparison <= 0;
631
+ default: return false;
632
+ }
633
+ });
634
+ });
635
+ }
636
+ /**
637
+ * The highest published version satisfying a range.
638
+ * @param versions - the published version strings.
639
+ * @param rangeText - the requested range.
640
+ * @returns the highest satisfying version exactly as published, or undefined.
641
+ */
642
+ export function highestSatisfyingVersion(versions, rangeText) {
643
+ let best;
644
+ for (const text of versions) {
645
+ const parsed = parseVersion(text);
646
+ if (parsed === undefined)
647
+ continue;
648
+ if (!satisfiesRegistryRange(text, rangeText))
649
+ continue;
650
+ if (best === undefined || compareVersions(parsed, best.parsed) > 0)
651
+ best = { text, parsed };
652
+ }
653
+ return best?.text;
654
+ }
655
+ /** Read one version's `dist` or throw the named refusal. */
656
+ function readDist(name, version, raw) {
657
+ if (typeof raw !== 'object' || raw === null) {
658
+ throw new RegistryError('registry-invalid-metadata', `${name}@${version} has no dist object in the registry metadata`);
659
+ }
660
+ // SAFETY: JSON.parse output was checked to be a non-null object; every field is
661
+ // validated below, and a non-string/missing value is a named refusal.
662
+ const dist = raw;
663
+ const tarball = dist.tarball;
664
+ if (typeof tarball !== 'string' || !/^https?:\/\//i.test(tarball)) {
665
+ throw new RegistryError('registry-invalid-metadata', `${name}@${version} has no http(s) dist.tarball in the registry metadata`);
666
+ }
667
+ const integrity = dist.integrity;
668
+ if (typeof integrity !== 'string' || !/^sha512-/i.test(integrity)) {
669
+ throw new RegistryError('registry-integrity-missing', `${name}@${version} declares no sha512 dist.integrity; unverified bytes are never installed`, { cause: undefined });
670
+ }
671
+ return { tarball, integrity };
672
+ }
673
+ /** Validate the packument shape this client can trust. */
674
+ function readPackument(name, raw) {
675
+ if (typeof raw !== 'object' || raw === null || Array.isArray(raw)) {
676
+ throw new RegistryError('registry-invalid-metadata', `the registry metadata for ${name} is not a JSON object`);
677
+ }
678
+ // SAFETY: JSON.parse output was checked to be a non-null non-array object; the
679
+ // two fields read from it are validated before use.
680
+ const document = raw;
681
+ const versions = document.versions;
682
+ if (typeof versions !== 'object' || versions === null || Array.isArray(versions)) {
683
+ throw new RegistryError('registry-invalid-metadata', `the registry metadata for ${name} declares no versions object`);
684
+ }
685
+ const distTags = {};
686
+ const rawTags = document['dist-tags'];
687
+ if (typeof rawTags === 'object' && rawTags !== null) {
688
+ // SAFETY: the dist-tags container was checked to be a non-null object; only
689
+ // string tag targets are kept, so an unexpected shape contributes nothing.
690
+ for (const [tag, value] of Object.entries(rawTags)) {
691
+ if (typeof value === 'string')
692
+ distTags[tag] = value;
693
+ }
694
+ }
695
+ // SAFETY: the versions container was checked to be a non-null non-array object;
696
+ // each entry is parsed defensively by readDist before it is trusted.
697
+ return { distTags, versions: versions };
698
+ }
699
+ /**
700
+ * Resolve which published version one spec names.
701
+ * @param name - the package name.
702
+ * @param spec - dist-tag, exact version, or range; empty means `latest`.
703
+ * @param packument - the validated metadata.
704
+ * @returns the exact version string.
705
+ */
706
+ function selectPublishedVersion(name, spec, packument) {
707
+ const requested = spec.trim();
708
+ if (requested.length === 0 || requested === 'latest') {
709
+ const latest = packument.distTags['latest'];
710
+ if (latest === undefined) {
711
+ throw new RegistryError('registry-not-found', `${name} has no latest dist-tag in the registry metadata`);
712
+ }
713
+ return { version: latest, distTag: 'latest' };
714
+ }
715
+ const tagged = packument.distTags[requested];
716
+ if (tagged !== undefined)
717
+ return { version: tagged, distTag: requested };
718
+ if (Object.hasOwn(packument.versions, requested))
719
+ return { version: requested };
720
+ const best = highestSatisfyingVersion(Object.keys(packument.versions), requested);
721
+ if (best === undefined) {
722
+ throw new RegistryError('registry-no-matching-version', `${name} has no published version satisfying ${JSON.stringify(requested)}`);
723
+ }
724
+ return { version: best };
725
+ }
726
+ /**
727
+ * Create a registry session over one npm registry.
728
+ *
729
+ * The same session serves the extension package and every runtime dependency, so
730
+ * the tarball map (key → URL) the resolver registers is the map the archive
731
+ * fetches through. Nothing is downloaded until a caller fetches; nothing is
732
+ * verified here (the SDK verifies the sha512 before extraction, and this module
733
+ * refuses metadata without one).
734
+ * @param options - registry URL, archive factory, bounds and fetch seam.
735
+ * @returns the session.
736
+ */
737
+ export function createRegistrySession(options) {
738
+ const registryUrl = normalizeRegistryUrl(options.registryUrl);
739
+ const maxTarballBytes = options.maxTarballBytes ?? MAX_DEPENDENCY_ARCHIVE_BYTES;
740
+ const timeoutMs = options.timeoutMs ?? 30_000;
741
+ const fetchImpl = options.fetchImpl ?? fetch;
742
+ /** tarball URL per `name@version` key, registered by resolve(). */
743
+ const tarballs = new Map();
744
+ const timed = (signal) => signal === undefined
745
+ ? AbortSignal.timeout(timeoutMs)
746
+ : AbortSignal.any([signal, AbortSignal.timeout(timeoutMs)]);
747
+ const requireRegistry = () => {
748
+ if (registryUrl === '') {
749
+ throw new RegistryError('registry-not-configured', 'no npm registry is configured (config.extensionRegistry)');
750
+ }
751
+ return registryUrl;
752
+ };
753
+ const session = {
754
+ registryUrl,
755
+ archive: options.archiveFactory(source => session.download(source)),
756
+ async resolve(name, spec, signal) {
757
+ assertUsableName(name);
758
+ const base = requireRegistry();
759
+ const url = `${base}/${encodePackageName(name)}`;
760
+ let response;
761
+ try {
762
+ response = await fetchImpl(url, {
763
+ headers: { accept: 'application/vnd.npm.install-v1+json, application/json' },
764
+ signal: timed(signal),
765
+ });
766
+ }
767
+ catch (error) {
768
+ throw new RegistryError('registry-unavailable', `the npm registry ${base} could not be reached: ${messageOf(error)}`, { cause: error, transport: true });
769
+ }
770
+ if (!response.ok) {
771
+ await response.body?.cancel();
772
+ if (response.status === 404) {
773
+ throw new RegistryError('registry-not-found', `${name} was not found on the npm registry ${base} (HTTP 404)`);
774
+ }
775
+ if (response.status >= 500) {
776
+ throw new RegistryError('registry-unavailable', `the npm registry ${base} answered HTTP ${response.status} for ${name}`, { transport: true });
777
+ }
778
+ throw new RegistryError('registry-invalid-metadata', `the npm registry ${base} refused ${name} with HTTP ${response.status}`);
779
+ }
780
+ const metadata = await readBoundedBody(response, MAX_REGISTRY_METADATA_BYTES);
781
+ let parsed;
782
+ try {
783
+ parsed = JSON.parse(new TextDecoder().decode(metadata));
784
+ }
785
+ catch (error) {
786
+ throw new RegistryError('registry-invalid-metadata', `the npm registry returned unparseable metadata for ${name}: ${messageOf(error)}`, { cause: error });
787
+ }
788
+ const packument = readPackument(name, parsed);
789
+ const selected = selectPublishedVersion(name, spec, packument);
790
+ const versionEntry = packument.versions[selected.version];
791
+ if (typeof versionEntry !== 'object' || versionEntry === null) {
792
+ throw new RegistryError('registry-invalid-metadata', `${name}@${selected.version} has no version object in the registry metadata`);
793
+ }
794
+ // SAFETY: the version object was checked above; `dist` is read defensively by
795
+ // readDist, which refuses a missing/non-http tarball and a missing sha512.
796
+ const dist = readDist(name, selected.version, versionEntry.dist);
797
+ const key = `${name}@${selected.version}`;
798
+ tarballs.set(key, dist.tarball);
799
+ const published = { name, version: selected.version, tarball: dist.tarball, integrity: dist.integrity };
800
+ return selected.distTag === undefined ? published : { ...published, distTag: selected.distTag };
801
+ },
802
+ async download(source, signal) {
803
+ const url = tarballs.get(source.requested);
804
+ if (url === undefined) {
805
+ // A tarball for a package that was never resolved: the graph contract says
806
+ // resolve-then-fetch, so this is a programming error, not a package refusal.
807
+ throw new RegistryError('registry-invalid-metadata', `the registry tarball ${source.requested} was not resolved by this session`);
808
+ }
809
+ let response;
810
+ try {
811
+ response = await fetchImpl(url, { signal: timed(signal) });
812
+ }
813
+ catch (error) {
814
+ throw new RegistryError('registry-tarball-fetch-failed', `the tarball for ${source.requested} could not be downloaded: ${messageOf(error)}`, { cause: error });
815
+ }
816
+ if (!response.ok) {
817
+ await response.body?.cancel();
818
+ throw new RegistryError('registry-tarball-fetch-failed', `the tarball for ${source.requested} answered HTTP ${response.status}`);
819
+ }
820
+ try {
821
+ return await readBoundedBody(response, maxTarballBytes);
822
+ }
823
+ catch (error) {
824
+ if (error instanceof RegistryError)
825
+ throw error;
826
+ throw new RegistryError('registry-tarball-fetch-failed', `the tarball for ${source.requested} could not be read: ${messageOf(error)}`, { cause: error });
827
+ }
828
+ },
829
+ };
830
+ return session;
831
+ }
832
+ /**
833
+ * Wire a registry session into the SDK dependency-graph contract.
834
+ *
835
+ * Every declared dependency is resolved to one exact published version with its
836
+ * sha512 integrity; the SAME session archive fetches the bytes, so the manifest
837
+ * the graph verifies is the manifest that was resolved. EXT-10's script refusal is
838
+ * the SDK's own, unchanged.
839
+ * @param session - the registry session.
840
+ * @returns the install-dependency graph.
841
+ */
842
+ export function createRegistryDependencyGraph(session) {
843
+ return {
844
+ resolver: {
845
+ async resolve(name, range) {
846
+ const resolved = await session.resolve(name, range);
847
+ return {
848
+ name: resolved.name,
849
+ version: resolved.version,
850
+ tarball: { kind: 'registry', requested: `${resolved.name}@${resolved.version}` },
851
+ integrity: resolved.integrity,
852
+ };
853
+ },
854
+ },
855
+ archive: session.archive,
856
+ };
857
+ }
858
+ //# sourceMappingURL=extension-dependencies.js.map