@independo/inderun-contracts 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.
Files changed (73) hide show
  1. package/dist/generated/artifact-read-request.d.ts +24 -0
  2. package/dist/generated/artifact-read-request.d.ts.map +1 -0
  3. package/dist/generated/artifact-read-request.js +2 -0
  4. package/dist/generated/execution-provenance.d.ts +8 -1
  5. package/dist/generated/execution-provenance.d.ts.map +1 -1
  6. package/dist/generated/inderun-error.d.ts +8 -1
  7. package/dist/generated/inderun-error.d.ts.map +1 -1
  8. package/dist/generated/index.d.ts +6 -0
  9. package/dist/generated/index.d.ts.map +1 -1
  10. package/dist/generated/media-artifact.d.ts +53 -0
  11. package/dist/generated/media-artifact.d.ts.map +1 -0
  12. package/dist/generated/media-artifact.js +2 -0
  13. package/dist/generated/provider-capability-snapshot.d.ts +370 -0
  14. package/dist/generated/provider-capability-snapshot.d.ts.map +1 -0
  15. package/dist/generated/provider-capability-snapshot.js +2 -0
  16. package/dist/generated/provider-descriptor.d.ts +264 -0
  17. package/dist/generated/provider-descriptor.d.ts.map +1 -0
  18. package/dist/generated/provider-descriptor.js +2 -0
  19. package/dist/generated/provider-dynamic-capabilities.d.ts +99 -0
  20. package/dist/generated/provider-dynamic-capabilities.d.ts.map +1 -0
  21. package/dist/generated/provider-dynamic-capabilities.js +2 -0
  22. package/dist/generated/route-plan.d.ts +47 -3
  23. package/dist/generated/route-plan.d.ts.map +1 -1
  24. package/dist/generated/route-planner-input.d.ts +417 -32
  25. package/dist/generated/route-planner-input.d.ts.map +1 -1
  26. package/dist/generated/schemas.d.ts +833 -115
  27. package/dist/generated/schemas.d.ts.map +1 -1
  28. package/dist/generated/schemas.js +1038 -160
  29. package/dist/generated/stream-event.d.ts +136 -1
  30. package/dist/generated/stream-event.d.ts.map +1 -1
  31. package/dist/generated/stream-terminal-outcome.d.ts +182 -18
  32. package/dist/generated/stream-terminal-outcome.d.ts.map +1 -1
  33. package/dist/generated/task-output.d.ts +128 -0
  34. package/dist/generated/task-output.d.ts.map +1 -0
  35. package/dist/generated/task-output.js +2 -0
  36. package/dist/generated/task-request.d.ts +116 -11
  37. package/dist/generated/task-request.d.ts.map +1 -1
  38. package/dist/generated/task-result.d.ts +139 -15
  39. package/dist/generated/task-result.d.ts.map +1 -1
  40. package/dist/generated/telemetry-event.d.ts +2 -2
  41. package/dist/generated/telemetry-event.d.ts.map +1 -1
  42. package/dist/host.d.ts +24 -1
  43. package/dist/host.d.ts.map +1 -1
  44. package/dist/index.d.ts +5 -5
  45. package/dist/index.d.ts.map +1 -1
  46. package/dist/index.js +2 -2
  47. package/dist/types.d.ts +46 -2
  48. package/dist/types.d.ts.map +1 -1
  49. package/dist/validators.d.ts +18 -0
  50. package/dist/validators.d.ts.map +1 -1
  51. package/dist/validators.js +49 -1
  52. package/package.json +1 -1
  53. package/src/generated/artifact-read-request.ts +25 -0
  54. package/src/generated/execution-provenance.ts +8 -1
  55. package/src/generated/inderun-error.ts +8 -1
  56. package/src/generated/index.ts +6 -0
  57. package/src/generated/media-artifact.ts +56 -0
  58. package/src/generated/provider-capability-snapshot.ts +388 -0
  59. package/src/generated/provider-descriptor.ts +278 -0
  60. package/src/generated/provider-dynamic-capabilities.ts +103 -0
  61. package/src/generated/route-plan.ts +49 -3
  62. package/src/generated/route-planner-input.ts +437 -36
  63. package/src/generated/schemas.ts +1044 -160
  64. package/src/generated/stream-event.ts +145 -1
  65. package/src/generated/stream-terminal-outcome.ts +191 -18
  66. package/src/generated/task-output.ts +137 -0
  67. package/src/generated/task-request.ts +124 -11
  68. package/src/generated/task-result.ts +147 -15
  69. package/src/generated/telemetry-event.ts +2 -2
  70. package/src/host.ts +30 -1
  71. package/src/index.ts +28 -1
  72. package/src/types.ts +38 -1
  73. package/src/validators.ts +76 -0
@@ -3,23 +3,107 @@ export const taskRequestSchema = {
3
3
  "$schema": "https://json-schema.org/draft/2020-12/schema",
4
4
  "$id": "https://schemas.inderun.dev/1.0/task-request.schema.json",
5
5
  "title": "TaskRequest",
6
- "description": "The request payload for a Mode 1 (request/response) text-to-text execution. At least one of `prompt` (single-turn) or `messages` (multi-turn) must be present — both may be present together, though callers should typically supply just one; `constraints`/`preferences` steer routing but never select a provider directly.",
6
+ "description": "The request payload for a run() or stream() execution. The common envelope (schemaVersion, requestId, task, modelProfile, constraints, preferences, telemetry, authContextRef) is task-agnostic; each task kind owns its payload (ADR 016). text_to_text: at least one of `prompt` (single-turn) or `messages` (multi-turn) must be present — both may be present together, though callers should typically supply just one — and `speech`/`delivery` are forbidden. text_to_speech: `speech` and `delivery` are required and `prompt`, `messages` and `generation` are forbidden. `constraints`/`preferences` steer routing but never select a provider directly.",
7
7
  "type": "object",
8
8
  "additionalProperties": true,
9
9
  "required": [
10
10
  "schemaVersion",
11
11
  "task"
12
12
  ],
13
- "anyOf": [
13
+ "allOf": [
14
14
  {
15
- "required": [
16
- "prompt"
17
- ]
15
+ "if": {
16
+ "properties": {
17
+ "task": {
18
+ "type": "object",
19
+ "properties": {
20
+ "kind": {
21
+ "const": "text_to_text"
22
+ }
23
+ }
24
+ }
25
+ }
26
+ },
27
+ "then": {
28
+ "anyOf": [
29
+ {
30
+ "required": [
31
+ "prompt"
32
+ ]
33
+ },
34
+ {
35
+ "required": [
36
+ "messages"
37
+ ]
38
+ }
39
+ ],
40
+ "not": {
41
+ "anyOf": [
42
+ {
43
+ "required": [
44
+ "speech"
45
+ ]
46
+ },
47
+ {
48
+ "required": [
49
+ "delivery"
50
+ ]
51
+ }
52
+ ]
53
+ }
54
+ }
18
55
  },
19
56
  {
20
- "required": [
21
- "messages"
22
- ]
57
+ "if": {
58
+ "properties": {
59
+ "task": {
60
+ "type": "object",
61
+ "properties": {
62
+ "kind": {
63
+ "const": "text_to_speech"
64
+ }
65
+ }
66
+ }
67
+ }
68
+ },
69
+ "then": {
70
+ "required": [
71
+ "speech",
72
+ "delivery"
73
+ ],
74
+ "not": {
75
+ "anyOf": [
76
+ {
77
+ "required": [
78
+ "prompt"
79
+ ]
80
+ },
81
+ {
82
+ "required": [
83
+ "messages"
84
+ ]
85
+ },
86
+ {
87
+ "required": [
88
+ "generation"
89
+ ]
90
+ }
91
+ ]
92
+ },
93
+ "properties": {
94
+ "delivery": {
95
+ "type": "object",
96
+ "properties": {
97
+ "mode": {
98
+ "enum": [
99
+ "playback",
100
+ "artifact"
101
+ ]
102
+ }
103
+ }
104
+ }
105
+ }
106
+ }
23
107
  }
24
108
  ],
25
109
  "properties": {
@@ -33,7 +117,7 @@ export const taskRequestSchema = {
33
117
  "minLength": 1
34
118
  },
35
119
  "task": {
36
- "description": "A descriptor specifying the type of work to be performed. For text-to-text, the kind must be 'text_to_text'.",
120
+ "description": "A descriptor specifying the type of work to be performed. The kind selects which payload the request must carry.",
37
121
  "type": "object",
38
122
  "additionalProperties": true,
39
123
  "required": [
@@ -41,8 +125,12 @@ export const taskRequestSchema = {
41
125
  ],
42
126
  "properties": {
43
127
  "kind": {
44
- "description": "The standard task category. Currently supports 'text_to_text' for prompt-based interactions.",
45
- "const": "text_to_text"
128
+ "title": "TaskKind",
129
+ "description": "The operation: 'text_to_text' (prompt-based text generation) or 'text_to_speech' (speech synthesis from text). A consumer built against an older revision rejects a kind it does not know through validation.",
130
+ "enum": [
131
+ "text_to_text",
132
+ "text_to_speech"
133
+ ]
46
134
  }
47
135
  }
48
136
  },
@@ -80,6 +168,122 @@ export const taskRequestSchema = {
80
168
  }
81
169
  }
82
170
  },
171
+ "speech": {
172
+ "title": "SpeechInput",
173
+ "description": "The text_to_speech payload. Required for text_to_speech and forbidden for every other kind.",
174
+ "type": "object",
175
+ "additionalProperties": true,
176
+ "required": [
177
+ "text",
178
+ "locale"
179
+ ],
180
+ "properties": {
181
+ "text": {
182
+ "description": "Plain text to speak. Markup such as SSML is out of scope.",
183
+ "type": "string",
184
+ "minLength": 1
185
+ },
186
+ "locale": {
187
+ "description": "BCP 47 tag of the language to speak, e.g. 'de-AT'.",
188
+ "type": "string",
189
+ "minLength": 1
190
+ },
191
+ "localeMatch": {
192
+ "title": "LocaleMatch",
193
+ "description": "'language' (default): the primary language subtag is a hard requirement and the region is a preference. 'exact': the full tag is a hard requirement.",
194
+ "enum": [
195
+ "exact",
196
+ "language"
197
+ ]
198
+ },
199
+ "voice": {
200
+ "title": "SpeechVoice",
201
+ "description": "Provider-neutral voice traits. A 'preferred' voice (default) shapes which voice speaks and is never a reason to fail; a 'required' one takes part in routing and is never relaxed silently. Carries no provider voice identifier.",
202
+ "type": "object",
203
+ "additionalProperties": true,
204
+ "if": {
205
+ "properties": {
206
+ "requirement": {
207
+ "const": "required"
208
+ }
209
+ },
210
+ "required": [
211
+ "requirement"
212
+ ]
213
+ },
214
+ "then": {
215
+ "required": [
216
+ "gender"
217
+ ]
218
+ },
219
+ "properties": {
220
+ "gender": {
221
+ "$ref": "provider-dynamic-capabilities.schema.json#/$defs/voiceGender"
222
+ },
223
+ "requirement": {
224
+ "title": "RequirementLevel",
225
+ "description": "'preferred' (default) or 'required'. A required voice must name at least one trait.",
226
+ "enum": [
227
+ "required",
228
+ "preferred"
229
+ ]
230
+ }
231
+ }
232
+ }
233
+ }
234
+ },
235
+ "delivery": {
236
+ "title": "OutputDeliveryRequest",
237
+ "description": "How the result of a non-text task must reach the application (ADR 016). Required for non-text tasks and forbidden for text_to_text, whose output is implicitly inline. A provider never substitutes playback for an artifact or the reverse.",
238
+ "type": "object",
239
+ "additionalProperties": true,
240
+ "required": [
241
+ "mode"
242
+ ],
243
+ "if": {
244
+ "properties": {
245
+ "mode": {
246
+ "const": "artifact"
247
+ }
248
+ }
249
+ },
250
+ "else": {
251
+ "not": {
252
+ "anyOf": [
253
+ {
254
+ "required": [
255
+ "mediaTypes"
256
+ ]
257
+ },
258
+ {
259
+ "required": [
260
+ "destinationRef"
261
+ ]
262
+ }
263
+ ]
264
+ }
265
+ },
266
+ "properties": {
267
+ "mode": {
268
+ "$ref": "provider-descriptor.schema.json#/$defs/outputDelivery"
269
+ },
270
+ "mediaTypes": {
271
+ "description": "Only valid with 'artifact' delivery: the acceptable media types, always a hard requirement. Absent means the provider chooses and the artifact reports what it produced.",
272
+ "type": "array",
273
+ "minItems": 1,
274
+ "uniqueItems": true,
275
+ "items": {
276
+ "type": "string",
277
+ "minLength": 1
278
+ }
279
+ },
280
+ "destinationRef": {
281
+ "description": "Only valid with 'artifact' delivery: an opaque reference to an application destination the engine writes the finished artifact to, resolved by the host's artifact-destination service (ADR 017) -- never a raw path or URL, like authContextRef for credentials. The result's artifact is then application-owned and the engine keeps no copy. A host without the service refuses the request before routing.",
282
+ "type": "string",
283
+ "minLength": 1
284
+ }
285
+ }
286
+ },
83
287
  "modelProfile": {
84
288
  "description": "Name of the model profile to route within, as configured on the engine. Absent or 'auto' means every registered target, ranked by constraints and preferences alone. A profile is an application-defined, ordered allow-list of targets (provider instances bound to one model each); raw model or provider identifiers are never accepted here. An unknown profile name fails request validation.",
85
289
  "type": "string",
@@ -143,7 +347,7 @@ export const taskRequestSchema = {
143
347
  ]
144
348
  },
145
349
  "timeoutMs": {
146
- "description": "Optional routing timeout budget in milliseconds.",
350
+ "description": "Optional timeout budget in milliseconds, passed to the planner and to providers. It bounds the time until the run commits to a provider -- its first content, its first possible audible effect, or its artifact handoff -- not the duration of audio it plays. The engines add no timers of their own.",
147
351
  "type": "integer",
148
352
  "minimum": 1
149
353
  }
@@ -202,7 +406,7 @@ export const taskResultSchema = {
202
406
  "$schema": "https://json-schema.org/draft/2020-12/schema",
203
407
  "$id": "https://schemas.inderun.dev/1.0/task-result.schema.json",
204
408
  "title": "TaskResult",
205
- "description": "The response payload for a completed text-to-text execution. A full execution failure (validation, routing, or every attempted provider failing) is surfaced by run() throwing an IndeRunError instead of returning a TaskResult; finishReason and telemetry.errorClass are reserved for a provider reporting a non-fatal, degraded outcome on an otherwise-successful result (finishReason 'error' is produced by the Android ML Kit GenAI provider; telemetry.errorClass is not currently set by any provider in this codebase).",
409
+ "description": "The response payload for a completed run() execution, for every task kind; output.type tells the content representation apart (text, managed playback, retrievable artifact). A full execution failure (validation, routing, or every attempted provider failing) is surfaced by run() throwing an IndeRunError instead of returning a TaskResult; finishReason and telemetry.errorClass are reserved for a provider reporting a non-fatal, degraded outcome on an otherwise-successful result (finishReason 'error' is produced by the Android ML Kit GenAI provider; telemetry.errorClass is not currently set by any provider in this codebase).",
206
410
  "type": "object",
207
411
  "additionalProperties": true,
208
412
  "required": [
@@ -223,23 +427,8 @@ export const taskResultSchema = {
223
427
  "minLength": 1
224
428
  },
225
429
  "output": {
226
- "description": "The normalized content returned from the selected provider.",
227
- "type": "object",
228
- "additionalProperties": true,
229
- "required": [
230
- "type",
231
- "text"
232
- ],
233
- "properties": {
234
- "type": {
235
- "description": "Output payload category (e.g., 'text' for Mode 1 text-to-text).",
236
- "const": "text"
237
- },
238
- "text": {
239
- "description": "The actual text generated by the execution.",
240
- "type": "string"
241
- }
242
- }
430
+ "description": "The normalized output of the selected provider. See task-output.schema.json.",
431
+ "$ref": "task-output.schema.json"
243
432
  },
244
433
  "finishReason": {
245
434
  "description": "How generation ended: 'stop' (natural end), 'length' (hit maxOutputTokens), or 'cancelled'. 'error' is reserved for a provider reporting a non-fatal issue on an otherwise-returned result — the Android ML Kit GenAI provider produces it when Gemini Nano reports a finish reason that is neither a natural stop nor the token limit. A full execution failure is instead surfaced by run() throwing an IndeRunError.",
@@ -477,6 +666,7 @@ export const telemetryEventSchema = {
477
666
  ],
478
667
  "properties": {
479
668
  "type": {
669
+ "title": "TelemetryEventType",
480
670
  "description": "Telemetry event kind emitted by the orchestrator or provider integration.",
481
671
  "enum": [
482
672
  "route_decided",
@@ -522,7 +712,7 @@ export const routePlannerInputSchema = {
522
712
  ],
523
713
  "properties": {
524
714
  "task": {
525
- "description": "Minimal task descriptor for provider task matching.",
715
+ "description": "Task descriptor for provider task matching: the kind plus the requirements routing can check against declared capabilities. Absent requirement fields are never checked, so a text request carries the kind alone.",
526
716
  "type": "object",
527
717
  "additionalProperties": true,
528
718
  "required": [
@@ -530,8 +720,85 @@ export const routePlannerInputSchema = {
530
720
  ],
531
721
  "properties": {
532
722
  "kind": {
723
+ "description": "The task kind, matched against the providers' task capabilities.",
533
724
  "type": "string",
534
725
  "minLength": 1
726
+ },
727
+ "inputs": {
728
+ "description": "Input media kinds the request carries. Every one must be accepted by the provider's task capability. The SDKs send ['text'] for text_to_text.",
729
+ "type": "array",
730
+ "items": {
731
+ "$ref": "provider-descriptor.schema.json#/$defs/inputKind"
732
+ }
733
+ },
734
+ "delivery": {
735
+ "title": "DeliveryRequirement",
736
+ "description": "The output delivery the request requires. Absent for text, whose delivery is implicitly inline.",
737
+ "type": "object",
738
+ "additionalProperties": true,
739
+ "required": [
740
+ "mode"
741
+ ],
742
+ "properties": {
743
+ "mode": {
744
+ "$ref": "provider-descriptor.schema.json#/$defs/outputDelivery"
745
+ },
746
+ "mediaTypes": {
747
+ "description": "Acceptable media types; the provider must declare at least one for the requested delivery. Absent means any.",
748
+ "type": "array",
749
+ "items": {
750
+ "type": "string",
751
+ "minLength": 1
752
+ }
753
+ }
754
+ }
755
+ },
756
+ "locale": {
757
+ "title": "LocaleRequirement",
758
+ "description": "The locale the request requires. Under 'language' matching the region is a preference: a provider known to offer the exact tag ranks ahead of an otherwise equal one. A provider that cannot enumerate its locales is not rejected on this basis; it must fail before committing instead.",
759
+ "type": "object",
760
+ "additionalProperties": true,
761
+ "required": [
762
+ "tag",
763
+ "match"
764
+ ],
765
+ "properties": {
766
+ "tag": {
767
+ "description": "BCP 47 tag.",
768
+ "type": "string",
769
+ "minLength": 1
770
+ },
771
+ "match": {
772
+ "title": "LocaleMatch",
773
+ "description": "'exact' requires the full tag; 'language' requires only the primary language subtag and treats the region as a preference.",
774
+ "enum": [
775
+ "exact",
776
+ "language"
777
+ ]
778
+ }
779
+ }
780
+ },
781
+ "voice": {
782
+ "title": "VoiceRequirement",
783
+ "description": "Voice traits the request asks for. Only a 'required' voice can reject a provider; a 'preferred' one shapes which listed voice the plan binds a candidate to, always within the request's privacy policy.",
784
+ "type": "object",
785
+ "additionalProperties": true,
786
+ "required": [
787
+ "requirement"
788
+ ],
789
+ "properties": {
790
+ "gender": {
791
+ "$ref": "provider-dynamic-capabilities.schema.json#/$defs/voiceGender"
792
+ },
793
+ "requirement": {
794
+ "title": "RequirementLevel",
795
+ "description": "'required' takes part in routing; 'preferred' does not.",
796
+ "enum": [
797
+ "required",
798
+ "preferred"
799
+ ]
800
+ }
801
+ }
535
802
  }
536
803
  }
537
804
  },
@@ -612,7 +879,7 @@ export const routePlannerInputSchema = {
612
879
  }
613
880
  },
614
881
  "providers": {
615
- "description": "Static descriptors plus dynamic capability snapshots for planning.",
882
+ "description": "Static descriptors plus dynamic capability checks for planning. Both shapes are the provider schemas themselves, shared by $ref, so the planner reads exactly what checkCapabilities() reports.",
616
883
  "type": "array",
617
884
  "items": {
618
885
  "type": "object",
@@ -623,107 +890,12 @@ export const routePlannerInputSchema = {
623
890
  ],
624
891
  "properties": {
625
892
  "descriptor": {
626
- "type": "object",
627
- "additionalProperties": true,
628
- "required": [
629
- "id",
630
- "type",
631
- "supports",
632
- "tasks"
633
- ],
634
- "properties": {
635
- "id": {
636
- "type": "string",
637
- "minLength": 1
638
- },
639
- "type": {
640
- "enum": [
641
- "local",
642
- "edge",
643
- "cloud"
644
- ]
645
- },
646
- "supports": {
647
- "type": "object",
648
- "additionalProperties": true,
649
- "required": [
650
- "run"
651
- ],
652
- "properties": {
653
- "run": {
654
- "type": "boolean"
655
- },
656
- "streaming": {
657
- "description": "Whether the provider statically declares Mode-2 streaming. Absent is treated as false: a descriptor that predates this field cannot be assumed to stream.",
658
- "type": "boolean"
659
- }
660
- }
661
- },
662
- "cancel": {
663
- "description": "Cancellation guarantee the provider offers. Carried for route explanations and telemetry; the planner does not filter on it.",
664
- "enum": [
665
- "hard",
666
- "soft",
667
- "none"
668
- ]
669
- },
670
- "tasks": {
671
- "type": "array",
672
- "items": {
673
- "type": "string",
674
- "minLength": 1
675
- }
676
- },
677
- "privacy": {
678
- "description": "Descriptor privacy metadata used to enforce local/cloud routing rules.",
679
- "type": "object",
680
- "additionalProperties": true,
681
- "required": [
682
- "dataLeavesDevice"
683
- ],
684
- "properties": {
685
- "dataLeavesDevice": {
686
- "type": "boolean"
687
- },
688
- "regions": {
689
- "type": "array",
690
- "items": {
691
- "type": "string",
692
- "minLength": 1
693
- }
694
- }
695
- }
696
- }
697
- }
893
+ "description": "The provider's static capability declaration.",
894
+ "$ref": "provider-descriptor.schema.json"
698
895
  },
699
896
  "capabilities": {
700
- "type": "object",
701
- "additionalProperties": true,
702
- "required": [
703
- "available"
704
- ],
705
- "properties": {
706
- "available": {
707
- "type": "boolean"
708
- },
709
- "reason": {
710
- "type": "string",
711
- "minLength": 1
712
- },
713
- "streamingAvailable": {
714
- "description": "Whether the provider can stream right now in this host environment. Absent inherits the static descriptor.supports.streaming value.",
715
- "type": "boolean"
716
- },
717
- "streamingUnavailableReason": {
718
- "description": "Human-readable explanation used when streamingAvailable is false. Absent lets the planner synthesize a default message.",
719
- "type": "string",
720
- "minLength": 1
721
- },
722
- "cancellationAvailable": {
723
- "description": "Whether cancellation is honored right now in this host environment. Absent inherits the static descriptor.cancel value (any value other than 'none' means available).",
724
- "type": "boolean"
725
- }
726
- }
897
+ "description": "The provider's live capability check, already folded by the engine (for example, streamingAvailable is false when the provider declares streaming but does not implement it).",
898
+ "$ref": "provider-dynamic-capabilities.schema.json"
727
899
  }
728
900
  }
729
901
  }
@@ -775,6 +947,10 @@ export const routePlanSchema = {
775
947
  "order": {
776
948
  "type": "integer",
777
949
  "minimum": 0
950
+ },
951
+ "voice": {
952
+ "description": "The voice this candidate must use if it is attempted, chosen by the planner from the provider's dynamic voice inventory. It is the best voice that satisfies the request's locale, required voice traits and privacy — under local-required privacy always one that keeps data on the device — ranked by preferred gender, then exact locale region, then on-device before network. The adapter must use a voice with exactly these traits. Absent when the request names no locale or voice, or the provider does not list its voices; the adapter then chooses itself under the same rules.",
953
+ "$ref": "provider-dynamic-capabilities.schema.json#/$defs/voice"
778
954
  }
779
955
  }
780
956
  }
@@ -805,7 +981,7 @@ export const routePlanSchema = {
805
981
  ],
806
982
  "properties": {
807
983
  "code": {
808
- "description": "Normalized rejection reason. 'streaming_not_supported' means the descriptor does not statically declare Mode-2 streaming; 'streaming_unavailable' means it declares streaming but the dynamic capability snapshot reports it cannot stream in the current host environment. 'not_in_model_profile' means the request named a model profile that does not list this provider.",
984
+ "description": "Normalized rejection reason. 'streaming_not_supported' means the descriptor does not statically declare Mode-2 streaming; 'streaming_unavailable' means it declares streaming but the dynamic capability snapshot reports it cannot stream in the current host environment. 'not_in_model_profile' means the request named a model profile that does not list this provider. 'input_not_supported', 'output_delivery_not_supported', 'output_format_not_supported', 'locale_not_supported' and 'voice_not_available' mean the provider's task capability lacks an input kind, output delivery, media type, locale or voice the request requires.",
809
985
  "enum": [
810
986
  "task_not_supported",
811
987
  "run_not_supported",
@@ -815,7 +991,12 @@ export const routePlanSchema = {
815
991
  "cloud_constraint",
816
992
  "offline",
817
993
  "capability_unavailable",
818
- "not_in_model_profile"
994
+ "not_in_model_profile",
995
+ "input_not_supported",
996
+ "output_delivery_not_supported",
997
+ "output_format_not_supported",
998
+ "locale_not_supported",
999
+ "voice_not_available"
819
1000
  ]
820
1001
  },
821
1002
  "message": {
@@ -1309,8 +1490,19 @@ export const streamEventSchema = {
1309
1490
  "required": [
1310
1491
  "schemaVersion",
1311
1492
  "runId",
1312
- "outcome",
1313
- "finalText"
1493
+ "outcome"
1494
+ ],
1495
+ "anyOf": [
1496
+ {
1497
+ "required": [
1498
+ "finalText"
1499
+ ]
1500
+ },
1501
+ {
1502
+ "required": [
1503
+ "output"
1504
+ ]
1505
+ }
1314
1506
  ],
1315
1507
  "properties": {
1316
1508
  "schemaVersion": {
@@ -1326,6 +1518,9 @@ export const streamEventSchema = {
1326
1518
  "finalText": {
1327
1519
  "type": "string"
1328
1520
  },
1521
+ "output": {
1522
+ "$ref": "task-output.schema.json"
1523
+ },
1329
1524
  "finishReason": {
1330
1525
  "enum": [
1331
1526
  "stop",
@@ -1396,6 +1591,9 @@ export const streamEventSchema = {
1396
1591
  "outcome": {
1397
1592
  "const": "error"
1398
1593
  },
1594
+ "partialText": {
1595
+ "type": "string"
1596
+ },
1399
1597
  "error": {
1400
1598
  "type": "object",
1401
1599
  "additionalProperties": true,
@@ -1456,8 +1654,7 @@ export const streamEventSchema = {
1456
1654
  "required": [
1457
1655
  "schemaVersion",
1458
1656
  "runId",
1459
- "outcome",
1460
- "partialText"
1657
+ "outcome"
1461
1658
  ],
1462
1659
  "properties": {
1463
1660
  "schemaVersion": {
@@ -1473,6 +1670,9 @@ export const streamEventSchema = {
1473
1670
  "partialText": {
1474
1671
  "type": "string"
1475
1672
  },
1673
+ "output": {
1674
+ "$ref": "task-output.schema.json"
1675
+ },
1476
1676
  "reason": {
1477
1677
  "type": "string"
1478
1678
  },
@@ -1542,7 +1742,7 @@ export const streamTerminalOutcomeSchema = {
1542
1742
  "$schema": "https://json-schema.org/draft/2020-12/schema",
1543
1743
  "$id": "https://schemas.inderun.dev/1.0/stream-terminal-outcome.schema.json",
1544
1744
  "title": "StreamTerminalOutcome",
1545
- "description": "How a Mode 2 stream run ended. Completion, error, and cancellation are mutually exclusive terminal outcomes, enforced here as a closed set of three oneOf branches discriminated by 'outcome' (unlike StreamEvent.type, this set is not open-ended: the three-way terminal outcome is a fixed architectural guarantee, not something new outcome kinds get added to). Exactly one StreamTerminalOutcome is produced per run, and no further StreamEvent is delivered after it (see docs/architecture/architecture.md, 'Cancellation And Fallback'). This shape is also embedded by value as the payload of the terminal StreamEvent (stream-event.schema.json) so it can additionally be exposed standalone, e.g. as a completion future/promise a stream handle resolves independently of consuming the full event sequence; the two copies must stay structurally identical. Note on additionalProperties: per this repo's forward-compatible convention every branch permits unknown extra fields, so mutual exclusivity is enforced via the required 'outcome' discriminator plus each branch's own required peer field (finalText/error/partialText) being present, not by forbidding a payload from also carrying an unrelated stray field from another branch's vocabulary.",
1745
+ "description": "How a Mode 2 stream run ended. Completion, error, and cancellation are mutually exclusive terminal outcomes, enforced here as a closed set of three oneOf branches discriminated by 'outcome' (unlike StreamEvent.type, this set is not open-ended: the three-way terminal outcome is a fixed architectural guarantee, not something new outcome kinds get added to; the payload of each branch may grow additively). Exactly one StreamTerminalOutcome is produced per run, and no further StreamEvent is delivered after it (see docs/architecture/architecture.md, 'Cancellation And Fallback'). This shape is also embedded by value as the payload of the terminal StreamEvent (stream-event.schema.json) so it can additionally be exposed standalone, e.g. as a completion future/promise a stream handle resolves independently of consuming the full event sequence; the two copies must stay structurally identical. Note on additionalProperties: per this repo's forward-compatible convention every branch permits unknown extra fields, so mutual exclusivity is enforced via the required 'outcome' discriminator plus each branch's own required peer field being present (finalText or output / error), and for 'cancelled' by the discriminator alone, not by forbidding a payload from also carrying an unrelated stray field from another branch's vocabulary.",
1546
1746
  "oneOf": [
1547
1747
  {
1548
1748
  "type": "object",
@@ -1550,8 +1750,19 @@ export const streamTerminalOutcomeSchema = {
1550
1750
  "required": [
1551
1751
  "schemaVersion",
1552
1752
  "runId",
1553
- "outcome",
1554
- "finalText"
1753
+ "outcome"
1754
+ ],
1755
+ "anyOf": [
1756
+ {
1757
+ "required": [
1758
+ "finalText"
1759
+ ]
1760
+ },
1761
+ {
1762
+ "required": [
1763
+ "output"
1764
+ ]
1765
+ }
1555
1766
  ],
1556
1767
  "properties": {
1557
1768
  "schemaVersion": {
@@ -1568,9 +1779,13 @@ export const streamTerminalOutcomeSchema = {
1568
1779
  "const": "completed"
1569
1780
  },
1570
1781
  "finalText": {
1571
- "description": "The full, cumulative text produced by the run. Equivalent in role to TaskResult.output.text for Mode 1.",
1782
+ "description": "The full, cumulative text produced by a text run. Equivalent in role to TaskResult.output.text for Mode 1. A text run sets it; a non-text run carries output instead.",
1572
1783
  "type": "string"
1573
1784
  },
1785
+ "output": {
1786
+ "description": "The normalized output of the run, the same shape run() returns in TaskResult.output. Set for every non-text run; a text run may set it alongside finalText. See task-output.schema.json.",
1787
+ "$ref": "task-output.schema.json"
1788
+ },
1574
1789
  "finishReason": {
1575
1790
  "description": "How generation ended, mirroring TaskResult.finishReason for Mode 1: 'stop' (natural end), 'length' (hit maxOutputTokens), 'error' (provider reported a non-fatal issue on an otherwise completed run). 'cancelled' is included so this enum stays identical to TaskResult.finishReason, but it is unreachable here: cancellation is its own terminal outcome branch. Optional: providers that do not report a finish reason omit it.",
1576
1791
  "enum": [
@@ -1708,6 +1923,10 @@ export const streamTerminalOutcomeSchema = {
1708
1923
  }
1709
1924
  }
1710
1925
  },
1926
+ "partialText": {
1927
+ "description": "Whatever cumulative text a text run had already delivered before it failed after committing to its provider. Absent when nothing was delivered.",
1928
+ "type": "string"
1929
+ },
1711
1930
  "provenance": {
1712
1931
  "description": "Normalized execution provenance: which target ran, its declared locality, the configured and reported model, and how fallback and routing played out. See execution-provenance.schema.json.",
1713
1932
  "$ref": "execution-provenance.schema.json"
@@ -1720,8 +1939,7 @@ export const streamTerminalOutcomeSchema = {
1720
1939
  "required": [
1721
1940
  "schemaVersion",
1722
1941
  "runId",
1723
- "outcome",
1724
- "partialText"
1942
+ "outcome"
1725
1943
  ],
1726
1944
  "properties": {
1727
1945
  "schemaVersion": {
@@ -1738,9 +1956,13 @@ export const streamTerminalOutcomeSchema = {
1738
1956
  "const": "cancelled"
1739
1957
  },
1740
1958
  "partialText": {
1741
- "description": "Whatever cumulative text had already been delivered via content_delta/content_snapshot StreamEvents before the cancellation point. May be empty if cancellation occurred before any content was produced.",
1959
+ "description": "Whatever cumulative text a text run had already delivered via content_delta/content_snapshot StreamEvents before the cancellation point. Always set for a text run, and empty if cancellation occurred before any content was produced. A non-text run never sets it.",
1742
1960
  "type": "string"
1743
1961
  },
1962
+ "output": {
1963
+ "description": "What a cancelled non-text run had already produced: a playback run that committed (audio may have been heard) carries a playback output, and an artifact run cancelled while the engine was writing its artifact to an application destination carries that application-owned artifact, whose bytes the write left at the destination. Absent when nothing was produced, e.g. an artifact run cancelled before its artifact was handed over, whose partial media the engine discards. See task-output.schema.json.",
1964
+ "$ref": "task-output.schema.json"
1965
+ },
1744
1966
  "reason": {
1745
1967
  "description": "Optional human-readable reason the run was cancelled (e.g. caller-initiated abort). Not a machine-taxonomy field; use errorClass on the 'error' outcome branch for failure classification.",
1746
1968
  "type": "string"
@@ -1753,27 +1975,674 @@ export const streamTerminalOutcomeSchema = {
1753
1975
  }
1754
1976
  ]
1755
1977
  };
1756
- export const executionProvenanceSchema = {
1978
+ export const providerCapabilitySnapshotSchema = {
1757
1979
  "$schema": "https://json-schema.org/draft/2020-12/schema",
1758
- "$id": "https://schemas.inderun.dev/1.0/execution-provenance.schema.json",
1759
- "title": "ExecutionProvenance",
1760
- "description": "Provider-neutral record of how an execution was carried out: the target the outcome is attributed to, its declared locality, the configured and provider-reported model, every attempt, and why other candidates were rejected. Carried on TaskResult, on every StreamTerminalOutcome branch, and on IndeRunError. It reports what the engine observed and what providers declared or reported; it never infers a model or locality a provider did not state, and it never contains request content, credentials, or provider payloads.",
1980
+ "$id": "https://schemas.inderun.dev/1.0/provider-capability-snapshot.schema.json",
1981
+ "title": "ProviderCapabilitySnapshot",
1982
+ "description": "One registered provider's identity, static declaration and live capability check, as returned by checkCapabilities(). A read-only snapshot: producing it executes no task and has no side effects.",
1761
1983
  "type": "object",
1762
1984
  "additionalProperties": true,
1763
1985
  "required": [
1764
- "schemaVersion",
1765
- "modelProfile",
1766
- "locality",
1767
- "attemptCount",
1768
- "fallbackOccurred",
1769
- "attempts",
1770
- "routeRejections"
1986
+ "providerId",
1987
+ "descriptor",
1988
+ "capabilities"
1771
1989
  ],
1772
1990
  "properties": {
1773
- "schemaVersion": {
1774
- "description": "Contract schema version used to interpret this provenance record.",
1775
- "const": "1.0"
1776
- },
1991
+ "providerId": {
1992
+ "description": "Identifier of the provider; equal to descriptor.id.",
1993
+ "type": "string",
1994
+ "minLength": 1
1995
+ },
1996
+ "descriptor": {
1997
+ "description": "The provider's static capability declaration.",
1998
+ "$ref": "provider-descriptor.schema.json"
1999
+ },
2000
+ "capabilities": {
2001
+ "description": "The provider's live capability check at the time of the call.",
2002
+ "$ref": "provider-dynamic-capabilities.schema.json"
2003
+ }
2004
+ }
2005
+ };
2006
+ export const providerDescriptorSchema = {
2007
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
2008
+ "$id": "https://schemas.inderun.dev/1.0/provider-descriptor.schema.json",
2009
+ "title": "ProviderDescriptor",
2010
+ "description": "A provider's static capability declaration: identity, placement, transport, supported interaction modes and tasks, cancellation guarantee, resource limits and privacy posture. It does not change while the provider is registered. The route planner, checkCapabilities() and execution provenance all read this one shape.",
2011
+ "type": "object",
2012
+ "additionalProperties": true,
2013
+ "required": [
2014
+ "id",
2015
+ "type",
2016
+ "transport",
2017
+ "supports",
2018
+ "cancel",
2019
+ "tasks"
2020
+ ],
2021
+ "properties": {
2022
+ "id": {
2023
+ "description": "Unique identifier of the provider instance (a target), e.g. 'apple_foundation_models' or 'openai_compatible_cloud:gpt-5-mini'. Model profiles list targets by this id.",
2024
+ "type": "string",
2025
+ "minLength": 1
2026
+ },
2027
+ "type": {
2028
+ "title": "ProviderType",
2029
+ "description": "Execution placement: 'local' (on the device), 'edge', or 'cloud'.",
2030
+ "enum": [
2031
+ "local",
2032
+ "edge",
2033
+ "cloud"
2034
+ ]
2035
+ },
2036
+ "model": {
2037
+ "description": "The model this provider instance is bound to, when it can name one. Cloud providers report the model id they send; platform-managed models report an opaque platform alias rather than a guessed weight name. Absent means the provider cannot name its model.",
2038
+ "type": "string",
2039
+ "minLength": 1
2040
+ },
2041
+ "transport": {
2042
+ "title": "ProviderTransport",
2043
+ "description": "How the engine reaches the provider: in-process runtime, operating-system service, buffered HTTP, server-sent events, or a realtime connection.",
2044
+ "enum": [
2045
+ "in_process",
2046
+ "system_service",
2047
+ "http",
2048
+ "sse",
2049
+ "realtime"
2050
+ ]
2051
+ },
2052
+ "streamingStyle": {
2053
+ "title": "StreamingStyle",
2054
+ "description": "How a streaming provider delivers incremental output: individual tokens, larger chunks, or whole snapshots. Absent for providers that do not stream.",
2055
+ "enum": [
2056
+ "tokens",
2057
+ "chunks",
2058
+ "snapshots"
2059
+ ]
2060
+ },
2061
+ "supports": {
2062
+ "title": "ProviderSupports",
2063
+ "description": "Interaction modes and features the provider statically declares.",
2064
+ "type": "object",
2065
+ "additionalProperties": true,
2066
+ "required": [
2067
+ "run",
2068
+ "streaming",
2069
+ "realtime",
2070
+ "tools",
2071
+ "reasoningEvents",
2072
+ "structuredOutput",
2073
+ "multimodal"
2074
+ ],
2075
+ "properties": {
2076
+ "run": {
2077
+ "description": "Whether the provider supports Mode 1 run (request/response).",
2078
+ "type": "boolean"
2079
+ },
2080
+ "streaming": {
2081
+ "description": "Whether the provider supports Mode 2 stream. A provider that declares it must also implement the streaming adapter; the engines report it as unavailable otherwise.",
2082
+ "type": "boolean"
2083
+ },
2084
+ "realtime": {
2085
+ "description": "Whether the provider supports Mode 3 realtime sessions. Reserved; no engine implements Mode 3 yet.",
2086
+ "type": "boolean"
2087
+ },
2088
+ "tools": {
2089
+ "description": "Whether the provider supports tool calling.",
2090
+ "type": "boolean"
2091
+ },
2092
+ "reasoningEvents": {
2093
+ "description": "Whether the provider emits reasoning events alongside content.",
2094
+ "type": "boolean"
2095
+ },
2096
+ "structuredOutput": {
2097
+ "description": "Whether the provider supports structured output constrained by a JSON schema.",
2098
+ "type": "boolean"
2099
+ },
2100
+ "multimodal": {
2101
+ "description": "Whether the provider accepts non-text inputs such as images or audio.",
2102
+ "type": "boolean"
2103
+ }
2104
+ }
2105
+ },
2106
+ "cancel": {
2107
+ "title": "CancelSemantics",
2108
+ "description": "Cancellation guarantee: 'hard' stops processing including remote work, 'soft' stops reading locally while remote work may continue, 'none' means cancellation is unsupported. Independently of this value, the engine delivers no event after a cancel.",
2109
+ "enum": [
2110
+ "hard",
2111
+ "soft",
2112
+ "none"
2113
+ ]
2114
+ },
2115
+ "tasks": {
2116
+ "description": "Task kinds the provider supports, e.g. 'text_to_text'. When taskCapabilities is present it must describe the same kinds and is what routing reads; without it, routing infers capabilities for 'text_to_text' alone (see taskCapabilities).",
2117
+ "type": "array",
2118
+ "items": {
2119
+ "type": "string",
2120
+ "minLength": 1
2121
+ }
2122
+ },
2123
+ "limits": {
2124
+ "title": "ProviderLimits",
2125
+ "description": "Optional input and output resource limits.",
2126
+ "type": "object",
2127
+ "additionalProperties": true,
2128
+ "properties": {
2129
+ "maxInputTokens": {
2130
+ "description": "Largest accepted input, in tokens.",
2131
+ "type": "integer",
2132
+ "minimum": 1
2133
+ },
2134
+ "maxOutputTokens": {
2135
+ "description": "Largest output the provider produces, in tokens.",
2136
+ "type": "integer",
2137
+ "minimum": 1
2138
+ },
2139
+ "maxImageBytes": {
2140
+ "description": "Largest accepted image, in bytes.",
2141
+ "type": "integer",
2142
+ "minimum": 1
2143
+ },
2144
+ "maxAudioSeconds": {
2145
+ "description": "Longest accepted or produced audio, in seconds.",
2146
+ "type": "integer",
2147
+ "minimum": 1
2148
+ }
2149
+ }
2150
+ },
2151
+ "privacy": {
2152
+ "title": "ProviderPrivacy",
2153
+ "description": "Data-egress posture, used to enforce local and cloud routing rules. Absent means a local or edge provider is treated as keeping data on the device and a cloud provider as sending it off.",
2154
+ "type": "object",
2155
+ "additionalProperties": true,
2156
+ "required": [
2157
+ "dataLeavesDevice"
2158
+ ],
2159
+ "properties": {
2160
+ "dataLeavesDevice": {
2161
+ "description": "Whether request content leaves the device.",
2162
+ "type": "boolean"
2163
+ },
2164
+ "regions": {
2165
+ "description": "Geographic regions where a cloud provider processes data.",
2166
+ "type": "array",
2167
+ "items": {
2168
+ "type": "string",
2169
+ "minLength": 1
2170
+ }
2171
+ }
2172
+ }
2173
+ },
2174
+ "taskCapabilities": {
2175
+ "description": "What the provider can do for each task: accepted inputs, interaction modes, output deliveries and formats, cancellation, locales and privacy. When present it is authoritative for routing and must list every task the provider serves. When absent (a legacy descriptor), routing infers exactly one entry — 'text_to_text' with text input, inline output, run/stream from supports and cancel from cancel — if tasks contains 'text_to_text', and nothing for any other task: a legacy descriptor is never treated as capable of a non-text task.",
2176
+ "type": "array",
2177
+ "items": {
2178
+ "$ref": "#/$defs/taskCapability"
2179
+ }
2180
+ }
2181
+ },
2182
+ "$defs": {
2183
+ "taskCapability": {
2184
+ "title": "ProviderTaskCapability",
2185
+ "description": "The provider's static capabilities for one task kind.",
2186
+ "type": "object",
2187
+ "additionalProperties": true,
2188
+ "required": [
2189
+ "kind",
2190
+ "inputs",
2191
+ "interaction",
2192
+ "cancel",
2193
+ "outputs"
2194
+ ],
2195
+ "properties": {
2196
+ "kind": {
2197
+ "description": "The task kind this entry describes, e.g. 'text_to_speech'.",
2198
+ "type": "string",
2199
+ "minLength": 1
2200
+ },
2201
+ "inputs": {
2202
+ "description": "Input media kinds the provider accepts for this task.",
2203
+ "type": "array",
2204
+ "minItems": 1,
2205
+ "items": {
2206
+ "$ref": "#/$defs/inputKind"
2207
+ }
2208
+ },
2209
+ "interaction": {
2210
+ "title": "ProviderTaskInteraction",
2211
+ "description": "Interaction modes the provider supports for this task.",
2212
+ "type": "object",
2213
+ "additionalProperties": true,
2214
+ "required": [
2215
+ "run",
2216
+ "stream"
2217
+ ],
2218
+ "properties": {
2219
+ "run": {
2220
+ "description": "Whether the provider supports Mode 1 run for this task.",
2221
+ "type": "boolean"
2222
+ },
2223
+ "stream": {
2224
+ "description": "Whether the provider supports Mode 2 stream for this task.",
2225
+ "type": "boolean"
2226
+ }
2227
+ }
2228
+ },
2229
+ "cancel": {
2230
+ "description": "Cancellation guarantee for this task. It may differ from the provider-level cancel, e.g. when stopping playback is immediate but a synthesis request cannot be aborted.",
2231
+ "$ref": "#/properties/cancel"
2232
+ },
2233
+ "outputs": {
2234
+ "description": "How results of this task can be delivered. A provider lists only what it really produces: a provider that can only play audio lists 'playback' and never 'artifact'.",
2235
+ "type": "array",
2236
+ "minItems": 1,
2237
+ "items": {
2238
+ "$ref": "#/$defs/taskOutput"
2239
+ }
2240
+ },
2241
+ "locales": {
2242
+ "description": "BCP 47 locales the provider can serve for this task, when it knows them statically. Absent means unknown; the dynamic check may report them instead.",
2243
+ "type": "array",
2244
+ "items": {
2245
+ "type": "string",
2246
+ "minLength": 1
2247
+ }
2248
+ },
2249
+ "privacy": {
2250
+ "description": "Data-egress posture for this task when it differs from the provider-level privacy, e.g. a provider whose speech synthesis uses a network service. Absent inherits descriptor.privacy.",
2251
+ "$ref": "#/properties/privacy"
2252
+ }
2253
+ }
2254
+ },
2255
+ "taskOutput": {
2256
+ "title": "ProviderTaskOutput",
2257
+ "description": "One way the provider can deliver a task's result.",
2258
+ "type": "object",
2259
+ "additionalProperties": true,
2260
+ "required": [
2261
+ "delivery"
2262
+ ],
2263
+ "properties": {
2264
+ "delivery": {
2265
+ "$ref": "#/$defs/outputDelivery"
2266
+ },
2267
+ "mediaTypes": {
2268
+ "description": "Media types the provider can produce with this delivery, e.g. 'audio/wav'. Absent means the provider does not declare them; a request that requires specific media types never routes to such an output.",
2269
+ "type": "array",
2270
+ "items": {
2271
+ "type": "string",
2272
+ "minLength": 1
2273
+ }
2274
+ }
2275
+ }
2276
+ },
2277
+ "inputKind": {
2278
+ "title": "TaskInputKind",
2279
+ "description": "A kind of input medium a task consumes.",
2280
+ "enum": [
2281
+ "text",
2282
+ "image",
2283
+ "audio"
2284
+ ]
2285
+ },
2286
+ "outputDelivery": {
2287
+ "title": "OutputDelivery",
2288
+ "description": "How a task's result reaches the application: 'inline' content in the result (text), 'playback' as a managed, audible effect on the device with no retrievable bytes, or 'artifact' as retrievable media.",
2289
+ "enum": [
2290
+ "inline",
2291
+ "playback",
2292
+ "artifact"
2293
+ ]
2294
+ }
2295
+ }
2296
+ };
2297
+ export const providerDynamicCapabilitiesSchema = {
2298
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
2299
+ "$id": "https://schemas.inderun.dev/1.0/provider-dynamic-capabilities.schema.json",
2300
+ "title": "ProviderDynamicCapabilities",
2301
+ "$comment": "streamingAvailable and cancellationAvailable are tri-state on purpose: true, false, or absent. Absent means 'inherit the static declaration' and is a distinct state from both booleans. They must never become nullable: null would be a fourth state no consumer handles, and bridges omit the key specifically to preserve 'absent'.",
2302
+ "description": "A provider's live capability check, evaluated on the current host when routing or when checkCapabilities() is called. Unlike the descriptor it can change between calls.",
2303
+ "type": "object",
2304
+ "additionalProperties": true,
2305
+ "required": [
2306
+ "available"
2307
+ ],
2308
+ "properties": {
2309
+ "available": {
2310
+ "description": "Whether the provider is available and functional on this host right now.",
2311
+ "type": "boolean"
2312
+ },
2313
+ "reason": {
2314
+ "description": "Why the provider is unavailable, when available is false.",
2315
+ "type": "string",
2316
+ "minLength": 1
2317
+ },
2318
+ "streamingAvailable": {
2319
+ "description": "Whether the provider can stream (Mode 2) on this host right now. Absent inherits the static descriptor.supports.streaming value; only set it when the host takes streaming away from a provider that otherwise declares it. Never null.",
2320
+ "type": "boolean"
2321
+ },
2322
+ "streamingUnavailableReason": {
2323
+ "description": "Why streaming is unavailable, when streamingAvailable is false. Absent lets the route planner synthesize a default message.",
2324
+ "type": "string",
2325
+ "minLength": 1
2326
+ },
2327
+ "cancellationAvailable": {
2328
+ "description": "Whether cancellation is honored on this host right now. Absent inherits the static descriptor.cancel value: any value other than 'none' means available. Never null.",
2329
+ "type": "boolean"
2330
+ },
2331
+ "taskCapabilities": {
2332
+ "description": "Per-task overrides of the provider-level check, for tasks whose availability differs on this host, e.g. a voice that is not installed. Every field of an entry is optional and absent inherits the provider-level value; a false at either level wins.",
2333
+ "type": "array",
2334
+ "items": {
2335
+ "$ref": "#/$defs/taskAvailability"
2336
+ }
2337
+ }
2338
+ },
2339
+ "$defs": {
2340
+ "taskAvailability": {
2341
+ "title": "ProviderTaskAvailability",
2342
+ "description": "The live check for one task kind.",
2343
+ "type": "object",
2344
+ "additionalProperties": true,
2345
+ "required": [
2346
+ "kind"
2347
+ ],
2348
+ "properties": {
2349
+ "kind": {
2350
+ "description": "The task kind this entry describes.",
2351
+ "type": "string",
2352
+ "minLength": 1
2353
+ },
2354
+ "available": {
2355
+ "description": "Whether the task can run on this host right now. Absent inherits the provider-level available. Never null.",
2356
+ "type": "boolean"
2357
+ },
2358
+ "reason": {
2359
+ "description": "Why the task is unavailable, when available is false.",
2360
+ "type": "string",
2361
+ "minLength": 1
2362
+ },
2363
+ "streamingAvailable": {
2364
+ "description": "Whether the task can stream on this host right now. Absent inherits. Never null.",
2365
+ "type": "boolean"
2366
+ },
2367
+ "streamingUnavailableReason": {
2368
+ "description": "Why streaming is unavailable for this task, when streamingAvailable is false.",
2369
+ "type": "string",
2370
+ "minLength": 1
2371
+ },
2372
+ "cancellationAvailable": {
2373
+ "description": "Whether cancellation is honored for this task right now. Absent inherits. Never null.",
2374
+ "type": "boolean"
2375
+ },
2376
+ "locales": {
2377
+ "description": "BCP 47 locales available for this task right now. Absent means the provider cannot enumerate them.",
2378
+ "type": "array",
2379
+ "items": {
2380
+ "type": "string",
2381
+ "minLength": 1
2382
+ }
2383
+ },
2384
+ "voices": {
2385
+ "description": "Voices available for this task right now. Absent means the provider cannot enumerate them; a request that requires a voice then never routes to it.",
2386
+ "type": "array",
2387
+ "items": {
2388
+ "$ref": "#/$defs/voice"
2389
+ }
2390
+ }
2391
+ }
2392
+ },
2393
+ "voice": {
2394
+ "title": "ProviderVoice",
2395
+ "description": "Provider-neutral traits of one available voice. Carries no provider voice identifier.",
2396
+ "type": "object",
2397
+ "additionalProperties": true,
2398
+ "required": [
2399
+ "locale"
2400
+ ],
2401
+ "properties": {
2402
+ "locale": {
2403
+ "description": "BCP 47 locale the voice speaks.",
2404
+ "type": "string",
2405
+ "minLength": 1
2406
+ },
2407
+ "gender": {
2408
+ "$ref": "#/$defs/voiceGender"
2409
+ },
2410
+ "networkRequired": {
2411
+ "description": "Whether using the voice sends data off the device. Absent inherits the task's privacy posture.",
2412
+ "type": "boolean"
2413
+ }
2414
+ }
2415
+ },
2416
+ "voiceGender": {
2417
+ "title": "VoiceGender",
2418
+ "description": "Perceived gender of a voice, as the platform reports it.",
2419
+ "enum": [
2420
+ "female",
2421
+ "male",
2422
+ "neutral"
2423
+ ]
2424
+ }
2425
+ }
2426
+ };
2427
+ export const artifactReadRequestSchema = {
2428
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
2429
+ "$id": "https://schemas.inderun.dev/1.0/artifact-read-request.schema.json",
2430
+ "title": "ArtifactReadRequest",
2431
+ "description": "A ranged read of an engine-owned media artifact through IndeRunApi.readArtifact (ADR 017). Reads are repeatable until the artifact is disposed; each returns at most the engine's configured maximum read size, which bounds what one call -- and one bridge message -- carries.",
2432
+ "type": "object",
2433
+ "additionalProperties": true,
2434
+ "required": [
2435
+ "artifactId"
2436
+ ],
2437
+ "properties": {
2438
+ "artifactId": {
2439
+ "description": "MediaArtifact.id of the artifact to read.",
2440
+ "type": "string",
2441
+ "minLength": 1
2442
+ },
2443
+ "offset": {
2444
+ "description": "Byte offset to read from. Absent means 0. An offset equal to the artifact's size returns an empty, final chunk; a larger one is rejected.",
2445
+ "type": "integer",
2446
+ "minimum": 0
2447
+ },
2448
+ "maxBytes": {
2449
+ "description": "Most bytes to return. Absent, or larger than the engine's maximum read size, means that maximum.",
2450
+ "type": "integer",
2451
+ "minimum": 1
2452
+ }
2453
+ }
2454
+ };
2455
+ export const taskOutputSchema = {
2456
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
2457
+ "$id": "https://schemas.inderun.dev/1.0/task-output.schema.json",
2458
+ "title": "TaskOutput",
2459
+ "description": "The normalized output of a run, shared by TaskResult and the completed and cancelled stream terminal outcomes (ADR 016). 'type' describes the content representation, not the task: 'text' carries inline text, 'playback' reports a managed, audible effect that has no retrievable bytes, and 'artifact' references retrievable media (ADR 017). A playback output is never an artifact, and an artifact output never implies that anything was played.",
2460
+ "type": "object",
2461
+ "additionalProperties": true,
2462
+ "required": [
2463
+ "type"
2464
+ ],
2465
+ "allOf": [
2466
+ {
2467
+ "if": {
2468
+ "properties": {
2469
+ "type": {
2470
+ "const": "text"
2471
+ }
2472
+ }
2473
+ },
2474
+ "then": {
2475
+ "required": [
2476
+ "text"
2477
+ ]
2478
+ }
2479
+ },
2480
+ {
2481
+ "if": {
2482
+ "properties": {
2483
+ "type": {
2484
+ "const": "playback"
2485
+ }
2486
+ }
2487
+ },
2488
+ "then": {
2489
+ "required": [
2490
+ "playback"
2491
+ ]
2492
+ }
2493
+ },
2494
+ {
2495
+ "if": {
2496
+ "properties": {
2497
+ "type": {
2498
+ "const": "artifact"
2499
+ }
2500
+ }
2501
+ },
2502
+ "then": {
2503
+ "required": [
2504
+ "artifact"
2505
+ ]
2506
+ }
2507
+ }
2508
+ ],
2509
+ "properties": {
2510
+ "type": {
2511
+ "title": "TaskOutputType",
2512
+ "description": "The content representation: 'text', 'playback' or 'artifact'.",
2513
+ "enum": [
2514
+ "text",
2515
+ "playback",
2516
+ "artifact"
2517
+ ]
2518
+ },
2519
+ "text": {
2520
+ "description": "The text the run produced. Required when type is 'text'.",
2521
+ "type": "string"
2522
+ },
2523
+ "playback": {
2524
+ "title": "PlaybackOutput",
2525
+ "description": "Completion of managed playback. Required when type is 'playback'. Carries no media.",
2526
+ "type": "object",
2527
+ "additionalProperties": true,
2528
+ "properties": {
2529
+ "durationMs": {
2530
+ "description": "How long audible playback lasted, in milliseconds. For a cancelled run, how long it played before it was stopped.",
2531
+ "type": "number",
2532
+ "minimum": 0
2533
+ }
2534
+ }
2535
+ },
2536
+ "artifact": {
2537
+ "description": "The retrievable media the run produced. Required when type is 'artifact'.",
2538
+ "$ref": "media-artifact.schema.json"
2539
+ },
2540
+ "speech": {
2541
+ "title": "SpeechOutput",
2542
+ "description": "The locale and voice traits a text-to-speech run actually used, so an unmet preference is visible without being an error.",
2543
+ "type": "object",
2544
+ "additionalProperties": true,
2545
+ "required": [
2546
+ "locale"
2547
+ ],
2548
+ "properties": {
2549
+ "locale": {
2550
+ "description": "BCP 47 tag of the locale that was spoken.",
2551
+ "type": "string",
2552
+ "minLength": 1
2553
+ },
2554
+ "voice": {
2555
+ "title": "SpeechOutputVoice",
2556
+ "description": "Provider-neutral traits of the voice that spoke. Carries no provider voice identifier.",
2557
+ "type": "object",
2558
+ "additionalProperties": true,
2559
+ "properties": {
2560
+ "gender": {
2561
+ "$ref": "provider-dynamic-capabilities.schema.json#/$defs/voiceGender"
2562
+ }
2563
+ }
2564
+ }
2565
+ }
2566
+ }
2567
+ }
2568
+ };
2569
+ export const mediaArtifactSchema = {
2570
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
2571
+ "$id": "https://schemas.inderun.dev/1.0/media-artifact.schema.json",
2572
+ "title": "MediaArtifact",
2573
+ "description": "A reference to retrievable media a run produced (ADR 017). It carries no bytes and no location: an engine-owned artifact is read through the engine by id, and an application-owned one was written to an application destination. Media-neutral: audio, images and video share this shape and differ only in mediaType.",
2574
+ "type": "object",
2575
+ "additionalProperties": true,
2576
+ "required": [
2577
+ "id",
2578
+ "mediaType",
2579
+ "owner",
2580
+ "lifetime"
2581
+ ],
2582
+ "properties": {
2583
+ "id": {
2584
+ "description": "Opaque, engine-issued identifier. Never a path, URL or provider identifier.",
2585
+ "type": "string",
2586
+ "minLength": 1
2587
+ },
2588
+ "mediaType": {
2589
+ "description": "IANA media type of the bytes, e.g. 'audio/wav'. Encoding parameters ride on the media type (e.g. 'audio/L16;rate=22050').",
2590
+ "type": "string",
2591
+ "minLength": 1
2592
+ },
2593
+ "owner": {
2594
+ "title": "ArtifactOwner",
2595
+ "description": "'engine': the engine holds the bytes and the application reads them by id until it disposes the artifact. 'application': the bytes were written to the application destination named by destinationRef and the engine holds nothing.",
2596
+ "enum": [
2597
+ "engine",
2598
+ "application"
2599
+ ]
2600
+ },
2601
+ "lifetime": {
2602
+ "title": "ArtifactLifetime",
2603
+ "description": "How long the engine keeps the bytes. 'until_disposed': until the application disposes the artifact or the process ends; the artifact outlives the run that produced it.",
2604
+ "enum": [
2605
+ "until_disposed"
2606
+ ]
2607
+ },
2608
+ "byteLength": {
2609
+ "description": "Size of the media in bytes. Always set for an artifact the engine materialized.",
2610
+ "type": "integer",
2611
+ "minimum": 0
2612
+ },
2613
+ "durationMs": {
2614
+ "description": "Duration of time-based media in milliseconds, where the producer knows it.",
2615
+ "type": "number",
2616
+ "minimum": 0
2617
+ },
2618
+ "destinationRef": {
2619
+ "description": "The request's delivery.destinationRef the bytes were written to. Set exactly when owner is 'application'.",
2620
+ "type": "string",
2621
+ "minLength": 1
2622
+ }
2623
+ }
2624
+ };
2625
+ export const executionProvenanceSchema = {
2626
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
2627
+ "$id": "https://schemas.inderun.dev/1.0/execution-provenance.schema.json",
2628
+ "title": "ExecutionProvenance",
2629
+ "description": "Provider-neutral record of how an execution was carried out: the target the outcome is attributed to, its declared locality, the configured and provider-reported model, every attempt, and why other candidates were rejected. Carried on TaskResult, on every StreamTerminalOutcome branch, and on IndeRunError. It reports what the engine observed and what providers declared or reported; it never infers a model or locality a provider did not state, and it never contains request content, credentials, or provider payloads.",
2630
+ "type": "object",
2631
+ "additionalProperties": true,
2632
+ "required": [
2633
+ "schemaVersion",
2634
+ "modelProfile",
2635
+ "locality",
2636
+ "attemptCount",
2637
+ "fallbackOccurred",
2638
+ "attempts",
2639
+ "routeRejections"
2640
+ ],
2641
+ "properties": {
2642
+ "schemaVersion": {
2643
+ "description": "Contract schema version used to interpret this provenance record.",
2644
+ "const": "1.0"
2645
+ },
1777
2646
  "modelProfile": {
1778
2647
  "description": "The model profile the request routed within; 'auto' when the request named none.",
1779
2648
  "type": "string",
@@ -1837,6 +2706,10 @@ export const executionProvenanceSchema = {
1837
2706
  "cancelled"
1838
2707
  ]
1839
2708
  },
2709
+ "committed": {
2710
+ "description": "Whether the run committed to this provider during the attempt: its first content was delivered, it signalled the first possible audible effect, or it handed over an artifact. After commit a failure ends the run instead of falling back. Absent means the attempt did not commit.",
2711
+ "type": "boolean"
2712
+ },
1840
2713
  "errorClass": {
1841
2714
  "description": "Normalized error class of a failed attempt.",
1842
2715
  "enum": [
@@ -1880,7 +2753,12 @@ export const executionProvenanceSchema = {
1880
2753
  "cloud_constraint",
1881
2754
  "offline",
1882
2755
  "capability_unavailable",
1883
- "not_in_model_profile"
2756
+ "not_in_model_profile",
2757
+ "input_not_supported",
2758
+ "output_delivery_not_supported",
2759
+ "output_format_not_supported",
2760
+ "locale_not_supported",
2761
+ "voice_not_available"
1884
2762
  ]
1885
2763
  }
1886
2764
  }