@try-works/dsh-model-as-tool 0.1.3 → 0.3.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/dsh-tool-mode-credentials.mjs +20 -0
- package/bin/dsh-tool-mode.mjs +202 -0
- package/catalog/desert-ant-models.json +693 -693
- package/client/client.model-tools.js +592 -0
- package/client/index.js +1679 -0
- package/lib/adapters/index.d.ts.map +1 -1
- package/lib/adapters/index.js +4 -4
- package/lib/bindings.d.ts.map +1 -1
- package/lib/bindings.js +26 -25
- package/lib/capability.d.ts.map +1 -1
- package/lib/capability.js +17 -0
- package/lib/capture-path.d.ts +97 -0
- package/lib/capture-path.d.ts.map +1 -0
- package/lib/capture-path.js +152 -0
- package/lib/diagnostics.d.ts +166 -0
- package/lib/diagnostics.d.ts.map +1 -0
- package/lib/diagnostics.js +135 -0
- package/lib/extension-dependencies.d.ts +197 -0
- package/lib/extension-dependencies.d.ts.map +1 -0
- package/lib/extension-dependencies.js +369 -0
- package/lib/extension-dispatch.d.ts +117 -0
- package/lib/extension-dispatch.d.ts.map +1 -0
- package/lib/extension-dispatch.js +124 -0
- package/lib/extension-registrar.d.ts +100 -0
- package/lib/extension-registrar.d.ts.map +1 -0
- package/lib/extension-registrar.js +134 -0
- package/lib/extensions-api.d.ts +217 -0
- package/lib/extensions-api.d.ts.map +1 -0
- package/lib/extensions-api.js +17 -0
- package/lib/extensions-remote.d.ts +54 -0
- package/lib/extensions-remote.d.ts.map +1 -0
- package/lib/extensions-remote.js +66 -0
- package/lib/extensions-service.d.ts +334 -0
- package/lib/extensions-service.d.ts.map +1 -0
- package/lib/extensions-service.js +1351 -0
- package/lib/harness-llm.d.ts +138 -0
- package/lib/harness-llm.d.ts.map +1 -0
- package/lib/harness-llm.js +218 -0
- package/lib/harness-serving.d.ts +95 -0
- package/lib/harness-serving.d.ts.map +1 -0
- package/lib/harness-serving.js +157 -0
- package/lib/headless.d.ts +97 -0
- package/lib/headless.d.ts.map +1 -0
- package/lib/headless.js +349 -0
- package/lib/http-acquisition.d.ts +27 -0
- package/lib/http-acquisition.d.ts.map +1 -0
- package/lib/http-acquisition.js +136 -0
- package/lib/index.d.ts +250 -7
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +2625 -70
- package/lib/invocation-accounting.d.ts +169 -0
- package/lib/invocation-accounting.d.ts.map +1 -0
- package/lib/invocation-accounting.js +148 -0
- package/lib/legacy-migration.d.ts +95 -0
- package/lib/legacy-migration.d.ts.map +1 -0
- package/lib/legacy-migration.js +429 -0
- package/lib/managed-workers.d.ts +151 -0
- package/lib/managed-workers.d.ts.map +1 -0
- package/lib/managed-workers.js +249 -0
- package/lib/mcp/client-grant.d.ts +74 -0
- package/lib/mcp/client-grant.d.ts.map +1 -0
- package/lib/mcp/client-grant.js +60 -0
- package/lib/mcp/credential-cli.d.ts +38 -0
- package/lib/mcp/credential-cli.d.ts.map +1 -0
- package/lib/mcp/credential-cli.js +148 -0
- package/lib/mcp/credential-store.d.ts +143 -0
- package/lib/mcp/credential-store.d.ts.map +1 -0
- package/lib/mcp/credential-store.js +454 -0
- package/lib/mcp/host-binding.d.ts +25 -0
- package/lib/mcp/host-binding.d.ts.map +1 -1
- package/lib/mcp/host-binding.js +107 -14
- package/lib/mcp/http.d.ts +68 -18
- package/lib/mcp/http.d.ts.map +1 -1
- package/lib/mcp/http.js +390 -62
- package/lib/mcp/oauth.d.ts +149 -0
- package/lib/mcp/oauth.d.ts.map +1 -0
- package/lib/mcp/oauth.js +396 -0
- package/lib/mcp/protocol.d.ts +46 -7
- package/lib/mcp/protocol.d.ts.map +1 -1
- package/lib/mcp/protocol.js +63 -16
- package/lib/mcp/rate-limit.d.ts +68 -0
- package/lib/mcp/rate-limit.d.ts.map +1 -0
- package/lib/mcp/rate-limit.js +114 -0
- package/lib/mcp/sdk-v2.d.ts +17 -0
- package/lib/mcp/sdk-v2.d.ts.map +1 -0
- package/lib/mcp/sdk-v2.js +181 -0
- package/lib/mcp/server.d.ts +11 -1
- package/lib/mcp/server.d.ts.map +1 -1
- package/lib/mcp/server.js +80 -13
- package/lib/mcp/stdio.d.ts.map +1 -1
- package/lib/mcp/stdio.js +9 -1
- package/lib/mcp/subscriptions.d.ts +43 -1
- package/lib/mcp/subscriptions.d.ts.map +1 -1
- package/lib/mcp/subscriptions.js +90 -9
- package/lib/model-qualification.d.ts +192 -0
- package/lib/model-qualification.d.ts.map +1 -0
- package/lib/model-qualification.js +178 -0
- package/lib/planning.d.ts +246 -0
- package/lib/planning.d.ts.map +1 -0
- package/lib/planning.js +457 -0
- package/lib/prepared-assets.d.ts +178 -0
- package/lib/prepared-assets.d.ts.map +1 -0
- package/lib/prepared-assets.js +286 -0
- package/lib/provider-bridge.d.ts +37 -0
- package/lib/provider-bridge.d.ts.map +1 -1
- package/lib/provider-bridge.js +189 -12
- package/lib/ptc-authority.d.ts +140 -0
- package/lib/ptc-authority.d.ts.map +1 -0
- package/lib/ptc-authority.js +216 -0
- package/lib/ptc-runtime.d.ts +156 -0
- package/lib/ptc-runtime.d.ts.map +1 -0
- package/lib/ptc-runtime.js +191 -0
- package/lib/publication.d.ts +2 -1
- package/lib/publication.d.ts.map +1 -1
- package/lib/publication.js +13 -5
- package/lib/receipt.d.ts.map +1 -1
- package/lib/receipt.js +8 -0
- package/lib/title-proposal.d.ts +194 -0
- package/lib/title-proposal.d.ts.map +1 -0
- package/lib/title-proposal.js +244 -0
- package/lib/tool-mode-run.d.ts +235 -0
- package/lib/tool-mode-run.d.ts.map +1 -0
- package/lib/tool-mode-run.js +283 -0
- package/lib/types.d.ts +3 -1
- package/lib/types.d.ts.map +1 -1
- package/lib/update-surface.d.ts +79 -0
- package/lib/update-surface.d.ts.map +1 -0
- package/lib/update-surface.js +224 -0
- package/node_modules/@try-works/tool-mode-contracts/lib/contracts/standard.d.ts +38 -0
- package/node_modules/@try-works/tool-mode-contracts/lib/contracts/standard.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-contracts/lib/contracts/standard.js +445 -0
- package/node_modules/@try-works/tool-mode-contracts/lib/contracts/text-embed.d.ts +14 -0
- package/node_modules/@try-works/tool-mode-contracts/lib/contracts/text-embed.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-contracts/lib/contracts/text-embed.js +63 -0
- package/node_modules/@try-works/tool-mode-contracts/lib/contracts/text-rerank.d.ts +14 -0
- package/node_modules/@try-works/tool-mode-contracts/lib/contracts/text-rerank.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-contracts/lib/contracts/text-rerank.js +76 -0
- package/node_modules/@try-works/tool-mode-contracts/lib/dsh.d.ts.map +1 -1
- package/node_modules/@try-works/tool-mode-contracts/lib/dsh.js +69 -55
- package/node_modules/@try-works/tool-mode-contracts/lib/index.d.ts +4 -1
- package/node_modules/@try-works/tool-mode-contracts/lib/index.d.ts.map +1 -1
- package/node_modules/@try-works/tool-mode-contracts/lib/index.js +26 -2
- package/node_modules/@try-works/tool-mode-contracts/lib/registry.d.ts +28 -2
- package/node_modules/@try-works/tool-mode-contracts/lib/registry.d.ts.map +1 -1
- package/node_modules/@try-works/tool-mode-contracts/lib/registry.js +93 -10
- package/node_modules/@try-works/tool-mode-contracts/package.json +0 -1
- package/node_modules/@try-works/tool-mode-extension-sdk/bin/dsh-tool-mode.mjs +20 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/cli.d.ts +80 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/cli.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/cli.js +533 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/declarative-recipes.d.ts +83 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/declarative-recipes.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/declarative-recipes.js +125 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/discovery.d.ts +70 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/discovery.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/discovery.js +201 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/errors.d.ts +57 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/errors.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/errors.js +83 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/index.d.ts +31 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/index.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/index.js +31 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/archive.d.ts +86 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/archive.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/archive.js +185 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/dependencies.d.ts +126 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/dependencies.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/dependencies.js +297 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/lifecycle.d.ts +216 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/lifecycle.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/lifecycle.js +800 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/migration.d.ts +210 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/migration.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/migration.js +368 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/runtime-link.d.ts +63 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/runtime-link.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/runtime-link.js +149 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/source-directory.d.ts +35 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/source-directory.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/source-directory.js +179 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/staged.d.ts +179 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/staged.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/staged.js +235 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/store.d.ts +367 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/store.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/store.js +807 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/update-flow.d.ts +185 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/update-flow.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/update-flow.js +504 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/zip-source.d.ts +17 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/zip-source.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/zip-source.js +80 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/load.d.ts +52 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/load.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/load.js +102 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/manifest.d.ts +171 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/manifest.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/manifest.js +271 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/runtime.d.ts +242 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/runtime.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/runtime.js +58 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/state.d.ts +118 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/state.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/state.js +113 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/worker/worker-child.d.ts +2 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/worker/worker-child.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/worker/worker-child.js +351 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/worker.d.ts +150 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/worker.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/lib/worker.js +670 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/package.json +53 -0
- package/node_modules/@try-works/tool-mode-extension-sdk/schema/extension-manifest-1.1.0.schema.json +253 -0
- package/node_modules/@try-works/tool-mode-recipes/bin/tool-mode-recipe.mjs +15 -0
- package/node_modules/@try-works/tool-mode-recipes/lib/broker.d.ts +427 -0
- package/node_modules/@try-works/tool-mode-recipes/lib/broker.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-recipes/lib/broker.js +592 -0
- package/node_modules/@try-works/tool-mode-recipes/lib/cli.d.ts +60 -0
- package/node_modules/@try-works/tool-mode-recipes/lib/cli.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-recipes/lib/cli.js +936 -0
- package/node_modules/@try-works/tool-mode-recipes/lib/dsh-adapter.d.ts +237 -0
- package/node_modules/@try-works/tool-mode-recipes/lib/dsh-adapter.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-recipes/lib/dsh-adapter.js +200 -0
- package/node_modules/@try-works/tool-mode-recipes/lib/fixture-provider.d.ts +85 -0
- package/node_modules/@try-works/tool-mode-recipes/lib/fixture-provider.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-recipes/lib/fixture-provider.js +176 -0
- package/node_modules/@try-works/tool-mode-recipes/lib/images.d.ts +105 -0
- package/node_modules/@try-works/tool-mode-recipes/lib/images.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-recipes/lib/images.js +129 -0
- package/node_modules/@try-works/tool-mode-recipes/lib/index.d.ts +25 -0
- package/node_modules/@try-works/tool-mode-recipes/lib/index.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-recipes/lib/index.js +25 -0
- package/node_modules/@try-works/tool-mode-recipes/lib/recipe-format.d.ts +325 -0
- package/node_modules/@try-works/tool-mode-recipes/lib/recipe-format.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-recipes/lib/recipe-format.js +1310 -0
- package/node_modules/@try-works/tool-mode-recipes/lib/templates.d.ts +51 -0
- package/node_modules/@try-works/tool-mode-recipes/lib/templates.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-recipes/lib/templates.js +310 -0
- package/node_modules/@try-works/tool-mode-recipes/lib/titles.d.ts +227 -0
- package/node_modules/@try-works/tool-mode-recipes/lib/titles.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-recipes/lib/titles.js +407 -0
- package/node_modules/@try-works/tool-mode-recipes/package.json +56 -0
- package/node_modules/@try-works/tool-mode-recipes/recipes/describe-image.json +38 -0
- package/node_modules/@try-works/tool-mode-recipes/recipes/text-title.json +45 -0
- package/node_modules/@try-works/tool-mode-recipes/schema/harness-model-recipe.schema.json +273 -0
- package/package.json +43 -15
- package/remote/spec.mjs +439 -0
- package/remote/typert.host.mjs +14 -0
- package/remote/typert.remote-client.mjs +16 -0
- package/lib/adapters/tongue.d.ts +0 -31
- package/lib/adapters/tongue.d.ts.map +0 -1
- package/lib/adapters/tongue.js +0 -93
- package/lib/adapters/voz.d.ts +0 -28
- package/lib/adapters/voz.d.ts.map +0 -1
- package/lib/adapters/voz.js +0 -102
- /package/{lib → node_modules/@try-works/tool-mode-extension-sdk/lib}/licensing.d.ts +0 -0
- /package/{lib → node_modules/@try-works/tool-mode-extension-sdk/lib}/licensing.d.ts.map +0 -0
- /package/{lib → node_modules/@try-works/tool-mode-extension-sdk/lib}/licensing.js +0 -0
|
@@ -0,0 +1,369 @@
|
|
|
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
|
+
/**
|
|
51
|
+
* The dependency archive byte cap.
|
|
52
|
+
*
|
|
53
|
+
* The SDK's `installDependencyGraph` calls `fetch` with a 256 MiB bound and
|
|
54
|
+
* requires the source to enforce it, so this module refuses an archive it could
|
|
55
|
+
* not truthfully hand over under that contract.
|
|
56
|
+
*/
|
|
57
|
+
export const MAX_DEPENDENCY_ARCHIVE_BYTES = 256 * 1024 * 1024;
|
|
58
|
+
/**
|
|
59
|
+
* Refuse an identity that cannot safely become a path component.
|
|
60
|
+
* @param name - the declared dependency name.
|
|
61
|
+
* @throws ManagementError `DEPENDENCY_UNSATISFIED` for an unusable name.
|
|
62
|
+
*/
|
|
63
|
+
function assertUsableName(name) {
|
|
64
|
+
if (!PACKAGE_ID.test(name)) {
|
|
65
|
+
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.');
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/** Read one installed package's identity, or undefined when it is not a package. */
|
|
69
|
+
function readInstalledPackage(directory, expectedName) {
|
|
70
|
+
let parsed;
|
|
71
|
+
try {
|
|
72
|
+
parsed = JSON.parse(readFileSync(join(directory, 'package.json'), 'utf8'));
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
return undefined;
|
|
76
|
+
}
|
|
77
|
+
if (typeof parsed !== 'object' || parsed === null)
|
|
78
|
+
return undefined;
|
|
79
|
+
// SAFETY: the value came from JSON.parse and the object guard above; every field
|
|
80
|
+
// is re-checked (name equality, string version) before it reaches the caller, so
|
|
81
|
+
// an unexpected shape is treated as "not this package" rather than trusted.
|
|
82
|
+
const recorded = parsed;
|
|
83
|
+
const name = recorded.name;
|
|
84
|
+
const version = recorded.version;
|
|
85
|
+
if (name !== expectedName)
|
|
86
|
+
return undefined;
|
|
87
|
+
if (typeof version !== 'string' || version.length === 0)
|
|
88
|
+
return undefined;
|
|
89
|
+
return { name: expectedName, version, directory };
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Look for a package in the `node_modules` directory of a starting point and every
|
|
93
|
+
* ancestor, exactly the order Node's own resolver applies.
|
|
94
|
+
* @param start - a directory inside the requesting tree.
|
|
95
|
+
* @param name - the dependency name.
|
|
96
|
+
* @returns the installed package, or undefined when no ancestor holds it.
|
|
97
|
+
*/
|
|
98
|
+
function walkNodeModules(start, name) {
|
|
99
|
+
let current = resolve(start);
|
|
100
|
+
for (let depth = 0; depth < 64; depth += 1) {
|
|
101
|
+
const candidate = join(current, 'node_modules', ...name.split('/'));
|
|
102
|
+
if (existsSync(join(candidate, 'package.json'))) {
|
|
103
|
+
const found = readInstalledPackage(candidate, name);
|
|
104
|
+
if (found !== undefined)
|
|
105
|
+
return found;
|
|
106
|
+
}
|
|
107
|
+
const parent = dirname(current);
|
|
108
|
+
if (parent === current)
|
|
109
|
+
return undefined;
|
|
110
|
+
current = parent;
|
|
111
|
+
}
|
|
112
|
+
return undefined;
|
|
113
|
+
}
|
|
114
|
+
/** The host's own module directory: the fallback tree for host-installed dependencies. */
|
|
115
|
+
function hostModuleDirectory() {
|
|
116
|
+
return dirname(fileURLToPath(import.meta.url));
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Resolve one declared dependency from the host's installed tree.
|
|
120
|
+
*
|
|
121
|
+
* The source checkout's own ancestor chain is searched first (the tree whose
|
|
122
|
+
* `package.json` declared the dependency), then the host's own module chain, then
|
|
123
|
+
* any explicitly configured roots. The first installed package whose own
|
|
124
|
+
* `package.json` name matches wins; nothing is downloaded and no version is
|
|
125
|
+
* inferred.
|
|
126
|
+
* @param search - where to look.
|
|
127
|
+
* @param name - the declared dependency name.
|
|
128
|
+
* @param range - the declared spec, used only in the refusal message.
|
|
129
|
+
* @returns the installed dependency.
|
|
130
|
+
* @throws ManagementError `DEPENDENCY_UNSATISFIED` when it is not installed.
|
|
131
|
+
*/
|
|
132
|
+
export function resolveFirstPartyDependency(search, name, range) {
|
|
133
|
+
assertUsableName(name);
|
|
134
|
+
const origins = [resolve(search.sourcePackageDir), hostModuleDirectory()];
|
|
135
|
+
for (const origin of origins) {
|
|
136
|
+
const found = walkNodeModules(origin, name);
|
|
137
|
+
if (found !== undefined)
|
|
138
|
+
return found;
|
|
139
|
+
}
|
|
140
|
+
for (const root of search.extraSearchRoots ?? []) {
|
|
141
|
+
const candidate = join(resolve(root), 'node_modules', ...name.split('/'));
|
|
142
|
+
if (existsSync(join(candidate, 'package.json'))) {
|
|
143
|
+
const found = readInstalledPackage(candidate, name);
|
|
144
|
+
if (found !== undefined)
|
|
145
|
+
return found;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
throw new ManagementError('DEPENDENCY_UNSATISFIED', 'install', `${name} (declared as ${JSON.stringify(range)}) is not installed anywhere in the host's own dependency tree, `
|
|
149
|
+
+ '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 '
|
|
150
|
+
+ 'dependencies for a source-directory install and never silently drops a declared one.');
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Collect a package directory's files as archive entries.
|
|
154
|
+
*
|
|
155
|
+
* `node_modules` is skipped (the SDK resolves the transitive graph through this
|
|
156
|
+
* same resolver), and any symbolic link or junction inside the tree is REFUSED
|
|
157
|
+
* rather than followed: a link is how a package reaches bytes outside the content
|
|
158
|
+
* that was resolved.
|
|
159
|
+
* @param root - the package directory.
|
|
160
|
+
* @param limits - the byte bounds.
|
|
161
|
+
* @returns the selected files, in sorted walk order.
|
|
162
|
+
*/
|
|
163
|
+
function collectPackageFiles(root, limits) {
|
|
164
|
+
const files = [];
|
|
165
|
+
let totalBytes = 0;
|
|
166
|
+
const walk = (current) => {
|
|
167
|
+
for (const entry of readdirSync(current, { withFileTypes: true }).sort((left, right) => left.name.localeCompare(right.name))) {
|
|
168
|
+
const absolute = join(current, entry.name);
|
|
169
|
+
if (entry.isSymbolicLink()) {
|
|
170
|
+
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.');
|
|
171
|
+
}
|
|
172
|
+
if (entry.isDirectory()) {
|
|
173
|
+
if (entry.name === 'node_modules')
|
|
174
|
+
continue;
|
|
175
|
+
walk(absolute);
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
if (!entry.isFile()) {
|
|
179
|
+
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.');
|
|
180
|
+
}
|
|
181
|
+
const size = lstatSync(absolute).size;
|
|
182
|
+
if (size > limits.maxEntryBytes) {
|
|
183
|
+
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.');
|
|
184
|
+
}
|
|
185
|
+
totalBytes += size;
|
|
186
|
+
if (totalBytes > limits.maxSourceBytes) {
|
|
187
|
+
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.');
|
|
188
|
+
}
|
|
189
|
+
files.push({ relative: relativePath(root, absolute), absolute, size });
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
walk(root);
|
|
193
|
+
if (files.length === 0) {
|
|
194
|
+
throw new ManagementError('DEPENDENCY_UNSATISFIED', 'install', `the installed dependency at ${root} contains no files`, 'Repair the installed dependency, then retry.');
|
|
195
|
+
}
|
|
196
|
+
return files;
|
|
197
|
+
}
|
|
198
|
+
/** A path relative to the package root, using `/` separators for the archive. */
|
|
199
|
+
function relativePath(root, absolute) {
|
|
200
|
+
return relative(resolve(root), absolute).split('\\').join('/');
|
|
201
|
+
}
|
|
202
|
+
/** Whether a directory is the root or lives underneath it. */
|
|
203
|
+
function isUnder(root, candidate) {
|
|
204
|
+
const rootPath = resolve(root);
|
|
205
|
+
const candidatePath = resolve(candidate);
|
|
206
|
+
if (candidatePath === rootPath)
|
|
207
|
+
return true;
|
|
208
|
+
const relativePathFromRoot = relative(rootPath, candidatePath);
|
|
209
|
+
return relativePathFromRoot.length > 0 && !relativePathFromRoot.startsWith('..') && !isAbsolute(relativePathFromRoot);
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Build a deterministic zip of one installed dependency directory.
|
|
213
|
+
*
|
|
214
|
+
* The result is the archive the SDK's dependency graph fetches and verifies. Its
|
|
215
|
+
* SHA-512 is the integrity handed to the graph; the directory's own
|
|
216
|
+
* `digestInstalledContent` is checked before and after the build so a concurrent
|
|
217
|
+
* change refuses.
|
|
218
|
+
* @param dependency - the resolved installed dependency.
|
|
219
|
+
* @param limits - the byte bounds.
|
|
220
|
+
* @returns the archive bytes, their integrity, and the directory digest.
|
|
221
|
+
* @throws ManagementError `DEPENDENCY_UNSATISFIED` for any refusal.
|
|
222
|
+
*/
|
|
223
|
+
export function buildDependencyArchive(dependency, limits) {
|
|
224
|
+
const files = collectPackageFiles(dependency.directory, limits);
|
|
225
|
+
// The same content-digest discipline the SDK applies to a directory install:
|
|
226
|
+
// the tree's identity is checked around the build, so the archive cannot be
|
|
227
|
+
// assembled from a tree that changed while it was being read.
|
|
228
|
+
let before;
|
|
229
|
+
try {
|
|
230
|
+
before = digestInstalledContent(dependency.directory);
|
|
231
|
+
}
|
|
232
|
+
catch (error) {
|
|
233
|
+
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.');
|
|
234
|
+
}
|
|
235
|
+
const zip = new AdmZip();
|
|
236
|
+
for (const file of files)
|
|
237
|
+
zip.addFile(file.relative, readFileSync(file.absolute));
|
|
238
|
+
const bytes = new Uint8Array(zip.toBuffer());
|
|
239
|
+
if (bytes.byteLength > limits.maxArchiveBytes) {
|
|
240
|
+
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.');
|
|
241
|
+
}
|
|
242
|
+
const after = digestInstalledContent(dependency.directory);
|
|
243
|
+
if (before !== after) {
|
|
244
|
+
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.');
|
|
245
|
+
}
|
|
246
|
+
return {
|
|
247
|
+
bytes,
|
|
248
|
+
integrity: createHash('sha512').update(bytes).digest('hex'),
|
|
249
|
+
contentDigest: after,
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Decide how this host supplies one dependency, applying the operator gate.
|
|
254
|
+
*
|
|
255
|
+
* With the preinstalled gate on, the resolved directory is returned as-is (the SDK
|
|
256
|
+
* copies it and records provenance; scripts are not run and are NOT stripped).
|
|
257
|
+
* With the gate off, the bytes arm applies: the SDK's EXT-10 refusal is consulted
|
|
258
|
+
* BEFORE any archive is built, so a script-declaring dependency refuses cheaply
|
|
259
|
+
* with the SDK's exact named error, then the archive is built and its SHA-512 is
|
|
260
|
+
* returned as the integrity the SDK will verify.
|
|
261
|
+
* @param options - the search tree and gate.
|
|
262
|
+
* @param limits - the archive bounds used by the bytes arm.
|
|
263
|
+
* @param name - the declared dependency name.
|
|
264
|
+
* @param range - the declared dependency spec, for refusals.
|
|
265
|
+
* @returns the supply decision.
|
|
266
|
+
* @throws ManagementError `DEPENDENCY_UNSATISFIED` for every refusal.
|
|
267
|
+
*/
|
|
268
|
+
function resolveDependency(options, limits, name, range) {
|
|
269
|
+
const dependency = resolveFirstPartyDependency(options, name, range);
|
|
270
|
+
if (options.allowPreinstalled === true) {
|
|
271
|
+
const root = options.preinstalledRoot;
|
|
272
|
+
if (root === undefined || !isUnder(root, dependency.directory)) {
|
|
273
|
+
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.');
|
|
274
|
+
}
|
|
275
|
+
return { kind: 'preinstalled', dependency, provenance: 'preinstalled' };
|
|
276
|
+
}
|
|
277
|
+
// The SDK's own EXT-10 gate runs before any bytes are built: a dependency that
|
|
278
|
+
// declares an install-time script refuses here with the SDK's exact named error
|
|
279
|
+
// instead of after a full archive/extract cycle. The SDK checks again after
|
|
280
|
+
// extraction; this copy is the cheap, identical refusal.
|
|
281
|
+
refuseInstallScripts(dependency.directory, dependency.name, dependency.version);
|
|
282
|
+
const built = buildDependencyArchive(dependency, limits);
|
|
283
|
+
return { kind: 'registry-bytes', dependency, integrity: built.integrity, bytes: built.bytes };
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Create the first-party dependency graph supplied to the SDK's directory installer.
|
|
287
|
+
*
|
|
288
|
+
* Resolution and archiving happen lazily, one dependency at a time: `resolve`
|
|
289
|
+
* builds the exact bytes the SDK will verify and `fetch` hands back those same
|
|
290
|
+
* bytes, so the integrity the lock records is the integrity of the served archive
|
|
291
|
+
* rather than a value guessed from metadata. Only the most recently built archive
|
|
292
|
+
* is retained, which matches the SDK's resolve-then-fetch sequence and bounds
|
|
293
|
+
* memory to one dependency.
|
|
294
|
+
*
|
|
295
|
+
* The preinstalled-content gate is OPT-IN and OFF by default, matching the SDK:
|
|
296
|
+
* without it every dependency is served as verified bytes and EXT-10 applies. With
|
|
297
|
+
* it, the SDK requires `preinstalledRoot`, copies the resolved directory into the
|
|
298
|
+
* extension's own `node_modules`, and records `provenance: 'preinstalled'`.
|
|
299
|
+
* @param options - the search tree, bounds, gate and forwarded graph limits.
|
|
300
|
+
* @returns the install-dependency graph plus its resolver and archive halves.
|
|
301
|
+
* @throws ManagementError `DEPENDENCY_UNSATISFIED` when the gate has no root.
|
|
302
|
+
*/
|
|
303
|
+
export function createFirstPartyDependencyGraph(options) {
|
|
304
|
+
const limits = {
|
|
305
|
+
maxSourceBytes: options.maxSourceBytes ?? DEFAULT_ARCHIVE_LIMITS.maxTotalBytes,
|
|
306
|
+
maxEntryBytes: options.maxEntryBytes ?? DEFAULT_ARCHIVE_LIMITS.maxEntryBytes,
|
|
307
|
+
maxArchiveBytes: options.maxArchiveBytes ?? MAX_DEPENDENCY_ARCHIVE_BYTES,
|
|
308
|
+
};
|
|
309
|
+
let preinstalledGate;
|
|
310
|
+
if (options.allowPreinstalled === true) {
|
|
311
|
+
const root = options.preinstalledRoot;
|
|
312
|
+
if (root === undefined || root.trim().length === 0) {
|
|
313
|
+
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.');
|
|
314
|
+
}
|
|
315
|
+
preinstalledGate = { allowPreinstalled: true, preinstalledRoot: resolve(root) };
|
|
316
|
+
}
|
|
317
|
+
/** The archive built by the most recent resolve, keyed by name@version. */
|
|
318
|
+
let latest;
|
|
319
|
+
const keyOf = (name, version) => `${name}@${version}`;
|
|
320
|
+
const resolver = {
|
|
321
|
+
async resolve(name, range) {
|
|
322
|
+
const supply = resolveDependency(options, limits, name, range);
|
|
323
|
+
const key = keyOf(supply.dependency.name, supply.dependency.version);
|
|
324
|
+
if (supply.kind === 'preinstalled') {
|
|
325
|
+
return {
|
|
326
|
+
name: supply.dependency.name,
|
|
327
|
+
version: supply.dependency.version,
|
|
328
|
+
dir: supply.dependency.directory,
|
|
329
|
+
provenance: 'preinstalled',
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
latest = { key, bytes: supply.bytes };
|
|
333
|
+
return {
|
|
334
|
+
name: supply.dependency.name,
|
|
335
|
+
version: supply.dependency.version,
|
|
336
|
+
tarball: { kind: 'local', requested: key },
|
|
337
|
+
integrity: supply.integrity,
|
|
338
|
+
};
|
|
339
|
+
},
|
|
340
|
+
};
|
|
341
|
+
const archive = createZipArchiveSource(async (source) => {
|
|
342
|
+
if (latest === undefined || latest.key !== source.requested) {
|
|
343
|
+
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.');
|
|
344
|
+
}
|
|
345
|
+
if (latest.bytes.byteLength > MAX_DEPENDENCY_ARCHIVE_BYTES) {
|
|
346
|
+
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.');
|
|
347
|
+
}
|
|
348
|
+
return latest.bytes;
|
|
349
|
+
});
|
|
350
|
+
/** The optional graph fields, added only when configured (no empty-object spreads). */
|
|
351
|
+
const optionalGraphFields = {};
|
|
352
|
+
if (preinstalledGate !== undefined) {
|
|
353
|
+
optionalGraphFields.allowPreinstalled = preinstalledGate.allowPreinstalled;
|
|
354
|
+
optionalGraphFields.preinstalledRoot = preinstalledGate.preinstalledRoot;
|
|
355
|
+
}
|
|
356
|
+
if (options.maxDepth !== undefined)
|
|
357
|
+
optionalGraphFields.maxDepth = options.maxDepth;
|
|
358
|
+
if (options.maxPackages !== undefined)
|
|
359
|
+
optionalGraphFields.maxPackages = options.maxPackages;
|
|
360
|
+
const graph = {
|
|
361
|
+
resolver,
|
|
362
|
+
archive,
|
|
363
|
+
...optionalGraphFields,
|
|
364
|
+
};
|
|
365
|
+
return { graph, resolver, archive };
|
|
366
|
+
}
|
|
367
|
+
/** Re-export for callers that want the size bound wording. */
|
|
368
|
+
export { DEFAULT_ARCHIVE_LIMITS };
|
|
369
|
+
//# sourceMappingURL=extension-dependencies.js.map
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Provider-backed dispatch for extension-declared tools (EXT-19, EXT-21, EXT-22, ARCH-05).
|
|
3
|
+
*
|
|
4
|
+
* The TMX runtime API has **no tool-invocation surface**. `ExtensionInstanceV1` exposes
|
|
5
|
+
* exactly `createProvider` and `dispose` — nothing else. So an extension's tool is not
|
|
6
|
+
* something the host calls directly; the tool is a caller-facing NAME whose contract is
|
|
7
|
+
* bound to an IMPLEMENTATION, and the implementation is a model PROVIDER.
|
|
8
|
+
*
|
|
9
|
+
* manifest.tools[] -> the caller-facing name and its contract
|
|
10
|
+
* manifest.implementations[] -> which contract, served by which model
|
|
11
|
+
* instance.createProvider() -> the thing that actually runs
|
|
12
|
+
*
|
|
13
|
+
* That chain is what this module walks. An earlier version of the host registrar
|
|
14
|
+
* registered a tool whose `execute` simply REFUSED, which was honest about there being no
|
|
15
|
+
* implementation bound but left the bridge functionally incomplete: the name appeared in
|
|
16
|
+
* the registry and could never be called.
|
|
17
|
+
*
|
|
18
|
+
* The context is INJECTED so the chain is testable without a real extension on disk.
|
|
19
|
+
*
|
|
20
|
+
* @module @try-works/dsh-model-as-tool/extension-dispatch
|
|
21
|
+
*/
|
|
22
|
+
import type { PreparedAssetMap } from './prepared-assets.ts';
|
|
23
|
+
/** What a manifest declares about one tool. */
|
|
24
|
+
export interface DeclaredTool {
|
|
25
|
+
readonly name: string;
|
|
26
|
+
readonly contractId: string;
|
|
27
|
+
readonly contractVersion: string;
|
|
28
|
+
}
|
|
29
|
+
/** What a manifest declares about one implementation. */
|
|
30
|
+
export interface DeclaredImplementation {
|
|
31
|
+
readonly id: string;
|
|
32
|
+
readonly contractId: string;
|
|
33
|
+
readonly contractVersion: string;
|
|
34
|
+
/** The model that serves the contract. */
|
|
35
|
+
readonly modelId: string;
|
|
36
|
+
}
|
|
37
|
+
/** The parts of a manifest this module needs. */
|
|
38
|
+
export interface DispatchManifest {
|
|
39
|
+
readonly tools: readonly DeclaredTool[];
|
|
40
|
+
readonly implementations: readonly DeclaredImplementation[];
|
|
41
|
+
}
|
|
42
|
+
/** A provider, narrowed to what dispatch needs. */
|
|
43
|
+
export interface DispatchProvider {
|
|
44
|
+
load(request: {
|
|
45
|
+
readonly assets: unknown;
|
|
46
|
+
readonly signal: AbortSignal;
|
|
47
|
+
/** The manifest model id; the managed worker's load keys by it. */
|
|
48
|
+
readonly modelId?: string;
|
|
49
|
+
}): Promise<void>;
|
|
50
|
+
invoke(request: {
|
|
51
|
+
readonly implementationId: string;
|
|
52
|
+
readonly input: Readonly<Record<string, unknown>>;
|
|
53
|
+
readonly context: unknown;
|
|
54
|
+
}): Promise<Record<string, unknown>>;
|
|
55
|
+
}
|
|
56
|
+
/** What dispatch needs from the host to reach an extension. */
|
|
57
|
+
export interface DispatchEnvironment {
|
|
58
|
+
/** The manifest of the activated extension that declared the tool. */
|
|
59
|
+
getManifest(): DispatchManifest;
|
|
60
|
+
/** Create (or return a cached) provider for a model deployment. */
|
|
61
|
+
providerFor(modelId: string): Promise<DispatchProvider>;
|
|
62
|
+
/** Build the invocation context, which carries host-issued identity. */
|
|
63
|
+
contextFor(implementationId: string, signal: AbortSignal): unknown;
|
|
64
|
+
/**
|
|
65
|
+
* The host's verified prepared-asset map for THIS extension, or undefined
|
|
66
|
+
* when nothing was prepared (the load then receives the EMPTY map — an
|
|
67
|
+
* extension needing assets refuses by name inside its own load, which is the
|
|
68
|
+
* honest MOD-02 behavior rather than an invented verification). Closes over
|
|
69
|
+
* the extension the environment was built for.
|
|
70
|
+
*/
|
|
71
|
+
assetsFor?(): PreparedAssetMap | undefined;
|
|
72
|
+
}
|
|
73
|
+
/** Why a tool could not be dispatched. */
|
|
74
|
+
export interface DispatchFailure {
|
|
75
|
+
readonly code: 'not-declared' | 'no-implementation' | 'unsupported';
|
|
76
|
+
readonly message: string;
|
|
77
|
+
readonly remedy: string;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Resolve the implementation chain for a declared tool.
|
|
81
|
+
*
|
|
82
|
+
* @param manifest - the activated extension's manifest.
|
|
83
|
+
* @param toolName - the caller-facing name.
|
|
84
|
+
* @returns the tool, its implementation, or why the chain is incomplete.
|
|
85
|
+
*/
|
|
86
|
+
export declare function resolveImplementation(manifest: DispatchManifest, toolName: string): {
|
|
87
|
+
readonly ok: true;
|
|
88
|
+
readonly tool: DeclaredTool;
|
|
89
|
+
readonly implementation: DeclaredImplementation;
|
|
90
|
+
} | {
|
|
91
|
+
readonly ok: false;
|
|
92
|
+
readonly failure: DispatchFailure;
|
|
93
|
+
};
|
|
94
|
+
/**
|
|
95
|
+
* Build an `execute` for one declared tool.
|
|
96
|
+
*
|
|
97
|
+
* The provider is created lazily on first call, so ACTIVATION stays lightweight as EXT-19
|
|
98
|
+
* requires: nothing is loaded until a caller actually invokes the tool.
|
|
99
|
+
*
|
|
100
|
+
* @param environment - how to reach the extension.
|
|
101
|
+
* @param toolName - the caller-facing name.
|
|
102
|
+
* @returns the execute function, or the failure that prevents one.
|
|
103
|
+
*/
|
|
104
|
+
export declare function createDispatch(environment: DispatchEnvironment, toolName: string): Promise<{
|
|
105
|
+
readonly ok: true;
|
|
106
|
+
readonly execute: (args: Record<string, unknown>, signal: AbortSignal) => Promise<Record<string, unknown>>;
|
|
107
|
+
} | {
|
|
108
|
+
readonly ok: false;
|
|
109
|
+
readonly failure: DispatchFailure;
|
|
110
|
+
}>;
|
|
111
|
+
/**
|
|
112
|
+
* Describe a dispatch failure for a caller (EXT-30).
|
|
113
|
+
* @param failure - the failure.
|
|
114
|
+
* @returns a readable message naming what to do.
|
|
115
|
+
*/
|
|
116
|
+
export declare function describeDispatchFailure(failure: DispatchFailure): string;
|
|
117
|
+
//# sourceMappingURL=extension-dispatch.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"extension-dispatch.d.ts","sourceRoot":"","sources":["../src/extension-dispatch.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Provider-backed dispatch for extension-declared tools (EXT-19, EXT-21, EXT-22, ARCH-05).
|
|
3
|
+
*
|
|
4
|
+
* The TMX runtime API has **no tool-invocation surface**. `ExtensionInstanceV1` exposes
|
|
5
|
+
* exactly `createProvider` and `dispose` — nothing else. So an extension's tool is not
|
|
6
|
+
* something the host calls directly; the tool is a caller-facing NAME whose contract is
|
|
7
|
+
* bound to an IMPLEMENTATION, and the implementation is a model PROVIDER.
|
|
8
|
+
*
|
|
9
|
+
* manifest.tools[] -> the caller-facing name and its contract
|
|
10
|
+
* manifest.implementations[] -> which contract, served by which model
|
|
11
|
+
* instance.createProvider() -> the thing that actually runs
|
|
12
|
+
*
|
|
13
|
+
* That chain is what this module walks. An earlier version of the host registrar
|
|
14
|
+
* registered a tool whose `execute` simply REFUSED, which was honest about there being no
|
|
15
|
+
* implementation bound but left the bridge functionally incomplete: the name appeared in
|
|
16
|
+
* the registry and could never be called.
|
|
17
|
+
*
|
|
18
|
+
* The context is INJECTED so the chain is testable without a real extension on disk.
|
|
19
|
+
*
|
|
20
|
+
* @module @try-works/dsh-model-as-tool/extension-dispatch
|
|
21
|
+
*/
|
|
22
|
+
import { assertNoReservedMeta } from '@try-works/tool-mode-extension-sdk';
|
|
23
|
+
/**
|
|
24
|
+
* Resolve the implementation chain for a declared tool.
|
|
25
|
+
*
|
|
26
|
+
* @param manifest - the activated extension's manifest.
|
|
27
|
+
* @param toolName - the caller-facing name.
|
|
28
|
+
* @returns the tool, its implementation, or why the chain is incomplete.
|
|
29
|
+
*/
|
|
30
|
+
export function resolveImplementation(manifest, toolName) {
|
|
31
|
+
const tool = manifest.tools.find(candidate => candidate.name === toolName);
|
|
32
|
+
if (tool === undefined) {
|
|
33
|
+
return {
|
|
34
|
+
ok: false,
|
|
35
|
+
failure: {
|
|
36
|
+
code: 'not-declared',
|
|
37
|
+
message: `${toolName} is not declared by this extension's manifest`,
|
|
38
|
+
remedy: 'A registered tool must be declared in the manifest it was published from.',
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
const implementation = manifest.implementations.find(candidate => candidate.contractId === tool.contractId && candidate.contractVersion === tool.contractVersion);
|
|
43
|
+
if (implementation === undefined) {
|
|
44
|
+
/**
|
|
45
|
+
* THE HONEST REFUSAL, and the case the earlier stub conflated with every other.
|
|
46
|
+
*
|
|
47
|
+
* An extension may DECLARE a tool it does not yet serve — EmbeddingGemma does exactly
|
|
48
|
+
* this, listing contracts whose ONNX path is not wired. The name is legitimately
|
|
49
|
+
* registered and legitimately uncallable, and the caller must be told WHICH of those
|
|
50
|
+
* two situations they are in.
|
|
51
|
+
*/
|
|
52
|
+
return {
|
|
53
|
+
ok: false,
|
|
54
|
+
failure: {
|
|
55
|
+
code: 'no-implementation',
|
|
56
|
+
message: `${toolName} declares contract ${tool.contractId}, which no implementation serves`,
|
|
57
|
+
remedy: 'The extension declares this tool but binds no model to it yet; it is not callable in this build.',
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
return { ok: true, tool, implementation };
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Build an `execute` for one declared tool.
|
|
65
|
+
*
|
|
66
|
+
* The provider is created lazily on first call, so ACTIVATION stays lightweight as EXT-19
|
|
67
|
+
* requires: nothing is loaded until a caller actually invokes the tool.
|
|
68
|
+
*
|
|
69
|
+
* @param environment - how to reach the extension.
|
|
70
|
+
* @param toolName - the caller-facing name.
|
|
71
|
+
* @returns the execute function, or the failure that prevents one.
|
|
72
|
+
*/
|
|
73
|
+
export async function createDispatch(environment, toolName) {
|
|
74
|
+
const resolved = resolveImplementation(environment.getManifest(), toolName);
|
|
75
|
+
if (!resolved.ok)
|
|
76
|
+
return resolved;
|
|
77
|
+
const { implementation } = resolved;
|
|
78
|
+
return {
|
|
79
|
+
ok: true,
|
|
80
|
+
async execute(args, signal) {
|
|
81
|
+
// Resolved per call rather than captured, so a provider that was unloaded — or an
|
|
82
|
+
// extension that was deactivated and reactivated — is not served from a stale handle.
|
|
83
|
+
const provider = await environment.providerFor(implementation.modelId);
|
|
84
|
+
/**
|
|
85
|
+
* The HOST-VERIFIED prepared-asset map, when a preparation completed.
|
|
86
|
+
* An absent map means the empty map — the extension's own load refuses a
|
|
87
|
+
* missing group by name (MOD-02), which is the honest outcome rather than
|
|
88
|
+
* a host that invents a verification that never happened. The same map
|
|
89
|
+
* reaches BOTH provider routes here: the in-process module provider and
|
|
90
|
+
* the managed worker (its load forwards the assets to the child).
|
|
91
|
+
*/
|
|
92
|
+
const prepared = environment.assetsFor?.();
|
|
93
|
+
await provider.load({ assets: prepared ?? { get: () => undefined, assetIds: [] }, signal, modelId: implementation.modelId });
|
|
94
|
+
const value = await provider.invoke({
|
|
95
|
+
implementationId: implementation.id,
|
|
96
|
+
input: args,
|
|
97
|
+
context: environment.contextFor(implementation.id, signal),
|
|
98
|
+
});
|
|
99
|
+
/**
|
|
100
|
+
* THE EXT-17 META RESERVATION IS ENFORCED HERE, AT THE DISPATCH BOUNDARY.
|
|
101
|
+
*
|
|
102
|
+
* The reserved root `meta` is HOST-authored provenance; an extension supplying one
|
|
103
|
+
* is asserting who ran it and under what receipt, which it does not get to do. The
|
|
104
|
+
* ProviderBridge asserts this on the CATALOG path, but the contribution path
|
|
105
|
+
* (module and declarative providers alike) went unasserted — so a provider that
|
|
106
|
+
* smuggled a `meta` key reached callers whenever its declared output schema did not
|
|
107
|
+
* require the envelope. Asserting at the boundary the two provider routes share
|
|
108
|
+
* closes the gap for both, and it runs BEFORE any host stamping, so a forged
|
|
109
|
+
* envelope is refused rather than overwritten.
|
|
110
|
+
*/
|
|
111
|
+
assertNoReservedMeta(value, implementation.id);
|
|
112
|
+
return value;
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Describe a dispatch failure for a caller (EXT-30).
|
|
118
|
+
* @param failure - the failure.
|
|
119
|
+
* @returns a readable message naming what to do.
|
|
120
|
+
*/
|
|
121
|
+
export function describeDispatchFailure(failure) {
|
|
122
|
+
return `${failure.message}. ${failure.remedy}`;
|
|
123
|
+
}
|
|
124
|
+
//# sourceMappingURL=extension-dispatch.js.map
|