@independo/inderun-web 0.4.0 → 0.5.0-rc.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/dist/core/artifacts.d.ts +110 -0
- package/dist/core/artifacts.d.ts.map +1 -0
- package/dist/core/artifacts.js +190 -0
- package/dist/core/engine.d.ts +28 -1
- package/dist/core/engine.d.ts.map +1 -1
- package/dist/core/engine.js +232 -28
- package/dist/core/generated/inderun-api.d.ts +12 -4
- package/dist/core/generated/inderun-api.d.ts.map +1 -1
- package/dist/core/host.d.ts +1 -1
- package/dist/core/host.d.ts.map +1 -1
- package/dist/core/provenance.d.ts +5 -2
- package/dist/core/provenance.d.ts.map +1 -1
- package/dist/core/provenance.js +6 -2
- package/dist/core/provider.d.ts +50 -130
- package/dist/core/provider.d.ts.map +1 -1
- package/dist/core/route-planner.d.ts +5 -5
- package/dist/core/route-planner.d.ts.map +1 -1
- package/dist/core/route-planner.js +15 -29
- package/dist/core/router.d.ts +16 -0
- package/dist/core/router.d.ts.map +1 -1
- package/dist/core/router.js +14 -5
- package/dist/core/task-profiles.d.ts +54 -0
- package/dist/core/task-profiles.d.ts.map +1 -0
- package/dist/core/task-profiles.js +92 -0
- package/dist/facade/text.d.ts.map +1 -1
- package/dist/facade/text.js +5 -3
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/providers/onnx/runtime.d.ts +2 -2
- package/dist/providers/onnx/runtime.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/core/artifacts.ts +280 -0
- package/src/core/engine.ts +281 -27
- package/src/core/generated/inderun-api.ts +12 -4
- package/src/core/host.ts +1 -0
- package/src/core/provenance.ts +7 -2
- package/src/core/provider.ts +64 -135
- package/src/core/route-planner.ts +20 -36
- package/src/core/router.ts +30 -7
- package/src/core/task-profiles.ts +145 -0
- package/src/facade/text.ts +5 -3
- package/src/index.ts +6 -1
- package/src/providers/onnx/runtime.ts +2 -2
package/dist/core/engine.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { getTaskRequestValidationIssues } from "@independo/inderun-contracts";
|
|
1
|
+
import { getArtifactReadRequestValidationIssues, getTaskRequestValidationIssues } from "@independo/inderun-contracts";
|
|
2
2
|
import { ProviderRegistry } from "./registry.js";
|
|
3
3
|
import { Router } from "./router.js";
|
|
4
4
|
import { createCapabilityMismatch, createInternal, createUnavailable, toIndeRunException } from "./errors.js";
|
|
@@ -6,6 +6,8 @@ import { NoOpTelemetryService } from "./telemetry.js";
|
|
|
6
6
|
import { EventGate } from "./event-gate.js";
|
|
7
7
|
import { AUTO_MODEL_PROFILE, resolveModelProfile, validateModelProfiles } from "./model-profiles.js";
|
|
8
8
|
import { ProvenanceRecorder } from "./provenance.js";
|
|
9
|
+
import { checkOutput, recheckBoundVoice, taskProfileFor } from "./task-profiles.js";
|
|
10
|
+
import { ArtifactStore } from "./artifacts.js";
|
|
9
11
|
/**
|
|
10
12
|
* Main orchestrator SDK entrypoint class.
|
|
11
13
|
* Validates tasks, executes routing rules based on request constraints and connectivity,
|
|
@@ -15,6 +17,7 @@ export class IndeRun {
|
|
|
15
17
|
registry;
|
|
16
18
|
hostServices;
|
|
17
19
|
router;
|
|
20
|
+
artifacts;
|
|
18
21
|
telemetryService;
|
|
19
22
|
modelProfiles;
|
|
20
23
|
/**
|
|
@@ -32,6 +35,7 @@ export class IndeRun {
|
|
|
32
35
|
options.telemetryService || this.hostServices.telemetry || new NoOpTelemetryService();
|
|
33
36
|
this.modelProfiles = options.modelProfiles ?? {};
|
|
34
37
|
validateModelProfiles(this.modelProfiles);
|
|
38
|
+
this.artifacts = new ArtifactStore(options.artifacts);
|
|
35
39
|
}
|
|
36
40
|
/**
|
|
37
41
|
* Validates the request against the schema and resolves its model profile.
|
|
@@ -40,6 +44,12 @@ export class IndeRun {
|
|
|
40
44
|
*/
|
|
41
45
|
validateRequest(request, runId) {
|
|
42
46
|
const validationIssues = getTaskRequestValidationIssues(request);
|
|
47
|
+
if (validationIssues.length === 0 && taskProfileFor(request.task.kind) === undefined) {
|
|
48
|
+
validationIssues.push({
|
|
49
|
+
path: "/task/kind",
|
|
50
|
+
message: `task kind "${request.task.kind}" is not supported by this engine`
|
|
51
|
+
});
|
|
52
|
+
}
|
|
43
53
|
if (validationIssues.length === 0) {
|
|
44
54
|
const resolved = resolveModelProfile(this.modelProfiles, request.modelProfile);
|
|
45
55
|
if (resolved.known) {
|
|
@@ -133,25 +143,52 @@ export class IndeRun {
|
|
|
133
143
|
const startTime = this.now();
|
|
134
144
|
const runId = request.requestId || `run_${Math.random().toString(36).substring(2, 11)}`;
|
|
135
145
|
const provenance = new ProvenanceRecorder(request.modelProfile ?? AUTO_MODEL_PROFILE);
|
|
146
|
+
// The provider a failed run had committed to, which the failure is attributed to.
|
|
147
|
+
let committedProvider;
|
|
136
148
|
try {
|
|
137
149
|
// 1. Validate request payload using schema contracts and resolve its model profile
|
|
138
150
|
const modelProfile = this.validateRequest(request, runId);
|
|
151
|
+
await this.checkDestination(request, runId);
|
|
139
152
|
// 2. Select the route based on constraints, model profile, and host capabilities
|
|
140
153
|
const routeSelection = await this.router.selectRoute(request, this.hostServices, "run", modelProfile);
|
|
141
154
|
const providers = [routeSelection.provider, ...routeSelection.fallbackProviders];
|
|
142
155
|
provenance.routed(routeSelection.routePlan);
|
|
156
|
+
const delivery = taskProfileFor(request.task.kind).delivery(request);
|
|
143
157
|
this.emitRouteDecided(runId, request, routeSelection, providers.length);
|
|
144
|
-
// 3. Execute the run task using the planned provider chain
|
|
158
|
+
// 3. Execute the run task using the planned provider chain. An attempt that committed
|
|
159
|
+
// (see RunContext.commit) ends the chain: its failure is the run's failure.
|
|
145
160
|
const attemptedProviderIds = [];
|
|
146
161
|
let lastError;
|
|
147
162
|
for (const [index, provider] of providers.entries()) {
|
|
148
163
|
const providerId = provider.describe().id;
|
|
164
|
+
const voice = routeSelection.candidates[index]?.voice;
|
|
149
165
|
attemptedProviderIds.push(providerId);
|
|
166
|
+
let committed = false;
|
|
167
|
+
// Closed when the attempt ends: a provider callback that fires after the run moved
|
|
168
|
+
// on must not be told it may start, say, audible playback.
|
|
169
|
+
let attemptOpen = true;
|
|
170
|
+
const commit = () => {
|
|
171
|
+
if (!attemptOpen)
|
|
172
|
+
return false;
|
|
173
|
+
committed = true;
|
|
174
|
+
return true;
|
|
175
|
+
};
|
|
176
|
+
const attemptArtifacts = this.artifacts.attempt(commit);
|
|
177
|
+
let keptArtifactId;
|
|
150
178
|
try {
|
|
179
|
+
const refusal = recheckBoundVoice(request, providerId, voice);
|
|
180
|
+
if (refusal !== undefined)
|
|
181
|
+
throw refusal;
|
|
151
182
|
const result = await provider.run(request, {
|
|
152
183
|
runId,
|
|
153
|
-
hostServices: this.hostServices
|
|
184
|
+
hostServices: this.hostServices,
|
|
185
|
+
...(voice !== undefined ? { voice } : {}),
|
|
186
|
+
commit,
|
|
187
|
+
artifacts: attemptArtifacts
|
|
154
188
|
});
|
|
189
|
+
result.output = this.normalizeOutput(request, delivery, result.output, providerId, attemptArtifacts);
|
|
190
|
+
result.output = await this.deliverToDestination(request, result.output, providerId);
|
|
191
|
+
keptArtifactId = engineOwnedArtifactId(result.output);
|
|
155
192
|
const endTime = this.now();
|
|
156
193
|
const totalMs = endTime - startTime;
|
|
157
194
|
result.runId = runId;
|
|
@@ -160,7 +197,7 @@ export class IndeRun {
|
|
|
160
197
|
providerUsed: providerId,
|
|
161
198
|
totalMs
|
|
162
199
|
};
|
|
163
|
-
provenance.attempted(providerId, "succeeded");
|
|
200
|
+
provenance.attempted(providerId, "succeeded", undefined, committed);
|
|
164
201
|
result.provenance = provenance.build(provider.describe(), result.telemetry.reportedModel);
|
|
165
202
|
this.safeEmit({
|
|
166
203
|
type: "attempt_succeeded",
|
|
@@ -185,8 +222,16 @@ export class IndeRun {
|
|
|
185
222
|
routePlan: routeSelection.routePlan
|
|
186
223
|
}
|
|
187
224
|
});
|
|
188
|
-
provenance.attempted(providerId, "failed", attemptError.errorClass);
|
|
225
|
+
provenance.attempted(providerId, "failed", attemptError.errorClass, committed);
|
|
189
226
|
lastError = attemptError;
|
|
227
|
+
if (committed) {
|
|
228
|
+
committedProvider = provider;
|
|
229
|
+
break;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
finally {
|
|
233
|
+
attemptOpen = false;
|
|
234
|
+
attemptArtifacts.release(keptArtifactId);
|
|
190
235
|
}
|
|
191
236
|
}
|
|
192
237
|
const endTime = this.now();
|
|
@@ -213,8 +258,9 @@ export class IndeRun {
|
|
|
213
258
|
});
|
|
214
259
|
// A routing refusal throws before any attempt; its rejections travel on the details.
|
|
215
260
|
provenance.routingFailed(normalized.details);
|
|
216
|
-
// No attempt produced the result, so the outcome is attributed to no provider
|
|
217
|
-
|
|
261
|
+
// No attempt produced the result, so the outcome is attributed to no provider -- unless
|
|
262
|
+
// the run had committed to one, which then owns the failure.
|
|
263
|
+
const exception = normalized.withProvenance(provenance.build(committedProvider?.describe()));
|
|
218
264
|
const endTime = this.now();
|
|
219
265
|
const totalMs = typeof exception.details?.totalMs === "number"
|
|
220
266
|
? exception.details?.totalMs
|
|
@@ -259,6 +305,7 @@ export class IndeRun {
|
|
|
259
305
|
let modelProfile;
|
|
260
306
|
try {
|
|
261
307
|
modelProfile = this.validateRequest(request, runId);
|
|
308
|
+
await this.checkDestination(request, runId);
|
|
262
309
|
}
|
|
263
310
|
catch (error) {
|
|
264
311
|
throw toIndeRunException(error, { runId }).withProvenance(provenance.build());
|
|
@@ -298,19 +345,33 @@ export class IndeRun {
|
|
|
298
345
|
startedAt: startTime,
|
|
299
346
|
providerId: providers[0].describe().id
|
|
300
347
|
};
|
|
348
|
+
const delivery = taskProfileFor(request.task.kind).delivery(request);
|
|
349
|
+
const textRun = delivery === "inline";
|
|
350
|
+
const artifacts = this.artifacts;
|
|
351
|
+
const normalizeOutput = this.normalizeOutput.bind(this);
|
|
352
|
+
const deliverToDestination = this.deliverToDestination.bind(this);
|
|
301
353
|
async function* run() {
|
|
302
354
|
const attemptedProviderIds = [];
|
|
355
|
+
// Set once, by whichever comes first in an attempt: the provider's commit(), its first
|
|
356
|
+
// admitted content event, or its result handoff. After it no other provider is tried.
|
|
303
357
|
let committed = false;
|
|
304
|
-
|
|
358
|
+
// Whether the committed attempt was managed playback that may have been heard.
|
|
359
|
+
let playbackCommitted = false;
|
|
360
|
+
const cancelledOutcome = (partialText, cancelledProvider, delivered) => {
|
|
305
361
|
const reason = typeof controller.signal.reason === "string" ? controller.signal.reason : undefined;
|
|
306
362
|
if (cancelledProvider !== undefined) {
|
|
307
|
-
provenance.attempted(cancelledProvider.describe().id, "cancelled");
|
|
363
|
+
provenance.attempted(cancelledProvider.describe().id, "cancelled", undefined, committed);
|
|
308
364
|
}
|
|
309
365
|
return {
|
|
310
366
|
outcome: "cancelled",
|
|
311
367
|
runId,
|
|
312
368
|
schemaVersion: "1.0",
|
|
313
|
-
|
|
369
|
+
// A text run reports what it delivered; committed playback reports that it played; an
|
|
370
|
+
// artifact already written to its destination is reported, since the application
|
|
371
|
+
// owns those bytes now. An uncommitted non-text run produced nothing to report.
|
|
372
|
+
...(textRun ? { partialText } : {}),
|
|
373
|
+
...(playbackCommitted ? { output: { type: "playback", playback: {} } } : {}),
|
|
374
|
+
...(delivered !== undefined ? { output: delivered } : {}),
|
|
314
375
|
...(reason !== undefined ? { reason } : {}),
|
|
315
376
|
provenance: provenance.build(cancelledProvider?.describe())
|
|
316
377
|
};
|
|
@@ -320,6 +381,7 @@ export class IndeRun {
|
|
|
320
381
|
break;
|
|
321
382
|
}
|
|
322
383
|
const providerId = provider.describe().id;
|
|
384
|
+
const voice = routeSelection.candidates[index]?.voice;
|
|
323
385
|
attemptedProviderIds.push(providerId);
|
|
324
386
|
safeEmit({
|
|
325
387
|
type: "stream_attempt_started",
|
|
@@ -327,12 +389,41 @@ export class IndeRun {
|
|
|
327
389
|
timestamp: now(),
|
|
328
390
|
payload: { providerId, fallbackOccurred: index > 0 }
|
|
329
391
|
});
|
|
392
|
+
const markCommitted = () => {
|
|
393
|
+
if (committed)
|
|
394
|
+
return;
|
|
395
|
+
committed = true;
|
|
396
|
+
playbackCommitted = delivery === "playback";
|
|
397
|
+
safeEmit({
|
|
398
|
+
type: "stream_attempt_succeeded",
|
|
399
|
+
runId,
|
|
400
|
+
timestamp: now(),
|
|
401
|
+
payload: { providerId, attemptedProviderIds }
|
|
402
|
+
});
|
|
403
|
+
};
|
|
404
|
+
// Closed when the attempt ends. A provider callback that fires after the run fell back
|
|
405
|
+
// must neither start audio nor commit the attempt that replaced this one.
|
|
406
|
+
let attemptOpen = true;
|
|
407
|
+
const commit = () => {
|
|
408
|
+
if (!attemptOpen || controller.signal.aborted || gate.isTerminated())
|
|
409
|
+
return false;
|
|
410
|
+
markCommitted();
|
|
411
|
+
return true;
|
|
412
|
+
};
|
|
413
|
+
const attemptArtifacts = artifacts.attempt(commit);
|
|
414
|
+
let keptArtifactId;
|
|
330
415
|
let partialText = "";
|
|
331
416
|
try {
|
|
417
|
+
const refusal = recheckBoundVoice(request, providerId, voice);
|
|
418
|
+
if (refusal !== undefined)
|
|
419
|
+
throw refusal;
|
|
332
420
|
const iterable = provider.stream(request, {
|
|
333
421
|
runId,
|
|
334
422
|
hostServices: hostServices,
|
|
335
|
-
signal: controller.signal
|
|
423
|
+
signal: controller.signal,
|
|
424
|
+
...(voice !== undefined ? { voice } : {}),
|
|
425
|
+
commit,
|
|
426
|
+
artifacts: attemptArtifacts
|
|
336
427
|
});
|
|
337
428
|
let terminatedInLoop = false;
|
|
338
429
|
for await (const ev of iterable) {
|
|
@@ -343,16 +434,12 @@ export class IndeRun {
|
|
|
343
434
|
throw ev.error;
|
|
344
435
|
}
|
|
345
436
|
if (ev.kind === "delta" || ev.kind === "snapshot") {
|
|
346
|
-
|
|
347
|
-
if (!
|
|
348
|
-
|
|
349
|
-
safeEmit({
|
|
350
|
-
type: "stream_attempt_succeeded",
|
|
351
|
-
runId,
|
|
352
|
-
timestamp: now(),
|
|
353
|
-
payload: { providerId, attemptedProviderIds }
|
|
354
|
-
});
|
|
437
|
+
// Content events are text-only (ADR 016 §5).
|
|
438
|
+
if (!textRun) {
|
|
439
|
+
throw checkOutput(request, delivery, { type: "text", text: "" }, providerId);
|
|
355
440
|
}
|
|
441
|
+
partialText = ev.kind === "delta" ? partialText + ev.text : ev.text;
|
|
442
|
+
markCommitted();
|
|
356
443
|
const streamEvent = gate.admit({
|
|
357
444
|
timestamp: now(),
|
|
358
445
|
type: ev.kind === "delta" ? "content_delta" : "content_snapshot",
|
|
@@ -362,16 +449,42 @@ export class IndeRun {
|
|
|
362
449
|
yield streamEvent;
|
|
363
450
|
continue;
|
|
364
451
|
}
|
|
365
|
-
// ev.kind === "done"
|
|
366
|
-
|
|
452
|
+
// ev.kind === "done" (text) or "result" (any task): the run's final output.
|
|
453
|
+
const output = normalizeOutput(request, delivery, ev.kind === "done" ? { type: "text", text: ev.finalText } : ev.output, providerId, attemptArtifacts);
|
|
454
|
+
markCommitted();
|
|
455
|
+
const delivered = await deliverToDestination(request, output, providerId);
|
|
456
|
+
// A cancel that landed during the destination write did not interrupt it, but the
|
|
457
|
+
// run still ends cancelled. Bytes already at the destination are the application's,
|
|
458
|
+
// so the outcome reports them; anything else the usual cancel path below releases.
|
|
459
|
+
if (controller.signal.aborted) {
|
|
460
|
+
if (delivered.artifact?.owner !== "application")
|
|
461
|
+
break;
|
|
462
|
+
const terminal = gate.terminate(cancelledOutcome(partialText, provider, delivered), now());
|
|
463
|
+
safeEmit({
|
|
464
|
+
type: "stream_cancelled",
|
|
465
|
+
runId,
|
|
466
|
+
timestamp: now(),
|
|
467
|
+
payload: { providerId, attemptedProviderIds }
|
|
468
|
+
});
|
|
469
|
+
terminatedInLoop = true;
|
|
470
|
+
if (terminal)
|
|
471
|
+
yield terminal;
|
|
472
|
+
return;
|
|
473
|
+
}
|
|
474
|
+
keptArtifactId = engineOwnedArtifactId(delivered);
|
|
475
|
+
provenance.attempted(providerId, "succeeded", undefined, committed);
|
|
367
476
|
const outcome = {
|
|
368
477
|
outcome: "completed",
|
|
369
478
|
runId,
|
|
370
479
|
schemaVersion: "1.0",
|
|
371
|
-
|
|
480
|
+
...(ev.kind === "done"
|
|
481
|
+
? { finalText: ev.finalText }
|
|
482
|
+
: output.type === "text"
|
|
483
|
+
? { finalText: output.text, output }
|
|
484
|
+
: { output }),
|
|
372
485
|
telemetry: { providerUsed: providerId, totalMs: now() - startTime },
|
|
373
486
|
...(ev.finishReason !== undefined ? { finishReason: ev.finishReason } : {}),
|
|
374
|
-
...(ev.usage !== undefined ? { usage: ev.usage } : {}),
|
|
487
|
+
...(ev.kind === "done" && ev.usage !== undefined ? { usage: ev.usage } : {}),
|
|
375
488
|
provenance: provenance.build(provider.describe(), ev.reportedModel)
|
|
376
489
|
};
|
|
377
490
|
const terminal = gate.terminate(outcome, now());
|
|
@@ -390,6 +503,7 @@ export class IndeRun {
|
|
|
390
503
|
return;
|
|
391
504
|
}
|
|
392
505
|
if (controller.signal.aborted) {
|
|
506
|
+
attemptArtifacts.release();
|
|
393
507
|
const terminal = gate.terminate(cancelledOutcome(partialText, provider), now());
|
|
394
508
|
safeEmit({
|
|
395
509
|
type: "stream_cancelled",
|
|
@@ -401,11 +515,12 @@ export class IndeRun {
|
|
|
401
515
|
yield terminal;
|
|
402
516
|
return;
|
|
403
517
|
}
|
|
404
|
-
// Provider's iterable ended without a terminal "done"/"error" event.
|
|
518
|
+
// Provider's iterable ended without a terminal "done"/"result"/"error" event.
|
|
405
519
|
throw new Error(`Provider '${providerId}' stream ended without a terminal event.`);
|
|
406
520
|
}
|
|
407
521
|
catch (err) {
|
|
408
522
|
if (controller.signal.aborted) {
|
|
523
|
+
attemptArtifacts.release();
|
|
409
524
|
const terminal = gate.terminate(cancelledOutcome(partialText, provider), now());
|
|
410
525
|
safeEmit({
|
|
411
526
|
type: "stream_cancelled",
|
|
@@ -422,7 +537,7 @@ export class IndeRun {
|
|
|
422
537
|
runId,
|
|
423
538
|
details: { attemptedProviderIds }
|
|
424
539
|
});
|
|
425
|
-
provenance.attempted(providerId, "failed", exception.errorClass);
|
|
540
|
+
provenance.attempted(providerId, "failed", exception.errorClass, committed);
|
|
426
541
|
if (!committed) {
|
|
427
542
|
safeEmit({
|
|
428
543
|
type: "stream_attempt_failed",
|
|
@@ -437,12 +552,13 @@ export class IndeRun {
|
|
|
437
552
|
continue;
|
|
438
553
|
}
|
|
439
554
|
// Committed: the failure belongs to this provider, not to the chain.
|
|
555
|
+
attemptArtifacts.release();
|
|
440
556
|
const outcome = {
|
|
441
557
|
outcome: "error",
|
|
442
558
|
runId,
|
|
443
559
|
schemaVersion: "1.0",
|
|
444
560
|
error: exception.toContractError(),
|
|
445
|
-
partialText,
|
|
561
|
+
...(textRun ? { partialText } : {}),
|
|
446
562
|
provenance: provenance.build(provider.describe())
|
|
447
563
|
};
|
|
448
564
|
const terminal = gate.terminate(outcome, now());
|
|
@@ -461,6 +577,10 @@ export class IndeRun {
|
|
|
461
577
|
yield terminal;
|
|
462
578
|
return;
|
|
463
579
|
}
|
|
580
|
+
finally {
|
|
581
|
+
attemptOpen = false;
|
|
582
|
+
attemptArtifacts.release(keptArtifactId);
|
|
583
|
+
}
|
|
464
584
|
}
|
|
465
585
|
// Every provider failed pre-commit, or cancellation landed before any attempt started.
|
|
466
586
|
if (controller.signal.aborted) {
|
|
@@ -484,7 +604,7 @@ export class IndeRun {
|
|
|
484
604
|
runId,
|
|
485
605
|
schemaVersion: "1.0",
|
|
486
606
|
error: exception.toContractError(),
|
|
487
|
-
partialText: "",
|
|
607
|
+
...(textRun ? { partialText: "" } : {}),
|
|
488
608
|
provenance: provenance.build()
|
|
489
609
|
};
|
|
490
610
|
const terminal = gate.terminate(outcome, now());
|
|
@@ -507,6 +627,86 @@ export class IndeRun {
|
|
|
507
627
|
* Reports each registered provider's static descriptor and current dynamic capability check,
|
|
508
628
|
* without executing a task. Useful for UI that shows live provider availability before a run.
|
|
509
629
|
*/
|
|
630
|
+
async readArtifact(request) {
|
|
631
|
+
const validationIssues = getArtifactReadRequestValidationIssues(request);
|
|
632
|
+
if (validationIssues.length > 0) {
|
|
633
|
+
const message = `Validation failed for ArtifactReadRequest: ${validationIssues
|
|
634
|
+
.map((i) => `${i.path} - ${i.message}`)
|
|
635
|
+
.join("; ")}`;
|
|
636
|
+
throw createInternal(message, { details: { validationIssues } });
|
|
637
|
+
}
|
|
638
|
+
return this.artifacts.read(request);
|
|
639
|
+
}
|
|
640
|
+
async disposeArtifact(artifact) {
|
|
641
|
+
if (artifact.owner === "engine") {
|
|
642
|
+
this.artifacts.dispose(artifact.id);
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
/**
|
|
646
|
+
* Refuses, before routing, an artifact request whose `destinationRef` this host cannot
|
|
647
|
+
* write to: there is no point running a provider whose result has nowhere to go.
|
|
648
|
+
*/
|
|
649
|
+
async checkDestination(request, runId) {
|
|
650
|
+
const destinationRef = request.delivery?.destinationRef;
|
|
651
|
+
if (destinationRef === undefined)
|
|
652
|
+
return;
|
|
653
|
+
const destinations = this.hostServices.artifactDestinations;
|
|
654
|
+
if (destinations === undefined) {
|
|
655
|
+
throw createCapabilityMismatch("This host has no application artifact destinations.", {
|
|
656
|
+
runId,
|
|
657
|
+
details: { code: "artifact_destination_unsupported" }
|
|
658
|
+
});
|
|
659
|
+
}
|
|
660
|
+
if (!(await destinations.accepts(destinationRef, request.delivery?.mediaTypes))) {
|
|
661
|
+
throw createCapabilityMismatch("The host refused the requested artifact destination.", {
|
|
662
|
+
runId,
|
|
663
|
+
details: { code: "artifact_destination_refused" }
|
|
664
|
+
});
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
/**
|
|
668
|
+
* Writes a finished artifact to the request's application destination, if it names one,
|
|
669
|
+
* and drops the engine's copy: the result's artifact is then application-owned. The run
|
|
670
|
+
* has committed by now, so a failed write is the run's failure, never a fallback.
|
|
671
|
+
*/
|
|
672
|
+
async deliverToDestination(request, output, providerId) {
|
|
673
|
+
const destinationRef = request.delivery?.destinationRef;
|
|
674
|
+
const artifact = output.artifact;
|
|
675
|
+
if (destinationRef === undefined || artifact === undefined)
|
|
676
|
+
return output;
|
|
677
|
+
try {
|
|
678
|
+
await this.hostServices.artifactDestinations.write(destinationRef, artifact, this.artifacts.bytes(artifact.id));
|
|
679
|
+
}
|
|
680
|
+
catch {
|
|
681
|
+
// The host's error stays out of the run's error: it may name the destination.
|
|
682
|
+
this.artifacts.dispose(artifact.id);
|
|
683
|
+
throw createInternal("Writing the artifact to its application destination failed.", {
|
|
684
|
+
providerId,
|
|
685
|
+
details: { code: "artifact_destination_write_failed" }
|
|
686
|
+
});
|
|
687
|
+
}
|
|
688
|
+
this.artifacts.dispose(artifact.id);
|
|
689
|
+
return { ...output, artifact: { ...artifact, owner: "application", destinationRef } };
|
|
690
|
+
}
|
|
691
|
+
/**
|
|
692
|
+
* Checks that an artifact in a provider's output is one this attempt produced, swaps in the
|
|
693
|
+
* reference the store holds, and only then checks the output against the requested delivery:
|
|
694
|
+
* the artifact's media type, size and ownership are the engine's record rather than the
|
|
695
|
+
* provider's claim.
|
|
696
|
+
*/
|
|
697
|
+
normalizeOutput(request, delivery, output, providerId, attemptArtifacts) {
|
|
698
|
+
let resolved = output;
|
|
699
|
+
if (output?.artifact !== undefined) {
|
|
700
|
+
if (!attemptArtifacts.owns(output.artifact.id)) {
|
|
701
|
+
throw createInternal(`Provider '${providerId}' returned an artifact it did not hand over in this attempt.`, { providerId, details: { code: "output_mismatch" } });
|
|
702
|
+
}
|
|
703
|
+
resolved = { ...output, artifact: this.artifacts.reference(output.artifact.id) };
|
|
704
|
+
}
|
|
705
|
+
const mismatch = checkOutput(request, delivery, resolved, providerId);
|
|
706
|
+
if (mismatch !== undefined)
|
|
707
|
+
throw mismatch;
|
|
708
|
+
return resolved;
|
|
709
|
+
}
|
|
510
710
|
async checkCapabilities() {
|
|
511
711
|
return Promise.all(this.registry.list().map(async (provider) => ({
|
|
512
712
|
providerId: provider.describe().id,
|
|
@@ -515,3 +715,7 @@ export class IndeRun {
|
|
|
515
715
|
})));
|
|
516
716
|
}
|
|
517
717
|
}
|
|
718
|
+
/** The id of an artifact output the engine still holds, which an attempt must keep. */
|
|
719
|
+
function engineOwnedArtifactId(output) {
|
|
720
|
+
return output.artifact?.owner === "engine" ? output.artifact.id : undefined;
|
|
721
|
+
}
|
|
@@ -1,17 +1,25 @@
|
|
|
1
|
-
import type { TaskRequest, TaskResult } from "@independo/inderun-contracts";
|
|
2
|
-
import type { StreamRun,
|
|
1
|
+
import type { TaskRequest, TaskResult, ProviderCapabilitySnapshot, ArtifactReadRequest, MediaArtifact } from "@independo/inderun-contracts";
|
|
2
|
+
import type { StreamRun, ArtifactChunk } from "../provider.js";
|
|
3
3
|
export interface IndeRunApi {
|
|
4
4
|
/**
|
|
5
|
-
* Executes a Mode 1 (request/response)
|
|
5
|
+
* Executes a Mode 1 (request/response) task of any supported kind against a routed provider and returns the completed TaskResult, whose output is text, a managed-playback completion, or a media artifact as the request's delivery asks. Routing (local vs. cloud, which provider) is decided internally from TaskRequest.constraints/preferences — the caller does not select a provider directly. Rejects the request up front on validation failure; otherwise throws an IndeRunException (carrying an IndeRunError) if no eligible provider is found or every attempted provider fails. There is no partial or streaming result in this mode: run() either resolves with a complete TaskResult or throws. A provider failure falls back to the next planned provider unless the attempt had committed (for managed playback, no later than its first possible audible effect); a committed attempt's failure is the run's failure.
|
|
6
6
|
*/
|
|
7
7
|
run(request: TaskRequest): Promise<TaskResult>;
|
|
8
8
|
/**
|
|
9
|
-
* Executes a Mode 2 (streaming)
|
|
9
|
+
* Executes a Mode 2 (streaming) task of any supported kind and returns a handle to the run, its canonical StreamEvent sequence, and a cancel hook. The requested interaction mode is a routing input rather than a filter applied after routing: providers that cannot stream are rejected during planning with a normalized reason, so a stream and a run over the same registry may legitimately resolve to different provider chains. Two failure surfaces exist deliberately — validation and route-selection failures reject this call, since there is no handle to correlate them with yet, while provider failure, cancellation, and completion all arrive as the single terminal StreamEvent in the returned sequence. Fallback is narrower than run()'s: a provider failure is only fallback-eligible before the run commits to that provider -- its first content event, its explicit commit (managed playback, before the first possible audible effect), or its result handoff, whichever comes first; once committed, a later failure becomes a terminal error rather than a silent provider swap. Cancellation forecloses both at any commit state and always produces exactly one cancelled outcome.
|
|
10
10
|
*/
|
|
11
11
|
stream(request: TaskRequest): Promise<StreamRun>;
|
|
12
12
|
/**
|
|
13
13
|
* Returns a live snapshot of every registered provider's static descriptor and current dynamic availability, without executing a task or producing any side effects. Call this before run() when you need to show which providers/models are currently available — for example a settings screen or provider picker. Availability can change between calls (a local model can unload, cloud credentials can expire), so don't cache the result across a run() call.
|
|
14
14
|
*/
|
|
15
15
|
checkCapabilities(): Promise<ProviderCapabilitySnapshot[]>;
|
|
16
|
+
/**
|
|
17
|
+
* Reads a byte range of an engine-owned media artifact a run produced (ADR 017): the bytes in the platform's native byte type, with their offset, the artifact's total size, and whether the range reaches its end. Reads are repeatable until the artifact is disposed, and each returns at most the engine's configured maximum read size. Throws an IndeRunException with errorClass Unavailable and details.code 'artifact_not_found' for an unknown, disposed or application-owned artifact, and a validation error for an offset beyond the end.
|
|
18
|
+
*/
|
|
19
|
+
readArtifact(request: ArtifactReadRequest): Promise<ArtifactChunk>;
|
|
20
|
+
/**
|
|
21
|
+
* Releases the bytes of an engine-owned media artifact. Idempotent: disposing an artifact twice, or one the engine does not hold, does nothing. After disposal readArtifact reports 'artifact_not_found'. An artifact outlives the run that produced it, so an application that keeps none should dispose each one it receives.
|
|
22
|
+
*/
|
|
23
|
+
disposeArtifact(artifact: MediaArtifact): Promise<void>;
|
|
16
24
|
}
|
|
17
25
|
//# sourceMappingURL=inderun-api.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"inderun-api.d.ts","sourceRoot":"","sources":["../../../src/core/generated/inderun-api.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAC;
|
|
1
|
+
{"version":3,"file":"inderun-api.d.ts","sourceRoot":"","sources":["../../../src/core/generated/inderun-api.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,0BAA0B,EAAE,mBAAmB,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAC5I,OAAO,KAAK,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAE/D,MAAM,WAAW,UAAU;IACzB;;OAEG;IACH,GAAG,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;IAC/C;;OAEG;IACH,MAAM,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC;IACjD;;OAEG;IACH,iBAAiB,IAAI,OAAO,CAAC,0BAA0B,EAAE,CAAC,CAAC;IAC3D;;OAEG;IACH,YAAY,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,aAAa,CAAC,CAAC;IACnE;;OAEG;IACH,eAAe,CAAC,QAAQ,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACzD"}
|
package/dist/core/host.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export type { AbortSignalLike, ClockService, ConnectivityService, DeviceConstraintsService, HostServices, HttpClientService, HttpRequest, HttpResponse, HttpStreamResponse, HttpStreamingClientService, SecureStorageService, TelemetryService, ThermalState } from "@independo/inderun-contracts";
|
|
1
|
+
export type { AbortSignalLike, ArtifactDestinationService, ClockService, ConnectivityService, DeviceConstraintsService, HostServices, HttpClientService, HttpRequest, HttpResponse, HttpStreamResponse, HttpStreamingClientService, SecureStorageService, TelemetryService, ThermalState } from "@independo/inderun-contracts";
|
|
2
2
|
//# sourceMappingURL=host.d.ts.map
|
package/dist/core/host.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"host.d.ts","sourceRoot":"","sources":["../../src/core/host.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,eAAe,EACf,YAAY,EACZ,mBAAmB,EACnB,wBAAwB,EACxB,YAAY,EACZ,iBAAiB,EACjB,WAAW,EACX,YAAY,EACZ,kBAAkB,EAClB,0BAA0B,EAC1B,oBAAoB,EACpB,gBAAgB,EAChB,YAAY,EACb,MAAM,8BAA8B,CAAC"}
|
|
1
|
+
{"version":3,"file":"host.d.ts","sourceRoot":"","sources":["../../src/core/host.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,eAAe,EACf,0BAA0B,EAC1B,YAAY,EACZ,mBAAmB,EACnB,wBAAwB,EACxB,YAAY,EACZ,iBAAiB,EACjB,WAAW,EACX,YAAY,EACZ,kBAAkB,EAClB,0BAA0B,EAC1B,oBAAoB,EACpB,gBAAgB,EAChB,YAAY,EACb,MAAM,8BAA8B,CAAC"}
|
|
@@ -19,8 +19,11 @@ export declare class ProvenanceRecorder {
|
|
|
19
19
|
routed(routePlan: SharedPlannerRoutePlan): void;
|
|
20
20
|
/** Records the rejections a routing failure carried out on its exception's details. */
|
|
21
21
|
routingFailed(details: Record<string, unknown> | undefined): void;
|
|
22
|
-
/**
|
|
23
|
-
|
|
22
|
+
/**
|
|
23
|
+
* Records how one provider attempt ended, and whether the run had committed to it
|
|
24
|
+
* (first content delivered, first possible audible effect, or an artifact handed over).
|
|
25
|
+
*/
|
|
26
|
+
attempted(providerId: string, outcome: ExecutionProvenanceAttempt["outcome"], errorClass?: IndeRunErrorClass, committed?: boolean): void;
|
|
24
27
|
/**
|
|
25
28
|
* Renders the record. `attributedTo` is the provider the outcome belongs to -- the one that
|
|
26
29
|
* produced the result, the one a stream was committed to, or the one running at cancellation --
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"provenance.d.ts","sourceRoot":"","sources":["../../src/core/provenance.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AAC3F,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AACxD,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAC;AAGjE,KAAK,0BAA0B,GAAG,mBAAmB,CAAC,UAAU,CAAC,CAAC,MAAM,CAAC,CAAC;AAG1E;;;;;;;GAOG;AACH,qBAAa,kBAAkB;IAIjB,OAAO,CAAC,QAAQ,CAAC,YAAY;IAHzC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAoC;IAC7D,OAAO,CAAC,eAAe,CAAwB;gBAElB,YAAY,GAAE,MAA2B;IAEtE,0FAA0F;IAC1F,MAAM,CAAC,SAAS,EAAE,sBAAsB,GAAG,IAAI;IAI/C,uFAAuF;IACvF,aAAa,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,GAAG,IAAI;IASjE
|
|
1
|
+
{"version":3,"file":"provenance.d.ts","sourceRoot":"","sources":["../../src/core/provenance.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AAC3F,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AACxD,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAC;AAGjE,KAAK,0BAA0B,GAAG,mBAAmB,CAAC,UAAU,CAAC,CAAC,MAAM,CAAC,CAAC;AAG1E;;;;;;;GAOG;AACH,qBAAa,kBAAkB;IAIjB,OAAO,CAAC,QAAQ,CAAC,YAAY;IAHzC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAoC;IAC7D,OAAO,CAAC,eAAe,CAAwB;gBAElB,YAAY,GAAE,MAA2B;IAEtE,0FAA0F;IAC1F,MAAM,CAAC,SAAS,EAAE,sBAAsB,GAAG,IAAI;IAI/C,uFAAuF;IACvF,aAAa,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,GAAG,IAAI;IASjE;;;OAGG;IACH,SAAS,CACP,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,0BAA0B,CAAC,SAAS,CAAC,EAC9C,UAAU,CAAC,EAAE,iBAAiB,EAC9B,SAAS,UAAQ,GAChB,IAAI;IASP;;;;OAIG;IACH,KAAK,CAAC,YAAY,CAAC,EAAE,kBAAkB,EAAE,aAAa,CAAC,EAAE,MAAM,GAAG,mBAAmB;CActF"}
|
package/dist/core/provenance.js
CHANGED
|
@@ -25,11 +25,15 @@ export class ProvenanceRecorder {
|
|
|
25
25
|
this.routeRejections = routeRejectionsFrom(rejected);
|
|
26
26
|
}
|
|
27
27
|
}
|
|
28
|
-
/**
|
|
29
|
-
|
|
28
|
+
/**
|
|
29
|
+
* Records how one provider attempt ended, and whether the run had committed to it
|
|
30
|
+
* (first content delivered, first possible audible effect, or an artifact handed over).
|
|
31
|
+
*/
|
|
32
|
+
attempted(providerId, outcome, errorClass, committed = false) {
|
|
30
33
|
this.attempts.push({
|
|
31
34
|
providerId,
|
|
32
35
|
outcome,
|
|
36
|
+
...(committed ? { committed: true } : {}),
|
|
33
37
|
...(errorClass !== undefined ? { errorClass } : {})
|
|
34
38
|
});
|
|
35
39
|
}
|