@novaqore/ai 0.2.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 +8 -6
  2. package/index.js +46 -24
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -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 |
package/index.js CHANGED
@@ -10,7 +10,9 @@ class NovaQoreAI {
10
10
  #uid;
11
11
  #quantumKey;
12
12
  #keyId;
13
- #bearerToken;
13
+ #authToken;
14
+ #getAuthToken;
15
+ #abort;
14
16
 
15
17
  constructor(config) {
16
18
  if (config === undefined) {
@@ -46,12 +48,17 @@ class NovaQoreAI {
46
48
  this.#uid = config.uid;
47
49
  this.#quantumKey = config.quantumKey;
48
50
  this.#keyId = config.keyId;
49
- this.#bearerToken = config.bearerToken || null;
51
+ this.#authToken = config.authToken || null;
52
+ this.#getAuthToken = config.getAuthToken || null;
50
53
  this.version = version;
51
54
  this.description = "NovaQore AI - Quantum-encrypted LLM client by NovaQore";
52
55
  this.methods = ["chat", "health"];
53
56
  }
54
57
 
58
+ setAuthToken(token) {
59
+ this.#authToken = token;
60
+ }
61
+
55
62
  async #encryptPayload(payload) {
56
63
  const quantumKey = new Uint8Array(Buffer.from(this.#quantumKey, "base64"));
57
64
 
@@ -88,6 +95,8 @@ class NovaQoreAI {
88
95
  throw new Error("Messages must be a non-empty array");
89
96
  }
90
97
 
98
+ this.#abort = new AbortController();
99
+
91
100
  try {
92
101
  const payload = {
93
102
  messages,
@@ -100,8 +109,11 @@ class NovaQoreAI {
100
109
  const { ciphertext, encrypted, sharedSecret } = await this.#encryptPayload(payload);
101
110
 
102
111
  const headers = { "Content-Type": "application/json" };
103
- if (this.#bearerToken) {
104
- headers["Authorization"] = `Bearer ${this.#bearerToken}`;
112
+ if (this.#getAuthToken) {
113
+ this.#authToken = await this.#getAuthToken();
114
+ }
115
+ if (this.#authToken) {
116
+ headers["Authorization"] = `Bearer ${this.#authToken}`;
105
117
  }
106
118
 
107
119
  const res = await fetch(`${BASE_URL}/v1/chat/completions`, {
@@ -113,6 +125,7 @@ class NovaQoreAI {
113
125
  ciphertext,
114
126
  encrypted,
115
127
  }),
128
+ signal: this.#abort.signal,
116
129
  });
117
130
 
118
131
  if (!res.ok) {
@@ -127,12 +140,16 @@ class NovaQoreAI {
127
140
  }
128
141
 
129
142
  if (options.stream) {
130
- return this.#readStream(res, sharedSecret);
143
+ const stream = this.#readStream(res, sharedSecret);
144
+ const stop = () => this.#abort.abort();
145
+ return { stream, stop };
131
146
  }
132
147
 
133
148
  const { encrypted: encryptedResponse } = await res.json();
134
- return this.#decryptResponse(encryptedResponse, sharedSecret);
149
+ const result = this.#decryptResponse(encryptedResponse, sharedSecret);
150
+ return { result, stop: () => {} };
135
151
  } catch (err) {
152
+ if (err.name === "AbortError") return;
136
153
  if (err.message) throw err;
137
154
  throw new Error("Failed to connect to NovaQore AI");
138
155
  }
@@ -144,31 +161,36 @@ class NovaQoreAI {
144
161
  let buffer = "";
145
162
  let expectedSeq = 0;
146
163
 
147
- while (true) {
148
- const { done, value } = await reader.read();
149
- if (done) break;
150
- 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 });
151
169
 
152
- const lines = buffer.split("\n");
153
- buffer = lines.pop();
170
+ const lines = buffer.split("\n");
171
+ buffer = lines.pop();
154
172
 
155
- for (const line of lines) {
156
- const trimmed = line.trim();
157
- if (!trimmed || !trimmed.startsWith("data: ")) continue;
158
- 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);
159
177
 
160
- if (data === "[DONE]") return;
178
+ if (data === "[DONE]") return;
161
179
 
162
- const { encrypted } = JSON.parse(data);
163
- const chunk = this.#decryptResponse(encrypted, sharedSecret);
180
+ const { encrypted } = JSON.parse(data);
181
+ const chunk = this.#decryptResponse(encrypted, sharedSecret);
164
182
 
165
- if (chunk.seq !== expectedSeq) {
166
- throw new Error(`Chunk out of order: expected ${expectedSeq}, got ${chunk.seq}`);
167
- }
168
- expectedSeq++;
183
+ if (chunk.seq !== expectedSeq) {
184
+ throw new Error(`Chunk out of order: expected ${expectedSeq}, got ${chunk.seq}`);
185
+ }
186
+ expectedSeq++;
169
187
 
170
- yield chunk;
188
+ yield chunk;
189
+ }
171
190
  }
191
+ } catch (err) {
192
+ if (err.name === "AbortError") return;
193
+ throw err;
172
194
  }
173
195
  }
174
196
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@novaqore/ai",
3
- "version": "0.2.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": [