theorum 1.0.0 → 1.1.3

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 (204) hide show
  1. package/README.md +81 -25
  2. package/esm/mod.d.ts +48 -29
  3. package/esm/mod.js +37 -24
  4. package/esm/src/cli/commands/bench.js +13 -16
  5. package/esm/src/cli/commands/fuzz-canary.js +25 -29
  6. package/esm/src/cli/commands/guardrails-eval.d.ts +14 -0
  7. package/esm/src/cli/commands/guardrails-eval.js +15 -0
  8. package/esm/src/cli/commands/profile.js +12 -3
  9. package/esm/src/cli/commands/run.js +8 -9
  10. package/esm/src/cli/commands/test.js +8 -8
  11. package/esm/src/cli/event-log.d.ts +1 -1
  12. package/esm/src/cli/index.js +12 -0
  13. package/esm/src/cli/matrix/synthesizer.d.ts +6 -6
  14. package/esm/src/cli/matrix/synthesizer.js +23 -22
  15. package/esm/src/guardrails/canary-gate.d.ts +2 -1
  16. package/esm/src/guardrails/canary-gate.js +2 -1
  17. package/esm/src/guardrails/canary.d.ts +9 -1
  18. package/esm/src/guardrails/canary.js +21 -10
  19. package/esm/src/guardrails/corpus/canary-egress-attacks.d.ts +1 -0
  20. package/esm/src/guardrails/corpus/fuzz-inbound.js +12 -15
  21. package/esm/src/guardrails/corpus/inbound-payloads.js +1 -0
  22. package/esm/src/guardrails/corpus/live-attacks.js +0 -1
  23. package/esm/src/guardrails/corpus/mod.d.ts +1 -0
  24. package/esm/src/guardrails/corpus/secrets.d.ts +1 -0
  25. package/esm/src/guardrails/corpus/secrets.js +1 -0
  26. package/esm/src/guardrails/corpus/strings.d.ts +1 -0
  27. package/esm/src/guardrails/corpus/strings.js +1 -0
  28. package/esm/src/guardrails/corpus/types.d.ts +1 -0
  29. package/esm/src/guardrails/egress.d.ts +26 -3
  30. package/esm/src/guardrails/egress.js +69 -18
  31. package/esm/src/guardrails/error.d.ts +14 -23
  32. package/esm/src/guardrails/error.js +86 -77
  33. package/esm/src/guardrails/eval/corpus.d.ts +108 -0
  34. package/esm/src/guardrails/eval/corpus.js +978 -0
  35. package/esm/src/guardrails/eval/mod.d.ts +51 -0
  36. package/esm/src/guardrails/eval/mod.js +133 -0
  37. package/esm/src/guardrails/eval/score.d.ts +66 -0
  38. package/esm/src/guardrails/eval/score.js +114 -0
  39. package/esm/src/guardrails/events.d.ts +25 -0
  40. package/esm/src/guardrails/events.js +56 -0
  41. package/esm/src/guardrails/hits.d.ts +24 -0
  42. package/esm/src/guardrails/hits.js +45 -0
  43. package/esm/src/guardrails/lexicon.d.ts +39 -0
  44. package/esm/src/guardrails/lexicon.js +200 -0
  45. package/esm/src/guardrails/live-outbound-gate.d.ts +15 -13
  46. package/esm/src/guardrails/live-outbound-gate.js +131 -89
  47. package/esm/src/guardrails/mod.d.ts +19 -4
  48. package/esm/src/guardrails/mod.js +12 -3
  49. package/esm/src/guardrails/network.d.ts +19 -0
  50. package/esm/src/guardrails/network.js +234 -0
  51. package/esm/src/guardrails/policy.d.ts +35 -0
  52. package/esm/src/guardrails/policy.js +50 -0
  53. package/esm/src/guardrails/progressive-yield.d.ts +51 -0
  54. package/esm/src/guardrails/progressive-yield.js +98 -0
  55. package/esm/src/guardrails/quota.d.ts +17 -3
  56. package/esm/src/guardrails/quota.js +18 -4
  57. package/esm/src/guardrails/sanitize.d.ts +36 -6
  58. package/esm/src/guardrails/sanitize.js +152 -60
  59. package/esm/src/guardrails/serialize.d.ts +35 -0
  60. package/esm/src/guardrails/serialize.js +58 -0
  61. package/esm/src/guardrails/testing.d.ts +7 -0
  62. package/esm/src/guardrails/testing.js +4 -0
  63. package/esm/src/guardrails/theorum-error.d.ts +12 -0
  64. package/esm/src/guardrails/theorum-error.js +15 -0
  65. package/esm/src/guardrails/tool-directives.d.ts +48 -0
  66. package/esm/src/guardrails/tool-directives.js +124 -0
  67. package/esm/src/guardrails/tool-result.d.ts +93 -0
  68. package/esm/src/guardrails/tool-result.js +276 -0
  69. package/esm/src/guardrails/types.d.ts +291 -0
  70. package/esm/src/guardrails/types.js +72 -0
  71. package/esm/src/host/client-turn.js +4 -0
  72. package/esm/src/host/mint-trace.d.ts +1 -1
  73. package/esm/src/kernel/auth/crypto.d.ts +42 -0
  74. package/esm/src/kernel/auth/crypto.js +106 -0
  75. package/esm/src/kernel/auth/mod.d.ts +11 -0
  76. package/esm/src/kernel/auth/mod.js +11 -0
  77. package/esm/src/kernel/auth/oauth.d.ts +47 -0
  78. package/esm/src/kernel/auth/oauth.js +278 -0
  79. package/esm/src/kernel/auth/types.d.ts +133 -0
  80. package/esm/src/kernel/auth/types.js +13 -0
  81. package/esm/src/kernel/engine/delta.js +43 -2
  82. package/esm/src/kernel/engine/live-inbound.d.ts +15 -3
  83. package/esm/src/kernel/engine/live-inbound.js +22 -8
  84. package/esm/src/kernel/engine/live-ingress.d.ts +19 -0
  85. package/esm/src/kernel/engine/live-ingress.js +47 -0
  86. package/esm/src/kernel/engine/repair.js +13 -12
  87. package/esm/src/kernel/engine/runner/gates.js +127 -40
  88. package/esm/src/kernel/engine/runner/mod.d.ts +6 -4
  89. package/esm/src/kernel/engine/runner/mod.js +182 -43
  90. package/esm/src/kernel/engine/runner/schema-validation.js +3 -3
  91. package/esm/src/kernel/engine/runner/stages.d.ts +39 -0
  92. package/esm/src/kernel/engine/runner/stages.js +89 -0
  93. package/esm/src/kernel/engine/runner/state.d.ts +24 -0
  94. package/esm/src/kernel/engine/runner/steps.js +128 -61
  95. package/esm/src/kernel/engine/runner/stream.d.ts +9 -2
  96. package/esm/src/kernel/engine/runner/stream.js +114 -55
  97. package/esm/src/kernel/engine/session/mod.d.ts +3 -1
  98. package/esm/src/kernel/engine/session/mod.js +386 -34
  99. package/esm/src/kernel/interaction-parts.d.ts +14 -0
  100. package/esm/src/kernel/interaction-parts.js +23 -0
  101. package/esm/src/kernel/mod.d.ts +19 -7
  102. package/esm/src/kernel/mod.js +10 -5
  103. package/esm/src/kernel/profile-graph.d.ts +159 -0
  104. package/esm/src/kernel/profile-graph.js +156 -0
  105. package/esm/src/kernel/registry/attachments.d.ts +10 -10
  106. package/esm/src/kernel/registry/attachments.js +29 -26
  107. package/esm/src/kernel/registry/catalog.d.ts +25 -11
  108. package/esm/src/kernel/registry/catalog.js +59 -24
  109. package/esm/src/kernel/registry/ingress.js +34 -22
  110. package/esm/src/kernel/registry/profile-outputs.d.ts +4 -0
  111. package/esm/src/kernel/registry/profile-outputs.js +8 -0
  112. package/esm/src/kernel/registry/profiles.d.ts +34 -14
  113. package/esm/src/kernel/registry/profiles.js +306 -59
  114. package/esm/src/kernel/registry/provider-request.js +2 -0
  115. package/esm/src/kernel/registry/resolve.d.ts +7 -6
  116. package/esm/src/kernel/registry/resolve.js +102 -117
  117. package/esm/src/kernel/registry/schemas.js +1 -1
  118. package/esm/src/kernel/registry/sole-model.d.ts +8 -0
  119. package/esm/src/kernel/registry/sole-model.js +10 -0
  120. package/esm/src/kernel/registry/system-prompt.d.ts +10 -0
  121. package/esm/src/kernel/registry/system-prompt.js +40 -0
  122. package/esm/src/kernel/registry/system-role.d.ts +8 -0
  123. package/esm/src/kernel/registry/system-role.js +14 -0
  124. package/esm/src/kernel/registry/vault.d.ts +2 -2
  125. package/esm/src/kernel/registry/vault.js +4 -4
  126. package/esm/src/kernel/schema.d.ts +97 -11
  127. package/esm/src/kernel/schema.js +256 -111
  128. package/esm/src/kernel/stages.d.ts +175 -0
  129. package/esm/src/kernel/stages.js +476 -0
  130. package/esm/src/kernel/stop.d.ts +65 -11
  131. package/esm/src/kernel/stop.js +46 -11
  132. package/esm/src/kernel/tools/events.d.ts +41 -0
  133. package/esm/src/kernel/tools/events.js +71 -0
  134. package/esm/src/kernel/tools/execute.d.ts +63 -24
  135. package/esm/src/kernel/tools/execute.js +475 -224
  136. package/esm/src/kernel/tools/harness.js +16 -14
  137. package/esm/src/kernel/tools/invoke.js +26 -8
  138. package/esm/src/kernel/tools/mod.d.ts +3 -1
  139. package/esm/src/kernel/tools/mod.js +2 -1
  140. package/esm/src/kernel/tools/permission.d.ts +15 -0
  141. package/esm/src/kernel/tools/permission.js +47 -0
  142. package/esm/src/kernel/tools/project.js +5 -3
  143. package/esm/src/kernel/tools/registry.js +20 -10
  144. package/esm/src/kernel/tools/remote.d.ts +94 -0
  145. package/esm/src/kernel/tools/remote.js +577 -0
  146. package/esm/src/kernel/tools/resolve.d.ts +17 -7
  147. package/esm/src/kernel/tools/resolve.js +38 -17
  148. package/esm/src/kernel/tools/schema.d.ts +2 -0
  149. package/esm/src/kernel/tools/schema.js +23 -6
  150. package/esm/src/kernel/tools/stage-run.d.ts +105 -0
  151. package/esm/src/kernel/tools/stage-run.js +155 -0
  152. package/esm/src/kernel/tools/types.d.ts +198 -20
  153. package/esm/src/kernel/types.d.ts +253 -114
  154. package/esm/src/kernel/util/find-last.d.ts +2 -0
  155. package/esm/src/kernel/util/find-last.js +10 -0
  156. package/esm/src/observability/destinations.d.ts +31 -0
  157. package/esm/src/observability/destinations.js +67 -0
  158. package/esm/src/observability/mod.d.ts +10 -3
  159. package/esm/src/observability/mod.js +6 -2
  160. package/esm/src/observability/policy.d.ts +27 -0
  161. package/esm/src/observability/policy.js +80 -0
  162. package/esm/src/observability/resolve-policy.d.ts +16 -0
  163. package/esm/src/observability/resolve-policy.js +64 -0
  164. package/esm/src/observability/trace-attach.d.ts +3 -1
  165. package/esm/src/observability/trace-attach.js +17 -10
  166. package/esm/src/observability/trace-record.d.ts +12 -4
  167. package/esm/src/observability/trace-record.js +63 -19
  168. package/esm/src/observability/trace-sink.d.ts +19 -0
  169. package/esm/src/observability/trace-sink.js +10 -0
  170. package/esm/src/observability/trace.d.ts +14 -11
  171. package/esm/src/observability/trace.js +23 -14
  172. package/esm/src/observability/types.d.ts +113 -0
  173. package/esm/src/observability/types.js +11 -0
  174. package/esm/src/providers/create-provider.d.ts +7 -4
  175. package/esm/src/providers/create-provider.js +21 -7
  176. package/esm/src/providers/google/interactions/framing.d.ts +8 -0
  177. package/esm/src/providers/google/interactions/framing.js +74 -13
  178. package/esm/src/providers/google/live/framing.d.ts +5 -2
  179. package/esm/src/providers/google/live/framing.js +49 -12
  180. package/esm/src/providers/google/live/openapi-schema.js +3 -2
  181. package/esm/src/providers/google/live/stream.d.ts +9 -0
  182. package/esm/src/providers/google/live/stream.js +16 -3
  183. package/esm/src/providers/openrouter/cache-control.d.ts +24 -0
  184. package/esm/src/providers/openrouter/cache-control.js +23 -0
  185. package/esm/src/providers/openrouter/chat.d.ts +21 -1
  186. package/esm/src/providers/openrouter/chat.js +51 -6
  187. package/esm/src/providers/openrouter/image.js +8 -19
  188. package/esm/src/providers/openrouter/openai/chat-payload.js +23 -1
  189. package/esm/src/providers/openrouter/openai/compat.d.ts +7 -1
  190. package/esm/src/providers/openrouter/openai/compat.js +23 -4
  191. package/esm/src/providers/openrouter/openai/image-payload.js +8 -2
  192. package/esm/src/providers/openrouter/openai/sdk-messages.d.ts +1 -1
  193. package/esm/src/providers/openrouter/openai/sdk-messages.js +32 -5
  194. package/package.json +1 -1
  195. package/esm/src/interface/blocks.d.ts +0 -20
  196. package/esm/src/interface/blocks.js +0 -180
  197. package/esm/src/interface/from-profile.d.ts +0 -14
  198. package/esm/src/interface/from-profile.js +0 -104
  199. package/esm/src/interface/inputs.d.ts +0 -8
  200. package/esm/src/interface/inputs.js +0 -127
  201. package/esm/src/interface/mod.d.ts +0 -10
  202. package/esm/src/interface/mod.js +0 -9
  203. package/esm/src/interface/types.d.ts +0 -153
  204. package/esm/src/interface/types.js +0 -13
@@ -0,0 +1,159 @@
1
+ /**
2
+ * Profile graph — playground / authoring topology projected from PROFILE_FIELDS.
3
+ *
4
+ * This is not runtime policy. It declares which profile sections are graph
5
+ * facets, for which ProfileType, whether they may be omitted, and whether the
6
+ * host UI uses a schema form or a structural editor. The playground must import
7
+ * this catalog; it must not invent FacetKind unions.
8
+ *
9
+ * @module
10
+ */
11
+ import type { ProfileType } from './schema.js';
12
+ /** How the playground (or other host UI) should edit this facet. */
13
+ export type ProfileGraphEditor = 'schema' | 'structural';
14
+ /** Where the facet sits in the authoring graph. */
15
+ export type ProfileGraphRole = 'root' | 'spine' | 'branch';
16
+ /**
17
+ * Authoring-graph catalog. Adding a profile section? Add PROFILE_FIELDS and a row
18
+ * here — never a FacetKind in the frontend.
19
+ *
20
+ * `ProfileGraphFacetId` is derived from this array; do not maintain a union by hand.
21
+ */
22
+ declare const PROFILE_GRAPH_DEF: readonly [{
23
+ readonly id: "identity";
24
+ readonly profilePath: "identity";
25
+ readonly role: "root";
26
+ readonly profileTypes: readonly ("text" | "image" | "speech" | "live" | "host")[];
27
+ readonly optional: false;
28
+ readonly editor: "structural";
29
+ readonly label: "Identity";
30
+ readonly ownsFields: readonly ["id", "type"];
31
+ }, {
32
+ readonly id: "models";
33
+ readonly profilePath: "models";
34
+ readonly role: "spine";
35
+ readonly profileTypes: readonly ("text" | "image" | "speech" | "live" | "host")[];
36
+ readonly optional: false;
37
+ readonly editor: "structural";
38
+ readonly label: "Models";
39
+ readonly ownsFields: readonly ["defaultModel", "allowModelSelect", "maxSteps", "key"];
40
+ }, {
41
+ readonly id: "modelBinding";
42
+ readonly profilePath: "models.*";
43
+ readonly role: "branch";
44
+ readonly parent: "models";
45
+ readonly profileTypes: readonly ("text" | "image" | "speech" | "live" | "host")[];
46
+ readonly optional: false;
47
+ readonly editor: "structural";
48
+ readonly label: "Model binding";
49
+ }, {
50
+ readonly id: "image";
51
+ readonly profilePath: "image";
52
+ readonly role: "spine";
53
+ readonly profileTypes: readonly ["image"];
54
+ readonly optional: false;
55
+ readonly editor: "structural";
56
+ readonly label: "Image";
57
+ }, {
58
+ readonly id: "speech";
59
+ readonly profilePath: "speech";
60
+ readonly role: "spine";
61
+ readonly profileTypes: readonly ["speech"];
62
+ readonly optional: false;
63
+ readonly editor: "structural";
64
+ readonly label: "Speech";
65
+ }, {
66
+ readonly id: "live";
67
+ readonly profilePath: "live";
68
+ readonly role: "spine";
69
+ readonly profileTypes: readonly ["live"];
70
+ readonly optional: false;
71
+ readonly editor: "structural";
72
+ readonly label: "Live";
73
+ }, {
74
+ readonly id: "tools";
75
+ readonly profilePath: "tools";
76
+ readonly role: "spine";
77
+ readonly profileTypes: readonly ["text", "image", "live", "host"];
78
+ readonly optional: false;
79
+ readonly editor: "structural";
80
+ readonly label: "Tools";
81
+ }, {
82
+ readonly id: "toolSpec";
83
+ readonly profilePath: "tools.allow";
84
+ readonly role: "branch";
85
+ readonly parent: "tools";
86
+ readonly profileTypes: readonly ["text", "image", "live", "host"];
87
+ readonly optional: true;
88
+ readonly editor: "structural";
89
+ readonly label: "Tool";
90
+ }, {
91
+ readonly id: "inputs";
92
+ readonly profilePath: "inputs";
93
+ readonly role: "spine";
94
+ readonly profileTypes: readonly ["text", "image"];
95
+ readonly optional: false;
96
+ readonly editor: "structural";
97
+ readonly label: "Inputs";
98
+ }, {
99
+ readonly id: "outputs";
100
+ readonly profilePath: "outputs";
101
+ readonly role: "spine";
102
+ readonly profileTypes: readonly ["text", "image", "speech"];
103
+ readonly optional: true;
104
+ readonly editor: "structural";
105
+ readonly label: "Outputs";
106
+ }, {
107
+ readonly id: "turnBehaviour";
108
+ readonly profilePath: "turnBehaviour";
109
+ readonly role: "spine";
110
+ readonly profileTypes: readonly ["text", "image", "speech", "live"];
111
+ readonly optional: true;
112
+ readonly editor: "structural";
113
+ readonly label: "Turn behaviour";
114
+ }, {
115
+ readonly id: "guardrails";
116
+ readonly profilePath: "guardrails";
117
+ readonly role: "spine";
118
+ readonly profileTypes: readonly ("text" | "image" | "speech" | "live" | "host")[];
119
+ readonly optional: true;
120
+ readonly editor: "structural";
121
+ readonly label: "Guardrails";
122
+ }, {
123
+ readonly id: "observability";
124
+ readonly profilePath: "observability";
125
+ readonly role: "spine";
126
+ readonly profileTypes: readonly ("text" | "image" | "speech" | "live" | "host")[];
127
+ readonly optional: true;
128
+ readonly editor: "structural";
129
+ readonly label: "Observability";
130
+ }];
131
+ /** Stable facet ids — derived from PROFILE_GRAPH; never hand-maintained. */
132
+ export type ProfileGraphFacetId = (typeof PROFILE_GRAPH_DEF)[number]['id'];
133
+ /**
134
+ * One node kind on the profile authoring graph.
135
+ *
136
+ * `profilePath` is a PROFILE_FIELDS key (section root) or a dynamic path
137
+ * (`models.*`). Branch facets nest under `parent`.
138
+ */
139
+ export interface ProfileGraphFacet {
140
+ id: ProfileGraphFacetId;
141
+ profilePath: string;
142
+ role: ProfileGraphRole;
143
+ parent?: ProfileGraphFacetId;
144
+ profileTypes: readonly ProfileType[];
145
+ optional: boolean;
146
+ editor: ProfileGraphEditor;
147
+ label: string;
148
+ /**
149
+ * Extra PROFILE_FIELDS top-level keys owned by this facet (not 1:1 with id).
150
+ * Used by the drift gate so `defaultModel` / `id` / `type` are not orphaned.
151
+ */
152
+ ownsFields?: readonly string[];
153
+ }
154
+ export declare const PROFILE_GRAPH: readonly ProfileGraphFacet[];
155
+ /** Spine (and root) facets visible for a profile type, in catalog order. */
156
+ declare function spineFacetsForProfileType(type: ProfileType): ProfileGraphFacet[];
157
+ /** Look up a graph facet by id. */
158
+ declare function profileGraphFacet(id: ProfileGraphFacetId): ProfileGraphFacet | undefined;
159
+ export { profileGraphFacet, spineFacetsForProfileType };
@@ -0,0 +1,156 @@
1
+ /**
2
+ * Profile graph — playground / authoring topology projected from PROFILE_FIELDS.
3
+ *
4
+ * This is not runtime policy. It declares which profile sections are graph
5
+ * facets, for which ProfileType, whether they may be omitted, and whether the
6
+ * host UI uses a schema form or a structural editor. The playground must import
7
+ * this catalog; it must not invent FacetKind unions.
8
+ *
9
+ * @module
10
+ */
11
+ /**
12
+ * Mirrors PROFILE_TYPES — value import would cycle through schema re-exports.
13
+ * Drift is gated by tests/kernel/profile-graph.test.ts.
14
+ */
15
+ const ALL = ['text', 'image', 'speech', 'live', 'host'];
16
+ /** Types that bind models — `host` never runs a model. */
17
+ const MODEL_TYPES = ['text', 'image', 'speech', 'live'];
18
+ /**
19
+ * Authoring-graph catalog. Adding a profile section? Add PROFILE_FIELDS and a row
20
+ * here — never a FacetKind in the frontend.
21
+ *
22
+ * `ProfileGraphFacetId` is derived from this array; do not maintain a union by hand.
23
+ */
24
+ const PROFILE_GRAPH_DEF = [
25
+ {
26
+ id: 'identity',
27
+ profilePath: 'identity',
28
+ role: 'root',
29
+ profileTypes: ALL,
30
+ optional: false,
31
+ editor: 'structural',
32
+ label: 'Identity',
33
+ ownsFields: ['id', 'type'],
34
+ },
35
+ {
36
+ id: 'models',
37
+ profilePath: 'models',
38
+ role: 'spine',
39
+ profileTypes: MODEL_TYPES,
40
+ optional: false,
41
+ editor: 'structural',
42
+ label: 'Models',
43
+ ownsFields: ['defaultModel', 'allowModelSelect', 'maxSteps', 'key'],
44
+ },
45
+ {
46
+ id: 'modelBinding',
47
+ profilePath: 'models.*',
48
+ role: 'branch',
49
+ parent: 'models',
50
+ profileTypes: MODEL_TYPES,
51
+ optional: false,
52
+ editor: 'structural',
53
+ label: 'Model binding',
54
+ },
55
+ {
56
+ id: 'image',
57
+ profilePath: 'image',
58
+ role: 'spine',
59
+ profileTypes: ['image'],
60
+ optional: false,
61
+ editor: 'structural',
62
+ label: 'Image',
63
+ },
64
+ {
65
+ id: 'speech',
66
+ profilePath: 'speech',
67
+ role: 'spine',
68
+ profileTypes: ['speech'],
69
+ optional: false,
70
+ editor: 'structural',
71
+ label: 'Speech',
72
+ },
73
+ {
74
+ id: 'live',
75
+ profilePath: 'live',
76
+ role: 'spine',
77
+ profileTypes: ['live'],
78
+ optional: false,
79
+ editor: 'structural',
80
+ label: 'Live',
81
+ },
82
+ {
83
+ id: 'tools',
84
+ profilePath: 'tools',
85
+ role: 'spine',
86
+ profileTypes: ['text', 'image', 'live', 'host'],
87
+ optional: false,
88
+ editor: 'structural',
89
+ label: 'Tools',
90
+ },
91
+ {
92
+ id: 'toolSpec',
93
+ profilePath: 'tools.allow',
94
+ role: 'branch',
95
+ parent: 'tools',
96
+ profileTypes: ['text', 'image', 'live', 'host'],
97
+ optional: true,
98
+ editor: 'structural',
99
+ label: 'Tool',
100
+ },
101
+ {
102
+ id: 'inputs',
103
+ profilePath: 'inputs',
104
+ role: 'spine',
105
+ profileTypes: ['text', 'image'],
106
+ optional: false,
107
+ editor: 'structural',
108
+ label: 'Inputs',
109
+ },
110
+ {
111
+ id: 'outputs',
112
+ profilePath: 'outputs',
113
+ role: 'spine',
114
+ profileTypes: ['text', 'image', 'speech'],
115
+ optional: true,
116
+ editor: 'structural',
117
+ label: 'Outputs',
118
+ },
119
+ {
120
+ id: 'turnBehaviour',
121
+ profilePath: 'turnBehaviour',
122
+ role: 'spine',
123
+ profileTypes: ['text', 'image', 'speech', 'live'],
124
+ optional: true,
125
+ editor: 'structural',
126
+ label: 'Turn behaviour',
127
+ },
128
+ {
129
+ id: 'guardrails',
130
+ profilePath: 'guardrails',
131
+ role: 'spine',
132
+ profileTypes: ALL,
133
+ optional: true,
134
+ editor: 'structural',
135
+ label: 'Guardrails',
136
+ },
137
+ {
138
+ id: 'observability',
139
+ profilePath: 'observability',
140
+ role: 'spine',
141
+ profileTypes: ALL,
142
+ optional: true,
143
+ editor: 'structural',
144
+ label: 'Observability',
145
+ },
146
+ ];
147
+ export const PROFILE_GRAPH = PROFILE_GRAPH_DEF;
148
+ /** Spine (and root) facets visible for a profile type, in catalog order. */
149
+ function spineFacetsForProfileType(type) {
150
+ return PROFILE_GRAPH.filter((facet) => (facet.role === 'root' || facet.role === 'spine') && facet.profileTypes.includes(type));
151
+ }
152
+ /** Look up a graph facet by id. */
153
+ function profileGraphFacet(id) {
154
+ return PROFILE_GRAPH.find((facet) => facet.id === id);
155
+ }
156
+ export { profileGraphFacet, spineFacetsForProfileType };
@@ -1,18 +1,18 @@
1
- import type { MediaLimits, MimeInputs, Profile, TurnBlob } from '../types.js';
2
- declare function tooManyFilesMessage(maxFiles: number): string;
3
- declare function fileTooLargeMessage(maxBytes: number): string;
4
- declare function turnTooLargeMessage(maxTurnBytes: number): string;
1
+ import type { MediaLimits, MimeInputs, Profile, TurnBlob, TurnMediaRef } from '../types.js';
5
2
  declare function resolveMediaLimits(inputs: MimeInputs): MediaLimits | undefined;
6
3
  declare function maxBytesForMime(mimeType: string, limits: MediaLimits): number;
4
+ /** Attachment supplied by provider file reference — no bytes to size-check or sanitize. */
5
+ declare function isTurnMediaRef(item: TurnBlob | TurnMediaRef): item is TurnMediaRef;
7
6
  declare function requireMediaLimits(profile: Profile): MediaLimits;
8
7
  declare function sanitizeCsvText(text: string): string;
9
- declare function assertAttachmentLimits(blobs: TurnBlob[], limits: MediaLimits): void;
10
- declare function sanitizeTurnBlobs(attachments: TurnBlob[] | undefined, voice: TurnBlob[] | undefined, limits: MediaLimits | undefined): {
11
- attachments?: TurnBlob[];
8
+ /** Enforce file count on every attachment; base64 and byte limits only on inline blobs. */
9
+ declare function assertAttachmentLimits(attachments: Array<TurnBlob | TurnMediaRef>, limits: MediaLimits): void;
10
+ declare function sanitizeTurnBlobs(attachments: Array<TurnBlob | TurnMediaRef> | undefined, voice: TurnBlob[] | undefined, limits: MediaLimits | undefined): {
11
+ attachments?: Array<TurnBlob | TurnMediaRef>;
12
12
  voice?: TurnBlob[];
13
13
  };
14
- declare function sanitizeTurnBlobsForProfile(profileId: string, attachments: TurnBlob[] | undefined, voice: TurnBlob[] | undefined): {
15
- attachments?: TurnBlob[];
14
+ declare function sanitizeTurnBlobsForProfile(profileId: string, attachments: Array<TurnBlob | TurnMediaRef> | undefined, voice: TurnBlob[] | undefined): {
15
+ attachments?: Array<TurnBlob | TurnMediaRef>;
16
16
  voice?: TurnBlob[];
17
17
  };
18
- export { assertAttachmentLimits, fileTooLargeMessage, maxBytesForMime, requireMediaLimits, resolveMediaLimits, sanitizeCsvText, sanitizeTurnBlobs, sanitizeTurnBlobsForProfile, tooManyFilesMessage, turnTooLargeMessage, };
18
+ export { assertAttachmentLimits, isTurnMediaRef, maxBytesForMime, requireMediaLimits, resolveMediaLimits, sanitizeCsvText, sanitizeTurnBlobs, sanitizeTurnBlobsForProfile, };
@@ -1,5 +1,6 @@
1
1
  import { TheorumError } from '../../guardrails/error.js';
2
2
  import { injectionSpans } from '../../guardrails/injection.js';
3
+ import { lexiconText } from '../../guardrails/lexicon.js';
3
4
  import { sensitiveSpans } from '../../guardrails/sensitive.js';
4
5
  import { applySpans } from '../../observability/spans.js';
5
6
  import { getProfile } from './profiles.js';
@@ -9,22 +10,6 @@ const B64_TRIPLET = 3;
9
10
  const CSV_FORMULA = /(^|,)(\s*)("?)(?:([=@])|([+-])(?![0-9."]))/gm;
10
11
  const B64_BODY = /^[A-Za-z0-9+/]*={0,2}$/;
11
12
  const TEXT_MIMES = new Set(['text/csv', 'text/plain', 'text/markdown']);
12
- const BYTES_PER_KIB = 1024;
13
- function formatMb(bytes) {
14
- const mb = bytes / (BYTES_PER_KIB * BYTES_PER_KIB);
15
- return Number.isInteger(mb) ? `${String(mb)} MB` : `${mb.toFixed(1)} MB`;
16
- }
17
- function tooManyFilesMessage(maxFiles) {
18
- return maxFiles === 1
19
- ? 'Only 1 file per message.'
20
- : `Only ${String(maxFiles)} files per message.`;
21
- }
22
- function fileTooLargeMessage(maxBytes) {
23
- return `Each file must be ${formatMb(maxBytes)} or smaller.`;
24
- }
25
- function turnTooLargeMessage(maxTurnBytes) {
26
- return `Those files together are too large for one message (${formatMb(maxTurnBytes)} max).`;
27
- }
28
13
  function resolveMediaLimits(inputs) {
29
14
  const { maxFiles, maxBytes, maxTurnBytes, limitsByMime } = inputs;
30
15
  if (maxFiles && maxBytes && maxTurnBytes) {
@@ -46,13 +31,23 @@ function maxBytesForMime(mimeType, limits) {
46
31
  }
47
32
  return limits.maxBytes;
48
33
  }
34
+ /** Attachment supplied by provider file reference — no bytes to size-check or sanitize. */
35
+ function isTurnMediaRef(item) {
36
+ return 'uri' in item;
37
+ }
49
38
  function requireMediaLimits(profile) {
50
39
  if (profile.type === 'speech') {
51
- throw new TheorumError(`Profile ${profile.id} (speech) does not accept media input`);
40
+ throw new TheorumError(`Profile ${profile.id} (speech) does not accept media input`); // lexicon-exempt: developer contract error
41
+ }
42
+ if (profile.type === 'live') {
43
+ throw new TheorumError(`Profile ${profile.id} (live) does not accept turn attachment input`); // lexicon-exempt: developer contract error
44
+ }
45
+ if (profile.type === 'host') {
46
+ throw new TheorumError(`Profile ${profile.id} (host) does not accept turn input`); // lexicon-exempt: developer contract error
52
47
  }
53
48
  const limits = resolveMediaLimits(profile.inputs ?? {});
54
49
  if (!limits) {
55
- throw new TheorumError(`Profile ${profile.id} must set maxFiles, maxBytes, and maxTurnBytes`);
50
+ throw new TheorumError(`Profile ${profile.id} must set maxFiles, maxBytes, and maxTurnBytes`); // lexicon-exempt: developer contract error
56
51
  }
57
52
  return limits;
58
53
  }
@@ -102,28 +97,36 @@ function sanitizeTextBytes(mime, bytes) {
102
97
  }
103
98
  return new TextEncoder().encode(applySpans(text, [...injectionSpans(text), ...sensitiveSpans(text)]));
104
99
  }
105
- function assertAttachmentLimits(blobs, limits) {
106
- if (blobs.length > limits.maxFiles) {
107
- throw new TheorumError(tooManyFilesMessage(limits.maxFiles));
100
+ /** Enforce file count on every attachment; base64 and byte limits only on inline blobs. */
101
+ function assertAttachmentLimits(attachments, limits) {
102
+ if (attachments.length > limits.maxFiles) {
103
+ throw new TheorumError(lexiconText('attachments.too_many_files', { maxFiles: limits.maxFiles }));
108
104
  }
109
105
  let total = 0;
110
- for (const blob of blobs) {
106
+ for (const blob of attachments) {
107
+ if (isTurnMediaRef(blob)) {
108
+ continue;
109
+ }
111
110
  const { data, mimeType } = blob;
112
111
  if (!B64_BODY.test(data)) {
112
+ // lexicon-exempt: developer-facing wire-format diagnostic, not product copy
113
113
  throw new TheorumError('attachment data must be base64');
114
114
  }
115
115
  const size = b64DecodedLen(data);
116
116
  const maxAllowed = maxBytesForMime(mimeType, limits);
117
117
  if (size > maxAllowed) {
118
- throw new TheorumError(fileTooLargeMessage(maxAllowed));
118
+ throw new TheorumError(lexiconText('attachments.file_too_large', { maxBytes: maxAllowed }));
119
119
  }
120
120
  total += size;
121
121
  }
122
122
  if (total > limits.maxTurnBytes) {
123
- throw new TheorumError(turnTooLargeMessage(limits.maxTurnBytes));
123
+ throw new TheorumError(lexiconText('attachments.turn_too_large', { maxTurnBytes: limits.maxTurnBytes }));
124
124
  }
125
125
  }
126
126
  function sanitizeAttachment(blob) {
127
+ if (isTurnMediaRef(blob)) {
128
+ return blob;
129
+ }
127
130
  const { mimeType, data } = blob;
128
131
  if (!TEXT_MIMES.has(mimeType.split(';')[0]?.trim().toLowerCase() ?? '')) {
129
132
  return blob;
@@ -141,7 +144,7 @@ function sanitizeTurnBlobs(attachments, voice, limits) {
141
144
  const files = attachments ?? [];
142
145
  const clips = voice ?? [];
143
146
  if (!limits) {
144
- throw new TheorumError('This profile does not accept files.');
147
+ throw new TheorumError(lexiconText('attachments.not_accepted', { channel: 'file' }));
145
148
  }
146
149
  assertAttachmentLimits([...files, ...clips], limits);
147
150
  return {
@@ -156,4 +159,4 @@ function sanitizeTurnBlobsForProfile(profileId, attachments, voice) {
156
159
  const limits = requireMediaLimits(getProfile(profileId));
157
160
  return sanitizeTurnBlobs(attachments, voice, limits);
158
161
  }
159
- export { assertAttachmentLimits, fileTooLargeMessage, maxBytesForMime, requireMediaLimits, resolveMediaLimits, sanitizeCsvText, sanitizeTurnBlobs, sanitizeTurnBlobsForProfile, tooManyFilesMessage, turnTooLargeMessage, };
162
+ export { assertAttachmentLimits, isTurnMediaRef, maxBytesForMime, requireMediaLimits, resolveMediaLimits, sanitizeCsvText, sanitizeTurnBlobs, sanitizeTurnBlobsForProfile, };
@@ -1,18 +1,32 @@
1
1
  /**
2
- * MIME helpers and model spec utilities.
2
+ * MIME helpers and model binding utilities.
3
3
  *
4
4
  * @module
5
5
  */
6
- import type { MediaInputKind, ModelId, ModelSpec, Profile, ThinkingLevel } from '../types.js';
6
+ import type { MediaInputKind, ModelBinding, ModelId, ModelProfile, Profile, ThinkingLevel } from '../types.js';
7
+ /** `TurnInput` field a media file rides in. */
8
+ type MediaInputChannel = 'attachments' | 'voice';
7
9
  declare function mimeEssence(mime: string): string;
8
10
  declare function mimeAllowed(accept: string[], mime: string): boolean;
9
11
  declare function mediaKindForMime(mime: string): MediaInputKind | undefined;
10
- /** Require a host-declared model spec for an allowed profile model id. */
11
- declare function requireModelSpec(profile: Profile, modelId: ModelId): ModelSpec;
12
- /** Clamp a requested thinking level to what the model spec accepts. */
13
- declare function clampThinkingLevel(spec: ModelSpec, level: ThinkingLevel): ThinkingLevel;
14
- /** Look up a model spec by provider-native API id within a host specs map. */
15
- declare function modelEntryByApiId(specs: Record<string, ModelSpec>, apiId: string): ModelSpec | undefined;
16
- /** Clamp thinking level using a provider-native API id within a host specs map. */
17
- declare function clampThinkingLevelForApiId(specs: Record<string, ModelSpec>, apiId: string, level: ThinkingLevel): ThinkingLevel;
18
- export { clampThinkingLevel, clampThinkingLevelForApiId, mediaKindForMime, mimeAllowed, mimeEssence, modelEntryByApiId, requireModelSpec, };
12
+ /** The `accept` list a profile declares for one input channel, if it declares one. */
13
+ declare function profileAccept(profile: Profile, channel: MediaInputChannel): string[] | undefined;
14
+ /**
15
+ * Which `TurnInput` channel of a profile accepts this MIME, or `undefined` when
16
+ * the profile accepts it nowhere (or the kernel cannot classify it at all).
17
+ *
18
+ * The one public answer to "does this profile take this file". Hosts route and
19
+ * filter channel ingress with it instead of keeping their own MIME table: the
20
+ * profile's `accept` lists are the whole declaration.
21
+ */
22
+ declare function mediaChannelForMime(profile: Profile, mime: string): MediaInputChannel | undefined;
23
+ /** Require a host-declared model binding for a profile model id. */
24
+ declare function requireModelBinding(profile: ModelProfile, modelId: ModelId): ModelBinding;
25
+ /** Clamp a requested thinking level to what the model binding accepts. */
26
+ declare function clampThinkingLevel(binding: ModelBinding, level: ThinkingLevel): ThinkingLevel;
27
+ /** Look up a model binding by provider-native API id within a host map. */
28
+ declare function modelEntryByApiId(bindings: Record<string, ModelBinding>, apiId: string): ModelBinding | undefined;
29
+ /** Clamp thinking level using a provider-native API id within a host map. */
30
+ declare function clampThinkingLevelForApiId(bindings: Record<string, ModelBinding>, apiId: string, level: ThinkingLevel): ThinkingLevel;
31
+ export type { MediaInputChannel };
32
+ export { clampThinkingLevel, clampThinkingLevelForApiId, mediaChannelForMime, mediaKindForMime, mimeAllowed, mimeEssence, modelEntryByApiId, profileAccept, requireModelBinding, };
@@ -1,5 +1,5 @@
1
1
  /**
2
- * MIME helpers and model spec utilities.
2
+ * MIME helpers and model binding utilities.
3
3
  *
4
4
  * @module
5
5
  */
@@ -22,38 +22,73 @@ function mimeAllowed(accept, mime) {
22
22
  function mediaKindForMime(mime) {
23
23
  return MEDIA_INPUT_KINDS[mimeEssence(mime)];
24
24
  }
25
- /** Require a host-declared model spec for an allowed profile model id. */
26
- function requireModelSpec(profile, modelId) {
27
- const spec = profile.model.config[modelId];
28
- if (!spec) {
29
- throw new TheorumError(`Profile ${profile.id} has no model spec for '${modelId}'`);
25
+ /** The `accept` list a profile declares for one input channel, if it declares one. */
26
+ function profileAccept(profile, channel) {
27
+ if (profile.type === 'speech' || profile.type === 'live' || profile.type === 'host') {
28
+ return undefined;
30
29
  }
31
- return spec;
30
+ const inputs = profile.inputs;
31
+ return channel === 'voice' ? inputs?.voice?.accept : inputs?.attachments?.accept;
32
32
  }
33
- function clampLevels(entry, level) {
34
- if (!entry?.thinkingLevels || entry.thinkingLevels.length === 0) {
33
+ /**
34
+ * Which `TurnInput` channel of a profile accepts this MIME, or `undefined` when
35
+ * the profile accepts it nowhere (or the kernel cannot classify it at all).
36
+ *
37
+ * The one public answer to "does this profile take this file". Hosts route and
38
+ * filter channel ingress with it instead of keeping their own MIME table: the
39
+ * profile's `accept` lists are the whole declaration.
40
+ */
41
+ function mediaChannelForMime(profile, mime) {
42
+ if (!mediaKindForMime(mime)) {
43
+ return undefined;
44
+ }
45
+ for (const channel of ['attachments', 'voice']) {
46
+ const accept = profileAccept(profile, channel);
47
+ if (accept && mimeAllowed(accept, mime)) {
48
+ return channel;
49
+ }
50
+ }
51
+ return undefined;
52
+ }
53
+ /** Require a host-declared model binding for a profile model id. */
54
+ function requireModelBinding(profile, modelId) {
55
+ const binding = profile.models[modelId];
56
+ if (!binding) {
57
+ throw new TheorumError(`Profile ${profile.id} has no model binding for '${modelId}'`); // lexicon-exempt: developer contract / internal diagnostic — not end-user or model copy (P2)
58
+ }
59
+ return binding;
60
+ }
61
+ function effortLevels(binding) {
62
+ if (!binding?.efforts) {
63
+ return [];
64
+ }
65
+ return Object.values(binding.efforts);
66
+ }
67
+ function clampLevels(binding, level) {
68
+ const legal = effortLevels(binding);
69
+ if (legal.length === 0) {
35
70
  return level;
36
71
  }
37
- if (entry.thinkingLevels.includes(level)) {
72
+ if (legal.includes(level)) {
38
73
  return level;
39
74
  }
40
- const fallback = entry.thinking?.off;
41
- if (fallback && entry.thinkingLevels.includes(fallback)) {
75
+ const fallbackAlias = binding?.defaultEffort;
76
+ const fallback = fallbackAlias ? binding?.efforts?.[fallbackAlias] : undefined;
77
+ if (fallback && legal.includes(fallback)) {
42
78
  return fallback;
43
79
  }
44
- const first = entry.thinkingLevels[0];
45
- return first ?? level;
80
+ return legal[0] ?? level;
46
81
  }
47
- /** Clamp a requested thinking level to what the model spec accepts. */
48
- function clampThinkingLevel(spec, level) {
49
- return clampLevels(spec, level);
82
+ /** Clamp a requested thinking level to what the model binding accepts. */
83
+ function clampThinkingLevel(binding, level) {
84
+ return clampLevels(binding, level);
50
85
  }
51
- /** Look up a model spec by provider-native API id within a host specs map. */
52
- function modelEntryByApiId(specs, apiId) {
53
- return Object.values(specs).find((m) => m.apiId === apiId);
86
+ /** Look up a model binding by provider-native API id within a host map. */
87
+ function modelEntryByApiId(bindings, apiId) {
88
+ return Object.values(bindings).find((m) => m.apiId === apiId);
54
89
  }
55
- /** Clamp thinking level using a provider-native API id within a host specs map. */
56
- function clampThinkingLevelForApiId(specs, apiId, level) {
57
- return clampLevels(modelEntryByApiId(specs, apiId), level);
90
+ /** Clamp thinking level using a provider-native API id within a host map. */
91
+ function clampThinkingLevelForApiId(bindings, apiId, level) {
92
+ return clampLevels(modelEntryByApiId(bindings, apiId), level);
58
93
  }
59
- export { clampThinkingLevel, clampThinkingLevelForApiId, mediaKindForMime, mimeAllowed, mimeEssence, modelEntryByApiId, requireModelSpec, };
94
+ export { clampThinkingLevel, clampThinkingLevelForApiId, mediaChannelForMime, mediaKindForMime, mimeAllowed, mimeEssence, modelEntryByApiId, profileAccept, requireModelBinding, };