redashify 0.1.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/README.md CHANGED
@@ -93,7 +93,7 @@ const result = await redashify(text, {
93
93
  | `model` | `string` | *(per provider)* | Model name. Required if no provider default. |
94
94
  | `baseURL` | `string` | — | Custom endpoint URL. Overrides provider mapping. |
95
95
  | `llm` | `(messages) => Promise<string>` | — | Custom LLM function. Overrides apiKey/provider/model. |
96
- | `rules` | `string` | `""` | Custom rules prepended to the system prompt |
96
+ | `rules` | `string` | `“”` | Custom rules prepended to the system prompt |
97
97
  | `contextSize` | `number` | `50` | Characters of context on each side of a dash |
98
98
  | `batchSize` | `number` | `50` | Maximum dashes per LLM call |
99
99
 
@@ -116,7 +116,7 @@ interface RedashifyResult {
116
116
 
117
117
  **No dashes in text:** LLM is not called. Returns immediately with `unchanged: true`.
118
118
 
119
- **All dashes already correct:** LLM is called (correctness can't be pre-judged), but `corrections` is empty and `unchanged` is `true`.
119
+ **All dashes already correct:** LLM is called (correctness cant be pre-judged), but `corrections` is empty and `unchanged` is `true`.
120
120
 
121
121
  ## Custom rules
122
122
 
@@ -147,7 +147,7 @@ Keep hyphens in compound modifiers and vote tallies (5-4).`,
147
147
 
148
148
  ## Design decisions
149
149
 
150
- **LLM over regex.** Dash correction depends on semantic context — is "10-20" a range (en dash) or a compound modifier (hyphen)? Is "--" an em dash or a typo? Regex can't answer these questions. An LLM can, given a few words of surrounding context.
150
+ **LLM over regex.** Dash correction depends on semantic context — is 10-20 a range (en dash) or a compound modifier (hyphen)? Is “--” an em dash or a typo? Regex cant answer these questions. An LLM can, given a few words of surrounding context.
151
151
 
152
152
  **Privacy by design.** Only short context windows around each dash are sent to the LLM — never the full document. A 10,000-word document with 5 dashes sends ~5 small context snippets, regardless of document length.
153
153
 
@@ -159,7 +159,7 @@ Keep hyphens in compound modifiers and vote tallies (5-4).`,
159
159
 
160
160
  ```sh
161
161
  npm install
162
- npm test # 57 tests
162
+ npm test
163
163
  npm run typecheck
164
164
  npm run build # ESM + CJS + .d.ts
165
165
  ```
package/dist/index.cjs CHANGED
@@ -1,9 +1,7 @@
1
1
  "use strict";
2
- var __create = Object.create;
3
2
  var __defProp = Object.defineProperty;
4
3
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
4
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
- var __getProtoOf = Object.getPrototypeOf;
7
5
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
6
  var __export = (target, all) => {
9
7
  for (var name in all)
@@ -17,14 +15,6 @@ var __copyProps = (to, from, except, desc) => {
17
15
  }
18
16
  return to;
19
17
  };
20
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
- // If the importer is in node compatibility mode or this is not an ESM
22
- // file that has been converted to a CommonJS file using a Babel-
23
- // compatible transform (i.e. "__esModule" has not been set), then set
24
- // "default" to the CommonJS "module.exports" for node compatibility.
25
- isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
- mod
27
- ));
28
18
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
19
 
30
20
  // src/index.ts
@@ -37,6 +27,9 @@ __export(index_exports, {
37
27
  });
38
28
  module.exports = __toCommonJS(index_exports);
39
29
 
30
+ // src/core.ts
31
+ var import_llm_client = require("@lexstyle/llm-client");
32
+
40
33
  // src/extractor.ts
41
34
  var DASH_RE = /---+|--|\u2014|\u2013|-/g;
42
35
  function extractDashes(text, contextSize = 50) {
@@ -91,83 +84,6 @@ Format: [{"id":0,"dash":"-"},{"id":1,"dash":"\u2013"}]`;
91
84
  ];
92
85
  }
93
86
 
94
- // src/llmClient.ts
95
- var import_openai = __toESM(require("openai"), 1);
96
- var PROVIDER_CONFIGS = {
97
- openai: { baseURL: "https://api.openai.com/v1", defaultModel: "gpt-4o-mini" },
98
- anthropic: { baseURL: "https://api.anthropic.com", defaultModel: "claude-haiku-4-5-20251001" },
99
- gemini: { baseURL: "https://generativelanguage.googleapis.com/v1beta/openai", defaultModel: "gemini-2.0-flash" },
100
- groq: { baseURL: "https://api.groq.com/openai/v1", defaultModel: "llama-3.3-70b-versatile" },
101
- together: { baseURL: "https://api.together.xyz/v1", defaultModel: "meta-llama/Meta-Llama-3.1-8B-Instruct-Turbo" },
102
- mistral: { baseURL: "https://api.mistral.ai/v1", defaultModel: "mistral-small-latest" },
103
- openrouter: { baseURL: "https://openrouter.ai/api/v1", defaultModel: "" },
104
- xai: { baseURL: "https://api.x.ai/v1", defaultModel: "grok-3-mini-fast" },
105
- deepseek: { baseURL: "https://api.deepseek.com", defaultModel: "deepseek-chat" }
106
- };
107
- function getProviderConfig(provider) {
108
- if (!(provider in PROVIDER_CONFIGS)) {
109
- const valid = Object.keys(PROVIDER_CONFIGS).join(", ");
110
- throw new Error(`Unknown provider "${provider}". Valid providers: ${valid}`);
111
- }
112
- return PROVIDER_CONFIGS[provider];
113
- }
114
- async function callAnthropic(messages, apiKey, model, baseURL) {
115
- const resolvedBaseURL = (baseURL ?? PROVIDER_CONFIGS.anthropic.baseURL).replace(/\/+$/, "").replace(/\/v1$/i, "");
116
- const url = `${resolvedBaseURL}/v1/messages`;
117
- const systemMsg = messages.find((m) => m.role === "system");
118
- const nonSystemMsgs = messages.filter((m) => m.role !== "system");
119
- const body = {
120
- model,
121
- max_tokens: 4096,
122
- messages: nonSystemMsgs.map((m) => ({ role: m.role, content: m.content }))
123
- };
124
- if (systemMsg) {
125
- body.system = systemMsg.content;
126
- }
127
- const res = await fetch(url, {
128
- method: "POST",
129
- headers: {
130
- "content-type": "application/json",
131
- "x-api-key": apiKey,
132
- "anthropic-version": "2023-06-01"
133
- },
134
- body: JSON.stringify(body)
135
- });
136
- if (!res.ok) {
137
- const text = await res.text();
138
- throw new Error(`Anthropic API error (${res.status}): ${text}`);
139
- }
140
- const data = await res.json();
141
- const textBlock = data.content?.find((b) => b.type === "text");
142
- if (!textBlock?.text) {
143
- throw new Error("Anthropic returned an empty response");
144
- }
145
- return textBlock.text;
146
- }
147
- async function callOpenAICompatible(messages, apiKey, model, baseURL) {
148
- const client = new import_openai.default({
149
- apiKey,
150
- ...baseURL ? { baseURL } : {}
151
- });
152
- const response = await client.chat.completions.create({
153
- model,
154
- messages,
155
- temperature: 0
156
- });
157
- const content = response.choices[0]?.message?.content;
158
- if (!content) {
159
- throw new Error("LLM returned an empty response");
160
- }
161
- return content;
162
- }
163
- async function callLLM(messages, apiKey, model, provider, baseURL) {
164
- if (provider === "anthropic") {
165
- return callAnthropic(messages, apiKey, model, baseURL);
166
- }
167
- const resolvedBaseURL = baseURL ?? (provider ? PROVIDER_CONFIGS[provider].baseURL : void 0);
168
- return callOpenAICompatible(messages, apiKey, model, resolvedBaseURL);
169
- }
170
-
171
87
  // src/types.ts
172
88
  var VALID_DASHES = Object.freeze(["-", "\u2013", "\u2014"]);
173
89
  var VALID_DASH_SET = new Set(VALID_DASHES);
@@ -278,24 +194,6 @@ function requireInt(value, name, min) {
278
194
  throw new Error(`Invalid ${name}: ${value}. Must be a finite integer >= ${min}.`);
279
195
  }
280
196
  }
281
- function resolveLlm(options) {
282
- if (options.llm) {
283
- if (typeof options.llm !== "function") {
284
- throw new Error("`llm` option must be a function");
285
- }
286
- return options.llm;
287
- }
288
- if (options.apiKey) {
289
- const providerConfig = options.provider ? getProviderConfig(options.provider) : void 0;
290
- const model = options.model ?? providerConfig?.defaultModel;
291
- if (!model) {
292
- throw new Error("redashify requires `model` when using `apiKey` (or use a `provider` with a default model)");
293
- }
294
- const { apiKey, provider, baseURL } = options;
295
- return (messages) => callLLM(messages, apiKey, model, provider, baseURL);
296
- }
297
- throw new Error("redashify requires either `apiKey` + `model`, `apiKey` + `provider`, or `llm` option");
298
- }
299
197
  function validateBatchIds(corrections, expectedIds) {
300
198
  for (const { id } of corrections) {
301
199
  if (!expectedIds.has(id)) {
@@ -316,7 +214,7 @@ async function redashify(text, options) {
316
214
  requireInt(batchSize, "batchSize", 1);
317
215
  const contextSize = options.contextSize ?? 50;
318
216
  requireInt(contextSize, "contextSize", 0);
319
- const llmFn = resolveLlm(options);
217
+ const llmFn = (0, import_llm_client.resolveLlm)(options, "redashify");
320
218
  const contexts = extractDashes(text, contextSize);
321
219
  if (contexts.length === 0) {
322
220
  return { text, corrections: [], unchanged: true };
package/dist/index.d.cts CHANGED
@@ -1,3 +1,6 @@
1
+ import { LlmOptions, Message } from '@lexstyle/llm-client';
2
+ export { Message, Provider } from '@lexstyle/llm-client';
3
+
1
4
  /** The valid dash characters, exported for documentation/reference */
2
5
  declare const VALID_DASHES: readonly ["-", "–", "—"];
3
6
  /** A single valid dash character: hyphen, en dash, or em dash */
@@ -42,25 +45,8 @@ interface RedashifyResult {
42
45
  /** True if no changes were needed */
43
46
  unchanged: boolean;
44
47
  }
45
- /** Chat message format for the LLM function */
46
- interface Message {
47
- role: "system" | "user" | "assistant";
48
- content: string;
49
- }
50
- /** Known providers with built-in base URL mapping */
51
- type Provider = "openai" | "anthropic" | "gemini" | "groq" | "together" | "mistral" | "openrouter" | "xai" | "deepseek";
52
48
  /** Options for redashify */
53
- interface RedashifyOptions {
54
- /** API key for the LLM provider */
55
- apiKey?: string;
56
- /** LLM provider name (e.g. "openai", "groq", "together"). Maps to a known base URL. */
57
- provider?: Provider;
58
- /** Model name (e.g. "gpt-4o-mini", "llama-3.3-70b-versatile"). Required when using apiKey without a known provider default. */
59
- model?: string;
60
- /** Custom base URL for OpenAI-compatible APIs. Overrides provider mapping. */
61
- baseURL?: string;
62
- /** Custom LLM function: receives messages, returns the raw text response. Overrides apiKey/provider/model. */
63
- llm?: (messages: Message[]) => Promise<string>;
49
+ interface RedashifyOptions extends LlmOptions {
64
50
  /** Characters of context on each side of a dash (default: 50) */
65
51
  contextSize?: number;
66
52
  /** Custom rules to prepend to the system prompt */
@@ -93,4 +79,4 @@ declare function extractDashes(text: string, contextSize?: number): DashContext[
93
79
  */
94
80
  declare function buildMessages(contexts: readonly DashContext[], rules?: string): Message[];
95
81
 
96
- export { type ApplyResult, type Correction, type DashChar, type DashContext, type Message, type Provider, type RedashifyOptions, type RedashifyResult, VALID_DASHES, buildMessages, extractDashes, redashify };
82
+ export { type ApplyResult, type Correction, type DashChar, type DashContext, type RedashifyOptions, type RedashifyResult, VALID_DASHES, buildMessages, extractDashes, redashify };
package/dist/index.d.ts CHANGED
@@ -1,3 +1,6 @@
1
+ import { LlmOptions, Message } from '@lexstyle/llm-client';
2
+ export { Message, Provider } from '@lexstyle/llm-client';
3
+
1
4
  /** The valid dash characters, exported for documentation/reference */
2
5
  declare const VALID_DASHES: readonly ["-", "–", "—"];
3
6
  /** A single valid dash character: hyphen, en dash, or em dash */
@@ -42,25 +45,8 @@ interface RedashifyResult {
42
45
  /** True if no changes were needed */
43
46
  unchanged: boolean;
44
47
  }
45
- /** Chat message format for the LLM function */
46
- interface Message {
47
- role: "system" | "user" | "assistant";
48
- content: string;
49
- }
50
- /** Known providers with built-in base URL mapping */
51
- type Provider = "openai" | "anthropic" | "gemini" | "groq" | "together" | "mistral" | "openrouter" | "xai" | "deepseek";
52
48
  /** Options for redashify */
53
- interface RedashifyOptions {
54
- /** API key for the LLM provider */
55
- apiKey?: string;
56
- /** LLM provider name (e.g. "openai", "groq", "together"). Maps to a known base URL. */
57
- provider?: Provider;
58
- /** Model name (e.g. "gpt-4o-mini", "llama-3.3-70b-versatile"). Required when using apiKey without a known provider default. */
59
- model?: string;
60
- /** Custom base URL for OpenAI-compatible APIs. Overrides provider mapping. */
61
- baseURL?: string;
62
- /** Custom LLM function: receives messages, returns the raw text response. Overrides apiKey/provider/model. */
63
- llm?: (messages: Message[]) => Promise<string>;
49
+ interface RedashifyOptions extends LlmOptions {
64
50
  /** Characters of context on each side of a dash (default: 50) */
65
51
  contextSize?: number;
66
52
  /** Custom rules to prepend to the system prompt */
@@ -93,4 +79,4 @@ declare function extractDashes(text: string, contextSize?: number): DashContext[
93
79
  */
94
80
  declare function buildMessages(contexts: readonly DashContext[], rules?: string): Message[];
95
81
 
96
- export { type ApplyResult, type Correction, type DashChar, type DashContext, type Message, type Provider, type RedashifyOptions, type RedashifyResult, VALID_DASHES, buildMessages, extractDashes, redashify };
82
+ export { type ApplyResult, type Correction, type DashChar, type DashContext, type RedashifyOptions, type RedashifyResult, VALID_DASHES, buildMessages, extractDashes, redashify };
package/dist/index.js CHANGED
@@ -1,3 +1,6 @@
1
+ // src/core.ts
2
+ import { resolveLlm } from "@lexstyle/llm-client";
3
+
1
4
  // src/extractor.ts
2
5
  var DASH_RE = /---+|--|\u2014|\u2013|-/g;
3
6
  function extractDashes(text, contextSize = 50) {
@@ -52,83 +55,6 @@ Format: [{"id":0,"dash":"-"},{"id":1,"dash":"\u2013"}]`;
52
55
  ];
53
56
  }
54
57
 
55
- // src/llmClient.ts
56
- import OpenAI from "openai";
57
- var PROVIDER_CONFIGS = {
58
- openai: { baseURL: "https://api.openai.com/v1", defaultModel: "gpt-4o-mini" },
59
- anthropic: { baseURL: "https://api.anthropic.com", defaultModel: "claude-haiku-4-5-20251001" },
60
- gemini: { baseURL: "https://generativelanguage.googleapis.com/v1beta/openai", defaultModel: "gemini-2.0-flash" },
61
- groq: { baseURL: "https://api.groq.com/openai/v1", defaultModel: "llama-3.3-70b-versatile" },
62
- together: { baseURL: "https://api.together.xyz/v1", defaultModel: "meta-llama/Meta-Llama-3.1-8B-Instruct-Turbo" },
63
- mistral: { baseURL: "https://api.mistral.ai/v1", defaultModel: "mistral-small-latest" },
64
- openrouter: { baseURL: "https://openrouter.ai/api/v1", defaultModel: "" },
65
- xai: { baseURL: "https://api.x.ai/v1", defaultModel: "grok-3-mini-fast" },
66
- deepseek: { baseURL: "https://api.deepseek.com", defaultModel: "deepseek-chat" }
67
- };
68
- function getProviderConfig(provider) {
69
- if (!(provider in PROVIDER_CONFIGS)) {
70
- const valid = Object.keys(PROVIDER_CONFIGS).join(", ");
71
- throw new Error(`Unknown provider "${provider}". Valid providers: ${valid}`);
72
- }
73
- return PROVIDER_CONFIGS[provider];
74
- }
75
- async function callAnthropic(messages, apiKey, model, baseURL) {
76
- const resolvedBaseURL = (baseURL ?? PROVIDER_CONFIGS.anthropic.baseURL).replace(/\/+$/, "").replace(/\/v1$/i, "");
77
- const url = `${resolvedBaseURL}/v1/messages`;
78
- const systemMsg = messages.find((m) => m.role === "system");
79
- const nonSystemMsgs = messages.filter((m) => m.role !== "system");
80
- const body = {
81
- model,
82
- max_tokens: 4096,
83
- messages: nonSystemMsgs.map((m) => ({ role: m.role, content: m.content }))
84
- };
85
- if (systemMsg) {
86
- body.system = systemMsg.content;
87
- }
88
- const res = await fetch(url, {
89
- method: "POST",
90
- headers: {
91
- "content-type": "application/json",
92
- "x-api-key": apiKey,
93
- "anthropic-version": "2023-06-01"
94
- },
95
- body: JSON.stringify(body)
96
- });
97
- if (!res.ok) {
98
- const text = await res.text();
99
- throw new Error(`Anthropic API error (${res.status}): ${text}`);
100
- }
101
- const data = await res.json();
102
- const textBlock = data.content?.find((b) => b.type === "text");
103
- if (!textBlock?.text) {
104
- throw new Error("Anthropic returned an empty response");
105
- }
106
- return textBlock.text;
107
- }
108
- async function callOpenAICompatible(messages, apiKey, model, baseURL) {
109
- const client = new OpenAI({
110
- apiKey,
111
- ...baseURL ? { baseURL } : {}
112
- });
113
- const response = await client.chat.completions.create({
114
- model,
115
- messages,
116
- temperature: 0
117
- });
118
- const content = response.choices[0]?.message?.content;
119
- if (!content) {
120
- throw new Error("LLM returned an empty response");
121
- }
122
- return content;
123
- }
124
- async function callLLM(messages, apiKey, model, provider, baseURL) {
125
- if (provider === "anthropic") {
126
- return callAnthropic(messages, apiKey, model, baseURL);
127
- }
128
- const resolvedBaseURL = baseURL ?? (provider ? PROVIDER_CONFIGS[provider].baseURL : void 0);
129
- return callOpenAICompatible(messages, apiKey, model, resolvedBaseURL);
130
- }
131
-
132
58
  // src/types.ts
133
59
  var VALID_DASHES = Object.freeze(["-", "\u2013", "\u2014"]);
134
60
  var VALID_DASH_SET = new Set(VALID_DASHES);
@@ -239,24 +165,6 @@ function requireInt(value, name, min) {
239
165
  throw new Error(`Invalid ${name}: ${value}. Must be a finite integer >= ${min}.`);
240
166
  }
241
167
  }
242
- function resolveLlm(options) {
243
- if (options.llm) {
244
- if (typeof options.llm !== "function") {
245
- throw new Error("`llm` option must be a function");
246
- }
247
- return options.llm;
248
- }
249
- if (options.apiKey) {
250
- const providerConfig = options.provider ? getProviderConfig(options.provider) : void 0;
251
- const model = options.model ?? providerConfig?.defaultModel;
252
- if (!model) {
253
- throw new Error("redashify requires `model` when using `apiKey` (or use a `provider` with a default model)");
254
- }
255
- const { apiKey, provider, baseURL } = options;
256
- return (messages) => callLLM(messages, apiKey, model, provider, baseURL);
257
- }
258
- throw new Error("redashify requires either `apiKey` + `model`, `apiKey` + `provider`, or `llm` option");
259
- }
260
168
  function validateBatchIds(corrections, expectedIds) {
261
169
  for (const { id } of corrections) {
262
170
  if (!expectedIds.has(id)) {
@@ -277,7 +185,7 @@ async function redashify(text, options) {
277
185
  requireInt(batchSize, "batchSize", 1);
278
186
  const contextSize = options.contextSize ?? 50;
279
187
  requireInt(contextSize, "contextSize", 0);
280
- const llmFn = resolveLlm(options);
188
+ const llmFn = resolveLlm(options, "redashify");
281
189
  const contexts = extractDashes(text, contextSize);
282
190
  if (contexts.length === 0) {
283
191
  return { text, corrections: [], unchanged: true };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "redashify",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Context-aware dash correction powered by LLMs",
5
5
  "author": "Haihang Wang",
6
6
  "type": "module",
@@ -54,7 +54,7 @@
54
54
  "url": "https://github.com/hangingahaw/redashify/issues"
55
55
  },
56
56
  "dependencies": {
57
- "openai": "^4.50.0"
57
+ "@lexstyle/llm-client": "^0.1.0"
58
58
  },
59
59
  "devDependencies": {
60
60
  "tsup": "^8.0.0",