@qevrion/sdk 0.0.0-stage → 0.1.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 +99 -2
- package/dist/genkit.d.ts +23 -0
- package/dist/genkit.js +110 -0
- package/dist/genkit.js.map +1 -0
- package/dist/index.d.ts +33 -0
- package/dist/index.js +192 -0
- package/dist/index.js.map +1 -0
- package/dist/types.d.ts +73 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +52 -4
package/README.md
CHANGED
|
@@ -1,3 +1,100 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @qevrion/sdk
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Small, dependency-free TypeScript client for QevrionAI text chat, streaming, and model discovery. ESM, Node.js 20.3+. Keep API keys in your server environment.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
After publication:
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
npm install @qevrion/sdk
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import { Qevrion } from '@qevrion/sdk';
|
|
15
|
+
|
|
16
|
+
const client = new Qevrion({ apiKey: process.env.QEVRION_API_KEY! });
|
|
17
|
+
const { data: models } = await client.models.list();
|
|
18
|
+
const model = models[0]?.id;
|
|
19
|
+
if (!model) throw new Error('No models are enabled.');
|
|
20
|
+
|
|
21
|
+
const result = await client.chat.completions.create({
|
|
22
|
+
model,
|
|
23
|
+
messages: [{ role: 'user', content: 'Hello!' }],
|
|
24
|
+
max_tokens: 256,
|
|
25
|
+
});
|
|
26
|
+
console.log(result.choices[0]?.message.content);
|
|
27
|
+
|
|
28
|
+
for await (const chunk of client.chat.completions.stream({
|
|
29
|
+
model,
|
|
30
|
+
messages: [{ role: 'user', content: 'Tell me a short story.' }],
|
|
31
|
+
})) {
|
|
32
|
+
process.stdout.write(chunk.choices[0]?.delta.content ?? '');
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
`create()` returns a JSON completion; `stream()` returns an async iterable of raw chat chunks, including usage-only chunks. Breaking the loop cancels the reader and aborts the HTTP request. A stream ending without `[DONE]` raises `QevrionProtocolError` so interrupted responses are detectable. Cancellation cannot guarantee that the upstream provider stops generating or charging.
|
|
37
|
+
|
|
38
|
+
Supported input: text `system`, `user`, and `assistant` messages; `max_tokens` (1–4096, default 1024), `temperature` (0–2), and `top_p` (0–1). Model IDs must be enabled in the catalog. Tool calls, images, embeddings, and native JSON schema output are not supported by the current API. Upstream `usage.cost`, when included, is not the final Qevrion account charge.
|
|
39
|
+
|
|
40
|
+
## Configuration and errors
|
|
41
|
+
|
|
42
|
+
The default API root is `https://useqevrion.com/v1`. For local development pass `baseURL: 'http://localhost:3000/v1'`. A custom `fetch` can be supplied for testing or transport integration. Redirects are rejected.
|
|
43
|
+
|
|
44
|
+
All operations accept a second options argument (the first argument for `models.list`) with `signal` and `timeoutMs`. The default deadline is 120 seconds and includes reading the response body or stream. Network failures and aborts propagate from fetch. Automatic retries are disabled because chat requests can incur charges.
|
|
45
|
+
|
|
46
|
+
```ts
|
|
47
|
+
import { QevrionAPIError } from '@qevrion/sdk';
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
await client.chat.completions.create({ model, messages: [] });
|
|
51
|
+
} catch (error) {
|
|
52
|
+
if (error instanceof QevrionAPIError) {
|
|
53
|
+
console.error(error.status, error.type, error.code, error.requestId);
|
|
54
|
+
} else {
|
|
55
|
+
throw error;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
API errors include HTTP `status`, provider `type`/`code` when supplied, and the Qevrion request ID when supplied. An error delivered inside an established SSE stream has HTTP status 200; use its type/code to diagnose it. JSON responses have TypeScript declarations; they are not runtime schema validated.
|
|
61
|
+
|
|
62
|
+
## Genkit
|
|
63
|
+
|
|
64
|
+
Install the optional peer only if you use the separate Genkit entry point:
|
|
65
|
+
|
|
66
|
+
```sh
|
|
67
|
+
npm install genkit
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
```ts
|
|
71
|
+
import { genkit } from 'genkit';
|
|
72
|
+
import { qevrion, qevrionModel } from '@qevrion/sdk/genkit';
|
|
73
|
+
|
|
74
|
+
const ai = genkit({
|
|
75
|
+
plugins: [qevrion({ apiKey: process.env.QEVRION_API_KEY! })],
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
const response = await ai.generate({
|
|
79
|
+
// Replace with an enabled catalog ID.
|
|
80
|
+
model: qevrionModel('openai/gpt-4o-mini'),
|
|
81
|
+
prompt: 'Hello!',
|
|
82
|
+
config: { maxOutputTokens: 256, temperature: 0 },
|
|
83
|
+
});
|
|
84
|
+
console.log(response.text);
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
The adapter uses the SDK transport, including its cancellation, deadlines, errors, and disabled retries. It accepts `baseURL`, `timeoutMs`, a custom `fetch`, and a plugin `name` (default `qevrion`). If you change the name, pass the same name as the second argument to `qevrionModel`. It supports `generate` and `generateStream`, advertises text chat capabilities only, and maps `maxOutputTokens`, `temperature`, and `topP` to the API parameters. Unsupported configuration is rejected rather than silently ignored.
|
|
88
|
+
|
|
89
|
+
## Build and release
|
|
90
|
+
|
|
91
|
+
From `packages/sdk`:
|
|
92
|
+
|
|
93
|
+
```sh
|
|
94
|
+
npm ci
|
|
95
|
+
npm test
|
|
96
|
+
npm pack --dry-run
|
|
97
|
+
npm pack
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Install the generated tarball in another project to try it before publication. The package name is proposed; scope ownership and name availability must be checked by the publisher. Set the intended license before release (currently `UNLICENSED`), then publish with `npm publish --access public` using an account authorized for the `@qevrion` scope. This repository does not publish automatically. Only compiled files and this README are included in the package.
|
package/dist/genkit.d.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { z } from 'genkit';
|
|
2
|
+
import { type QevrionOptions } from './index.js';
|
|
3
|
+
export interface QevrionGenkitOptions extends QevrionOptions {
|
|
4
|
+
name?: string;
|
|
5
|
+
}
|
|
6
|
+
export declare function qevrion(options: QevrionGenkitOptions): import("genkit/plugin").GenkitPluginV2Instance;
|
|
7
|
+
/** Pass a catalog model ID, including its provider, e.g. openai/gpt-4o-mini. */
|
|
8
|
+
export declare function qevrionModel(modelId: string, pluginName?: string): import("genkit").ModelReference<z.ZodObject<{
|
|
9
|
+
version: z.ZodOptional<z.ZodUndefined>;
|
|
10
|
+
maxOutputTokens: z.ZodOptional<z.ZodNumber>;
|
|
11
|
+
temperature: z.ZodOptional<z.ZodNumber>;
|
|
12
|
+
topP: z.ZodOptional<z.ZodNumber>;
|
|
13
|
+
}, "strict", z.ZodTypeAny, {
|
|
14
|
+
version?: undefined;
|
|
15
|
+
maxOutputTokens?: number | undefined;
|
|
16
|
+
temperature?: number | undefined;
|
|
17
|
+
topP?: number | undefined;
|
|
18
|
+
}, {
|
|
19
|
+
version?: undefined;
|
|
20
|
+
maxOutputTokens?: number | undefined;
|
|
21
|
+
temperature?: number | undefined;
|
|
22
|
+
topP?: number | undefined;
|
|
23
|
+
}>>;
|
package/dist/genkit.js
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { modelRef, z } from 'genkit';
|
|
2
|
+
import { genkitPluginV2, model } from 'genkit/plugin';
|
|
3
|
+
import { Qevrion } from './index.js';
|
|
4
|
+
const configSchema = z
|
|
5
|
+
.object({
|
|
6
|
+
// Genkit injects this field even when no model version was requested.
|
|
7
|
+
version: z.undefined().optional(),
|
|
8
|
+
maxOutputTokens: z.number().int().min(1).max(4096).optional(),
|
|
9
|
+
temperature: z.number().min(0).max(2).optional(),
|
|
10
|
+
topP: z.number().min(0).max(1).optional(),
|
|
11
|
+
})
|
|
12
|
+
.strict();
|
|
13
|
+
const supports = {
|
|
14
|
+
multiturn: true,
|
|
15
|
+
tools: false,
|
|
16
|
+
media: false,
|
|
17
|
+
systemRole: true,
|
|
18
|
+
output: ['text'],
|
|
19
|
+
};
|
|
20
|
+
function usage(value) {
|
|
21
|
+
return value
|
|
22
|
+
? {
|
|
23
|
+
inputTokens: value.prompt_tokens,
|
|
24
|
+
outputTokens: value.completion_tokens,
|
|
25
|
+
totalTokens: value.total_tokens,
|
|
26
|
+
}
|
|
27
|
+
: undefined;
|
|
28
|
+
}
|
|
29
|
+
function finishReason(value) {
|
|
30
|
+
if (value === 'stop' || value === 'length')
|
|
31
|
+
return value;
|
|
32
|
+
return value === 'content_filter' ? 'blocked' : 'other';
|
|
33
|
+
}
|
|
34
|
+
export function qevrion(options) {
|
|
35
|
+
const client = new Qevrion(options);
|
|
36
|
+
const name = options.name ?? 'qevrion';
|
|
37
|
+
return genkitPluginV2({
|
|
38
|
+
name,
|
|
39
|
+
async resolve(actionType, actionName) {
|
|
40
|
+
if (actionType !== 'model')
|
|
41
|
+
return undefined;
|
|
42
|
+
const id = actionName.startsWith(`${name}/`) ? actionName.slice(name.length + 1) : actionName;
|
|
43
|
+
return model({ name: `${name}/${id}`, supports, configSchema }, async (request, context) => {
|
|
44
|
+
const config = configSchema.parse(request.config ?? {});
|
|
45
|
+
if (request.tools?.length || (request.output?.format && request.output.format !== 'text')) {
|
|
46
|
+
throw new TypeError('Qevrion supports text chat only.');
|
|
47
|
+
}
|
|
48
|
+
const params = {
|
|
49
|
+
model: id,
|
|
50
|
+
messages: request.messages.map((message) => {
|
|
51
|
+
if (!['user', 'model', 'system'].includes(message.role) ||
|
|
52
|
+
message.content.some((part) => typeof part.text !== 'string')) {
|
|
53
|
+
throw new TypeError('Qevrion supports text system, user, and assistant messages only.');
|
|
54
|
+
}
|
|
55
|
+
return {
|
|
56
|
+
role: message.role === 'model' ? 'assistant' : message.role,
|
|
57
|
+
content: message.content.map((part) => part.text).join(''),
|
|
58
|
+
};
|
|
59
|
+
}),
|
|
60
|
+
max_tokens: config.maxOutputTokens,
|
|
61
|
+
temperature: config.temperature,
|
|
62
|
+
top_p: config.topP,
|
|
63
|
+
};
|
|
64
|
+
const requestOptions = { signal: context.abortSignal };
|
|
65
|
+
if (!context.streamingRequested) {
|
|
66
|
+
const result = await client.chat.completions.create(params, requestOptions);
|
|
67
|
+
const choice = result.choices[0];
|
|
68
|
+
if (!choice)
|
|
69
|
+
throw new Error('Qevrion returned no completion choices.');
|
|
70
|
+
return {
|
|
71
|
+
message: { role: 'model', content: [{ text: choice.message.content ?? '' }] },
|
|
72
|
+
finishReason: finishReason(choice.finish_reason),
|
|
73
|
+
usage: usage(result.usage),
|
|
74
|
+
raw: result,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
let text = '';
|
|
78
|
+
let reason = null;
|
|
79
|
+
let finalUsage;
|
|
80
|
+
for await (const chunk of client.chat.completions.stream(params, requestOptions)) {
|
|
81
|
+
if (chunk.usage)
|
|
82
|
+
finalUsage = chunk.usage;
|
|
83
|
+
const choice = chunk.choices.find((item) => item.index === 0);
|
|
84
|
+
if (!choice)
|
|
85
|
+
continue;
|
|
86
|
+
if (choice.finish_reason)
|
|
87
|
+
reason = choice.finish_reason;
|
|
88
|
+
if (choice.delta.content) {
|
|
89
|
+
text += choice.delta.content;
|
|
90
|
+
await context.sendChunk({ index: 0, content: [{ text: choice.delta.content }] });
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return {
|
|
94
|
+
message: { role: 'model', content: [{ text }] },
|
|
95
|
+
finishReason: finishReason(reason),
|
|
96
|
+
usage: usage(finalUsage),
|
|
97
|
+
};
|
|
98
|
+
});
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
/** Pass a catalog model ID, including its provider, e.g. openai/gpt-4o-mini. */
|
|
103
|
+
export function qevrionModel(modelId, pluginName = 'qevrion') {
|
|
104
|
+
return modelRef({
|
|
105
|
+
name: `${pluginName}/${modelId}`,
|
|
106
|
+
configSchema,
|
|
107
|
+
info: { label: modelId, supports },
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
//# sourceMappingURL=genkit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"genkit.js","sourceRoot":"","sources":["../src/genkit.ts"],"names":[],"mappings":"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","sourcesContent":["import { modelRef, z } from 'genkit';\nimport { genkitPluginV2, model } from 'genkit/plugin';\nimport { Qevrion, type ChatParams, type QevrionOptions, type Usage } from './index.js';\n\nexport interface QevrionGenkitOptions extends QevrionOptions {\n name?: string;\n}\n\nconst configSchema = z\n .object({\n // Genkit injects this field even when no model version was requested.\n version: z.undefined().optional(),\n maxOutputTokens: z.number().int().min(1).max(4096).optional(),\n temperature: z.number().min(0).max(2).optional(),\n topP: z.number().min(0).max(1).optional(),\n })\n .strict();\n\nconst supports = {\n multiturn: true,\n tools: false,\n media: false,\n systemRole: true,\n output: ['text' as const],\n};\n\nfunction usage(value?: Usage | null) {\n return value\n ? {\n inputTokens: value.prompt_tokens,\n outputTokens: value.completion_tokens,\n totalTokens: value.total_tokens,\n }\n : undefined;\n}\n\nfunction finishReason(value: string | null): 'stop' | 'length' | 'blocked' | 'other' {\n if (value === 'stop' || value === 'length') return value;\n return value === 'content_filter' ? 'blocked' : 'other';\n}\n\nexport function qevrion(options: QevrionGenkitOptions) {\n const client = new Qevrion(options);\n const name = options.name ?? 'qevrion';\n return genkitPluginV2({\n name,\n async resolve(actionType, actionName) {\n if (actionType !== 'model') return undefined;\n const id = actionName.startsWith(`${name}/`) ? actionName.slice(name.length + 1) : actionName;\n return model({ name: `${name}/${id}`, supports, configSchema }, async (request, context) => {\n const config = configSchema.parse(request.config ?? {});\n if (request.tools?.length || (request.output?.format && request.output.format !== 'text')) {\n throw new TypeError('Qevrion supports text chat only.');\n }\n const params: ChatParams = {\n model: id,\n messages: request.messages.map((message) => {\n if (\n !['user', 'model', 'system'].includes(message.role) ||\n message.content.some((part) => typeof part.text !== 'string')\n ) {\n throw new TypeError(\n 'Qevrion supports text system, user, and assistant messages only.',\n );\n }\n return {\n role: message.role === 'model' ? 'assistant' : (message.role as 'system' | 'user'),\n content: message.content.map((part) => part.text).join(''),\n };\n }),\n max_tokens: config.maxOutputTokens,\n temperature: config.temperature,\n top_p: config.topP,\n };\n const requestOptions = { signal: context.abortSignal };\n if (!context.streamingRequested) {\n const result = await client.chat.completions.create(params, requestOptions);\n const choice = result.choices[0];\n if (!choice) throw new Error('Qevrion returned no completion choices.');\n return {\n message: { role: 'model', content: [{ text: choice.message.content ?? '' }] },\n finishReason: finishReason(choice.finish_reason),\n usage: usage(result.usage),\n raw: result,\n };\n }\n let text = '';\n let reason: string | null = null;\n let finalUsage: Usage | null | undefined;\n for await (const chunk of client.chat.completions.stream(params, requestOptions)) {\n if (chunk.usage) finalUsage = chunk.usage;\n const choice = chunk.choices.find((item) => item.index === 0);\n if (!choice) continue;\n if (choice.finish_reason) reason = choice.finish_reason;\n if (choice.delta.content) {\n text += choice.delta.content;\n await context.sendChunk({ index: 0, content: [{ text: choice.delta.content }] });\n }\n }\n return {\n message: { role: 'model', content: [{ text }] },\n finishReason: finishReason(reason),\n usage: usage(finalUsage),\n };\n });\n },\n });\n}\n\n/** Pass a catalog model ID, including its provider, e.g. openai/gpt-4o-mini. */\nexport function qevrionModel(modelId: string, pluginName = 'qevrion') {\n return modelRef({\n name: `${pluginName}/${modelId}`,\n configSchema,\n info: { label: modelId, supports },\n });\n}\n"]}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { ChatChunk, ChatCompletion, ChatParams, ModelList, QevrionOptions, RequestOptions } from './types.js';
|
|
2
|
+
export type * from './types.js';
|
|
3
|
+
export declare const DEFAULT_BASE_URL = "https://useqevrion.com/v1";
|
|
4
|
+
export declare class QevrionAPIError extends Error {
|
|
5
|
+
readonly status: number;
|
|
6
|
+
readonly type?: string;
|
|
7
|
+
readonly code?: string;
|
|
8
|
+
readonly requestId?: string;
|
|
9
|
+
constructor(message: string, status: number, details?: {
|
|
10
|
+
type?: string;
|
|
11
|
+
code?: string;
|
|
12
|
+
requestId?: string;
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
export declare class QevrionProtocolError extends Error {
|
|
16
|
+
constructor(message: string, options?: ErrorOptions);
|
|
17
|
+
}
|
|
18
|
+
export declare function normalizeBaseURL(value?: string): string;
|
|
19
|
+
export declare function validateApiKey(apiKey: string): void;
|
|
20
|
+
/** Server-side ESM client. Requests are never automatically retried. */
|
|
21
|
+
export declare class Qevrion {
|
|
22
|
+
#private;
|
|
23
|
+
constructor(options: QevrionOptions);
|
|
24
|
+
readonly models: {
|
|
25
|
+
list: (options?: RequestOptions) => Promise<ModelList>;
|
|
26
|
+
};
|
|
27
|
+
readonly chat: {
|
|
28
|
+
completions: {
|
|
29
|
+
create: (params: ChatParams, options?: RequestOptions) => Promise<ChatCompletion>;
|
|
30
|
+
stream: (params: ChatParams, options?: RequestOptions) => AsyncGenerator<ChatChunk>;
|
|
31
|
+
};
|
|
32
|
+
};
|
|
33
|
+
}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
export const DEFAULT_BASE_URL = 'https://useqevrion.com/v1';
|
|
2
|
+
export class QevrionAPIError extends Error {
|
|
3
|
+
status;
|
|
4
|
+
type;
|
|
5
|
+
code;
|
|
6
|
+
requestId;
|
|
7
|
+
constructor(message, status, details = {}) {
|
|
8
|
+
super(message);
|
|
9
|
+
this.name = 'QevrionAPIError';
|
|
10
|
+
this.status = status;
|
|
11
|
+
this.type = details.type;
|
|
12
|
+
this.code = details.code;
|
|
13
|
+
this.requestId = details.requestId;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
export class QevrionProtocolError extends Error {
|
|
17
|
+
constructor(message, options) {
|
|
18
|
+
super(message, options);
|
|
19
|
+
this.name = 'QevrionProtocolError';
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
export function normalizeBaseURL(value = DEFAULT_BASE_URL) {
|
|
23
|
+
const url = new URL(value);
|
|
24
|
+
if (!['http:', 'https:'].includes(url.protocol) ||
|
|
25
|
+
url.username ||
|
|
26
|
+
url.password ||
|
|
27
|
+
url.search ||
|
|
28
|
+
url.hash) {
|
|
29
|
+
throw new TypeError('baseURL must be an HTTP(S) API root without credentials, query, or fragment.');
|
|
30
|
+
}
|
|
31
|
+
return url.href.replace(/\/+$/, '');
|
|
32
|
+
}
|
|
33
|
+
export function validateApiKey(apiKey) {
|
|
34
|
+
if (typeof apiKey !== 'string' || !/^qv_[a-zA-Z0-9_-]+$/.test(apiKey)) {
|
|
35
|
+
throw new TypeError('A Qevrion API key beginning with qv_ is required.');
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
function validateTimeout(timeout) {
|
|
39
|
+
if (!Number.isInteger(timeout) || timeout < 1 || timeout > 2_147_483_647) {
|
|
40
|
+
throw new TypeError('timeoutMs must be a positive integer no greater than 2147483647.');
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function apiError(body, response) {
|
|
44
|
+
const error = body
|
|
45
|
+
?.error;
|
|
46
|
+
return new QevrionAPIError(typeof error?.message === 'string'
|
|
47
|
+
? error.message
|
|
48
|
+
: `Qevrion request failed (${response.status}).`, response.status, {
|
|
49
|
+
type: typeof error?.type === 'string' ? error.type : undefined,
|
|
50
|
+
code: typeof error?.code === 'string' ? error.code : undefined,
|
|
51
|
+
requestId: response.headers.get('x-qevrionai-request-id') ?? undefined,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
/** Server-side ESM client. Requests are never automatically retried. */
|
|
55
|
+
export class Qevrion {
|
|
56
|
+
#apiKey;
|
|
57
|
+
#baseURL;
|
|
58
|
+
#timeoutMs;
|
|
59
|
+
#fetch;
|
|
60
|
+
constructor(options) {
|
|
61
|
+
validateApiKey(options.apiKey);
|
|
62
|
+
this.#apiKey = options.apiKey;
|
|
63
|
+
this.#baseURL = normalizeBaseURL(options.baseURL);
|
|
64
|
+
this.#timeoutMs = options.timeoutMs ?? 120_000;
|
|
65
|
+
validateTimeout(this.#timeoutMs);
|
|
66
|
+
this.#fetch = options.fetch ?? globalThis.fetch.bind(globalThis);
|
|
67
|
+
}
|
|
68
|
+
models = {
|
|
69
|
+
list: (options) => this.#json('/models', undefined, options),
|
|
70
|
+
};
|
|
71
|
+
chat = {
|
|
72
|
+
completions: {
|
|
73
|
+
create: (params, options) => this.#json('/chat/completions', { ...params, stream: false }, options),
|
|
74
|
+
stream: (params, options) => this.#stream(params, options),
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
async #request(path, body, options = {}) {
|
|
78
|
+
const timeout = options.timeoutMs ?? this.#timeoutMs;
|
|
79
|
+
validateTimeout(timeout);
|
|
80
|
+
const controller = new AbortController();
|
|
81
|
+
const timer = setTimeout(() => controller.abort(new DOMException('Qevrion request timed out.', 'TimeoutError')), timeout);
|
|
82
|
+
const signal = options.signal
|
|
83
|
+
? AbortSignal.any([options.signal, controller.signal])
|
|
84
|
+
: controller.signal;
|
|
85
|
+
try {
|
|
86
|
+
const response = await this.#fetch(`${this.#baseURL}${path}`, {
|
|
87
|
+
method: body === undefined ? 'GET' : 'POST',
|
|
88
|
+
headers: {
|
|
89
|
+
Authorization: `Bearer ${this.#apiKey}`,
|
|
90
|
+
...(body === undefined ? {} : { 'Content-Type': 'application/json' }),
|
|
91
|
+
},
|
|
92
|
+
body: body === undefined ? undefined : JSON.stringify(body),
|
|
93
|
+
signal,
|
|
94
|
+
redirect: 'error',
|
|
95
|
+
});
|
|
96
|
+
if (!response.ok) {
|
|
97
|
+
const text = await response.text();
|
|
98
|
+
let parsed;
|
|
99
|
+
try {
|
|
100
|
+
parsed = JSON.parse(text);
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
/* Proxy errors can be HTML. */
|
|
104
|
+
}
|
|
105
|
+
throw apiError(parsed, response);
|
|
106
|
+
}
|
|
107
|
+
return {
|
|
108
|
+
response,
|
|
109
|
+
dispose: () => {
|
|
110
|
+
clearTimeout(timer);
|
|
111
|
+
controller.abort();
|
|
112
|
+
},
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
catch (error) {
|
|
116
|
+
clearTimeout(timer);
|
|
117
|
+
controller.abort();
|
|
118
|
+
throw error;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
async #json(path, body, options) {
|
|
122
|
+
const { response, dispose } = await this.#request(path, body, options);
|
|
123
|
+
try {
|
|
124
|
+
const text = await response.text();
|
|
125
|
+
try {
|
|
126
|
+
return JSON.parse(text);
|
|
127
|
+
}
|
|
128
|
+
catch (cause) {
|
|
129
|
+
throw new QevrionProtocolError('Qevrion returned invalid JSON.', { cause });
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
finally {
|
|
133
|
+
dispose();
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
async *#stream(params, options) {
|
|
137
|
+
const { response, dispose } = await this.#request('/chat/completions', { ...params, stream: true }, options);
|
|
138
|
+
let reader;
|
|
139
|
+
try {
|
|
140
|
+
if (!response.headers.get('content-type')?.toLowerCase().startsWith('text/event-stream') ||
|
|
141
|
+
!response.body) {
|
|
142
|
+
throw new QevrionProtocolError('Expected a text/event-stream response.');
|
|
143
|
+
}
|
|
144
|
+
reader = response.body.getReader();
|
|
145
|
+
const decoder = new TextDecoder();
|
|
146
|
+
let buffer = '';
|
|
147
|
+
while (true) {
|
|
148
|
+
const { value, done } = await reader.read();
|
|
149
|
+
buffer += decoder.decode(value, { stream: !done });
|
|
150
|
+
let boundary;
|
|
151
|
+
while ((boundary = /\r?\n\r?\n/.exec(buffer)) !== null || (done && buffer.length > 0)) {
|
|
152
|
+
const frame = boundary ? buffer.slice(0, boundary.index) : buffer;
|
|
153
|
+
buffer = boundary ? buffer.slice(boundary.index + boundary[0].length) : '';
|
|
154
|
+
const data = frame
|
|
155
|
+
.split(/\r?\n/)
|
|
156
|
+
.filter((line) => line.startsWith('data:'))
|
|
157
|
+
.map((line) => line.slice(5).replace(/^ /, ''))
|
|
158
|
+
.join('\n');
|
|
159
|
+
if (!data)
|
|
160
|
+
continue;
|
|
161
|
+
if (data.trim() === '[DONE]')
|
|
162
|
+
return;
|
|
163
|
+
let chunk;
|
|
164
|
+
try {
|
|
165
|
+
chunk = JSON.parse(data);
|
|
166
|
+
}
|
|
167
|
+
catch (cause) {
|
|
168
|
+
throw new QevrionProtocolError('Invalid JSON in Qevrion stream.', { cause });
|
|
169
|
+
}
|
|
170
|
+
if (chunk && typeof chunk === 'object' && 'error' in chunk)
|
|
171
|
+
throw apiError(chunk, response);
|
|
172
|
+
yield chunk;
|
|
173
|
+
}
|
|
174
|
+
if (done)
|
|
175
|
+
throw new QevrionProtocolError('Qevrion stream ended before [DONE].');
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
finally {
|
|
179
|
+
dispose();
|
|
180
|
+
if (reader) {
|
|
181
|
+
try {
|
|
182
|
+
await reader.cancel();
|
|
183
|
+
}
|
|
184
|
+
catch {
|
|
185
|
+
/* An aborted reader may already be closed. */
|
|
186
|
+
}
|
|
187
|
+
reader.releaseLock();
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n ChatChunk,\n ChatCompletion,\n ChatParams,\n ModelList,\n QevrionOptions,\n RequestOptions,\n} from './types.js';\nexport type * from './types.js';\n\nexport const DEFAULT_BASE_URL = 'https://useqevrion.com/v1';\n\nexport class QevrionAPIError extends Error {\n readonly status: number;\n readonly type?: string;\n readonly code?: string;\n readonly requestId?: string;\n\n constructor(\n message: string,\n status: number,\n details: {\n type?: string;\n code?: string;\n requestId?: string;\n } = {},\n ) {\n super(message);\n this.name = 'QevrionAPIError';\n this.status = status;\n this.type = details.type;\n this.code = details.code;\n this.requestId = details.requestId;\n }\n}\n\nexport class QevrionProtocolError extends Error {\n constructor(message: string, options?: ErrorOptions) {\n super(message, options);\n this.name = 'QevrionProtocolError';\n }\n}\n\nexport function normalizeBaseURL(value: string = DEFAULT_BASE_URL): string {\n const url = new URL(value);\n if (\n !['http:', 'https:'].includes(url.protocol) ||\n url.username ||\n url.password ||\n url.search ||\n url.hash\n ) {\n throw new TypeError(\n 'baseURL must be an HTTP(S) API root without credentials, query, or fragment.',\n );\n }\n return url.href.replace(/\\/+$/, '');\n}\n\nexport function validateApiKey(apiKey: string): void {\n if (typeof apiKey !== 'string' || !/^qv_[a-zA-Z0-9_-]+$/.test(apiKey)) {\n throw new TypeError('A Qevrion API key beginning with qv_ is required.');\n }\n}\n\nfunction validateTimeout(timeout: number): void {\n if (!Number.isInteger(timeout) || timeout < 1 || timeout > 2_147_483_647) {\n throw new TypeError('timeoutMs must be a positive integer no greater than 2147483647.');\n }\n}\n\nfunction apiError(body: unknown, response: Response): QevrionAPIError {\n const error = (body as { error?: { message?: unknown; type?: unknown; code?: unknown } } | null)\n ?.error;\n return new QevrionAPIError(\n typeof error?.message === 'string'\n ? error.message\n : `Qevrion request failed (${response.status}).`,\n response.status,\n {\n type: typeof error?.type === 'string' ? error.type : undefined,\n code: typeof error?.code === 'string' ? error.code : undefined,\n requestId: response.headers.get('x-qevrionai-request-id') ?? undefined,\n },\n );\n}\n\n/** Server-side ESM client. Requests are never automatically retried. */\nexport class Qevrion {\n readonly #apiKey: string;\n readonly #baseURL: string;\n readonly #timeoutMs: number;\n readonly #fetch: typeof globalThis.fetch;\n\n constructor(options: QevrionOptions) {\n validateApiKey(options.apiKey);\n this.#apiKey = options.apiKey;\n this.#baseURL = normalizeBaseURL(options.baseURL);\n this.#timeoutMs = options.timeoutMs ?? 120_000;\n validateTimeout(this.#timeoutMs);\n this.#fetch = options.fetch ?? globalThis.fetch.bind(globalThis);\n }\n\n readonly models = {\n list: (options?: RequestOptions): Promise<ModelList> =>\n this.#json('/models', undefined, options),\n };\n\n readonly chat = {\n completions: {\n create: (params: ChatParams, options?: RequestOptions): Promise<ChatCompletion> =>\n this.#json('/chat/completions', { ...params, stream: false }, options),\n stream: (params: ChatParams, options?: RequestOptions): AsyncGenerator<ChatChunk> =>\n this.#stream(params, options),\n },\n };\n\n async #request(path: string, body: unknown, options: RequestOptions = {}) {\n const timeout = options.timeoutMs ?? this.#timeoutMs;\n validateTimeout(timeout);\n const controller = new AbortController();\n const timer = setTimeout(\n () => controller.abort(new DOMException('Qevrion request timed out.', 'TimeoutError')),\n timeout,\n );\n const signal = options.signal\n ? AbortSignal.any([options.signal, controller.signal])\n : controller.signal;\n try {\n const response = await this.#fetch(`${this.#baseURL}${path}`, {\n method: body === undefined ? 'GET' : 'POST',\n headers: {\n Authorization: `Bearer ${this.#apiKey}`,\n ...(body === undefined ? {} : { 'Content-Type': 'application/json' }),\n },\n body: body === undefined ? undefined : JSON.stringify(body),\n signal,\n redirect: 'error',\n });\n if (!response.ok) {\n const text = await response.text();\n let parsed: unknown;\n try {\n parsed = JSON.parse(text);\n } catch {\n /* Proxy errors can be HTML. */\n }\n throw apiError(parsed, response);\n }\n return {\n response,\n dispose: () => {\n clearTimeout(timer);\n controller.abort();\n },\n };\n } catch (error) {\n clearTimeout(timer);\n controller.abort();\n throw error;\n }\n }\n\n async #json<T>(path: string, body?: unknown, options?: RequestOptions): Promise<T> {\n const { response, dispose } = await this.#request(path, body, options);\n try {\n const text = await response.text();\n try {\n return JSON.parse(text) as T;\n } catch (cause) {\n throw new QevrionProtocolError('Qevrion returned invalid JSON.', { cause });\n }\n } finally {\n dispose();\n }\n }\n\n async *#stream(params: ChatParams, options?: RequestOptions): AsyncGenerator<ChatChunk> {\n const { response, dispose } = await this.#request(\n '/chat/completions',\n { ...params, stream: true },\n options,\n );\n let reader: ReadableStreamDefaultReader<Uint8Array> | undefined;\n try {\n if (\n !response.headers.get('content-type')?.toLowerCase().startsWith('text/event-stream') ||\n !response.body\n ) {\n throw new QevrionProtocolError('Expected a text/event-stream response.');\n }\n reader = response.body.getReader();\n const decoder = new TextDecoder();\n let buffer = '';\n while (true) {\n const { value, done } = await reader.read();\n buffer += decoder.decode(value, { stream: !done });\n let boundary: RegExpExecArray | null;\n while ((boundary = /\\r?\\n\\r?\\n/.exec(buffer)) !== null || (done && buffer.length > 0)) {\n const frame = boundary ? buffer.slice(0, boundary.index) : buffer;\n buffer = boundary ? buffer.slice(boundary.index + boundary[0].length) : '';\n const data = frame\n .split(/\\r?\\n/)\n .filter((line) => line.startsWith('data:'))\n .map((line) => line.slice(5).replace(/^ /, ''))\n .join('\\n');\n if (!data) continue;\n if (data.trim() === '[DONE]') return;\n let chunk: ChatChunk;\n try {\n chunk = JSON.parse(data) as ChatChunk;\n } catch (cause) {\n throw new QevrionProtocolError('Invalid JSON in Qevrion stream.', { cause });\n }\n if (chunk && typeof chunk === 'object' && 'error' in chunk)\n throw apiError(chunk, response);\n yield chunk;\n }\n if (done) throw new QevrionProtocolError('Qevrion stream ended before [DONE].');\n }\n } finally {\n dispose();\n if (reader) {\n try {\n await reader.cancel();\n } catch {\n /* An aborted reader may already be closed. */\n }\n reader.releaseLock();\n }\n }\n }\n}\n"]}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
export interface Message {
|
|
2
|
+
role: 'system' | 'user' | 'assistant';
|
|
3
|
+
content: string;
|
|
4
|
+
}
|
|
5
|
+
export interface ChatParams {
|
|
6
|
+
model: string;
|
|
7
|
+
messages: Message[];
|
|
8
|
+
/** Between 1 and 4096. Server default: 1024. */
|
|
9
|
+
max_tokens?: number;
|
|
10
|
+
temperature?: number;
|
|
11
|
+
top_p?: number;
|
|
12
|
+
}
|
|
13
|
+
export interface Usage {
|
|
14
|
+
prompt_tokens: number;
|
|
15
|
+
completion_tokens: number;
|
|
16
|
+
total_tokens: number;
|
|
17
|
+
/** Upstream cost, when present; not the final Qevrion account charge. */
|
|
18
|
+
cost?: number;
|
|
19
|
+
}
|
|
20
|
+
export interface ChatCompletion {
|
|
21
|
+
id: string;
|
|
22
|
+
object: 'chat.completion';
|
|
23
|
+
created: number;
|
|
24
|
+
model: string;
|
|
25
|
+
choices: Array<{
|
|
26
|
+
index: number;
|
|
27
|
+
message: {
|
|
28
|
+
role: 'assistant';
|
|
29
|
+
content: string | null;
|
|
30
|
+
};
|
|
31
|
+
finish_reason: string | null;
|
|
32
|
+
}>;
|
|
33
|
+
usage?: Usage;
|
|
34
|
+
}
|
|
35
|
+
export interface ChatChunk {
|
|
36
|
+
id: string;
|
|
37
|
+
object: 'chat.completion.chunk';
|
|
38
|
+
created: number;
|
|
39
|
+
model: string;
|
|
40
|
+
choices: Array<{
|
|
41
|
+
index: number;
|
|
42
|
+
delta: {
|
|
43
|
+
role?: 'assistant';
|
|
44
|
+
content?: string | null;
|
|
45
|
+
};
|
|
46
|
+
finish_reason: string | null;
|
|
47
|
+
}>;
|
|
48
|
+
usage?: Usage | null;
|
|
49
|
+
}
|
|
50
|
+
export interface Model {
|
|
51
|
+
id: string;
|
|
52
|
+
object: 'model';
|
|
53
|
+
owned_by: string;
|
|
54
|
+
name: string;
|
|
55
|
+
context_length: number;
|
|
56
|
+
}
|
|
57
|
+
export interface ModelList {
|
|
58
|
+
object: 'list';
|
|
59
|
+
data: Model[];
|
|
60
|
+
}
|
|
61
|
+
export interface RequestOptions {
|
|
62
|
+
signal?: AbortSignal;
|
|
63
|
+
/** Deadline for the entire request, including stream consumption. */
|
|
64
|
+
timeoutMs?: number;
|
|
65
|
+
}
|
|
66
|
+
export interface QevrionOptions {
|
|
67
|
+
apiKey: string;
|
|
68
|
+
/** API root including /v1. */
|
|
69
|
+
baseURL?: string;
|
|
70
|
+
/** Default: 120000 milliseconds. */
|
|
71
|
+
timeoutMs?: number;
|
|
72
|
+
fetch?: typeof globalThis.fetch;
|
|
73
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"","sourcesContent":["export interface Message {\n role: 'system' | 'user' | 'assistant';\n content: string;\n}\n\nexport interface ChatParams {\n model: string;\n messages: Message[];\n /** Between 1 and 4096. Server default: 1024. */\n max_tokens?: number;\n temperature?: number;\n top_p?: number;\n}\n\nexport interface Usage {\n prompt_tokens: number;\n completion_tokens: number;\n total_tokens: number;\n /** Upstream cost, when present; not the final Qevrion account charge. */\n cost?: number;\n}\n\nexport interface ChatCompletion {\n id: string;\n object: 'chat.completion';\n created: number;\n model: string;\n choices: Array<{\n index: number;\n message: { role: 'assistant'; content: string | null };\n finish_reason: string | null;\n }>;\n usage?: Usage;\n}\n\nexport interface ChatChunk {\n id: string;\n object: 'chat.completion.chunk';\n created: number;\n model: string;\n choices: Array<{\n index: number;\n delta: { role?: 'assistant'; content?: string | null };\n finish_reason: string | null;\n }>;\n usage?: Usage | null;\n}\n\nexport interface Model {\n id: string;\n object: 'model';\n owned_by: string;\n name: string;\n context_length: number;\n}\n\nexport interface ModelList {\n object: 'list';\n data: Model[];\n}\n\nexport interface RequestOptions {\n signal?: AbortSignal;\n /** Deadline for the entire request, including stream consumption. */\n timeoutMs?: number;\n}\n\nexport interface QevrionOptions {\n apiKey: string;\n /** API root including /v1. */\n baseURL?: string;\n /** Default: 120000 milliseconds. */\n timeoutMs?: number;\n fetch?: typeof globalThis.fetch;\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,54 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@qevrion/sdk",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Small TypeScript client for QevrionAI with optional Genkit integration",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js"
|
|
12
|
+
},
|
|
13
|
+
"./genkit": {
|
|
14
|
+
"types": "./dist/genkit.d.ts",
|
|
15
|
+
"import": "./dist/genkit.js"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"dist",
|
|
20
|
+
"README.md"
|
|
21
|
+
],
|
|
22
|
+
"sideEffects": false,
|
|
23
|
+
"engines": {
|
|
24
|
+
"node": ">=20.3"
|
|
25
|
+
},
|
|
26
|
+
"license": "UNLICENSED",
|
|
27
|
+
"repository": {
|
|
28
|
+
"type": "git",
|
|
29
|
+
"url": "git+https://github.com/ultimaterichy/useqevrion.git",
|
|
30
|
+
"directory": "packages/sdk"
|
|
31
|
+
},
|
|
32
|
+
"publishConfig": {
|
|
33
|
+
"access": "public"
|
|
34
|
+
},
|
|
35
|
+
"scripts": {
|
|
36
|
+
"build": "tsc -p tsconfig.json",
|
|
37
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
38
|
+
"test": "npm run build && node --test test/*.test.mjs",
|
|
39
|
+
"prepack": "npm run build",
|
|
40
|
+
"prepublishOnly": "npm test"
|
|
41
|
+
},
|
|
42
|
+
"peerDependencies": {
|
|
43
|
+
"genkit": "^1.42.0"
|
|
44
|
+
},
|
|
45
|
+
"peerDependenciesMeta": {
|
|
46
|
+
"genkit": {
|
|
47
|
+
"optional": true
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
"devDependencies": {
|
|
51
|
+
"genkit": "^1.42.0",
|
|
52
|
+
"typescript": "^5.9.3"
|
|
53
|
+
}
|
|
54
|
+
}
|