write-language 0.1.94 → 0.1.96
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 +0 -6
- package/dist/language-model-families.d.ts +5 -0
- package/dist/rewrite-modes.d.ts +15 -0
- package/dist/tools/qwksearch-api-tools.d.ts +7 -0
- package/dist/write-language.cjs.js +1 -1
- package/dist/write-language.cjs.js.map +1 -1
- package/dist/write-language.es.js +1 -1
- package/dist/write-language.es.js.map +1 -1
- package/package.json +3 -1
- package/src/language-model-families.ts +5 -0
- package/src/provider-factory.ts +6 -0
- package/src/rewrite-modes.ts +69 -0
- package/src/tools/qwksearch-api-tools.ts +8 -0
- package/src/utils/prism.ts +5 -0
- package/dist/generate-response.attachments.test.d.ts +0 -1
- package/src/generate-response.attachments.test.ts +0 -110
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "write-language",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.96",
|
|
4
4
|
"description": "Multi-provider language generation toolkit using Vercel AI SDK - generate responses with 10+ LLM providers including OpenAI, Anthropic, Google, and more.",
|
|
5
5
|
"author": "vtempest <grokthiscontact@gmail.com>",
|
|
6
6
|
"license": "rights.institute/PROSPER",
|
|
@@ -43,6 +43,7 @@
|
|
|
43
43
|
"scripts": {
|
|
44
44
|
"build": "vite build",
|
|
45
45
|
"test": "vitest",
|
|
46
|
+
"test:coverage": "vitest run --coverage",
|
|
46
47
|
"test-ui": "vitest --ui --watch",
|
|
47
48
|
"make": "rm -rf dist/*; NODE_OPTIONS=--max-old-space-size=15192 vite build",
|
|
48
49
|
"ship": "npx standard-version --release-as patch && npm publish --access public"
|
|
@@ -60,6 +61,7 @@
|
|
|
60
61
|
}
|
|
61
62
|
},
|
|
62
63
|
"devDependencies": {
|
|
64
|
+
"@vitest/coverage-v8": "^4.0.18",
|
|
63
65
|
"@vitest/ui": "^4.0.18",
|
|
64
66
|
"terser": "^5.46.0",
|
|
65
67
|
"typescript": "^5.9.3",
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Static metadata for well-known LLM model families (Claude, ChatGPT,
|
|
3
|
+
* Gemini, Llama, etc.) used for display purposes such as provider/maker attribution,
|
|
4
|
+
* flagship model naming, and open vs. closed licensing status.
|
|
5
|
+
*/
|
|
1
6
|
|
|
2
7
|
export interface LanguageModelFamily {
|
|
3
8
|
model_family: string;
|
package/src/provider-factory.ts
CHANGED
|
@@ -71,6 +71,12 @@ export function createLLMProvider(
|
|
|
71
71
|
case "openrouter":
|
|
72
72
|
return createOpenRouter({ apiKey }).chat(model);
|
|
73
73
|
|
|
74
|
+
case "anyapi":
|
|
75
|
+
return createOpenAI({
|
|
76
|
+
apiKey,
|
|
77
|
+
baseURL: "https://api.anyapi.ai/v1",
|
|
78
|
+
})(model);
|
|
79
|
+
|
|
74
80
|
case "perplexity":
|
|
75
81
|
return createOpenAI({
|
|
76
82
|
apiKey,
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Defines the default text-rewrite mode presets (clarity, concise,
|
|
3
|
+
* summarize, rephrase, expand) and helpers to read/write/reset the user's custom
|
|
4
|
+
* modes from `localStorage`.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export interface RewriteMode {
|
|
8
|
+
id: string;
|
|
9
|
+
name: string;
|
|
10
|
+
prompt: string;
|
|
11
|
+
color?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const DEFAULT_REWRITE_MODES: RewriteMode[] = [
|
|
15
|
+
{
|
|
16
|
+
id: 'clarity',
|
|
17
|
+
name: 'Clarity',
|
|
18
|
+
prompt: 'Rewrite this paragraph for maximum clarity and straightforwardness, keeping all original meaning but removing ambiguity and simplifying complex sentences:',
|
|
19
|
+
color: 'blue',
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
id: 'concise',
|
|
23
|
+
name: 'Concise',
|
|
24
|
+
prompt: 'Rewrite this text to be more concise, removing redundancy and filler while preserving all key points and tone. Aim for about 50% of the original length:',
|
|
25
|
+
color: 'purple',
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
id: 'summarize',
|
|
29
|
+
name: 'Summarize',
|
|
30
|
+
prompt: 'Summarize the following text into a shorter paragraph, keeping the main ideas and overall tone but removing details and repetition:',
|
|
31
|
+
color: 'green',
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
id: 'rephrase',
|
|
35
|
+
name: 'Rephrase',
|
|
36
|
+
prompt: 'Rephrase this paragraph with fresh wording and more engaging style, varying sentence structure and word choice while preserving the core message:',
|
|
37
|
+
color: 'orange',
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
id: 'expand',
|
|
41
|
+
name: 'Expand',
|
|
42
|
+
prompt: 'Keep the original paragraph as-is, then expand it by adding one additional paragraph that elaborates on the main idea, gives an example, or adds helpful context for the reader:',
|
|
43
|
+
color: 'pink',
|
|
44
|
+
},
|
|
45
|
+
];
|
|
46
|
+
|
|
47
|
+
const STORAGE_KEY = 'REASON-rewrite-modes';
|
|
48
|
+
|
|
49
|
+
export const getRewriteModes = (): RewriteMode[] => {
|
|
50
|
+
try {
|
|
51
|
+
const stored = localStorage.getItem(STORAGE_KEY);
|
|
52
|
+
if (stored) return JSON.parse(stored);
|
|
53
|
+
} catch (e) {
|
|
54
|
+
console.error('Failed to load rewrite modes:', e);
|
|
55
|
+
}
|
|
56
|
+
return DEFAULT_REWRITE_MODES;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
export const saveRewriteModes = (modes: RewriteMode[]): void => {
|
|
60
|
+
try {
|
|
61
|
+
localStorage.setItem(STORAGE_KEY, JSON.stringify(modes));
|
|
62
|
+
} catch (e) {
|
|
63
|
+
console.error('Failed to save rewrite modes:', e);
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export const resetRewriteModes = (): void => {
|
|
68
|
+
saveRewriteModes(DEFAULT_REWRITE_MODES);
|
|
69
|
+
};
|
|
@@ -1 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Registry of agent tools (function-calling definitions backed by the
|
|
3
|
+
* qwksearch API) that can be attached to agent prompts in prompt-templates.ts.
|
|
4
|
+
*
|
|
5
|
+
* Currently empty; populate with `AgentTool` entries (name, schema, func) as
|
|
6
|
+
* qwksearch-backed tools are implemented.
|
|
7
|
+
*/
|
|
8
|
+
|
|
1
9
|
export const AGENT_TOOLS: any[] = [];
|
package/src/utils/prism.ts
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Configures Prism.js with a fixed set of language grammars and exposes
|
|
3
|
+
* a small `highlightCode` helper used by markdown-to-html.ts for code-block syntax
|
|
4
|
+
* highlighting.
|
|
5
|
+
*/
|
|
1
6
|
import Prism from "prismjs";
|
|
2
7
|
import "prismjs/components/prism-markup";
|
|
3
8
|
import "prismjs/components/prism-css";
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Verifies that file/image attachments are forwarded to the
|
|
3
|
-
* Vercel AI SDK as multimodal `messages` content parts, and that plain
|
|
4
|
-
* (attachment-free) calls keep using the simpler `prompt` form.
|
|
5
|
-
*/
|
|
6
|
-
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
7
|
-
|
|
8
|
-
// Capture the exact arguments passed to generateText so we can assert on the
|
|
9
|
-
// shape of the request the SDK receives.
|
|
10
|
-
const generateTextMock = vi.fn((_args: unknown) => Promise.resolve({ text: "ok" }));
|
|
11
|
-
|
|
12
|
-
vi.mock("ai", () => ({
|
|
13
|
-
generateText: (args: unknown) => generateTextMock(args),
|
|
14
|
-
stepCountIs: (n: number) => n,
|
|
15
|
-
tool: (t: unknown) => t,
|
|
16
|
-
}));
|
|
17
|
-
|
|
18
|
-
// Return a truthy fake model so writeLanguageResponse proceeds to generateText.
|
|
19
|
-
vi.mock("./provider-factory", () => ({
|
|
20
|
-
createLLMProvider: () => ({ id: "fake-model" }),
|
|
21
|
-
}));
|
|
22
|
-
|
|
23
|
-
// Avoid pulling the markdown/prism stack into the test.
|
|
24
|
-
vi.mock("./utils/markdown-to-html", () => ({
|
|
25
|
-
convertMarkdownToHTMLEscaped: async (s: string) => s,
|
|
26
|
-
}));
|
|
27
|
-
|
|
28
|
-
import { writeLanguageResponse } from "./generate-response";
|
|
29
|
-
|
|
30
|
-
afterEach(() => {
|
|
31
|
-
generateTextMock.mockClear();
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
describe("writeLanguageResponse attachments", () => {
|
|
35
|
-
const base = {
|
|
36
|
-
provider: "groq",
|
|
37
|
-
apiKey: "test-key",
|
|
38
|
-
agent: "question",
|
|
39
|
-
query: "Describe the attached file",
|
|
40
|
-
html: false,
|
|
41
|
-
applyContextLimit: false,
|
|
42
|
-
} as const;
|
|
43
|
-
|
|
44
|
-
it("sends a plain text prompt when there are no attachments", async () => {
|
|
45
|
-
await writeLanguageResponse({ ...base });
|
|
46
|
-
|
|
47
|
-
expect(generateTextMock).toHaveBeenCalledTimes(1);
|
|
48
|
-
const arg = generateTextMock.mock.calls[0][0] as Record<string, unknown>;
|
|
49
|
-
expect(typeof arg.prompt).toBe("string");
|
|
50
|
-
expect(arg.messages).toBeUndefined();
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
it("sends an image attachment as an image content part", async () => {
|
|
54
|
-
await writeLanguageResponse({
|
|
55
|
-
...base,
|
|
56
|
-
attachments: [
|
|
57
|
-
{ mediaType: "image/png", data: "data:image/png;base64,AAAA" },
|
|
58
|
-
],
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
const arg = generateTextMock.mock.calls[0][0] as any;
|
|
62
|
-
expect(arg.prompt).toBeUndefined();
|
|
63
|
-
expect(Array.isArray(arg.messages)).toBe(true);
|
|
64
|
-
const parts = arg.messages[0].content;
|
|
65
|
-
expect(parts[0]).toMatchObject({ type: "text" });
|
|
66
|
-
expect(parts[1]).toMatchObject({
|
|
67
|
-
type: "image",
|
|
68
|
-
image: "data:image/png;base64,AAAA",
|
|
69
|
-
});
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
it("sends a document attachment as a file content part with its mediaType", async () => {
|
|
73
|
-
await writeLanguageResponse({
|
|
74
|
-
...base,
|
|
75
|
-
attachments: [
|
|
76
|
-
{
|
|
77
|
-
mediaType: "application/pdf",
|
|
78
|
-
data: "JVBERi0=",
|
|
79
|
-
filename: "report.pdf",
|
|
80
|
-
},
|
|
81
|
-
],
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
const arg = generateTextMock.mock.calls[0][0] as any;
|
|
85
|
-
const parts = arg.messages[0].content;
|
|
86
|
-
expect(parts[1]).toMatchObject({
|
|
87
|
-
type: "file",
|
|
88
|
-
mediaType: "application/pdf",
|
|
89
|
-
data: "JVBERi0=",
|
|
90
|
-
filename: "report.pdf",
|
|
91
|
-
});
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
it("skips malformed attachments (missing data or mediaType)", async () => {
|
|
95
|
-
await writeLanguageResponse({
|
|
96
|
-
...base,
|
|
97
|
-
attachments: [
|
|
98
|
-
{ mediaType: "", data: "x" } as any,
|
|
99
|
-
{ mediaType: "image/png" } as any,
|
|
100
|
-
{ mediaType: "image/jpeg", data: "data:image/jpeg;base64,BBBB" },
|
|
101
|
-
],
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
const arg = generateTextMock.mock.calls[0][0] as any;
|
|
105
|
-
const parts = arg.messages[0].content;
|
|
106
|
-
// 1 text part + 1 valid image part only.
|
|
107
|
-
expect(parts).toHaveLength(2);
|
|
108
|
-
expect(parts[1]).toMatchObject({ type: "image" });
|
|
109
|
-
});
|
|
110
|
-
});
|