@pipedog/sentencex-wasm 0.0.0 → 0.2.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/LICENSE +25 -0
- package/LICENSE-sentencex +21 -0
- package/NOTICE +9 -0
- package/README.md +129 -2
- package/THIRD_PARTY_LICENSES.md +1060 -0
- package/dist/api.d.ts +11 -0
- package/dist/api.js +24 -0
- package/dist/engine.d.ts +42 -0
- package/dist/engine.js +105 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +49 -0
- package/dist/wasm_bytes.d.ts +5 -0
- package/dist/wasm_bytes.js +9 -0
- package/dist/workerd.d.ts +13 -0
- package/dist/workerd.js +26 -0
- package/package.json +48 -3
- package/rust/Cargo.lock +312 -0
- package/rust/Cargo.toml +27 -0
- package/rust/src/lib.rs +195 -0
- package/wasm/sentencex.wasm +0 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
BSD 2-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024–2026, Daily
|
|
4
|
+
Copyright (c) 2026, pipedog contributors
|
|
5
|
+
|
|
6
|
+
Redistribution and use in source and binary forms, with or without
|
|
7
|
+
modification, are permitted provided that the following conditions are met:
|
|
8
|
+
|
|
9
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
10
|
+
list of conditions and the following disclaimer.
|
|
11
|
+
|
|
12
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
13
|
+
this list of conditions and the following disclaimer in the documentation
|
|
14
|
+
and/or other materials provided with the distribution.
|
|
15
|
+
|
|
16
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
17
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
18
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
19
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
20
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
21
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
22
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
23
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
24
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
25
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Santhosh Thottingal
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/NOTICE
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
pipedog is an unofficial TypeScript port of Pipecat (https://github.com/pipecat-ai/pipecat),
|
|
2
|
+
a framework for voice and multimodal conversational AI created by Daily.
|
|
3
|
+
|
|
4
|
+
It is not affiliated with or endorsed by Daily. Pipecat is distributed under
|
|
5
|
+
the BSD 2-Clause License, Copyright (c) 2024–2026, Daily; that copyright notice
|
|
6
|
+
and license are retained in LICENSE, and every ported source file carries a
|
|
7
|
+
header naming the upstream file it was derived from.
|
|
8
|
+
|
|
9
|
+
The upstream commit each file was ported from is recorded in porting/manifest.json.
|
package/README.md
CHANGED
|
@@ -1,5 +1,132 @@
|
|
|
1
1
|
# @pipedog/sentencex-wasm
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Sentence segmentation for pipedog with [sentencex](https://github.com/wikimedia/sentencex)
|
|
4
|
+
(Wikimedia, MIT), compiled to WebAssembly. Upstream Pipecat splits sentences with
|
|
5
|
+
`sentencex==1.0.32` (`pipecat/utils/string.py`). This package runs the same Rust core, so
|
|
6
|
+
pipedog gets the same sentence boundaries as upstream on every runtime.
|
|
4
7
|
|
|
5
|
-
|
|
8
|
+
- The `sentencex` npm package is a native Node addon with per-platform binaries. It cannot run
|
|
9
|
+
on Deno or Cloudflare Workers. This package can.
|
|
10
|
+
- The Rust crate is [`sentencex` 0.1.32](https://crates.io/crates/sentencex/0.1.32) from
|
|
11
|
+
crates.io, pinned with `=0.1.32`. It is the same release as the Python and npm `1.0.32`: git
|
|
12
|
+
tag `v1.0.32`, commit `268ff94` (the crate's `.cargo_vcs_info.json`). Its `src/` is
|
|
13
|
+
byte-identical to the tag. Nothing is vendored or patched. `rust/Cargo.lock` pins `regex`,
|
|
14
|
+
`regex-automata`, `regex-syntax`, `aho-corasick`, `memchr` and `rustc-hash` to the versions in
|
|
15
|
+
sentencex's own `Cargo.lock` at that tag.
|
|
16
|
+
- No wasm-bindgen and no generated JS glue. The module has **zero imports** and a small C ABI
|
|
17
|
+
(`rust/src/lib.rs`). `src/engine.ts` wraps the `WebAssembly.Instance` exports by hand.
|
|
18
|
+
- `wasm/sentencex.wasm` is a committed build artifact of **1,054,520 bytes** (353 KB gzipped).
|
|
19
|
+
It is built with cargo `opt-level = "z"`, LTO, `panic = "abort"`, then `wasm-opt -Oz`.
|
|
20
|
+
Most of the size comes from the `regex` crate, which sentencex depends on with default
|
|
21
|
+
features: about 350 KB of regex engine code and its Unicode tables. The language rules
|
|
22
|
+
themselves (abbreviation lists etc.) are a smaller part.
|
|
23
|
+
- The API is **synchronous**, including the first call. Verified on Bun, Node 24, Deno 2 and
|
|
24
|
+
workerd (Cloudflare Workers).
|
|
25
|
+
- Output is identical to the native `sentencex@1.0.32` addon on more than 67,000
|
|
26
|
+
(language, text) inputs (`test/parity.test.ts`).
|
|
27
|
+
|
|
28
|
+
## Usage
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
import { getSentenceBoundaries, segment } from "@pipedog/sentencex-wasm";
|
|
32
|
+
|
|
33
|
+
segment("en", "Dr. Smith arrived at 5 p.m. He was late.");
|
|
34
|
+
// ["Dr. Smith arrived at 5 p.m. ", "He was late."]
|
|
35
|
+
|
|
36
|
+
getSentenceBoundaries("en", "Hi 👋. Bye.");
|
|
37
|
+
// [
|
|
38
|
+
// { start_index: 0, end_index: 7, text: "Hi 👋. ", boundary_symbol: ".", is_paragraph_break: false },
|
|
39
|
+
// { start_index: 7, end_index: 11, text: "Bye.", boundary_symbol: ".", is_paragraph_break: false },
|
|
40
|
+
// ]
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
- `segment(language, text): string[]` returns the same result as sentencex's `segment`.
|
|
44
|
+
Joining the result gives back `text`, including whitespace and `"\n\n"` paragraph spans.
|
|
45
|
+
- `getSentenceBoundaries(language, text): SentenceBoundary[]` returns the same shape as the npm
|
|
46
|
+
package's `get_sentence_boundaries` typings (`start_index`, `end_index`, `text`,
|
|
47
|
+
`boundary_symbol`, `is_paragraph_break`). `get_sentence_boundaries` is exported as an alias.
|
|
48
|
+
- **Indices are JavaScript string (UTF-16) indices**, so
|
|
49
|
+
`text.slice(b.start_index, b.end_index) === b.text`. sentencex itself returns Unicode scalar
|
|
50
|
+
(code point) indices. The npm and Python packages pass those through unchanged, so they do not
|
|
51
|
+
match JS string positions after an emoji or any other astral character. The conversion
|
|
52
|
+
happens in Rust (`Utf16Cursor`). The parity test converts the native indices the same way
|
|
53
|
+
before it compares them.
|
|
54
|
+
- `language` is a code such as `"en"`, `"de"` or `"ja"`. Codes that sentencex does not know go
|
|
55
|
+
through its fallback map and end at English, just as in the native package. Upstream
|
|
56
|
+
normalizes codes first (`resolve_sentence_tokenizer_language`: lowercase, strip the region).
|
|
57
|
+
That is core's job, not this package's.
|
|
58
|
+
- Lone surrogates: sentencex sees them as U+FFFD, as the native addon does, and the boundaries
|
|
59
|
+
are the same. The returned strings are slices of your input, so the original code units are
|
|
60
|
+
kept.
|
|
61
|
+
- `initSentencex(source?)` compiles and instantiates now instead of on the first call.
|
|
62
|
+
`source` is a `WebAssembly.Module` or wasm bytes (bytes do not work on workerd), and it
|
|
63
|
+
replaces the bundled module. `getSentencex()` returns the shared `SentencexEngine`.
|
|
64
|
+
`new SentencexEngine(module)` creates a separate instance.
|
|
65
|
+
- Cost: the first call takes about 10 to 12 ms (compiling the module and sentencex's regexes
|
|
66
|
+
for English). Later calls take about 7 to 10 µs for a short sentence (Bun, Node, Deno on
|
|
67
|
+
Apple Silicon). The first call for another language compiles that language's regexes, about
|
|
68
|
+
1.5 ms.
|
|
69
|
+
|
|
70
|
+
## Loading on each runtime
|
|
71
|
+
|
|
72
|
+
`package.json` `"exports"["."]` uses export conditions:
|
|
73
|
+
|
|
74
|
+
| condition | entry | how the module gets loaded |
|
|
75
|
+
|---|---|---|
|
|
76
|
+
| `workerd` | `src/workerd.ts` | `import wasm from "../wasm/sentencex.wasm"` gives a compiled `WebAssembly.Module` (wrangler's default `.wasm` rule). It is instantiated synchronously, with `new WebAssembly.Instance(module, {})`, when the entry module loads. workerd refuses to compile WASM from bytes. |
|
|
77
|
+
| `default` | `src/index.ts` | The bytes are embedded as base64 in the generated `src/wasm_bytes.ts`. On the first call they are decoded (`Uint8Array.fromBase64` when available, else `atob`) and compiled with `new WebAssembly.Module(bytes)`, then `new WebAssembly.Instance`. There is no file access and no `fetch`, so it works the same in bundles. |
|
|
78
|
+
|
|
79
|
+
Synchronous compilation of a module this size works on Bun, Node 24 and Deno 2; the compat
|
|
80
|
+
harness checks all three. Browsers can refuse to compile large modules synchronously on the
|
|
81
|
+
main thread. For a browser, first run `initSentencex(await WebAssembly.compile(bytes))` with the
|
|
82
|
+
bytes of `./sentencex.wasm`. Browsers are not tested.
|
|
83
|
+
|
|
84
|
+
The embedded base64 adds about 1.4 MB to the default entry's JavaScript. On workerd only the
|
|
85
|
+
`.wasm` module is bundled.
|
|
86
|
+
|
|
87
|
+
## C ABI (for reference)
|
|
88
|
+
|
|
89
|
+
| export | |
|
|
90
|
+
|---|---|
|
|
91
|
+
| `sx_alloc(len) -> ptr`, `sx_free(ptr, len)` | raw linear-memory blocks |
|
|
92
|
+
| `sx_segment(lang, lang_len, text, text_len) -> n` | `n` records of `[start16, end16]` at `sx_out()` |
|
|
93
|
+
| `sx_boundaries(lang, lang_len, text, text_len) -> n` | `n` records of `[start_index16, end_index16, text_start16, text_end16, symbol_ptr, symbol_len, is_paragraph_break]`, where `symbol_len = 0xFFFFFFFF` means no symbol |
|
|
94
|
+
| `sx_out() -> *u32` | records from the last call |
|
|
95
|
+
|
|
96
|
+
All values are u32. A negative count means invalid UTF-8 input. The JS side writes
|
|
97
|
+
`language` and `text` into one allocation with `TextEncoder.encodeInto`. It reads the records,
|
|
98
|
+
slices the original string, decodes the boundary symbols, and only then frees the buffer,
|
|
99
|
+
because symbols point into it. If the module traps (a Rust panic), the engine re-instantiates
|
|
100
|
+
it on the next call.
|
|
101
|
+
|
|
102
|
+
## Licensing
|
|
103
|
+
|
|
104
|
+
- Our code (`src/`, `rust/src/lib.rs`, scripts and tests) is BSD-2-Clause.
|
|
105
|
+
- `wasm/sentencex.wasm` and `src/wasm_bytes.ts` contain compiled sentencex (MIT, © 2025 Santhosh
|
|
106
|
+
Thottingal; see [`LICENSE-sentencex`](./LICENSE-sentencex)). They also contain its
|
|
107
|
+
dependencies `regex`, `regex-automata`, `regex-syntax` and `rustc-hash` (MIT OR Apache-2.0),
|
|
108
|
+
`aho-corasick` and `memchr` (Unlicense OR MIT), and Rust's `std` and `dlmalloc`
|
|
109
|
+
(MIT OR Apache-2.0). Each of these is available under MIT. That is why the package `license` is `BSD-2-Clause AND MIT`.
|
|
110
|
+
|
|
111
|
+
## Building and testing
|
|
112
|
+
|
|
113
|
+
```sh
|
|
114
|
+
rustup target add wasm32-unknown-unknown
|
|
115
|
+
bun run build:wasm # cargo + wasm-opt -Oz -> wasm/sentencex.wasm and src/wasm_bytes.ts; --no-opt to skip wasm-opt
|
|
116
|
+
bun test # unit tests + parity against the native sentencex@1.0.32 addon
|
|
117
|
+
bun scripts/extract-upstream-corpus.ts # refresh test/fixtures/upstream-corpus.json from .upstream
|
|
118
|
+
bun tools/compat/run.ts --package sentencex-wasm # from the repo root: bun, node, deno, workerd
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
The parity test needs the native `sentencex` devDependency, which has prebuilt binaries for
|
|
122
|
+
macOS, Linux x64/arm64 and Windows x64. Its corpus covers:
|
|
123
|
+
|
|
124
|
+
- every prefix of a hand-written corpus: abbreviations, initials, times, decimals, ellipses,
|
|
125
|
+
quotes, attributed questions and exclamations, URLs, emails, lists, paragraph breaks, emoji
|
|
126
|
+
and other astral characters, in en, es, fr, de, ja, zh, hi, ar, pt, pl, it and nl
|
|
127
|
+
- every string literal in upstream's `test_english_sentence_aggregation.py`,
|
|
128
|
+
`test_sentence_tokenizer_language.py`, `test_sentencex_tokenizer.py`,
|
|
129
|
+
`test_simple_text_aggregator.py` and `test_utils_string.py`, alone and joined with its
|
|
130
|
+
neighbour, in 12 languages and as streaming prefixes
|
|
131
|
+
- a text over 10 KB, which takes sentencex's paragraph-chunking path
|
|
132
|
+
- a mixed sample in every language code sentencex knows, plus unknown codes
|