nadir-sdk 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 +94 -0
- package/dist/index.cjs +337 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +268 -0
- package/dist/index.d.ts +268 -0
- package/dist/index.js +304 -0
- package/dist/index.js.map +1 -0
- package/package.json +52 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
// src/errors.ts
|
|
2
|
+
var NadirError = class extends Error {
|
|
3
|
+
status;
|
|
4
|
+
body;
|
|
5
|
+
errorType;
|
|
6
|
+
constructor(message, options = {}) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.name = "NadirError";
|
|
9
|
+
this.status = options.status ?? 0;
|
|
10
|
+
this.body = options.body;
|
|
11
|
+
this.errorType = options.errorType;
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
var AuthenticationError = class extends NadirError {
|
|
15
|
+
constructor(message, body) {
|
|
16
|
+
super(message, { status: 401, body });
|
|
17
|
+
this.name = "AuthenticationError";
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
var RateLimitError = class extends NadirError {
|
|
21
|
+
retryAfter;
|
|
22
|
+
constructor(message, options = {}) {
|
|
23
|
+
super(message, { status: 429, body: options.body });
|
|
24
|
+
this.name = "RateLimitError";
|
|
25
|
+
this.retryAfter = options.retryAfter;
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
var APIError = class extends NadirError {
|
|
29
|
+
constructor(message, status, body) {
|
|
30
|
+
super(message, { status, body });
|
|
31
|
+
this.name = "APIError";
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
var ConnectionError = class extends NadirError {
|
|
35
|
+
constructor(message) {
|
|
36
|
+
super(message, { status: 0 });
|
|
37
|
+
this.name = "ConnectionError";
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
// src/streaming.ts
|
|
42
|
+
var Stream = class {
|
|
43
|
+
reader;
|
|
44
|
+
decoder = new TextDecoder();
|
|
45
|
+
buffer = "";
|
|
46
|
+
done = false;
|
|
47
|
+
parse;
|
|
48
|
+
constructor(body, parse) {
|
|
49
|
+
this.reader = body.getReader();
|
|
50
|
+
this.parse = parse;
|
|
51
|
+
}
|
|
52
|
+
async *[Symbol.asyncIterator]() {
|
|
53
|
+
try {
|
|
54
|
+
while (!this.done) {
|
|
55
|
+
const { value, done } = await this.reader.read();
|
|
56
|
+
if (done) {
|
|
57
|
+
this.done = true;
|
|
58
|
+
yield* this.processBuffer(true);
|
|
59
|
+
break;
|
|
60
|
+
}
|
|
61
|
+
this.buffer += this.decoder.decode(value, { stream: true });
|
|
62
|
+
yield* this.processBuffer(false);
|
|
63
|
+
}
|
|
64
|
+
} finally {
|
|
65
|
+
this.reader.releaseLock();
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
*processBuffer(flush) {
|
|
69
|
+
const lines = this.buffer.split("\n");
|
|
70
|
+
if (!flush) {
|
|
71
|
+
this.buffer = lines.pop() ?? "";
|
|
72
|
+
} else {
|
|
73
|
+
this.buffer = "";
|
|
74
|
+
}
|
|
75
|
+
for (const line of lines) {
|
|
76
|
+
const trimmed = line.trim();
|
|
77
|
+
if (!trimmed || !trimmed.startsWith("data: ")) {
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
const payload = trimmed.slice(6);
|
|
81
|
+
if (payload === "[DONE]") {
|
|
82
|
+
this.done = true;
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
try {
|
|
86
|
+
const data = JSON.parse(payload);
|
|
87
|
+
yield this.parse(data);
|
|
88
|
+
} catch {
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
function parseChatCompletionChunk(data) {
|
|
94
|
+
return data;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// src/client.ts
|
|
98
|
+
var DEFAULT_BASE_URL = "https://api.getnadir.dev";
|
|
99
|
+
var SDK_VERSION = "0.1.0";
|
|
100
|
+
var Completions = class {
|
|
101
|
+
constructor(client) {
|
|
102
|
+
this.client = client;
|
|
103
|
+
}
|
|
104
|
+
client;
|
|
105
|
+
async create(params) {
|
|
106
|
+
if (params.stream) {
|
|
107
|
+
const response = await this.client._rawRequest(
|
|
108
|
+
"POST",
|
|
109
|
+
"/v1/chat/completions",
|
|
110
|
+
params
|
|
111
|
+
);
|
|
112
|
+
if (!response.body) {
|
|
113
|
+
throw new ConnectionError("Response body is null \u2014 streaming not supported");
|
|
114
|
+
}
|
|
115
|
+
return new Stream(
|
|
116
|
+
response.body,
|
|
117
|
+
parseChatCompletionChunk
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
return this.client._request(
|
|
121
|
+
"POST",
|
|
122
|
+
"/v1/chat/completions",
|
|
123
|
+
params
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
var Chat = class {
|
|
128
|
+
completions;
|
|
129
|
+
constructor(client) {
|
|
130
|
+
this.completions = new Completions(client);
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
var SmartExport = class {
|
|
134
|
+
constructor(client) {
|
|
135
|
+
this.client = client;
|
|
136
|
+
}
|
|
137
|
+
client;
|
|
138
|
+
async status() {
|
|
139
|
+
return this.client._request("GET", "/v1/smart-export/status");
|
|
140
|
+
}
|
|
141
|
+
async train(clusterId, options = {}) {
|
|
142
|
+
return this.client._request(
|
|
143
|
+
"POST",
|
|
144
|
+
`/v1/smart-export/clusters/${clusterId}/train`,
|
|
145
|
+
{ base_model: options.baseModel ?? "gpt-4o-mini" }
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
async exportData(clusterId, options = {}) {
|
|
149
|
+
return this.client._request(
|
|
150
|
+
"POST",
|
|
151
|
+
`/v1/smart-export/clusters/${clusterId}/export`,
|
|
152
|
+
{ format: options.format ?? "jsonl" }
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
async listJobs() {
|
|
156
|
+
return this.client._request("GET", "/v1/smart-export/jobs");
|
|
157
|
+
}
|
|
158
|
+
async getJob(jobId) {
|
|
159
|
+
return this.client._request("GET", `/v1/smart-export/jobs/${jobId}`);
|
|
160
|
+
}
|
|
161
|
+
async cancelJob(jobId) {
|
|
162
|
+
return this.client._request("DELETE", `/v1/smart-export/jobs/${jobId}`);
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
var Clustering = class {
|
|
166
|
+
constructor(client) {
|
|
167
|
+
this.client = client;
|
|
168
|
+
}
|
|
169
|
+
client;
|
|
170
|
+
async classify(prompt) {
|
|
171
|
+
return this.client._request("POST", "/v1/clustering/classify", { prompt });
|
|
172
|
+
}
|
|
173
|
+
async listClusters() {
|
|
174
|
+
return this.client._request("GET", "/v1/clustering/clusters");
|
|
175
|
+
}
|
|
176
|
+
async createCluster(params) {
|
|
177
|
+
return this.client._request("POST", "/v1/clustering/clusters", params);
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
var Organizations = class {
|
|
181
|
+
constructor(client) {
|
|
182
|
+
this.client = client;
|
|
183
|
+
}
|
|
184
|
+
client;
|
|
185
|
+
async list() {
|
|
186
|
+
return this.client._request("GET", "/v1/organizations");
|
|
187
|
+
}
|
|
188
|
+
async create(params) {
|
|
189
|
+
return this.client._request("POST", "/v1/organizations", params);
|
|
190
|
+
}
|
|
191
|
+
async get(orgId) {
|
|
192
|
+
return this.client._request("GET", `/v1/organizations/${orgId}`);
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
var NadirClient = class {
|
|
196
|
+
apiKey;
|
|
197
|
+
baseURL;
|
|
198
|
+
timeout;
|
|
199
|
+
/** OpenAI-compatible chat completions namespace. */
|
|
200
|
+
chat;
|
|
201
|
+
/** Smart Export / distillation endpoints. */
|
|
202
|
+
smartExport;
|
|
203
|
+
/** Prompt clustering endpoints. */
|
|
204
|
+
clustering;
|
|
205
|
+
/** Organization management endpoints. */
|
|
206
|
+
organizations;
|
|
207
|
+
constructor(options = {}) {
|
|
208
|
+
this.apiKey = options.apiKey ?? (typeof process !== "undefined" ? process.env.NADIR_API_KEY ?? "" : "");
|
|
209
|
+
this.baseURL = (options.baseURL ?? (typeof process !== "undefined" ? process.env.NADIR_BASE_URL ?? DEFAULT_BASE_URL : DEFAULT_BASE_URL)).replace(/\/+$/, "");
|
|
210
|
+
this.timeout = options.timeout ?? 12e4;
|
|
211
|
+
this.chat = new Chat(this);
|
|
212
|
+
this.smartExport = new SmartExport(this);
|
|
213
|
+
this.clustering = new Clustering(this);
|
|
214
|
+
this.organizations = new Organizations(this);
|
|
215
|
+
}
|
|
216
|
+
// ── Convenience methods ─────────────────────────────────────────
|
|
217
|
+
/** Get a model recommendation without making an LLM call. */
|
|
218
|
+
async recommend(params) {
|
|
219
|
+
return this._request("POST", "/v1/public/recommendation", params);
|
|
220
|
+
}
|
|
221
|
+
/** List available models. */
|
|
222
|
+
async listModels() {
|
|
223
|
+
return this._request("GET", "/v1/models");
|
|
224
|
+
}
|
|
225
|
+
/** List user presets. */
|
|
226
|
+
async listPresets() {
|
|
227
|
+
return this._request("GET", "/v1/user/presets");
|
|
228
|
+
}
|
|
229
|
+
/** Check API health. */
|
|
230
|
+
async health() {
|
|
231
|
+
return this._request("GET", "/health");
|
|
232
|
+
}
|
|
233
|
+
// ── HTTP internals (public for namespace classes) ───────────────
|
|
234
|
+
/** @internal */
|
|
235
|
+
async _request(method, path, body) {
|
|
236
|
+
const response = await this._rawRequest(method, path, body);
|
|
237
|
+
return await response.json();
|
|
238
|
+
}
|
|
239
|
+
/** @internal — returns the raw Response (for streaming). */
|
|
240
|
+
async _rawRequest(method, path, body) {
|
|
241
|
+
const url = `${this.baseURL}${path}`;
|
|
242
|
+
const headers = {
|
|
243
|
+
"Content-Type": "application/json",
|
|
244
|
+
"X-API-Key": this.apiKey,
|
|
245
|
+
"User-Agent": `nadir-node/${SDK_VERSION}`
|
|
246
|
+
};
|
|
247
|
+
let response;
|
|
248
|
+
try {
|
|
249
|
+
response = await fetch(url, {
|
|
250
|
+
method,
|
|
251
|
+
headers,
|
|
252
|
+
body: body != null ? JSON.stringify(body) : void 0,
|
|
253
|
+
signal: AbortSignal.timeout(this.timeout)
|
|
254
|
+
});
|
|
255
|
+
} catch (err) {
|
|
256
|
+
if (err instanceof Error && err.name === "TimeoutError") {
|
|
257
|
+
throw new ConnectionError(
|
|
258
|
+
`Request timed out after ${this.timeout}ms: ${method} ${path}`
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
throw new ConnectionError(
|
|
262
|
+
`Failed to connect to ${this.baseURL}: ${err instanceof Error ? err.message : String(err)}`
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
if (response.ok) {
|
|
266
|
+
return response;
|
|
267
|
+
}
|
|
268
|
+
let errorBody;
|
|
269
|
+
let detail = "";
|
|
270
|
+
try {
|
|
271
|
+
errorBody = await response.json();
|
|
272
|
+
if (typeof errorBody === "object" && errorBody !== null) {
|
|
273
|
+
const obj = errorBody;
|
|
274
|
+
detail = obj.detail ?? obj.error?.message ?? "";
|
|
275
|
+
}
|
|
276
|
+
} catch {
|
|
277
|
+
detail = await response.text().catch(() => "");
|
|
278
|
+
}
|
|
279
|
+
if (!detail) {
|
|
280
|
+
detail = `HTTP ${response.status}`;
|
|
281
|
+
}
|
|
282
|
+
if (response.status === 401) {
|
|
283
|
+
throw new AuthenticationError(detail, errorBody);
|
|
284
|
+
}
|
|
285
|
+
if (response.status === 429) {
|
|
286
|
+
const retryAfter = response.headers.get("retry-after");
|
|
287
|
+
throw new RateLimitError(detail, {
|
|
288
|
+
body: errorBody,
|
|
289
|
+
retryAfter: retryAfter ? parseFloat(retryAfter) : void 0
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
throw new APIError(detail, response.status, errorBody);
|
|
293
|
+
}
|
|
294
|
+
};
|
|
295
|
+
export {
|
|
296
|
+
APIError,
|
|
297
|
+
AuthenticationError,
|
|
298
|
+
ConnectionError,
|
|
299
|
+
NadirClient,
|
|
300
|
+
NadirError,
|
|
301
|
+
RateLimitError,
|
|
302
|
+
Stream
|
|
303
|
+
};
|
|
304
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/errors.ts","../src/streaming.ts","../src/client.ts"],"sourcesContent":["/** Base error for all Nadir API errors. */\nexport class NadirError extends Error {\n readonly status: number;\n readonly body: unknown;\n readonly errorType: string | undefined;\n\n constructor(\n message: string,\n options: { status?: number; body?: unknown; errorType?: string } = {},\n ) {\n super(message);\n this.name = \"NadirError\";\n this.status = options.status ?? 0;\n this.body = options.body;\n this.errorType = options.errorType;\n }\n}\n\n/** Raised on 401 Unauthorized — invalid or missing API key. */\nexport class AuthenticationError extends NadirError {\n constructor(message: string, body?: unknown) {\n super(message, { status: 401, body });\n this.name = \"AuthenticationError\";\n }\n}\n\n/** Raised on 429 Too Many Requests. */\nexport class RateLimitError extends NadirError {\n readonly retryAfter: number | undefined;\n\n constructor(\n message: string,\n options: { body?: unknown; retryAfter?: number } = {},\n ) {\n super(message, { status: 429, body: options.body });\n this.name = \"RateLimitError\";\n this.retryAfter = options.retryAfter;\n }\n}\n\n/** Raised on server errors (4xx/5xx) not covered by a more specific class. */\nexport class APIError extends NadirError {\n constructor(message: string, status: number, body?: unknown) {\n super(message, { status, body });\n this.name = \"APIError\";\n }\n}\n\n/** Raised when the SDK cannot reach the Nadir API. */\nexport class ConnectionError extends NadirError {\n constructor(message: string) {\n super(message, { status: 0 });\n this.name = \"ConnectionError\";\n }\n}\n","import type { ChatCompletionChunk } from \"./types.js\";\n\n/**\n * Async iterable wrapper around an SSE response stream.\n *\n * Usage:\n * ```ts\n * for await (const chunk of stream) {\n * process.stdout.write(chunk.choices[0]?.delta?.content ?? \"\");\n * }\n * ```\n */\nexport class Stream<T> implements AsyncIterable<T> {\n private reader: ReadableStreamDefaultReader<Uint8Array>;\n private decoder = new TextDecoder();\n private buffer = \"\";\n private done = false;\n private parse: (data: unknown) => T;\n\n constructor(\n body: ReadableStream<Uint8Array>,\n parse: (data: unknown) => T,\n ) {\n this.reader = body.getReader();\n this.parse = parse;\n }\n\n async *[Symbol.asyncIterator](): AsyncIterator<T> {\n try {\n while (!this.done) {\n const { value, done } = await this.reader.read();\n if (done) {\n this.done = true;\n // Process any remaining buffer\n yield* this.processBuffer(true);\n break;\n }\n\n this.buffer += this.decoder.decode(value, { stream: true });\n yield* this.processBuffer(false);\n }\n } finally {\n this.reader.releaseLock();\n }\n }\n\n private *processBuffer(flush: boolean): Generator<T> {\n const lines = this.buffer.split(\"\\n\");\n\n // Keep the last incomplete line in the buffer (unless flushing)\n if (!flush) {\n this.buffer = lines.pop() ?? \"\";\n } else {\n this.buffer = \"\";\n }\n\n for (const line of lines) {\n const trimmed = line.trim();\n if (!trimmed || !trimmed.startsWith(\"data: \")) {\n continue;\n }\n const payload = trimmed.slice(6);\n if (payload === \"[DONE]\") {\n this.done = true;\n return;\n }\n try {\n const data = JSON.parse(payload);\n yield this.parse(data);\n } catch {\n // Skip malformed JSON lines\n }\n }\n }\n}\n\n/** Identity parser for ChatCompletionChunk (the API returns the correct shape). */\nexport function parseChatCompletionChunk(data: unknown): ChatCompletionChunk {\n return data as ChatCompletionChunk;\n}\n","/**\n * Nadir Node.js SDK — OpenAI-compatible client for the Nadir LLM router.\n *\n * @example\n * ```ts\n * import { NadirClient } from \"nadir-sdk\";\n *\n * const client = new NadirClient({ apiKey: \"ndr_...\" });\n *\n * // Chat completion — Nadir picks the optimal model\n * const response = await client.chat.completions.create({\n * messages: [{ role: \"user\", content: \"Hello!\" }],\n * });\n * console.log(response.choices[0].message?.content);\n *\n * // Streaming\n * const stream = await client.chat.completions.create({\n * messages: [{ role: \"user\", content: \"Tell me a story\" }],\n * stream: true,\n * });\n * for await (const chunk of stream) {\n * process.stdout.write(chunk.choices[0]?.delta?.content ?? \"\");\n * }\n * ```\n */\n\nimport {\n APIError,\n AuthenticationError,\n ConnectionError,\n RateLimitError,\n} from \"./errors.js\";\nimport { Stream, parseChatCompletionChunk } from \"./streaming.js\";\nimport type {\n ChatCompletion,\n ChatCompletionChunk,\n ChatCompletionCreateParams,\n CreateClusterParams,\n CreateOrganizationParams,\n RecommendParams,\n} from \"./types.js\";\n\nconst DEFAULT_BASE_URL = \"https://api.getnadir.dev\";\nconst SDK_VERSION = \"0.1.0\";\n\n// ---------------------------------------------------------------------------\n// Client options\n// ---------------------------------------------------------------------------\n\nexport interface NadirClientOptions {\n /** Nadir API key (`ndr_...`). Falls back to `NADIR_API_KEY` env var. */\n apiKey?: string;\n /** API base URL. Falls back to `NADIR_BASE_URL` env var. */\n baseURL?: string;\n /** Request timeout in milliseconds (default 120_000). */\n timeout?: number;\n}\n\n// ---------------------------------------------------------------------------\n// Namespace classes\n// ---------------------------------------------------------------------------\n\nclass Completions {\n constructor(private client: NadirClient) {}\n\n /** Create a chat completion (non-streaming). */\n async create(\n params: ChatCompletionCreateParams & { stream?: false },\n ): Promise<ChatCompletion>;\n /** Create a streaming chat completion. */\n async create(\n params: ChatCompletionCreateParams & { stream: true },\n ): Promise<Stream<ChatCompletionChunk>>;\n /** Create a chat completion (overload resolution). */\n async create(\n params: ChatCompletionCreateParams,\n ): Promise<ChatCompletion | Stream<ChatCompletionChunk>>;\n async create(\n params: ChatCompletionCreateParams,\n ): Promise<ChatCompletion | Stream<ChatCompletionChunk>> {\n if (params.stream) {\n const response = await this.client._rawRequest(\n \"POST\",\n \"/v1/chat/completions\",\n params,\n );\n\n if (!response.body) {\n throw new ConnectionError(\"Response body is null — streaming not supported\");\n }\n\n return new Stream<ChatCompletionChunk>(\n response.body,\n parseChatCompletionChunk,\n );\n }\n\n return this.client._request<ChatCompletion>(\n \"POST\",\n \"/v1/chat/completions\",\n params,\n );\n }\n}\n\nclass Chat {\n readonly completions: Completions;\n constructor(client: NadirClient) {\n this.completions = new Completions(client);\n }\n}\n\nclass SmartExport {\n constructor(private client: NadirClient) {}\n\n async status(): Promise<Record<string, unknown>> {\n return this.client._request(\"GET\", \"/v1/smart-export/status\");\n }\n\n async train(\n clusterId: string,\n options: { baseModel?: string } = {},\n ): Promise<Record<string, unknown>> {\n return this.client._request(\n \"POST\",\n `/v1/smart-export/clusters/${clusterId}/train`,\n { base_model: options.baseModel ?? \"gpt-4o-mini\" },\n );\n }\n\n async exportData(\n clusterId: string,\n options: { format?: string } = {},\n ): Promise<Record<string, unknown>> {\n return this.client._request(\n \"POST\",\n `/v1/smart-export/clusters/${clusterId}/export`,\n { format: options.format ?? \"jsonl\" },\n );\n }\n\n async listJobs(): Promise<Record<string, unknown>> {\n return this.client._request(\"GET\", \"/v1/smart-export/jobs\");\n }\n\n async getJob(jobId: string): Promise<Record<string, unknown>> {\n return this.client._request(\"GET\", `/v1/smart-export/jobs/${jobId}`);\n }\n\n async cancelJob(jobId: string): Promise<Record<string, unknown>> {\n return this.client._request(\"DELETE\", `/v1/smart-export/jobs/${jobId}`);\n }\n}\n\nclass Clustering {\n constructor(private client: NadirClient) {}\n\n async classify(prompt: string): Promise<Record<string, unknown>> {\n return this.client._request(\"POST\", \"/v1/clustering/classify\", { prompt });\n }\n\n async listClusters(): Promise<Record<string, unknown>> {\n return this.client._request(\"GET\", \"/v1/clustering/clusters\");\n }\n\n async createCluster(params: CreateClusterParams): Promise<Record<string, unknown>> {\n return this.client._request(\"POST\", \"/v1/clustering/clusters\", params);\n }\n}\n\nclass Organizations {\n constructor(private client: NadirClient) {}\n\n async list(): Promise<Record<string, unknown>> {\n return this.client._request(\"GET\", \"/v1/organizations\");\n }\n\n async create(params: CreateOrganizationParams): Promise<Record<string, unknown>> {\n return this.client._request(\"POST\", \"/v1/organizations\", params);\n }\n\n async get(orgId: string): Promise<Record<string, unknown>> {\n return this.client._request(\"GET\", `/v1/organizations/${orgId}`);\n }\n}\n\n// ---------------------------------------------------------------------------\n// Main client\n// ---------------------------------------------------------------------------\n\nexport class NadirClient {\n private readonly apiKey: string;\n private readonly baseURL: string;\n private readonly timeout: number;\n\n /** OpenAI-compatible chat completions namespace. */\n readonly chat: Chat;\n /** Smart Export / distillation endpoints. */\n readonly smartExport: SmartExport;\n /** Prompt clustering endpoints. */\n readonly clustering: Clustering;\n /** Organization management endpoints. */\n readonly organizations: Organizations;\n\n constructor(options: NadirClientOptions = {}) {\n this.apiKey =\n options.apiKey ??\n (typeof process !== \"undefined\" ? process.env.NADIR_API_KEY ?? \"\" : \"\");\n this.baseURL = (\n options.baseURL ??\n (typeof process !== \"undefined\"\n ? process.env.NADIR_BASE_URL ?? DEFAULT_BASE_URL\n : DEFAULT_BASE_URL)\n ).replace(/\\/+$/, \"\");\n this.timeout = options.timeout ?? 120_000;\n\n this.chat = new Chat(this);\n this.smartExport = new SmartExport(this);\n this.clustering = new Clustering(this);\n this.organizations = new Organizations(this);\n }\n\n // ── Convenience methods ─────────────────────────────────────────\n\n /** Get a model recommendation without making an LLM call. */\n async recommend(params: RecommendParams): Promise<Record<string, unknown>> {\n return this._request(\"POST\", \"/v1/public/recommendation\", params);\n }\n\n /** List available models. */\n async listModels(): Promise<Record<string, unknown>> {\n return this._request(\"GET\", \"/v1/models\");\n }\n\n /** List user presets. */\n async listPresets(): Promise<Record<string, unknown>> {\n return this._request(\"GET\", \"/v1/user/presets\");\n }\n\n /** Check API health. */\n async health(): Promise<Record<string, unknown>> {\n return this._request(\"GET\", \"/health\");\n }\n\n // ── HTTP internals (public for namespace classes) ───────────────\n\n /** @internal */\n async _request<T = Record<string, unknown>>(\n method: string,\n path: string,\n body?: unknown,\n ): Promise<T> {\n const response = await this._rawRequest(method, path, body);\n return (await response.json()) as T;\n }\n\n /** @internal — returns the raw Response (for streaming). */\n async _rawRequest(\n method: string,\n path: string,\n body?: unknown,\n ): Promise<Response> {\n const url = `${this.baseURL}${path}`;\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n \"X-API-Key\": this.apiKey,\n \"User-Agent\": `nadir-node/${SDK_VERSION}`,\n };\n\n let response: Response;\n try {\n response = await fetch(url, {\n method,\n headers,\n body: body != null ? JSON.stringify(body) : undefined,\n signal: AbortSignal.timeout(this.timeout),\n });\n } catch (err: unknown) {\n if (err instanceof Error && err.name === \"TimeoutError\") {\n throw new ConnectionError(\n `Request timed out after ${this.timeout}ms: ${method} ${path}`,\n );\n }\n throw new ConnectionError(\n `Failed to connect to ${this.baseURL}: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n\n if (response.ok) {\n return response;\n }\n\n // Parse error body\n let errorBody: unknown;\n let detail = \"\";\n try {\n errorBody = await response.json();\n if (typeof errorBody === \"object\" && errorBody !== null) {\n const obj = errorBody as Record<string, unknown>;\n detail =\n (obj.detail as string) ??\n ((obj.error as Record<string, unknown>)?.message as string) ??\n \"\";\n }\n } catch {\n detail = await response.text().catch(() => \"\");\n }\n\n if (!detail) {\n detail = `HTTP ${response.status}`;\n }\n\n if (response.status === 401) {\n throw new AuthenticationError(detail, errorBody);\n }\n if (response.status === 429) {\n const retryAfter = response.headers.get(\"retry-after\");\n throw new RateLimitError(detail, {\n body: errorBody,\n retryAfter: retryAfter ? parseFloat(retryAfter) : undefined,\n });\n }\n throw new APIError(detail, response.status, errorBody);\n }\n}\n"],"mappings":"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","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "nadir-sdk",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Official Node.js SDK for the Nadir LLM Router — intelligent model routing that cuts API costs 30-60%",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.cjs",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"import": "./dist/index.js",
|
|
12
|
+
"require": "./dist/index.cjs",
|
|
13
|
+
"types": "./dist/index.d.ts"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist",
|
|
18
|
+
"README.md"
|
|
19
|
+
],
|
|
20
|
+
"engines": {
|
|
21
|
+
"node": ">=18"
|
|
22
|
+
},
|
|
23
|
+
"scripts": {
|
|
24
|
+
"build": "tsup",
|
|
25
|
+
"typecheck": "tsc --noEmit",
|
|
26
|
+
"test": "vitest run",
|
|
27
|
+
"test:watch": "vitest",
|
|
28
|
+
"prepublishOnly": "npm run build"
|
|
29
|
+
},
|
|
30
|
+
"keywords": [
|
|
31
|
+
"nadir",
|
|
32
|
+
"llm",
|
|
33
|
+
"router",
|
|
34
|
+
"openai",
|
|
35
|
+
"anthropic",
|
|
36
|
+
"ai",
|
|
37
|
+
"sdk"
|
|
38
|
+
],
|
|
39
|
+
"author": "Nadir <support@getnadir.dev>",
|
|
40
|
+
"license": "MIT",
|
|
41
|
+
"homepage": "https://getnadir.dev",
|
|
42
|
+
"repository": {
|
|
43
|
+
"type": "git",
|
|
44
|
+
"url": "https://github.com/NadirRouter/nadir-node"
|
|
45
|
+
},
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"@types/node": "^20.0.0",
|
|
48
|
+
"tsup": "^8.0.0",
|
|
49
|
+
"typescript": "^5.4.0",
|
|
50
|
+
"vitest": "^2.0.0"
|
|
51
|
+
}
|
|
52
|
+
}
|