@company-semantics/contracts 97.0.0 → 99.0.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 +62 -8
- package/src/chat/README.md +31 -2
- package/src/chat/__tests__/alternatives.test.ts +121 -0
- package/src/chat/__tests__/chat-exports.test.ts +73 -1
- package/src/chat/__tests__/feedback-reasons.test.ts +99 -0
- package/src/chat/__tests__/message-actions.test.ts +41 -9
- package/src/chat/__tests__/message-mentions.test.ts +1 -0
- package/src/chat/__tests__/multi-party.test.ts +1 -0
- package/src/chat/__tests__/runtime-profile.test.ts +23 -5
- package/src/chat/__tests__/turn-wire.test.ts +43 -4
- package/src/chat/feedback.ts +80 -0
- package/src/chat/index.ts +26 -0
- package/src/chat/message-actions.ts +36 -30
- package/src/chat/schemas.ts +51 -1
- package/src/chat/types.ts +15 -0
- package/src/generated/openapi-routes.ts +1 -0
- 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
|
@@ -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
|
}
|
package/src/ingestion/types.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* private dispatch fields locally) and the app upload UI (which derives its
|
|
8
8
|
* `accept` attribute and supported-formats copy from this).
|
|
9
9
|
*
|
|
10
|
-
* Types
|
|
10
|
+
* Types plus the `FORMAT_CLASSES` and `TREATMENTS` vocabulary constants. The backend-only normalizer routing
|
|
11
11
|
* (`canonicalKind`, `normalizerKey`) deliberately does NOT live here; it is an
|
|
12
12
|
* implementation detail of the backend dispatch and would pollute the shared
|
|
13
13
|
* vocabulary.
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
19
|
* The semantic family a format belongs to. A SURFACE (an upload picker and the
|
|
20
|
-
* route behind it) declares
|
|
20
|
+
* route behind it) declares a treatment per family it consumes; the accept-set and the
|
|
21
21
|
* byte ceiling for that surface derive from the registry through this field.
|
|
22
22
|
* Public and semantic — NOT the backend normalizer dispatch (`canonicalKind` /
|
|
23
23
|
* `normalizerKey` stay backend-local, ADR-CONTRACTS-075).
|
|
@@ -27,6 +27,29 @@
|
|
|
27
27
|
export type IngestionFamily =
|
|
28
28
|
"text" | "document" | "image" | "diagram" | "audio" | "video";
|
|
29
29
|
|
|
30
|
+
/**
|
|
31
|
+
* The coarse, provider-agnostic kind of a format, used wherever the system
|
|
32
|
+
* classes a file without reading it: the Source Sync inventory `mime_class`,
|
|
33
|
+
* the discovery topology mix, probe groups, and the spreadsheet `source_kind`.
|
|
34
|
+
* Declared ON the format entry — no consumer keeps an extension → class table.
|
|
35
|
+
* Canonical order (the inventory CHECK and every derived list follow it).
|
|
36
|
+
*/
|
|
37
|
+
export const FORMAT_CLASSES = [
|
|
38
|
+
"docs",
|
|
39
|
+
"slides",
|
|
40
|
+
"sheets",
|
|
41
|
+
"pdf",
|
|
42
|
+
"text",
|
|
43
|
+
"image",
|
|
44
|
+
"diagram",
|
|
45
|
+
"audio",
|
|
46
|
+
"video",
|
|
47
|
+
] as const;
|
|
48
|
+
export type FormatClass = (typeof FORMAT_CLASSES)[number];
|
|
49
|
+
|
|
50
|
+
/** A Source Sync inventory class: a format class, or a provider shortcut row. */
|
|
51
|
+
export type InventoryClass = FormatClass | "shortcut";
|
|
52
|
+
|
|
30
53
|
/**
|
|
31
54
|
* One accepted upload format, keyed by its canonical MIME type.
|
|
32
55
|
*
|
|
@@ -35,14 +58,17 @@ export type IngestionFamily =
|
|
|
35
58
|
* - `extensions` are lowercase, dot-prefixed (e.g. `.docx`).
|
|
36
59
|
* - `label` is the human display GROUP and is intentionally shared across
|
|
37
60
|
* related formats (`.docx` + `.doc` are both `Word`; the rasters are all
|
|
38
|
-
* `Image`; `.svg`
|
|
61
|
+
* `Image`; `.svg` is `Diagram`).
|
|
39
62
|
* - `family` is the semantic family a surface selects on; every entry has one.
|
|
63
|
+
* - `class` is the coarse class (one of `FORMAT_CLASSES`); every entry has one.
|
|
40
64
|
*/
|
|
41
65
|
export interface IngestionFormat {
|
|
42
66
|
/** Canonical MIME type (the registry key). */
|
|
43
67
|
mime: string;
|
|
44
|
-
/** Semantic family the format belongs to (what a surface
|
|
68
|
+
/** Semantic family the format belongs to (what a surface declares a treatment for). */
|
|
45
69
|
family: IngestionFamily;
|
|
70
|
+
/** Coarse class for classing without reading (inventory, topology, probes). */
|
|
71
|
+
class: FormatClass;
|
|
46
72
|
/** Lowercase, dot-prefixed filename extensions for this format. */
|
|
47
73
|
extensions: readonly string[];
|
|
48
74
|
/** Human display group label (e.g. `Word`, `Image`, `Audio`). */
|
|
@@ -51,23 +77,62 @@ export interface IngestionFormat {
|
|
|
51
77
|
maxBytes: number;
|
|
52
78
|
}
|
|
53
79
|
|
|
54
|
-
/** The upload surfaces that consume the registry. */
|
|
55
|
-
export type IngestionSurfaceKey = "company-md" | "org-chart";
|
|
56
|
-
|
|
57
80
|
/**
|
|
58
|
-
* What
|
|
59
|
-
*
|
|
81
|
+
* What a surface does with an admitted family. The backend proves at load that
|
|
82
|
+
* it can perform every declared (surface, family, treatment) for every admitted
|
|
83
|
+
* format, or it refuses to start.
|
|
60
84
|
*
|
|
61
|
-
*
|
|
85
|
+
* - `text`: normalize to text only (a PDF is text-extracted).
|
|
86
|
+
* - `vision`: hand the model the richest native form — images as image parts,
|
|
87
|
+
* a PDF as a binary document, text-native formats (docx, csv, …) as text.
|
|
88
|
+
* - `transcript`: audio/video become a transcript.
|
|
89
|
+
* - `store`: bytes are kept as-is (after format-preserving preparation such as
|
|
90
|
+
* HEIC → JPEG); nothing is extracted.
|
|
91
|
+
*/
|
|
92
|
+
export const TREATMENTS = ["text", "vision", "transcript", "store"] as const;
|
|
93
|
+
export type Treatment = (typeof TREATMENTS)[number];
|
|
94
|
+
|
|
95
|
+
/** The surfaces that consume the registry. */
|
|
96
|
+
export type IngestionSurfaceKey =
|
|
97
|
+
"company-md" | "org-chart" | "chat" | "profile-image";
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* One surface: the treatment it applies per admitted family, and its byte
|
|
101
|
+
* ceiling. `families` is derived (`familiesOf`), never declared.
|
|
62
102
|
*/
|
|
63
103
|
export interface IngestionSurface {
|
|
64
104
|
/** The surface this declaration describes. */
|
|
65
105
|
key: IngestionSurfaceKey;
|
|
66
|
-
/**
|
|
67
|
-
|
|
106
|
+
/** Admitted family → what the surface does with it. Absent family = refused (415). */
|
|
107
|
+
treatments: Readonly<Partial<Record<IngestionFamily, Treatment>>>;
|
|
108
|
+
/** Surface byte ceiling (required). Caps every admitted format below its registry `maxBytes`. */
|
|
109
|
+
maxBytes: number;
|
|
68
110
|
/**
|
|
69
|
-
*
|
|
70
|
-
*
|
|
111
|
+
* Most files one turn may carry. Declared only on `chat` (required there,
|
|
112
|
+
* pinned by test); upload surfaces take one file per request and omit it.
|
|
71
113
|
*/
|
|
72
|
-
|
|
114
|
+
maxFilesPerTurn?: number;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** External sources that hand the pipeline provider-native files. */
|
|
118
|
+
export type IngestionProviderKey = "google-drive";
|
|
119
|
+
|
|
120
|
+
/** A provider-native type that is exported to a registry format before ingestion. */
|
|
121
|
+
export interface ProviderNativeFormat {
|
|
122
|
+
/** The provider's own MIME (e.g. a Google Workspace type). */
|
|
123
|
+
mime: string;
|
|
124
|
+
/** Human label for the native type (e.g. `Google Doc`). */
|
|
125
|
+
label: string;
|
|
126
|
+
/** Registry MIME the provider exports this type to. Must resolve via `byMime`. */
|
|
127
|
+
exportsTo: string;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** One provider: its native types and the structural container MIMEs it lists. */
|
|
131
|
+
export interface IngestionProvider {
|
|
132
|
+
/** The provider this declaration describes. */
|
|
133
|
+
key: IngestionProviderKey;
|
|
134
|
+
/** Native types the provider exports to registry formats. */
|
|
135
|
+
nativeFormats: readonly ProviderNativeFormat[];
|
|
136
|
+
/** Structural (non-file) MIMEs the provider lists: folders and shortcuts. */
|
|
137
|
+
containerMimes: { readonly folder: string; readonly shortcut: string };
|
|
73
138
|
}
|