@nacre.work/core 0.17.2 → 0.17.4

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.
@@ -0,0 +1,135 @@
1
+ /**
2
+ * What a caller is told about a document that failed to index.
3
+ *
4
+ * ## Why this is not just the error string
5
+ *
6
+ * The worker stores whatever went wrong verbatim, which is right for the
7
+ * operator reading a log and wrong for the caller reading an API. That string
8
+ * carries the embedding endpoint's URL, the parser's, and whatever a sidecar
9
+ * put in its message — infrastructure names, on an internal network, written
10
+ * by somebody who was not thinking about who reads them.
11
+ *
12
+ * The caller who most needs this is the one who should see it least: an agent
13
+ * over MCP, possibly acting through a delegation somebody granted to a third
14
+ * party. It needs to know its document failed and whether re-sending would
15
+ * help. It does not need `http://embedder.internal:8080/embeddings`.
16
+ *
17
+ * So a failure becomes a **reason** — a stable string a program can branch on —
18
+ * beside the message itself with anything shaped like a host or a URL taken
19
+ * out. Relaying the real wording matters: a scanned PDF fails with a message
20
+ * naming the scan and pointing at OCR, and a sentence invented here would
21
+ * send an operator looking for a corrupt file. The unredacted text stays in
22
+ * Postgres and in the worker's log, where the operator is.
23
+ *
24
+ * ## The reasons
25
+ *
26
+ * Deliberately few. Each one answers "what should the caller do now", because
27
+ * that is the only question the answer is for; two failures with the same
28
+ * answer do not need two reasons.
29
+ */
30
+ /**
31
+ * Classify a stored failure.
32
+ *
33
+ * Matched on wording, which is unavoidable: what arrives here is a string built
34
+ * from somebody else's error. The mapping is deliberately loose — an unmatched
35
+ * failure is `internal`, which is honest, rather than a guess that sends the
36
+ * caller to re-send a document that will never index.
37
+ */
38
+ export function classifyIngestFailure(raw) {
39
+ const text = raw.toLowerCase();
40
+ const reason = /must have less than \d+ tokens|too long|maximum (?:input )?length|context length/.test(text)
41
+ ? 'too_long'
42
+ : /quota|max_documents|limit reached|too many documents/.test(text)
43
+ ? 'quota'
44
+ : /parse|unsupported|not a pdf|%pdf|decode|extract/.test(text)
45
+ ? 'unreadable'
46
+ : /econnrefused|enotfound|etimedout|timeout|fetch failed|socket|answered 5\d\d|unavailable|network/.test(text)
47
+ ? 'unavailable'
48
+ : 'internal';
49
+ // The message is the real one with hosts removed, not a sentence written
50
+ // here. Replacing it looked safer and threw away the only thing that made
51
+ // some failures actionable: a scanned PDF fails with a message naming the
52
+ // scan and pointing at OCR, and "This document could not be read" sends an
53
+ // operator hunting for a corrupt file instead. The compose smoke asserts
54
+ // that specific wording survives, and caught exactly this.
55
+ //
56
+ // What the caller must not receive is the *infrastructure* in it, which is
57
+ // what `withoutHosts` takes out. The advice that used to be canned here —
58
+ // whether re-sending helps — is what `reason` is for, and it is documented
59
+ // against the reason rather than repeated into every message.
60
+ const message = withoutHosts(raw).slice(0, 300);
61
+ return { reason, message };
62
+ }
63
+ /**
64
+ * Whatever is safe to repeat from a stored failure.
65
+ *
66
+ * ## Redacted by context, not by the shape of a token
67
+ *
68
+ * The first version matched anything with a dot and a two-letter tail, and that
69
+ * is wrong in both directions.
70
+ *
71
+ * It **missed** every hostname this product actually deploys with. Service
72
+ * names in `docker-compose.yml` and in the chart are single-label — `embedder`,
73
+ * `qdrant`, `parser`, `minio` — so a rule needing a dot held for the example in
74
+ * the paragraph above (`embedder.internal`) and for nothing that ships. The way
75
+ * the name survived is worth keeping: the URL was removed, and then undici
76
+ * appended its cause verbatim, so `getaddrinfo ENOTFOUND embedder` sat at the
77
+ * end of an otherwise redacted sentence. IPv6 was untouched entirely.
78
+ *
79
+ * So a host is now recognised where a host can *be*: after a scheme, inside
80
+ * brackets, as `name:port`, after a DNS or socket error code, and after the
81
+ * handful of phrases this repository writes itself. Those are the positions a
82
+ * machine puts one in, and unlike a token's shape they are not shared with
83
+ * anything a person wrote.
84
+ *
85
+ * ## What it still over-redacts, and why that is the direction to fail in
86
+ *
87
+ * The dotted-name rule stays, last, and it cannot tell `contract.pdf` from
88
+ * `example.com` — nothing can, they are the same string with a different tail,
89
+ * and a list of file extensions is a list that goes stale. So a filename in a
90
+ * parser's message becomes `[host]`, which costs the sender the name of their
91
+ * own document.
92
+ *
93
+ * That is the trade taken deliberately: over-redacting a filename is a
94
+ * usability cost on one message, and under-redacting a hostname is the thing
95
+ * this function exists to prevent, on a surface a third party reaches through
96
+ * a delegation. It is stated here and in `docs/openapi.yaml` rather than left
97
+ * for somebody to discover.
98
+ *
99
+ * Not a general redactor and does not pretend to be one: a credential that is
100
+ * not inside a URL passes through, because there is no shape to match it on.
101
+ */
102
+ export function withoutHosts(raw) {
103
+ return (raw
104
+ // A URL, whatever the scheme. Everything after `://` goes, which is what
105
+ // takes the userinfo with it: `postgres://user:hunter2@db:5432/x`.
106
+ .replace(/[a-z][a-z0-9+.-]*:\/\/[^\s"']+/gi, '[endpoint]')
107
+ // IPv6 in brackets, with or without a port: `[fd00:ec2::23]:8080`.
108
+ .replace(/\[[0-9a-f]{0,4}(?::[0-9a-f]{0,4}){2,}\](?::\d{1,5})?/gi, '[host]')
109
+ // Bare IPv6. Three colons at least, so a clock (`12:30:45`) is not one
110
+ // and `::1:8080` is — the lookbehind rather than `\b` because a leading
111
+ // `::` has no word boundary in front of it, which is how that one
112
+ // survived the first attempt at this rule.
113
+ .replace(/(?<![\w:.])(?:[0-9a-f]{0,4}:){3,}[0-9a-f]{0,4}/gi, '[host]')
114
+ .replace(/\b\d{1,3}(?:\.\d{1,3}){3}(?::\d+)?\b/g, '[host]')
115
+ // `host:port`, which is the shape that makes a bare label a host rather
116
+ // than a word. This is what catches `embedder:8080` — every service name
117
+ // this product ships with is single-label.
118
+ .replace(/\b[a-z][a-z0-9-]*(?:\.[a-z0-9-]+)*:\d{2,5}\b/gi, '[host]')
119
+ // Node names the host straight after the code, and undici appends the
120
+ // cause verbatim to a message whose URL has already been taken out — so
121
+ // `getaddrinfo ENOTFOUND embedder` is where the name actually survived.
122
+ //
123
+ // Case-sensitive, and the lookahead skips a second code, because the
124
+ // real string is `getaddrinfo ENOTFOUND embedder` and the naive rule
125
+ // redacts `ENOTFOUND` and keeps the host.
126
+ .replace(/\b(ENOTFOUND|EAI_AGAIN|ECONNREFUSED|ECONNRESET|ETIMEDOUT|EHOSTUNREACH|ENETUNREACH)\s+(?![A-Z_]{3,}\b)[A-Za-z0-9_.[\]-]*[A-Za-z0-9\]]/g, '$1 [host]')
127
+ // And our own wording says it in prose, where there is no port and no
128
+ // code to anchor on.
129
+ .replace(/\b(could not reach|could not connect to|connection to|connecting to|connect to|no route to)\s+[A-Za-z0-9_.[\]-]*[A-Za-z0-9\]]/gi, '$1 [host]')
130
+ // A dotted name. Last, and deliberately kept even though it is the rule
131
+ // that cannot tell `contract.pdf` from `example.com` — see the note in
132
+ // this function's header about which way that trade goes.
133
+ .replace(/\b(?:[a-z0-9-]+\.)+[a-z]{2,}(?::\d+)?\b/gi, '[host]'));
134
+ }
135
+ //# sourceMappingURL=ingest-failure.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ingest-failure.js","sourceRoot":"","sources":["../ingest-failure.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAoBH;;;;;;;GAOG;AACH,MAAM,UAAU,qBAAqB,CAAC,GAAW;IAC/C,MAAM,IAAI,GAAG,GAAG,CAAC,WAAW,EAAE,CAAA;IAE9B,MAAM,MAAM,GACV,kFAAkF,CAAC,IAAI,CAAC,IAAI,CAAC;QAC3F,CAAC,CAAC,UAAU;QACZ,CAAC,CAAC,sDAAsD,CAAC,IAAI,CAAC,IAAI,CAAC;YACjE,CAAC,CAAC,OAAO;YACT,CAAC,CAAC,iDAAiD,CAAC,IAAI,CAAC,IAAI,CAAC;gBAC5D,CAAC,CAAC,YAAY;gBACd,CAAC,CAAC,iGAAiG,CAAC,IAAI,CAClG,IAAI,CACL;oBACH,CAAC,CAAC,aAAa;oBACf,CAAC,CAAC,UAAU,CAAA;IAEtB,yEAAyE;IACzE,0EAA0E;IAC1E,0EAA0E;IAC1E,2EAA2E;IAC3E,yEAAyE;IACzE,2DAA2D;IAC3D,EAAE;IACF,2EAA2E;IAC3E,0EAA0E;IAC1E,2EAA2E;IAC3E,8DAA8D;IAC9D,MAAM,OAAO,GAAG,YAAY,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAA;IAC/C,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,CAAA;AAC5B,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,MAAM,UAAU,YAAY,CAAC,GAAW;IACtC,OAAO,CACL,GAAG;QACD,yEAAyE;QACzE,mEAAmE;SAClE,OAAO,CAAC,kCAAkC,EAAE,YAAY,CAAC;QAC1D,mEAAmE;SAClE,OAAO,CAAC,wDAAwD,EAAE,QAAQ,CAAC;QAC5E,uEAAuE;QACvE,wEAAwE;QACxE,kEAAkE;QAClE,2CAA2C;SAC1C,OAAO,CAAC,kDAAkD,EAAE,QAAQ,CAAC;SACrE,OAAO,CAAC,uCAAuC,EAAE,QAAQ,CAAC;QAC3D,wEAAwE;QACxE,yEAAyE;QACzE,2CAA2C;SAC1C,OAAO,CAAC,gDAAgD,EAAE,QAAQ,CAAC;QACpE,sEAAsE;QACtE,wEAAwE;QACxE,wEAAwE;QACxE,EAAE;QACF,qEAAqE;QACrE,qEAAqE;QACrE,0CAA0C;SACzC,OAAO,CACN,uIAAuI,EACvI,WAAW,CACZ;QACD,sEAAsE;QACtE,qBAAqB;SACpB,OAAO,CACN,iIAAiI,EACjI,WAAW,CACZ;QACD,wEAAwE;QACxE,uEAAuE;QACvE,0DAA0D;SACzD,OAAO,CAAC,2CAA2C,EAAE,QAAQ,CAAC,CAClE,CAAA;AACH,CAAC"}
@@ -41,6 +41,23 @@ import type { VectorStore } from './vector/search.js';
41
41
  * So the rule is here, `provisionOrganization` applies it before anything else,
42
42
  * and no caller can be the one that forgot.
43
43
  */
44
+ /**
45
+ * An email address, checked only enough to refuse what cannot be one.
46
+ *
47
+ * Here rather than beside the endpoints because `provisionOrganization` writes
48
+ * the first `users` row of every organization, and it was the one writer that
49
+ * did not ask. `init` asked for an `@` and nothing else, so
50
+ * `--email admin@localhost` created an organization whose administrator holds
51
+ * an address `POST /v1/users` refuses for anybody else — and the comment above
52
+ * that line already stated the rule it was breaking: the CLI asks in order to
53
+ * produce a usage message, and the function refuses regardless.
54
+ *
55
+ * Deliberately not RFC 5322. A stricter test rejects addresses that work; what
56
+ * matters is that the value is a single token with an `@` and a dot after it,
57
+ * so a name or a uuid typed into the wrong field is refused where the operator
58
+ * can see it rather than becoming a user nobody can sign in as.
59
+ */
60
+ export declare function looksLikeEmail(value: string): boolean;
44
61
  export declare function organizationSlugError(slug: string): string | undefined;
45
62
  export interface ProvisionOptions {
46
63
  readonly slug: string;
@@ -1 +1 @@
1
- {"version":3,"file":"provision.d.ts","sourceRoot":"","sources":["../provision.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,IAAI,CAAA;AAG9B,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AAErD;;;;;;;;;;;;;;;;;;;GAmBG;AAEH;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAWtE;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B;;;;;;OAMG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAA;CAC/B;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;CAC5B;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAC5B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B;;;;;;;;OAQG;IACH,QAAQ,CAAC,QAAQ,EAAE,YAAY,CAAA;IAC/B,8EAA8E;IAC9E,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,gFAAgF;IAChF,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;IACzB;;;;;;;;OAQG;IACH,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAA;CAC9B;AAED,KAAK,GAAG,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAA;AAEjE;;;;;;GAMG;AACH,wBAAsB,mBAAmB,CACvC,IAAI,EAAE,IAAI,EACV,OAAO,EAAE,gBAAgB,EACzB,QAAQ,EAAE,YAAY,EACtB,UAAU,EAAE,MAAM,EAClB,GAAG,GAAE,GAAqB,GACzB,OAAO,CAAC,IAAI,CAAC,eAAe,EAAE,YAAY,CAAC,CAAC,CAkK9C;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,qBAAqB,CACzC,IAAI,EAAE,IAAI,EACV,OAAO,EAAE,IAAI,CAAC,WAAW,EAAE,kBAAkB,CAAC,EAC9C,OAAO,EAAE,gBAAgB,EACzB,QAAQ,EAAE,YAAY,EACtB,GAAG,GAAE,GAAqB,GACzB,OAAO,CAAC,eAAe,CAAC,CAkB1B"}
1
+ {"version":3,"file":"provision.d.ts","sourceRoot":"","sources":["../provision.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,IAAI,CAAA;AAG9B,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AAErD;;;;;;;;;;;;;;;;;;;GAmBG;AAEH;;;;;;;;;;;;;;;;;;;;GAoBG;AACH;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAErD;AAED,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAWtE;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B;;;;;;OAMG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAA;CAC/B;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;CAC5B;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAC5B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B;;;;;;;;OAQG;IACH,QAAQ,CAAC,QAAQ,EAAE,YAAY,CAAA;IAC/B,8EAA8E;IAC9E,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,gFAAgF;IAChF,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;IACzB;;;;;;;;OAQG;IACH,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAA;CAC9B;AAED,KAAK,GAAG,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAA;AAEjE;;;;;;GAMG;AACH,wBAAsB,mBAAmB,CACvC,IAAI,EAAE,IAAI,EACV,OAAO,EAAE,gBAAgB,EACzB,QAAQ,EAAE,YAAY,EACtB,UAAU,EAAE,MAAM,EAClB,GAAG,GAAE,GAAqB,GACzB,OAAO,CAAC,IAAI,CAAC,eAAe,EAAE,YAAY,CAAC,CAAC,CAkK9C;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,qBAAqB,CACzC,IAAI,EAAE,IAAI,EACV,OAAO,EAAE,IAAI,CAAC,WAAW,EAAE,kBAAkB,CAAC,EAC9C,OAAO,EAAE,gBAAgB,EACzB,QAAQ,EAAE,YAAY,EACtB,GAAG,GAAE,GAAqB,GACzB,OAAO,CAAC,eAAe,CAAC,CAqB1B"}
package/dist/provision.js CHANGED
@@ -40,6 +40,25 @@ import { collectionName, vectorName } from './vector/query.js';
40
40
  * So the rule is here, `provisionOrganization` applies it before anything else,
41
41
  * and no caller can be the one that forgot.
42
42
  */
43
+ /**
44
+ * An email address, checked only enough to refuse what cannot be one.
45
+ *
46
+ * Here rather than beside the endpoints because `provisionOrganization` writes
47
+ * the first `users` row of every organization, and it was the one writer that
48
+ * did not ask. `init` asked for an `@` and nothing else, so
49
+ * `--email admin@localhost` created an organization whose administrator holds
50
+ * an address `POST /v1/users` refuses for anybody else — and the comment above
51
+ * that line already stated the rule it was breaking: the CLI asks in order to
52
+ * produce a usage message, and the function refuses regardless.
53
+ *
54
+ * Deliberately not RFC 5322. A stricter test rejects addresses that work; what
55
+ * matters is that the value is a single token with an `@` and a dot after it,
56
+ * so a name or a uuid typed into the wrong field is refused where the operator
57
+ * can see it rather than becoming a user nobody can sign in as.
58
+ */
59
+ export function looksLikeEmail(value) {
60
+ return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
61
+ }
43
62
  export function organizationSlugError(slug) {
44
63
  // The slug becomes the Qdrant collection name, so the characters it may
45
64
  // contain are not a matter of taste: one with a slash or a quote in it
@@ -227,6 +246,9 @@ export async function provisionOrganization(pool, vectors, options, provider, lo
227
246
  const wrong = organizationSlugError(options.slug);
228
247
  if (wrong !== undefined)
229
248
  throw new Error(`organization slug ${wrong}`);
249
+ if (!looksLikeEmail(options.email)) {
250
+ throw new Error(`administrator email does not look like an address: ${options.email}`);
251
+ }
230
252
  const collection = collectionName(options.slug);
231
253
  const ids = await provisionInPostgres(pool, options, provider, collection, log);
232
254
  // `ensureCollection` is already idempotent and already creates the payload
@@ -1 +1 @@
1
- {"version":3,"file":"provision.js","sourceRoot":"","sources":["../provision.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"provision.js","sourceRoot":"","sources":["../provision.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,99 @@
1
+ /**
2
+ * What a piece of text will cost an embedding model, in tokens.
3
+ *
4
+ * ## Why this exists
5
+ *
6
+ * A chunk size in **characters** says nothing about whether the embedder will
7
+ * accept it. Every embedding model has a hard ceiling on input length measured
8
+ * in tokens — 512 for the BGE and E5 families, which is what these Compose
9
+ * profiles start and what most self-hosted deployments run — and a character
10
+ * is not a token. Under an English tokenizer a Cyrillic or Korean character
11
+ * costs several.
12
+ *
13
+ * Measured against a real `BAAI/bge-small-en-v1.5` through Text Embeddings
14
+ * Inference's `/tokenize`, at the 800-character chunk this repository shipped:
15
+ *
16
+ * | script | chars | tokens | over 512? |
17
+ * |---------|-------|--------|-----------|
18
+ * | English | 800 | 149 | no |
19
+ * | Hindi | 800 | 471 | no |
20
+ * | Greek | 800 | 642 | **yes** |
21
+ * | Russian | 800 | 655 | **yes** |
22
+ * | Hebrew | 800 | 664 | **yes** |
23
+ * | Arabic | 800 | 676 | **yes** |
24
+ * | Chinese | 800 | 802 | **yes** |
25
+ * | Japanese| 800 | 802 | **yes** |
26
+ * | Korean | 800 | 1094 | **yes** |
27
+ *
28
+ * Seven scripts out of eleven tried. The endpoint answers `413` and the worker
29
+ * marks the document `failed`, which nothing retries — so a Russian corpus
30
+ * indexed nothing at all while the API answered `queued`. Exactly the shape of
31
+ * the batch-size defect, arriving through the other bound on the same request.
32
+ *
33
+ * ## The cost model, and why it is not the provable one
34
+ *
35
+ * The provable bound is the UTF-8 **byte** length: a byte-fallback tokenizer
36
+ * emits at most one token per byte, and the worst case measured was 0.97
37
+ * tokens per byte. Using it would be correct and would also cut English chunks
38
+ * from 800 characters to about 510, for text that was never near the ceiling —
39
+ * a 57% increase in vectors to fix a problem English does not have.
40
+ *
41
+ * So ASCII is charged at **half a token per character** and everything else at
42
+ * its byte length. That is above ASCII prose (0.18 measured) and identifiers
43
+ * (0.42), and below the one ASCII case that beats it — a run of pure
44
+ * punctuation, at 0.97. This estimate is therefore *not* an upper bound, and
45
+ * that is a deliberate trade rather than an oversight: `ingest.ts` re-chunks
46
+ * and retries when the endpoint refuses, so an underestimate costs one extra
47
+ * request and never a document. Being provably safe here would cost every
48
+ * English deployment half its context, permanently, to avoid a retry.
49
+ */
50
+ /**
51
+ * The ceiling to assume when a deployment has not said and the endpoint will
52
+ * not answer.
53
+ *
54
+ * **512, because that is what the BGE and E5 families accept** — the models
55
+ * this project's own profiles start. The same argument as `DEFAULT_EMBED_BATCH`
56
+ * being 32: a bound the ecosystem's most common self-hosted server actually
57
+ * enforces, rather than a guess at a good number.
58
+ */
59
+ export declare const DEFAULT_EMBED_MAX_TOKENS = 512;
60
+ /**
61
+ * Reserved out of the ceiling for whatever the model adds around the input.
62
+ *
63
+ * BERT-family tokenizers wrap every input in `[CLS]` and `[SEP]`, and those
64
+ * count against the same 512. Four rather than two: a tokenizer that adds a
65
+ * language or task prefix — E5's `query:` and `passage:`, which some
66
+ * deployments prepend — costs a little more, and the difference between four
67
+ * tokens and two is not worth a refusal.
68
+ */
69
+ export declare const TOKEN_RESERVE = 4;
70
+ /**
71
+ * An estimate of what `text` costs the embedder, in tokens.
72
+ *
73
+ * Deliberately cheap: one pass, no table, no allocation per character. It runs
74
+ * on every chunk boundary of every document, so it has to cost about what
75
+ * measuring a length costs.
76
+ */
77
+ export declare function estimateTokens(text: string): number;
78
+ /**
79
+ * How much of a model's window a chunk may fill.
80
+ *
81
+ * Separate from the ceiling itself so the reserve is applied in one place
82
+ * rather than at each call site — the defect this file exists for is a bound
83
+ * that two pieces of code disagreed about.
84
+ */
85
+ export declare function tokenBudget(maxTokens?: number): number;
86
+ /**
87
+ * Whether an error from an embedding endpoint says the input was too long.
88
+ *
89
+ * Matched on the message rather than the status, and both are checked, because
90
+ * the status alone is ambiguous: Text Embeddings Inference answers **413** for
91
+ * a batch with too many texts *and* for one text with too many tokens, and the
92
+ * two want opposite repairs — send fewer texts, or send shorter ones. The
93
+ * wording is what tells them apart.
94
+ *
95
+ * Checked against a real TEI: the OpenAI-shaped route answers
96
+ * `Input validation error: \`inputs\` must have less than 512 tokens. Given: 1482`.
97
+ */
98
+ export declare function refusedForLength(message: string): boolean;
99
+ //# sourceMappingURL=tokens.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tokens.d.ts","sourceRoot":"","sources":["../../text/tokens.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgDG;AAEH;;;;;;;;GAQG;AACH,eAAO,MAAM,wBAAwB,MAAM,CAAA;AAE3C;;;;;;;;GAQG;AACH,eAAO,MAAM,aAAa,IAAI,CAAA;AAE9B;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAkBnD;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,SAAS,GAAE,MAAiC,GAAG,MAAM,CAShF;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAIzD"}
@@ -0,0 +1,129 @@
1
+ /**
2
+ * What a piece of text will cost an embedding model, in tokens.
3
+ *
4
+ * ## Why this exists
5
+ *
6
+ * A chunk size in **characters** says nothing about whether the embedder will
7
+ * accept it. Every embedding model has a hard ceiling on input length measured
8
+ * in tokens — 512 for the BGE and E5 families, which is what these Compose
9
+ * profiles start and what most self-hosted deployments run — and a character
10
+ * is not a token. Under an English tokenizer a Cyrillic or Korean character
11
+ * costs several.
12
+ *
13
+ * Measured against a real `BAAI/bge-small-en-v1.5` through Text Embeddings
14
+ * Inference's `/tokenize`, at the 800-character chunk this repository shipped:
15
+ *
16
+ * | script | chars | tokens | over 512? |
17
+ * |---------|-------|--------|-----------|
18
+ * | English | 800 | 149 | no |
19
+ * | Hindi | 800 | 471 | no |
20
+ * | Greek | 800 | 642 | **yes** |
21
+ * | Russian | 800 | 655 | **yes** |
22
+ * | Hebrew | 800 | 664 | **yes** |
23
+ * | Arabic | 800 | 676 | **yes** |
24
+ * | Chinese | 800 | 802 | **yes** |
25
+ * | Japanese| 800 | 802 | **yes** |
26
+ * | Korean | 800 | 1094 | **yes** |
27
+ *
28
+ * Seven scripts out of eleven tried. The endpoint answers `413` and the worker
29
+ * marks the document `failed`, which nothing retries — so a Russian corpus
30
+ * indexed nothing at all while the API answered `queued`. Exactly the shape of
31
+ * the batch-size defect, arriving through the other bound on the same request.
32
+ *
33
+ * ## The cost model, and why it is not the provable one
34
+ *
35
+ * The provable bound is the UTF-8 **byte** length: a byte-fallback tokenizer
36
+ * emits at most one token per byte, and the worst case measured was 0.97
37
+ * tokens per byte. Using it would be correct and would also cut English chunks
38
+ * from 800 characters to about 510, for text that was never near the ceiling —
39
+ * a 57% increase in vectors to fix a problem English does not have.
40
+ *
41
+ * So ASCII is charged at **half a token per character** and everything else at
42
+ * its byte length. That is above ASCII prose (0.18 measured) and identifiers
43
+ * (0.42), and below the one ASCII case that beats it — a run of pure
44
+ * punctuation, at 0.97. This estimate is therefore *not* an upper bound, and
45
+ * that is a deliberate trade rather than an oversight: `ingest.ts` re-chunks
46
+ * and retries when the endpoint refuses, so an underestimate costs one extra
47
+ * request and never a document. Being provably safe here would cost every
48
+ * English deployment half its context, permanently, to avoid a retry.
49
+ */
50
+ /**
51
+ * The ceiling to assume when a deployment has not said and the endpoint will
52
+ * not answer.
53
+ *
54
+ * **512, because that is what the BGE and E5 families accept** — the models
55
+ * this project's own profiles start. The same argument as `DEFAULT_EMBED_BATCH`
56
+ * being 32: a bound the ecosystem's most common self-hosted server actually
57
+ * enforces, rather than a guess at a good number.
58
+ */
59
+ export const DEFAULT_EMBED_MAX_TOKENS = 512;
60
+ /**
61
+ * Reserved out of the ceiling for whatever the model adds around the input.
62
+ *
63
+ * BERT-family tokenizers wrap every input in `[CLS]` and `[SEP]`, and those
64
+ * count against the same 512. Four rather than two: a tokenizer that adds a
65
+ * language or task prefix — E5's `query:` and `passage:`, which some
66
+ * deployments prepend — costs a little more, and the difference between four
67
+ * tokens and two is not worth a refusal.
68
+ */
69
+ export const TOKEN_RESERVE = 4;
70
+ /**
71
+ * An estimate of what `text` costs the embedder, in tokens.
72
+ *
73
+ * Deliberately cheap: one pass, no table, no allocation per character. It runs
74
+ * on every chunk boundary of every document, so it has to cost about what
75
+ * measuring a length costs.
76
+ */
77
+ export function estimateTokens(text) {
78
+ let cost = 0;
79
+ for (const character of text) {
80
+ const code = character.codePointAt(0);
81
+ if (code < 0x80) {
82
+ // ASCII: half a token each. See the note above — over prose and
83
+ // identifiers, under a run of pure punctuation.
84
+ cost += 0.5;
85
+ }
86
+ else if (code < 0x800) {
87
+ cost += 2;
88
+ }
89
+ else if (code < 0x10000) {
90
+ cost += 3;
91
+ }
92
+ else {
93
+ // Astral: emoji, and the rarer CJK planes. Four bytes.
94
+ cost += 4;
95
+ }
96
+ }
97
+ return Math.ceil(cost);
98
+ }
99
+ /**
100
+ * How much of a model's window a chunk may fill.
101
+ *
102
+ * Separate from the ceiling itself so the reserve is applied in one place
103
+ * rather than at each call site — the defect this file exists for is a bound
104
+ * that two pieces of code disagreed about.
105
+ */
106
+ export function tokenBudget(maxTokens = DEFAULT_EMBED_MAX_TOKENS) {
107
+ const budget = maxTokens - TOKEN_RESERVE;
108
+ if (budget < 1) {
109
+ throw new Error(`an embedding token ceiling of ${String(maxTokens)} leaves no room for input once ` +
110
+ `${String(TOKEN_RESERVE)} tokens are reserved for what the model wraps around it`);
111
+ }
112
+ return budget;
113
+ }
114
+ /**
115
+ * Whether an error from an embedding endpoint says the input was too long.
116
+ *
117
+ * Matched on the message rather than the status, and both are checked, because
118
+ * the status alone is ambiguous: Text Embeddings Inference answers **413** for
119
+ * a batch with too many texts *and* for one text with too many tokens, and the
120
+ * two want opposite repairs — send fewer texts, or send shorter ones. The
121
+ * wording is what tells them apart.
122
+ *
123
+ * Checked against a real TEI: the OpenAI-shaped route answers
124
+ * `Input validation error: \`inputs\` must have less than 512 tokens. Given: 1482`.
125
+ */
126
+ export function refusedForLength(message) {
127
+ return /must have less than \d+ tokens|too long|exceeds? (?:the )?maximum (?:input )?length|context length/i.test(message);
128
+ }
129
+ //# sourceMappingURL=tokens.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tokens.js","sourceRoot":"","sources":["../../text/tokens.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgDG;AAEH;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG,GAAG,CAAA;AAE3C;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,CAAA;AAE9B;;;;;;GAMG;AACH,MAAM,UAAU,cAAc,CAAC,IAAY;IACzC,IAAI,IAAI,GAAG,CAAC,CAAA;IACZ,KAAK,MAAM,SAAS,IAAI,IAAI,EAAE,CAAC;QAC7B,MAAM,IAAI,GAAG,SAAS,CAAC,WAAW,CAAC,CAAC,CAAW,CAAA;QAC/C,IAAI,IAAI,GAAG,IAAI,EAAE,CAAC;YAChB,gEAAgE;YAChE,gDAAgD;YAChD,IAAI,IAAI,GAAG,CAAA;QACb,CAAC;aAAM,IAAI,IAAI,GAAG,KAAK,EAAE,CAAC;YACxB,IAAI,IAAI,CAAC,CAAA;QACX,CAAC;aAAM,IAAI,IAAI,GAAG,OAAO,EAAE,CAAC;YAC1B,IAAI,IAAI,CAAC,CAAA;QACX,CAAC;aAAM,CAAC;YACN,uDAAuD;YACvD,IAAI,IAAI,CAAC,CAAA;QACX,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;AACxB,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,WAAW,CAAC,YAAoB,wBAAwB;IACtE,MAAM,MAAM,GAAG,SAAS,GAAG,aAAa,CAAA;IACxC,IAAI,MAAM,GAAG,CAAC,EAAE,CAAC;QACf,MAAM,IAAI,KAAK,CACb,iCAAiC,MAAM,CAAC,SAAS,CAAC,iCAAiC;YACjF,GAAG,MAAM,CAAC,aAAa,CAAC,yDAAyD,CACpF,CAAA;IACH,CAAC;IACD,OAAO,MAAM,CAAA;AACf,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,gBAAgB,CAAC,OAAe;IAC9C,OAAO,qGAAqG,CAAC,IAAI,CAC/G,OAAO,CACR,CAAA;AACH,CAAC"}