write-language 0.1.95 → 0.1.97
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 -3
- package/dist/language-model-families.d.ts +5 -0
- package/dist/rewrite-modes.d.ts +5 -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 +6 -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/dist/rewrite-modes.test.d.ts +0 -1
- package/src/generate-response.attachments.test.ts +0 -110
- package/src/rewrite-modes.test.ts +0 -96
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "write-language",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.97",
|
|
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,
|
package/src/rewrite-modes.ts
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
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
|
+
|
|
1
7
|
export interface RewriteMode {
|
|
2
8
|
id: string;
|
|
3
9
|
name: string;
|
|
@@ -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 +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
|
-
});
|
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
2
|
-
import {
|
|
3
|
-
DEFAULT_REWRITE_MODES,
|
|
4
|
-
getRewriteModes,
|
|
5
|
-
saveRewriteModes,
|
|
6
|
-
resetRewriteModes,
|
|
7
|
-
type RewriteMode,
|
|
8
|
-
} from './rewrite-modes'
|
|
9
|
-
|
|
10
|
-
const STORAGE_KEY = 'REASON-rewrite-modes'
|
|
11
|
-
|
|
12
|
-
// Minimal in-memory localStorage stub for the node test environment.
|
|
13
|
-
function createLocalStorageStub() {
|
|
14
|
-
const store = new Map<string, string>()
|
|
15
|
-
return {
|
|
16
|
-
getItem: vi.fn((k: string) => (store.has(k) ? store.get(k)! : null)),
|
|
17
|
-
setItem: vi.fn((k: string, v: string) => {
|
|
18
|
-
store.set(k, v)
|
|
19
|
-
}),
|
|
20
|
-
removeItem: vi.fn((k: string) => {
|
|
21
|
-
store.delete(k)
|
|
22
|
-
}),
|
|
23
|
-
clear: vi.fn(() => store.clear()),
|
|
24
|
-
_store: store,
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
let ls: ReturnType<typeof createLocalStorageStub>
|
|
29
|
-
|
|
30
|
-
beforeEach(() => {
|
|
31
|
-
ls = createLocalStorageStub()
|
|
32
|
-
vi.stubGlobal('localStorage', ls)
|
|
33
|
-
})
|
|
34
|
-
|
|
35
|
-
afterEach(() => {
|
|
36
|
-
vi.unstubAllGlobals()
|
|
37
|
-
})
|
|
38
|
-
|
|
39
|
-
describe('DEFAULT_REWRITE_MODES', () => {
|
|
40
|
-
it('includes the five built-in modes with unique ids', () => {
|
|
41
|
-
const ids = DEFAULT_REWRITE_MODES.map((m) => m.id)
|
|
42
|
-
expect(ids).toEqual(['clarity', 'concise', 'summarize', 'rephrase', 'expand'])
|
|
43
|
-
expect(new Set(ids).size).toBe(ids.length)
|
|
44
|
-
})
|
|
45
|
-
|
|
46
|
-
it('gives every mode a name and prompt', () => {
|
|
47
|
-
for (const mode of DEFAULT_REWRITE_MODES) {
|
|
48
|
-
expect(mode.name).toBeTruthy()
|
|
49
|
-
expect(mode.prompt.length).toBeGreaterThan(0)
|
|
50
|
-
}
|
|
51
|
-
})
|
|
52
|
-
})
|
|
53
|
-
|
|
54
|
-
describe('getRewriteModes', () => {
|
|
55
|
-
it('returns the defaults when nothing is stored', () => {
|
|
56
|
-
expect(getRewriteModes()).toEqual(DEFAULT_REWRITE_MODES)
|
|
57
|
-
})
|
|
58
|
-
|
|
59
|
-
it('returns the stored modes when present', () => {
|
|
60
|
-
const custom: RewriteMode[] = [{ id: 'x', name: 'X', prompt: 'do x' }]
|
|
61
|
-
ls._store.set(STORAGE_KEY, JSON.stringify(custom))
|
|
62
|
-
expect(getRewriteModes()).toEqual(custom)
|
|
63
|
-
})
|
|
64
|
-
|
|
65
|
-
it('falls back to defaults on malformed JSON', () => {
|
|
66
|
-
ls._store.set(STORAGE_KEY, '{not valid json')
|
|
67
|
-
const spy = vi.spyOn(console, 'error').mockImplementation(() => {})
|
|
68
|
-
expect(getRewriteModes()).toEqual(DEFAULT_REWRITE_MODES)
|
|
69
|
-
expect(spy).toHaveBeenCalled()
|
|
70
|
-
})
|
|
71
|
-
})
|
|
72
|
-
|
|
73
|
-
describe('saveRewriteModes', () => {
|
|
74
|
-
it('serialises the modes to localStorage', () => {
|
|
75
|
-
const modes: RewriteMode[] = [{ id: 'y', name: 'Y', prompt: 'do y' }]
|
|
76
|
-
saveRewriteModes(modes)
|
|
77
|
-
expect(JSON.parse(ls._store.get(STORAGE_KEY)!)).toEqual(modes)
|
|
78
|
-
})
|
|
79
|
-
|
|
80
|
-
it('logs but does not throw when storage rejects', () => {
|
|
81
|
-
ls.setItem.mockImplementation(() => {
|
|
82
|
-
throw new Error('quota exceeded')
|
|
83
|
-
})
|
|
84
|
-
const spy = vi.spyOn(console, 'error').mockImplementation(() => {})
|
|
85
|
-
expect(() => saveRewriteModes([])).not.toThrow()
|
|
86
|
-
expect(spy).toHaveBeenCalled()
|
|
87
|
-
})
|
|
88
|
-
})
|
|
89
|
-
|
|
90
|
-
describe('resetRewriteModes', () => {
|
|
91
|
-
it('writes the defaults back to storage', () => {
|
|
92
|
-
ls._store.set(STORAGE_KEY, JSON.stringify([{ id: 'z', name: 'Z', prompt: 'z' }]))
|
|
93
|
-
resetRewriteModes()
|
|
94
|
-
expect(JSON.parse(ls._store.get(STORAGE_KEY)!)).toEqual(DEFAULT_REWRITE_MODES)
|
|
95
|
-
})
|
|
96
|
-
})
|