@qualve/ai 0.0.1
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/.prettierignore +2 -0
- package/.prettierrc +17 -0
- package/README.md +96 -0
- package/core/README.md +74 -0
- package/core/package.json +28 -0
- package/core/src/index.js +1 -0
- package/core/src/prompts.js +83 -0
- package/core/src/types/llm.js +404 -0
- package/core/src/util.js +111 -0
- package/package.json +44 -0
- package/providers/anthropic/README.md +52 -0
- package/providers/anthropic/package.json +29 -0
- package/providers/anthropic/src/index.js +164 -0
- package/providers/googleai/README.md +54 -0
- package/providers/googleai/package.json +29 -0
- package/providers/googleai/src/index.js +210 -0
- package/providers/openai/README.md +53 -0
- package/providers/openai/package.json +29 -0
- package/providers/openai/src/index.js +182 -0
- package/src/core.js +1 -0
- package/src/index.js +3 -0
- package/src/providers/anthropic.js +3 -0
- package/src/providers/googleai.js +3 -0
- package/src/providers/index.js +3 -0
- package/src/providers/openai.js +3 -0
package/.prettierignore
ADDED
package/.prettierrc
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"plugins": [
|
|
3
|
+
"prettier-plugin-brace-style",
|
|
4
|
+
"prettier-plugin-space-before-function-paren",
|
|
5
|
+
"prettier-plugin-merge"
|
|
6
|
+
],
|
|
7
|
+
"braceStyle": "stroustrup",
|
|
8
|
+
"arrowParens": "avoid",
|
|
9
|
+
"bracketSpacing": true,
|
|
10
|
+
"endOfLine": "auto",
|
|
11
|
+
"semi": true,
|
|
12
|
+
"singleQuote": false,
|
|
13
|
+
"tabWidth": 4,
|
|
14
|
+
"useTabs": true,
|
|
15
|
+
"trailingComma": "all",
|
|
16
|
+
"printWidth": 100
|
|
17
|
+
}
|
package/README.md
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# @qualve/ai
|
|
2
|
+
|
|
3
|
+
Meta-package for [Qualve](https://npmjs.com/package/qualve) LLM support.
|
|
4
|
+
Installs the core LLM framework and all official provider adapters in one go.
|
|
5
|
+
|
|
6
|
+
## Setup
|
|
7
|
+
|
|
8
|
+
Requires **Node.js v23+**.
|
|
9
|
+
|
|
10
|
+
```sh
|
|
11
|
+
npm install @qualve/ai
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
This installs:
|
|
15
|
+
- [@qualve/llm](https://npmjs.com/package/@qualve/llm) — Core LLM task framework
|
|
16
|
+
- [@qualve/anthropic](https://npmjs.com/package/@qualve/anthropic) — Claude provider
|
|
17
|
+
- [@qualve/openai](https://npmjs.com/package/@qualve/openai) — OpenAI provider
|
|
18
|
+
- [@qualve/googleai](https://npmjs.com/package/@qualve/googleai) — Gemini provider
|
|
19
|
+
|
|
20
|
+
If you only need specific providers, install them individually instead (each pulls in `@qualve/llm` automatically).
|
|
21
|
+
|
|
22
|
+
## Usage
|
|
23
|
+
|
|
24
|
+
```js
|
|
25
|
+
import "@qualve/ai";
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Importing the package registers all three providers with the Qualve task system.
|
|
29
|
+
|
|
30
|
+
You can also import individual providers via sub-paths:
|
|
31
|
+
|
|
32
|
+
```js
|
|
33
|
+
import "@qualve/ai/anthropic";
|
|
34
|
+
import "@qualve/ai/openai";
|
|
35
|
+
import "@qualve/ai/googleai";
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Or import the core framework:
|
|
39
|
+
|
|
40
|
+
```js
|
|
41
|
+
import { LLMTask } from "@qualve/ai/core";
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## API Keys
|
|
45
|
+
|
|
46
|
+
Create a `.env` file with API keys for the providers you want to use:
|
|
47
|
+
|
|
48
|
+
```sh
|
|
49
|
+
GEMINI_API_KEY=... # https://aistudio.google.com/api-keys
|
|
50
|
+
OPENAI_API_KEY=... # https://platform.openai.com/api-keys
|
|
51
|
+
ANTHROPIC_API_KEY=... # https://platform.claude.com/settings/keys
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Packages
|
|
55
|
+
|
|
56
|
+
| Package | Description |
|
|
57
|
+
| --- | --- |
|
|
58
|
+
| [@qualve/llm](https://npmjs.com/package/@qualve/llm) | Core LLM task framework (`LLMTask` class) |
|
|
59
|
+
| [@qualve/anthropic](https://npmjs.com/package/@qualve/anthropic) | Claude adapter |
|
|
60
|
+
| [@qualve/openai](https://npmjs.com/package/@qualve/openai) | OpenAI adapter |
|
|
61
|
+
| [@qualve/googleai](https://npmjs.com/package/@qualve/googleai) | Gemini adapter |
|
|
62
|
+
|
|
63
|
+
## Models
|
|
64
|
+
|
|
65
|
+
| Provider | Model | Context window | Max output |
|
|
66
|
+
| --- | --- | --- | --- |
|
|
67
|
+
| Gemini | `gemini-3.1-pro-preview`\* | 1,048,576 | 65,536 |
|
|
68
|
+
| Gemini | `gemini-3.1-flash-preview` | 1,048,576 | 65,536 |
|
|
69
|
+
| Gemini | `gemini-3.1-flash-lite-preview` | 1,048,576 | 65,536 |
|
|
70
|
+
| OpenAI | `gpt-5.4`\* | 1,050,000 | 128K |
|
|
71
|
+
| OpenAI | `gpt-5-mini` | 400K | 128K |
|
|
72
|
+
| OpenAI | `gpt-5-nano` | 400K | 128K |
|
|
73
|
+
| Claude | `claude-sonnet-4-6`\* | 1M | 64K |
|
|
74
|
+
| Claude | `claude-haiku-4-6` | 200K | 64K |
|
|
75
|
+
| Claude | `claude-opus-4-5` | 1M | 128K |
|
|
76
|
+
|
|
77
|
+
\* Default
|
|
78
|
+
|
|
79
|
+
## Options
|
|
80
|
+
|
|
81
|
+
| Option | Flag | Description |
|
|
82
|
+
| --- | --- | --- |
|
|
83
|
+
| `llm` | `--llm` | Provider to use (`gemini`, `openai`, `claude`) |
|
|
84
|
+
| `model` | `--model` | Model name (see table above) |
|
|
85
|
+
| `thinking` | `--thinking` | Reasoning effort level |
|
|
86
|
+
| `fresh` | `--fresh` | Force re-upload of input files |
|
|
87
|
+
|
|
88
|
+
### Thinking levels
|
|
89
|
+
|
|
90
|
+
Control reasoning effort via `--thinking <LEVEL>` or the `thinking` task property.
|
|
91
|
+
|
|
92
|
+
| Provider | Accepted values |
|
|
93
|
+
| --- | --- |
|
|
94
|
+
| [Gemini](https://ai.google.dev/gemini-api/docs/thinking) | `minimal`, `low`, `medium`, `high`\* |
|
|
95
|
+
| [OpenAI](https://platform.openai.com/docs/guides/reasoning) | `none`, `minimal`, `low`, `medium`\*, `high`, `xhigh` |
|
|
96
|
+
| [Claude](https://platform.claude.com/docs/en/build-with-claude/extended-thinking) | _(not yet configurable)_ |
|
package/core/README.md
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# @qualve/llm
|
|
2
|
+
|
|
3
|
+
Core LLM task framework for [Qualve](https://npmjs.com/package/qualve).
|
|
4
|
+
Provides the `LLMTask` base class that all LLM provider adapters extend.
|
|
5
|
+
|
|
6
|
+
If you want all providers out of the box, install [@qualve/llms](https://npmjs.com/package/@qualve/llms) instead.
|
|
7
|
+
|
|
8
|
+
## Setup
|
|
9
|
+
|
|
10
|
+
Requires **Node.js v23+**.
|
|
11
|
+
|
|
12
|
+
```sh
|
|
13
|
+
npm install @qualve/llm
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Usage
|
|
17
|
+
|
|
18
|
+
Import alongside a provider to register the `llm` task type:
|
|
19
|
+
|
|
20
|
+
```js
|
|
21
|
+
import "@qualve/anthropic"; // or @qualve/openai, @qualve/googleai
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
### Writing a custom provider
|
|
25
|
+
|
|
26
|
+
```js
|
|
27
|
+
import { LLMTask } from "@qualve/llm";
|
|
28
|
+
|
|
29
|
+
export default class MyProvider extends LLMTask {
|
|
30
|
+
static id = "my-provider";
|
|
31
|
+
static name = "My Provider";
|
|
32
|
+
static models = ["my-model-v1"];
|
|
33
|
+
static capabilities = {};
|
|
34
|
+
|
|
35
|
+
// Required: implement these abstract methods
|
|
36
|
+
async uploadFile (filepath, { mimeType, contents }) { /* ... */ }
|
|
37
|
+
async getFile (filepath) { /* ... */ }
|
|
38
|
+
async deleteFile (filepath) { /* ... */ }
|
|
39
|
+
async listFiles () { /* ... */ }
|
|
40
|
+
async createStream () { /* ... */ }
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
LLMTask.register(MyProvider);
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## API
|
|
47
|
+
|
|
48
|
+
### `LLMTask`
|
|
49
|
+
|
|
50
|
+
Extends the base Qualve `Task` class with LLM-specific functionality:
|
|
51
|
+
|
|
52
|
+
- **Provider dispatch** — `LLMTask.create()` routes to the registered provider based on `task.llm`
|
|
53
|
+
- **File management** — Upload, retrieve, and manage files on the provider
|
|
54
|
+
- **Streaming** — `handleStream()` writes streamed responses to disk with backpressure handling
|
|
55
|
+
- **Prompt helpers** — `this.inputFile()`, `this.inputFiles()`, `this.outputFile()` generate prompt text describing task I/O
|
|
56
|
+
- **Thinking levels** — Normalized across providers via `thinkingLevels` and per-provider `levelMap`
|
|
57
|
+
- **Stop reasons** — Normalized stop reasons (`COMPLETE`, `MAX_TOKENS`, `ABORTED`, `UNKNOWN`)
|
|
58
|
+
|
|
59
|
+
### Abstract methods (providers must implement)
|
|
60
|
+
|
|
61
|
+
| Method | Description |
|
|
62
|
+
| --- | --- |
|
|
63
|
+
| `uploadFile(filepath, { mimeType, contents })` | Upload data to the provider |
|
|
64
|
+
| `getFile(filepath)` | Retrieve a previously uploaded file, or `null` |
|
|
65
|
+
| `deleteFile(filepath)` | Delete a previously uploaded file |
|
|
66
|
+
| `listFiles()` | List all uploaded files |
|
|
67
|
+
| `createStream()` | Create the streaming API call; returns `{ stream, transformChunk, onChunk?, onFinish? }` |
|
|
68
|
+
|
|
69
|
+
### Optional overrides
|
|
70
|
+
|
|
71
|
+
| Method | Description |
|
|
72
|
+
| --- | --- |
|
|
73
|
+
| `getStatus(chunk)` | Extract a human-readable status from a streaming chunk |
|
|
74
|
+
| `countTokens()` | Count input tokens for a dry run |
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@qualve/llm",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Core LLM task framework for Qualve.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"engines": {
|
|
7
|
+
"node": ">=23"
|
|
8
|
+
},
|
|
9
|
+
"main": "src/index.js",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": "./src/index.js"
|
|
12
|
+
},
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "https://github.com/qualve/ai.git",
|
|
16
|
+
"directory": "core"
|
|
17
|
+
},
|
|
18
|
+
"author": "Lea Verou",
|
|
19
|
+
"contributors": [
|
|
20
|
+
"Dmitry Sharabin"
|
|
21
|
+
],
|
|
22
|
+
"peerDependencies": {
|
|
23
|
+
"qualve": "*"
|
|
24
|
+
},
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"dedent": "^1.7.2"
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as LLMTask } from "./types/llm.js";
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Describe a single input file for inclusion in the prompt.
|
|
5
|
+
* Called with `this` bound to the LLMTask instance.
|
|
6
|
+
* @param {object} file
|
|
7
|
+
* @returns {string}
|
|
8
|
+
*/
|
|
9
|
+
export function inputFile (file) {
|
|
10
|
+
let ret = [];
|
|
11
|
+
|
|
12
|
+
if (file.description && !this.capabilities.inputDescriptions) {
|
|
13
|
+
ret.push(`containing ${file.description}`);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if (file.schema && !this.capabilities.inputSchema) {
|
|
17
|
+
ret.push(`follows the JSON schema: ${JSON.stringify(file.schema, null, "\t")}`);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
ret = ret.join(" and ");
|
|
21
|
+
|
|
22
|
+
if (!ret.startsWith("containing")) {
|
|
23
|
+
ret = "which " + ret;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
ret = `\`${path.basename(file.filePath)}\` ${ret}.`;
|
|
27
|
+
|
|
28
|
+
if (file.schema) {
|
|
29
|
+
ret += "\nRead the field descriptions in the JSON schema for details on each field.";
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return ret;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Describe all input files for inclusion in the prompt.
|
|
37
|
+
* Called with `this` bound to the LLMTask instance.
|
|
38
|
+
* @param {Array} files
|
|
39
|
+
* @returns {string}
|
|
40
|
+
*/
|
|
41
|
+
export function inputFiles (files) {
|
|
42
|
+
if (files.length === 0) {
|
|
43
|
+
return "";
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return `I provide the contents of the following files:
|
|
47
|
+
${files.map(file => inputFile.call(this, file)).join("\n")}`;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Describe the expected output file for inclusion in the prompt.
|
|
52
|
+
* Called with `this` bound to the LLMTask instance.
|
|
53
|
+
* @param {object} file
|
|
54
|
+
* @returns {string}
|
|
55
|
+
*/
|
|
56
|
+
export function outputFile (file) {
|
|
57
|
+
let ret = [`Produce a JSON file that I’m going to save as \`${path.basename(file.filePath)}\``];
|
|
58
|
+
|
|
59
|
+
if (file.description && !this.capabilities.outputDescriptions) {
|
|
60
|
+
ret.push(`containing ${file.description}`);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (file.schema && !this.capabilities.outputSchema) {
|
|
64
|
+
ret.push(`following the JSON schema: ${JSON.stringify(file.schema, null, "\t")}`);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (ret.length <= 1) {
|
|
68
|
+
ret = ret[0];
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
if (ret.length === 3) {
|
|
72
|
+
ret[2] = "and " + ret[2];
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
ret = ret.join(", ");
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (file.schema) {
|
|
79
|
+
ret += ".\nRead the field descriptions in the JSON schema for details on each field.";
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return ret;
|
|
83
|
+
}
|