@novaqore/ai 0.1.0 → 0.3.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.
Files changed (3) hide show
  1. package/README.md +10 -8
  2. package/index.js +50 -21
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -10,7 +10,7 @@
10
10
  <a href="https://www.npmjs.com/package/@novaqore/ai"><img src="https://img.shields.io/npm/v/@novaqore/ai?color=blue&label=npm" alt="npm version" /></a>
11
11
  <a href="https://github.com/novaqore/ai/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-MIT-green" alt="license" /></a>
12
12
  <a href="https://img.shields.io/node/v/@novaqore/ai"><img src="https://img.shields.io/node/v/@novaqore/ai?color=brightgreen" alt="node version" /></a>
13
- <a href="https://discord.gg/novaqore"><img src="https://img.shields.io/discord/1234567890?color=5865F2&label=discord&logo=discord&logoColor=white" alt="Discord" /></a>
13
+ <a href="https://discord.gg/JAzgcf6r"><img src="https://img.shields.io/discord/1234567890?color=5865F2&label=discord&logo=discord&logoColor=white" alt="Discord" /></a>
14
14
  <br><br>
15
15
  <a href="https://novaqore.ai/developer"><strong>🔑 Get Your Quantum Keys</strong></a> &nbsp;|&nbsp;
16
16
  <a href="https://novaqore.ai"><strong>🌐 Website</strong></a> &nbsp;|&nbsp;
@@ -137,11 +137,11 @@ import NovaQoreAI from "@novaqore/ai";
137
137
 
138
138
  const nq = new NovaQoreAI();
139
139
 
140
- const res = await nq.chat([
140
+ const { result } = await nq.chat([
141
141
  { role: "user", content: "Hello" }
142
142
  ]);
143
143
 
144
- console.log(res.choices[0].message.content);
144
+ console.log(result.choices[0].message.content);
145
145
  ```
146
146
 
147
147
  NovaQore AI auto-detects the service file. If no service file is found, it falls back to environment variables automatically.
@@ -177,7 +177,7 @@ If you call `new NovaQoreAI()` with no arguments and no service file is present,
177
177
  ### System prompt
178
178
 
179
179
  ```javascript
180
- const res = await nq.chat([
180
+ const { result } = await nq.chat([
181
181
  { role: "system", content: "You are a helpful assistant that responds in haikus." },
182
182
  { role: "user", content: "Tell me about the ocean" }
183
183
  ]);
@@ -186,7 +186,7 @@ const res = await nq.chat([
186
186
  ### Tool use
187
187
 
188
188
  ```javascript
189
- const res = await nq.chat(
189
+ const { result } = await nq.chat(
190
190
  [{ role: "user", content: "What's the weather in NYC?" }],
191
191
  {
192
192
  tools: [
@@ -208,7 +208,7 @@ const res = await nq.chat(
208
208
  }
209
209
  );
210
210
 
211
- const toolCall = res.choices[0].message.tool_calls[0];
211
+ const toolCall = result.choices[0].message.tool_calls[0];
212
212
  console.log(toolCall.function.name); // "get_weather"
213
213
  console.log(toolCall.function.arguments); // '{"location":"New York City"}'
214
214
  ```
@@ -216,7 +216,7 @@ console.log(toolCall.function.arguments); // '{"location":"New York City"}'
216
216
  ### Streaming
217
217
 
218
218
  ```javascript
219
- const stream = await nq.chat(
219
+ const { stream, stop } = await nq.chat(
220
220
  [{ role: "user", content: "Tell me about the ocean" }],
221
221
  { stream: true }
222
222
  );
@@ -228,6 +228,8 @@ for await (const chunk of stream) {
228
228
  console.log();
229
229
  ```
230
230
 
231
+ Call `stop()` at any time to abort the stream (e.g. wire it to a stop button in your UI).
232
+
231
233
  ### Options
232
234
 
233
235
  | Option | Type | Default | Description |
@@ -272,7 +274,7 @@ Technical deep dives into the cryptography, architecture, and threat models behi
272
274
  We are building this in the open. Join us.
273
275
 
274
276
  <p>
275
- <a href="https://discord.gg/novaqore"><strong>Discord</strong></a> &nbsp;|&nbsp;
277
+ <a href="https://discord.gg/JAzgcf6r"><strong>Discord</strong></a> &nbsp;|&nbsp;
276
278
  <a href="https://x.com/novaqore"><strong>X (Twitter)</strong></a> &nbsp;|&nbsp;
277
279
  <a href="https://tiktok.com/@novaqore"><strong>TikTok</strong></a> &nbsp;|&nbsp;
278
280
  <a href="https://youtube.com/@novaqore"><strong>YouTube</strong></a>
package/index.js CHANGED
@@ -10,6 +10,9 @@ class NovaQoreAI {
10
10
  #uid;
11
11
  #quantumKey;
12
12
  #keyId;
13
+ #authToken;
14
+ #getAuthToken;
15
+ #abort;
13
16
 
14
17
  constructor(config) {
15
18
  if (config === undefined) {
@@ -45,11 +48,17 @@ class NovaQoreAI {
45
48
  this.#uid = config.uid;
46
49
  this.#quantumKey = config.quantumKey;
47
50
  this.#keyId = config.keyId;
51
+ this.#authToken = config.authToken || null;
52
+ this.#getAuthToken = config.getAuthToken || null;
48
53
  this.version = version;
49
54
  this.description = "NovaQore AI - Quantum-encrypted LLM client by NovaQore";
50
55
  this.methods = ["chat", "health"];
51
56
  }
52
57
 
58
+ setAuthToken(token) {
59
+ this.#authToken = token;
60
+ }
61
+
53
62
  async #encryptPayload(payload) {
54
63
  const quantumKey = new Uint8Array(Buffer.from(this.#quantumKey, "base64"));
55
64
 
@@ -86,6 +95,8 @@ class NovaQoreAI {
86
95
  throw new Error("Messages must be a non-empty array");
87
96
  }
88
97
 
98
+ this.#abort = new AbortController();
99
+
89
100
  try {
90
101
  const payload = {
91
102
  messages,
@@ -97,15 +108,24 @@ class NovaQoreAI {
97
108
 
98
109
  const { ciphertext, encrypted, sharedSecret } = await this.#encryptPayload(payload);
99
110
 
111
+ const headers = { "Content-Type": "application/json" };
112
+ if (this.#getAuthToken) {
113
+ this.#authToken = await this.#getAuthToken();
114
+ }
115
+ if (this.#authToken) {
116
+ headers["Authorization"] = `Bearer ${this.#authToken}`;
117
+ }
118
+
100
119
  const res = await fetch(`${BASE_URL}/v1/chat/completions`, {
101
120
  method: "POST",
102
- headers: { "Content-Type": "application/json" },
121
+ headers,
103
122
  body: JSON.stringify({
104
123
  uid: this.#uid,
105
124
  keyId: this.#keyId,
106
125
  ciphertext,
107
126
  encrypted,
108
127
  }),
128
+ signal: this.#abort.signal,
109
129
  });
110
130
 
111
131
  if (!res.ok) {
@@ -120,12 +140,16 @@ class NovaQoreAI {
120
140
  }
121
141
 
122
142
  if (options.stream) {
123
- return this.#readStream(res, sharedSecret);
143
+ const stream = this.#readStream(res, sharedSecret);
144
+ const stop = () => this.#abort.abort();
145
+ return { stream, stop };
124
146
  }
125
147
 
126
148
  const { encrypted: encryptedResponse } = await res.json();
127
- return this.#decryptResponse(encryptedResponse, sharedSecret);
149
+ const result = this.#decryptResponse(encryptedResponse, sharedSecret);
150
+ return { result, stop: () => {} };
128
151
  } catch (err) {
152
+ if (err.name === "AbortError") return;
129
153
  if (err.message) throw err;
130
154
  throw new Error("Failed to connect to NovaQore AI");
131
155
  }
@@ -137,31 +161,36 @@ class NovaQoreAI {
137
161
  let buffer = "";
138
162
  let expectedSeq = 0;
139
163
 
140
- while (true) {
141
- const { done, value } = await reader.read();
142
- if (done) break;
143
- buffer += decoder.decode(value, { stream: true });
164
+ try {
165
+ while (true) {
166
+ const { done, value } = await reader.read();
167
+ if (done) break;
168
+ buffer += decoder.decode(value, { stream: true });
144
169
 
145
- const lines = buffer.split("\n");
146
- buffer = lines.pop();
170
+ const lines = buffer.split("\n");
171
+ buffer = lines.pop();
147
172
 
148
- for (const line of lines) {
149
- const trimmed = line.trim();
150
- if (!trimmed || !trimmed.startsWith("data: ")) continue;
151
- const data = trimmed.slice(6);
173
+ for (const line of lines) {
174
+ const trimmed = line.trim();
175
+ if (!trimmed || !trimmed.startsWith("data: ")) continue;
176
+ const data = trimmed.slice(6);
152
177
 
153
- if (data === "[DONE]") return;
178
+ if (data === "[DONE]") return;
154
179
 
155
- const { encrypted } = JSON.parse(data);
156
- const chunk = this.#decryptResponse(encrypted, sharedSecret);
180
+ const { encrypted } = JSON.parse(data);
181
+ const chunk = this.#decryptResponse(encrypted, sharedSecret);
157
182
 
158
- if (chunk.seq !== expectedSeq) {
159
- throw new Error(`Chunk out of order: expected ${expectedSeq}, got ${chunk.seq}`);
160
- }
161
- expectedSeq++;
183
+ if (chunk.seq !== expectedSeq) {
184
+ throw new Error(`Chunk out of order: expected ${expectedSeq}, got ${chunk.seq}`);
185
+ }
186
+ expectedSeq++;
162
187
 
163
- yield chunk;
188
+ yield chunk;
189
+ }
164
190
  }
191
+ } catch (err) {
192
+ if (err.name === "AbortError") return;
193
+ throw err;
165
194
  }
166
195
  }
167
196
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@novaqore/ai",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "description": "The world's first private, quantum-encrypted LLM clients. CRYSTALS-Kyber + AES-256-GCM end-to-end encryption. Zero dependencies.",
5
5
  "main": "index.js",
6
6
  "files": [