@ai-matrx/content-ir 0.18.5 → 0.19.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/CHANGELOG.md +83 -4
- package/README.md +4 -2
- package/dist/index.cjs +168 -48
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +168 -49
- package/dist/index.js.map +1 -1
- package/dist/registry.cjs +88 -0
- package/dist/registry.cjs.map +1 -1
- package/dist/registry.d.cts +104 -1
- package/dist/registry.d.ts +104 -1
- package/dist/registry.js +88 -1
- package/dist/registry.js.map +1 -1
- package/dist/source.cjs +80 -48
- package/dist/source.cjs.map +1 -1
- package/dist/source.js +80 -48
- package/dist/source.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,10 +1,88 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.19.0 — kind validation behind a schema-source port (Matrx Alchemy ALC-13)
|
|
4
|
+
|
|
5
|
+
`/registry` (and the root) now export `createKindValidator(source, options?)`,
|
|
6
|
+
`SchemaSourcePort`, `SchemaUnavailableReason`, `KindValidationResult`,
|
|
7
|
+
`KindValidator` and `Json`. This is matrx-frontend's `validateAgainstKind`,
|
|
8
|
+
moved into the package with its database read inverted: the host implements
|
|
9
|
+
`SchemaSourcePort.kindSchema(kind, signal)` once and answers `{ schema,
|
|
10
|
+
version }` or `{ unavailable: "kind_not_registered" | "schema_unavailable" |
|
|
11
|
+
"schema_invalid" | "catalog_unreachable" }`. The verdict (`checked`, `ok`,
|
|
12
|
+
`errors`, `degradedReason`) and its messages are unchanged; a source that
|
|
13
|
+
throws reads as `catalog_unreachable`; an uncompilable schema reads as
|
|
14
|
+
`schema_invalid`. The schema cache (30 s default, `invalidate`,
|
|
15
|
+
`cachedSchema`) belongs to each validator instance, never to the module.
|
|
16
|
+
|
|
17
|
+
### Consumer action
|
|
18
|
+
|
|
19
|
+
matrx-frontend: implement `SchemaSourcePort` once over
|
|
20
|
+
`content_ir.kind_definition`, build one validator with `createKindValidator`,
|
|
21
|
+
repoint `features/surfaces/runtime/surface-writeback.ts` and
|
|
22
|
+
`build-tool-injection.ts` to it, and delete
|
|
23
|
+
`features/content-ir/registry/validate-against-kind.ts`.
|
|
24
|
+
|
|
25
|
+
### Also in 0.19.0 — the RC-B3 fix round (RC-B3 lane)
|
|
26
|
+
|
|
27
|
+
**Carries: RC-B3 fix round (verify-RC-B3 § 0.18.3) and RC-B3r R3 — builder:
|
|
28
|
+
the RC-B3 standard lane.** (aidream 1174edb640 — RC-B10, the print lane —
|
|
29
|
+
shipped in 0.18.3, and 094594d3a1 — the escaped-bracket rule, "Unreleased"
|
|
30
|
+
below — shipped in 0.18.4; both under generic "no source changes" notes.)
|
|
31
|
+
|
|
32
|
+
**`/source` streaming: settled means settled** (F3/F4). Found with the
|
|
33
|
+
verifier's generator (realistic model fragments, every prefix, with and
|
|
34
|
+
without a final newline), now permanent in `__tests__/source-attack.test.ts`
|
|
35
|
+
(`LLM-fragment documents…`; 12,000 documents in `SOURCE_ATTACK=long`) plus 30
|
|
36
|
+
pinned reproducers. `tokenizeSource(prefix, { streaming: true })` now also
|
|
37
|
+
holds back, as prose or as an open island:
|
|
38
|
+
- lines a box-drawing tree still forming at the end may claim as its root
|
|
39
|
+
label (the renderer's walk-back), the open last line included;
|
|
40
|
+
- a paragraph cut short by a block that may still dissolve (a `$$` pair
|
|
41
|
+
closing on the open line, an unbalanced JSON head, any opener still
|
|
42
|
+
arriving on the open line) — and a footnote before one;
|
|
43
|
+
- a block island ending on the open last line (inlines there are covered by
|
|
44
|
+
the tree-root hold);
|
|
45
|
+
- a nested-fence closer found only by the strict-CommonMark retry, a tilde
|
|
46
|
+
fence whose inner block may still grow past it, braces that balance on
|
|
47
|
+
the open last line;
|
|
48
|
+
- a code span that a blank-so-far or `>`-only open line may still close
|
|
49
|
+
(`pendingCodeSpanStart`, `source/code-ranges.ts`);
|
|
50
|
+
- a block whose opener is provisional text, and a paragraph break there.
|
|
51
|
+
The two lone-HTML-tag holdbacks (M-N6/N7) collapsed into the open-tail block
|
|
52
|
+
rule. Each holdback turns the attack suite red on its own when removed
|
|
53
|
+
(planted mutations on a scratch copy). The H2 exception is unchanged.
|
|
54
|
+
|
|
55
|
+
**`/source` one-shot: a mid-line reasoning mention that never closes is prose**
|
|
56
|
+
(RC-B3r R3). `grep -c '<thinking>' log.txt > count.txt` no longer opens an
|
|
57
|
+
`xml_region` to the end of the text (the renderer shows prose). While a stream
|
|
58
|
+
is still arriving it stays an open region (its closer may yet come).
|
|
59
|
+
|
|
60
|
+
**`isSingleDollarMath` refuses shell and template variable chains** (F5):
|
|
61
|
+
`$USER$HOSTNAME`, `A=$A$B`, `df$col$sub`, `$name.$ext`, `PS1='$USER@$HOST'`,
|
|
62
|
+
`${a}${b}` stay text; `$n$th`, `$x_i$s` stay math. Measured read-only with
|
|
63
|
+
matrx-frontend `scripts/measure-single-dollar-rule.ts` over 85,325 assistant
|
|
64
|
+
messages and 4,317 notes: 0 spans gained, 3 lost (2 shell-variable-chain, 1
|
|
65
|
+
template-literal), each an intended false positive; notes unchanged. New
|
|
66
|
+
export `singleDollarAfter(text, close)` — the `after` argument the rule now
|
|
67
|
+
reads (the following name, `\n` marking the end of text). Shared vectors grew
|
|
68
|
+
(`__tests__/fixtures/single-dollar-vectors.json`, including the trailing-space
|
|
69
|
+
guard case `echo $HOME $PATH`).
|
|
70
|
+
- Consumer action: callers of `isSingleDollarMath` pass
|
|
71
|
+
`singleDollarAfter(text, close)` instead of `text[close + 1]` (a one-character
|
|
72
|
+
`after` still works; it just cannot see a variable chain).
|
|
73
|
+
|
|
74
|
+
## 0.18.6
|
|
75
|
+
|
|
76
|
+
Automatic republish. Despite the generated note, this version DID carry source
|
|
77
|
+
changes: most of the 0.19.0 work above (through aidream cd35751bea). 0.19.0
|
|
78
|
+
completes it (aidream d6241c5496, f1e9156e82).
|
|
79
|
+
|
|
3
80
|
## 0.18.5
|
|
4
81
|
|
|
5
|
-
Automatic
|
|
6
|
-
|
|
7
|
-
|
|
82
|
+
Automatic republish. Despite the generated note, this version DID carry source
|
|
83
|
+
changes: the first half of the 0.19.0 work above (aidream edb3ec222d streaming
|
|
84
|
+
holds, 3a1dae805c + 9a6c8db0b2 variable-chain guard, 60d3f61803
|
|
85
|
+
`singleDollarAfter`). 0.19.0 supersedes it.
|
|
8
86
|
|
|
9
87
|
## 0.18.4
|
|
10
88
|
|
|
@@ -12,7 +90,7 @@ Automatic changed-only republish (docs/metadata drift since the last tag — see
|
|
|
12
90
|
`git diff npm/content-ir-core/v0.18.3..npm/content-ir-core/v0.18.4 -- apps/shared/content-ir-core`).
|
|
13
91
|
No source changes intended and no consumer action required.
|
|
14
92
|
|
|
15
|
-
##
|
|
93
|
+
## Shipped in 0.18.4 (listed here before the release)
|
|
16
94
|
|
|
17
95
|
**`/source`: THE escaped-bracket rule** (verify-RC-B4 R3-2). `\[ … \]` is a
|
|
18
96
|
display-math island only when its content is real TeX — a command, `^ { } =
|
|
@@ -897,3 +975,4 @@ unchanged. Two things are worth doing at your next working session:
|
|
|
897
975
|
- Promoted the existing verified Content IR twin into an independently installable public package.
|
|
898
976
|
- Added ESM and declaration builds, package linting, packed-artifact analysis, and an isolated consumer canary.
|
|
899
977
|
- Preserved all parser, session, normalization, schema conversion, and structural-validation behavior with the existing shared fixtures and fingerprint vectors.
|
|
978
|
+
|
package/README.md
CHANGED
|
@@ -15,7 +15,7 @@ The root export is the full surface and always will be. Since 0.10.0 each intern
|
|
|
15
15
|
| `@ai-matrx/content-ir` | everything below |
|
|
16
16
|
| `@ai-matrx/content-ir/core` | IR types + tree, JSON tokenizer, kind parser, snapshots, normalize, fingerprint, discriminator, envelope read/value/cache, region, schema structure |
|
|
17
17
|
| `@ai-matrx/content-ir/session` | `ParseSession` and the one-writer-per-identity registry |
|
|
18
|
-
| `@ai-matrx/content-ir/registry` | kind registry types, storage transform, the dual gate |
|
|
18
|
+
| `@ai-matrx/content-ir/registry` | kind registry types, storage transform, the dual gate, `createKindValidator` over a host `SchemaSourcePort` |
|
|
19
19
|
| `@ai-matrx/content-ir/convert` | kind ⇄ JSON Schema, the OpenAI structured-output converter |
|
|
20
20
|
| `@ai-matrx/content-ir/wire` | partial-kind events, runtime wrapper readers, emit payload |
|
|
21
21
|
| `@ai-matrx/content-ir/directives` | Kind Directives: slug grammar, the ONE decoder |
|
|
@@ -48,7 +48,9 @@ The package has no browser, React runtime, application store, database client, r
|
|
|
48
48
|
`tokenizeSource(text)` reads stored text; `tokenizeSource(prefix, { streaming: true })`
|
|
49
49
|
reads a message still arriving. In streaming mode anything the next bytes could
|
|
50
50
|
still change is held back as plain prose (an open last line, an unclosed code
|
|
51
|
-
span, a `$$` that can still pair, a footnote whose continuation is undecided,
|
|
51
|
+
span, a `$$` that can still pair, a footnote whose continuation is undecided,
|
|
52
|
+
lines a box-drawing tree still forming below may claim as its root label, a
|
|
53
|
+
paragraph cut short by a block that may still dissolve, …),
|
|
52
54
|
so **an island reported `complete` is the island the finished text has** — with
|
|
53
55
|
ONE documented exception:
|
|
54
56
|
|
package/dist/index.cjs
CHANGED
|
@@ -2968,6 +2968,93 @@ function describeDualGateFailure(kind, result) {
|
|
|
2968
2968
|
return `kind "${kind}" failed the dual gate \u2014 ${parts.join(" | ")}`;
|
|
2969
2969
|
}
|
|
2970
2970
|
|
|
2971
|
+
// registry/kind-validator.ts
|
|
2972
|
+
var SCHEMA_COMPILE_FAILURE_PREFIX = "emitted_json_schema failed to compile:";
|
|
2973
|
+
var DEFAULT_CACHE_TTL_MS = 3e4;
|
|
2974
|
+
function unavailableMessage(kind, reason, detail) {
|
|
2975
|
+
switch (reason) {
|
|
2976
|
+
case "kind_not_registered":
|
|
2977
|
+
return `kind "${kind}" is not registered in content_ir.kind_definition`;
|
|
2978
|
+
case "schema_unavailable":
|
|
2979
|
+
return `kind "${kind}" has no emitted_json_schema \u2014 nothing to validate against`;
|
|
2980
|
+
case "schema_invalid":
|
|
2981
|
+
return `kind "${kind}" has an uncompilable emitted_json_schema${detail ? `: ${detail}` : ""}`;
|
|
2982
|
+
case "catalog_unreachable":
|
|
2983
|
+
return `could not read the contract for kind "${kind}"${detail ? `: ${detail}` : ""}`;
|
|
2984
|
+
}
|
|
2985
|
+
}
|
|
2986
|
+
function degraded(kind, reason, detail) {
|
|
2987
|
+
return {
|
|
2988
|
+
kind,
|
|
2989
|
+
checked: false,
|
|
2990
|
+
ok: false,
|
|
2991
|
+
errors: [unavailableMessage(kind, reason, detail)],
|
|
2992
|
+
degradedReason: reason
|
|
2993
|
+
};
|
|
2994
|
+
}
|
|
2995
|
+
function createKindValidator(source, options = {}) {
|
|
2996
|
+
const ttl = options.cacheTtlMs ?? DEFAULT_CACHE_TTL_MS;
|
|
2997
|
+
const now = options.now ?? Date.now;
|
|
2998
|
+
const cache = /* @__PURE__ */ new Map();
|
|
2999
|
+
async function fetchSchema(kind, signal) {
|
|
3000
|
+
const cached = cache.get(kind);
|
|
3001
|
+
if (cached && now() - cached.fetchedAt <= ttl) {
|
|
3002
|
+
return { schema: cached.schema, version: cached.version };
|
|
3003
|
+
}
|
|
3004
|
+
let answer;
|
|
3005
|
+
try {
|
|
3006
|
+
answer = await source.kindSchema(kind, signal);
|
|
3007
|
+
} catch (err) {
|
|
3008
|
+
return {
|
|
3009
|
+
unavailable: "catalog_unreachable",
|
|
3010
|
+
detail: err instanceof Error ? err.message : String(err)
|
|
3011
|
+
};
|
|
3012
|
+
}
|
|
3013
|
+
if ("unavailable" in answer) return { unavailable: answer.unavailable };
|
|
3014
|
+
if (answer.schema == null) {
|
|
3015
|
+
return { unavailable: "schema_unavailable" };
|
|
3016
|
+
}
|
|
3017
|
+
cache.set(kind, {
|
|
3018
|
+
schema: answer.schema,
|
|
3019
|
+
version: answer.version,
|
|
3020
|
+
fetchedAt: now()
|
|
3021
|
+
});
|
|
3022
|
+
return answer;
|
|
3023
|
+
}
|
|
3024
|
+
return {
|
|
3025
|
+
async validate(value, kind, signal = new AbortController().signal) {
|
|
3026
|
+
if (!kind || typeof kind !== "string") {
|
|
3027
|
+
throw new TypeError(
|
|
3028
|
+
`validateAgainstKind: "kind" must be a non-empty slug (received ${typeof kind})`
|
|
3029
|
+
);
|
|
3030
|
+
}
|
|
3031
|
+
const fetched = await fetchSchema(kind, signal);
|
|
3032
|
+
if ("unavailable" in fetched) {
|
|
3033
|
+
return degraded(kind, fetched.unavailable, fetched.detail);
|
|
3034
|
+
}
|
|
3035
|
+
const leg = validateStructuralLeg(value, fetched.schema);
|
|
3036
|
+
if (!leg.ok && leg.detail?.startsWith(SCHEMA_COMPILE_FAILURE_PREFIX)) {
|
|
3037
|
+
return degraded(kind, "schema_invalid", leg.detail);
|
|
3038
|
+
}
|
|
3039
|
+
return {
|
|
3040
|
+
kind,
|
|
3041
|
+
checked: true,
|
|
3042
|
+
ok: leg.ok,
|
|
3043
|
+
errors: leg.ok ? [] : [leg.detail ?? "validation failed"],
|
|
3044
|
+
degradedReason: null
|
|
3045
|
+
};
|
|
3046
|
+
},
|
|
3047
|
+
async cachedSchema(kind, signal = new AbortController().signal) {
|
|
3048
|
+
const fetched = await fetchSchema(kind, signal);
|
|
3049
|
+
return "unavailable" in fetched ? null : fetched.schema;
|
|
3050
|
+
},
|
|
3051
|
+
invalidate(kind) {
|
|
3052
|
+
if (kind) cache.delete(kind);
|
|
3053
|
+
else cache.clear();
|
|
3054
|
+
}
|
|
3055
|
+
};
|
|
3056
|
+
}
|
|
3057
|
+
|
|
2971
3058
|
// convert/openai-schema-converter.ts
|
|
2972
3059
|
function requiredNullableFlags(required, nullable) {
|
|
2973
3060
|
return {
|
|
@@ -5243,12 +5330,6 @@ function fenceOpenerOf(content) {
|
|
|
5243
5330
|
return null;
|
|
5244
5331
|
}
|
|
5245
5332
|
var FenceReader = class {
|
|
5246
|
-
constructor(opener, options = {}) {
|
|
5247
|
-
this.opener = opener;
|
|
5248
|
-
this.isJson = opener.char === "`" && opener.lang === "json";
|
|
5249
|
-
this.nesting = opener.char === "`" && (options.nesting ?? fenceNestsInnerFences(opener.lang));
|
|
5250
|
-
this.tildeCloser = opener.char === "~" ? new RegExp(`^ {0,3}~{${opener.ticks},}[ \\t]*$`) : null;
|
|
5251
|
-
}
|
|
5252
5333
|
opener;
|
|
5253
5334
|
state = { inString: false, escaped: false };
|
|
5254
5335
|
depth = 0;
|
|
@@ -5257,6 +5338,12 @@ var FenceReader = class {
|
|
|
5257
5338
|
isJson;
|
|
5258
5339
|
nesting;
|
|
5259
5340
|
tildeCloser;
|
|
5341
|
+
constructor(opener, options = {}) {
|
|
5342
|
+
this.opener = opener;
|
|
5343
|
+
this.isJson = opener.char === "`" && opener.lang === "json";
|
|
5344
|
+
this.nesting = opener.char === "`" && (options.nesting ?? fenceNestsInnerFences(opener.lang));
|
|
5345
|
+
this.tildeCloser = opener.char === "~" ? new RegExp(`^ {0,3}~{${opener.ticks},}[ \\t]*$`) : null;
|
|
5346
|
+
}
|
|
5260
5347
|
/** How many nested fences are open right now. */
|
|
5261
5348
|
get nestedDepth() {
|
|
5262
5349
|
return this.depth;
|
|
@@ -5874,9 +5961,6 @@ function isEscapedBacktick(line, offset) {
|
|
|
5874
5961
|
return slashes % 2 === 1;
|
|
5875
5962
|
}
|
|
5876
5963
|
var XmlContainerTracker = class {
|
|
5877
|
-
constructor(rootTag) {
|
|
5878
|
-
this.rootTag = rootTag;
|
|
5879
|
-
}
|
|
5880
5964
|
rootTag;
|
|
5881
5965
|
depth = 0;
|
|
5882
5966
|
inComment = false;
|
|
@@ -5884,6 +5968,9 @@ var XmlContainerTracker = class {
|
|
|
5884
5968
|
inlineTicks = 0;
|
|
5885
5969
|
/** The open fence, read by THE one closer (source/code-ranges.ts). */
|
|
5886
5970
|
fence = null;
|
|
5971
|
+
constructor(rootTag) {
|
|
5972
|
+
this.rootTag = rootTag;
|
|
5973
|
+
}
|
|
5887
5974
|
consumeLine(line, startOffset = 0) {
|
|
5888
5975
|
if (this.fence) {
|
|
5889
5976
|
if (this.fence.feed(line)) this.fence = null;
|
|
@@ -6095,6 +6182,23 @@ function isSplitterTreeLine(line) {
|
|
|
6095
6182
|
}
|
|
6096
6183
|
var MEDIA_REF_PREFIXES = ["[Image URL:", "[Video URL:", "[Audio URL:"];
|
|
6097
6184
|
var Tokenizer = class {
|
|
6185
|
+
text;
|
|
6186
|
+
streaming;
|
|
6187
|
+
meter;
|
|
6188
|
+
n;
|
|
6189
|
+
lineStarts = [];
|
|
6190
|
+
contentEnds = [];
|
|
6191
|
+
lineEnds = [];
|
|
6192
|
+
blocks = [];
|
|
6193
|
+
blockCache = /* @__PURE__ */ new Map();
|
|
6194
|
+
/** Lazily built: for line i, first line j >= i where brace net from i drops to <= 0. */
|
|
6195
|
+
braceStop = null;
|
|
6196
|
+
braceNet = null;
|
|
6197
|
+
/** Every occurrence offset of each needle searched so far (overlapping, like indexOf). */
|
|
6198
|
+
occurrences = /* @__PURE__ */ new Map();
|
|
6199
|
+
/** `$$` opener → closer for the pairs the core renders as math. */
|
|
6200
|
+
mathPairs;
|
|
6201
|
+
finder = (needle, from) => this.next(needle, from);
|
|
6098
6202
|
constructor(text, streaming = false, meter = void 0) {
|
|
6099
6203
|
this.text = text;
|
|
6100
6204
|
this.streaming = streaming;
|
|
@@ -6141,23 +6245,6 @@ var Tokenizer = class {
|
|
|
6141
6245
|
this.lineEnds.push(text.length);
|
|
6142
6246
|
}
|
|
6143
6247
|
}
|
|
6144
|
-
text;
|
|
6145
|
-
streaming;
|
|
6146
|
-
meter;
|
|
6147
|
-
n;
|
|
6148
|
-
lineStarts = [];
|
|
6149
|
-
contentEnds = [];
|
|
6150
|
-
lineEnds = [];
|
|
6151
|
-
blocks = [];
|
|
6152
|
-
blockCache = /* @__PURE__ */ new Map();
|
|
6153
|
-
/** Lazily built: for line i, first line j >= i where brace net from i drops to <= 0. */
|
|
6154
|
-
braceStop = null;
|
|
6155
|
-
braceNet = null;
|
|
6156
|
-
/** Every occurrence offset of each needle searched so far (overlapping, like indexOf). */
|
|
6157
|
-
occurrences = /* @__PURE__ */ new Map();
|
|
6158
|
-
/** `$$` opener → closer for the pairs the core renders as math. */
|
|
6159
|
-
mathPairs;
|
|
6160
|
-
finder = (needle, from) => this.next(needle, from);
|
|
6161
6248
|
/** Streaming: where provisional text starts (a pending `$$`), or +∞. */
|
|
6162
6249
|
provisionalFrom = Number.POSITIVE_INFINITY;
|
|
6163
6250
|
/** Streaming: the text ends in a lone CR whose LF has not arrived yet. */
|
|
@@ -6214,10 +6301,13 @@ var Tokenizer = class {
|
|
|
6214
6301
|
/**
|
|
6215
6302
|
* Streaming: an open block that may still turn back into prose — a `$$`
|
|
6216
6303
|
* pair whose closer is on the open last line (prose after it makes it
|
|
6217
|
-
* inline),
|
|
6304
|
+
* inline), a JSON head whose braces have not balanced (it may never parse),
|
|
6305
|
+
* or any opener that is itself still arriving on the open last line.
|
|
6218
6306
|
*/
|
|
6219
6307
|
mayDissolve(block) {
|
|
6220
|
-
|
|
6308
|
+
if (!this.streaming || block.complete) return false;
|
|
6309
|
+
if (block.type === "math_block" || block.type === "json") return true;
|
|
6310
|
+
return this.isOpenTail(this.lineOf(block.start));
|
|
6221
6311
|
}
|
|
6222
6312
|
/** Streaming: paragraph ends where an undecided, dissolvable block cut them short. */
|
|
6223
6313
|
undecidedBreaks = /* @__PURE__ */ new Set();
|
|
@@ -6238,6 +6328,12 @@ var Tokenizer = class {
|
|
|
6238
6328
|
}
|
|
6239
6329
|
return lo;
|
|
6240
6330
|
}
|
|
6331
|
+
/** How many whitespace characters open the text at `from` (up to `to`). */
|
|
6332
|
+
leadingSpace(from, to) {
|
|
6333
|
+
let k = from;
|
|
6334
|
+
while (k < to && /\s/.test(this.text[k])) k++;
|
|
6335
|
+
return k - from;
|
|
6336
|
+
}
|
|
6241
6337
|
isBlank(from, to) {
|
|
6242
6338
|
return !NOT_WHITESPACE.test(this.text.slice(from, to));
|
|
6243
6339
|
}
|
|
@@ -6262,6 +6358,10 @@ var Tokenizer = class {
|
|
|
6262
6358
|
break;
|
|
6263
6359
|
}
|
|
6264
6360
|
const island = this.detectBlock(pos, li, true);
|
|
6361
|
+
if (island && pos + this.leadingSpace(pos, this.contentEnds[li]) >= this.provisionalFrom) {
|
|
6362
|
+
this.push({ kind: "prose", start: pos, end: this.n, island: null, inlines: [] });
|
|
6363
|
+
break;
|
|
6364
|
+
}
|
|
6265
6365
|
if (island) {
|
|
6266
6366
|
this.push({ kind: "island", start: island.start, end: island.end, island, inlines: [] });
|
|
6267
6367
|
pos = island.end;
|
|
@@ -6291,12 +6391,6 @@ var Tokenizer = class {
|
|
|
6291
6391
|
const last = this.lineStarts.length - 1;
|
|
6292
6392
|
if (last < 0) return;
|
|
6293
6393
|
if (this.isOpenTail(last)) {
|
|
6294
|
-
const lineStart = this.lineStarts[last];
|
|
6295
|
-
for (const block2 of this.blocks) {
|
|
6296
|
-
if (block2.kind === "prose" && block2.end > lineStart) {
|
|
6297
|
-
block2.inlines = block2.inlines.filter((inline) => inline.end <= lineStart);
|
|
6298
|
-
}
|
|
6299
|
-
}
|
|
6300
6394
|
let b = this.blocks.length - 1;
|
|
6301
6395
|
while (b >= 0 && this.blocks[b].kind === "gap") b--;
|
|
6302
6396
|
const block = this.blocks[b];
|
|
@@ -6322,36 +6416,55 @@ var Tokenizer = class {
|
|
|
6322
6416
|
* region…) is never a tree line nor a root label, so it stops the walk.
|
|
6323
6417
|
*/
|
|
6324
6418
|
treeRootHoldFrom() {
|
|
6325
|
-
const
|
|
6419
|
+
const claimFrom = (k2) => {
|
|
6420
|
+
const lineStart = this.lineStarts[k2];
|
|
6421
|
+
const lineEnd = this.contentEnds[k2];
|
|
6422
|
+
let pos = lineStart;
|
|
6326
6423
|
for (; ; ) {
|
|
6327
|
-
const block = this.blockAt(
|
|
6328
|
-
if (!block || block.kind !== "island" || block.island
|
|
6329
|
-
|
|
6330
|
-
if (
|
|
6331
|
-
|
|
6424
|
+
const block = this.blockAt(pos);
|
|
6425
|
+
if (!block || block.kind !== "island" || !block.island) return pos;
|
|
6426
|
+
if (block.island.fromParagraph === true || this.mayDissolve(block.island)) return pos;
|
|
6427
|
+
if (block.start < lineStart) {
|
|
6428
|
+
const opened = this.lineOf(block.start);
|
|
6429
|
+
if (this.lineStarts[opened] !== block.start && claimFrom(opened) !== -1) return pos;
|
|
6430
|
+
if (block.end >= lineEnd) return -1;
|
|
6431
|
+
pos = block.end;
|
|
6432
|
+
continue;
|
|
6433
|
+
}
|
|
6434
|
+
if (block.end >= lineEnd) return -1;
|
|
6435
|
+
pos = block.end;
|
|
6332
6436
|
}
|
|
6333
6437
|
};
|
|
6334
6438
|
let k = this.lineCount - 1;
|
|
6335
6439
|
let start = this.lineCount;
|
|
6440
|
+
let startPos = this.n;
|
|
6336
6441
|
if (k >= 0 && this.isOpenTail(k)) {
|
|
6337
|
-
|
|
6442
|
+
const at = claimFrom(k);
|
|
6443
|
+
if (at === -1) return this.n;
|
|
6338
6444
|
start = k;
|
|
6445
|
+
startPos = at;
|
|
6339
6446
|
k--;
|
|
6340
6447
|
}
|
|
6341
|
-
for (; k >= 0
|
|
6448
|
+
for (; k >= 0; k--) {
|
|
6342
6449
|
this.tick(1);
|
|
6450
|
+
const at = claimFrom(k);
|
|
6451
|
+
if (at === -1) break;
|
|
6343
6452
|
if (this.lineIsBlank(k)) continue;
|
|
6344
6453
|
if (!TREE_GLYPHS.test(this.lineText(k))) break;
|
|
6345
6454
|
start = k;
|
|
6455
|
+
startPos = at;
|
|
6346
6456
|
}
|
|
6347
|
-
let
|
|
6348
|
-
for (let j = start - 1; j >= 0
|
|
6457
|
+
let rootPos = startPos;
|
|
6458
|
+
for (let j = start - 1; j >= 0; j--) {
|
|
6459
|
+
const at = claimFrom(j);
|
|
6460
|
+
if (at === -1) break;
|
|
6349
6461
|
const trimmed = this.lineText(j).trim();
|
|
6350
6462
|
this.tick(trimmed.length + 1);
|
|
6351
6463
|
if (!trimmed || isSplitterTreeLine(trimmed) || /^#{1,6}\s/.test(trimmed)) break;
|
|
6352
|
-
|
|
6464
|
+
rootPos = at;
|
|
6465
|
+
if (at > this.lineStarts[j]) break;
|
|
6353
6466
|
}
|
|
6354
|
-
return
|
|
6467
|
+
return rootPos;
|
|
6355
6468
|
}
|
|
6356
6469
|
/** The block (so far) containing offset `pos`, or null. */
|
|
6357
6470
|
blockAt(pos) {
|
|
@@ -6883,7 +6996,9 @@ var Tokenizer = class {
|
|
|
6883
6996
|
const block = this.detectBlock(this.lineStarts[k], k, false);
|
|
6884
6997
|
if (block) {
|
|
6885
6998
|
breakLine = k;
|
|
6886
|
-
if (this.mayDissolve(block)
|
|
6999
|
+
if (this.mayDissolve(block) || this.streaming && block.start >= this.provisionalFrom) {
|
|
7000
|
+
this.undecidedBreaks.add(end);
|
|
7001
|
+
}
|
|
6887
7002
|
break;
|
|
6888
7003
|
}
|
|
6889
7004
|
end = this.contentEnds[k];
|
|
@@ -7121,6 +7236,10 @@ var Tokenizer = class {
|
|
|
7121
7236
|
const closer = `</${name}>`;
|
|
7122
7237
|
const close = this.next(closer, tagEnd);
|
|
7123
7238
|
const blockType = isAttr ? "xml_attr" : "xml_region";
|
|
7239
|
+
if (close === -1 && isThinking && !this.streaming) {
|
|
7240
|
+
i = tagEnd;
|
|
7241
|
+
continue;
|
|
7242
|
+
}
|
|
7124
7243
|
if (close === -1) return stop(this.island(blockType, i, this.n, false, { tag: name }));
|
|
7125
7244
|
const end = close + closer.length;
|
|
7126
7245
|
if (end > pe) return stop(this.spill(blockType, i, end, pe, { tag: name }));
|
|
@@ -7224,12 +7343,12 @@ function blockAt(blocks, offset) {
|
|
|
7224
7343
|
|
|
7225
7344
|
// source/splice.ts
|
|
7226
7345
|
var SourceSpliceError = class extends Error {
|
|
7346
|
+
code;
|
|
7227
7347
|
constructor(code, message) {
|
|
7228
7348
|
super(message);
|
|
7229
7349
|
this.code = code;
|
|
7230
7350
|
this.name = "SourceSpliceError";
|
|
7231
7351
|
}
|
|
7232
|
-
code;
|
|
7233
7352
|
};
|
|
7234
7353
|
function blockEdit(block, text) {
|
|
7235
7354
|
return { start: block.start, end: block.end, text };
|
|
@@ -7561,6 +7680,7 @@ exports.compareWithExistingKindSchema = compareWithExistingKindSchema;
|
|
|
7561
7680
|
exports.convertAiSchemaToBlockFields = convertAiSchemaToBlockFields;
|
|
7562
7681
|
exports.createFingerprinter = createFingerprinter;
|
|
7563
7682
|
exports.createKindStreamParser = createKindStreamParser;
|
|
7683
|
+
exports.createKindValidator = createKindValidator;
|
|
7564
7684
|
exports.decodeDirective = decodeDirective;
|
|
7565
7685
|
exports.describeDualGateFailure = describeDualGateFailure;
|
|
7566
7686
|
exports.directiveDisplay = directiveDisplay;
|