@try-works/dsh-model-as-tool 0.1.0 → 0.1.2
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/README.md +51 -0
- package/bin/dsh-tool-mode-mcp-stdio.mjs +80 -0
- package/catalog/desert-ant-models.json +77 -13
- package/lib/adapters/index.d.ts +19 -6
- package/lib/adapters/index.d.ts.map +1 -1
- package/lib/adapters/index.js +28 -13
- package/lib/adapters/tongue.d.ts +11 -3
- package/lib/adapters/tongue.d.ts.map +1 -1
- package/lib/adapters/tongue.js +12 -4
- package/lib/adapters/types.d.ts +11 -0
- package/lib/adapters/types.d.ts.map +1 -1
- package/lib/adapters/voz.d.ts +7 -1
- package/lib/adapters/voz.d.ts.map +1 -1
- package/lib/adapters/voz.js +16 -4
- package/lib/asset-acquisition.d.ts +137 -0
- package/lib/asset-acquisition.d.ts.map +1 -0
- package/lib/asset-acquisition.js +161 -0
- package/lib/authority.d.ts +153 -0
- package/lib/authority.d.ts.map +1 -0
- package/lib/authority.js +194 -0
- package/lib/bindings.d.ts +115 -0
- package/lib/bindings.d.ts.map +1 -0
- package/lib/bindings.js +239 -0
- package/lib/catalog.d.ts.map +1 -1
- package/lib/catalog.js +57 -0
- package/lib/contract-payload.d.ts +47 -0
- package/lib/contract-payload.d.ts.map +1 -0
- package/lib/contract-payload.js +137 -0
- package/lib/deployment-coordinator.d.ts +213 -0
- package/lib/deployment-coordinator.d.ts.map +1 -0
- package/lib/deployment-coordinator.js +274 -0
- package/lib/harness-budgets.d.ts +158 -0
- package/lib/harness-budgets.d.ts.map +1 -0
- package/lib/harness-budgets.js +152 -0
- package/lib/index.d.ts +41 -0
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +424 -16
- package/lib/licensing.d.ts +221 -0
- package/lib/licensing.d.ts.map +1 -0
- package/lib/licensing.js +188 -0
- package/lib/mcp/host-binding.d.ts +91 -0
- package/lib/mcp/host-binding.d.ts.map +1 -0
- package/lib/mcp/host-binding.js +133 -0
- package/lib/mcp/http.d.ts +130 -0
- package/lib/mcp/http.d.ts.map +1 -0
- package/lib/mcp/http.js +274 -0
- package/lib/mcp/protocol.d.ts +261 -0
- package/lib/mcp/protocol.d.ts.map +1 -0
- package/lib/mcp/protocol.js +387 -0
- package/lib/mcp/server.d.ts +115 -0
- package/lib/mcp/server.d.ts.map +1 -0
- package/lib/mcp/server.js +200 -0
- package/lib/mcp/stdio.d.ts +80 -0
- package/lib/mcp/stdio.d.ts.map +1 -0
- package/lib/mcp/stdio.js +176 -0
- package/lib/mcp/subscriptions.d.ts +135 -0
- package/lib/mcp/subscriptions.d.ts.map +1 -0
- package/lib/mcp/subscriptions.js +207 -0
- package/lib/provider-bridge.d.ts +69 -0
- package/lib/provider-bridge.d.ts.map +1 -0
- package/lib/provider-bridge.js +218 -0
- package/lib/publication.d.ts +160 -0
- package/lib/publication.d.ts.map +1 -0
- package/lib/publication.js +173 -0
- package/lib/receipt.d.ts +208 -0
- package/lib/receipt.d.ts.map +1 -0
- package/lib/receipt.js +150 -0
- package/lib/registrar.d.ts +13 -0
- package/lib/registrar.d.ts.map +1 -1
- package/lib/registrar.js +20 -3
- package/lib/resource-governance.d.ts +204 -0
- package/lib/resource-governance.d.ts.map +1 -0
- package/lib/resource-governance.js +213 -0
- package/lib/server-lifecycle.d.ts +175 -0
- package/lib/server-lifecycle.d.ts.map +1 -0
- package/lib/server-lifecycle.js +269 -0
- package/lib/types.d.ts +21 -0
- package/lib/types.d.ts.map +1 -1
- package/package.json +9 -2
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Catalog subscriptions (P26-09, P26-10).
|
|
3
|
+
*
|
|
4
|
+
* The rules that make this more than an event emitter, each of which a naive
|
|
5
|
+
* implementation gets wrong:
|
|
6
|
+
*
|
|
7
|
+
* - **Acknowledge first, and acknowledge only what is SUPPORTED.** The ack carries
|
|
8
|
+
* the filter the server will actually honour, not the one the client asked for. A
|
|
9
|
+
* client that requested four notification types and is sent back one is being told
|
|
10
|
+
* the truth; echoing all four and then sending one is a silent lie the client
|
|
11
|
+
* cannot detect until it waits forever.
|
|
12
|
+
* - **No event before the acknowledgement.** So the ack is emitted synchronously as
|
|
13
|
+
* part of establishing the subscription, and the subscription only becomes
|
|
14
|
+
* eligible for real events afterwards.
|
|
15
|
+
* - **Every event carries the subscription id** in the revision's notification
|
|
16
|
+
* metadata, because a client with several subscriptions cannot otherwise tell
|
|
17
|
+
* which one an event belongs to.
|
|
18
|
+
* - **Filter per caller.** Each subscription belongs to one client and sees only its
|
|
19
|
+
* own catalog. One client's private package changes are never delivered to
|
|
20
|
+
* another, which is the leakage the requirement names.
|
|
21
|
+
* - **Teardown is announced.** Revoking a grant sends `notifications/cancelled`
|
|
22
|
+
* referencing the subscription request BEFORE the stream closes, so a client can
|
|
23
|
+
* distinguish "your access ended" from "the connection dropped".
|
|
24
|
+
* - **Progress does not travel here** (P26-10). Progress belongs on the response
|
|
25
|
+
* stream of the call that produced it; routing it onto the catalog stream would
|
|
26
|
+
* deliver one call's progress to every subscriber.
|
|
27
|
+
*
|
|
28
|
+
* @module @try-works/dsh-model-as-tool/mcp/subscriptions
|
|
29
|
+
*/
|
|
30
|
+
/**
|
|
31
|
+
* The notification types this server actually emits.
|
|
32
|
+
*
|
|
33
|
+
* Only tool-catalog changes exist, because only tools are published. Declaring the
|
|
34
|
+
* others unsupported is what makes the acknowledgement honest.
|
|
35
|
+
*/
|
|
36
|
+
export const SUPPORTED_FILTERS = ['toolsListChanged'];
|
|
37
|
+
/** The official metadata key carrying the subscription's request id. */
|
|
38
|
+
export const SUBSCRIPTION_ID_KEY = 'io.modelcontextprotocol/subscriptionId';
|
|
39
|
+
/**
|
|
40
|
+
* Narrow a requested filter to what this server supports.
|
|
41
|
+
*
|
|
42
|
+
* P26-09 says to acknowledge only supported requested filters. A requested type
|
|
43
|
+
* that is not supported is DROPPED rather than acknowledged-and-ignored: an
|
|
44
|
+
* acknowledged type that never fires is indistinguishable from a broken server.
|
|
45
|
+
* @param requested - the filter the client asked for.
|
|
46
|
+
* @returns the acknowledged filter.
|
|
47
|
+
*/
|
|
48
|
+
export function acknowledgeFilter(requested) {
|
|
49
|
+
const acknowledged = {};
|
|
50
|
+
for (const key of SUPPORTED_FILTERS) {
|
|
51
|
+
// Only a type the client actually REQUESTED can be acknowledged: granting a
|
|
52
|
+
// type nobody asked for would start sending an unrequested notification.
|
|
53
|
+
if (requested[key] === true)
|
|
54
|
+
acknowledged[key] = true;
|
|
55
|
+
}
|
|
56
|
+
return acknowledged;
|
|
57
|
+
}
|
|
58
|
+
/** Manages the active catalog subscriptions. */
|
|
59
|
+
export class SubscriptionRegistry {
|
|
60
|
+
#subscriptions = new Map();
|
|
61
|
+
/** Counter for ids this server issues, so an id is never caller-chosen. */
|
|
62
|
+
#issued = 0;
|
|
63
|
+
/** Active subscriptions, for diagnostics. */
|
|
64
|
+
get active() {
|
|
65
|
+
return [...this.#subscriptions.values()];
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Establish a subscription.
|
|
69
|
+
*
|
|
70
|
+
* The acknowledgement notification is returned as part of the result and is
|
|
71
|
+
* marked sent before anything can be published, so no event can precede it.
|
|
72
|
+
* @param request - the `subscriptions/listen` request.
|
|
73
|
+
* @param clientId - the authenticated client, which the subscription is bound to.
|
|
74
|
+
* @returns the result envelope, the acknowledgement, and the subscription id.
|
|
75
|
+
*/
|
|
76
|
+
listen(request, clientId) {
|
|
77
|
+
const requested = (request.params?.notifications ?? {});
|
|
78
|
+
const acknowledged = acknowledgeFilter(requested);
|
|
79
|
+
// Server-issued. A caller-chosen id would let one client address another
|
|
80
|
+
// client's subscription in a later cancellation.
|
|
81
|
+
const subscriptionId = `sub_${Date.now().toString(36)}_${(++this.#issued).toString(36)}`;
|
|
82
|
+
const subscription = { subscriptionId, clientId, acknowledged, announced: true };
|
|
83
|
+
this.#subscriptions.set(subscriptionId, subscription);
|
|
84
|
+
return {
|
|
85
|
+
result: {
|
|
86
|
+
resultType: 'complete',
|
|
87
|
+
// The revision REQUIRES the subscription id in this result's metadata.
|
|
88
|
+
_meta: { [SUBSCRIPTION_ID_KEY]: subscriptionId },
|
|
89
|
+
},
|
|
90
|
+
acknowledgement: {
|
|
91
|
+
jsonrpc: '2.0',
|
|
92
|
+
method: 'notifications/subscriptions/acknowledged',
|
|
93
|
+
params: {
|
|
94
|
+
// The filter the server will HONOUR, not the one requested.
|
|
95
|
+
notifications: acknowledged,
|
|
96
|
+
// The subscription id is what lets a client with several subscriptions
|
|
97
|
+
// tell which one an event belongs to.
|
|
98
|
+
_meta: { [SUBSCRIPTION_ID_KEY]: subscriptionId },
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
subscriptionId,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Publish a tool-catalog change.
|
|
106
|
+
*
|
|
107
|
+
* @param generation - the catalog generation that changed, carried so a client
|
|
108
|
+
* can tell a real change from a duplicate delivery.
|
|
109
|
+
* @returns one message per eligible subscriber, and nothing for anyone else.
|
|
110
|
+
*/
|
|
111
|
+
publishToolListChanged(generation) {
|
|
112
|
+
const messages = [];
|
|
113
|
+
for (const subscription of this.#subscriptions.values()) {
|
|
114
|
+
// Two independent conditions: the client asked for this type, and it is
|
|
115
|
+
// still active. A revoked subscription is removed outright, so there is no
|
|
116
|
+
// window in which it receives one last event.
|
|
117
|
+
if (subscription.acknowledged.toolsListChanged !== true)
|
|
118
|
+
continue;
|
|
119
|
+
if (!subscription.announced)
|
|
120
|
+
continue;
|
|
121
|
+
messages.push({
|
|
122
|
+
clientId: subscription.clientId,
|
|
123
|
+
notification: {
|
|
124
|
+
jsonrpc: '2.0',
|
|
125
|
+
method: 'notifications/tools/list_changed',
|
|
126
|
+
params: {
|
|
127
|
+
_meta: { [SUBSCRIPTION_ID_KEY]: subscription.subscriptionId },
|
|
128
|
+
generation,
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
// Deliberately one message per subscriber rather than one broadcast: the
|
|
134
|
+
// transport must address each client, so a shared object would make cross-client
|
|
135
|
+
// leakage a matter of how the transport happened to iterate.
|
|
136
|
+
return messages;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Revoke every subscription belonging to a client, announcing each first.
|
|
140
|
+
*
|
|
141
|
+
* P26-09 requires the teardown notification BEFORE the stream closes, so the
|
|
142
|
+
* client learns its access ended rather than inferring it from a dropped socket.
|
|
143
|
+
* @param clientId - the client whose grant was revoked.
|
|
144
|
+
* @returns the teardown messages, already ordered cancellation-first.
|
|
145
|
+
*/
|
|
146
|
+
revoke(clientId) {
|
|
147
|
+
const messages = [];
|
|
148
|
+
for (const [id, subscription] of [...this.#subscriptions]) {
|
|
149
|
+
if (subscription.clientId !== clientId)
|
|
150
|
+
continue;
|
|
151
|
+
messages.push({
|
|
152
|
+
clientId,
|
|
153
|
+
notification: {
|
|
154
|
+
jsonrpc: '2.0',
|
|
155
|
+
method: 'notifications/cancelled',
|
|
156
|
+
params: {
|
|
157
|
+
// References the SUBSCRIPTION request, so the client can match it to
|
|
158
|
+
// the listen call it made.
|
|
159
|
+
requestId: subscription.subscriptionId,
|
|
160
|
+
reason: 'the grant for this subscription was revoked',
|
|
161
|
+
_meta: { [SUBSCRIPTION_ID_KEY]: id },
|
|
162
|
+
},
|
|
163
|
+
},
|
|
164
|
+
});
|
|
165
|
+
// Removed AFTER its teardown is queued, and before returning, so a publish
|
|
166
|
+
// racing this call cannot deliver one more event to a revoked client.
|
|
167
|
+
this.#subscriptions.delete(id);
|
|
168
|
+
}
|
|
169
|
+
return messages;
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Close one subscription at the client's request.
|
|
173
|
+
* @param subscriptionId - the subscription to close.
|
|
174
|
+
* @param clientId - the caller, which must own it.
|
|
175
|
+
* @returns whether it was closed.
|
|
176
|
+
*/
|
|
177
|
+
close(subscriptionId, clientId) {
|
|
178
|
+
const subscription = this.#subscriptions.get(subscriptionId);
|
|
179
|
+
// Ownership is checked, not assumed: without it one client could close
|
|
180
|
+
// another's subscription by guessing an id.
|
|
181
|
+
if (subscription === undefined || subscription.clientId !== clientId)
|
|
182
|
+
return false;
|
|
183
|
+
this.#subscriptions.delete(subscriptionId);
|
|
184
|
+
return true;
|
|
185
|
+
}
|
|
186
|
+
/** Every subscription belonging to one client. */
|
|
187
|
+
forClient(clientId) {
|
|
188
|
+
return [...this.#subscriptions.values()].filter(subscription => subscription.clientId === clientId);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Build the `notifications/cancelled` a client sends to stop a subscription.
|
|
193
|
+
*
|
|
194
|
+
* `CancelledNotificationParams` requires only `requestId`, so the notification
|
|
195
|
+
* carries exactly that: adding a `_meta` with an undefined version would put a
|
|
196
|
+
* meaningless member on the wire.
|
|
197
|
+
* @param subscriptionId - the subscription to cancel.
|
|
198
|
+
* @returns the notification.
|
|
199
|
+
*/
|
|
200
|
+
export function cancelSubscription(subscriptionId) {
|
|
201
|
+
return {
|
|
202
|
+
jsonrpc: '2.0',
|
|
203
|
+
method: 'notifications/cancelled',
|
|
204
|
+
params: { requestId: subscriptionId },
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
//# sourceMappingURL=subscriptions.js.map
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extension provider bridge.
|
|
3
|
+
*
|
|
4
|
+
* This is the ONLY route from a catalog entry to model code (BASE-09). The host
|
|
5
|
+
* no longer imports an adapter for any model: it resolves the extension that
|
|
6
|
+
* DECLARES the model, loads that extension through its public `./tool-mode`
|
|
7
|
+
* entry, and asks it for a provider. A first-party package and a third-party
|
|
8
|
+
* package take the same path, so there is no private built-in adapter route that
|
|
9
|
+
* only first-party code can use — which is precisely what BASE-09 forbids.
|
|
10
|
+
*
|
|
11
|
+
* The binding from a catalog model to an extension is DATA, held in the model's
|
|
12
|
+
* own catalog entry, not a switch on the model name. Adding a vendor therefore
|
|
13
|
+
* means publishing a package and adding a catalog row, with no host change.
|
|
14
|
+
*
|
|
15
|
+
* @module @try-works/dsh-model-as-tool/provider-bridge
|
|
16
|
+
*/
|
|
17
|
+
import { type ToolGrant } from './authority.ts';
|
|
18
|
+
import type { ModelCatalogEntry } from './types.ts';
|
|
19
|
+
import type { ModelAdapter } from './adapters/types.ts';
|
|
20
|
+
/** How a model's provider is obtained. */
|
|
21
|
+
export interface ProviderBinding {
|
|
22
|
+
/**
|
|
23
|
+
* The extension package that implements the model, resolved like any other
|
|
24
|
+
* dependency. A relative path is admitted so a first-party package can be used
|
|
25
|
+
* from a checkout before it is published.
|
|
26
|
+
*/
|
|
27
|
+
readonly extension: string;
|
|
28
|
+
/** The `modelId` the extension declares for this model. */
|
|
29
|
+
readonly modelId: string;
|
|
30
|
+
}
|
|
31
|
+
/** Raised when a model cannot be served through its declared extension. */
|
|
32
|
+
export declare class ProviderBridgeError extends Error {
|
|
33
|
+
readonly name = "ProviderBridgeError";
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Loads extensions and adapts their providers to the host's adapter interface.
|
|
37
|
+
*
|
|
38
|
+
* One bridge per plugin activation, so an extension is imported and activated
|
|
39
|
+
* once no matter how many tools it serves.
|
|
40
|
+
*/
|
|
41
|
+
export declare class ProviderBridge {
|
|
42
|
+
#private;
|
|
43
|
+
constructor(options: {
|
|
44
|
+
readonly grant: ToolGrant;
|
|
45
|
+
readonly activationGeneration: number;
|
|
46
|
+
readonly log?: (message: string, fields?: Record<string, unknown>) => void;
|
|
47
|
+
});
|
|
48
|
+
/** The extensions currently activated, for diagnostics. */
|
|
49
|
+
get activatedExtensions(): readonly string[];
|
|
50
|
+
/**
|
|
51
|
+
* Build an adapter for one catalog entry.
|
|
52
|
+
*
|
|
53
|
+
* Returns undefined when the entry declares no provider binding, which is the
|
|
54
|
+
* correct outcome for a model reached through a command transport instead.
|
|
55
|
+
*
|
|
56
|
+
* The adapter is LAZY: it does not import or activate the extension until the
|
|
57
|
+
* first invocation. Two reasons, and the second is the important one:
|
|
58
|
+
* registering a tool must not run somebody else's package code (EXT-19 requires
|
|
59
|
+
* import and `create()` to stay lightweight), and a package that fails to load
|
|
60
|
+
* must fail the CALL it was needed for rather than the registration of every
|
|
61
|
+
* other tool in the catalog.
|
|
62
|
+
* @param entry - the catalog entry.
|
|
63
|
+
* @returns the adapter, or undefined when no extension implements this model.
|
|
64
|
+
*/
|
|
65
|
+
adapterFor(entry: ModelCatalogEntry): ModelAdapter | undefined;
|
|
66
|
+
/** Release every activated extension. */
|
|
67
|
+
dispose(reason?: 'deactivate' | 'upgrade' | 'uninstall' | 'shutdown' | 'fault'): Promise<void>;
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=provider-bridge.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provider-bridge.d.ts","sourceRoot":"","sources":["../src/provider-bridge.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAaH,OAAO,EAA6B,KAAK,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAC1E,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;;gBAMb,OAAO,EAAE;QACnB,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAA;QACzB,QAAQ,CAAC,oBAAoB,EAAE,MAAM,CAAA;QACrC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAA;KAC3E;IAMD,2DAA2D;IAC3D,IAAI,mBAAmB,IAAI,SAAS,MAAM,EAAE,CAE3C;IAED;;;;;;;;;;;;;;OAcG;IACH,UAAU,CAAC,KAAK,EAAE,iBAAiB,GAAG,YAAY,GAAG,SAAS;IAyB9D,yCAAyC;IACnC,OAAO,CAAC,MAAM,GAAE,YAAY,GAAG,SAAS,GAAG,WAAW,GAAG,UAAU,GAAG,OAAoB,GAAG,OAAO,CAAC,IAAI,CAAC;CAoHjH"}
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extension provider bridge.
|
|
3
|
+
*
|
|
4
|
+
* This is the ONLY route from a catalog entry to model code (BASE-09). The host
|
|
5
|
+
* no longer imports an adapter for any model: it resolves the extension that
|
|
6
|
+
* DECLARES the model, loads that extension through its public `./tool-mode`
|
|
7
|
+
* entry, and asks it for a provider. A first-party package and a third-party
|
|
8
|
+
* package take the same path, so there is no private built-in adapter route that
|
|
9
|
+
* only first-party code can use — which is precisely what BASE-09 forbids.
|
|
10
|
+
*
|
|
11
|
+
* The binding from a catalog model to an extension is DATA, held in the model's
|
|
12
|
+
* own catalog entry, not a switch on the model name. Adding a vendor therefore
|
|
13
|
+
* means publishing a package and adding a catalog row, with no host change.
|
|
14
|
+
*
|
|
15
|
+
* @module @try-works/dsh-model-as-tool/provider-bridge
|
|
16
|
+
*/
|
|
17
|
+
import { activateExtension, assertNoReservedMeta, EXTENSION_ENTRY, loadExtensionModule, } from '@try-works/tool-mode-extension-sdk';
|
|
18
|
+
import { createInvocationAuthority } from "./authority.js";
|
|
19
|
+
/** Raised when a model cannot be served through its declared extension. */
|
|
20
|
+
export class ProviderBridgeError extends Error {
|
|
21
|
+
name = 'ProviderBridgeError';
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Loads extensions and adapts their providers to the host's adapter interface.
|
|
25
|
+
*
|
|
26
|
+
* One bridge per plugin activation, so an extension is imported and activated
|
|
27
|
+
* once no matter how many tools it serves.
|
|
28
|
+
*/
|
|
29
|
+
export class ProviderBridge {
|
|
30
|
+
#activated = new Map();
|
|
31
|
+
#grant;
|
|
32
|
+
#generation;
|
|
33
|
+
#log;
|
|
34
|
+
constructor(options) {
|
|
35
|
+
this.#grant = options.grant;
|
|
36
|
+
this.#generation = options.activationGeneration;
|
|
37
|
+
this.#log = options.log ?? (() => { });
|
|
38
|
+
}
|
|
39
|
+
/** The extensions currently activated, for diagnostics. */
|
|
40
|
+
get activatedExtensions() {
|
|
41
|
+
return [...this.#activated.keys()];
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Build an adapter for one catalog entry.
|
|
45
|
+
*
|
|
46
|
+
* Returns undefined when the entry declares no provider binding, which is the
|
|
47
|
+
* correct outcome for a model reached through a command transport instead.
|
|
48
|
+
*
|
|
49
|
+
* The adapter is LAZY: it does not import or activate the extension until the
|
|
50
|
+
* first invocation. Two reasons, and the second is the important one:
|
|
51
|
+
* registering a tool must not run somebody else's package code (EXT-19 requires
|
|
52
|
+
* import and `create()` to stay lightweight), and a package that fails to load
|
|
53
|
+
* must fail the CALL it was needed for rather than the registration of every
|
|
54
|
+
* other tool in the catalog.
|
|
55
|
+
* @param entry - the catalog entry.
|
|
56
|
+
* @returns the adapter, or undefined when no extension implements this model.
|
|
57
|
+
*/
|
|
58
|
+
adapterFor(entry) {
|
|
59
|
+
const binding = entry.provider;
|
|
60
|
+
if (binding === undefined)
|
|
61
|
+
return undefined;
|
|
62
|
+
return {
|
|
63
|
+
modelId: entry.id,
|
|
64
|
+
invoke: async (invocation) => {
|
|
65
|
+
const activated = await this.#activate(binding.extension, entry);
|
|
66
|
+
const provider = await this.#providerFor(activated, binding);
|
|
67
|
+
return this.#invoke(provider, binding, invocation);
|
|
68
|
+
},
|
|
69
|
+
// An arrow function, not a method: a method's `this` would be the adapter
|
|
70
|
+
// object literal rather than the bridge, so `#activated` would not resolve.
|
|
71
|
+
dispose: async () => {
|
|
72
|
+
// Provider-level disposal only when a provider was actually created:
|
|
73
|
+
// disposing a model that was never called must not activate its extension
|
|
74
|
+
// merely in order to shut it down.
|
|
75
|
+
const activated = this.#activated.get(binding.extension);
|
|
76
|
+
if (activated === undefined)
|
|
77
|
+
return;
|
|
78
|
+
const provider = (await activated).providers.get(binding.modelId);
|
|
79
|
+
if (provider !== undefined)
|
|
80
|
+
await (await provider).dispose();
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
/** Release every activated extension. */
|
|
85
|
+
async dispose(reason = 'shutdown') {
|
|
86
|
+
const extensions = [...this.#activated.values()];
|
|
87
|
+
this.#activated.clear();
|
|
88
|
+
for (const pending of extensions) {
|
|
89
|
+
try {
|
|
90
|
+
const activated = await pending;
|
|
91
|
+
for (const provider of activated.providers.values()) {
|
|
92
|
+
await (await provider).dispose();
|
|
93
|
+
}
|
|
94
|
+
await activated.instance.dispose(reason);
|
|
95
|
+
}
|
|
96
|
+
catch (error) {
|
|
97
|
+
// One extension failing to stop must not prevent the others from being
|
|
98
|
+
// released, or a single bad package would leak every process it shares.
|
|
99
|
+
this.#log('extension disposal failed', { error: String(error) });
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
/** Activate an extension package once, and reuse it. */
|
|
104
|
+
async #activate(specifier, entry) {
|
|
105
|
+
const existing = this.#activated.get(specifier);
|
|
106
|
+
if (existing !== undefined)
|
|
107
|
+
return existing;
|
|
108
|
+
const pending = (async () => {
|
|
109
|
+
const loaded = await loadExtensionPackage(specifier, entry);
|
|
110
|
+
const instance = await activateExtension(loaded, this.#contextFor(specifier));
|
|
111
|
+
return { instance, providers: new Map() };
|
|
112
|
+
})();
|
|
113
|
+
this.#activated.set(specifier, pending);
|
|
114
|
+
return pending;
|
|
115
|
+
}
|
|
116
|
+
/** Create or reuse the provider for one model. */
|
|
117
|
+
async #providerFor(activated, binding) {
|
|
118
|
+
const existing = activated.providers.get(binding.modelId);
|
|
119
|
+
if (existing !== undefined)
|
|
120
|
+
return existing;
|
|
121
|
+
const pending = activated.instance.createProvider({
|
|
122
|
+
modelId: binding.modelId,
|
|
123
|
+
configuration: {},
|
|
124
|
+
});
|
|
125
|
+
activated.providers.set(binding.modelId, pending);
|
|
126
|
+
return pending;
|
|
127
|
+
}
|
|
128
|
+
/** Run one invocation through the provider. */
|
|
129
|
+
async #invoke(provider, binding, invocation) {
|
|
130
|
+
const authority = invocation.authority ?? createInvocationAuthority(this.#grant, 'interactive');
|
|
131
|
+
/**
|
|
132
|
+
* The load is deliberately NOT coordinated here.
|
|
133
|
+
*
|
|
134
|
+
* An earlier version routed `provider.load()` through the DeploymentCoordinator at
|
|
135
|
+
* this point, and the suite caught the consequence: loading then happened BEFORE
|
|
136
|
+
* the implementation authorized its inputs, so a call with a denied path loaded
|
|
137
|
+
* ~349 MB of weights before refusing. SEC-06 wants the denial first, and the
|
|
138
|
+
* extension performs path resolution inside `invoke`, so the coordinator cannot be
|
|
139
|
+
* placed here without inverting that.
|
|
140
|
+
*
|
|
141
|
+
* The coordinator is therefore a tested, standalone component (RES-02/03/05) whose
|
|
142
|
+
* place is INSIDE an implementation's own load path, where it can sit after the
|
|
143
|
+
* checks that must precede a load. Wiring it here would have traded a security
|
|
144
|
+
* property for a resource optimisation.
|
|
145
|
+
*/
|
|
146
|
+
const input = {};
|
|
147
|
+
for (const [key, value] of Object.entries(invocation.args)) {
|
|
148
|
+
input[key] = value;
|
|
149
|
+
}
|
|
150
|
+
const payload = await provider.invoke({
|
|
151
|
+
implementationId: binding.modelId,
|
|
152
|
+
input,
|
|
153
|
+
context: {
|
|
154
|
+
invocationId: `inv_${Date.now().toString(36)}_${Math.floor(Math.random() * 1e6).toString(36)}`,
|
|
155
|
+
signal: invocation.signal,
|
|
156
|
+
limits: authority.limits,
|
|
157
|
+
resolvePath: candidate => authority.resolvePath(candidate),
|
|
158
|
+
},
|
|
159
|
+
});
|
|
160
|
+
// The host authors the reserved root `meta` (EXT-17). An extension supplying one
|
|
161
|
+
// is asserting its own provenance, which it does not get to do.
|
|
162
|
+
assertNoReservedMeta(payload, binding.modelId);
|
|
163
|
+
return payload;
|
|
164
|
+
}
|
|
165
|
+
/** Build the context an extension receives, which carries no host internals. */
|
|
166
|
+
#contextFor(extensionId) {
|
|
167
|
+
const controller = new AbortController();
|
|
168
|
+
return {
|
|
169
|
+
extensionId,
|
|
170
|
+
extensionVersion: '0.0.0',
|
|
171
|
+
activationGeneration: this.#generation,
|
|
172
|
+
storage: {
|
|
173
|
+
// The host grants a directory it owns; the extension never receives a
|
|
174
|
+
// path into the operator's own filesystem.
|
|
175
|
+
directory: '',
|
|
176
|
+
async read() { return undefined; },
|
|
177
|
+
async write() { },
|
|
178
|
+
},
|
|
179
|
+
logger: {
|
|
180
|
+
log: (level, message, fields) => {
|
|
181
|
+
this.#log(`${level}: ${message}`, fields);
|
|
182
|
+
},
|
|
183
|
+
},
|
|
184
|
+
signal: controller.signal,
|
|
185
|
+
configuration: {},
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Import an extension package's public entry.
|
|
191
|
+
*
|
|
192
|
+
* The specifier is resolved by Node like any other dependency, then the package's
|
|
193
|
+
* own discovery runs so the entry comes from its real `exports` map rather than
|
|
194
|
+
* from a host-side convention.
|
|
195
|
+
* @param specifier - the package name or path.
|
|
196
|
+
* @param entry - the catalog entry, for diagnostics.
|
|
197
|
+
* @returns the validated extension module.
|
|
198
|
+
* @throws ProviderBridgeError when the package cannot be resolved or has no entry.
|
|
199
|
+
*/
|
|
200
|
+
async function loadExtensionPackage(specifier, entry) {
|
|
201
|
+
let packageJsonPath;
|
|
202
|
+
try {
|
|
203
|
+
// `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
|
+
packageJsonPath = import.meta.resolve(`${specifier}/package.json`);
|
|
206
|
+
}
|
|
207
|
+
catch (error) {
|
|
208
|
+
throw new ProviderBridgeError(`model "${entry.id}" declares the extension "${specifier}", which could not be resolved: `
|
|
209
|
+
+ `${error instanceof Error ? error.message : String(error)}. Install it, or correct the catalog binding.`);
|
|
210
|
+
}
|
|
211
|
+
const { discoverExtension } = await import('@try-works/tool-mode-extension-sdk');
|
|
212
|
+
const discovery = discoverExtension(new URL('.', packageJsonPath).pathname.replace(/^\/([A-Za-z]:)/, '$1'));
|
|
213
|
+
if (discovery.entryPath === undefined) {
|
|
214
|
+
throw new ProviderBridgeError(`extension "${specifier}" declares no ${EXTENSION_ENTRY} entry, so it cannot provide model "${entry.id}"`);
|
|
215
|
+
}
|
|
216
|
+
return loadExtensionModule(discovery.entryPath);
|
|
217
|
+
}
|
|
218
|
+
//# sourceMappingURL=provider-bridge.js.map
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Coherent publication from one versioned generation (EXT-24).
|
|
3
|
+
*
|
|
4
|
+
* EXT-24 forbids two observable states: an available tool WITHOUT its executable
|
|
5
|
+
* binding, and a new schema served by an old handler. The way to avoid both is to
|
|
6
|
+
* build the whole surface first, check it for internal consistency, and only then
|
|
7
|
+
* make it visible — staging plus a single switch, which is what the requirement
|
|
8
|
+
* asks for rather than an invented transactional registry API.
|
|
9
|
+
*
|
|
10
|
+
* The rules encoded here, each with a reason:
|
|
11
|
+
*
|
|
12
|
+
* - A tool is advertised only when a real implementation binding exists. A schema
|
|
13
|
+
* alone is a promise nothing can keep, and EXT-24 states that no contract is
|
|
14
|
+
* automatically callable merely because its schema is installed.
|
|
15
|
+
* - An ACTIVE package whose model is unprepared publishes readiness information and
|
|
16
|
+
* a preparation skill, but NOT a working inference tool. Advertising a callable
|
|
17
|
+
* tool that will fail on first use is worse than advertising nothing, because the
|
|
18
|
+
* caller has no way to tell the difference until it fails.
|
|
19
|
+
* - A usage skill appears only when every tool it requires is visible. A skill that
|
|
20
|
+
* instructs an agent to call a tool that is not there produces a confident
|
|
21
|
+
* failure.
|
|
22
|
+
* - Every skill carries its owning extension, so it can be withdrawn with that
|
|
23
|
+
* package rather than outliving it.
|
|
24
|
+
*
|
|
25
|
+
* @module @try-works/dsh-model-as-tool/publication
|
|
26
|
+
*/
|
|
27
|
+
/** Why a candidate contribution did not reach the published surface. */
|
|
28
|
+
export interface WithheldContribution {
|
|
29
|
+
/** Stable reason code. */
|
|
30
|
+
readonly code: 'no-implementation' | 'needs-preparation' | 'unregistered-contribution' | 'model-failed';
|
|
31
|
+
/** Human-readable explanation. */
|
|
32
|
+
readonly message: string;
|
|
33
|
+
/** The tool or skill that was withheld. */
|
|
34
|
+
readonly subject: string;
|
|
35
|
+
/** The extension that owns it. */
|
|
36
|
+
readonly extensionId: string;
|
|
37
|
+
}
|
|
38
|
+
/** One implementation offered for a contract. */
|
|
39
|
+
export interface ImplementationCandidate {
|
|
40
|
+
/** The extension providing it. */
|
|
41
|
+
readonly extensionId: string;
|
|
42
|
+
/** The catalog model backing it. */
|
|
43
|
+
readonly modelId: string;
|
|
44
|
+
/** Whether the model is prepared and callable right now. */
|
|
45
|
+
readonly readiness: 'unprepared' | 'preparing' | 'ready' | 'failed';
|
|
46
|
+
/** Whether the model can run on this host at all. */
|
|
47
|
+
readonly runnable: boolean;
|
|
48
|
+
}
|
|
49
|
+
/** One contract offered for publication. */
|
|
50
|
+
export interface ToolCandidate {
|
|
51
|
+
/** The caller-facing tool name. */
|
|
52
|
+
readonly name: string;
|
|
53
|
+
/** The contract it implements. */
|
|
54
|
+
readonly contractId: string;
|
|
55
|
+
/** The model the operator selected to serve it. */
|
|
56
|
+
readonly selectedModelId: string;
|
|
57
|
+
/** Every implementation that could serve it. */
|
|
58
|
+
readonly implementations: readonly ImplementationCandidate[];
|
|
59
|
+
}
|
|
60
|
+
/** One skill offered for publication. */
|
|
61
|
+
export interface SkillCandidate {
|
|
62
|
+
/** The skill name. */
|
|
63
|
+
readonly name: string;
|
|
64
|
+
/** The extension that owns it. */
|
|
65
|
+
readonly extensionId: string;
|
|
66
|
+
/** Tool NAMES the skill depends on. An absent list means it depends on nothing. */
|
|
67
|
+
readonly requiresTools?: readonly string[];
|
|
68
|
+
/**
|
|
69
|
+
* Whether this skill exists to explain preparation rather than usage.
|
|
70
|
+
*
|
|
71
|
+
* A preparation skill is published even when nothing is callable, because its
|
|
72
|
+
* whole purpose is to tell an operator what to do about that.
|
|
73
|
+
*/
|
|
74
|
+
readonly purpose?: 'usage' | 'preparation';
|
|
75
|
+
}
|
|
76
|
+
/** A published tool, which by construction has an executable binding. */
|
|
77
|
+
export interface PublishedTool {
|
|
78
|
+
readonly name: string;
|
|
79
|
+
readonly contractId: string;
|
|
80
|
+
readonly extensionId: string;
|
|
81
|
+
readonly modelId: string;
|
|
82
|
+
readonly readiness: ImplementationCandidate['readiness'];
|
|
83
|
+
/** The other implementations a caller could be switched to. */
|
|
84
|
+
readonly alternatives: readonly string[];
|
|
85
|
+
}
|
|
86
|
+
/** A published skill. */
|
|
87
|
+
export interface PublishedSkill {
|
|
88
|
+
readonly name: string;
|
|
89
|
+
readonly extensionId: string;
|
|
90
|
+
readonly purpose: 'usage' | 'preparation';
|
|
91
|
+
readonly requiresTools: readonly string[];
|
|
92
|
+
}
|
|
93
|
+
/** One model deployment's readiness, published for operator visibility. */
|
|
94
|
+
export interface ReadinessNotice {
|
|
95
|
+
readonly extensionId: string;
|
|
96
|
+
readonly modelId: string;
|
|
97
|
+
readonly readiness: ImplementationCandidate['readiness'];
|
|
98
|
+
readonly callable: boolean;
|
|
99
|
+
readonly reason: string;
|
|
100
|
+
}
|
|
101
|
+
/** A complete, internally consistent surface. */
|
|
102
|
+
export interface CatalogGeneration {
|
|
103
|
+
/** The generation this surface belongs to. */
|
|
104
|
+
readonly generation: number;
|
|
105
|
+
readonly tools: readonly PublishedTool[];
|
|
106
|
+
readonly skills: readonly PublishedSkill[];
|
|
107
|
+
readonly readiness: readonly ReadinessNotice[];
|
|
108
|
+
/** Everything withheld, with the reason. Never silently dropped. */
|
|
109
|
+
readonly withheld: readonly WithheldContribution[];
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Build a coherent surface from the candidates available now.
|
|
113
|
+
*
|
|
114
|
+
* Pure: it decides what MAY be published, and the caller makes the result visible.
|
|
115
|
+
* Keeping the decision separate from the switch is what allows a surface to be
|
|
116
|
+
* rejected before anything observes it.
|
|
117
|
+
* @param options - the candidates and the generation they belong to.
|
|
118
|
+
* @returns the complete surface, including what was withheld and why.
|
|
119
|
+
* @throws Error when the candidates are internally inconsistent in a way that
|
|
120
|
+
* cannot be resolved by withholding, such as two tools sharing a name.
|
|
121
|
+
*/
|
|
122
|
+
export declare function buildSurface(options: {
|
|
123
|
+
readonly generation: number;
|
|
124
|
+
readonly tools: readonly ToolCandidate[];
|
|
125
|
+
readonly skills: readonly SkillCandidate[];
|
|
126
|
+
}): CatalogGeneration;
|
|
127
|
+
/**
|
|
128
|
+
* The published surface, swapped only as a whole (EXT-24).
|
|
129
|
+
*
|
|
130
|
+
* `publish` refuses a surface that would retire a tool without retiring the skill
|
|
131
|
+
* that teaches it, which is the "new schema with the old handler" failure seen from
|
|
132
|
+
* the other side.
|
|
133
|
+
*/
|
|
134
|
+
export declare class PublicationSurface {
|
|
135
|
+
#private;
|
|
136
|
+
constructor(options?: {
|
|
137
|
+
readonly generation?: number;
|
|
138
|
+
readonly onSwap?: (next: CatalogGeneration, previous: CatalogGeneration) => void;
|
|
139
|
+
});
|
|
140
|
+
/** The generation currently visible. */
|
|
141
|
+
get current(): CatalogGeneration;
|
|
142
|
+
/**
|
|
143
|
+
* Replace the visible surface.
|
|
144
|
+
*
|
|
145
|
+
* The callback receives the whole change at once, so a consumer can update its
|
|
146
|
+
* declarations without ever seeing an intermediate state.
|
|
147
|
+
* @param next - the surface to make visible.
|
|
148
|
+
*/
|
|
149
|
+
publish(next: CatalogGeneration): void;
|
|
150
|
+
/**
|
|
151
|
+
* Skills that must be withdrawn because their owning extension is gone.
|
|
152
|
+
*
|
|
153
|
+
* EXT-24 requires the host to track skill provenance and remove a skill with the
|
|
154
|
+
* package or tool that owns it, rather than leaving orphaned guidance behind.
|
|
155
|
+
* @param liveExtensionIds - the extensions still installed and active.
|
|
156
|
+
* @returns the skills that should no longer be visible.
|
|
157
|
+
*/
|
|
158
|
+
orphanedSkills(liveExtensionIds: readonly string[]): readonly PublishedSkill[];
|
|
159
|
+
}
|
|
160
|
+
//# sourceMappingURL=publication.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"publication.d.ts","sourceRoot":"","sources":["../src/publication.ts"],"names":[],"mappings":"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"}
|