@company-semantics/contracts 98.0.0 → 99.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/api/generated-spec-hash.ts +2 -2
- package/src/api/generated.ts +51 -2
- package/src/chat/__tests__/runtime-profile.test.ts +23 -5
- package/src/generated/openapi-routes.ts +1 -0
- package/src/index.ts +4 -1
- package/src/ingestion/README.md +51 -21
- package/src/ingestion/__tests__/providers.test.ts +59 -0
- package/src/ingestion/__tests__/registry.test.ts +18 -18
- package/src/ingestion/__tests__/surfaces.test.ts +68 -40
- package/src/ingestion/index.ts +20 -2
- package/src/ingestion/providers.ts +141 -0
- package/src/ingestion/registry.ts +44 -25
- package/src/ingestion/surfaces.ts +81 -28
- package/src/ingestion/types.ts +80 -15
- package/src/schedules/README.md +118 -0
- package/src/schedules/__tests__/README.md +27 -0
- package/src/schedules/__tests__/api.test.ts +72 -0
- package/src/schedules/__tests__/cron.test.ts +140 -0
- package/src/schedules/__tests__/definition.test.ts +97 -0
- package/src/schedules/__tests__/execution.test.ts +75 -0
- package/src/schedules/__tests__/identity.test.ts +51 -0
- package/src/schedules/__tests__/schedules-exports.test.ts +26 -0
- package/src/schedules/api.ts +139 -0
- package/src/schedules/cron.ts +268 -0
- package/src/schedules/definition.ts +148 -0
- package/src/schedules/execution.ts +146 -0
- package/src/schedules/identity.ts +99 -0
- package/src/schedules/index.ts +124 -0
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ingestion Providers
|
|
3
|
+
*
|
|
4
|
+
* Provider vocabulary: the native types an external source hands us (exported
|
|
5
|
+
* to a registry format before ingestion) and its structural container MIMEs.
|
|
6
|
+
* Discovery's net, the Drive picker filter, inventory classes and display
|
|
7
|
+
* labels all derive from this plus the surface declarations — no backend or
|
|
8
|
+
* app module keeps its own Workspace MIME table.
|
|
9
|
+
*/
|
|
10
|
+
import { INGESTION_FORMATS, byMime } from "./registry";
|
|
11
|
+
import { surfaceAdmits } from "./surfaces";
|
|
12
|
+
import {
|
|
13
|
+
FORMAT_CLASSES,
|
|
14
|
+
type FormatClass,
|
|
15
|
+
type IngestionFormat,
|
|
16
|
+
type IngestionProvider,
|
|
17
|
+
type IngestionProviderKey,
|
|
18
|
+
type IngestionSurfaceKey,
|
|
19
|
+
type InventoryClass,
|
|
20
|
+
type ProviderNativeFormat,
|
|
21
|
+
} from "./types";
|
|
22
|
+
|
|
23
|
+
/** Every provider's native types and container MIMEs, keyed by provider. */
|
|
24
|
+
export const INGESTION_PROVIDERS: Readonly<
|
|
25
|
+
Record<IngestionProviderKey, IngestionProvider>
|
|
26
|
+
> = {
|
|
27
|
+
"google-drive": {
|
|
28
|
+
key: "google-drive",
|
|
29
|
+
nativeFormats: [
|
|
30
|
+
{
|
|
31
|
+
mime: "application/vnd.google-apps.document",
|
|
32
|
+
label: "Google Doc",
|
|
33
|
+
exportsTo:
|
|
34
|
+
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
mime: "application/vnd.google-apps.presentation",
|
|
38
|
+
label: "Google Slides",
|
|
39
|
+
exportsTo:
|
|
40
|
+
"application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
mime: "application/vnd.google-apps.spreadsheet",
|
|
44
|
+
label: "Google Sheet",
|
|
45
|
+
exportsTo:
|
|
46
|
+
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
47
|
+
},
|
|
48
|
+
],
|
|
49
|
+
containerMimes: {
|
|
50
|
+
folder: "application/vnd.google-apps.folder",
|
|
51
|
+
shortcut: "application/vnd.google-apps.shortcut",
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
/** One MIME a provider can hand the pipeline, with the registry format it lands as. */
|
|
57
|
+
export interface ProviderFormat {
|
|
58
|
+
/** The MIME as the provider lists it (native or a registry MIME). */
|
|
59
|
+
mime: string;
|
|
60
|
+
/** True for a provider-native type that is exported before ingestion. */
|
|
61
|
+
native: boolean;
|
|
62
|
+
/** The registry format the bytes arrive as (the export target for natives). */
|
|
63
|
+
format: IngestionFormat;
|
|
64
|
+
/** Display label: the native label, else the registry label. */
|
|
65
|
+
label: string;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function exportTarget(native: ProviderNativeFormat): IngestionFormat {
|
|
69
|
+
const target = byMime(native.exportsTo);
|
|
70
|
+
if (!target) {
|
|
71
|
+
throw new Error(
|
|
72
|
+
`ingestion provider: ${native.mime} exports to ${native.exportsTo}, which is not in INGESTION_FORMATS`,
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
return target;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** Every MIME the provider can hand a surface (natives first, then registry order). */
|
|
79
|
+
export function providerFormatsFor(
|
|
80
|
+
provider: IngestionProviderKey,
|
|
81
|
+
surface: IngestionSurfaceKey,
|
|
82
|
+
): ProviderFormat[] {
|
|
83
|
+
const natives: ProviderFormat[] = INGESTION_PROVIDERS[
|
|
84
|
+
provider
|
|
85
|
+
].nativeFormats.map((native) => ({
|
|
86
|
+
mime: native.mime,
|
|
87
|
+
native: true,
|
|
88
|
+
format: exportTarget(native),
|
|
89
|
+
label: native.label,
|
|
90
|
+
}));
|
|
91
|
+
const downloads: ProviderFormat[] = INGESTION_FORMATS.map((format) => ({
|
|
92
|
+
mime: format.mime,
|
|
93
|
+
native: false,
|
|
94
|
+
format,
|
|
95
|
+
label: format.label,
|
|
96
|
+
}));
|
|
97
|
+
return [...natives, ...downloads].filter((pf) =>
|
|
98
|
+
surfaceAdmits(surface, pf.format),
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** The class of a provider-listed MIME; null for containers and unknown types. */
|
|
103
|
+
export function classOfMime(
|
|
104
|
+
provider: IngestionProviderKey,
|
|
105
|
+
mime: string,
|
|
106
|
+
): FormatClass | null {
|
|
107
|
+
const native = INGESTION_PROVIDERS[provider].nativeFormats.find(
|
|
108
|
+
(n) => n.mime === mime,
|
|
109
|
+
);
|
|
110
|
+
if (native) return exportTarget(native).class;
|
|
111
|
+
return byMime(mime)?.class ?? null;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* The inventory classes discovery can record for a provider feeding a surface,
|
|
116
|
+
* in canonical class order, plus "shortcut". The DB CHECK on
|
|
117
|
+
* source_inventory_items.mime_class is pinned to this.
|
|
118
|
+
*/
|
|
119
|
+
export function inventoryClassesFor(
|
|
120
|
+
provider: IngestionProviderKey,
|
|
121
|
+
surface: IngestionSurfaceKey,
|
|
122
|
+
): InventoryClass[] {
|
|
123
|
+
const present = new Set(
|
|
124
|
+
providerFormatsFor(provider, surface).map((pf) => pf.format.class),
|
|
125
|
+
);
|
|
126
|
+
return [...FORMAT_CLASSES.filter((c) => present.has(c)), "shortcut"];
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** Display label for a MIME (provider-native first when a provider is given); null if unknown. */
|
|
130
|
+
export function labelOfMime(
|
|
131
|
+
mime: string,
|
|
132
|
+
provider?: IngestionProviderKey,
|
|
133
|
+
): string | null {
|
|
134
|
+
if (provider) {
|
|
135
|
+
const native = INGESTION_PROVIDERS[provider].nativeFormats.find(
|
|
136
|
+
(n) => n.mime === mime,
|
|
137
|
+
);
|
|
138
|
+
if (native) return native.label;
|
|
139
|
+
}
|
|
140
|
+
return byMime(mime)?.label ?? null;
|
|
141
|
+
}
|
|
@@ -2,15 +2,18 @@
|
|
|
2
2
|
* Ingestion Format Registry
|
|
3
3
|
*
|
|
4
4
|
* THE single source of truth for the public document-ingestion accept-set. The
|
|
5
|
-
* backend normalizer joins these entries with its private dispatch table;
|
|
6
|
-
*
|
|
7
|
-
* the helpers
|
|
5
|
+
* backend normalizer joins these entries with its private dispatch table; each
|
|
6
|
+
* upload surface derives its file-input `accept` attribute and supported-formats
|
|
7
|
+
* copy from the surface helpers. Neither side re-spells the extension, label or
|
|
8
|
+
* class list.
|
|
8
9
|
*
|
|
9
10
|
* Invariants:
|
|
10
11
|
* - Entries are ordered text → documents → images/diagrams → audio/video; the
|
|
11
12
|
* backend's MIME-order test and the derived UI strings depend on this order.
|
|
12
|
-
* -
|
|
13
|
-
*
|
|
13
|
+
* - Every entry declares its `class` (one of `FORMAT_CLASSES`); no consumer
|
|
14
|
+
* keeps its own extension → class table.
|
|
15
|
+
* - `.drawio` is not listed: no normalizer can rasterize a raw `<mxfile>`, so
|
|
16
|
+
* admitting it would be a lie. It returns when a rasterizer exists.
|
|
14
17
|
*
|
|
15
18
|
* @see ADR-CONT-075 for design rationale
|
|
16
19
|
*/
|
|
@@ -22,6 +25,7 @@ export const INGESTION_FORMATS: readonly IngestionFormat[] = [
|
|
|
22
25
|
{
|
|
23
26
|
mime: "text/plain",
|
|
24
27
|
family: "text",
|
|
28
|
+
class: "text",
|
|
25
29
|
extensions: [".txt"],
|
|
26
30
|
label: "Text",
|
|
27
31
|
maxBytes: 10_000_000,
|
|
@@ -29,6 +33,7 @@ export const INGESTION_FORMATS: readonly IngestionFormat[] = [
|
|
|
29
33
|
{
|
|
30
34
|
mime: "text/markdown",
|
|
31
35
|
family: "text",
|
|
36
|
+
class: "text",
|
|
32
37
|
extensions: [".md"],
|
|
33
38
|
label: "Markdown",
|
|
34
39
|
maxBytes: 10_000_000,
|
|
@@ -36,6 +41,7 @@ export const INGESTION_FORMATS: readonly IngestionFormat[] = [
|
|
|
36
41
|
{
|
|
37
42
|
mime: "text/html",
|
|
38
43
|
family: "text",
|
|
44
|
+
class: "text",
|
|
39
45
|
extensions: [".html", ".htm"],
|
|
40
46
|
label: "HTML",
|
|
41
47
|
maxBytes: 10_000_000,
|
|
@@ -43,6 +49,7 @@ export const INGESTION_FORMATS: readonly IngestionFormat[] = [
|
|
|
43
49
|
{
|
|
44
50
|
mime: "text/csv",
|
|
45
51
|
family: "text",
|
|
52
|
+
class: "sheets",
|
|
46
53
|
extensions: [".csv"],
|
|
47
54
|
label: "CSV",
|
|
48
55
|
maxBytes: 25_000_000,
|
|
@@ -50,6 +57,7 @@ export const INGESTION_FORMATS: readonly IngestionFormat[] = [
|
|
|
50
57
|
{
|
|
51
58
|
mime: "application/rtf",
|
|
52
59
|
family: "document",
|
|
60
|
+
class: "docs",
|
|
53
61
|
extensions: [".rtf"],
|
|
54
62
|
label: "RTF",
|
|
55
63
|
maxBytes: 25_000_000,
|
|
@@ -57,6 +65,7 @@ export const INGESTION_FORMATS: readonly IngestionFormat[] = [
|
|
|
57
65
|
{
|
|
58
66
|
mime: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
59
67
|
family: "document",
|
|
68
|
+
class: "docs",
|
|
60
69
|
extensions: [".docx"],
|
|
61
70
|
label: "Word",
|
|
62
71
|
maxBytes: 25_000_000,
|
|
@@ -64,6 +73,7 @@ export const INGESTION_FORMATS: readonly IngestionFormat[] = [
|
|
|
64
73
|
{
|
|
65
74
|
mime: "application/msword",
|
|
66
75
|
family: "document",
|
|
76
|
+
class: "docs",
|
|
67
77
|
extensions: [".doc"],
|
|
68
78
|
label: "Word",
|
|
69
79
|
maxBytes: 25_000_000,
|
|
@@ -71,6 +81,7 @@ export const INGESTION_FORMATS: readonly IngestionFormat[] = [
|
|
|
71
81
|
{
|
|
72
82
|
mime: "application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
|
73
83
|
family: "document",
|
|
84
|
+
class: "slides",
|
|
74
85
|
extensions: [".pptx"],
|
|
75
86
|
label: "PowerPoint",
|
|
76
87
|
maxBytes: 50_000_000,
|
|
@@ -78,6 +89,7 @@ export const INGESTION_FORMATS: readonly IngestionFormat[] = [
|
|
|
78
89
|
{
|
|
79
90
|
mime: "application/vnd.ms-powerpoint",
|
|
80
91
|
family: "document",
|
|
92
|
+
class: "slides",
|
|
81
93
|
extensions: [".ppt"],
|
|
82
94
|
label: "PowerPoint",
|
|
83
95
|
maxBytes: 50_000_000,
|
|
@@ -85,6 +97,7 @@ export const INGESTION_FORMATS: readonly IngestionFormat[] = [
|
|
|
85
97
|
{
|
|
86
98
|
mime: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
87
99
|
family: "document",
|
|
100
|
+
class: "sheets",
|
|
88
101
|
extensions: [".xlsx"],
|
|
89
102
|
label: "Excel",
|
|
90
103
|
maxBytes: 50_000_000,
|
|
@@ -92,6 +105,7 @@ export const INGESTION_FORMATS: readonly IngestionFormat[] = [
|
|
|
92
105
|
{
|
|
93
106
|
mime: "application/vnd.ms-excel",
|
|
94
107
|
family: "document",
|
|
108
|
+
class: "sheets",
|
|
95
109
|
extensions: [".xls"],
|
|
96
110
|
label: "Excel",
|
|
97
111
|
maxBytes: 50_000_000,
|
|
@@ -99,6 +113,7 @@ export const INGESTION_FORMATS: readonly IngestionFormat[] = [
|
|
|
99
113
|
{
|
|
100
114
|
mime: "application/vnd.oasis.opendocument.text",
|
|
101
115
|
family: "document",
|
|
116
|
+
class: "docs",
|
|
102
117
|
extensions: [".odt"],
|
|
103
118
|
label: "OpenDocument",
|
|
104
119
|
maxBytes: 25_000_000,
|
|
@@ -106,6 +121,7 @@ export const INGESTION_FORMATS: readonly IngestionFormat[] = [
|
|
|
106
121
|
{
|
|
107
122
|
mime: "application/vnd.oasis.opendocument.spreadsheet",
|
|
108
123
|
family: "document",
|
|
124
|
+
class: "sheets",
|
|
109
125
|
extensions: [".ods"],
|
|
110
126
|
label: "OpenDocument",
|
|
111
127
|
maxBytes: 50_000_000,
|
|
@@ -113,6 +129,7 @@ export const INGESTION_FORMATS: readonly IngestionFormat[] = [
|
|
|
113
129
|
{
|
|
114
130
|
mime: "application/vnd.oasis.opendocument.presentation",
|
|
115
131
|
family: "document",
|
|
132
|
+
class: "slides",
|
|
116
133
|
extensions: [".odp"],
|
|
117
134
|
label: "OpenDocument",
|
|
118
135
|
maxBytes: 50_000_000,
|
|
@@ -120,6 +137,7 @@ export const INGESTION_FORMATS: readonly IngestionFormat[] = [
|
|
|
120
137
|
{
|
|
121
138
|
mime: "application/epub+zip",
|
|
122
139
|
family: "document",
|
|
140
|
+
class: "docs",
|
|
123
141
|
extensions: [".epub"],
|
|
124
142
|
label: "EPUB",
|
|
125
143
|
maxBytes: 25_000_000,
|
|
@@ -127,6 +145,7 @@ export const INGESTION_FORMATS: readonly IngestionFormat[] = [
|
|
|
127
145
|
{
|
|
128
146
|
mime: "application/pdf",
|
|
129
147
|
family: "document",
|
|
148
|
+
class: "pdf",
|
|
130
149
|
extensions: [".pdf"],
|
|
131
150
|
label: "PDF",
|
|
132
151
|
maxBytes: 50_000_000,
|
|
@@ -134,6 +153,7 @@ export const INGESTION_FORMATS: readonly IngestionFormat[] = [
|
|
|
134
153
|
{
|
|
135
154
|
mime: "image/png",
|
|
136
155
|
family: "image",
|
|
156
|
+
class: "image",
|
|
137
157
|
extensions: [".png"],
|
|
138
158
|
label: "Image",
|
|
139
159
|
maxBytes: 25_000_000,
|
|
@@ -141,6 +161,7 @@ export const INGESTION_FORMATS: readonly IngestionFormat[] = [
|
|
|
141
161
|
{
|
|
142
162
|
mime: "image/jpeg",
|
|
143
163
|
family: "image",
|
|
164
|
+
class: "image",
|
|
144
165
|
extensions: [".jpg", ".jpeg"],
|
|
145
166
|
label: "Image",
|
|
146
167
|
maxBytes: 25_000_000,
|
|
@@ -148,6 +169,7 @@ export const INGESTION_FORMATS: readonly IngestionFormat[] = [
|
|
|
148
169
|
{
|
|
149
170
|
mime: "image/webp",
|
|
150
171
|
family: "image",
|
|
172
|
+
class: "image",
|
|
151
173
|
extensions: [".webp"],
|
|
152
174
|
label: "Image",
|
|
153
175
|
maxBytes: 25_000_000,
|
|
@@ -155,6 +177,7 @@ export const INGESTION_FORMATS: readonly IngestionFormat[] = [
|
|
|
155
177
|
{
|
|
156
178
|
mime: "image/gif",
|
|
157
179
|
family: "image",
|
|
180
|
+
class: "image",
|
|
158
181
|
extensions: [".gif"],
|
|
159
182
|
label: "Image",
|
|
160
183
|
maxBytes: 25_000_000,
|
|
@@ -162,27 +185,31 @@ export const INGESTION_FORMATS: readonly IngestionFormat[] = [
|
|
|
162
185
|
{
|
|
163
186
|
mime: "image/heic",
|
|
164
187
|
family: "image",
|
|
188
|
+
class: "image",
|
|
165
189
|
extensions: [".heic"],
|
|
166
190
|
label: "Image",
|
|
167
191
|
maxBytes: 25_000_000,
|
|
168
192
|
},
|
|
169
193
|
{
|
|
170
|
-
mime: "image/
|
|
171
|
-
family: "
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
194
|
+
mime: "image/heif",
|
|
195
|
+
family: "image",
|
|
196
|
+
class: "image",
|
|
197
|
+
extensions: [".heif"],
|
|
198
|
+
label: "Image",
|
|
199
|
+
maxBytes: 25_000_000,
|
|
175
200
|
},
|
|
176
201
|
{
|
|
177
|
-
mime: "
|
|
202
|
+
mime: "image/svg+xml",
|
|
178
203
|
family: "diagram",
|
|
179
|
-
|
|
204
|
+
class: "diagram",
|
|
205
|
+
extensions: [".svg"],
|
|
180
206
|
label: "Diagram",
|
|
181
207
|
maxBytes: 10_000_000,
|
|
182
208
|
},
|
|
183
209
|
{
|
|
184
210
|
mime: "audio/mpeg",
|
|
185
211
|
family: "audio",
|
|
212
|
+
class: "audio",
|
|
186
213
|
extensions: [".mp3"],
|
|
187
214
|
label: "Audio",
|
|
188
215
|
maxBytes: 500_000_000,
|
|
@@ -190,6 +217,7 @@ export const INGESTION_FORMATS: readonly IngestionFormat[] = [
|
|
|
190
217
|
{
|
|
191
218
|
mime: "audio/x-m4a",
|
|
192
219
|
family: "audio",
|
|
220
|
+
class: "audio",
|
|
193
221
|
extensions: [".m4a"],
|
|
194
222
|
label: "Audio",
|
|
195
223
|
maxBytes: 500_000_000,
|
|
@@ -197,6 +225,7 @@ export const INGESTION_FORMATS: readonly IngestionFormat[] = [
|
|
|
197
225
|
{
|
|
198
226
|
mime: "audio/wav",
|
|
199
227
|
family: "audio",
|
|
228
|
+
class: "audio",
|
|
200
229
|
extensions: [".wav"],
|
|
201
230
|
label: "Audio",
|
|
202
231
|
maxBytes: 500_000_000,
|
|
@@ -204,6 +233,7 @@ export const INGESTION_FORMATS: readonly IngestionFormat[] = [
|
|
|
204
233
|
{
|
|
205
234
|
mime: "audio/aac",
|
|
206
235
|
family: "audio",
|
|
236
|
+
class: "audio",
|
|
207
237
|
extensions: [".aac"],
|
|
208
238
|
label: "Audio",
|
|
209
239
|
maxBytes: 500_000_000,
|
|
@@ -211,6 +241,7 @@ export const INGESTION_FORMATS: readonly IngestionFormat[] = [
|
|
|
211
241
|
{
|
|
212
242
|
mime: "video/mp4",
|
|
213
243
|
family: "video",
|
|
244
|
+
class: "video",
|
|
214
245
|
extensions: [".mp4", ".m4v"],
|
|
215
246
|
label: "Video",
|
|
216
247
|
maxBytes: 500_000_000,
|
|
@@ -218,6 +249,7 @@ export const INGESTION_FORMATS: readonly IngestionFormat[] = [
|
|
|
218
249
|
{
|
|
219
250
|
mime: "video/quicktime",
|
|
220
251
|
family: "video",
|
|
252
|
+
class: "video",
|
|
221
253
|
extensions: [".mov"],
|
|
222
254
|
label: "Video",
|
|
223
255
|
maxBytes: 500_000_000,
|
|
@@ -238,16 +270,3 @@ export function acceptExtensions(): string[] {
|
|
|
238
270
|
export function humanLabels(): string[] {
|
|
239
271
|
return [...new Set(INGESTION_FORMATS.map((entry) => entry.label))];
|
|
240
272
|
}
|
|
241
|
-
|
|
242
|
-
/**
|
|
243
|
-
* Comma-joined extension string for a file input's `accept` attribute.
|
|
244
|
-
* Derived from {@link acceptExtensions} — never hand-maintained.
|
|
245
|
-
*/
|
|
246
|
-
export const INGEST_ACCEPT: string = acceptExtensions().join(",");
|
|
247
|
-
|
|
248
|
-
/**
|
|
249
|
-
* Human-readable supported-formats summary, derived from {@link humanLabels}.
|
|
250
|
-
* Names every accepted group (in registry order) so the copy can't undersell
|
|
251
|
-
* what actually works.
|
|
252
|
-
*/
|
|
253
|
-
export const SUPPORTED_LABEL: string = `Supported: ${humanLabels().join(", ")}`;
|
|
@@ -1,58 +1,104 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Ingestion Surfaces
|
|
3
3
|
*
|
|
4
|
-
* Per-surface views of the single format registry. A surface
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Per-surface views of the single format registry. A surface declares a
|
|
5
|
+
* treatment per family it consumes plus a required byte ceiling; everything
|
|
6
|
+
* else — the families, the formats, the file-input `accept` string, the
|
|
7
|
+
* "Supported: …" copy, the transport byte ceiling — is DERIVED from
|
|
7
8
|
* `INGESTION_FORMATS` through that declaration. No surface re-spells a MIME.
|
|
8
9
|
*
|
|
9
10
|
* Invariants:
|
|
10
11
|
* - Every surface admits at least one registry format.
|
|
11
12
|
* - `maxBytesFor` never exceeds the registry cap of any admitted format, and
|
|
12
|
-
* never exceeds the surface ceiling
|
|
13
|
+
* never exceeds the surface ceiling.
|
|
13
14
|
*
|
|
14
15
|
* @see ADR-CONTRACTS-159 for design rationale
|
|
15
16
|
*/
|
|
16
17
|
|
|
17
18
|
import { INGESTION_FORMATS } from "./registry";
|
|
18
19
|
import type {
|
|
20
|
+
IngestionFamily,
|
|
19
21
|
IngestionFormat,
|
|
20
22
|
IngestionSurface,
|
|
21
23
|
IngestionSurfaceKey,
|
|
24
|
+
Treatment,
|
|
22
25
|
} from "./types";
|
|
23
26
|
|
|
24
|
-
|
|
25
|
-
|
|
27
|
+
const MIB = 1024 * 1024;
|
|
28
|
+
|
|
29
|
+
/** Canonical family order; `familiesOf` returns families in this order. */
|
|
30
|
+
const FAMILY_ORDER: readonly IngestionFamily[] = [
|
|
31
|
+
"text",
|
|
32
|
+
"document",
|
|
33
|
+
"image",
|
|
34
|
+
"diagram",
|
|
35
|
+
"audio",
|
|
36
|
+
"video",
|
|
37
|
+
];
|
|
26
38
|
|
|
27
39
|
/**
|
|
28
40
|
* The canonical surface declarations.
|
|
29
41
|
*
|
|
30
|
-
* - `company-md`: context
|
|
31
|
-
* documents
|
|
32
|
-
*
|
|
33
|
-
* - `org-chart`:
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
* per
|
|
42
|
+
* - `company-md`: context bank, goals extraction, Source Sync landing. Text and
|
|
43
|
+
* documents become text. (Image/diagram vision on this surface is a later,
|
|
44
|
+
* separate change: it needs its own extraction prompt and discovery eval.)
|
|
45
|
+
* - `org-chart`: HRIS / org-chart import. A photographed, drawn or PDF chart
|
|
46
|
+
* goes to a vision-capable model natively; 10 MiB bounds the bytes per upload.
|
|
47
|
+
* - `chat`: chat composer attachments. Text becomes text; a PDF and images go
|
|
48
|
+
* to the model natively. At most 8 files per turn. 5 MiB per file = the composer's per-file cap today;
|
|
49
|
+
* the request body ceiling (base64, several files) is derived from it by the
|
|
50
|
+
* chat PRD, not declared here. Audio/video are refused until a transcript
|
|
51
|
+
* path is wired.
|
|
52
|
+
* - `profile-image`: avatar and org-logo uploads. Images are stored, never
|
|
53
|
+
* extracted; 5 MiB matches the current avatar/logo cap.
|
|
37
54
|
*/
|
|
38
55
|
export const INGESTION_SURFACES: Readonly<
|
|
39
56
|
Record<IngestionSurfaceKey, IngestionSurface>
|
|
40
57
|
> = {
|
|
41
58
|
"company-md": {
|
|
42
59
|
key: "company-md",
|
|
43
|
-
|
|
60
|
+
treatments: { text: "text", document: "text" },
|
|
61
|
+
maxBytes: 50_000_000,
|
|
44
62
|
},
|
|
45
63
|
"org-chart": {
|
|
46
64
|
key: "org-chart",
|
|
47
|
-
|
|
48
|
-
|
|
65
|
+
treatments: {
|
|
66
|
+
text: "text",
|
|
67
|
+
document: "vision",
|
|
68
|
+
image: "vision",
|
|
69
|
+
diagram: "vision",
|
|
70
|
+
},
|
|
71
|
+
maxBytes: 10 * MIB,
|
|
72
|
+
},
|
|
73
|
+
chat: {
|
|
74
|
+
key: "chat",
|
|
75
|
+
treatments: { text: "text", document: "vision", image: "vision" },
|
|
76
|
+
maxBytes: 5 * MIB,
|
|
77
|
+
// The composer sets no maxFiles today (unbounded; only the 10 MiB body cap
|
|
78
|
+
// limits a turn). 8 bounds per-turn vision/billing cost and the derived body
|
|
79
|
+
// ceiling (8 × 5 MiB × 4/3 base64 + envelope, computed by the chat PRD) while
|
|
80
|
+
// covering the multi-screenshot case.
|
|
81
|
+
maxFilesPerTurn: 8,
|
|
82
|
+
},
|
|
83
|
+
"profile-image": {
|
|
84
|
+
key: "profile-image",
|
|
85
|
+
treatments: { image: "store" },
|
|
86
|
+
maxBytes: 5 * MIB,
|
|
49
87
|
},
|
|
50
88
|
};
|
|
51
89
|
|
|
52
|
-
/** The
|
|
53
|
-
export function
|
|
54
|
-
const {
|
|
55
|
-
return
|
|
90
|
+
/** The families a surface admits, in canonical family order. */
|
|
91
|
+
export function familiesOf(surface: IngestionSurfaceKey): IngestionFamily[] {
|
|
92
|
+
const { treatments } = INGESTION_SURFACES[surface];
|
|
93
|
+
return FAMILY_ORDER.filter((family) => treatments[family] !== undefined);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** What the surface does with this format; null = the surface refuses it. */
|
|
97
|
+
export function treatmentFor(
|
|
98
|
+
surface: IngestionSurfaceKey,
|
|
99
|
+
format: IngestionFormat,
|
|
100
|
+
): Treatment | null {
|
|
101
|
+
return INGESTION_SURFACES[surface].treatments[format.family] ?? null;
|
|
56
102
|
}
|
|
57
103
|
|
|
58
104
|
/** True when the surface admits this registry format. */
|
|
@@ -60,13 +106,22 @@ export function surfaceAdmits(
|
|
|
60
106
|
surface: IngestionSurfaceKey,
|
|
61
107
|
format: IngestionFormat,
|
|
62
108
|
): boolean {
|
|
63
|
-
return
|
|
109
|
+
return treatmentFor(surface, format) !== null;
|
|
64
110
|
}
|
|
65
111
|
|
|
66
|
-
/**
|
|
112
|
+
/** The registry formats a surface admits, in registry order. */
|
|
113
|
+
export function formatsFor(surface: IngestionSurfaceKey): IngestionFormat[] {
|
|
114
|
+
return INGESTION_FORMATS.filter((entry) => surfaceAdmits(surface, entry));
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* The `<input accept>` value for a surface: per format, its extensions then its
|
|
119
|
+
* MIME (".png,image/png,.jpg,.jpeg,image/jpeg,…"). Both forms are valid in
|
|
120
|
+
* `accept`, and a MIME-only matcher (the chat composer) can use the same string.
|
|
121
|
+
*/
|
|
67
122
|
export function acceptFor(surface: IngestionSurfaceKey): string {
|
|
68
123
|
return formatsFor(surface)
|
|
69
|
-
.flatMap((entry) => [...entry.extensions])
|
|
124
|
+
.flatMap((entry) => [...entry.extensions, entry.mime])
|
|
70
125
|
.join(",");
|
|
71
126
|
}
|
|
72
127
|
|
|
@@ -77,15 +132,13 @@ export function supportedLabelFor(surface: IngestionSurfaceKey): string {
|
|
|
77
132
|
}
|
|
78
133
|
|
|
79
134
|
/**
|
|
80
|
-
* The transport byte ceiling
|
|
81
|
-
*
|
|
82
|
-
* The per-format registry cap still applies underneath it.
|
|
135
|
+
* The transport byte ceiling: the largest registry cap among admitted formats,
|
|
136
|
+
* tightened to the (required) surface ceiling.
|
|
83
137
|
*/
|
|
84
138
|
export function maxBytesFor(surface: IngestionSurfaceKey): number {
|
|
85
139
|
const registryMax = formatsFor(surface).reduce(
|
|
86
140
|
(max, entry) => Math.max(max, entry.maxBytes),
|
|
87
141
|
0,
|
|
88
142
|
);
|
|
89
|
-
|
|
90
|
-
return ceiling === undefined ? registryMax : Math.min(registryMax, ceiling);
|
|
143
|
+
return Math.min(registryMax, INGESTION_SURFACES[surface].maxBytes);
|
|
91
144
|
}
|