@independo/inderun-web 0.3.0-dev.4 → 0.3.0-dev.5
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/dist/core/engine.d.ts +12 -7
- package/dist/core/engine.d.ts.map +1 -1
- package/dist/core/engine.js +27 -11
- package/dist/core/provider.d.ts +19 -0
- package/dist/core/provider.d.ts.map +1 -1
- package/dist/core/route-planner.d.ts +6 -1
- package/dist/core/route-planner.d.ts.map +1 -1
- package/dist/core/route-planner.js +29 -5
- package/dist/core/router.d.ts +36 -2
- package/dist/core/router.d.ts.map +1 -1
- package/dist/core/router.js +158 -45
- package/package.json +3 -3
package/dist/core/engine.d.ts
CHANGED
|
@@ -54,13 +54,18 @@ export declare class IndeRun implements IndeRunApi {
|
|
|
54
54
|
/**
|
|
55
55
|
* Orchestrates a Mode 2 streaming execution of a TaskRequest.
|
|
56
56
|
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
* is
|
|
57
|
+
* Routes through the same planner as run(), asking it for a `stream` route: the
|
|
58
|
+
* planner rejects providers that do not declare `supports.streaming` or whose
|
|
59
|
+
* dynamic capability snapshot says they cannot stream here, and reports why. The
|
|
60
|
+
* whole returned chain is therefore mode-compatible and subject to the same
|
|
61
|
+
* privacy, locality, availability, and policy constraints as a Mode 1 route.
|
|
62
|
+
*
|
|
63
|
+
* Fallback to the next provider is allowed only before the first content event
|
|
64
|
+
* has been admitted for the run; once one has been delivered, the run is
|
|
65
|
+
* committed to that provider and any later provider failure becomes a terminal
|
|
66
|
+
* error outcome rather than a silent provider swap — partial output spliced from
|
|
67
|
+
* two providers is undetectable to the caller. Cancellation always forecloses
|
|
68
|
+
* fallback, regardless of commit state, and is idempotent.
|
|
64
69
|
* @param request - The canonical task request containing prompts, tasks, and constraints.
|
|
65
70
|
* @throws {IndeRunException} If validation fails or no eligible streaming provider exists.
|
|
66
71
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../../src/core/engine.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,WAAW,EAChB,KAAK,eAAe,EAEpB,KAAK,WAAW,EAChB,KAAK,UAAU,EAChB,MAAM,8BAA8B,CAAC;AACtC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAC9C,OAAO,KAAK,EAAmB,0BAA0B,EAAE,MAAM,eAAe,CAAC;AACjF,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AAC7D,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAQjD,OAAO,EAAuB,KAAK,gBAAgB,EAAwB,MAAM,gBAAgB,CAAC;AAGlG;;;;;GAKG;AACH,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,eAAe,CAAC;IACxB,MAAM,EAAE,aAAa,CAAC,WAAW,CAAC,CAAC;IACnC,MAAM,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC/B;AAED;;;;GAIG;AACH,qBAAa,OAAQ,YAAW,UAAU;IAWtC,OAAO,CAAC,QAAQ;IAChB,OAAO,CAAC,YAAY;IAXtB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,gBAAgB,CAAmB;IAE3C;;;;;OAKG;gBAEO,QAAQ,EAAE,gBAAgB,EAC1B,YAAY,EAAE,YAAY,EAClC,gBAAgB,CAAC,EAAE,gBAAgB;IAOrC;;;OAGG;IACH,OAAO,CAAC,QAAQ;IAShB;;;OAGG;IACH,OAAO,CAAC,gBAAgB;IAoBxB;;;;;;;OAOG;IACG,GAAG,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC;IAuIpD
|
|
1
|
+
{"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../../src/core/engine.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,WAAW,EAChB,KAAK,eAAe,EAEpB,KAAK,WAAW,EAChB,KAAK,UAAU,EAChB,MAAM,8BAA8B,CAAC;AACtC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAC9C,OAAO,KAAK,EAAmB,0BAA0B,EAAE,MAAM,eAAe,CAAC;AACjF,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AAC7D,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAQjD,OAAO,EAAuB,KAAK,gBAAgB,EAAwB,MAAM,gBAAgB,CAAC;AAGlG;;;;;GAKG;AACH,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,eAAe,CAAC;IACxB,MAAM,EAAE,aAAa,CAAC,WAAW,CAAC,CAAC;IACnC,MAAM,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC/B;AAED;;;;GAIG;AACH,qBAAa,OAAQ,YAAW,UAAU;IAWtC,OAAO,CAAC,QAAQ;IAChB,OAAO,CAAC,YAAY;IAXtB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,gBAAgB,CAAmB;IAE3C;;;;;OAKG;gBAEO,QAAQ,EAAE,gBAAgB,EAC1B,YAAY,EAAE,YAAY,EAClC,gBAAgB,CAAC,EAAE,gBAAgB;IAOrC;;;OAGG;IACH,OAAO,CAAC,QAAQ;IAShB;;;OAGG;IACH,OAAO,CAAC,gBAAgB;IAoBxB;;;;;;;OAOG;IACG,GAAG,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC;IAuIpD;;;;;;;;;;;;;;;;;OAiBG;IACG,MAAM,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,kBAAkB,CAAC;IA8Q/D;;;OAGG;IACG,iBAAiB,IAAI,OAAO,CAAC,0BAA0B,EAAE,CAAC;CASjE"}
|
package/dist/core/engine.js
CHANGED
|
@@ -87,7 +87,7 @@ export class IndeRun {
|
|
|
87
87
|
});
|
|
88
88
|
}
|
|
89
89
|
// 2. Select the route based on constraints and host capabilities
|
|
90
|
-
const routeSelection = await this.router.selectRoute(request, this.hostServices);
|
|
90
|
+
const routeSelection = await this.router.selectRoute(request, this.hostServices, "run");
|
|
91
91
|
const providers = [routeSelection.provider, ...routeSelection.fallbackProviders];
|
|
92
92
|
// Emit route_decided event
|
|
93
93
|
this.safeEmit({
|
|
@@ -194,13 +194,18 @@ export class IndeRun {
|
|
|
194
194
|
/**
|
|
195
195
|
* Orchestrates a Mode 2 streaming execution of a TaskRequest.
|
|
196
196
|
*
|
|
197
|
-
*
|
|
198
|
-
*
|
|
199
|
-
*
|
|
200
|
-
*
|
|
201
|
-
*
|
|
202
|
-
*
|
|
203
|
-
* is
|
|
197
|
+
* Routes through the same planner as run(), asking it for a `stream` route: the
|
|
198
|
+
* planner rejects providers that do not declare `supports.streaming` or whose
|
|
199
|
+
* dynamic capability snapshot says they cannot stream here, and reports why. The
|
|
200
|
+
* whole returned chain is therefore mode-compatible and subject to the same
|
|
201
|
+
* privacy, locality, availability, and policy constraints as a Mode 1 route.
|
|
202
|
+
*
|
|
203
|
+
* Fallback to the next provider is allowed only before the first content event
|
|
204
|
+
* has been admitted for the run; once one has been delivered, the run is
|
|
205
|
+
* committed to that provider and any later provider failure becomes a terminal
|
|
206
|
+
* error outcome rather than a silent provider swap — partial output spliced from
|
|
207
|
+
* two providers is undetectable to the caller. Cancellation always forecloses
|
|
208
|
+
* fallback, regardless of commit state, and is idempotent.
|
|
204
209
|
* @param request - The canonical task request containing prompts, tasks, and constraints.
|
|
205
210
|
* @throws {IndeRunException} If validation fails or no eligible streaming provider exists.
|
|
206
211
|
*/
|
|
@@ -215,9 +220,17 @@ export class IndeRun {
|
|
|
215
220
|
.join("; ")}`;
|
|
216
221
|
throw createInternal(message, { runId, details: { validationIssues } });
|
|
217
222
|
}
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
223
|
+
// Route selection failures are the one class of stream error that surfaces as a
|
|
224
|
+
// rejected promise instead of a terminal event, so they must still carry the
|
|
225
|
+
// runId — there is no handle for the caller to correlate them with otherwise.
|
|
226
|
+
let routeSelection;
|
|
227
|
+
try {
|
|
228
|
+
routeSelection = await this.router.selectRoute(request, this.hostServices, "stream");
|
|
229
|
+
}
|
|
230
|
+
catch (error) {
|
|
231
|
+
throw toIndeRunException(error, { runId });
|
|
232
|
+
}
|
|
233
|
+
const providers = [routeSelection.provider, ...routeSelection.fallbackProviders];
|
|
221
234
|
this.safeEmit({
|
|
222
235
|
type: "route_decided",
|
|
223
236
|
runId,
|
|
@@ -235,6 +248,9 @@ export class IndeRun {
|
|
|
235
248
|
plannerUnavailableReason: routeSelection.plannerUnavailableReason ?? null
|
|
236
249
|
}
|
|
237
250
|
});
|
|
251
|
+
// Defensive only: streaming eligibility is decided by the route planner, which
|
|
252
|
+
// throws with its own rejection reasons when nothing can stream. Reaching here
|
|
253
|
+
// means a planned provider was unregistered between planning and selection.
|
|
238
254
|
if (providers.length === 0) {
|
|
239
255
|
throw createCapabilityMismatch("No eligible provider supports streaming for this request.", {
|
|
240
256
|
runId,
|
package/dist/core/provider.d.ts
CHANGED
|
@@ -82,6 +82,25 @@ export interface ProviderDynamicCapabilities {
|
|
|
82
82
|
* Detail message if availability check fails.
|
|
83
83
|
*/
|
|
84
84
|
reason?: string;
|
|
85
|
+
/**
|
|
86
|
+
* Whether the provider can stream (Mode 2) right now on this host. Leave
|
|
87
|
+
* undefined to inherit the static `descriptor.supports.streaming` value —
|
|
88
|
+
* only set it when the runtime environment can take streaming away from a
|
|
89
|
+
* provider that otherwise declares it (e.g. the host has no chunked HTTP
|
|
90
|
+
* capability).
|
|
91
|
+
*/
|
|
92
|
+
streamingAvailable?: boolean;
|
|
93
|
+
/**
|
|
94
|
+
* Detail message used when `streamingAvailable` is false. Omit to let the
|
|
95
|
+
* route planner synthesize a generic message.
|
|
96
|
+
*/
|
|
97
|
+
streamingUnavailableReason?: string;
|
|
98
|
+
/**
|
|
99
|
+
* Whether cancellation is honored right now on this host. Leave undefined to
|
|
100
|
+
* inherit the static `descriptor.cancel` value (anything other than `"none"`
|
|
101
|
+
* means available).
|
|
102
|
+
*/
|
|
103
|
+
cancellationAvailable?: boolean;
|
|
85
104
|
}
|
|
86
105
|
/**
|
|
87
106
|
* One registered provider's identity, fixed capabilities, and live availability,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../../src/core/provider.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAC;AAC5E,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAE9C;;;GAGG;AACH,MAAM,WAAW,kBAAkB;IACjC;;OAEG;IACH,EAAE,EAAE,MAAM,CAAC;IACX;;OAEG;IACH,IAAI,EAAE,OAAO,GAAG,MAAM,GAAG,OAAO,CAAC;IACjC;;OAEG;IACH,SAAS,EAAE,YAAY,GAAG,gBAAgB,GAAG,MAAM,GAAG,KAAK,GAAG,UAAU,CAAC;IACzE;;OAEG;IACH,cAAc,CAAC,EAAE,QAAQ,GAAG,QAAQ,GAAG,WAAW,CAAC;IAEnD;;OAEG;IACH,QAAQ,EAAE;QACR,mEAAmE;QACnE,GAAG,EAAE,OAAO,CAAC;QACb,mDAAmD;QACnD,SAAS,EAAE,OAAO,CAAC;QACnB,6DAA6D;QAC7D,QAAQ,EAAE,OAAO,CAAC;QAClB,6DAA6D;QAC7D,KAAK,EAAE,OAAO,CAAC;QACf,wEAAwE;QACxE,eAAe,EAAE,OAAO,CAAC;QACzB,kFAAkF;QAClF,gBAAgB,EAAE,OAAO,CAAC;QAC1B,6EAA6E;QAC7E,UAAU,EAAE,OAAO,CAAC;KACrB,CAAC;IAEF;;;;;OAKG;IACH,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;IAEjC;;OAEG;IACH,KAAK,EAAE,MAAM,EAAE,CAAC;IAEhB;;OAEG;IACH,MAAM,CAAC,EAAE;QACP,cAAc,CAAC,EAAE,MAAM,CAAC;QACxB,eAAe,CAAC,EAAE,MAAM,CAAC;QACzB,aAAa,CAAC,EAAE,MAAM,CAAC;QACvB,eAAe,CAAC,EAAE,MAAM,CAAC;KAC1B,CAAC;IAEF;;OAEG;IACH,OAAO,CAAC,EAAE;QACR,6DAA6D;QAC7D,gBAAgB,EAAE,OAAO,CAAC;QAC1B,2DAA2D;QAC3D,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;KACpB,CAAC;CACH;AAED;;GAEG;AACH,MAAM,WAAW,2BAA2B;IAC1C;;OAEG;IACH,SAAS,EAAE,OAAO,CAAC;IACnB;;OAEG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../../src/core/provider.ts"],"names":[],"mappings":"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"}
|
|
@@ -2,6 +2,11 @@ import type { RoutePlan as SharedPlannerRoutePlan, RoutePlannerInput as SharedPl
|
|
|
2
2
|
import type { HostServices } from "./host.js";
|
|
3
3
|
import type { ProviderAdapter } from "./provider.js";
|
|
4
4
|
export type { SharedPlannerInput, SharedPlannerRoutePlan };
|
|
5
|
+
/**
|
|
6
|
+
* Interaction mode the caller is requesting a route for. Passed to the shared
|
|
7
|
+
* planner, which rejects providers that cannot satisfy it.
|
|
8
|
+
*/
|
|
9
|
+
export type InteractionMode = NonNullable<SharedPlannerInput["interactionMode"]>;
|
|
5
10
|
/**
|
|
6
11
|
* Minimal shape of the shared route-core module (the WASM wrapper), used to
|
|
7
12
|
* plan routes via JSON in/JSON out. `initSharedCore` initializes the WASM
|
|
@@ -73,5 +78,5 @@ export declare function collectProviderRuntimeSnapshots(registryProviders: Provi
|
|
|
73
78
|
* consumes, applying default constraints/preferences and the current network
|
|
74
79
|
* state.
|
|
75
80
|
*/
|
|
76
|
-
export declare function buildSharedPlannerInput(request: TaskRequest, snapshots: ProviderRuntimeSnapshot[], networkOnline: boolean): SharedPlannerInput;
|
|
81
|
+
export declare function buildSharedPlannerInput(request: TaskRequest, snapshots: ProviderRuntimeSnapshot[], networkOnline: boolean, interactionMode?: InteractionMode): SharedPlannerInput;
|
|
77
82
|
//# sourceMappingURL=route-planner.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"route-planner.d.ts","sourceRoot":"","sources":["../../src/core/route-planner.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,SAAS,IAAI,sBAAsB,EACnC,iBAAiB,IAAI,kBAAkB,EACvC,WAAW,EACZ,MAAM,8BAA8B,CAAC;AACtC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAC9C,OAAO,KAAK,EACV,eAAe,EAGhB,MAAM,eAAe,CAAC;AAEvB,YAAY,EAAE,kBAAkB,EAAE,sBAAsB,EAAE,CAAC;AAE3D;;;;GAIG;AACH,MAAM,WAAW,mBAAmB;IAClC,cAAc,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACrC,aAAa,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CAChE;AAED;;;;GAIG;AACH,MAAM,MAAM,qBAAqB,GAC/B,eAAe,GAAG,sBAAsB,GAAG,aAAa,GAAG,aAAa,CAAC;AAE3E;;;;;GAKG;AACH,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,sBAAsB,GAAG,IAAI,CAAC;IACzC,MAAM,EAAE,MAAM,GAAG,aAAa,CAAC;IAC/B,iBAAiB,CAAC,EAAE,qBAAqB,CAAC;CAC3C;AAED;;;GAGG;AACH,MAAM,WAAW,YAAY;IAC3B,SAAS,CAAC,KAAK,EAAE,kBAAkB,GAAG,OAAO,CAAC,cAAc,CAAC,CAAC;CAC/D;AAED;;;GAGG;AACH,MAAM,WAAW,uBAAuB;IACtC,QAAQ,EAAE,eAAe,CAAC;IAC1B,UAAU,EAAE,kBAAkB,CAAC,WAAW,CAAC,CAAC,MAAM,CAAC,CAAC,YAAY,CAAC,CAAC;IAClE,YAAY,EAAE,kBAAkB,CAAC,WAAW,CAAC,CAAC,MAAM,CAAC,CAAC,cAAc,CAAC,CAAC;CACvE;AAED;;;;;;;;;GASG;AACH,qBAAa,gBAAiB,YAAW,YAAY;IACnD,OAAO,CAAC,aAAa,CAAC,CAAsC;IAC5D,OAAO,CAAC,iBAAiB,CAAC,CAAwB;IAClD,OAAO,CAAC,SAAS,CAAS;IAEpB,SAAS,CAAC,KAAK,EAAE,kBAAkB,GAAG,OAAO,CAAC,cAAc,CAAC;YAkBrD,UAAU;YAOV,YAAY;IA6B1B,OAAO,CAAC,QAAQ;CAUjB;AAED;;;GAGG;AACH,wBAAsB,+BAA+B,CACnD,iBAAiB,EAAE,eAAe,EAAE,EACpC,YAAY,EAAE,YAAY,GACzB,OAAO,CAAC,uBAAuB,EAAE,CAAC,CAepC;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,CACrC,OAAO,EAAE,WAAW,EACpB,SAAS,EAAE,uBAAuB,EAAE,EACpC,aAAa,EAAE,OAAO,
|
|
1
|
+
{"version":3,"file":"route-planner.d.ts","sourceRoot":"","sources":["../../src/core/route-planner.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,SAAS,IAAI,sBAAsB,EACnC,iBAAiB,IAAI,kBAAkB,EACvC,WAAW,EACZ,MAAM,8BAA8B,CAAC;AACtC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAC9C,OAAO,KAAK,EACV,eAAe,EAGhB,MAAM,eAAe,CAAC;AAEvB,YAAY,EAAE,kBAAkB,EAAE,sBAAsB,EAAE,CAAC;AAE3D;;;GAGG;AACH,MAAM,MAAM,eAAe,GAAG,WAAW,CAAC,kBAAkB,CAAC,iBAAiB,CAAC,CAAC,CAAC;AAEjF;;;;GAIG;AACH,MAAM,WAAW,mBAAmB;IAClC,cAAc,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACrC,aAAa,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CAChE;AAED;;;;GAIG;AACH,MAAM,MAAM,qBAAqB,GAC/B,eAAe,GAAG,sBAAsB,GAAG,aAAa,GAAG,aAAa,CAAC;AAE3E;;;;;GAKG;AACH,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,sBAAsB,GAAG,IAAI,CAAC;IACzC,MAAM,EAAE,MAAM,GAAG,aAAa,CAAC;IAC/B,iBAAiB,CAAC,EAAE,qBAAqB,CAAC;CAC3C;AAED;;;GAGG;AACH,MAAM,WAAW,YAAY;IAC3B,SAAS,CAAC,KAAK,EAAE,kBAAkB,GAAG,OAAO,CAAC,cAAc,CAAC,CAAC;CAC/D;AAED;;;GAGG;AACH,MAAM,WAAW,uBAAuB;IACtC,QAAQ,EAAE,eAAe,CAAC;IAC1B,UAAU,EAAE,kBAAkB,CAAC,WAAW,CAAC,CAAC,MAAM,CAAC,CAAC,YAAY,CAAC,CAAC;IAClE,YAAY,EAAE,kBAAkB,CAAC,WAAW,CAAC,CAAC,MAAM,CAAC,CAAC,cAAc,CAAC,CAAC;CACvE;AAED;;;;;;;;;GASG;AACH,qBAAa,gBAAiB,YAAW,YAAY;IACnD,OAAO,CAAC,aAAa,CAAC,CAAsC;IAC5D,OAAO,CAAC,iBAAiB,CAAC,CAAwB;IAClD,OAAO,CAAC,SAAS,CAAS;IAEpB,SAAS,CAAC,KAAK,EAAE,kBAAkB,GAAG,OAAO,CAAC,cAAc,CAAC;YAkBrD,UAAU;YAOV,YAAY;IA6B1B,OAAO,CAAC,QAAQ;CAUjB;AAED;;;GAGG;AACH,wBAAsB,+BAA+B,CACnD,iBAAiB,EAAE,eAAe,EAAE,EACpC,YAAY,EAAE,YAAY,GACzB,OAAO,CAAC,uBAAuB,EAAE,CAAC,CAepC;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,CACrC,OAAO,EAAE,WAAW,EACpB,SAAS,EAAE,uBAAuB,EAAE,EACpC,aAAa,EAAE,OAAO,EACtB,eAAe,GAAE,eAAuB,GACvC,kBAAkB,CAuBpB"}
|
|
@@ -81,7 +81,7 @@ export async function collectProviderRuntimeSnapshots(registryProviders, hostSer
|
|
|
81
81
|
return {
|
|
82
82
|
provider,
|
|
83
83
|
descriptor: toSharedPlannerDescriptor(descriptor),
|
|
84
|
-
capabilities: toSharedPlannerCapabilities(capabilities)
|
|
84
|
+
capabilities: toSharedPlannerCapabilities(capabilities, descriptor, provider)
|
|
85
85
|
};
|
|
86
86
|
}));
|
|
87
87
|
return snapshots.sort((left, right) => left.descriptor.id.localeCompare(right.descriptor.id));
|
|
@@ -91,13 +91,14 @@ export async function collectProviderRuntimeSnapshots(registryProviders, hostSer
|
|
|
91
91
|
* consumes, applying default constraints/preferences and the current network
|
|
92
92
|
* state.
|
|
93
93
|
*/
|
|
94
|
-
export function buildSharedPlannerInput(request, snapshots, networkOnline) {
|
|
94
|
+
export function buildSharedPlannerInput(request, snapshots, networkOnline, interactionMode = "run") {
|
|
95
95
|
const constraints = request.constraints ?? {};
|
|
96
96
|
const preferences = request.preferences ?? {};
|
|
97
97
|
return {
|
|
98
98
|
task: {
|
|
99
99
|
kind: request.task.kind
|
|
100
100
|
},
|
|
101
|
+
interactionMode,
|
|
101
102
|
constraints: {
|
|
102
103
|
privacy: constraints.privacy ?? "cloud_allowed",
|
|
103
104
|
cloud: constraints.cloud ?? "allowed",
|
|
@@ -118,8 +119,10 @@ function toSharedPlannerDescriptor(descriptor) {
|
|
|
118
119
|
id: descriptor.id,
|
|
119
120
|
type: descriptor.type,
|
|
120
121
|
supports: {
|
|
121
|
-
run: descriptor.supports.run
|
|
122
|
+
run: descriptor.supports.run,
|
|
123
|
+
streaming: descriptor.supports.streaming
|
|
122
124
|
},
|
|
125
|
+
cancel: descriptor.cancel,
|
|
123
126
|
tasks: descriptor.tasks,
|
|
124
127
|
...(descriptor.privacy
|
|
125
128
|
? {
|
|
@@ -131,9 +134,30 @@ function toSharedPlannerDescriptor(descriptor) {
|
|
|
131
134
|
: {})
|
|
132
135
|
};
|
|
133
136
|
}
|
|
134
|
-
|
|
137
|
+
/**
|
|
138
|
+
* Projects the dynamic capability snapshot for the planner.
|
|
139
|
+
*
|
|
140
|
+
* `stream()` being absent from the adapter is folded in here rather than
|
|
141
|
+
* filtered out downstream: an adapter that declares `supports.streaming` but
|
|
142
|
+
* never implemented the method is not streaming-capable, and saying so as a
|
|
143
|
+
* planner-visible reason is what lets the route explanation name the provider
|
|
144
|
+
* instead of silently dropping it.
|
|
145
|
+
*/
|
|
146
|
+
function toSharedPlannerCapabilities(capabilities, descriptor, provider) {
|
|
147
|
+
const declaresStreaming = capabilities.streamingAvailable ?? descriptor.supports.streaming;
|
|
148
|
+
const implementsStream = typeof provider.stream === "function";
|
|
149
|
+
const streamingAvailable = declaresStreaming && implementsStream;
|
|
150
|
+
const streamingUnavailableReason = capabilities.streamingUnavailableReason ??
|
|
151
|
+
(declaresStreaming && !implementsStream
|
|
152
|
+
? `Provider '${descriptor.id}' declares streaming but does not implement stream().`
|
|
153
|
+
: undefined);
|
|
135
154
|
return {
|
|
136
155
|
available: capabilities.available,
|
|
137
|
-
...(capabilities.reason ? { reason: capabilities.reason } : {})
|
|
156
|
+
...(capabilities.reason ? { reason: capabilities.reason } : {}),
|
|
157
|
+
streamingAvailable,
|
|
158
|
+
...(!streamingAvailable && streamingUnavailableReason ? { streamingUnavailableReason } : {}),
|
|
159
|
+
...(capabilities.cancellationAvailable !== undefined
|
|
160
|
+
? { cancellationAvailable: capabilities.cancellationAvailable }
|
|
161
|
+
: {})
|
|
138
162
|
};
|
|
139
163
|
}
|
package/dist/core/router.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import type { TaskRequest } from "@independo/inderun-contracts";
|
|
|
2
2
|
import type { HostServices } from "./host.js";
|
|
3
3
|
import type { ProviderAdapter } from "./provider.js";
|
|
4
4
|
import type { ProviderRegistry } from "./registry.js";
|
|
5
|
-
import { type RoutePlanner, type SharedPlannerRoutePlan, type WasmUnavailableReason } from "./route-planner.js";
|
|
5
|
+
import { type InteractionMode, type RoutePlanner, type SharedPlannerRoutePlan, type WasmUnavailableReason } from "./route-planner.js";
|
|
6
6
|
/**
|
|
7
7
|
* Output structure from the Router containing the chosen provider and a route selection description.
|
|
8
8
|
*/
|
|
@@ -52,13 +52,47 @@ export declare class Router {
|
|
|
52
52
|
* - `CapabilityMismatch` when `execution === 'on_device'` but no local provider is registered or available.
|
|
53
53
|
* - `Offline` when `execution === 'cloud'` but the device lacks internet connectivity.
|
|
54
54
|
* - `Unavailable` when `execution === 'cloud'` but no cloud provider is registered or functional.
|
|
55
|
+
*
|
|
56
|
+
* The requested `interactionMode` is a planning input, not a post-filter: the
|
|
57
|
+
* shared planner rejects providers that cannot satisfy it and says why, so the
|
|
58
|
+
* whole returned chain is mode-compatible.
|
|
55
59
|
*/
|
|
56
|
-
selectRoute(request: TaskRequest, hostServices: HostServices): Promise<RouteSelection>;
|
|
60
|
+
selectRoute(request: TaskRequest, hostServices: HostServices, interactionMode?: InteractionMode): Promise<RouteSelection>;
|
|
57
61
|
private selectFromSharedPlan;
|
|
58
62
|
private selectFallbackRoute;
|
|
59
63
|
private buildSelectionFromRoutePlan;
|
|
64
|
+
/**
|
|
65
|
+
* Hand-written mirror of the shared Rust planner (`rust/inderun-route-core/src/planner.rs`),
|
|
66
|
+
* used when the WASM core cannot be loaded. It reproduces the same rejection
|
|
67
|
+
* codes and messages so route explanations stay comparable across both paths.
|
|
68
|
+
*
|
|
69
|
+
* Known, deliberate divergence: this mirror keeps the id-sorted snapshot order
|
|
70
|
+
* instead of reproducing the Rust placement/preference ranks, so `optimizeFor`
|
|
71
|
+
* does not influence ordering here.
|
|
72
|
+
*/
|
|
60
73
|
private createFallbackPlan;
|
|
74
|
+
/**
|
|
75
|
+
* Mirrors `infer_failure_code` in the shared Rust planner. The offline class is
|
|
76
|
+
* derived from the rejection reasons rather than from the connectivity flag
|
|
77
|
+
* alone: an offline host whose only provider is local and non-streaming failed
|
|
78
|
+
* on capability, not on connectivity, and reporting `Offline` there would
|
|
79
|
+
* contradict the explanation shipped alongside it.
|
|
80
|
+
*/
|
|
81
|
+
private inferFallbackFailureCode;
|
|
82
|
+
/**
|
|
83
|
+
* Mirrors `evaluate_provider` in the shared Rust planner: it accumulates every
|
|
84
|
+
* violated rule rather than short-circuiting, so a rejected provider carries a
|
|
85
|
+
* complete explanation. The streaming checks are ordered static-before-dynamic
|
|
86
|
+
* for the same reason as in Rust — a provider that never declared streaming
|
|
87
|
+
* must not also be reported as dynamically unavailable.
|
|
88
|
+
*/
|
|
89
|
+
private evaluateFallbackCandidate;
|
|
61
90
|
private buildFallbackFailureSummary;
|
|
91
|
+
/**
|
|
92
|
+
* Routing failure throws before any `route_decided` telemetry is emitted, so the
|
|
93
|
+
* plan's diagnostics are attached to the exception — that is the only channel
|
|
94
|
+
* through which a caller learns *why* each provider was rejected.
|
|
95
|
+
*/
|
|
62
96
|
private routePlanFailure;
|
|
63
97
|
}
|
|
64
98
|
//# sourceMappingURL=router.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../../src/core/router.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAChE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAC9C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AACrD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAOtD,OAAO,
|
|
1
|
+
{"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../../src/core/router.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAChE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAC9C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AACrD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAOtD,OAAO,EAGL,KAAK,eAAe,EAEpB,KAAK,YAAY,EAEjB,KAAK,sBAAsB,EAC3B,KAAK,qBAAqB,EAE3B,MAAM,oBAAoB,CAAC;AAE5B;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B;;OAEG;IACH,QAAQ,EAAE,eAAe,CAAC;IAC1B;;OAEG;IACH,iBAAiB,EAAE,eAAe,EAAE,CAAC;IACrC;;OAEG;IACH,SAAS,EAAE,sBAAsB,CAAC;IAClC;;OAEG;IACH,WAAW,EAAE,MAAM,CAAC;IACpB;;;OAGG;IACH,aAAa,EAAE,MAAM,GAAG,UAAU,CAAC;IACnC;;;OAGG;IACH,wBAAwB,CAAC,EAAE,qBAAqB,CAAC;CAClD;AAED;;;;GAIG;AACH,qBAAa,MAAM;IAEf,OAAO,CAAC,QAAQ;IAChB,OAAO,CAAC,OAAO;gBADP,QAAQ,EAAE,gBAAgB,EAC1B,OAAO,GAAE,YAAqC;IAGxD;;;;;;;;;;;;;OAaG;IACG,WAAW,CACf,OAAO,EAAE,WAAW,EACpB,YAAY,EAAE,YAAY,EAC1B,eAAe,GAAE,eAAuB,GACvC,OAAO,CAAC,cAAc,CAAC;IAoB1B,OAAO,CAAC,oBAAoB;IAW5B,OAAO,CAAC,mBAAmB;IAe3B,OAAO,CAAC,2BAA2B;IA2BnC;;;;;;;;OAQG;IACH,OAAO,CAAC,kBAAkB;IAwE1B;;;;;;OAMG;IACH,OAAO,CAAC,wBAAwB;IAsBhC;;;;;;OAMG;IACH,OAAO,CAAC,yBAAyB;IA4FjC,OAAO,CAAC,2BAA2B;IAyCnC;;;;OAIG;IACH,OAAO,CAAC,gBAAgB;CAkBzB"}
|
package/dist/core/router.js
CHANGED
|
@@ -21,16 +21,20 @@ export class Router {
|
|
|
21
21
|
* - `CapabilityMismatch` when `execution === 'on_device'` but no local provider is registered or available.
|
|
22
22
|
* - `Offline` when `execution === 'cloud'` but the device lacks internet connectivity.
|
|
23
23
|
* - `Unavailable` when `execution === 'cloud'` but no cloud provider is registered or functional.
|
|
24
|
+
*
|
|
25
|
+
* The requested `interactionMode` is a planning input, not a post-filter: the
|
|
26
|
+
* shared planner rejects providers that cannot satisfy it and says why, so the
|
|
27
|
+
* whole returned chain is mode-compatible.
|
|
24
28
|
*/
|
|
25
|
-
async selectRoute(request, hostServices) {
|
|
29
|
+
async selectRoute(request, hostServices, interactionMode = "run") {
|
|
26
30
|
const online = await hostServices.connectivity.isOnline();
|
|
27
31
|
const snapshots = await collectProviderRuntimeSnapshots(this.registry.list(), hostServices);
|
|
28
|
-
const planInput = buildSharedPlannerInput(request, snapshots, online);
|
|
32
|
+
const planInput = buildSharedPlannerInput(request, snapshots, online, interactionMode);
|
|
29
33
|
const outcome = await this.planner.planRoute(planInput);
|
|
30
34
|
if (outcome.routePlan) {
|
|
31
35
|
return this.selectFromSharedPlan(snapshots, outcome.routePlan);
|
|
32
36
|
}
|
|
33
|
-
return this.selectFallbackRoute(request, snapshots, online, outcome.unavailableReason);
|
|
37
|
+
return this.selectFallbackRoute(request, snapshots, online, interactionMode, outcome.unavailableReason);
|
|
34
38
|
}
|
|
35
39
|
selectFromSharedPlan(snapshots, routePlan) {
|
|
36
40
|
if (!routePlan.selectedProviderId) {
|
|
@@ -38,8 +42,8 @@ export class Router {
|
|
|
38
42
|
}
|
|
39
43
|
return this.buildSelectionFromRoutePlan(snapshots, routePlan, "wasm");
|
|
40
44
|
}
|
|
41
|
-
selectFallbackRoute(request, snapshots, online, plannerUnavailableReason) {
|
|
42
|
-
const plan = this.createFallbackPlan(request, snapshots, online);
|
|
45
|
+
selectFallbackRoute(request, snapshots, online, interactionMode, plannerUnavailableReason) {
|
|
46
|
+
const plan = this.createFallbackPlan(request, snapshots, online, interactionMode);
|
|
43
47
|
if (!plan.selectedProviderId) {
|
|
44
48
|
throw this.routePlanFailure(plan);
|
|
45
49
|
}
|
|
@@ -62,58 +66,48 @@ export class Router {
|
|
|
62
66
|
...(plannerUnavailableReason ? { plannerUnavailableReason } : {})
|
|
63
67
|
};
|
|
64
68
|
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
69
|
+
/**
|
|
70
|
+
* Hand-written mirror of the shared Rust planner (`rust/inderun-route-core/src/planner.rs`),
|
|
71
|
+
* used when the WASM core cannot be loaded. It reproduces the same rejection
|
|
72
|
+
* codes and messages so route explanations stay comparable across both paths.
|
|
73
|
+
*
|
|
74
|
+
* Known, deliberate divergence: this mirror keeps the id-sorted snapshot order
|
|
75
|
+
* instead of reproducing the Rust placement/preference ranks, so `optimizeFor`
|
|
76
|
+
* does not influence ordering here.
|
|
77
|
+
*/
|
|
78
|
+
createFallbackPlan(request, snapshots, online, interactionMode) {
|
|
79
|
+
const planInput = buildSharedPlannerInput(request, snapshots, online, interactionMode);
|
|
80
|
+
const eligible = snapshots.filter((candidate) => candidate.descriptor.tasks.includes(planInput.task.kind));
|
|
70
81
|
const localCandidates = eligible.filter((candidate) => candidate.descriptor.type !== "cloud");
|
|
71
82
|
const cloudCandidates = eligible.filter((candidate) => candidate.descriptor.type === "cloud");
|
|
72
83
|
// Constraint filtering must apply to the whole candidate list, not just the selected provider:
|
|
73
84
|
// the remainder becomes the engine's fallback chain, and a fallback that violates the request
|
|
74
85
|
// constraints would let a `local_required` run be retried against a cloud provider.
|
|
75
|
-
const admissible =
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
? !descriptor.privacy.dataLeavesDevice
|
|
82
|
-
: descriptor.type !== "cloud";
|
|
83
|
-
if (constraints.cloud === "forbidden" && descriptor.type === "cloud") {
|
|
84
|
-
return false;
|
|
85
|
-
}
|
|
86
|
-
if (constraints.cloud === "required" && descriptor.type !== "cloud") {
|
|
87
|
-
return false;
|
|
88
|
-
}
|
|
89
|
-
if (constraints.privacy === "local_required" && !isDataPrivate) {
|
|
90
|
-
return false;
|
|
91
|
-
}
|
|
92
|
-
if (constraints.privacy === "cloud_required" && descriptor.type !== "cloud") {
|
|
93
|
-
return false;
|
|
86
|
+
const admissible = [];
|
|
87
|
+
const rejectedProviders = [];
|
|
88
|
+
for (const candidate of snapshots) {
|
|
89
|
+
const reasons = this.evaluateFallbackCandidate(candidate, planInput, interactionMode);
|
|
90
|
+
if (reasons.length === 0) {
|
|
91
|
+
admissible.push(candidate);
|
|
94
92
|
}
|
|
95
|
-
|
|
96
|
-
|
|
93
|
+
else {
|
|
94
|
+
rejectedProviders.push({ providerId: candidate.descriptor.id, reasons });
|
|
97
95
|
}
|
|
98
|
-
|
|
99
|
-
});
|
|
96
|
+
}
|
|
100
97
|
if (admissible.length === 0) {
|
|
101
98
|
const failureSummary = this.buildFallbackFailureSummary({
|
|
102
99
|
online,
|
|
100
|
+
interactionMode,
|
|
101
|
+
taskKind: planInput.task.kind,
|
|
103
102
|
constraints: planInput.constraints,
|
|
104
103
|
localCandidates,
|
|
105
104
|
cloudCandidates
|
|
106
105
|
});
|
|
107
|
-
const failureCode =
|
|
108
|
-
? "offline"
|
|
109
|
-
: planInput.constraints.cloud === "required" ||
|
|
110
|
-
planInput.constraints.privacy === "cloud_required"
|
|
111
|
-
? "unavailable"
|
|
112
|
-
: "capability_mismatch";
|
|
106
|
+
const failureCode = this.inferFallbackFailureCode(planInput, rejectedProviders);
|
|
113
107
|
return {
|
|
114
108
|
fallbackProviderIds: [],
|
|
115
109
|
candidates: [],
|
|
116
|
-
rejectedProviders
|
|
110
|
+
rejectedProviders,
|
|
117
111
|
failureCode,
|
|
118
112
|
explanation: {
|
|
119
113
|
summary: failureSummary
|
|
@@ -121,6 +115,9 @@ export class Router {
|
|
|
121
115
|
};
|
|
122
116
|
}
|
|
123
117
|
const selectedProviderId = admissible[0]?.descriptor.id;
|
|
118
|
+
const summary = interactionMode === "stream"
|
|
119
|
+
? `Selected streaming provider '${selectedProviderId}' deterministically from ${admissible.length} eligible candidate(s).`
|
|
120
|
+
: `Selected provider '${selectedProviderId}' deterministically from ${admissible.length} eligible candidate(s).`;
|
|
124
121
|
return {
|
|
125
122
|
selectedProviderId,
|
|
126
123
|
fallbackProviderIds: admissible.slice(1).map((candidate) => candidate.descriptor.id),
|
|
@@ -128,13 +125,113 @@ export class Router {
|
|
|
128
125
|
providerId: candidate.descriptor.id,
|
|
129
126
|
order: index
|
|
130
127
|
})),
|
|
131
|
-
rejectedProviders
|
|
128
|
+
rejectedProviders,
|
|
132
129
|
explanation: {
|
|
133
|
-
summary
|
|
130
|
+
summary,
|
|
134
131
|
selectedProviderId
|
|
135
132
|
}
|
|
136
133
|
};
|
|
137
134
|
}
|
|
135
|
+
/**
|
|
136
|
+
* Mirrors `infer_failure_code` in the shared Rust planner. The offline class is
|
|
137
|
+
* derived from the rejection reasons rather than from the connectivity flag
|
|
138
|
+
* alone: an offline host whose only provider is local and non-streaming failed
|
|
139
|
+
* on capability, not on connectivity, and reporting `Offline` there would
|
|
140
|
+
* contradict the explanation shipped alongside it.
|
|
141
|
+
*/
|
|
142
|
+
inferFallbackFailureCode(planInput, rejectedProviders) {
|
|
143
|
+
if (rejectedProviders.some((rejected) => rejected.reasons.some((reason) => reason.code === "offline"))) {
|
|
144
|
+
return "offline";
|
|
145
|
+
}
|
|
146
|
+
if (planInput.constraints.cloud === "required" ||
|
|
147
|
+
planInput.constraints.privacy === "cloud_required") {
|
|
148
|
+
return "unavailable";
|
|
149
|
+
}
|
|
150
|
+
return "capability_mismatch";
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Mirrors `evaluate_provider` in the shared Rust planner: it accumulates every
|
|
154
|
+
* violated rule rather than short-circuiting, so a rejected provider carries a
|
|
155
|
+
* complete explanation. The streaming checks are ordered static-before-dynamic
|
|
156
|
+
* for the same reason as in Rust — a provider that never declared streaming
|
|
157
|
+
* must not also be reported as dynamically unavailable.
|
|
158
|
+
*/
|
|
159
|
+
evaluateFallbackCandidate(candidate, planInput, interactionMode) {
|
|
160
|
+
const descriptor = candidate.descriptor;
|
|
161
|
+
const capabilities = candidate.capabilities;
|
|
162
|
+
const constraints = planInput.constraints;
|
|
163
|
+
const wantsStream = interactionMode === "stream";
|
|
164
|
+
const reasons = [];
|
|
165
|
+
// Mirrors `is_data_private` in the shared Rust planner: a provider is private when it
|
|
166
|
+
// declares that data does not leave the device, defaulting to non-cloud providers.
|
|
167
|
+
const isDataPrivate = descriptor.privacy
|
|
168
|
+
? !descriptor.privacy.dataLeavesDevice
|
|
169
|
+
: descriptor.type !== "cloud";
|
|
170
|
+
if (!descriptor.tasks.includes(planInput.task.kind)) {
|
|
171
|
+
reasons.push({
|
|
172
|
+
code: "task_not_supported",
|
|
173
|
+
message: `Provider '${descriptor.id}' does not support task '${planInput.task.kind}'.`
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
if (!wantsStream && !descriptor.supports.run) {
|
|
177
|
+
reasons.push({
|
|
178
|
+
code: "run_not_supported",
|
|
179
|
+
message: `Provider '${descriptor.id}' does not support run().`
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
if (wantsStream) {
|
|
183
|
+
if (!descriptor.supports.streaming) {
|
|
184
|
+
reasons.push({
|
|
185
|
+
code: "streaming_not_supported",
|
|
186
|
+
message: `Provider '${descriptor.id}' does not support streaming (Mode 2).`
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
else if (capabilities.streamingAvailable === false) {
|
|
190
|
+
reasons.push({
|
|
191
|
+
code: "streaming_unavailable",
|
|
192
|
+
message: capabilities.streamingUnavailableReason ??
|
|
193
|
+
`Provider '${descriptor.id}' cannot stream in the current host environment.`
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
if (constraints.privacy === "local_required" && !isDataPrivate) {
|
|
198
|
+
reasons.push({
|
|
199
|
+
code: "privacy_constraint",
|
|
200
|
+
message: `Provider '${descriptor.id}' does not satisfy local-required privacy.`
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
if (constraints.privacy === "cloud_required" && descriptor.type !== "cloud") {
|
|
204
|
+
reasons.push({
|
|
205
|
+
code: "privacy_constraint",
|
|
206
|
+
message: `Provider '${descriptor.id}' does not satisfy cloud-required privacy.`
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
if (constraints.cloud === "forbidden" && descriptor.type === "cloud") {
|
|
210
|
+
reasons.push({
|
|
211
|
+
code: "cloud_constraint",
|
|
212
|
+
message: `Provider '${descriptor.id}' is cloud-based but cloud execution is forbidden.`
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
if (constraints.cloud === "required" && descriptor.type !== "cloud") {
|
|
216
|
+
reasons.push({
|
|
217
|
+
code: "cloud_constraint",
|
|
218
|
+
message: `Provider '${descriptor.id}' is not cloud-based but cloud execution is required.`
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
if (constraints.networkOnline === false && descriptor.type === "cloud") {
|
|
222
|
+
reasons.push({
|
|
223
|
+
code: "offline",
|
|
224
|
+
message: `Provider '${descriptor.id}' requires cloud connectivity, but the host is offline.`
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
if (!capabilities.available) {
|
|
228
|
+
reasons.push({
|
|
229
|
+
code: "capability_unavailable",
|
|
230
|
+
message: capabilities.reason ?? `Provider '${descriptor.id}' is currently unavailable.`
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
return reasons;
|
|
234
|
+
}
|
|
138
235
|
buildFallbackFailureSummary(input) {
|
|
139
236
|
const wantsCloud = input.constraints.cloud === "required" || input.constraints.privacy === "cloud_required";
|
|
140
237
|
const wantsLocal = input.constraints.privacy === "local_required";
|
|
@@ -153,18 +250,34 @@ export class Router {
|
|
|
153
250
|
}
|
|
154
251
|
return "No on-device provider is currently available.";
|
|
155
252
|
}
|
|
253
|
+
if (input.interactionMode === "stream") {
|
|
254
|
+
// Kept verbatim in sync with the shared Rust planner's stream-mode failure
|
|
255
|
+
// summary so the message a caller sees does not depend on which planner ran.
|
|
256
|
+
return `No provider capable of streaming was found for task '${input.taskKind}'.`;
|
|
257
|
+
}
|
|
156
258
|
return "No eligible provider found for the current routing constraints.";
|
|
157
259
|
}
|
|
260
|
+
/**
|
|
261
|
+
* Routing failure throws before any `route_decided` telemetry is emitted, so the
|
|
262
|
+
* plan's diagnostics are attached to the exception — that is the only channel
|
|
263
|
+
* through which a caller learns *why* each provider was rejected.
|
|
264
|
+
*/
|
|
158
265
|
routePlanFailure(routePlan) {
|
|
159
266
|
const message = routePlan.explanation.summary;
|
|
267
|
+
const details = {
|
|
268
|
+
details: {
|
|
269
|
+
failureCode: routePlan.failureCode,
|
|
270
|
+
rejectedProviders: routePlan.rejectedProviders
|
|
271
|
+
}
|
|
272
|
+
};
|
|
160
273
|
switch (routePlan.failureCode) {
|
|
161
274
|
case "offline":
|
|
162
|
-
throw createOffline(message);
|
|
275
|
+
throw createOffline(message, details);
|
|
163
276
|
case "unavailable":
|
|
164
|
-
throw createUnavailable(message);
|
|
277
|
+
throw createUnavailable(message, details);
|
|
165
278
|
case "capability_mismatch":
|
|
166
279
|
default:
|
|
167
|
-
throw createCapabilityMismatch(message);
|
|
280
|
+
throw createCapabilityMismatch(message, details);
|
|
168
281
|
}
|
|
169
282
|
}
|
|
170
283
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@independo/inderun-web",
|
|
3
|
-
"version": "0.3.0-dev.
|
|
3
|
+
"version": "0.3.0-dev.5",
|
|
4
4
|
"description": "TypeScript/Web SDK for IndeRun.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": {
|
|
@@ -55,8 +55,8 @@
|
|
|
55
55
|
"README.md"
|
|
56
56
|
],
|
|
57
57
|
"dependencies": {
|
|
58
|
-
"@independo/inderun-contracts": "^0.3.0-dev.
|
|
59
|
-
"@independo/inderun-route-core-wasm": "^0.3.0-dev.
|
|
58
|
+
"@independo/inderun-contracts": "^0.3.0-dev.5",
|
|
59
|
+
"@independo/inderun-route-core-wasm": "^0.3.0-dev.5"
|
|
60
60
|
},
|
|
61
61
|
"peerDependencies": {
|
|
62
62
|
"@huggingface/transformers": "^4.0.0"
|