@modelrelay/sdk 0.2.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/LICENSE +201 -0
- package/README.md +86 -0
- package/dist/index.cjs +960 -0
- package/dist/index.d.cts +449 -0
- package/dist/index.d.ts +449 -0
- package/dist/index.js +920 -0
- package/package.json +39 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,960 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
ApiKeysClient: () => ApiKeysClient,
|
|
24
|
+
AuthClient: () => AuthClient,
|
|
25
|
+
BillingClient: () => BillingClient,
|
|
26
|
+
ChatClient: () => ChatClient,
|
|
27
|
+
ChatCompletionsStream: () => ChatCompletionsStream,
|
|
28
|
+
DEFAULT_BASE_URL: () => DEFAULT_BASE_URL,
|
|
29
|
+
DEFAULT_CLIENT_HEADER: () => DEFAULT_CLIENT_HEADER,
|
|
30
|
+
DEFAULT_REQUEST_TIMEOUT_MS: () => DEFAULT_REQUEST_TIMEOUT_MS,
|
|
31
|
+
ModelRelay: () => ModelRelay,
|
|
32
|
+
ModelRelayError: () => ModelRelayError,
|
|
33
|
+
SANDBOX_BASE_URL: () => SANDBOX_BASE_URL,
|
|
34
|
+
SDK_VERSION: () => SDK_VERSION,
|
|
35
|
+
STAGING_BASE_URL: () => STAGING_BASE_URL,
|
|
36
|
+
isPublishableKey: () => isPublishableKey
|
|
37
|
+
});
|
|
38
|
+
module.exports = __toCommonJS(index_exports);
|
|
39
|
+
|
|
40
|
+
// src/errors.ts
|
|
41
|
+
var ModelRelayError = class extends Error {
|
|
42
|
+
constructor(message, opts) {
|
|
43
|
+
super(message);
|
|
44
|
+
this.name = "ModelRelayError";
|
|
45
|
+
this.status = opts.status;
|
|
46
|
+
this.code = opts.code;
|
|
47
|
+
this.requestId = opts.requestId;
|
|
48
|
+
this.fields = opts.fields;
|
|
49
|
+
this.data = opts.data;
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
async function parseErrorResponse(response) {
|
|
53
|
+
const requestId = response.headers.get("X-ModelRelay-Chat-Request-Id") || response.headers.get("X-Request-Id") || void 0;
|
|
54
|
+
const fallbackMessage = response.statusText || "Request failed";
|
|
55
|
+
const status = response.status || 500;
|
|
56
|
+
let bodyText = "";
|
|
57
|
+
try {
|
|
58
|
+
bodyText = await response.text();
|
|
59
|
+
} catch {
|
|
60
|
+
}
|
|
61
|
+
if (!bodyText) {
|
|
62
|
+
return new ModelRelayError(fallbackMessage, { status, requestId });
|
|
63
|
+
}
|
|
64
|
+
try {
|
|
65
|
+
const parsed = JSON.parse(bodyText);
|
|
66
|
+
const parsedObj = typeof parsed === "object" && parsed !== null ? parsed : null;
|
|
67
|
+
if (parsedObj?.error) {
|
|
68
|
+
const errPayload = typeof parsedObj.error === "object" && parsedObj.error !== null ? parsedObj.error : null;
|
|
69
|
+
const message = errPayload?.message || fallbackMessage;
|
|
70
|
+
const code = errPayload?.code || void 0;
|
|
71
|
+
const fields = Array.isArray(errPayload?.fields) ? errPayload?.fields : void 0;
|
|
72
|
+
const parsedStatus = typeof errPayload?.status === "number" ? errPayload.status : status;
|
|
73
|
+
return new ModelRelayError(message, {
|
|
74
|
+
status: parsedStatus,
|
|
75
|
+
code,
|
|
76
|
+
fields,
|
|
77
|
+
requestId: parsedObj?.request_id || parsedObj?.requestId || requestId,
|
|
78
|
+
data: parsed
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
if (parsedObj?.message || parsedObj?.code) {
|
|
82
|
+
const message = parsedObj.message || fallbackMessage;
|
|
83
|
+
return new ModelRelayError(message, {
|
|
84
|
+
status,
|
|
85
|
+
code: parsedObj.code,
|
|
86
|
+
fields: parsedObj.fields,
|
|
87
|
+
requestId: parsedObj?.request_id || parsedObj?.requestId || requestId,
|
|
88
|
+
data: parsed
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
return new ModelRelayError(fallbackMessage, {
|
|
92
|
+
status,
|
|
93
|
+
requestId,
|
|
94
|
+
data: parsed
|
|
95
|
+
});
|
|
96
|
+
} catch {
|
|
97
|
+
return new ModelRelayError(bodyText, { status, requestId });
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// src/auth.ts
|
|
102
|
+
var AuthClient = class {
|
|
103
|
+
constructor(http, cfg) {
|
|
104
|
+
this.cachedFrontend = /* @__PURE__ */ new Map();
|
|
105
|
+
this.http = http;
|
|
106
|
+
this.apiKey = cfg.apiKey;
|
|
107
|
+
this.accessToken = cfg.accessToken;
|
|
108
|
+
this.endUser = cfg.endUser;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Exchange a publishable key for a short-lived frontend token.
|
|
112
|
+
* Tokens are cached until they are close to expiry.
|
|
113
|
+
*/
|
|
114
|
+
async frontendToken(request) {
|
|
115
|
+
const publishableKey = request?.publishableKey || (isPublishableKey(this.apiKey) ? this.apiKey : void 0);
|
|
116
|
+
if (!publishableKey) {
|
|
117
|
+
throw new ModelRelayError(
|
|
118
|
+
"publishable key required to issue frontend tokens",
|
|
119
|
+
{ status: 400 }
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
const userId = request?.userId || this.endUser?.id;
|
|
123
|
+
if (!userId) {
|
|
124
|
+
throw new ModelRelayError(
|
|
125
|
+
"endUserId is required to mint a frontend token",
|
|
126
|
+
{ status: 400 }
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
const deviceId = request?.deviceId || this.endUser?.deviceId;
|
|
130
|
+
const ttlSeconds = request?.ttlSeconds ?? this.endUser?.ttlSeconds;
|
|
131
|
+
const cacheKey = `${publishableKey}:${userId}:${deviceId || ""}`;
|
|
132
|
+
const cached = this.cachedFrontend.get(cacheKey);
|
|
133
|
+
if (cached && isTokenReusable(cached)) {
|
|
134
|
+
return cached;
|
|
135
|
+
}
|
|
136
|
+
const payload = {
|
|
137
|
+
publishable_key: publishableKey,
|
|
138
|
+
user_id: userId
|
|
139
|
+
};
|
|
140
|
+
if (deviceId) {
|
|
141
|
+
payload.device_id = deviceId;
|
|
142
|
+
}
|
|
143
|
+
if (typeof ttlSeconds === "number" && ttlSeconds > 0) {
|
|
144
|
+
payload.ttl_seconds = ttlSeconds;
|
|
145
|
+
}
|
|
146
|
+
const response = await this.http.json(
|
|
147
|
+
"/auth/frontend-token",
|
|
148
|
+
{
|
|
149
|
+
method: "POST",
|
|
150
|
+
body: payload
|
|
151
|
+
}
|
|
152
|
+
);
|
|
153
|
+
const token = normalizeFrontendToken(response, {
|
|
154
|
+
publishableKey,
|
|
155
|
+
userId,
|
|
156
|
+
deviceId
|
|
157
|
+
});
|
|
158
|
+
this.cachedFrontend.set(cacheKey, token);
|
|
159
|
+
return token;
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Determine the correct auth headers for chat completions.
|
|
163
|
+
* Publishable keys are automatically exchanged for frontend tokens.
|
|
164
|
+
*/
|
|
165
|
+
async authForChat(endUserId, overrides) {
|
|
166
|
+
if (this.accessToken) {
|
|
167
|
+
return { accessToken: this.accessToken };
|
|
168
|
+
}
|
|
169
|
+
if (!this.apiKey) {
|
|
170
|
+
throw new ModelRelayError("API key or token is required", {
|
|
171
|
+
status: 401
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
if (isPublishableKey(this.apiKey)) {
|
|
175
|
+
const token = await this.frontendToken({
|
|
176
|
+
userId: endUserId || overrides?.id,
|
|
177
|
+
deviceId: overrides?.deviceId,
|
|
178
|
+
ttlSeconds: overrides?.ttlSeconds
|
|
179
|
+
});
|
|
180
|
+
return { accessToken: token.token };
|
|
181
|
+
}
|
|
182
|
+
return { apiKey: this.apiKey };
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Billing calls accept either bearer tokens or API keys (including publishable keys).
|
|
186
|
+
*/
|
|
187
|
+
authForBilling() {
|
|
188
|
+
if (this.accessToken) {
|
|
189
|
+
return { accessToken: this.accessToken };
|
|
190
|
+
}
|
|
191
|
+
if (!this.apiKey) {
|
|
192
|
+
throw new ModelRelayError("API key or token is required", {
|
|
193
|
+
status: 401
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
return { apiKey: this.apiKey };
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
function isPublishableKey(value) {
|
|
200
|
+
if (!value) {
|
|
201
|
+
return false;
|
|
202
|
+
}
|
|
203
|
+
return value.trim().toLowerCase().startsWith("mr_pk_");
|
|
204
|
+
}
|
|
205
|
+
function normalizeFrontendToken(payload, meta) {
|
|
206
|
+
const expiresAt = payload.expires_at || payload.expiresAt;
|
|
207
|
+
return {
|
|
208
|
+
token: payload.token,
|
|
209
|
+
expiresAt: expiresAt ? new Date(expiresAt) : void 0,
|
|
210
|
+
expiresIn: payload.expires_in ?? payload.expiresIn,
|
|
211
|
+
tokenType: payload.token_type ?? payload.tokenType,
|
|
212
|
+
keyId: payload.key_id ?? payload.keyId,
|
|
213
|
+
sessionId: payload.session_id ?? payload.sessionId,
|
|
214
|
+
tokenScope: payload.token_scope ?? payload.tokenScope,
|
|
215
|
+
tokenSource: payload.token_source ?? payload.tokenSource,
|
|
216
|
+
endUserId: meta.userId,
|
|
217
|
+
publishableKey: meta.publishableKey,
|
|
218
|
+
deviceId: meta.deviceId
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
function isTokenReusable(token) {
|
|
222
|
+
if (!token.token) {
|
|
223
|
+
return false;
|
|
224
|
+
}
|
|
225
|
+
if (!token.expiresAt) {
|
|
226
|
+
return true;
|
|
227
|
+
}
|
|
228
|
+
return token.expiresAt.getTime() - Date.now() > 6e4;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// src/api-keys.ts
|
|
232
|
+
var ApiKeysClient = class {
|
|
233
|
+
constructor(http) {
|
|
234
|
+
this.http = http;
|
|
235
|
+
}
|
|
236
|
+
async list() {
|
|
237
|
+
const payload = await this.http.json("/api-keys", {
|
|
238
|
+
method: "GET"
|
|
239
|
+
});
|
|
240
|
+
const items = payload.api_keys || payload.apiKeys || [];
|
|
241
|
+
return items.map(normalizeApiKey).filter(Boolean);
|
|
242
|
+
}
|
|
243
|
+
async create(req) {
|
|
244
|
+
if (!req?.label?.trim()) {
|
|
245
|
+
throw new ModelRelayError("label is required", { status: 400 });
|
|
246
|
+
}
|
|
247
|
+
const body = {
|
|
248
|
+
label: req.label
|
|
249
|
+
};
|
|
250
|
+
if (req.kind) body.kind = req.kind;
|
|
251
|
+
if (req.expiresAt instanceof Date) {
|
|
252
|
+
body.expires_at = req.expiresAt.toISOString();
|
|
253
|
+
}
|
|
254
|
+
const payload = await this.http.json("/api-keys", {
|
|
255
|
+
method: "POST",
|
|
256
|
+
body
|
|
257
|
+
});
|
|
258
|
+
const record = payload.api_key || payload.apiKey;
|
|
259
|
+
if (!record) {
|
|
260
|
+
throw new ModelRelayError("missing api_key in response", {
|
|
261
|
+
status: 500
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
return normalizeApiKey(record);
|
|
265
|
+
}
|
|
266
|
+
async delete(id) {
|
|
267
|
+
if (!id?.trim()) {
|
|
268
|
+
throw new ModelRelayError("id is required", { status: 400 });
|
|
269
|
+
}
|
|
270
|
+
await this.http.request(`/api-keys/${encodeURIComponent(id)}`, {
|
|
271
|
+
method: "DELETE"
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
};
|
|
275
|
+
function normalizeApiKey(record) {
|
|
276
|
+
const created = record?.created_at || record?.createdAt || "";
|
|
277
|
+
const expires = record?.expires_at ?? record?.expiresAt ?? void 0;
|
|
278
|
+
const lastUsed = record?.last_used_at ?? record?.lastUsedAt ?? void 0;
|
|
279
|
+
return {
|
|
280
|
+
id: record?.id || "",
|
|
281
|
+
label: record?.label || "",
|
|
282
|
+
kind: record?.kind || "",
|
|
283
|
+
createdAt: created ? new Date(created) : /* @__PURE__ */ new Date(),
|
|
284
|
+
expiresAt: expires ? new Date(expires) : void 0,
|
|
285
|
+
lastUsedAt: lastUsed ? new Date(lastUsed) : void 0,
|
|
286
|
+
redactedKey: record?.redacted_key || record?.redactedKey || "",
|
|
287
|
+
secretKey: record?.secret_key ?? record?.secretKey ?? void 0
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// src/billing.ts
|
|
292
|
+
var BillingClient = class {
|
|
293
|
+
constructor(http, auth) {
|
|
294
|
+
this.http = http;
|
|
295
|
+
this.auth = auth;
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* Initiate a Stripe Checkout session for an end user.
|
|
299
|
+
*/
|
|
300
|
+
async checkout(params) {
|
|
301
|
+
if (!params?.endUserId?.trim()) {
|
|
302
|
+
throw new ModelRelayError("endUserId is required", { status: 400 });
|
|
303
|
+
}
|
|
304
|
+
if (!params.successUrl?.trim() || !params.cancelUrl?.trim()) {
|
|
305
|
+
throw new ModelRelayError("successUrl and cancelUrl are required", {
|
|
306
|
+
status: 400
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
const authHeaders = this.auth.authForBilling();
|
|
310
|
+
const body = {
|
|
311
|
+
end_user_id: params.endUserId,
|
|
312
|
+
success_url: params.successUrl,
|
|
313
|
+
cancel_url: params.cancelUrl
|
|
314
|
+
};
|
|
315
|
+
if (params.deviceId) body.device_id = params.deviceId;
|
|
316
|
+
if (params.planId) body.plan_id = params.planId;
|
|
317
|
+
if (params.plan) body.plan = params.plan;
|
|
318
|
+
const response = await this.http.json(
|
|
319
|
+
"/end-users/checkout",
|
|
320
|
+
{
|
|
321
|
+
method: "POST",
|
|
322
|
+
body,
|
|
323
|
+
apiKey: authHeaders.apiKey,
|
|
324
|
+
accessToken: authHeaders.accessToken
|
|
325
|
+
}
|
|
326
|
+
);
|
|
327
|
+
return normalizeCheckoutResponse(response);
|
|
328
|
+
}
|
|
329
|
+
};
|
|
330
|
+
function normalizeCheckoutResponse(payload) {
|
|
331
|
+
const endUser = {
|
|
332
|
+
id: payload.end_user?.id || "",
|
|
333
|
+
externalId: payload.end_user?.external_id || "",
|
|
334
|
+
ownerId: payload.end_user?.owner_id || ""
|
|
335
|
+
};
|
|
336
|
+
const session = {
|
|
337
|
+
id: payload.session?.id || "",
|
|
338
|
+
plan: payload.session?.plan || "",
|
|
339
|
+
status: payload.session?.status || "",
|
|
340
|
+
url: payload.session?.url || "",
|
|
341
|
+
expiresAt: payload.session?.expires_at ? new Date(payload.session.expires_at) : void 0,
|
|
342
|
+
completedAt: payload.session?.completed_at ? new Date(payload.session.completed_at) : void 0
|
|
343
|
+
};
|
|
344
|
+
return { endUser, session };
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// src/chat.ts
|
|
348
|
+
var REQUEST_ID_HEADER = "X-ModelRelay-Chat-Request-Id";
|
|
349
|
+
var ChatClient = class {
|
|
350
|
+
constructor(http, auth, cfg = {}) {
|
|
351
|
+
this.completions = new ChatCompletionsClient(
|
|
352
|
+
http,
|
|
353
|
+
auth,
|
|
354
|
+
cfg.defaultMetadata
|
|
355
|
+
);
|
|
356
|
+
}
|
|
357
|
+
};
|
|
358
|
+
var ChatCompletionsClient = class {
|
|
359
|
+
constructor(http, auth, defaultMetadata) {
|
|
360
|
+
this.http = http;
|
|
361
|
+
this.auth = auth;
|
|
362
|
+
this.defaultMetadata = defaultMetadata;
|
|
363
|
+
}
|
|
364
|
+
async create(params, options = {}) {
|
|
365
|
+
const stream = options.stream ?? params.stream ?? true;
|
|
366
|
+
if (!params?.model?.trim()) {
|
|
367
|
+
throw new ModelRelayError("model is required", { status: 400 });
|
|
368
|
+
}
|
|
369
|
+
if (!params?.messages?.length) {
|
|
370
|
+
throw new ModelRelayError("at least one message is required", {
|
|
371
|
+
status: 400
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
if (!hasUserMessage(params.messages)) {
|
|
375
|
+
throw new ModelRelayError(
|
|
376
|
+
"at least one user message is required",
|
|
377
|
+
{ status: 400 }
|
|
378
|
+
);
|
|
379
|
+
}
|
|
380
|
+
const authHeaders = await this.auth.authForChat(params.endUserId);
|
|
381
|
+
const body = buildProxyBody(
|
|
382
|
+
params,
|
|
383
|
+
mergeMetadata(this.defaultMetadata, params.metadata, options.metadata)
|
|
384
|
+
);
|
|
385
|
+
const requestId = params.requestId || options.requestId;
|
|
386
|
+
const headers = { ...options.headers || {} };
|
|
387
|
+
if (requestId) {
|
|
388
|
+
headers[REQUEST_ID_HEADER] = requestId;
|
|
389
|
+
}
|
|
390
|
+
const response = await this.http.request("/llm/proxy", {
|
|
391
|
+
method: "POST",
|
|
392
|
+
body,
|
|
393
|
+
headers,
|
|
394
|
+
apiKey: authHeaders.apiKey,
|
|
395
|
+
accessToken: authHeaders.accessToken,
|
|
396
|
+
accept: stream ? "text/event-stream" : "application/json",
|
|
397
|
+
raw: true,
|
|
398
|
+
signal: options.signal,
|
|
399
|
+
timeoutMs: options.timeoutMs ?? (stream ? 0 : void 0),
|
|
400
|
+
useDefaultTimeout: !stream,
|
|
401
|
+
retry: options.retry
|
|
402
|
+
});
|
|
403
|
+
const resolvedRequestId = requestIdFromHeaders(response.headers) || requestId || void 0;
|
|
404
|
+
if (!response.ok) {
|
|
405
|
+
throw await parseErrorResponse(response);
|
|
406
|
+
}
|
|
407
|
+
if (!stream) {
|
|
408
|
+
const payload = await response.json();
|
|
409
|
+
return normalizeChatResponse(payload, resolvedRequestId);
|
|
410
|
+
}
|
|
411
|
+
return new ChatCompletionsStream(response, resolvedRequestId);
|
|
412
|
+
}
|
|
413
|
+
};
|
|
414
|
+
var ChatCompletionsStream = class {
|
|
415
|
+
constructor(response, requestId) {
|
|
416
|
+
this.closed = false;
|
|
417
|
+
if (!response.body) {
|
|
418
|
+
throw new ModelRelayError("streaming response is missing a body", {
|
|
419
|
+
status: 500
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
this.response = response;
|
|
423
|
+
this.requestId = requestId;
|
|
424
|
+
}
|
|
425
|
+
async cancel(reason) {
|
|
426
|
+
this.closed = true;
|
|
427
|
+
try {
|
|
428
|
+
await this.response.body?.cancel(reason);
|
|
429
|
+
} catch {
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
async *[Symbol.asyncIterator]() {
|
|
433
|
+
if (this.closed) {
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
436
|
+
const body = this.response.body;
|
|
437
|
+
if (!body) {
|
|
438
|
+
throw new ModelRelayError("streaming response is missing a body", {
|
|
439
|
+
status: 500
|
|
440
|
+
});
|
|
441
|
+
}
|
|
442
|
+
const reader = body.getReader();
|
|
443
|
+
const decoder = new TextDecoder();
|
|
444
|
+
let buffer = "";
|
|
445
|
+
try {
|
|
446
|
+
while (true) {
|
|
447
|
+
if (this.closed) {
|
|
448
|
+
await reader.cancel();
|
|
449
|
+
return;
|
|
450
|
+
}
|
|
451
|
+
const { value, done } = await reader.read();
|
|
452
|
+
if (done) {
|
|
453
|
+
const { events: events2 } = consumeSSEBuffer(buffer, true);
|
|
454
|
+
for (const evt of events2) {
|
|
455
|
+
const parsed = mapChatEvent(evt, this.requestId);
|
|
456
|
+
if (parsed) {
|
|
457
|
+
yield parsed;
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
return;
|
|
461
|
+
}
|
|
462
|
+
buffer += decoder.decode(value, { stream: true });
|
|
463
|
+
const { events, remainder } = consumeSSEBuffer(buffer);
|
|
464
|
+
buffer = remainder;
|
|
465
|
+
for (const evt of events) {
|
|
466
|
+
const parsed = mapChatEvent(evt, this.requestId);
|
|
467
|
+
if (parsed) {
|
|
468
|
+
yield parsed;
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
} finally {
|
|
473
|
+
this.closed = true;
|
|
474
|
+
reader.releaseLock();
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
};
|
|
478
|
+
function consumeSSEBuffer(buffer, flush = false) {
|
|
479
|
+
const events = [];
|
|
480
|
+
let eventName = "";
|
|
481
|
+
let dataLines = [];
|
|
482
|
+
let remainder = "";
|
|
483
|
+
const lines = buffer.split(/\r?\n/);
|
|
484
|
+
const lastIndex = lines.length - 1;
|
|
485
|
+
const limit = flush ? lines.length : Math.max(0, lastIndex);
|
|
486
|
+
const pushEvent = () => {
|
|
487
|
+
if (!eventName && dataLines.length === 0) {
|
|
488
|
+
return;
|
|
489
|
+
}
|
|
490
|
+
events.push({
|
|
491
|
+
event: eventName || "message",
|
|
492
|
+
data: dataLines.join("\n")
|
|
493
|
+
});
|
|
494
|
+
eventName = "";
|
|
495
|
+
dataLines = [];
|
|
496
|
+
};
|
|
497
|
+
for (let i = 0; i < limit; i++) {
|
|
498
|
+
const line = lines[i];
|
|
499
|
+
if (line === "") {
|
|
500
|
+
pushEvent();
|
|
501
|
+
continue;
|
|
502
|
+
}
|
|
503
|
+
if (line.startsWith(":")) {
|
|
504
|
+
continue;
|
|
505
|
+
}
|
|
506
|
+
if (line.startsWith("event:")) {
|
|
507
|
+
eventName = line.slice(6).trim();
|
|
508
|
+
} else if (line.startsWith("data:")) {
|
|
509
|
+
dataLines.push(line.slice(5).trimStart());
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
if (flush) {
|
|
513
|
+
pushEvent();
|
|
514
|
+
remainder = "";
|
|
515
|
+
} else {
|
|
516
|
+
remainder = lines[lastIndex] ?? "";
|
|
517
|
+
}
|
|
518
|
+
return { events, remainder };
|
|
519
|
+
}
|
|
520
|
+
function mapChatEvent(raw, requestId) {
|
|
521
|
+
let parsed = raw.data;
|
|
522
|
+
if (raw.data) {
|
|
523
|
+
try {
|
|
524
|
+
parsed = JSON.parse(raw.data);
|
|
525
|
+
} catch {
|
|
526
|
+
parsed = raw.data;
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
const payload = typeof parsed === "object" && parsed !== null ? parsed : {};
|
|
530
|
+
const p = payload;
|
|
531
|
+
const type = normalizeEventType(raw.event, p);
|
|
532
|
+
const usage = normalizeUsage(p.usage);
|
|
533
|
+
const responseId = p.response_id || p.responseId || p.id || p?.message?.id;
|
|
534
|
+
const model = p.model || p?.message?.model;
|
|
535
|
+
const stopReason = p.stop_reason || p.stopReason;
|
|
536
|
+
const textDelta = extractTextDelta(p);
|
|
537
|
+
return {
|
|
538
|
+
type,
|
|
539
|
+
event: raw.event || type,
|
|
540
|
+
data: p,
|
|
541
|
+
textDelta,
|
|
542
|
+
responseId,
|
|
543
|
+
model,
|
|
544
|
+
stopReason,
|
|
545
|
+
usage,
|
|
546
|
+
requestId,
|
|
547
|
+
raw: raw.data || ""
|
|
548
|
+
};
|
|
549
|
+
}
|
|
550
|
+
function normalizeEventType(eventName, payload) {
|
|
551
|
+
const hint = String(
|
|
552
|
+
payload?.type || payload?.event || eventName || ""
|
|
553
|
+
).trim();
|
|
554
|
+
switch (hint) {
|
|
555
|
+
case "message_start":
|
|
556
|
+
return "message_start";
|
|
557
|
+
case "message_delta":
|
|
558
|
+
return "message_delta";
|
|
559
|
+
case "message_stop":
|
|
560
|
+
return "message_stop";
|
|
561
|
+
case "ping":
|
|
562
|
+
return "ping";
|
|
563
|
+
default:
|
|
564
|
+
return "custom";
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
function extractTextDelta(payload) {
|
|
568
|
+
if (!payload || typeof payload !== "object") {
|
|
569
|
+
return void 0;
|
|
570
|
+
}
|
|
571
|
+
if (typeof payload.delta === "string") {
|
|
572
|
+
return payload.delta;
|
|
573
|
+
}
|
|
574
|
+
if (payload.delta && typeof payload.delta === "object") {
|
|
575
|
+
if (typeof payload.delta.text === "string") {
|
|
576
|
+
return payload.delta.text;
|
|
577
|
+
}
|
|
578
|
+
if (typeof payload.delta.content === "string") {
|
|
579
|
+
return payload.delta.content;
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
return void 0;
|
|
583
|
+
}
|
|
584
|
+
function normalizeChatResponse(payload, requestId) {
|
|
585
|
+
const p = payload;
|
|
586
|
+
return {
|
|
587
|
+
id: p?.id,
|
|
588
|
+
provider: p?.provider,
|
|
589
|
+
content: Array.isArray(p?.content) ? p.content : p?.content ? [String(p.content)] : [],
|
|
590
|
+
stopReason: p?.stop_reason ?? p?.stopReason,
|
|
591
|
+
model: p?.model,
|
|
592
|
+
usage: normalizeUsage(p?.usage),
|
|
593
|
+
requestId
|
|
594
|
+
};
|
|
595
|
+
}
|
|
596
|
+
function normalizeUsage(payload) {
|
|
597
|
+
if (!payload) {
|
|
598
|
+
return { inputTokens: 0, outputTokens: 0, totalTokens: 0 };
|
|
599
|
+
}
|
|
600
|
+
return {
|
|
601
|
+
inputTokens: Number(payload.input_tokens ?? payload.inputTokens ?? 0),
|
|
602
|
+
outputTokens: Number(payload.output_tokens ?? payload.outputTokens ?? 0),
|
|
603
|
+
totalTokens: Number(payload.total_tokens ?? payload.totalTokens ?? 0)
|
|
604
|
+
};
|
|
605
|
+
}
|
|
606
|
+
function buildProxyBody(params, metadata) {
|
|
607
|
+
const body = {
|
|
608
|
+
model: params.model,
|
|
609
|
+
messages: normalizeMessages(params.messages)
|
|
610
|
+
};
|
|
611
|
+
if (typeof params.maxTokens === "number") body.max_tokens = params.maxTokens;
|
|
612
|
+
if (params.provider) body.provider = params.provider;
|
|
613
|
+
if (typeof params.temperature === "number")
|
|
614
|
+
body.temperature = params.temperature;
|
|
615
|
+
if (metadata && Object.keys(metadata).length > 0) body.metadata = metadata;
|
|
616
|
+
if (params.stop?.length) body.stop = params.stop;
|
|
617
|
+
if (params.stopSequences?.length) body.stop_sequences = params.stopSequences;
|
|
618
|
+
return body;
|
|
619
|
+
}
|
|
620
|
+
function normalizeMessages(messages) {
|
|
621
|
+
return messages.map((msg) => ({
|
|
622
|
+
role: msg.role || "user",
|
|
623
|
+
content: msg.content
|
|
624
|
+
}));
|
|
625
|
+
}
|
|
626
|
+
function requestIdFromHeaders(headers) {
|
|
627
|
+
return headers.get(REQUEST_ID_HEADER) || headers.get("X-Request-Id") || void 0;
|
|
628
|
+
}
|
|
629
|
+
function mergeMetadata(...sources) {
|
|
630
|
+
const merged = {};
|
|
631
|
+
for (const src of sources) {
|
|
632
|
+
if (!src) continue;
|
|
633
|
+
for (const [key, value] of Object.entries(src)) {
|
|
634
|
+
const k = key?.trim();
|
|
635
|
+
const v = value?.trim();
|
|
636
|
+
if (!k || !v) continue;
|
|
637
|
+
merged[k] = v;
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
return Object.keys(merged).length ? merged : void 0;
|
|
641
|
+
}
|
|
642
|
+
function hasUserMessage(messages) {
|
|
643
|
+
return messages.some(
|
|
644
|
+
(msg) => msg.role?.toLowerCase?.() === "user" && !!msg.content
|
|
645
|
+
);
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
// package.json
|
|
649
|
+
var package_default = {
|
|
650
|
+
name: "@modelrelay/sdk",
|
|
651
|
+
version: "0.2.0",
|
|
652
|
+
description: "TypeScript SDK for the ModelRelay API",
|
|
653
|
+
type: "module",
|
|
654
|
+
main: "dist/index.cjs",
|
|
655
|
+
module: "dist/index.js",
|
|
656
|
+
types: "dist/index.d.ts",
|
|
657
|
+
exports: {
|
|
658
|
+
".": {
|
|
659
|
+
types: "./dist/index.d.ts",
|
|
660
|
+
import: "./dist/index.js",
|
|
661
|
+
require: "./dist/index.cjs"
|
|
662
|
+
}
|
|
663
|
+
},
|
|
664
|
+
publishConfig: { access: "public" },
|
|
665
|
+
files: [
|
|
666
|
+
"dist"
|
|
667
|
+
],
|
|
668
|
+
scripts: {
|
|
669
|
+
build: "tsup src/index.ts --format esm,cjs --dts",
|
|
670
|
+
dev: "tsup src/index.ts --format esm,cjs --dts --watch",
|
|
671
|
+
lint: "tsc --noEmit",
|
|
672
|
+
test: "vitest run"
|
|
673
|
+
},
|
|
674
|
+
keywords: [
|
|
675
|
+
"modelrelay",
|
|
676
|
+
"llm",
|
|
677
|
+
"sdk",
|
|
678
|
+
"typescript"
|
|
679
|
+
],
|
|
680
|
+
author: "Shane Vitarana",
|
|
681
|
+
license: "Apache-2.0",
|
|
682
|
+
devDependencies: {
|
|
683
|
+
tsup: "^8.2.4",
|
|
684
|
+
typescript: "^5.6.3",
|
|
685
|
+
vitest: "^2.1.4"
|
|
686
|
+
}
|
|
687
|
+
};
|
|
688
|
+
|
|
689
|
+
// src/types.ts
|
|
690
|
+
var SDK_VERSION = package_default.version || "0.0.0";
|
|
691
|
+
var DEFAULT_BASE_URL = "https://api.modelrelay.ai/api/v1";
|
|
692
|
+
var STAGING_BASE_URL = "https://api-stg.modelrelay.ai/api/v1";
|
|
693
|
+
var SANDBOX_BASE_URL = "https://api.sandbox.modelrelay.ai/api/v1";
|
|
694
|
+
var DEFAULT_CLIENT_HEADER = `modelrelay-ts/${SDK_VERSION}`;
|
|
695
|
+
var DEFAULT_REQUEST_TIMEOUT_MS = 6e4;
|
|
696
|
+
|
|
697
|
+
// src/http.ts
|
|
698
|
+
var HTTPClient = class {
|
|
699
|
+
constructor(cfg) {
|
|
700
|
+
const baseFromEnv = baseUrlForEnvironment(cfg.environment);
|
|
701
|
+
this.baseUrl = normalizeBaseUrl(cfg.baseUrl || baseFromEnv || DEFAULT_BASE_URL);
|
|
702
|
+
this.apiKey = cfg.apiKey?.trim();
|
|
703
|
+
this.accessToken = cfg.accessToken?.trim();
|
|
704
|
+
this.fetchImpl = cfg.fetchImpl;
|
|
705
|
+
this.clientHeader = cfg.clientHeader?.trim() || DEFAULT_CLIENT_HEADER;
|
|
706
|
+
this.defaultTimeoutMs = cfg.timeoutMs === void 0 ? DEFAULT_REQUEST_TIMEOUT_MS : Math.max(0, cfg.timeoutMs);
|
|
707
|
+
this.retry = normalizeRetryConfig(cfg.retry);
|
|
708
|
+
this.defaultHeaders = normalizeHeaders(cfg.defaultHeaders);
|
|
709
|
+
}
|
|
710
|
+
async request(path, options = {}) {
|
|
711
|
+
const fetchFn = this.fetchImpl ?? globalThis.fetch;
|
|
712
|
+
if (!fetchFn) {
|
|
713
|
+
throw new ModelRelayError(
|
|
714
|
+
"fetch is not available; provide a fetch implementation",
|
|
715
|
+
{ status: 500 }
|
|
716
|
+
);
|
|
717
|
+
}
|
|
718
|
+
const method = options.method || "GET";
|
|
719
|
+
const url = buildUrl(this.baseUrl, path);
|
|
720
|
+
const headers = new Headers({
|
|
721
|
+
...this.defaultHeaders,
|
|
722
|
+
...options.headers || {}
|
|
723
|
+
});
|
|
724
|
+
const accepts = options.accept || (options.raw ? void 0 : "application/json");
|
|
725
|
+
if (accepts && !headers.has("Accept")) {
|
|
726
|
+
headers.set("Accept", accepts);
|
|
727
|
+
}
|
|
728
|
+
const body = options.body;
|
|
729
|
+
const shouldEncodeJSON = body !== void 0 && body !== null && typeof body === "object" && !(body instanceof FormData) && !(body instanceof Blob);
|
|
730
|
+
const payload = shouldEncodeJSON ? JSON.stringify(body) : body;
|
|
731
|
+
if (shouldEncodeJSON && !headers.has("Content-Type")) {
|
|
732
|
+
headers.set("Content-Type", "application/json");
|
|
733
|
+
}
|
|
734
|
+
const accessToken = options.accessToken ?? this.accessToken;
|
|
735
|
+
if (accessToken) {
|
|
736
|
+
const bearer = accessToken.toLowerCase().startsWith("bearer ") ? accessToken : `Bearer ${accessToken}`;
|
|
737
|
+
headers.set("Authorization", bearer);
|
|
738
|
+
}
|
|
739
|
+
const apiKey = options.apiKey ?? this.apiKey;
|
|
740
|
+
if (apiKey) {
|
|
741
|
+
headers.set("X-ModelRelay-Api-Key", apiKey);
|
|
742
|
+
}
|
|
743
|
+
if (this.clientHeader && !headers.has("X-ModelRelay-Client")) {
|
|
744
|
+
headers.set("X-ModelRelay-Client", this.clientHeader);
|
|
745
|
+
}
|
|
746
|
+
const timeoutMs = options.useDefaultTimeout === false ? options.timeoutMs : options.timeoutMs ?? this.defaultTimeoutMs;
|
|
747
|
+
const retryCfg = normalizeRetryConfig(
|
|
748
|
+
options.retry === void 0 ? this.retry : options.retry
|
|
749
|
+
);
|
|
750
|
+
const attempts = retryCfg ? Math.max(1, retryCfg.maxAttempts) : 1;
|
|
751
|
+
let lastError;
|
|
752
|
+
for (let attempt = 1; attempt <= attempts; attempt++) {
|
|
753
|
+
const controller = timeoutMs && timeoutMs > 0 ? new AbortController() : void 0;
|
|
754
|
+
const signal = mergeSignals(options.signal, controller?.signal);
|
|
755
|
+
const timer = controller && setTimeout(() => controller.abort(new DOMException("timeout", "AbortError")), timeoutMs);
|
|
756
|
+
try {
|
|
757
|
+
const response = await fetchFn(url, {
|
|
758
|
+
method,
|
|
759
|
+
headers,
|
|
760
|
+
body: payload,
|
|
761
|
+
signal
|
|
762
|
+
});
|
|
763
|
+
if (!response.ok) {
|
|
764
|
+
const shouldRetry = retryCfg && shouldRetryStatus(
|
|
765
|
+
response.status,
|
|
766
|
+
method,
|
|
767
|
+
retryCfg.retryPost
|
|
768
|
+
) && attempt < attempts;
|
|
769
|
+
if (shouldRetry) {
|
|
770
|
+
await backoff(attempt, retryCfg);
|
|
771
|
+
continue;
|
|
772
|
+
}
|
|
773
|
+
if (!options.raw) {
|
|
774
|
+
throw await parseErrorResponse(response);
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
return response;
|
|
778
|
+
} catch (err) {
|
|
779
|
+
if (options.signal?.aborted) {
|
|
780
|
+
throw err;
|
|
781
|
+
}
|
|
782
|
+
const shouldRetry = retryCfg && isRetryableError(err) && (method !== "POST" || retryCfg.retryPost) && attempt < attempts;
|
|
783
|
+
if (!shouldRetry) {
|
|
784
|
+
throw err;
|
|
785
|
+
}
|
|
786
|
+
lastError = err;
|
|
787
|
+
await backoff(attempt, retryCfg);
|
|
788
|
+
} finally {
|
|
789
|
+
if (timer) {
|
|
790
|
+
clearTimeout(timer);
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
throw lastError instanceof Error ? lastError : new ModelRelayError("request failed", { status: 500 });
|
|
795
|
+
}
|
|
796
|
+
async json(path, options = {}) {
|
|
797
|
+
const response = await this.request(path, {
|
|
798
|
+
...options,
|
|
799
|
+
raw: true,
|
|
800
|
+
accept: options.accept || "application/json"
|
|
801
|
+
});
|
|
802
|
+
if (!response.ok) {
|
|
803
|
+
throw await parseErrorResponse(response);
|
|
804
|
+
}
|
|
805
|
+
if (response.status === 204) {
|
|
806
|
+
return void 0;
|
|
807
|
+
}
|
|
808
|
+
try {
|
|
809
|
+
return await response.json();
|
|
810
|
+
} catch (err) {
|
|
811
|
+
throw new ModelRelayError("failed to parse response JSON", {
|
|
812
|
+
status: response.status,
|
|
813
|
+
data: err
|
|
814
|
+
});
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
};
|
|
818
|
+
function buildUrl(baseUrl, path) {
|
|
819
|
+
if (/^https?:\/\//i.test(path)) {
|
|
820
|
+
return path;
|
|
821
|
+
}
|
|
822
|
+
if (!path.startsWith("/")) {
|
|
823
|
+
path = `/${path}`;
|
|
824
|
+
}
|
|
825
|
+
return `${baseUrl}${path}`;
|
|
826
|
+
}
|
|
827
|
+
function normalizeBaseUrl(value) {
|
|
828
|
+
const trimmed = value.trim();
|
|
829
|
+
if (trimmed.endsWith("/")) {
|
|
830
|
+
return trimmed.slice(0, -1);
|
|
831
|
+
}
|
|
832
|
+
return trimmed;
|
|
833
|
+
}
|
|
834
|
+
function baseUrlForEnvironment(env) {
|
|
835
|
+
if (!env || env === "production") return void 0;
|
|
836
|
+
if (env === "staging") return STAGING_BASE_URL;
|
|
837
|
+
if (env === "sandbox") return SANDBOX_BASE_URL;
|
|
838
|
+
return void 0;
|
|
839
|
+
}
|
|
840
|
+
function normalizeRetryConfig(retry) {
|
|
841
|
+
if (retry === false) return void 0;
|
|
842
|
+
const cfg = retry || {};
|
|
843
|
+
return {
|
|
844
|
+
maxAttempts: Math.max(1, cfg.maxAttempts ?? 3),
|
|
845
|
+
baseBackoffMs: Math.max(0, cfg.baseBackoffMs ?? 300),
|
|
846
|
+
maxBackoffMs: Math.max(0, cfg.maxBackoffMs ?? 5e3),
|
|
847
|
+
retryPost: cfg.retryPost ?? true
|
|
848
|
+
};
|
|
849
|
+
}
|
|
850
|
+
function shouldRetryStatus(status, method, retryPost) {
|
|
851
|
+
if (status === 408 || status === 429) {
|
|
852
|
+
return method !== "POST" || retryPost;
|
|
853
|
+
}
|
|
854
|
+
if (status >= 500 && status < 600) {
|
|
855
|
+
return method !== "POST" || retryPost;
|
|
856
|
+
}
|
|
857
|
+
return false;
|
|
858
|
+
}
|
|
859
|
+
function isRetryableError(err) {
|
|
860
|
+
if (!err) return false;
|
|
861
|
+
return err instanceof DOMException && err.name === "AbortError" || err instanceof TypeError;
|
|
862
|
+
}
|
|
863
|
+
function backoff(attempt, cfg) {
|
|
864
|
+
const exp = Math.max(0, attempt - 1);
|
|
865
|
+
const base = cfg.baseBackoffMs * Math.pow(2, Math.min(exp, 10));
|
|
866
|
+
const capped = Math.min(base, cfg.maxBackoffMs);
|
|
867
|
+
const jitter = 0.5 + Math.random();
|
|
868
|
+
const delay = Math.min(cfg.maxBackoffMs, capped * jitter);
|
|
869
|
+
if (delay <= 0) return Promise.resolve();
|
|
870
|
+
return new Promise((resolve) => setTimeout(resolve, delay));
|
|
871
|
+
}
|
|
872
|
+
function mergeSignals(user, timeoutSignal) {
|
|
873
|
+
if (!user && !timeoutSignal) return void 0;
|
|
874
|
+
if (user && !timeoutSignal) return user;
|
|
875
|
+
if (!user && timeoutSignal) return timeoutSignal;
|
|
876
|
+
const controller = new AbortController();
|
|
877
|
+
const propagate = (source) => {
|
|
878
|
+
if (source.aborted) {
|
|
879
|
+
controller.abort(source.reason);
|
|
880
|
+
} else {
|
|
881
|
+
source.addEventListener(
|
|
882
|
+
"abort",
|
|
883
|
+
() => controller.abort(source.reason),
|
|
884
|
+
{ once: true }
|
|
885
|
+
);
|
|
886
|
+
}
|
|
887
|
+
};
|
|
888
|
+
if (user) propagate(user);
|
|
889
|
+
if (timeoutSignal) propagate(timeoutSignal);
|
|
890
|
+
return controller.signal;
|
|
891
|
+
}
|
|
892
|
+
function normalizeHeaders(headers) {
|
|
893
|
+
if (!headers) return {};
|
|
894
|
+
const normalized = {};
|
|
895
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
896
|
+
if (!key || !value) continue;
|
|
897
|
+
const k = key.trim();
|
|
898
|
+
const v = value.trim();
|
|
899
|
+
if (k && v) {
|
|
900
|
+
normalized[k] = v;
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
return normalized;
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
// src/index.ts
|
|
907
|
+
var ModelRelay = class {
|
|
908
|
+
constructor(options) {
|
|
909
|
+
const cfg = options || {};
|
|
910
|
+
if (!cfg.key && !cfg.token) {
|
|
911
|
+
throw new ModelRelayError("Provide an API key or access token", {
|
|
912
|
+
status: 400
|
|
913
|
+
});
|
|
914
|
+
}
|
|
915
|
+
this.baseUrl = resolveBaseUrl(cfg.environment, cfg.baseUrl);
|
|
916
|
+
const http = new HTTPClient({
|
|
917
|
+
baseUrl: this.baseUrl,
|
|
918
|
+
apiKey: cfg.key,
|
|
919
|
+
accessToken: cfg.token,
|
|
920
|
+
fetchImpl: cfg.fetch,
|
|
921
|
+
clientHeader: cfg.clientHeader || DEFAULT_CLIENT_HEADER,
|
|
922
|
+
timeoutMs: cfg.timeoutMs,
|
|
923
|
+
retry: cfg.retry,
|
|
924
|
+
defaultHeaders: cfg.defaultHeaders,
|
|
925
|
+
environment: cfg.environment
|
|
926
|
+
});
|
|
927
|
+
const auth = new AuthClient(http, {
|
|
928
|
+
apiKey: cfg.key,
|
|
929
|
+
accessToken: cfg.token,
|
|
930
|
+
endUser: cfg.endUser
|
|
931
|
+
});
|
|
932
|
+
this.auth = auth;
|
|
933
|
+
this.billing = new BillingClient(http, auth);
|
|
934
|
+
this.chat = new ChatClient(http, auth, {
|
|
935
|
+
defaultMetadata: cfg.defaultMetadata
|
|
936
|
+
});
|
|
937
|
+
this.apiKeys = new ApiKeysClient(http);
|
|
938
|
+
}
|
|
939
|
+
};
|
|
940
|
+
function resolveBaseUrl(env, override) {
|
|
941
|
+
const base = override || (env === "staging" ? STAGING_BASE_URL : env === "sandbox" ? SANDBOX_BASE_URL : DEFAULT_BASE_URL);
|
|
942
|
+
return base.replace(/\/+$/, "");
|
|
943
|
+
}
|
|
944
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
945
|
+
0 && (module.exports = {
|
|
946
|
+
ApiKeysClient,
|
|
947
|
+
AuthClient,
|
|
948
|
+
BillingClient,
|
|
949
|
+
ChatClient,
|
|
950
|
+
ChatCompletionsStream,
|
|
951
|
+
DEFAULT_BASE_URL,
|
|
952
|
+
DEFAULT_CLIENT_HEADER,
|
|
953
|
+
DEFAULT_REQUEST_TIMEOUT_MS,
|
|
954
|
+
ModelRelay,
|
|
955
|
+
ModelRelayError,
|
|
956
|
+
SANDBOX_BASE_URL,
|
|
957
|
+
SDK_VERSION,
|
|
958
|
+
STAGING_BASE_URL,
|
|
959
|
+
isPublishableKey
|
|
960
|
+
});
|