@independo/inderun-web 0.3.3 → 0.4.0-rc.1

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.
Files changed (47) hide show
  1. package/README.md +16 -7
  2. package/dist/core/engine.d.ts +26 -2
  3. package/dist/core/engine.d.ts.map +1 -1
  4. package/dist/core/engine.js +78 -34
  5. package/dist/core/errors.d.ts +13 -1
  6. package/dist/core/errors.d.ts.map +1 -1
  7. package/dist/core/errors.js +32 -0
  8. package/dist/core/model-profiles.d.ts +34 -0
  9. package/dist/core/model-profiles.d.ts.map +1 -0
  10. package/dist/core/model-profiles.js +49 -0
  11. package/dist/core/provenance.d.ts +32 -0
  12. package/dist/core/provenance.d.ts.map +1 -0
  13. package/dist/core/provenance.js +71 -0
  14. package/dist/core/provider.d.ts +13 -0
  15. package/dist/core/provider.d.ts.map +1 -1
  16. package/dist/core/route-planner.d.ts +1 -1
  17. package/dist/core/route-planner.d.ts.map +1 -1
  18. package/dist/core/route-planner.js +2 -1
  19. package/dist/core/router.d.ts +4 -3
  20. package/dist/core/router.d.ts.map +1 -1
  21. package/dist/core/router.js +4 -3
  22. package/dist/facade/text.d.ts +123 -0
  23. package/dist/facade/text.d.ts.map +1 -0
  24. package/dist/facade/text.js +234 -0
  25. package/dist/index.d.ts +12 -2
  26. package/dist/index.d.ts.map +1 -1
  27. package/dist/index.js +10 -0
  28. package/dist/providers/onnx/provider.d.ts.map +1 -1
  29. package/dist/providers/onnx/provider.js +1 -0
  30. package/dist/providers/openai/provider.d.ts.map +1 -1
  31. package/dist/providers/openai/provider.js +18 -0
  32. package/dist/web-sdk.d.ts +19 -2
  33. package/dist/web-sdk.d.ts.map +1 -1
  34. package/dist/web-sdk.js +10 -2
  35. package/package.json +3 -3
  36. package/src/core/engine.ts +114 -35
  37. package/src/core/errors.ts +35 -1
  38. package/src/core/model-profiles.ts +70 -0
  39. package/src/core/provenance.ts +90 -0
  40. package/src/core/provider.ts +13 -0
  41. package/src/core/route-planner.ts +3 -1
  42. package/src/core/router.ts +12 -3
  43. package/src/facade/text.ts +349 -0
  44. package/src/index.ts +28 -2
  45. package/src/providers/onnx/provider.ts +1 -0
  46. package/src/providers/openai/provider.ts +21 -0
  47. package/src/web-sdk.ts +31 -5
package/README.md CHANGED
@@ -15,26 +15,35 @@ The contract types this SDK's own signatures take and return — `TaskRequest`,
15
15
  ## Basic Usage
16
16
 
17
17
  ```ts
18
- import { createIndeRunWeb } from "@independo/inderun-web";
18
+ import { createIndeRunWeb, generateText, streamText } from "@independo/inderun-web";
19
19
 
20
20
  const inderun = createIndeRunWeb({
21
21
  openAI: {
22
- model: "gpt-5.2",
22
+ models: ["gpt-5.2"],
23
23
  endpointUrl: "/api/inderun/openai-responses",
24
24
  auth: "none"
25
25
  }
26
26
  });
27
27
 
28
- const result = await inderun.run({
29
- schemaVersion: "1.0",
30
- task: { kind: "text_to_text" },
31
- prompt: "Write a one-sentence summary of IndeRun."
28
+ const { text, provenance } = await generateText(inderun, {
29
+ prompt: "Write a one-sentence summary of IndeRun.",
30
+ execution: { privacy: "local_preferred" }
32
31
  });
32
+
33
+ const stream = await streamText(inderun, { system: "Use plain language.", prompt: "Explain routing." });
34
+ for await (const delta of stream.textStream) process.stdout.write(delta);
35
+ const { finishReason } = await stream.result;
33
36
  ```
34
37
 
38
+ `generateText` and `streamText` take `prompt` or `messages`, an optional `system`, `model` (a
39
+ [model profile](https://github.com/independo-gmbh/inderun/blob/main/docs/architecture/api-layering.md#model-profiles),
40
+ default `"auto"`), the usual sampling options, and an `execution` policy that reuses the canonical
41
+ constraint values. Each call compiles to one `TaskRequest` — `toTaskRequest(options)` shows which —
42
+ and runs through the engine below, so errors, fallback, and cancellation match the advanced API.
43
+
35
44
  ## Streaming
36
45
 
37
- `stream()` returns the run handle, its canonical `StreamEvent` sequence, and a `cancel()` hook:
46
+ For the full event sequence, use the advanced API. `stream()` returns the run handle, its canonical `StreamEvent` sequence, and a `cancel()` hook:
38
47
 
39
48
  ```ts
40
49
  const { handle, events, cancel } = await inderun.stream({
@@ -4,12 +4,28 @@ import type { ProviderCapabilitySnapshot, StreamRun } from "./provider.js";
4
4
  import type { IndeRunApi } from "./generated/inderun-api.js";
5
5
  import { ProviderRegistry } from "./registry.js";
6
6
  import { type TelemetryService } from "./telemetry.js";
7
+ import { type ModelProfiles } from "./model-profiles.js";
7
8
  /**
8
9
  * @deprecated Renamed to `StreamRun`, which is the name the generated
9
10
  * `IndeRunApi` surface and the Swift/Kotlin SDKs use. Kept as an alias so the
10
11
  * rename is not a breaking change.
11
12
  */
12
13
  export type StreamHandleResult = StreamRun;
14
+ /**
15
+ * Optional engine configuration.
16
+ */
17
+ export interface IndeRunOptions {
18
+ /**
19
+ * Telemetry listener. Defaults to the host's telemetry service, then to a no-op.
20
+ */
21
+ telemetryService?: TelemetryService;
22
+ /**
23
+ * Named model profiles requests can route within (`TaskRequest.modelProfile`).
24
+ * Each maps a profile name to an ordered list of target (provider instance) ids.
25
+ * `"auto"` is built in and cannot be redefined. See `docs/architecture/api-layering.md`.
26
+ */
27
+ modelProfiles?: ModelProfiles;
28
+ }
13
29
  /**
14
30
  * Main orchestrator SDK entrypoint class.
15
31
  * Validates tasks, executes routing rules based on request constraints and connectivity,
@@ -20,13 +36,21 @@ export declare class IndeRun implements IndeRunApi {
20
36
  private hostServices;
21
37
  private router;
22
38
  private telemetryService;
39
+ private modelProfiles;
23
40
  /**
24
41
  * Initializes the IndeRun instance.
25
42
  * @param registry - Registry filled with active providers.
26
43
  * @param hostServices - Services wrapping OS interfaces and indicators.
27
- * @param telemetryService - Optional custom telemetry service listener.
44
+ * @param options - Optional telemetry listener and model profiles.
45
+ * @throws {Error} If a model profile is malformed (see {@link validateModelProfiles}).
46
+ */
47
+ constructor(registry: ProviderRegistry, hostServices: HostServices, options?: IndeRunOptions);
48
+ /**
49
+ * Validates the request against the schema and resolves its model profile.
50
+ * Both failures are request validation errors: they are reported before any
51
+ * provider is probed, with the offending fields in `details.validationIssues`.
28
52
  */
29
- constructor(registry: ProviderRegistry, hostServices: HostServices, telemetryService?: TelemetryService);
53
+ private validateRequest;
30
54
  /**
31
55
  * Current time from the host clock, falling back to wall-clock time when the
32
56
  * host does not supply one.
@@ -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,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"}
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;AAElG,OAAO,EAEL,KAAK,aAAa,EAInB,MAAM,qBAAqB,CAAC;AAG7B;;;;GAIG;AACH,MAAM,MAAM,kBAAkB,GAAG,SAAS,CAAC;AAE3C;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B;;OAEG;IACH,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;IACpC;;;;OAIG;IACH,aAAa,CAAC,EAAE,aAAa,CAAC;CAC/B;AAED;;;;GAIG;AACH,qBAAa,OAAQ,YAAW,UAAU;IAatC,OAAO,CAAC,QAAQ;IAChB,OAAO,CAAC,YAAY;IAbtB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,gBAAgB,CAAmB;IAC3C,OAAO,CAAC,aAAa,CAAgB;IAErC;;;;;;OAMG;gBAEO,QAAQ,EAAE,gBAAgB,EAC1B,YAAY,EAAE,YAAY,EAClC,OAAO,GAAE,cAAmB;IAS9B;;;;OAIG;IACH,OAAO,CAAC,eAAe;IAkBvB;;;OAGG;IACH,OAAO,CAAC,GAAG;IAIX;;;;OAIG;IACH,OAAO,CAAC,gBAAgB;IAwBxB;;;OAGG;IACH,OAAO,CAAC,QAAQ;IAShB;;;OAGG;IACH,OAAO,CAAC,gBAAgB;IAoBxB;;;;;;;OAOG;IACG,GAAG,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC;IA4HpD;;;;;;;;;;;;;;;;;OAiBG;IACG,MAAM,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,SAAS,CAAC;IAoRtD;;;OAGG;IACG,iBAAiB,IAAI,OAAO,CAAC,0BAA0B,EAAE,CAAC;CASjE"}
@@ -4,6 +4,8 @@ import { Router } from "./router.js";
4
4
  import { createCapabilityMismatch, createInternal, createUnavailable, toIndeRunException } from "./errors.js";
5
5
  import { NoOpTelemetryService } from "./telemetry.js";
6
6
  import { EventGate } from "./event-gate.js";
7
+ import { AUTO_MODEL_PROFILE, resolveModelProfile, validateModelProfiles } from "./model-profiles.js";
8
+ import { ProvenanceRecorder } from "./provenance.js";
7
9
  /**
8
10
  * Main orchestrator SDK entrypoint class.
9
11
  * Validates tasks, executes routing rules based on request constraints and connectivity,
@@ -14,18 +16,44 @@ export class IndeRun {
14
16
  hostServices;
15
17
  router;
16
18
  telemetryService;
19
+ modelProfiles;
17
20
  /**
18
21
  * Initializes the IndeRun instance.
19
22
  * @param registry - Registry filled with active providers.
20
23
  * @param hostServices - Services wrapping OS interfaces and indicators.
21
- * @param telemetryService - Optional custom telemetry service listener.
24
+ * @param options - Optional telemetry listener and model profiles.
25
+ * @throws {Error} If a model profile is malformed (see {@link validateModelProfiles}).
22
26
  */
23
- constructor(registry, hostServices, telemetryService) {
27
+ constructor(registry, hostServices, options = {}) {
24
28
  this.registry = registry;
25
29
  this.hostServices = hostServices;
26
30
  this.router = new Router(this.registry);
27
31
  this.telemetryService =
28
- telemetryService || this.hostServices.telemetry || new NoOpTelemetryService();
32
+ options.telemetryService || this.hostServices.telemetry || new NoOpTelemetryService();
33
+ this.modelProfiles = options.modelProfiles ?? {};
34
+ validateModelProfiles(this.modelProfiles);
35
+ }
36
+ /**
37
+ * Validates the request against the schema and resolves its model profile.
38
+ * Both failures are request validation errors: they are reported before any
39
+ * provider is probed, with the offending fields in `details.validationIssues`.
40
+ */
41
+ validateRequest(request, runId) {
42
+ const validationIssues = getTaskRequestValidationIssues(request);
43
+ if (validationIssues.length === 0) {
44
+ const resolved = resolveModelProfile(this.modelProfiles, request.modelProfile);
45
+ if (resolved.known) {
46
+ return resolved.profile;
47
+ }
48
+ validationIssues.push({
49
+ path: "/modelProfile",
50
+ message: `unknown model profile "${request.modelProfile}"; configure it in the engine's modelProfiles`
51
+ });
52
+ }
53
+ const message = `Validation failed for TaskRequest: ${validationIssues
54
+ .map((i) => `${i.path} - ${i.message}`)
55
+ .join("; ")}`;
56
+ throw createInternal(message, { runId, details: { validationIssues } });
29
57
  }
30
58
  /**
31
59
  * Current time from the host clock, falling back to wall-clock time when the
@@ -50,6 +78,7 @@ export class IndeRun {
50
78
  rejectedProviders: routeSelection.routePlan.rejectedProviders,
51
79
  fallbackAvailable: providerCount > 1,
52
80
  taskKind: request.task.kind,
81
+ modelProfile: request.modelProfile ?? "auto",
53
82
  constraints: request.constraints ?? null,
54
83
  preferences: request.preferences ?? null,
55
84
  explanation: routeSelection.explanation
@@ -103,21 +132,14 @@ export class IndeRun {
103
132
  async run(request) {
104
133
  const startTime = this.now();
105
134
  const runId = request.requestId || `run_${Math.random().toString(36).substring(2, 11)}`;
135
+ const provenance = new ProvenanceRecorder(request.modelProfile ?? AUTO_MODEL_PROFILE);
106
136
  try {
107
- // 1. Validate request payload using schema contracts
108
- const validationIssues = getTaskRequestValidationIssues(request);
109
- if (validationIssues.length > 0) {
110
- const message = `Validation failed for TaskRequest: ${validationIssues
111
- .map((i) => `${i.path} - ${i.message}`)
112
- .join("; ")}`;
113
- throw createInternal(message, {
114
- runId,
115
- details: { validationIssues }
116
- });
117
- }
118
- // 2. Select the route based on constraints and host capabilities
119
- const routeSelection = await this.router.selectRoute(request, this.hostServices, "run");
137
+ // 1. Validate request payload using schema contracts and resolve its model profile
138
+ const modelProfile = this.validateRequest(request, runId);
139
+ // 2. Select the route based on constraints, model profile, and host capabilities
140
+ const routeSelection = await this.router.selectRoute(request, this.hostServices, "run", modelProfile);
120
141
  const providers = [routeSelection.provider, ...routeSelection.fallbackProviders];
142
+ provenance.routed(routeSelection.routePlan);
121
143
  this.emitRouteDecided(runId, request, routeSelection, providers.length);
122
144
  // 3. Execute the run task using the planned provider chain
123
145
  const attemptedProviderIds = [];
@@ -138,6 +160,8 @@ export class IndeRun {
138
160
  providerUsed: providerId,
139
161
  totalMs
140
162
  };
163
+ provenance.attempted(providerId, "succeeded");
164
+ result.provenance = provenance.build(provider.describe(), result.telemetry.reportedModel);
141
165
  this.safeEmit({
142
166
  type: "attempt_succeeded",
143
167
  runId,
@@ -152,7 +176,7 @@ export class IndeRun {
152
176
  return result;
153
177
  }
154
178
  catch (err) {
155
- lastError = toIndeRunException(err, {
179
+ const attemptError = toIndeRunException(err, {
156
180
  providerId,
157
181
  runId,
158
182
  details: {
@@ -161,6 +185,8 @@ export class IndeRun {
161
185
  routePlan: routeSelection.routePlan
162
186
  }
163
187
  });
188
+ provenance.attempted(providerId, "failed", attemptError.errorClass);
189
+ lastError = attemptError;
164
190
  }
165
191
  }
166
192
  const endTime = this.now();
@@ -170,14 +196,14 @@ export class IndeRun {
170
196
  details: {
171
197
  totalMs,
172
198
  attemptedProviderIds,
173
- fallbackOccurred: providers.length > 1,
199
+ fallbackOccurred: attemptedProviderIds.length > 1,
174
200
  routePlan: routeSelection.routePlan
175
201
  }
176
202
  });
177
203
  throw exception;
178
204
  }
179
205
  catch (error) {
180
- const exception = toIndeRunException(error, {
206
+ const normalized = toIndeRunException(error, {
181
207
  runId,
182
208
  details: {
183
209
  totalMs: typeof error.details?.totalMs === "number"
@@ -185,6 +211,10 @@ export class IndeRun {
185
211
  : this.now() - startTime
186
212
  }
187
213
  });
214
+ // A routing refusal throws before any attempt; its rejections travel on the details.
215
+ provenance.routingFailed(normalized.details);
216
+ // No attempt produced the result, so the outcome is attributed to no provider.
217
+ const exception = normalized.withProvenance(provenance.build());
188
218
  const endTime = this.now();
189
219
  const totalMs = typeof exception.details?.totalMs === "number"
190
220
  ? exception.details?.totalMs
@@ -225,24 +255,28 @@ export class IndeRun {
225
255
  const startTime = this.now();
226
256
  const now = () => this.now();
227
257
  const runId = request.requestId || `run_${Math.random().toString(36).substring(2, 11)}`;
228
- const validationIssues = getTaskRequestValidationIssues(request);
229
- if (validationIssues.length > 0) {
230
- const message = `Validation failed for TaskRequest: ${validationIssues
231
- .map((i) => `${i.path} - ${i.message}`)
232
- .join("; ")}`;
233
- throw createInternal(message, { runId, details: { validationIssues } });
258
+ const provenance = new ProvenanceRecorder(request.modelProfile ?? AUTO_MODEL_PROFILE);
259
+ let modelProfile;
260
+ try {
261
+ modelProfile = this.validateRequest(request, runId);
262
+ }
263
+ catch (error) {
264
+ throw toIndeRunException(error, { runId }).withProvenance(provenance.build());
234
265
  }
235
266
  // Route selection failures are the one class of stream error that surfaces as a
236
267
  // rejected promise instead of a terminal event, so they must still carry the
237
268
  // runId — there is no handle for the caller to correlate them with otherwise.
238
269
  let routeSelection;
239
270
  try {
240
- routeSelection = await this.router.selectRoute(request, this.hostServices, "stream");
271
+ routeSelection = await this.router.selectRoute(request, this.hostServices, "stream", modelProfile);
241
272
  }
242
273
  catch (error) {
243
- throw toIndeRunException(error, { runId });
274
+ const exception = toIndeRunException(error, { runId });
275
+ provenance.routingFailed(exception.details);
276
+ throw exception.withProvenance(provenance.build());
244
277
  }
245
278
  const providers = [routeSelection.provider, ...routeSelection.fallbackProviders];
279
+ provenance.routed(routeSelection.routePlan);
246
280
  this.emitRouteDecided(runId, request, routeSelection, providers.length);
247
281
  // Defensive only: streaming eligibility is decided by the route planner, which
248
282
  // throws with its own rejection reasons when nothing can stream. Reaching here
@@ -267,14 +301,18 @@ export class IndeRun {
267
301
  async function* run() {
268
302
  const attemptedProviderIds = [];
269
303
  let committed = false;
270
- const cancelledOutcome = (partialText) => {
304
+ const cancelledOutcome = (partialText, cancelledProvider) => {
271
305
  const reason = typeof controller.signal.reason === "string" ? controller.signal.reason : undefined;
306
+ if (cancelledProvider !== undefined) {
307
+ provenance.attempted(cancelledProvider.describe().id, "cancelled");
308
+ }
272
309
  return {
273
310
  outcome: "cancelled",
274
311
  runId,
275
312
  schemaVersion: "1.0",
276
313
  partialText,
277
- ...(reason !== undefined ? { reason } : {})
314
+ ...(reason !== undefined ? { reason } : {}),
315
+ provenance: provenance.build(cancelledProvider?.describe())
278
316
  };
279
317
  };
280
318
  for (const [index, provider] of providers.entries()) {
@@ -325,6 +363,7 @@ export class IndeRun {
325
363
  continue;
326
364
  }
327
365
  // ev.kind === "done"
366
+ provenance.attempted(providerId, "succeeded");
328
367
  const outcome = {
329
368
  outcome: "completed",
330
369
  runId,
@@ -332,7 +371,8 @@ export class IndeRun {
332
371
  finalText: ev.finalText,
333
372
  telemetry: { providerUsed: providerId, totalMs: now() - startTime },
334
373
  ...(ev.finishReason !== undefined ? { finishReason: ev.finishReason } : {}),
335
- ...(ev.usage !== undefined ? { usage: ev.usage } : {})
374
+ ...(ev.usage !== undefined ? { usage: ev.usage } : {}),
375
+ provenance: provenance.build(provider.describe(), ev.reportedModel)
336
376
  };
337
377
  const terminal = gate.terminate(outcome, now());
338
378
  safeEmit({
@@ -350,7 +390,7 @@ export class IndeRun {
350
390
  return;
351
391
  }
352
392
  if (controller.signal.aborted) {
353
- const terminal = gate.terminate(cancelledOutcome(partialText), now());
393
+ const terminal = gate.terminate(cancelledOutcome(partialText, provider), now());
354
394
  safeEmit({
355
395
  type: "stream_cancelled",
356
396
  runId,
@@ -366,7 +406,7 @@ export class IndeRun {
366
406
  }
367
407
  catch (err) {
368
408
  if (controller.signal.aborted) {
369
- const terminal = gate.terminate(cancelledOutcome(partialText), now());
409
+ const terminal = gate.terminate(cancelledOutcome(partialText, provider), now());
370
410
  safeEmit({
371
411
  type: "stream_cancelled",
372
412
  runId,
@@ -382,6 +422,7 @@ export class IndeRun {
382
422
  runId,
383
423
  details: { attemptedProviderIds }
384
424
  });
425
+ provenance.attempted(providerId, "failed", exception.errorClass);
385
426
  if (!committed) {
386
427
  safeEmit({
387
428
  type: "stream_attempt_failed",
@@ -395,12 +436,14 @@ export class IndeRun {
395
436
  });
396
437
  continue;
397
438
  }
439
+ // Committed: the failure belongs to this provider, not to the chain.
398
440
  const outcome = {
399
441
  outcome: "error",
400
442
  runId,
401
443
  schemaVersion: "1.0",
402
444
  error: exception.toContractError(),
403
- partialText
445
+ partialText,
446
+ provenance: provenance.build(provider.describe())
404
447
  };
405
448
  const terminal = gate.terminate(outcome, now());
406
449
  safeEmit({
@@ -441,7 +484,8 @@ export class IndeRun {
441
484
  runId,
442
485
  schemaVersion: "1.0",
443
486
  error: exception.toContractError(),
444
- partialText: ""
487
+ partialText: "",
488
+ provenance: provenance.build()
445
489
  };
446
490
  const terminal = gate.terminate(outcome, now());
447
491
  safeEmit({
@@ -1,4 +1,4 @@
1
- import type { IndeRunError, IndeRunErrorClass } from "@independo/inderun-contracts";
1
+ import type { ExecutionProvenance, IndeRunError, IndeRunErrorClass } from "@independo/inderun-contracts";
2
2
  /**
3
3
  * Parameter structure required to initialize an IndeRunException.
4
4
  */
@@ -31,6 +31,10 @@ export interface IndeRunExceptionParams {
31
31
  * Rich contextual metadata regarding the error.
32
32
  */
33
33
  details?: Record<string, unknown>;
34
+ /**
35
+ * How the failed execution played out, attached by the engine.
36
+ */
37
+ provenance?: ExecutionProvenance;
34
38
  }
35
39
  /**
36
40
  * Standardized framework exception thrown when route selection or provider execution fails.
@@ -51,11 +55,19 @@ export declare class IndeRunException extends Error {
51
55
  readonly retryAfterMs?: number;
52
56
  /** Custom details and metadata context. */
53
57
  readonly details?: Record<string, unknown>;
58
+ /** How the failed execution played out: attempts, rejections, the provider committed to. */
59
+ readonly provenance?: ExecutionProvenance;
54
60
  constructor(params: IndeRunExceptionParams);
55
61
  /**
56
62
  * Converts the exception into a JSON-compatible contract-compliant IndeRunError.
57
63
  */
58
64
  toContractError(): IndeRunError;
65
+ /**
66
+ * A copy of this exception carrying `provenance`. The engine attaches provenance once it
67
+ * knows how the whole execution ended, replacing anything set earlier.
68
+ */
69
+ withProvenance(provenance: ExecutionProvenance): IndeRunException;
70
+ private params;
59
71
  }
60
72
  /**
61
73
  * Factory helper for CapabilityMismatch error class.
@@ -1 +1 @@
1
- {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/core/errors.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AAEpF;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACrC;;OAEG;IACH,UAAU,EAAE,iBAAiB,CAAC;IAC9B;;OAEG;IACH,OAAO,EAAE,MAAM,CAAC;IAChB;;OAEG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;OAEG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;OAEG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB;;OAEG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACnC;AAED;;;GAGG;AACH,qBAAa,gBAAiB,SAAQ,KAAK;IACzC,qDAAqD;IACrD,QAAQ,CAAC,aAAa,SAAS;IAC/B,sCAAsC;IACtC,QAAQ,CAAC,UAAU,EAAE,iBAAiB,CAAC;IACvC,mCAAmC;IACnC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,iDAAiD;IACjD,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,oDAAoD;IACpD,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC;IAC7B,gEAAgE;IAChE,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,2CAA2C;IAC3C,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;gBAE/B,MAAM,EAAE,sBAAsB;IAa1C;;OAEG;IACH,eAAe,IAAI,YAAY;CAahC;AAED;;GAEG;AACH,wBAAgB,wBAAwB,CACtC,OAAO,EAAE,MAAM,EACf,KAAK,CAAC,EAAE,IAAI,CAAC,sBAAsB,EAAE,YAAY,GAAG,SAAS,CAAC,GAC7D,gBAAgB,CAElB;AAED;;GAEG;AACH,wBAAgB,aAAa,CAC3B,OAAO,EAAE,MAAM,EACf,KAAK,CAAC,EAAE,IAAI,CAAC,sBAAsB,EAAE,YAAY,GAAG,SAAS,CAAC,GAC7D,gBAAgB,CAElB;AAED;;GAEG;AACH,wBAAgB,eAAe,CAC7B,OAAO,EAAE,MAAM,EACf,KAAK,CAAC,EAAE,IAAI,CAAC,sBAAsB,EAAE,YAAY,GAAG,SAAS,CAAC,GAC7D,gBAAgB,CAElB;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,MAAM,EACf,KAAK,CAAC,EAAE,IAAI,CAAC,sBAAsB,EAAE,YAAY,GAAG,SAAS,CAAC,GAC7D,gBAAgB,CAElB;AAED;;GAEG;AACH,wBAAgB,aAAa,CAC3B,OAAO,EAAE,MAAM,EACf,KAAK,CAAC,EAAE,IAAI,CAAC,sBAAsB,EAAE,YAAY,GAAG,SAAS,CAAC,GAC7D,gBAAgB,CAElB;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,MAAM,EACf,KAAK,CAAC,EAAE,IAAI,CAAC,sBAAsB,EAAE,YAAY,GAAG,SAAS,CAAC,GAC7D,gBAAgB,CAElB;AAED;;GAEG;AACH,wBAAgB,cAAc,CAC5B,OAAO,EAAE,MAAM,EACf,KAAK,CAAC,EAAE,IAAI,CAAC,sBAAsB,EAAE,YAAY,GAAG,SAAS,CAAC,GAC7D,gBAAgB,CAElB;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,OAAO,EACd,cAAc,CAAC,EAAE,OAAO,CAAC,IAAI,CAAC,sBAAsB,EAAE,SAAS,CAAC,CAAC,GAChE,gBAAgB,CAuDlB"}
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/core/errors.ts"],"names":[],"mappings":"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"}
@@ -17,6 +17,8 @@ export class IndeRunException extends Error {
17
17
  retryAfterMs;
18
18
  /** Custom details and metadata context. */
19
19
  details;
20
+ /** How the failed execution played out: attempts, rejections, the provider committed to. */
21
+ provenance;
20
22
  constructor(params) {
21
23
  super(params.message);
22
24
  this.name = "IndeRunException";
@@ -31,6 +33,8 @@ export class IndeRunException extends Error {
31
33
  this.retryAfterMs = params.retryAfterMs;
32
34
  if (params.details !== undefined)
33
35
  this.details = params.details;
36
+ if (params.provenance !== undefined)
37
+ this.provenance = params.provenance;
34
38
  Object.setPrototypeOf(this, IndeRunException.prototype);
35
39
  }
36
40
  /**
@@ -52,8 +56,33 @@ export class IndeRunException extends Error {
52
56
  err.retryAfterMs = this.retryAfterMs;
53
57
  if (this.details !== undefined)
54
58
  err.details = this.details;
59
+ if (this.provenance !== undefined)
60
+ err.provenance = this.provenance;
55
61
  return err;
56
62
  }
63
+ /**
64
+ * A copy of this exception carrying `provenance`. The engine attaches provenance once it
65
+ * knows how the whole execution ended, replacing anything set earlier.
66
+ */
67
+ withProvenance(provenance) {
68
+ return new IndeRunException({ ...this.params(), provenance });
69
+ }
70
+ params() {
71
+ const params = { errorClass: this.errorClass, message: this.message };
72
+ if (this.runId !== undefined)
73
+ params.runId = this.runId;
74
+ if (this.providerId !== undefined)
75
+ params.providerId = this.providerId;
76
+ if (this.retryable !== undefined)
77
+ params.retryable = this.retryable;
78
+ if (this.retryAfterMs !== undefined)
79
+ params.retryAfterMs = this.retryAfterMs;
80
+ if (this.details !== undefined)
81
+ params.details = this.details;
82
+ if (this.provenance !== undefined)
83
+ params.provenance = this.provenance;
84
+ return params;
85
+ }
57
86
  }
58
87
  /**
59
88
  * Factory helper for CapabilityMismatch error class.
@@ -126,6 +155,9 @@ export function toIndeRunException(error, fallbackParams) {
126
155
  ...(fallbackParams.details || {})
127
156
  };
128
157
  }
158
+ const provenance = error.provenance ?? fallbackParams.provenance;
159
+ if (provenance !== undefined)
160
+ mergedParams.provenance = provenance;
129
161
  return new IndeRunException(mergedParams);
130
162
  }
131
163
  return error;
@@ -0,0 +1,34 @@
1
+ import type { SharedPlannerInput } from "./route-planner.js";
2
+ /**
3
+ * The built-in profile: every registered target, ranked by constraints and
4
+ * preferences alone. Requests that name no profile route within it.
5
+ */
6
+ export declare const AUTO_MODEL_PROFILE = "auto";
7
+ /**
8
+ * Application-defined model profiles: a profile name mapped to an ordered list of
9
+ * target ids (provider instance ids, each bound to one model). Requests name a
10
+ * profile through `TaskRequest.modelProfile`; the route planner treats the list as
11
+ * an allow-list and ranks its entries after placement and before preferences.
12
+ * Ids that name no registered provider are ignored by the planner.
13
+ */
14
+ export type ModelProfiles = Readonly<Record<string, readonly string[]>>;
15
+ /** A resolved profile as the shared planner consumes it; absent means `auto`. */
16
+ export type ResolvedModelProfile = SharedPlannerInput["modelProfile"];
17
+ /**
18
+ * Checks profile configuration once, when the engine is constructed, so a
19
+ * malformed profile is a setup error rather than a per-request surprise.
20
+ * @throws {Error} When a profile is named `auto`, is empty, or lists a target twice.
21
+ */
22
+ export declare function validateModelProfiles(profiles: ModelProfiles): void;
23
+ /**
24
+ * Resolves a request's profile name against the configured profiles.
25
+ * @returns `{ known: true }` with the planner input (undefined for `auto`), or
26
+ * `{ known: false }` when the name matches no configured profile.
27
+ */
28
+ export declare function resolveModelProfile(profiles: ModelProfiles, name: string | undefined): {
29
+ known: true;
30
+ profile: ResolvedModelProfile;
31
+ } | {
32
+ known: false;
33
+ };
34
+ //# sourceMappingURL=model-profiles.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"model-profiles.d.ts","sourceRoot":"","sources":["../../src/core/model-profiles.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAE7D;;;GAGG;AACH,eAAO,MAAM,kBAAkB,SAAS,CAAC;AAEzC;;;;;;GAMG;AACH,MAAM,MAAM,aAAa,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC,CAAC,CAAC;AAExE,iFAAiF;AACjF,MAAM,MAAM,oBAAoB,GAAG,kBAAkB,CAAC,cAAc,CAAC,CAAC;AAEtE;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,aAAa,GAAG,IAAI,CAwBnE;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,aAAa,EACvB,IAAI,EAAE,MAAM,GAAG,SAAS,GACvB;IAAE,KAAK,EAAE,IAAI,CAAC;IAAC,OAAO,EAAE,oBAAoB,CAAA;CAAE,GAAG;IAAE,KAAK,EAAE,KAAK,CAAA;CAAE,CAUnE"}
@@ -0,0 +1,49 @@
1
+ /**
2
+ * The built-in profile: every registered target, ranked by constraints and
3
+ * preferences alone. Requests that name no profile route within it.
4
+ */
5
+ export const AUTO_MODEL_PROFILE = "auto";
6
+ /**
7
+ * Checks profile configuration once, when the engine is constructed, so a
8
+ * malformed profile is a setup error rather than a per-request surprise.
9
+ * @throws {Error} When a profile is named `auto`, is empty, or lists a target twice.
10
+ */
11
+ export function validateModelProfiles(profiles) {
12
+ for (const [name, targetIds] of Object.entries(profiles)) {
13
+ if (name.length === 0) {
14
+ throw new Error("Model profile names must be non-empty.");
15
+ }
16
+ if (name === AUTO_MODEL_PROFILE) {
17
+ throw new Error(`"${AUTO_MODEL_PROFILE}" is the built-in model profile and cannot be redefined.`);
18
+ }
19
+ if (targetIds.length === 0) {
20
+ throw new Error(`Model profile "${name}" must list at least one target id.`);
21
+ }
22
+ const seen = new Set();
23
+ for (const id of targetIds) {
24
+ if (id.length === 0) {
25
+ throw new Error(`Model profile "${name}" lists an empty target id.`);
26
+ }
27
+ if (seen.has(id)) {
28
+ throw new Error(`Model profile "${name}" lists target "${id}" more than once.`);
29
+ }
30
+ seen.add(id);
31
+ }
32
+ }
33
+ }
34
+ /**
35
+ * Resolves a request's profile name against the configured profiles.
36
+ * @returns `{ known: true }` with the planner input (undefined for `auto`), or
37
+ * `{ known: false }` when the name matches no configured profile.
38
+ */
39
+ export function resolveModelProfile(profiles, name) {
40
+ if (name === undefined || name === AUTO_MODEL_PROFILE) {
41
+ return { known: true, profile: undefined };
42
+ }
43
+ if (!Object.hasOwn(profiles, name)) {
44
+ return { known: false };
45
+ }
46
+ // validateModelProfiles guarantees every configured profile is non-empty.
47
+ const [first, ...rest] = profiles[name];
48
+ return { known: true, profile: { name, targetIds: [first, ...rest] } };
49
+ }
@@ -0,0 +1,32 @@
1
+ import type { ExecutionProvenance, IndeRunErrorClass } from "@independo/inderun-contracts";
2
+ import type { ProviderDescriptor } from "./provider.js";
3
+ import type { SharedPlannerRoutePlan } from "./route-planner.js";
4
+ type ExecutionProvenanceAttempt = ExecutionProvenance["attempts"][number];
5
+ /**
6
+ * Accumulates what one execution did -- each attempt and how it ended -- and renders it as the
7
+ * normalized {@link ExecutionProvenance} carried on results, terminal outcomes, and errors.
8
+ *
9
+ * Everything recorded is either observed by the engine (attempts, their outcomes) or declared or
10
+ * reported by a provider (descriptor type and model, the model a response names). Nothing is
11
+ * inferred: an unknown is left absent or reported as `unknown`.
12
+ */
13
+ export declare class ProvenanceRecorder {
14
+ private readonly modelProfile;
15
+ private readonly attempts;
16
+ private routeRejections;
17
+ constructor(modelProfile?: string);
18
+ /** Records the planner's rejections; codes only, never the provider-supplied messages. */
19
+ routed(routePlan: SharedPlannerRoutePlan): void;
20
+ /** Records the rejections a routing failure carried out on its exception's details. */
21
+ routingFailed(details: Record<string, unknown> | undefined): void;
22
+ /** Records how one provider attempt ended. */
23
+ attempted(providerId: string, outcome: ExecutionProvenanceAttempt["outcome"], errorClass?: IndeRunErrorClass): void;
24
+ /**
25
+ * Renders the record. `attributedTo` is the provider the outcome belongs to -- the one that
26
+ * produced the result, the one a stream was committed to, or the one running at cancellation --
27
+ * and is absent when no provider was reached or every attempt failed before a commit.
28
+ */
29
+ build(attributedTo?: ProviderDescriptor, reportedModel?: string): ExecutionProvenance;
30
+ }
31
+ export {};
32
+ //# sourceMappingURL=provenance.d.ts.map
@@ -0,0 +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,8CAA8C;IAC9C,SAAS,CACP,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,0BAA0B,CAAC,SAAS,CAAC,EAC9C,UAAU,CAAC,EAAE,iBAAiB,GAC7B,IAAI;IAQP;;;;OAIG;IACH,KAAK,CAAC,YAAY,CAAC,EAAE,kBAAkB,EAAE,aAAa,CAAC,EAAE,MAAM,GAAG,mBAAmB;CActF"}