@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.
@@ -0,0 +1,2 @@
1
+ package-lock.json
2
+ package.json
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
+ }