@independo/inderun-web 0.3.0-dev.7 → 0.3.0-dev.9
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 +11 -0
- package/dist/core/engine.d.ts.map +1 -1
- package/dist/core/engine.js +38 -42
- package/dist/core/route-planner.d.ts +16 -13
- package/dist/core/route-planner.d.ts.map +1 -1
- package/dist/core/route-planner.js +22 -19
- package/dist/core/router.d.ts +1 -39
- package/dist/core/router.d.ts.map +1 -1
- package/dist/core/router.js +12 -206
- package/package.json +4 -4
package/dist/core/engine.d.ts
CHANGED
|
@@ -27,6 +27,17 @@ export declare class IndeRun implements IndeRunApi {
|
|
|
27
27
|
* @param telemetryService - Optional custom telemetry service listener.
|
|
28
28
|
*/
|
|
29
29
|
constructor(registry: ProviderRegistry, hostServices: HostServices, telemetryService?: TelemetryService);
|
|
30
|
+
/**
|
|
31
|
+
* Current time from the host clock, falling back to wall-clock time when the
|
|
32
|
+
* host does not supply one.
|
|
33
|
+
*/
|
|
34
|
+
private now;
|
|
35
|
+
/**
|
|
36
|
+
* Emits `route_decided` for a completed route selection. Shared by `run()` and
|
|
37
|
+
* `stream()`: the payload is identical for both modes, and the interaction
|
|
38
|
+
* mode is already reflected in the plan the router returned.
|
|
39
|
+
*/
|
|
40
|
+
private emitRouteDecided;
|
|
30
41
|
/**
|
|
31
42
|
* Safely emits a telemetry event by swallowing any internal logging/telemetry system errors.
|
|
32
43
|
* @param event - The telemetry event to emit.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../../src/core/engine.ts"],"names":[],"mappings":"AAAA,OAAO,EAKL,KAAK,WAAW,EAChB,KAAK,UAAU,EAChB,MAAM,8BAA8B,CAAC;AACtC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAC9C,OAAO,KAAK,EAAmB,0BAA0B,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAC5F,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;;;;GAIG;AACH,MAAM,MAAM,kBAAkB,GAAG,SAAS,CAAC;AAE3C;;;;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;
|
|
1
|
+
{"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../../src/core/engine.ts"],"names":[],"mappings":"AAAA,OAAO,EAKL,KAAK,WAAW,EAChB,KAAK,UAAU,EAChB,MAAM,8BAA8B,CAAC;AACtC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAC9C,OAAO,KAAK,EAAmB,0BAA0B,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAC5F,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;;;;GAIG;AACH,MAAM,MAAM,kBAAkB,GAAG,SAAS,CAAC;AAE3C;;;;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,GAAG;IAIX;;;;OAIG;IACH,OAAO,CAAC,gBAAgB;IAuBxB;;;OAGG;IACH,OAAO,CAAC,QAAQ;IAShB;;;OAGG;IACH,OAAO,CAAC,gBAAgB;IAoBxB;;;;;;;OAOG;IACG,GAAG,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC;IAsHpD;;;;;;;;;;;;;;;;;OAiBG;IACG,MAAM,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,SAAS,CAAC;IA+PtD;;;OAGG;IACG,iBAAiB,IAAI,OAAO,CAAC,0BAA0B,EAAE,CAAC;CASjE"}
|
package/dist/core/engine.js
CHANGED
|
@@ -27,6 +27,35 @@ export class IndeRun {
|
|
|
27
27
|
this.telemetryService =
|
|
28
28
|
telemetryService || this.hostServices.telemetry || new NoOpTelemetryService();
|
|
29
29
|
}
|
|
30
|
+
/**
|
|
31
|
+
* Current time from the host clock, falling back to wall-clock time when the
|
|
32
|
+
* host does not supply one.
|
|
33
|
+
*/
|
|
34
|
+
now() {
|
|
35
|
+
return this.hostServices.clock ? this.hostServices.clock.now() : Date.now();
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Emits `route_decided` for a completed route selection. Shared by `run()` and
|
|
39
|
+
* `stream()`: the payload is identical for both modes, and the interaction
|
|
40
|
+
* mode is already reflected in the plan the router returned.
|
|
41
|
+
*/
|
|
42
|
+
emitRouteDecided(runId, request, routeSelection, providerCount) {
|
|
43
|
+
this.safeEmit({
|
|
44
|
+
type: "route_decided",
|
|
45
|
+
runId,
|
|
46
|
+
timestamp: this.now(),
|
|
47
|
+
payload: {
|
|
48
|
+
selectedProviderId: routeSelection.routePlan.selectedProviderId,
|
|
49
|
+
fallbackProviderIds: routeSelection.routePlan.fallbackProviderIds,
|
|
50
|
+
rejectedProviders: routeSelection.routePlan.rejectedProviders,
|
|
51
|
+
fallbackAvailable: providerCount > 1,
|
|
52
|
+
taskKind: request.task.kind,
|
|
53
|
+
constraints: request.constraints ?? null,
|
|
54
|
+
preferences: request.preferences ?? null,
|
|
55
|
+
explanation: routeSelection.explanation
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
}
|
|
30
59
|
/**
|
|
31
60
|
* Safely emits a telemetry event by swallowing any internal logging/telemetry system errors.
|
|
32
61
|
* @param event - The telemetry event to emit.
|
|
@@ -72,7 +101,7 @@ export class IndeRun {
|
|
|
72
101
|
* @throws {IndeRunException} Standardized error indicating validation, connection, or provider failures.
|
|
73
102
|
*/
|
|
74
103
|
async run(request) {
|
|
75
|
-
const startTime = this.
|
|
104
|
+
const startTime = this.now();
|
|
76
105
|
const runId = request.requestId || `run_${Math.random().toString(36).substring(2, 11)}`;
|
|
77
106
|
try {
|
|
78
107
|
// 1. Validate request payload using schema contracts
|
|
@@ -89,24 +118,7 @@ export class IndeRun {
|
|
|
89
118
|
// 2. Select the route based on constraints and host capabilities
|
|
90
119
|
const routeSelection = await this.router.selectRoute(request, this.hostServices, "run");
|
|
91
120
|
const providers = [routeSelection.provider, ...routeSelection.fallbackProviders];
|
|
92
|
-
|
|
93
|
-
this.safeEmit({
|
|
94
|
-
type: "route_decided",
|
|
95
|
-
runId,
|
|
96
|
-
timestamp: this.hostServices.clock ? this.hostServices.clock.now() : Date.now(),
|
|
97
|
-
payload: {
|
|
98
|
-
selectedProviderId: routeSelection.routePlan.selectedProviderId,
|
|
99
|
-
fallbackProviderIds: routeSelection.routePlan.fallbackProviderIds,
|
|
100
|
-
rejectedProviders: routeSelection.routePlan.rejectedProviders,
|
|
101
|
-
fallbackAvailable: providers.length > 1,
|
|
102
|
-
taskKind: request.task.kind,
|
|
103
|
-
explanation: routeSelection.explanation,
|
|
104
|
-
constraints: request.constraints ?? null,
|
|
105
|
-
preferences: request.preferences ?? null,
|
|
106
|
-
plannerSource: routeSelection.plannerSource,
|
|
107
|
-
plannerUnavailableReason: routeSelection.plannerUnavailableReason ?? null
|
|
108
|
-
}
|
|
109
|
-
});
|
|
121
|
+
this.emitRouteDecided(runId, request, routeSelection, providers.length);
|
|
110
122
|
// 3. Execute the run task using the planned provider chain
|
|
111
123
|
const attemptedProviderIds = [];
|
|
112
124
|
let lastError;
|
|
@@ -118,7 +130,7 @@ export class IndeRun {
|
|
|
118
130
|
runId,
|
|
119
131
|
hostServices: this.hostServices
|
|
120
132
|
});
|
|
121
|
-
const endTime = this.
|
|
133
|
+
const endTime = this.now();
|
|
122
134
|
const totalMs = endTime - startTime;
|
|
123
135
|
result.runId = runId;
|
|
124
136
|
result.telemetry = {
|
|
@@ -151,7 +163,7 @@ export class IndeRun {
|
|
|
151
163
|
});
|
|
152
164
|
}
|
|
153
165
|
}
|
|
154
|
-
const endTime = this.
|
|
166
|
+
const endTime = this.now();
|
|
155
167
|
const totalMs = endTime - startTime;
|
|
156
168
|
const exception = toIndeRunException(lastError ?? new Error("No providers were attempted."), {
|
|
157
169
|
runId,
|
|
@@ -170,10 +182,10 @@ export class IndeRun {
|
|
|
170
182
|
details: {
|
|
171
183
|
totalMs: typeof error.details?.totalMs === "number"
|
|
172
184
|
? error.details?.totalMs
|
|
173
|
-
:
|
|
185
|
+
: this.now() - startTime
|
|
174
186
|
}
|
|
175
187
|
});
|
|
176
|
-
const endTime = this.
|
|
188
|
+
const endTime = this.now();
|
|
177
189
|
const totalMs = typeof exception.details?.totalMs === "number"
|
|
178
190
|
? exception.details?.totalMs
|
|
179
191
|
: endTime - startTime;
|
|
@@ -210,8 +222,8 @@ export class IndeRun {
|
|
|
210
222
|
* @throws {IndeRunException} If validation fails or no eligible streaming provider exists.
|
|
211
223
|
*/
|
|
212
224
|
async stream(request) {
|
|
213
|
-
const startTime = this.
|
|
214
|
-
const now = () =>
|
|
225
|
+
const startTime = this.now();
|
|
226
|
+
const now = () => this.now();
|
|
215
227
|
const runId = request.requestId || `run_${Math.random().toString(36).substring(2, 11)}`;
|
|
216
228
|
const validationIssues = getTaskRequestValidationIssues(request);
|
|
217
229
|
if (validationIssues.length > 0) {
|
|
@@ -231,23 +243,7 @@ export class IndeRun {
|
|
|
231
243
|
throw toIndeRunException(error, { runId });
|
|
232
244
|
}
|
|
233
245
|
const providers = [routeSelection.provider, ...routeSelection.fallbackProviders];
|
|
234
|
-
this.
|
|
235
|
-
type: "route_decided",
|
|
236
|
-
runId,
|
|
237
|
-
timestamp: now(),
|
|
238
|
-
payload: {
|
|
239
|
-
selectedProviderId: routeSelection.routePlan.selectedProviderId,
|
|
240
|
-
fallbackProviderIds: routeSelection.routePlan.fallbackProviderIds,
|
|
241
|
-
rejectedProviders: routeSelection.routePlan.rejectedProviders,
|
|
242
|
-
fallbackAvailable: providers.length > 1,
|
|
243
|
-
taskKind: request.task.kind,
|
|
244
|
-
explanation: routeSelection.explanation,
|
|
245
|
-
constraints: request.constraints ?? null,
|
|
246
|
-
preferences: request.preferences ?? null,
|
|
247
|
-
plannerSource: routeSelection.plannerSource,
|
|
248
|
-
plannerUnavailableReason: routeSelection.plannerUnavailableReason ?? null
|
|
249
|
-
}
|
|
250
|
-
});
|
|
246
|
+
this.emitRouteDecided(runId, request, routeSelection, providers.length);
|
|
251
247
|
// Defensive only: streaming eligibility is decided by the route planner, which
|
|
252
248
|
// throws with its own rejection reasons when nothing can stream. Reaching here
|
|
253
249
|
// means a planned provider was unregistered between planning and selection.
|
|
@@ -17,25 +17,23 @@ export interface SharedPlannerModule {
|
|
|
17
17
|
planRouteJson: (inputJson: string) => string | Promise<string>;
|
|
18
18
|
}
|
|
19
19
|
/**
|
|
20
|
-
* Coarse, privacy-safe reason the WASM planner produced no route plan.
|
|
21
|
-
*
|
|
22
|
-
*
|
|
20
|
+
* Coarse, privacy-safe reason the WASM planner produced no route plan. Surfaced
|
|
21
|
+
* on the resulting `Internal` routing error and in the `console.warn`, so an
|
|
22
|
+
* operator can tell a missing bundle chunk from a blocked instantiation.
|
|
23
23
|
*/
|
|
24
24
|
export type WasmUnavailableReason = "import_failed" | "invalid_module_shape" | "init_failed" | "plan_failed";
|
|
25
25
|
/**
|
|
26
26
|
* Result of a {@link RoutePlanner} attempt. `routePlan` is `null` when the
|
|
27
|
-
* planner could not produce a plan,
|
|
28
|
-
*
|
|
29
|
-
* instead of silently swapping planners.
|
|
27
|
+
* planner could not produce a plan, and `unavailableReason` then narrows down
|
|
28
|
+
* why.
|
|
30
29
|
*/
|
|
31
30
|
export interface PlannerOutcome {
|
|
32
31
|
routePlan: SharedPlannerRoutePlan | null;
|
|
33
|
-
source: "wasm" | "unavailable";
|
|
34
32
|
unavailableReason?: WasmUnavailableReason;
|
|
35
33
|
}
|
|
36
34
|
/**
|
|
37
35
|
* Strategy that turns planner input into a deterministic route plan, or an
|
|
38
|
-
* "unavailable" outcome (
|
|
36
|
+
* "unavailable" outcome (which the router reports as a routing failure).
|
|
39
37
|
*/
|
|
40
38
|
export interface RoutePlanner {
|
|
41
39
|
planRoute(input: SharedPlannerInput): Promise<PlannerOutcome>;
|
|
@@ -55,13 +53,14 @@ export interface ProviderRuntimeSnapshot {
|
|
|
55
53
|
* Vite/webpack/Rollup can statically resolve and chunk it — a variable
|
|
56
54
|
* specifier defeats bundler analysis and silently never loads in the browser
|
|
57
55
|
* (see issue #109). The module is imported lazily and memoized; if it cannot
|
|
58
|
-
* be loaded, `planRoute` resolves an "unavailable" outcome
|
|
59
|
-
*
|
|
60
|
-
*
|
|
56
|
+
* be loaded, `planRoute` resolves an "unavailable" outcome, which the engine
|
|
57
|
+
* turns into an `Internal` routing failure carrying the reason below. There is
|
|
58
|
+
* no second planner to degrade to — a failure here is a failure to route, not a
|
|
59
|
+
* quieter route — so this also logs once via `console.warn` to name the cause.
|
|
61
60
|
*/
|
|
62
61
|
export declare class WasmRoutePlanner implements RoutePlanner {
|
|
63
62
|
private modulePromise?;
|
|
64
|
-
private
|
|
63
|
+
private loadFailure?;
|
|
65
64
|
private hasWarned;
|
|
66
65
|
planRoute(input: SharedPlannerInput): Promise<PlannerOutcome>;
|
|
67
66
|
private loadModule;
|
|
@@ -70,7 +69,11 @@ export declare class WasmRoutePlanner implements RoutePlanner {
|
|
|
70
69
|
}
|
|
71
70
|
/**
|
|
72
71
|
* Describes each provider and evaluates its dynamic capabilities against the
|
|
73
|
-
* host
|
|
72
|
+
* host.
|
|
73
|
+
*
|
|
74
|
+
* Registration order is passed through untouched: the shared planner ranks
|
|
75
|
+
* candidates itself and tie-breaks by provider id, so sorting here would only
|
|
76
|
+
* assert an ordering the planner is free to ignore.
|
|
74
77
|
*/
|
|
75
78
|
export declare function collectProviderRuntimeSnapshots(registryProviders: ProviderAdapter[], hostServices: HostServices): Promise<ProviderRuntimeSnapshot[]>;
|
|
76
79
|
/**
|
|
@@ -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;;;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
|
|
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;;;;GAIG;AACH,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,sBAAsB,GAAG,IAAI,CAAC;IACzC,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;;;;;;;;;;GAUG;AACH,qBAAa,gBAAiB,YAAW,YAAY;IACnD,OAAO,CAAC,aAAa,CAAC,CAAsC;IAC5D,OAAO,CAAC,WAAW,CAAC,CAAqD;IACzE,OAAO,CAAC,SAAS,CAAS;IAEpB,SAAS,CAAC,KAAK,EAAE,kBAAkB,GAAG,OAAO,CAAC,cAAc,CAAC;YAoBrD,UAAU;YAOV,YAAY;IA0B1B,OAAO,CAAC,QAAQ;CAOjB;AAED;;;;;;;GAOG;AACH,wBAAsB,+BAA+B,CACnD,iBAAiB,EAAE,eAAe,EAAE,EACpC,YAAY,EAAE,YAAY,GACzB,OAAO,CAAC,uBAAuB,EAAE,CAAC,CAapC;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"}
|
|
@@ -4,29 +4,32 @@
|
|
|
4
4
|
* Vite/webpack/Rollup can statically resolve and chunk it — a variable
|
|
5
5
|
* specifier defeats bundler analysis and silently never loads in the browser
|
|
6
6
|
* (see issue #109). The module is imported lazily and memoized; if it cannot
|
|
7
|
-
* be loaded, `planRoute` resolves an "unavailable" outcome
|
|
8
|
-
*
|
|
9
|
-
*
|
|
7
|
+
* be loaded, `planRoute` resolves an "unavailable" outcome, which the engine
|
|
8
|
+
* turns into an `Internal` routing failure carrying the reason below. There is
|
|
9
|
+
* no second planner to degrade to — a failure here is a failure to route, not a
|
|
10
|
+
* quieter route — so this also logs once via `console.warn` to name the cause.
|
|
10
11
|
*/
|
|
11
12
|
export class WasmRoutePlanner {
|
|
12
13
|
modulePromise;
|
|
13
|
-
|
|
14
|
+
loadFailure;
|
|
14
15
|
hasWarned = false;
|
|
15
16
|
async planRoute(input) {
|
|
16
17
|
const module = await this.loadModule();
|
|
17
18
|
if (!module) {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
// The load path only records why it failed; warning here covers both the
|
|
20
|
+
// first attempt and every later call served from the memoized promise.
|
|
21
|
+
const unavailableReason = this.loadFailure?.reason ?? "import_failed";
|
|
22
|
+
this.warnOnce(unavailableReason, this.loadFailure?.error);
|
|
23
|
+
return { routePlan: null, unavailableReason };
|
|
21
24
|
}
|
|
22
25
|
try {
|
|
23
26
|
const json = JSON.stringify(input);
|
|
24
27
|
const result = await module.planRouteJson(json);
|
|
25
|
-
return { routePlan: JSON.parse(result)
|
|
28
|
+
return { routePlan: JSON.parse(result) };
|
|
26
29
|
}
|
|
27
30
|
catch (error) {
|
|
28
31
|
this.warnOnce("plan_failed", error);
|
|
29
|
-
return { routePlan: null,
|
|
32
|
+
return { routePlan: null, unavailableReason: "plan_failed" };
|
|
30
33
|
}
|
|
31
34
|
}
|
|
32
35
|
async loadModule() {
|
|
@@ -41,13 +44,11 @@ export class WasmRoutePlanner {
|
|
|
41
44
|
mod = (await import("@independo/inderun-route-core-wasm"));
|
|
42
45
|
}
|
|
43
46
|
catch (error) {
|
|
44
|
-
this.
|
|
45
|
-
this.warnOnce("import_failed", error);
|
|
47
|
+
this.loadFailure = { reason: "import_failed", error };
|
|
46
48
|
return null;
|
|
47
49
|
}
|
|
48
50
|
if (typeof mod.planRouteJson !== "function") {
|
|
49
|
-
this.
|
|
50
|
-
this.warnOnce("invalid_module_shape");
|
|
51
|
+
this.loadFailure = { reason: "invalid_module_shape" };
|
|
51
52
|
return null;
|
|
52
53
|
}
|
|
53
54
|
if (mod.initSharedCore) {
|
|
@@ -55,8 +56,7 @@ export class WasmRoutePlanner {
|
|
|
55
56
|
await mod.initSharedCore();
|
|
56
57
|
}
|
|
57
58
|
catch (error) {
|
|
58
|
-
this.
|
|
59
|
-
this.warnOnce("init_failed", error);
|
|
59
|
+
this.loadFailure = { reason: "init_failed", error };
|
|
60
60
|
return null;
|
|
61
61
|
}
|
|
62
62
|
}
|
|
@@ -67,15 +67,19 @@ export class WasmRoutePlanner {
|
|
|
67
67
|
return;
|
|
68
68
|
}
|
|
69
69
|
this.hasWarned = true;
|
|
70
|
-
console.warn(`[IndeRun] WASM route planner unavailable (${reason});
|
|
70
|
+
console.warn(`[IndeRun] WASM route planner unavailable (${reason}); routing will fail.`, error);
|
|
71
71
|
}
|
|
72
72
|
}
|
|
73
73
|
/**
|
|
74
74
|
* Describes each provider and evaluates its dynamic capabilities against the
|
|
75
|
-
* host
|
|
75
|
+
* host.
|
|
76
|
+
*
|
|
77
|
+
* Registration order is passed through untouched: the shared planner ranks
|
|
78
|
+
* candidates itself and tie-breaks by provider id, so sorting here would only
|
|
79
|
+
* assert an ordering the planner is free to ignore.
|
|
76
80
|
*/
|
|
77
81
|
export async function collectProviderRuntimeSnapshots(registryProviders, hostServices) {
|
|
78
|
-
|
|
82
|
+
return Promise.all(registryProviders.map(async (provider) => {
|
|
79
83
|
const descriptor = provider.describe();
|
|
80
84
|
const capabilities = await provider.capabilities(hostServices);
|
|
81
85
|
return {
|
|
@@ -84,7 +88,6 @@ export async function collectProviderRuntimeSnapshots(registryProviders, hostSer
|
|
|
84
88
|
capabilities: toSharedPlannerCapabilities(capabilities, descriptor, provider)
|
|
85
89
|
};
|
|
86
90
|
}));
|
|
87
|
-
return snapshots.sort((left, right) => left.descriptor.id.localeCompare(right.descriptor.id));
|
|
88
91
|
}
|
|
89
92
|
/**
|
|
90
93
|
* Assembles the normalized {@link SharedPlannerInput} the shared route core
|
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 InteractionMode, type RoutePlanner, type SharedPlannerRoutePlan
|
|
5
|
+
import { type InteractionMode, type RoutePlanner, type SharedPlannerRoutePlan } from "./route-planner.js";
|
|
6
6
|
/**
|
|
7
7
|
* Output structure from the Router containing the chosen provider and a route selection description.
|
|
8
8
|
*/
|
|
@@ -23,16 +23,6 @@ export interface RouteSelection {
|
|
|
23
23
|
* Explanation detailing the selection decision. Useful for debugging and telemetry.
|
|
24
24
|
*/
|
|
25
25
|
explanation: string;
|
|
26
|
-
/**
|
|
27
|
-
* Which planner produced `routePlan`: the shared Rust/WASM core, or the
|
|
28
|
-
* local TypeScript fallback used when the WASM planner is unavailable.
|
|
29
|
-
*/
|
|
30
|
-
plannerSource: "wasm" | "fallback";
|
|
31
|
-
/**
|
|
32
|
-
* Set when `plannerSource` is `"fallback"` because the WASM planner failed;
|
|
33
|
-
* `undefined` when the fallback was not caused by a planner failure.
|
|
34
|
-
*/
|
|
35
|
-
plannerUnavailableReason?: WasmUnavailableReason;
|
|
36
26
|
}
|
|
37
27
|
/**
|
|
38
28
|
* Routing engine module. Filters registered providers against task types, request constraints,
|
|
@@ -59,35 +49,7 @@ export declare class Router {
|
|
|
59
49
|
*/
|
|
60
50
|
selectRoute(request: TaskRequest, hostServices: HostServices, interactionMode?: InteractionMode): Promise<RouteSelection>;
|
|
61
51
|
private selectFromSharedPlan;
|
|
62
|
-
private selectFallbackRoute;
|
|
63
52
|
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
|
-
*/
|
|
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;
|
|
90
|
-
private buildFallbackFailureSummary;
|
|
91
53
|
/**
|
|
92
54
|
* Routing failure throws before any `route_decided` telemetry is emitted, so the
|
|
93
55
|
* plan's diagnostics are attached to the exception — that is the only channel
|
|
@@ -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,EAGL,KAAK,eAAe,EAEpB,KAAK,YAAY,
|
|
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,EACjB,KAAK,sBAAsB,EAE5B,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;CACrB;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,2BAA2B;IAuBnC;;;;OAIG;IACH,OAAO,CAAC,gBAAgB;CAkBzB"}
|
package/dist/core/router.js
CHANGED
|
@@ -31,25 +31,24 @@ export class Router {
|
|
|
31
31
|
const snapshots = await collectProviderRuntimeSnapshots(this.registry.list(), hostServices);
|
|
32
32
|
const planInput = buildSharedPlannerInput(request, snapshots, online, interactionMode);
|
|
33
33
|
const outcome = await this.planner.planRoute(planInput);
|
|
34
|
-
if (outcome.routePlan) {
|
|
35
|
-
|
|
34
|
+
if (!outcome.routePlan) {
|
|
35
|
+
// There is no second planner to degrade to: routing is the shared Rust
|
|
36
|
+
// core's semantics or nothing. Failing here keeps provider selection
|
|
37
|
+
// identical everywhere rather than forking it on a module load failure.
|
|
38
|
+
const reason = outcome.unavailableReason ?? "import_failed";
|
|
39
|
+
throw createInternal(`Route planner unavailable (${reason}).`, {
|
|
40
|
+
details: { plannerUnavailableReason: reason }
|
|
41
|
+
});
|
|
36
42
|
}
|
|
37
|
-
return this.
|
|
43
|
+
return this.selectFromSharedPlan(snapshots, outcome.routePlan);
|
|
38
44
|
}
|
|
39
45
|
selectFromSharedPlan(snapshots, routePlan) {
|
|
40
46
|
if (!routePlan.selectedProviderId) {
|
|
41
47
|
throw this.routePlanFailure(routePlan);
|
|
42
48
|
}
|
|
43
|
-
return this.buildSelectionFromRoutePlan(snapshots, routePlan
|
|
44
|
-
}
|
|
45
|
-
selectFallbackRoute(request, snapshots, online, interactionMode, plannerUnavailableReason) {
|
|
46
|
-
const plan = this.createFallbackPlan(request, snapshots, online, interactionMode);
|
|
47
|
-
if (!plan.selectedProviderId) {
|
|
48
|
-
throw this.routePlanFailure(plan);
|
|
49
|
-
}
|
|
50
|
-
return this.buildSelectionFromRoutePlan(snapshots, plan, "fallback", plannerUnavailableReason);
|
|
49
|
+
return this.buildSelectionFromRoutePlan(snapshots, routePlan);
|
|
51
50
|
}
|
|
52
|
-
buildSelectionFromRoutePlan(snapshots, routePlan
|
|
51
|
+
buildSelectionFromRoutePlan(snapshots, routePlan) {
|
|
53
52
|
const orderedProviders = routePlan.candidates
|
|
54
53
|
.map((candidate) => snapshots.find((snapshot) => snapshot.descriptor.id === candidate.providerId))
|
|
55
54
|
.filter((snapshot) => snapshot !== undefined);
|
|
@@ -61,202 +60,9 @@ export class Router {
|
|
|
61
60
|
provider: selected.provider,
|
|
62
61
|
fallbackProviders: orderedProviders.slice(1).map((snapshot) => snapshot.provider),
|
|
63
62
|
routePlan,
|
|
64
|
-
explanation: routePlan.explanation.summary
|
|
65
|
-
plannerSource,
|
|
66
|
-
...(plannerUnavailableReason ? { plannerUnavailableReason } : {})
|
|
63
|
+
explanation: routePlan.explanation.summary
|
|
67
64
|
};
|
|
68
65
|
}
|
|
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));
|
|
81
|
-
const localCandidates = eligible.filter((candidate) => candidate.descriptor.type !== "cloud");
|
|
82
|
-
const cloudCandidates = eligible.filter((candidate) => candidate.descriptor.type === "cloud");
|
|
83
|
-
// Constraint filtering must apply to the whole candidate list, not just the selected provider:
|
|
84
|
-
// the remainder becomes the engine's fallback chain, and a fallback that violates the request
|
|
85
|
-
// constraints would let a `local_required` run be retried against a cloud provider.
|
|
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);
|
|
92
|
-
}
|
|
93
|
-
else {
|
|
94
|
-
rejectedProviders.push({ providerId: candidate.descriptor.id, reasons });
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
if (admissible.length === 0) {
|
|
98
|
-
const failureSummary = this.buildFallbackFailureSummary({
|
|
99
|
-
online,
|
|
100
|
-
interactionMode,
|
|
101
|
-
taskKind: planInput.task.kind,
|
|
102
|
-
constraints: planInput.constraints,
|
|
103
|
-
localCandidates,
|
|
104
|
-
cloudCandidates
|
|
105
|
-
});
|
|
106
|
-
const failureCode = this.inferFallbackFailureCode(planInput, rejectedProviders);
|
|
107
|
-
return {
|
|
108
|
-
fallbackProviderIds: [],
|
|
109
|
-
candidates: [],
|
|
110
|
-
rejectedProviders,
|
|
111
|
-
failureCode,
|
|
112
|
-
explanation: {
|
|
113
|
-
summary: failureSummary
|
|
114
|
-
}
|
|
115
|
-
};
|
|
116
|
-
}
|
|
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).`;
|
|
121
|
-
return {
|
|
122
|
-
selectedProviderId,
|
|
123
|
-
fallbackProviderIds: admissible.slice(1).map((candidate) => candidate.descriptor.id),
|
|
124
|
-
candidates: admissible.map((candidate, index) => ({
|
|
125
|
-
providerId: candidate.descriptor.id,
|
|
126
|
-
order: index
|
|
127
|
-
})),
|
|
128
|
-
rejectedProviders,
|
|
129
|
-
explanation: {
|
|
130
|
-
summary,
|
|
131
|
-
selectedProviderId
|
|
132
|
-
}
|
|
133
|
-
};
|
|
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
|
-
}
|
|
235
|
-
buildFallbackFailureSummary(input) {
|
|
236
|
-
const wantsCloud = input.constraints.cloud === "required" || input.constraints.privacy === "cloud_required";
|
|
237
|
-
const wantsLocal = input.constraints.privacy === "local_required";
|
|
238
|
-
if (!input.online && (wantsCloud || input.cloudCandidates.length > 0)) {
|
|
239
|
-
return "No network connection is available.";
|
|
240
|
-
}
|
|
241
|
-
if (wantsCloud) {
|
|
242
|
-
if (input.cloudCandidates.length === 0) {
|
|
243
|
-
return "No cloud provider found.";
|
|
244
|
-
}
|
|
245
|
-
return "No cloud provider is currently available.";
|
|
246
|
-
}
|
|
247
|
-
if (wantsLocal) {
|
|
248
|
-
if (input.localCandidates.length === 0) {
|
|
249
|
-
return "No on-device provider found.";
|
|
250
|
-
}
|
|
251
|
-
return "No on-device provider is currently available.";
|
|
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
|
-
}
|
|
258
|
-
return "No eligible provider found for the current routing constraints.";
|
|
259
|
-
}
|
|
260
66
|
/**
|
|
261
67
|
* Routing failure throws before any `route_decided` telemetry is emitted, so the
|
|
262
68
|
* plan's diagnostics are attached to the exception — that is the only channel
|
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.9",
|
|
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.9",
|
|
59
|
+
"@independo/inderun-route-core-wasm": "^0.3.0-dev.9"
|
|
60
60
|
},
|
|
61
61
|
"peerDependencies": {
|
|
62
62
|
"@huggingface/transformers": "^4.0.0"
|
|
@@ -69,7 +69,7 @@
|
|
|
69
69
|
"devDependencies": {
|
|
70
70
|
"@types/node": "^26.2.0",
|
|
71
71
|
"typescript": "^6.0.3",
|
|
72
|
-
"vitest": "^4.1.
|
|
72
|
+
"vitest": "^4.1.11"
|
|
73
73
|
},
|
|
74
74
|
"scripts": {
|
|
75
75
|
"build": "tsc -p tsconfig.json",
|