@try-works/dsh-model-as-tool 0.1.2 → 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/lang-detect.d.ts +16 -0
- package/node_modules/@try-works/tool-mode-contracts/lib/contracts/lang-detect.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-contracts/lib/contracts/lang-detect.js +85 -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/stt-transcribe.d.ts +20 -0
- package/node_modules/@try-works/tool-mode-contracts/lib/contracts/stt-transcribe.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-contracts/lib/contracts/stt-transcribe.js +112 -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 +82 -0
- package/node_modules/@try-works/tool-mode-contracts/lib/dsh.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-contracts/lib/dsh.js +274 -0
- package/node_modules/@try-works/tool-mode-contracts/lib/index.d.ts +26 -0
- package/node_modules/@try-works/tool-mode-contracts/lib/index.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-contracts/lib/index.js +48 -0
- package/node_modules/@try-works/tool-mode-contracts/lib/meta.d.ts +104 -0
- package/node_modules/@try-works/tool-mode-contracts/lib/meta.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-contracts/lib/meta.js +55 -0
- package/node_modules/@try-works/tool-mode-contracts/lib/registry.d.ts +107 -0
- package/node_modules/@try-works/tool-mode-contracts/lib/registry.d.ts.map +1 -0
- package/node_modules/@try-works/tool-mode-contracts/lib/registry.js +246 -0
- package/node_modules/@try-works/tool-mode-contracts/package.json +34 -0
- 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 +50 -17
- 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,286 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The host's prepared-assets path (RES-02/03/05, the last in-repo slice):
|
|
3
|
+
* lock reading → preparation plan → approval-gated, digest-verified acquisition
|
|
4
|
+
* into an isolated content-addressed cache → the prepared-cold readiness flip →
|
|
5
|
+
* the PreparedAssetMapV1 the provider routes consume.
|
|
6
|
+
*
|
|
7
|
+
* The verified components this module finally gives callers to:
|
|
8
|
+
*
|
|
9
|
+
* - `planPreparation`/`DeploymentIdentity` (deployment-coordinator.ts) — the
|
|
10
|
+
* pre-approval disclosure;
|
|
11
|
+
* - `acquireAsset` (asset-acquisition.ts) — digest+size verified, atomic,
|
|
12
|
+
* resumable acquisition with tamper refusal;
|
|
13
|
+
* - `createHttpSource` (http-acquisition.ts) — the default ranged source.
|
|
14
|
+
*
|
|
15
|
+
* Honesty rules carried through:
|
|
16
|
+
*
|
|
17
|
+
* - a lock with no digests REFUSES (no guessed values);
|
|
18
|
+
* - an executable asset WITHOUT the explicit executable-preparation approval
|
|
19
|
+
* refuses (RES-02);
|
|
20
|
+
* - a tampered final path is refused and never flips readiness;
|
|
21
|
+
* - the cache lives under the host's isolated state dir, never the repo or
|
|
22
|
+
* HOME; a REAL network run is opt-in (the default source), and the suite
|
|
23
|
+
* injects a local file source so it performs no network.
|
|
24
|
+
*
|
|
25
|
+
* @module @try-works/dsh-model-as-tool/prepared-assets
|
|
26
|
+
*/
|
|
27
|
+
import { createHash } from 'node:crypto';
|
|
28
|
+
import { existsSync, linkSync, mkdirSync, readFileSync, renameSync, rmSync, statSync, writeFileSync } from 'node:fs';
|
|
29
|
+
import { join } from 'node:path';
|
|
30
|
+
import { acquireAsset } from "./asset-acquisition.js";
|
|
31
|
+
/**
|
|
32
|
+
* Parse a models.lock.json document.
|
|
33
|
+
*
|
|
34
|
+
* A lock with no digest on any asset REFUSES: a preparation built on guessed
|
|
35
|
+
* bytes would verify nothing (RES-03).
|
|
36
|
+
* @param bytes - the lock document.
|
|
37
|
+
* @returns the parsed lock.
|
|
38
|
+
*/
|
|
39
|
+
export function parseAssetLock(bytes) {
|
|
40
|
+
let document;
|
|
41
|
+
try {
|
|
42
|
+
document = JSON.parse(bytes);
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
throw new Error(`the asset lock is unreadable: ${error instanceof Error ? error.message : String(error)}`);
|
|
46
|
+
}
|
|
47
|
+
if (typeof document !== 'object' || document === null)
|
|
48
|
+
throw new Error('the asset lock is not a document');
|
|
49
|
+
const root = document;
|
|
50
|
+
const assetSets = root['assetSets'];
|
|
51
|
+
if (typeof assetSets !== 'object' || assetSets === null)
|
|
52
|
+
throw new Error('the asset lock declares no assetSets');
|
|
53
|
+
const sets = {};
|
|
54
|
+
for (const [setId, rawSet] of Object.entries(assetSets)) {
|
|
55
|
+
const set = rawSet;
|
|
56
|
+
const revision = typeof set['pinning'] === 'object' && set['pinning'] !== null
|
|
57
|
+
? set['pinning']['revision']
|
|
58
|
+
: undefined;
|
|
59
|
+
const license = typeof set['license'] === 'object' && set['license'] !== null
|
|
60
|
+
? set['license']
|
|
61
|
+
: {};
|
|
62
|
+
const rawGroups = set['groups'];
|
|
63
|
+
if (!Array.isArray(rawGroups))
|
|
64
|
+
throw new Error(`asset set ${setId} declares no groups`);
|
|
65
|
+
const groups = rawGroups.map((rawGroup, groupIndex) => {
|
|
66
|
+
const group = rawGroup;
|
|
67
|
+
const rawAssets = group['assets'];
|
|
68
|
+
if (!Array.isArray(rawAssets))
|
|
69
|
+
throw new Error(`asset set ${setId} group ${groupIndex} declares no assets`);
|
|
70
|
+
const assets = rawAssets.map((rawAsset, assetIndex) => {
|
|
71
|
+
const asset = rawAsset;
|
|
72
|
+
const digest = typeof asset['digest'] === 'string' ? asset['digest'] : '';
|
|
73
|
+
const bytes = typeof asset['bytes'] === 'number' ? asset['bytes'] : NaN;
|
|
74
|
+
const id = typeof asset['id'] === 'string' ? asset['id'] : '';
|
|
75
|
+
const file = typeof asset['file'] === 'string' ? asset['file'] : '';
|
|
76
|
+
const source = typeof asset['source'] === 'string' ? asset['source'] : '';
|
|
77
|
+
if (digest.length === 0 || !Number.isFinite(bytes) || id.length === 0 || file.length === 0) {
|
|
78
|
+
throw new Error(`asset set ${setId} group ${groupIndex} asset ${assetIndex} is incomplete; a lock without pinned digests and sizes refuses (RES-03)`);
|
|
79
|
+
}
|
|
80
|
+
return { id, file, bytes, source, digest, executable: asset['executable'] === true };
|
|
81
|
+
});
|
|
82
|
+
return { id: typeof group['id'] === 'string' ? group['id'] : `group-${groupIndex}`, assets };
|
|
83
|
+
});
|
|
84
|
+
sets[setId] = {
|
|
85
|
+
groups,
|
|
86
|
+
revision: typeof revision === 'string' ? revision : '',
|
|
87
|
+
license: {
|
|
88
|
+
name: typeof license['name'] === 'string' ? license['name'] : 'undeclared',
|
|
89
|
+
url: typeof license['url'] === 'string' ? license['url'] : '',
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
const lockRevision = Object.values(sets)[0]?.revision ?? '';
|
|
94
|
+
if (lockRevision.length === 0)
|
|
95
|
+
throw new Error('the asset lock pins no model revision; a preparation cannot be planned');
|
|
96
|
+
return { revision: lockRevision, license: Object.values(sets)[0]?.license ?? { name: 'undeclared', url: '' }, sets };
|
|
97
|
+
}
|
|
98
|
+
/** The identity the lock implies for one model deployment. */
|
|
99
|
+
export function identityFor(extensionId, modelId, lock) {
|
|
100
|
+
return {
|
|
101
|
+
extensionId,
|
|
102
|
+
modelId,
|
|
103
|
+
modelRevision: lock.revision,
|
|
104
|
+
engineVersion: 'onnxruntime-node',
|
|
105
|
+
quantization: 'unquantized-fp32',
|
|
106
|
+
tokenizer: 'gemma',
|
|
107
|
+
preprocessingRecipe: 'pinned',
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
/** The flat PlannedAsset list for one asset set. */
|
|
111
|
+
export function plannedAssets(lock, assetSetId) {
|
|
112
|
+
const set = lock.sets[assetSetId];
|
|
113
|
+
if (set === undefined)
|
|
114
|
+
throw new Error(`the lock declares no asset set "${assetSetId}"`);
|
|
115
|
+
return set.groups.flatMap(group => group.assets.map(asset => ({
|
|
116
|
+
assetId: `${group.id}/${asset.id}`,
|
|
117
|
+
bytes: asset.bytes,
|
|
118
|
+
source: asset.source,
|
|
119
|
+
digest: asset.digest,
|
|
120
|
+
executable: asset.executable,
|
|
121
|
+
})));
|
|
122
|
+
}
|
|
123
|
+
/** The group map for one asset set, keyed by group id. */
|
|
124
|
+
export function lockGroups(lock, assetSetId) {
|
|
125
|
+
const set = lock.sets[assetSetId];
|
|
126
|
+
if (set === undefined)
|
|
127
|
+
throw new Error(`the lock declares no asset set "${assetSetId}"`);
|
|
128
|
+
return set.groups;
|
|
129
|
+
}
|
|
130
|
+
/** The filesystem store acquisition needs, over node:fs. */
|
|
131
|
+
function fsStore() {
|
|
132
|
+
return {
|
|
133
|
+
write(path, bytes) {
|
|
134
|
+
mkdirSync(join(path, '..'), { recursive: true });
|
|
135
|
+
writeFileSync(path, bytes);
|
|
136
|
+
},
|
|
137
|
+
read(path) {
|
|
138
|
+
if (!existsSync(path))
|
|
139
|
+
return undefined;
|
|
140
|
+
return new Uint8Array(readFileSync(path));
|
|
141
|
+
},
|
|
142
|
+
sizeOf(path) {
|
|
143
|
+
if (!existsSync(path))
|
|
144
|
+
return 0;
|
|
145
|
+
return statSyncSize(path);
|
|
146
|
+
},
|
|
147
|
+
rename(from, to) {
|
|
148
|
+
renameSync(from, to);
|
|
149
|
+
},
|
|
150
|
+
remove(path) {
|
|
151
|
+
rmSync(path, { force: true });
|
|
152
|
+
},
|
|
153
|
+
exists(path) {
|
|
154
|
+
return existsSync(path);
|
|
155
|
+
},
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
/** Synchronous size (the store interface is sync). */
|
|
159
|
+
function statSyncSize(path) {
|
|
160
|
+
try {
|
|
161
|
+
return statSync(path).size;
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
return 0;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Create the prepared-assets registry.
|
|
169
|
+
* @param options - the cache root and the default source.
|
|
170
|
+
* @returns the registry.
|
|
171
|
+
*/
|
|
172
|
+
export function createPreparedAssetRegistry(options) {
|
|
173
|
+
const cacheRoot = options.cacheRoot;
|
|
174
|
+
const held = new Map();
|
|
175
|
+
// The content-addressed blob pool, shared across extensions (RES-03: two
|
|
176
|
+
// owners share one set of bytes).
|
|
177
|
+
return {
|
|
178
|
+
plans() {
|
|
179
|
+
return new Map([...held.entries()].map(([extensionId, preparation]) => [extensionId, preparation.plan]));
|
|
180
|
+
},
|
|
181
|
+
assetMapFor(extensionId) {
|
|
182
|
+
const preparation = held.get(extensionId);
|
|
183
|
+
if (preparation === undefined)
|
|
184
|
+
return undefined;
|
|
185
|
+
/**
|
|
186
|
+
* TWO projections over one verified preparation, both truthful:
|
|
187
|
+
*
|
|
188
|
+
* - the GROUP id resolves to the group directory (embeddinggemma's selective
|
|
189
|
+
* loading asks for `text-backbone` and reads `onnx/model.onnx` under it);
|
|
190
|
+
* - each ASSET id resolves to the individual materialized file (needle asks
|
|
191
|
+
* for `needle-engine-win-x64` and needs the executable's path).
|
|
192
|
+
*
|
|
193
|
+
* The SDK documents the map as asset ids to local handles, so the per-asset
|
|
194
|
+
* entries are the canonical projection and the group entry is the directory
|
|
195
|
+
* convenience the first-party group consumers already rely on. An exact
|
|
196
|
+
* asset-id match wins over a group-id match; the ids are namespaced by the
|
|
197
|
+
* lock and a collision is not silently served as the other kind.
|
|
198
|
+
*/
|
|
199
|
+
const groupHandles = new Map();
|
|
200
|
+
const assetHandles = new Map();
|
|
201
|
+
for (const group of preparation.groups) {
|
|
202
|
+
groupHandles.set(group.groupId, { path: group.path, digest: group.digests.join(',') });
|
|
203
|
+
for (const file of group.files) {
|
|
204
|
+
assetHandles.set(file.assetId, { path: file.path, digest: file.digest });
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
return {
|
|
208
|
+
assetIds: [...groupHandles.keys(), ...assetHandles.keys()],
|
|
209
|
+
get(assetId) {
|
|
210
|
+
return assetHandles.get(assetId) ?? groupHandles.get(assetId);
|
|
211
|
+
},
|
|
212
|
+
};
|
|
213
|
+
},
|
|
214
|
+
groupsFor(extensionId) {
|
|
215
|
+
return held.get(extensionId)?.groups ?? [];
|
|
216
|
+
},
|
|
217
|
+
async prepare(extensionId, plan, groups, options) {
|
|
218
|
+
if (!options.allowExecutable && plan.requiresExecutableApproval) {
|
|
219
|
+
return {
|
|
220
|
+
ok: false,
|
|
221
|
+
reason: 'the plan includes an EXECUTABLE asset, which requires the separate executable-preparation approval (RES-02); this call did not grant it',
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
const store = fsStore();
|
|
225
|
+
mkdirSync(cacheRoot, { recursive: true });
|
|
226
|
+
const prepared = [];
|
|
227
|
+
for (const group of groups) {
|
|
228
|
+
const groupDir = join(cacheRoot, 'groups', plan.identity.extensionId.replaceAll('/', '__'), plan.identity.modelId, group.id);
|
|
229
|
+
const digests = [];
|
|
230
|
+
const files = [];
|
|
231
|
+
for (const asset of group.assets) {
|
|
232
|
+
const planned = {
|
|
233
|
+
assetId: asset.id,
|
|
234
|
+
bytes: asset.bytes,
|
|
235
|
+
source: asset.source,
|
|
236
|
+
digest: asset.digest,
|
|
237
|
+
executable: asset.executable,
|
|
238
|
+
};
|
|
239
|
+
// NO in-memory blob shortcut: acquireAsset re-verifies the on-disk
|
|
240
|
+
// bytes on every call (a cache HIT transfers zero bytes but still
|
|
241
|
+
// checks the digest), so a blob tampered on disk is re-acquired
|
|
242
|
+
// rather than trusted from a stale memory record — and two owners
|
|
243
|
+
// still share the verified bytes.
|
|
244
|
+
const result = await acquireAsset(planned, {
|
|
245
|
+
store,
|
|
246
|
+
source: options.source,
|
|
247
|
+
root: cacheRoot,
|
|
248
|
+
signal: options.signal,
|
|
249
|
+
});
|
|
250
|
+
if (!result.acquired) {
|
|
251
|
+
return { ok: false, reason: `${group.id}/${asset.id}: ${result.reason}` };
|
|
252
|
+
}
|
|
253
|
+
const blob = { digest: result.digest, path: result.path };
|
|
254
|
+
// Materialize the lock's per-asset FILE layout inside the group dir via
|
|
255
|
+
// a hard link: the provider resolves `${groupDir}/onnx/model.onnx` etc.
|
|
256
|
+
// The link shares the verified blob's bytes (same volume); the digest
|
|
257
|
+
// was verified at acquisition and the blob path never changes.
|
|
258
|
+
mkdirSync(join(groupDir, asset.file, '..'), { recursive: true });
|
|
259
|
+
const linkTarget = join(groupDir, asset.file);
|
|
260
|
+
if (!existsSync(linkTarget)) {
|
|
261
|
+
try {
|
|
262
|
+
linkSync(blob.path, linkTarget);
|
|
263
|
+
}
|
|
264
|
+
catch {
|
|
265
|
+
// Hard links can fail across volumes; a copy is the fallback and
|
|
266
|
+
// is equally verified because the blob's digest was checked.
|
|
267
|
+
writeFileSync(linkTarget, readFileSync(blob.path));
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
else if (createHash('sha256').update(readFileSync(linkTarget)).digest('hex') !== asset.digest) {
|
|
271
|
+
// A tampered FINAL path never poisons readiness: it is removed and
|
|
272
|
+
// the verified blob re-linked.
|
|
273
|
+
rmSync(linkTarget, { force: true });
|
|
274
|
+
linkSync(blob.path, linkTarget);
|
|
275
|
+
}
|
|
276
|
+
digests.push(asset.digest);
|
|
277
|
+
files.push({ assetId: asset.id, file: asset.file, path: linkTarget, digest: asset.digest });
|
|
278
|
+
}
|
|
279
|
+
prepared.push({ groupId: group.id, path: groupDir, digests, files });
|
|
280
|
+
}
|
|
281
|
+
held.set(extensionId, { plan, groups: prepared });
|
|
282
|
+
return { ok: true, groups: prepared };
|
|
283
|
+
},
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
//# sourceMappingURL=prepared-assets.js.map
|
package/lib/provider-bridge.d.ts
CHANGED
|
@@ -14,7 +14,9 @@
|
|
|
14
14
|
*
|
|
15
15
|
* @module @try-works/dsh-model-as-tool/provider-bridge
|
|
16
16
|
*/
|
|
17
|
+
import { type ProviderInspectionV1 } from '@try-works/tool-mode-extension-sdk';
|
|
17
18
|
import { type ToolGrant } from './authority.ts';
|
|
19
|
+
import type { ManagedWorkerRegistry } from './managed-workers.ts';
|
|
18
20
|
import type { ModelCatalogEntry } from './types.ts';
|
|
19
21
|
import type { ModelAdapter } from './adapters/types.ts';
|
|
20
22
|
/** How a model's provider is obtained. */
|
|
@@ -43,10 +45,45 @@ export declare class ProviderBridge {
|
|
|
43
45
|
constructor(options: {
|
|
44
46
|
readonly grant: ToolGrant;
|
|
45
47
|
readonly activationGeneration: number;
|
|
48
|
+
readonly managed?: ManagedWorkerRegistry;
|
|
46
49
|
readonly log?: (message: string, fields?: Record<string, unknown>) => void;
|
|
47
50
|
});
|
|
48
51
|
/** The extensions currently activated, for diagnostics. */
|
|
49
52
|
get activatedExtensions(): readonly string[];
|
|
53
|
+
/**
|
|
54
|
+
* Inspect one extension-backed catalog entry through its provider.
|
|
55
|
+
*
|
|
56
|
+
* Inspection is the extension's read-only readiness path (EXT-18). It may import
|
|
57
|
+
* and activate the extension, but it does not load model assets or invoke the
|
|
58
|
+
* model; the provider itself must report only approved local state. This is used
|
|
59
|
+
* by publication and discovery so the host does not equate "the package
|
|
60
|
+
* resolves" with "the selected deployment is prepared".
|
|
61
|
+
* @param entry - the catalog entry to inspect.
|
|
62
|
+
* @param signal - cancellation for the read-only inspection.
|
|
63
|
+
* @returns the provider's inspection, or undefined for entries with no provider binding.
|
|
64
|
+
*/
|
|
65
|
+
inspect(entry: ModelCatalogEntry, signal: AbortSignal): Promise<ProviderInspectionV1 | undefined>;
|
|
66
|
+
/**
|
|
67
|
+
* Inspect one extension INSTALLED IN THE STORE, keyed by its own manifest model id.
|
|
68
|
+
*
|
|
69
|
+
* The catalog-driven {@link inspect} cannot reach an extension that has no catalog
|
|
70
|
+
* row (EmbeddingGemma and Needle ship declared tools but are not catalogued), which
|
|
71
|
+
* left their deployments permanently `needs-preparation`. This route activates the
|
|
72
|
+
* extension through its public entry and inspects the provider the MANIFEST declares,
|
|
73
|
+
* so "prepared" can become a durable fact for store-installed packages too.
|
|
74
|
+
*
|
|
75
|
+
* Inspection remains read-only (EXT-18): no model assets are loaded.
|
|
76
|
+
* @param specifier - the installed extension package name or ABSOLUTE install path.
|
|
77
|
+
* An absolute path runs the exact store copy the caller verified, rather than
|
|
78
|
+
* whatever the package name resolves to in the host's own dependency graph.
|
|
79
|
+
* @param modelId - the model id the extension's manifest declares.
|
|
80
|
+
* @param signal - cancellation for the read-only inspection.
|
|
81
|
+
* @returns the provider's inspection, or undefined when the model is not declared.
|
|
82
|
+
* @throws ProviderBridgeError when the package cannot be resolved or the provider
|
|
83
|
+
* refuses creation for this model id (the model's declaredness is the provider's
|
|
84
|
+
* to assert, so an undeclared id surfaces as a throw rather than undefined).
|
|
85
|
+
*/
|
|
86
|
+
inspectExtension(specifier: string, modelId: string, signal: AbortSignal): Promise<ProviderInspectionV1 | undefined>;
|
|
50
87
|
/**
|
|
51
88
|
* Build an adapter for one catalog entry.
|
|
52
89
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"provider-bridge.d.ts","sourceRoot":"","sources":["../src/provider-bridge.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;
|
|
1
|
+
{"version":3,"file":"provider-bridge.d.ts","sourceRoot":"","sources":["../src/provider-bridge.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EASL,KAAK,oBAAoB,EAC1B,MAAM,oCAAoC,CAAA;AAE3C,OAAO,EAA6B,KAAK,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAC1E,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAA;AAGjE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAA;AACnD,OAAO,KAAK,EAAgC,YAAY,EAAE,MAAM,qBAAqB,CAAA;AAErF,0CAA0C;AAC1C,MAAM,WAAW,eAAe;IAC9B;;;;OAIG;IACH,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,2DAA2D;IAC3D,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CACzB;AAeD,2EAA2E;AAC3E,qBAAa,mBAAoB,SAAQ,KAAK;IAC5C,SAAkB,IAAI,yBAAwB;CAC/C;AAED;;;;;GAKG;AACH,qBAAa,cAAc;;gBAcb,OAAO,EAAE;QACnB,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAA;QACzB,QAAQ,CAAC,oBAAoB,EAAE,MAAM,CAAA;QACrC,QAAQ,CAAC,OAAO,CAAC,EAAE,qBAAqB,CAAA;QACxC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAA;KAC3E;IAOD,2DAA2D;IAC3D,IAAI,mBAAmB,IAAI,SAAS,MAAM,EAAE,CAE3C;IAED;;;;;;;;;;;OAWG;IACG,OAAO,CAAC,KAAK,EAAE,iBAAiB,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,oBAAoB,GAAG,SAAS,CAAC;IAoBvG;;;;;;;;;;;;;;;;;;;OAmBG;IACG,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,oBAAoB,GAAG,SAAS,CAAC;IAa1H;;;;;;;;;;;;;;OAcG;IACH,UAAU,CAAC,KAAK,EAAE,iBAAiB,GAAG,YAAY,GAAG,SAAS;IAkF9D,yCAAyC;IACnC,OAAO,CAAC,MAAM,GAAE,YAAY,GAAG,SAAS,GAAG,WAAW,GAAG,UAAU,GAAG,OAAoB,GAAG,OAAO,CAAC,IAAI,CAAC;CA0HjH"}
|
package/lib/provider-bridge.js
CHANGED
|
@@ -16,6 +16,8 @@
|
|
|
16
16
|
*/
|
|
17
17
|
import { activateExtension, assertNoReservedMeta, EXTENSION_ENTRY, loadExtensionModule, } from '@try-works/tool-mode-extension-sdk';
|
|
18
18
|
import { createInvocationAuthority } from "./authority.js";
|
|
19
|
+
import { isAbsolute, join } from 'node:path';
|
|
20
|
+
import { pathToFileURL } from 'node:url';
|
|
19
21
|
/** Raised when a model cannot be served through its declared extension. */
|
|
20
22
|
export class ProviderBridgeError extends Error {
|
|
21
23
|
name = 'ProviderBridgeError';
|
|
@@ -31,15 +33,88 @@ export class ProviderBridge {
|
|
|
31
33
|
#grant;
|
|
32
34
|
#generation;
|
|
33
35
|
#log;
|
|
36
|
+
/**
|
|
37
|
+
* The managed-worker registry (EXT-20). Present ONLY when the operator opted in
|
|
38
|
+
* (`managedWorkers: true`): every executable route — catalog invocation and
|
|
39
|
+
* inspection — then goes through the worker, and the in-process import/activate
|
|
40
|
+
* path below is never taken for it. There is NO silent in-process fallback: a
|
|
41
|
+
* phase the protocol cannot serve is REFUSED with its reason.
|
|
42
|
+
*/
|
|
43
|
+
#managed;
|
|
34
44
|
constructor(options) {
|
|
35
45
|
this.#grant = options.grant;
|
|
36
46
|
this.#generation = options.activationGeneration;
|
|
47
|
+
this.#managed = options.managed;
|
|
37
48
|
this.#log = options.log ?? (() => { });
|
|
38
49
|
}
|
|
39
50
|
/** The extensions currently activated, for diagnostics. */
|
|
40
51
|
get activatedExtensions() {
|
|
41
52
|
return [...this.#activated.keys()];
|
|
42
53
|
}
|
|
54
|
+
/**
|
|
55
|
+
* Inspect one extension-backed catalog entry through its provider.
|
|
56
|
+
*
|
|
57
|
+
* Inspection is the extension's read-only readiness path (EXT-18). It may import
|
|
58
|
+
* and activate the extension, but it does not load model assets or invoke the
|
|
59
|
+
* model; the provider itself must report only approved local state. This is used
|
|
60
|
+
* by publication and discovery so the host does not equate "the package
|
|
61
|
+
* resolves" with "the selected deployment is prepared".
|
|
62
|
+
* @param entry - the catalog entry to inspect.
|
|
63
|
+
* @param signal - cancellation for the read-only inspection.
|
|
64
|
+
* @returns the provider's inspection, or undefined for entries with no provider binding.
|
|
65
|
+
*/
|
|
66
|
+
async inspect(entry, signal) {
|
|
67
|
+
const binding = entry.provider;
|
|
68
|
+
if (binding === undefined)
|
|
69
|
+
return undefined;
|
|
70
|
+
if (this.#managed !== undefined) {
|
|
71
|
+
// EXT-20, managed mode: inspection runs ENTIRELY in the managed process
|
|
72
|
+
// through the verified SDK inspect API — the entry is never imported in-host.
|
|
73
|
+
const resolved = await resolveExtensionPackage(binding.extension, entry);
|
|
74
|
+
const worker = await this.#managed.adapterFor({
|
|
75
|
+
extensionId: resolved.manifestId,
|
|
76
|
+
entryPath: resolved.entryPath,
|
|
77
|
+
manifestPath: resolved.manifestPath,
|
|
78
|
+
generation: this.#generation,
|
|
79
|
+
});
|
|
80
|
+
return await worker.inspect(binding.modelId, { signal });
|
|
81
|
+
}
|
|
82
|
+
const activated = await this.#activate(binding.extension, entry);
|
|
83
|
+
const provider = await this.#providerFor(activated, binding);
|
|
84
|
+
return provider.inspect(signal);
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Inspect one extension INSTALLED IN THE STORE, keyed by its own manifest model id.
|
|
88
|
+
*
|
|
89
|
+
* The catalog-driven {@link inspect} cannot reach an extension that has no catalog
|
|
90
|
+
* row (EmbeddingGemma and Needle ship declared tools but are not catalogued), which
|
|
91
|
+
* left their deployments permanently `needs-preparation`. This route activates the
|
|
92
|
+
* extension through its public entry and inspects the provider the MANIFEST declares,
|
|
93
|
+
* so "prepared" can become a durable fact for store-installed packages too.
|
|
94
|
+
*
|
|
95
|
+
* Inspection remains read-only (EXT-18): no model assets are loaded.
|
|
96
|
+
* @param specifier - the installed extension package name or ABSOLUTE install path.
|
|
97
|
+
* An absolute path runs the exact store copy the caller verified, rather than
|
|
98
|
+
* whatever the package name resolves to in the host's own dependency graph.
|
|
99
|
+
* @param modelId - the model id the extension's manifest declares.
|
|
100
|
+
* @param signal - cancellation for the read-only inspection.
|
|
101
|
+
* @returns the provider's inspection, or undefined when the model is not declared.
|
|
102
|
+
* @throws ProviderBridgeError when the package cannot be resolved or the provider
|
|
103
|
+
* refuses creation for this model id (the model's declaredness is the provider's
|
|
104
|
+
* to assert, so an undeclared id surfaces as a throw rather than undefined).
|
|
105
|
+
*/
|
|
106
|
+
async inspectExtension(specifier, modelId, signal) {
|
|
107
|
+
if (this.#managed !== undefined) {
|
|
108
|
+
// Managed mode: the SAME worker-backed inspection as the catalog path —
|
|
109
|
+
// the entry is imported and created only in the child process.
|
|
110
|
+
const entry = { id: modelId, provider: { extension: specifier, modelId } };
|
|
111
|
+
return await this.inspect(entry, signal);
|
|
112
|
+
}
|
|
113
|
+
const entry = { id: modelId, provider: { extension: specifier, modelId } };
|
|
114
|
+
const activated = await this.#activate(specifier, entry);
|
|
115
|
+
const provider = await this.#providerFor(activated, { extension: specifier, modelId });
|
|
116
|
+
return provider.inspect(signal);
|
|
117
|
+
}
|
|
43
118
|
/**
|
|
44
119
|
* Build an adapter for one catalog entry.
|
|
45
120
|
*
|
|
@@ -59,6 +134,62 @@ export class ProviderBridge {
|
|
|
59
134
|
const binding = entry.provider;
|
|
60
135
|
if (binding === undefined)
|
|
61
136
|
return undefined;
|
|
137
|
+
if (this.#managed !== undefined) {
|
|
138
|
+
/**
|
|
139
|
+
* EXT-20, MANAGED MODE: the catalog invocation runs OUT OF PROCESS. The
|
|
140
|
+
* bridge resolves the entry path by DISCOVERY ONLY (reading the manifest is
|
|
141
|
+
* not importing), binds the worker to the activation generation, and sends
|
|
142
|
+
* the pre-authorized input over the wire — the grant, the caller identity
|
|
143
|
+
* and the host environment never cross, and the HOST resolves the path
|
|
144
|
+
* authority before the wire because the child's resolver DENIES by design.
|
|
145
|
+
*/
|
|
146
|
+
const registry = this.#managed;
|
|
147
|
+
return {
|
|
148
|
+
modelId: entry.id,
|
|
149
|
+
invoke: async (invocation) => {
|
|
150
|
+
const resolved = await resolveExtensionPackage(binding.extension, entry);
|
|
151
|
+
const authority = invocation.authority ?? createInvocationAuthority(this.#grant, 'interactive');
|
|
152
|
+
const input = {};
|
|
153
|
+
for (const [key, value] of Object.entries(invocation.args)) {
|
|
154
|
+
input[key] = value;
|
|
155
|
+
}
|
|
156
|
+
// SEC-06: the HOST's authority is the child's path broker — the verified
|
|
157
|
+
// SDK protocol asks the host for EVERY path the provider touches, and an
|
|
158
|
+
// unauthorized candidate is refused by the authority inside this callback
|
|
159
|
+
// BEFORE the child ever opens the file. The broker is request-scoped and
|
|
160
|
+
// carries no grant or identity beyond the resolution function.
|
|
161
|
+
const resolvePath = (candidate) => authority.resolvePath(candidate);
|
|
162
|
+
// EXT-20: the worker is keyed by the TRUE MANIFEST ID, never by the path
|
|
163
|
+
// or the package specifier — and the manifest path rides along so the
|
|
164
|
+
// registry can stage an owning-package layout for manifest-less store copies.
|
|
165
|
+
const worker = await registry.adapterFor({
|
|
166
|
+
extensionId: resolved.manifestId,
|
|
167
|
+
entryPath: resolved.entryPath,
|
|
168
|
+
manifestPath: resolved.manifestPath,
|
|
169
|
+
generation: this.#generation,
|
|
170
|
+
});
|
|
171
|
+
// The CHILD's strict protocol maps IMPLEMENTATION ids to model ids through
|
|
172
|
+
// the manifest, so the wire carries the manifest's implementation id for
|
|
173
|
+
// this model — never the model id itself (a strict provider refuses a
|
|
174
|
+
// model id passed as an implementation id).
|
|
175
|
+
const implementationId = resolved.implementations
|
|
176
|
+
.find(implementation => implementation.modelId === binding.modelId)?.id
|
|
177
|
+
?? binding.modelId;
|
|
178
|
+
const payload = await worker.invoke({
|
|
179
|
+
implementationId,
|
|
180
|
+
input,
|
|
181
|
+
signal: invocation.signal,
|
|
182
|
+
resolvePath,
|
|
183
|
+
});
|
|
184
|
+
// The same reserved-root assertion as the in-process path (EXT-17).
|
|
185
|
+
assertNoReservedMeta(payload, binding.modelId);
|
|
186
|
+
return payload;
|
|
187
|
+
},
|
|
188
|
+
dispose: async () => {
|
|
189
|
+
await registry.dispose(binding.extension);
|
|
190
|
+
},
|
|
191
|
+
};
|
|
192
|
+
}
|
|
62
193
|
return {
|
|
63
194
|
modelId: entry.id,
|
|
64
195
|
invoke: async (invocation) => {
|
|
@@ -111,6 +242,10 @@ export class ProviderBridge {
|
|
|
111
242
|
return { instance, providers: new Map() };
|
|
112
243
|
})();
|
|
113
244
|
this.#activated.set(specifier, pending);
|
|
245
|
+
pending.catch(() => {
|
|
246
|
+
if (this.#activated.get(specifier) === pending)
|
|
247
|
+
this.#activated.delete(specifier);
|
|
248
|
+
});
|
|
114
249
|
return pending;
|
|
115
250
|
}
|
|
116
251
|
/** Create or reuse the provider for one model. */
|
|
@@ -123,6 +258,10 @@ export class ProviderBridge {
|
|
|
123
258
|
configuration: {},
|
|
124
259
|
});
|
|
125
260
|
activated.providers.set(binding.modelId, pending);
|
|
261
|
+
pending.catch(() => {
|
|
262
|
+
if (activated.providers.get(binding.modelId) === pending)
|
|
263
|
+
activated.providers.delete(binding.modelId);
|
|
264
|
+
});
|
|
126
265
|
return pending;
|
|
127
266
|
}
|
|
128
267
|
/** Run one invocation through the provider. */
|
|
@@ -187,32 +326,70 @@ export class ProviderBridge {
|
|
|
187
326
|
}
|
|
188
327
|
}
|
|
189
328
|
/**
|
|
190
|
-
*
|
|
329
|
+
* Resolve an extension package's entry path, manifest identity and manifest path BY
|
|
330
|
+
* DISCOVERY ONLY (EXT-20).
|
|
191
331
|
*
|
|
192
|
-
*
|
|
193
|
-
*
|
|
194
|
-
*
|
|
195
|
-
*
|
|
332
|
+
* This reads the package's metadata — the package.json and the manifest — and
|
|
333
|
+
* returns the absolute `./tool-mode` entry path, the manifest's TRUE id, and the
|
|
334
|
+
* manifest path WITHOUT importing the entry. Managed mode needs exactly this: the
|
|
335
|
+
* worker child imports the entry in its own process and derives its model mapping
|
|
336
|
+
* from the manifest, so the host must never load the module — and must key the
|
|
337
|
+
* worker by the manifest id, never by a path.
|
|
338
|
+
* @param specifier - the package name or absolute install path.
|
|
196
339
|
* @param entry - the catalog entry, for diagnostics.
|
|
197
|
-
* @returns the
|
|
340
|
+
* @returns the entry path, the manifest id and the manifest path.
|
|
198
341
|
* @throws ProviderBridgeError when the package cannot be resolved or has no entry.
|
|
199
342
|
*/
|
|
200
|
-
async function
|
|
343
|
+
async function resolveExtensionPackage(specifier, entry) {
|
|
201
344
|
let packageJsonPath;
|
|
202
345
|
try {
|
|
203
346
|
// `import.meta.resolve` is the resolver the runtime itself uses, so the
|
|
204
|
-
// extension is found exactly as Node would find it at call time.
|
|
205
|
-
|
|
347
|
+
// extension is found exactly as Node would find it at call time. An ABSOLUTE
|
|
348
|
+
// path resolves to the exact installed copy.
|
|
349
|
+
if (isAbsolute(specifier)) {
|
|
350
|
+
packageJsonPath = pathToFileURL(join(specifier, 'package.json')).href;
|
|
351
|
+
}
|
|
352
|
+
else {
|
|
353
|
+
packageJsonPath = import.meta.resolve(`${specifier}/package.json`);
|
|
354
|
+
}
|
|
206
355
|
}
|
|
207
356
|
catch (error) {
|
|
208
|
-
|
|
209
|
-
|
|
357
|
+
const origin = isAbsolute(specifier)
|
|
358
|
+
? 'the installed package'
|
|
359
|
+
: `the dependency "${specifier}"`;
|
|
360
|
+
throw new ProviderBridgeError(`model "${entry.id}" cannot reach ${origin}, which could not be resolved: `
|
|
361
|
+
+ `${error instanceof Error ? error.message : String(error)}. `
|
|
362
|
+
+ (isAbsolute(specifier)
|
|
363
|
+
? 'The store install path is unreadable; reinstall the package and retry.'
|
|
364
|
+
: 'Install it, or correct the catalog binding.'));
|
|
210
365
|
}
|
|
211
366
|
const { discoverExtension } = await import('@try-works/tool-mode-extension-sdk');
|
|
212
367
|
const discovery = discoverExtension(new URL('.', packageJsonPath).pathname.replace(/^\/([A-Za-z]:)/, '$1'));
|
|
213
368
|
if (discovery.entryPath === undefined) {
|
|
214
369
|
throw new ProviderBridgeError(`extension "${specifier}" declares no ${EXTENSION_ENTRY} entry, so it cannot provide model "${entry.id}"`);
|
|
215
370
|
}
|
|
216
|
-
return
|
|
371
|
+
return {
|
|
372
|
+
entryPath: discovery.entryPath,
|
|
373
|
+
manifestId: discovery.manifest.id,
|
|
374
|
+
manifestPath: discovery.manifestPath,
|
|
375
|
+
implementations: discovery.manifest.implementations
|
|
376
|
+
.filter((implementation) => typeof implementation.modelId === 'string')
|
|
377
|
+
.map(implementation => ({ id: implementation.id, modelId: implementation.modelId })),
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* Import an extension package's public entry.
|
|
382
|
+
*
|
|
383
|
+
* The specifier is resolved by Node like any other dependency, then the package's
|
|
384
|
+
* own discovery runs so the entry comes from its real `exports` map rather than
|
|
385
|
+
* from a host-side convention.
|
|
386
|
+
* @param specifier - the package name or path.
|
|
387
|
+
* @param entry - the catalog entry, for diagnostics.
|
|
388
|
+
* @returns the validated extension module.
|
|
389
|
+
* @throws ProviderBridgeError when the package cannot be resolved or has no entry.
|
|
390
|
+
*/
|
|
391
|
+
async function loadExtensionPackage(specifier, entry) {
|
|
392
|
+
const { entryPath } = await resolveExtensionPackage(specifier, entry);
|
|
393
|
+
return loadExtensionModule(entryPath);
|
|
217
394
|
}
|
|
218
395
|
//# sourceMappingURL=provider-bridge.js.map
|