@upstash/qstash 2.8.4 → 2.9.0-rc
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/{chunk-5PQP3YLP.mjs → chunk-NU64UBMT.mjs} +1 -1
- package/{chunk-EZZS7N6P.mjs → chunk-PB5UCB6Z.mjs} +6 -9
- package/{chunk-RQPZUJXG.mjs → chunk-WOMVRJIB.mjs} +640 -495
- package/{client-DKNfczbM.d.ts → client-BVG9vt90.d.mts} +17 -5
- package/{client-DKNfczbM.d.mts → client-BVG9vt90.d.ts} +17 -5
- package/cloudflare.d.mts +1 -1
- package/cloudflare.d.ts +1 -1
- package/cloudflare.js +812 -667
- package/cloudflare.mjs +1 -1
- package/h3.d.mts +1 -1
- package/h3.d.ts +1 -1
- package/h3.js +825 -683
- package/h3.mjs +3 -3
- package/hono.d.mts +1 -1
- package/hono.d.ts +1 -1
- package/hono.js +812 -667
- package/hono.mjs +1 -1
- package/index.d.mts +2 -2
- package/index.d.ts +2 -2
- package/index.js +637 -492
- package/index.mjs +2 -2
- package/nextjs.d.mts +1 -1
- package/nextjs.d.ts +1 -1
- package/nextjs.js +647 -511
- package/nextjs.mjs +16 -25
- package/nuxt.js +110 -12
- package/nuxt.mjs +3 -3
- package/package.json +1 -1
- package/solidjs.d.mts +1 -1
- package/solidjs.d.ts +1 -1
- package/solidjs.js +828 -682
- package/solidjs.mjs +9 -8
- package/svelte.d.mts +4 -4
- package/svelte.d.ts +4 -4
- package/svelte.js +830 -684
- package/svelte.mjs +11 -10
- package/workflow.d.mts +1 -1
- package/workflow.d.ts +1 -1
- package/workflow.js +812 -667
- package/workflow.mjs +1 -1
package/h3.js
CHANGED
|
@@ -361,120 +361,154 @@ var H3Response = globalThis.Response;
|
|
|
361
361
|
// src/receiver.ts
|
|
362
362
|
var jose = __toESM(require("jose"));
|
|
363
363
|
var import_crypto_js = __toESM(require("crypto-js"));
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
364
|
+
|
|
365
|
+
// src/client/api/base.ts
|
|
366
|
+
var BaseProvider = class {
|
|
367
|
+
baseUrl;
|
|
368
|
+
token;
|
|
369
|
+
owner;
|
|
370
|
+
constructor(baseUrl, token, owner) {
|
|
371
|
+
this.baseUrl = baseUrl;
|
|
372
|
+
this.token = token;
|
|
373
|
+
this.owner = owner;
|
|
374
|
+
}
|
|
375
|
+
getUrl() {
|
|
376
|
+
return `${this.baseUrl}/${this.getRoute().join("/")}`;
|
|
368
377
|
}
|
|
369
378
|
};
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
379
|
+
|
|
380
|
+
// src/client/api/llm.ts
|
|
381
|
+
var LLMProvider = class extends BaseProvider {
|
|
382
|
+
apiKind = "llm";
|
|
383
|
+
organization;
|
|
384
|
+
method = "POST";
|
|
385
|
+
constructor(baseUrl, token, owner, organization) {
|
|
386
|
+
super(baseUrl, token, owner);
|
|
387
|
+
this.organization = organization;
|
|
376
388
|
}
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
*
|
|
384
|
-
* If that fails, the signature is invalid and a `SignatureError` is thrown.
|
|
385
|
-
*/
|
|
386
|
-
async verify(request) {
|
|
387
|
-
let payload;
|
|
388
|
-
try {
|
|
389
|
-
payload = await this.verifyWithKey(this.currentSigningKey, request);
|
|
390
|
-
} catch {
|
|
391
|
-
payload = await this.verifyWithKey(this.nextSigningKey, request);
|
|
389
|
+
getRoute() {
|
|
390
|
+
return this.owner === "anthropic" ? ["v1", "messages"] : ["v1", "chat", "completions"];
|
|
391
|
+
}
|
|
392
|
+
getHeaders(options) {
|
|
393
|
+
if (this.owner === "upstash" && !options.analytics) {
|
|
394
|
+
return { "content-type": "application/json" };
|
|
392
395
|
}
|
|
393
|
-
this.
|
|
394
|
-
|
|
396
|
+
const header = this.owner === "anthropic" ? "x-api-key" : "authorization";
|
|
397
|
+
const headerValue = this.owner === "anthropic" ? this.token : `Bearer ${this.token}`;
|
|
398
|
+
const headers = {
|
|
399
|
+
[header]: headerValue,
|
|
400
|
+
"content-type": "application/json"
|
|
401
|
+
};
|
|
402
|
+
if (this.owner === "openai" && this.organization) {
|
|
403
|
+
headers["OpenAI-Organization"] = this.organization;
|
|
404
|
+
}
|
|
405
|
+
if (this.owner === "anthropic") {
|
|
406
|
+
headers["anthropic-version"] = "2023-06-01";
|
|
407
|
+
}
|
|
408
|
+
return headers;
|
|
395
409
|
}
|
|
396
410
|
/**
|
|
397
|
-
*
|
|
411
|
+
* Checks if callback exists and adds analytics in place if it's set.
|
|
412
|
+
*
|
|
413
|
+
* @param request
|
|
414
|
+
* @param options
|
|
398
415
|
*/
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
clockTolerance: request.clockTolerance
|
|
403
|
-
}).catch((error) => {
|
|
404
|
-
throw new SignatureError(error.message);
|
|
405
|
-
});
|
|
406
|
-
return jwt.payload;
|
|
407
|
-
}
|
|
408
|
-
verifyBodyAndUrl(payload, request) {
|
|
409
|
-
const p = payload;
|
|
410
|
-
if (request.url !== void 0 && p.sub !== request.url) {
|
|
411
|
-
throw new SignatureError(`invalid subject: ${p.sub}, want: ${request.url}`);
|
|
412
|
-
}
|
|
413
|
-
const bodyHash = import_crypto_js.default.SHA256(request.body).toString(import_crypto_js.default.enc.Base64url);
|
|
414
|
-
const padding = new RegExp(/=+$/);
|
|
415
|
-
if (p.body.replace(padding, "") !== bodyHash.replace(padding, "")) {
|
|
416
|
-
throw new SignatureError(`body hash does not match, want: ${p.body}, got: ${bodyHash}`);
|
|
416
|
+
onFinish(providerInfo, options) {
|
|
417
|
+
if (options.analytics) {
|
|
418
|
+
return updateWithAnalytics(providerInfo, options.analytics);
|
|
417
419
|
}
|
|
420
|
+
return providerInfo;
|
|
418
421
|
}
|
|
419
422
|
};
|
|
423
|
+
var upstash = () => {
|
|
424
|
+
return new LLMProvider("https://qstash.upstash.io/llm", "", "upstash");
|
|
425
|
+
};
|
|
420
426
|
|
|
421
|
-
// src/client/
|
|
422
|
-
var
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
427
|
+
// src/client/api/utils.ts
|
|
428
|
+
var getProviderInfo = (api, upstashToken) => {
|
|
429
|
+
const { name, provider, ...parameters } = api;
|
|
430
|
+
const finalProvider = provider ?? upstash();
|
|
431
|
+
if (finalProvider.owner === "upstash" && !finalProvider.token) {
|
|
432
|
+
finalProvider.token = upstashToken;
|
|
426
433
|
}
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
+
if (!finalProvider.baseUrl)
|
|
435
|
+
throw new TypeError("baseUrl cannot be empty or undefined!");
|
|
436
|
+
if (!finalProvider.token)
|
|
437
|
+
throw new TypeError("token cannot be empty or undefined!");
|
|
438
|
+
if (finalProvider.apiKind !== name) {
|
|
439
|
+
throw new TypeError(
|
|
440
|
+
`Unexpected api name. Expected '${finalProvider.apiKind}', received ${name}`
|
|
441
|
+
);
|
|
442
|
+
}
|
|
443
|
+
const providerInfo = {
|
|
444
|
+
url: finalProvider.getUrl(),
|
|
445
|
+
baseUrl: finalProvider.baseUrl,
|
|
446
|
+
route: finalProvider.getRoute(),
|
|
447
|
+
appendHeaders: finalProvider.getHeaders(parameters),
|
|
448
|
+
owner: finalProvider.owner,
|
|
449
|
+
method: finalProvider.method
|
|
450
|
+
};
|
|
451
|
+
return finalProvider.onFinish(providerInfo, parameters);
|
|
452
|
+
};
|
|
453
|
+
var safeJoinHeaders = (headers, record) => {
|
|
454
|
+
const joinedHeaders = new Headers(record);
|
|
455
|
+
for (const [header, value] of headers.entries()) {
|
|
456
|
+
joinedHeaders.set(header, value);
|
|
457
|
+
}
|
|
458
|
+
return joinedHeaders;
|
|
459
|
+
};
|
|
460
|
+
var processApi = (request, headers, upstashToken) => {
|
|
461
|
+
if (!request.api) {
|
|
462
|
+
request.headers = headers;
|
|
463
|
+
return request;
|
|
464
|
+
}
|
|
465
|
+
const { url, appendHeaders, owner, method } = getProviderInfo(request.api, upstashToken);
|
|
466
|
+
if (request.api.name === "llm") {
|
|
467
|
+
const callback = request.callback;
|
|
468
|
+
if (!callback) {
|
|
469
|
+
throw new TypeError("Callback cannot be undefined when using LLM api.");
|
|
470
|
+
}
|
|
471
|
+
return {
|
|
472
|
+
...request,
|
|
473
|
+
method: request.method ?? method,
|
|
474
|
+
headers: safeJoinHeaders(headers, appendHeaders),
|
|
475
|
+
...owner === "upstash" && !request.api.analytics ? { api: { name: "llm" }, url: void 0, callback } : { url, api: void 0 }
|
|
434
476
|
};
|
|
435
|
-
|
|
436
|
-
method: "GET",
|
|
437
|
-
path: ["v2", "dlq"],
|
|
438
|
-
query: {
|
|
439
|
-
cursor: options?.cursor,
|
|
440
|
-
count: options?.count,
|
|
441
|
-
...filterPayload
|
|
442
|
-
}
|
|
443
|
-
});
|
|
477
|
+
} else {
|
|
444
478
|
return {
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
};
|
|
451
|
-
}),
|
|
452
|
-
cursor: messagesPayload.cursor
|
|
479
|
+
...request,
|
|
480
|
+
method: request.method ?? method,
|
|
481
|
+
headers: safeJoinHeaders(headers, appendHeaders),
|
|
482
|
+
url,
|
|
483
|
+
api: void 0
|
|
453
484
|
};
|
|
454
485
|
}
|
|
455
|
-
/**
|
|
456
|
-
* Remove a message from the dlq using it's `dlqId`
|
|
457
|
-
*/
|
|
458
|
-
async delete(dlqMessageId) {
|
|
459
|
-
return await this.http.request({
|
|
460
|
-
method: "DELETE",
|
|
461
|
-
path: ["v2", "dlq", dlqMessageId],
|
|
462
|
-
parseResponseAsJson: false
|
|
463
|
-
// there is no response
|
|
464
|
-
});
|
|
465
|
-
}
|
|
466
|
-
/**
|
|
467
|
-
* Remove multiple messages from the dlq using their `dlqId`s
|
|
468
|
-
*/
|
|
469
|
-
async deleteMany(request) {
|
|
470
|
-
return await this.http.request({
|
|
471
|
-
method: "DELETE",
|
|
472
|
-
path: ["v2", "dlq"],
|
|
473
|
-
headers: { "Content-Type": "application/json" },
|
|
474
|
-
body: JSON.stringify({ dlqIds: request.dlqIds })
|
|
475
|
-
});
|
|
476
|
-
}
|
|
477
486
|
};
|
|
487
|
+
function updateWithAnalytics(providerInfo, analytics) {
|
|
488
|
+
switch (analytics.name) {
|
|
489
|
+
case "helicone": {
|
|
490
|
+
providerInfo.appendHeaders["Helicone-Auth"] = `Bearer ${analytics.token}`;
|
|
491
|
+
if (providerInfo.owner === "upstash") {
|
|
492
|
+
updateProviderInfo(providerInfo, "https://qstash.helicone.ai", [
|
|
493
|
+
"llm",
|
|
494
|
+
...providerInfo.route
|
|
495
|
+
]);
|
|
496
|
+
} else {
|
|
497
|
+
providerInfo.appendHeaders["Helicone-Target-Url"] = providerInfo.baseUrl;
|
|
498
|
+
updateProviderInfo(providerInfo, "https://gateway.helicone.ai", providerInfo.route);
|
|
499
|
+
}
|
|
500
|
+
return providerInfo;
|
|
501
|
+
}
|
|
502
|
+
default: {
|
|
503
|
+
throw new Error("Unknown analytics provider");
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
function updateProviderInfo(providerInfo, baseUrl, route) {
|
|
508
|
+
providerInfo.baseUrl = baseUrl;
|
|
509
|
+
providerInfo.route = route;
|
|
510
|
+
providerInfo.url = `${baseUrl}/${route.join("/")}`;
|
|
511
|
+
}
|
|
478
512
|
|
|
479
513
|
// src/client/error.ts
|
|
480
514
|
var RATELIMIT_STATUS = 429;
|
|
@@ -556,644 +590,764 @@ var formatWorkflowError = (error) => {
|
|
|
556
590
|
};
|
|
557
591
|
};
|
|
558
592
|
|
|
559
|
-
// src/client/
|
|
560
|
-
var
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
headers;
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
this.retry = // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
571
|
-
typeof config.retry === "boolean" && !config.retry ? {
|
|
572
|
-
attempts: 1,
|
|
573
|
-
backoff: () => 0
|
|
574
|
-
} : {
|
|
575
|
-
attempts: config.retry?.retries ?? 5,
|
|
576
|
-
backoff: config.retry?.backoff ?? ((retryCount) => Math.exp(retryCount) * 50)
|
|
577
|
-
};
|
|
578
|
-
this.headers = config.headers;
|
|
579
|
-
this.telemetryHeaders = config.telemetryHeaders;
|
|
580
|
-
}
|
|
581
|
-
async request(request) {
|
|
582
|
-
const { response } = await this.requestWithBackoff(request);
|
|
583
|
-
if (request.parseResponseAsJson === false) {
|
|
584
|
-
return void 0;
|
|
585
|
-
}
|
|
586
|
-
return await response.json();
|
|
587
|
-
}
|
|
588
|
-
async *requestStream(request) {
|
|
589
|
-
const { response } = await this.requestWithBackoff(request);
|
|
590
|
-
if (!response.body) {
|
|
591
|
-
throw new Error("No response body");
|
|
592
|
-
}
|
|
593
|
-
const body = response.body;
|
|
594
|
-
const reader = body.getReader();
|
|
595
|
-
const decoder = new TextDecoder();
|
|
596
|
-
try {
|
|
597
|
-
while (true) {
|
|
598
|
-
const { done, value } = await reader.read();
|
|
599
|
-
if (done) {
|
|
600
|
-
break;
|
|
601
|
-
}
|
|
602
|
-
const chunkText = decoder.decode(value, { stream: true });
|
|
603
|
-
const chunks = chunkText.split("\n").filter(Boolean);
|
|
604
|
-
for (const chunk of chunks) {
|
|
605
|
-
if (chunk.startsWith("data: ")) {
|
|
606
|
-
const data = chunk.slice(6);
|
|
607
|
-
if (data === "[DONE]") {
|
|
608
|
-
break;
|
|
609
|
-
}
|
|
610
|
-
yield JSON.parse(data);
|
|
611
|
-
}
|
|
612
|
-
}
|
|
613
|
-
}
|
|
614
|
-
} finally {
|
|
615
|
-
await reader.cancel();
|
|
593
|
+
// src/client/utils.ts
|
|
594
|
+
var isIgnoredHeader = (header) => {
|
|
595
|
+
const lowerCaseHeader = header.toLowerCase();
|
|
596
|
+
return lowerCaseHeader.startsWith("content-type") || lowerCaseHeader.startsWith("upstash-");
|
|
597
|
+
};
|
|
598
|
+
function prefixHeaders(headers) {
|
|
599
|
+
const keysToBePrefixed = [...headers.keys()].filter((key) => !isIgnoredHeader(key));
|
|
600
|
+
for (const key of keysToBePrefixed) {
|
|
601
|
+
const value = headers.get(key);
|
|
602
|
+
if (value !== null) {
|
|
603
|
+
headers.set(`Upstash-Forward-${key}`, value);
|
|
616
604
|
}
|
|
605
|
+
headers.delete(key);
|
|
617
606
|
}
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
const headers = new Headers(request.headers);
|
|
644
|
-
if (!headers.has("Authorization")) {
|
|
645
|
-
headers.set("Authorization", this.authorization);
|
|
607
|
+
return headers;
|
|
608
|
+
}
|
|
609
|
+
function wrapWithGlobalHeaders(headers, globalHeaders, telemetryHeaders) {
|
|
610
|
+
if (!globalHeaders) {
|
|
611
|
+
return headers;
|
|
612
|
+
}
|
|
613
|
+
const finalHeaders = new Headers(globalHeaders);
|
|
614
|
+
headers.forEach((value, key) => {
|
|
615
|
+
finalHeaders.set(key, value);
|
|
616
|
+
});
|
|
617
|
+
telemetryHeaders?.forEach((value, key) => {
|
|
618
|
+
if (!value)
|
|
619
|
+
return;
|
|
620
|
+
finalHeaders.append(key, value);
|
|
621
|
+
});
|
|
622
|
+
return finalHeaders;
|
|
623
|
+
}
|
|
624
|
+
function processHeaders(request) {
|
|
625
|
+
const headers = prefixHeaders(new Headers(request.headers));
|
|
626
|
+
headers.set("Upstash-Method", request.method ?? "POST");
|
|
627
|
+
if (request.delay !== void 0) {
|
|
628
|
+
if (typeof request.delay === "string") {
|
|
629
|
+
headers.set("Upstash-Delay", request.delay);
|
|
630
|
+
} else {
|
|
631
|
+
headers.set("Upstash-Delay", `${request.delay.toFixed(0)}s`);
|
|
646
632
|
}
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
633
|
+
}
|
|
634
|
+
if (request.notBefore !== void 0) {
|
|
635
|
+
headers.set("Upstash-Not-Before", request.notBefore.toFixed(0));
|
|
636
|
+
}
|
|
637
|
+
if (request.deduplicationId !== void 0) {
|
|
638
|
+
headers.set("Upstash-Deduplication-Id", request.deduplicationId);
|
|
639
|
+
}
|
|
640
|
+
if (request.contentBasedDeduplication) {
|
|
641
|
+
headers.set("Upstash-Content-Based-Deduplication", "true");
|
|
642
|
+
}
|
|
643
|
+
if (request.retries !== void 0) {
|
|
644
|
+
headers.set("Upstash-Retries", request.retries.toFixed(0));
|
|
645
|
+
}
|
|
646
|
+
if (request.retryDelay !== void 0) {
|
|
647
|
+
headers.set("Upstash-Retry-Delay", request.retryDelay);
|
|
648
|
+
}
|
|
649
|
+
if (request.callback !== void 0) {
|
|
650
|
+
headers.set("Upstash-Callback", request.callback);
|
|
651
|
+
}
|
|
652
|
+
if (request.failureCallback !== void 0) {
|
|
653
|
+
headers.set("Upstash-Failure-Callback", request.failureCallback);
|
|
654
|
+
}
|
|
655
|
+
if (request.timeout !== void 0) {
|
|
656
|
+
if (typeof request.timeout === "string") {
|
|
657
|
+
headers.set("Upstash-Timeout", request.timeout);
|
|
658
|
+
} else {
|
|
659
|
+
headers.set("Upstash-Timeout", `${request.timeout}s`);
|
|
660
660
|
}
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
});
|
|
674
|
-
} else if (response.headers.get("RateLimit-Limit")) {
|
|
675
|
-
throw new QstashDailyRatelimitError({
|
|
676
|
-
limit: response.headers.get("RateLimit-Limit"),
|
|
677
|
-
remaining: response.headers.get("RateLimit-Remaining"),
|
|
678
|
-
reset: response.headers.get("RateLimit-Reset")
|
|
679
|
-
});
|
|
680
|
-
}
|
|
681
|
-
throw new QstashRatelimitError({
|
|
682
|
-
limit: response.headers.get("Burst-RateLimit-Limit"),
|
|
683
|
-
remaining: response.headers.get("Burst-RateLimit-Remaining"),
|
|
684
|
-
reset: response.headers.get("Burst-RateLimit-Reset")
|
|
685
|
-
});
|
|
661
|
+
}
|
|
662
|
+
if (request.flowControl?.key) {
|
|
663
|
+
const parallelism = request.flowControl.parallelism?.toString();
|
|
664
|
+
const rate = (request.flowControl.rate ?? request.flowControl.ratePerSecond)?.toString();
|
|
665
|
+
const period = typeof request.flowControl.period === "number" ? `${request.flowControl.period}s` : request.flowControl.period;
|
|
666
|
+
const controlValue = [
|
|
667
|
+
parallelism ? `parallelism=${parallelism}` : void 0,
|
|
668
|
+
rate ? `rate=${rate}` : void 0,
|
|
669
|
+
period ? `period=${period}` : void 0
|
|
670
|
+
].filter(Boolean);
|
|
671
|
+
if (controlValue.length === 0) {
|
|
672
|
+
throw new QstashError("Provide at least one of parallelism or ratePerSecond for flowControl");
|
|
686
673
|
}
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
674
|
+
headers.set("Upstash-Flow-Control-Key", request.flowControl.key);
|
|
675
|
+
headers.set("Upstash-Flow-Control-Value", controlValue.join(", "));
|
|
676
|
+
}
|
|
677
|
+
if (request.label !== void 0) {
|
|
678
|
+
headers.set("Upstash-Label", request.label);
|
|
679
|
+
}
|
|
680
|
+
return headers;
|
|
681
|
+
}
|
|
682
|
+
function getRequestPath(request) {
|
|
683
|
+
const nonApiPath = request.url ?? request.urlGroup ?? request.topic;
|
|
684
|
+
if (nonApiPath)
|
|
685
|
+
return nonApiPath;
|
|
686
|
+
if (request.api?.name === "llm")
|
|
687
|
+
return `api/llm`;
|
|
688
|
+
if (request.api?.name === "email") {
|
|
689
|
+
const providerInfo = getProviderInfo(request.api, "not-needed");
|
|
690
|
+
return providerInfo.baseUrl;
|
|
691
|
+
}
|
|
692
|
+
throw new QstashError(`Failed to infer request path for ${JSON.stringify(request)}`);
|
|
693
|
+
}
|
|
694
|
+
var NANOID_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_";
|
|
695
|
+
var NANOID_LENGTH = 21;
|
|
696
|
+
function nanoid() {
|
|
697
|
+
return [...crypto.getRandomValues(new Uint8Array(NANOID_LENGTH))].map((x) => NANOID_CHARS[x % NANOID_CHARS.length]).join("");
|
|
698
|
+
}
|
|
699
|
+
function decodeBase64(base64) {
|
|
700
|
+
try {
|
|
701
|
+
const binString = atob(base64);
|
|
702
|
+
const intArray = Uint8Array.from(binString, (m) => m.codePointAt(0));
|
|
703
|
+
return new TextDecoder().decode(intArray);
|
|
704
|
+
} catch (error) {
|
|
705
|
+
try {
|
|
706
|
+
const result = atob(base64);
|
|
707
|
+
console.warn(
|
|
708
|
+
`Upstash QStash: Failed while decoding base64 "${base64}". Decoding with atob and returning it instead. ${error}`
|
|
692
709
|
);
|
|
710
|
+
return result;
|
|
711
|
+
} catch (error2) {
|
|
712
|
+
console.warn(
|
|
713
|
+
`Upstash QStash: Failed to decode base64 "${base64}" with atob. Returning it as it is. ${error2}`
|
|
714
|
+
);
|
|
715
|
+
return base64;
|
|
693
716
|
}
|
|
694
717
|
}
|
|
718
|
+
}
|
|
719
|
+
function getRuntime() {
|
|
720
|
+
if (typeof process === "object" && typeof process.versions == "object" && process.versions.bun)
|
|
721
|
+
return `bun@${process.versions.bun}`;
|
|
722
|
+
if (typeof EdgeRuntime === "string")
|
|
723
|
+
return "edge-light";
|
|
724
|
+
else if (typeof process === "object" && typeof process.version === "string")
|
|
725
|
+
return `node@${process.version}`;
|
|
726
|
+
return "";
|
|
727
|
+
}
|
|
728
|
+
function getSafeEnvironment() {
|
|
729
|
+
return typeof process === "undefined" ? {} : process.env;
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
// src/client/multi-region/utils.ts
|
|
733
|
+
var VALID_REGIONS = ["EU_CENTRAL_1", "US_EAST_1"];
|
|
734
|
+
var DEFAULT_QSTASH_URL = "https://qstash.upstash.io";
|
|
735
|
+
var getRegionFromEnvironment = (environment) => {
|
|
736
|
+
const region = environment.QSTASH_REGION;
|
|
737
|
+
return normalizeRegionHeader(region);
|
|
695
738
|
};
|
|
739
|
+
function readEnvironmentVariables(environmentVariables, environment, region) {
|
|
740
|
+
const result = {};
|
|
741
|
+
for (const variable of environmentVariables) {
|
|
742
|
+
const key = region ? `${region}_${variable}` : variable;
|
|
743
|
+
result[variable] = environment[key];
|
|
744
|
+
}
|
|
745
|
+
return result;
|
|
746
|
+
}
|
|
747
|
+
function readClientEnvironmentVariables(environment, region) {
|
|
748
|
+
return readEnvironmentVariables(["QSTASH_URL", "QSTASH_TOKEN"], environment, region);
|
|
749
|
+
}
|
|
750
|
+
function readReceiverEnvironmentVariables(environment, region) {
|
|
751
|
+
return readEnvironmentVariables(
|
|
752
|
+
["QSTASH_CURRENT_SIGNING_KEY", "QSTASH_NEXT_SIGNING_KEY"],
|
|
753
|
+
environment,
|
|
754
|
+
region
|
|
755
|
+
);
|
|
756
|
+
}
|
|
757
|
+
function normalizeRegionHeader(region) {
|
|
758
|
+
if (!region) {
|
|
759
|
+
return void 0;
|
|
760
|
+
}
|
|
761
|
+
region = region.replaceAll("-", "_").toUpperCase();
|
|
762
|
+
if (VALID_REGIONS.includes(region)) {
|
|
763
|
+
return region;
|
|
764
|
+
}
|
|
765
|
+
console.warn(
|
|
766
|
+
`[Upstash QStash] Invalid UPSTASH_REGION header value: "${region}". Expected one of: ${VALID_REGIONS.join(
|
|
767
|
+
", "
|
|
768
|
+
)}.`
|
|
769
|
+
);
|
|
770
|
+
return void 0;
|
|
771
|
+
}
|
|
696
772
|
|
|
697
|
-
// src/client/
|
|
698
|
-
var
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
773
|
+
// src/client/multi-region/incoming.ts
|
|
774
|
+
var getReceiverSigningKeys = ({
|
|
775
|
+
environment,
|
|
776
|
+
regionFromHeader,
|
|
777
|
+
config
|
|
778
|
+
}) => {
|
|
779
|
+
if (config?.currentSigningKey && config.nextSigningKey) {
|
|
780
|
+
return {
|
|
781
|
+
currentSigningKey: config.currentSigningKey,
|
|
782
|
+
nextSigningKey: config.nextSigningKey
|
|
783
|
+
};
|
|
784
|
+
}
|
|
785
|
+
const regionEnvironment = getRegionFromEnvironment(environment);
|
|
786
|
+
if (regionEnvironment) {
|
|
787
|
+
const regionHeader = normalizeRegionHeader(regionFromHeader);
|
|
788
|
+
if (regionHeader) {
|
|
789
|
+
const regionCreds = readReceiverEnvironmentVariables(environment, regionHeader);
|
|
790
|
+
if (regionCreds.QSTASH_CURRENT_SIGNING_KEY && regionCreds.QSTASH_NEXT_SIGNING_KEY) {
|
|
791
|
+
return {
|
|
792
|
+
currentSigningKey: regionCreds.QSTASH_CURRENT_SIGNING_KEY,
|
|
793
|
+
nextSigningKey: regionCreds.QSTASH_NEXT_SIGNING_KEY,
|
|
794
|
+
region: regionHeader
|
|
795
|
+
};
|
|
796
|
+
} else {
|
|
797
|
+
console.warn(
|
|
798
|
+
`[Upstash QStash] Signing keys not found for region "${regionHeader}". Falling back to default signing keys.`
|
|
799
|
+
);
|
|
723
800
|
}
|
|
724
|
-
}
|
|
725
|
-
|
|
726
|
-
|
|
801
|
+
} else {
|
|
802
|
+
console.warn(
|
|
803
|
+
`[Upstash QStash] Invalid UPSTASH_REGION header value: "${regionFromHeader}". Expected one of: EU-CENTRAL-1, US-EAST-1. Falling back to default signing keys.`
|
|
804
|
+
);
|
|
727
805
|
}
|
|
728
806
|
}
|
|
807
|
+
const defaultCreds = readReceiverEnvironmentVariables(environment);
|
|
808
|
+
if (defaultCreds.QSTASH_CURRENT_SIGNING_KEY && defaultCreds.QSTASH_NEXT_SIGNING_KEY) {
|
|
809
|
+
return {
|
|
810
|
+
currentSigningKey: defaultCreds.QSTASH_CURRENT_SIGNING_KEY,
|
|
811
|
+
nextSigningKey: defaultCreds.QSTASH_NEXT_SIGNING_KEY
|
|
812
|
+
};
|
|
813
|
+
}
|
|
729
814
|
};
|
|
730
815
|
|
|
731
|
-
// src/client/
|
|
732
|
-
var
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
);
|
|
745
|
-
const chatRequest = { ...request, messages };
|
|
746
|
-
return chatRequest;
|
|
747
|
-
}
|
|
748
|
-
/**
|
|
749
|
-
* Calls the Upstash completions api given a ChatRequest.
|
|
750
|
-
*
|
|
751
|
-
* Returns a ChatCompletion or a stream of ChatCompletionChunks
|
|
752
|
-
* if stream is enabled.
|
|
753
|
-
*
|
|
754
|
-
* @param request ChatRequest with messages
|
|
755
|
-
* @returns Chat completion or stream
|
|
756
|
-
*/
|
|
757
|
-
create = async (request) => {
|
|
758
|
-
if (request.provider.owner != "upstash")
|
|
759
|
-
return this.createThirdParty(request);
|
|
760
|
-
const body = JSON.stringify(request);
|
|
761
|
-
let baseUrl = void 0;
|
|
762
|
-
let headers = {
|
|
763
|
-
"Content-Type": "application/json",
|
|
764
|
-
Authorization: `Bearer ${this.token}`,
|
|
765
|
-
..."stream" in request && request.stream ? {
|
|
766
|
-
Connection: "keep-alive",
|
|
767
|
-
Accept: "text/event-stream",
|
|
768
|
-
"Cache-Control": "no-cache"
|
|
769
|
-
} : {}
|
|
816
|
+
// src/client/multi-region/outgoing.ts
|
|
817
|
+
var getClientCredentials = (clientCredentialConfig) => {
|
|
818
|
+
const credentials = resolveCredentials(clientCredentialConfig);
|
|
819
|
+
return verifyCredentials(credentials);
|
|
820
|
+
};
|
|
821
|
+
var resolveCredentials = ({
|
|
822
|
+
environment,
|
|
823
|
+
config
|
|
824
|
+
}) => {
|
|
825
|
+
if (config?.baseUrl && config.token) {
|
|
826
|
+
return {
|
|
827
|
+
baseUrl: config.baseUrl,
|
|
828
|
+
token: config.token
|
|
770
829
|
};
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
830
|
+
}
|
|
831
|
+
const region = getRegionFromEnvironment(environment);
|
|
832
|
+
if (region) {
|
|
833
|
+
const regionCreds = readClientEnvironmentVariables(environment, region);
|
|
834
|
+
if (regionCreds.QSTASH_URL && regionCreds.QSTASH_TOKEN) {
|
|
835
|
+
return {
|
|
836
|
+
baseUrl: regionCreds.QSTASH_URL,
|
|
837
|
+
token: regionCreds.QSTASH_TOKEN,
|
|
838
|
+
region
|
|
839
|
+
};
|
|
840
|
+
} else {
|
|
841
|
+
console.warn(
|
|
842
|
+
`[Upstash QStash] QSTASH_REGION is set to "${region}" but credentials are missing. Expected ${region}_QSTASH_URL and ${region}_QSTASH_TOKEN. Falling back to default credentials.`
|
|
777
843
|
);
|
|
778
|
-
headers = { ...headers, ...defaultHeaders };
|
|
779
|
-
baseUrl = baseURL;
|
|
780
844
|
}
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
baseUrl,
|
|
787
|
-
body
|
|
788
|
-
}) : this.http.request({
|
|
789
|
-
path,
|
|
790
|
-
method: "POST",
|
|
791
|
-
headers,
|
|
792
|
-
baseUrl,
|
|
793
|
-
body
|
|
794
|
-
});
|
|
845
|
+
}
|
|
846
|
+
const defaultCreds = readClientEnvironmentVariables(environment);
|
|
847
|
+
return {
|
|
848
|
+
baseUrl: config?.baseUrl ?? defaultCreds.QSTASH_URL ?? DEFAULT_QSTASH_URL,
|
|
849
|
+
token: config?.token ?? defaultCreds.QSTASH_TOKEN ?? ""
|
|
795
850
|
};
|
|
851
|
+
};
|
|
852
|
+
var verifyCredentials = (credentials) => {
|
|
853
|
+
const token = credentials.token;
|
|
854
|
+
let baseUrl = credentials.baseUrl;
|
|
855
|
+
baseUrl = baseUrl.replace(/\/$/, "");
|
|
856
|
+
if (baseUrl === "https://qstash.upstash.io/v2/publish") {
|
|
857
|
+
baseUrl = DEFAULT_QSTASH_URL;
|
|
858
|
+
}
|
|
859
|
+
if (!token) {
|
|
860
|
+
console.warn(
|
|
861
|
+
"[Upstash QStash] client token is not set. Either pass a token or set QSTASH_TOKEN env variable."
|
|
862
|
+
);
|
|
863
|
+
}
|
|
864
|
+
return { baseUrl, token };
|
|
865
|
+
};
|
|
866
|
+
|
|
867
|
+
// src/receiver.ts
|
|
868
|
+
var SignatureError = class extends Error {
|
|
869
|
+
constructor(message) {
|
|
870
|
+
super(message);
|
|
871
|
+
this.name = "SignatureError";
|
|
872
|
+
}
|
|
873
|
+
};
|
|
874
|
+
var Receiver = class {
|
|
875
|
+
currentSigningKey;
|
|
876
|
+
nextSigningKey;
|
|
877
|
+
constructor(config) {
|
|
878
|
+
this.currentSigningKey = config?.currentSigningKey;
|
|
879
|
+
this.nextSigningKey = config?.nextSigningKey;
|
|
880
|
+
}
|
|
796
881
|
/**
|
|
797
|
-
*
|
|
882
|
+
* Verify the signature of a request.
|
|
798
883
|
*
|
|
799
|
-
*
|
|
800
|
-
*
|
|
884
|
+
* Tries to verify the signature with the current signing key.
|
|
885
|
+
* If that fails, maybe because you have rotated the keys recently, it will
|
|
886
|
+
* try to verify the signature with the next signing key.
|
|
801
887
|
*
|
|
802
|
-
*
|
|
803
|
-
* @returns Chat completion or stream
|
|
888
|
+
* If that fails, the signature is invalid and a `SignatureError` is thrown.
|
|
804
889
|
*/
|
|
805
|
-
|
|
806
|
-
const
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
const isAnalyticsEnabled = analytics?.name && analytics.token;
|
|
815
|
-
const analyticsConfig = analytics?.name && analytics.token ? setupAnalytics({ name: analytics.name, token: analytics.token }, token, baseUrl, owner) : { defaultHeaders: void 0, baseURL: baseUrl };
|
|
816
|
-
const isStream = "stream" in request && request.stream;
|
|
817
|
-
const headers = {
|
|
818
|
-
"Content-Type": "application/json",
|
|
819
|
-
Authorization: `Bearer ${token}`,
|
|
820
|
-
...organization ? {
|
|
821
|
-
"OpenAI-Organization": organization
|
|
822
|
-
} : {},
|
|
823
|
-
...isStream ? {
|
|
824
|
-
Connection: "keep-alive",
|
|
825
|
-
Accept: "text/event-stream",
|
|
826
|
-
"Cache-Control": "no-cache"
|
|
827
|
-
} : {},
|
|
828
|
-
...analyticsConfig.defaultHeaders
|
|
829
|
-
};
|
|
830
|
-
const response = await this.http[isStream ? "requestStream" : "request"]({
|
|
831
|
-
path: isAnalyticsEnabled ? [] : ["v1", "chat", "completions"],
|
|
832
|
-
method: "POST",
|
|
833
|
-
headers,
|
|
834
|
-
body,
|
|
835
|
-
baseUrl: analyticsConfig.baseURL
|
|
890
|
+
async verify(request) {
|
|
891
|
+
const environment = getSafeEnvironment();
|
|
892
|
+
const signingKeys = getReceiverSigningKeys({
|
|
893
|
+
environment,
|
|
894
|
+
regionFromHeader: request.upstashRegion,
|
|
895
|
+
config: {
|
|
896
|
+
currentSigningKey: this.currentSigningKey,
|
|
897
|
+
nextSigningKey: this.nextSigningKey
|
|
898
|
+
}
|
|
836
899
|
});
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
const authHeader = String(this.http.authorization);
|
|
842
|
-
const match = /Bearer (.+)/.exec(authHeader);
|
|
843
|
-
if (!match) {
|
|
844
|
-
throw new Error("Invalid authorization header format");
|
|
900
|
+
if (!signingKeys) {
|
|
901
|
+
throw new Error(
|
|
902
|
+
"[Upstash QStash] No signing keys available for verification. See the warning above for more details."
|
|
903
|
+
);
|
|
845
904
|
}
|
|
846
|
-
|
|
905
|
+
let payload;
|
|
906
|
+
try {
|
|
907
|
+
payload = await this.verifyWithKey(signingKeys.currentSigningKey, request);
|
|
908
|
+
} catch {
|
|
909
|
+
payload = await this.verifyWithKey(signingKeys.nextSigningKey, request);
|
|
910
|
+
}
|
|
911
|
+
this.verifyBodyAndUrl(payload, request);
|
|
912
|
+
return true;
|
|
847
913
|
}
|
|
848
914
|
/**
|
|
849
|
-
*
|
|
850
|
-
*
|
|
851
|
-
* Returns a ChatCompletion or a stream of ChatCompletionChunks
|
|
852
|
-
* if stream is enabled.
|
|
853
|
-
*
|
|
854
|
-
* @param request PromptRequest with system and user messages.
|
|
855
|
-
* Note that system parameter shouldn't be passed in the case of
|
|
856
|
-
* mistralai/Mistral-7B-Instruct-v0.2 model.
|
|
857
|
-
* @returns Chat completion or stream
|
|
915
|
+
* Verify signature with a specific signing key
|
|
858
916
|
*/
|
|
859
|
-
|
|
860
|
-
const
|
|
861
|
-
|
|
862
|
-
|
|
917
|
+
async verifyWithKey(key, request) {
|
|
918
|
+
const jwt = await jose.jwtVerify(request.signature, new TextEncoder().encode(key), {
|
|
919
|
+
issuer: "Upstash",
|
|
920
|
+
clockTolerance: request.clockTolerance
|
|
921
|
+
}).catch((error) => {
|
|
922
|
+
throw new SignatureError(error.message);
|
|
923
|
+
});
|
|
924
|
+
return jwt.payload;
|
|
925
|
+
}
|
|
926
|
+
verifyBodyAndUrl(payload, request) {
|
|
927
|
+
const p = payload;
|
|
928
|
+
if (request.url !== void 0 && p.sub !== request.url) {
|
|
929
|
+
throw new SignatureError(`invalid subject: ${p.sub}, want: ${request.url}`);
|
|
930
|
+
}
|
|
931
|
+
const bodyHash = import_crypto_js.default.SHA256(request.body).toString(import_crypto_js.default.enc.Base64url);
|
|
932
|
+
const padding = new RegExp(/=+$/);
|
|
933
|
+
if (p.body.replace(padding, "") !== bodyHash.replace(padding, "")) {
|
|
934
|
+
throw new SignatureError(`body hash does not match, want: ${p.body}, got: ${bodyHash}`);
|
|
935
|
+
}
|
|
936
|
+
}
|
|
863
937
|
};
|
|
864
938
|
|
|
865
|
-
// src/client/
|
|
866
|
-
var
|
|
939
|
+
// src/client/dlq.ts
|
|
940
|
+
var DLQ = class {
|
|
867
941
|
http;
|
|
868
942
|
constructor(http) {
|
|
869
943
|
this.http = http;
|
|
870
944
|
}
|
|
871
945
|
/**
|
|
872
|
-
*
|
|
946
|
+
* List messages in the dlq
|
|
873
947
|
*/
|
|
874
|
-
async
|
|
875
|
-
const
|
|
948
|
+
async listMessages(options) {
|
|
949
|
+
const filterPayload = {
|
|
950
|
+
...options?.filter,
|
|
951
|
+
topicName: options?.filter?.urlGroup
|
|
952
|
+
};
|
|
953
|
+
const messagesPayload = await this.http.request({
|
|
876
954
|
method: "GET",
|
|
877
|
-
path: ["v2", "
|
|
955
|
+
path: ["v2", "dlq"],
|
|
956
|
+
query: {
|
|
957
|
+
cursor: options?.cursor,
|
|
958
|
+
count: options?.count,
|
|
959
|
+
...filterPayload
|
|
960
|
+
}
|
|
878
961
|
});
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
962
|
+
return {
|
|
963
|
+
messages: messagesPayload.messages.map((message) => {
|
|
964
|
+
return {
|
|
965
|
+
...message,
|
|
966
|
+
urlGroup: message.topicName,
|
|
967
|
+
ratePerSecond: "rate" in message ? message.rate : void 0
|
|
968
|
+
};
|
|
969
|
+
}),
|
|
970
|
+
cursor: messagesPayload.cursor
|
|
883
971
|
};
|
|
884
|
-
return message;
|
|
885
972
|
}
|
|
886
973
|
/**
|
|
887
|
-
*
|
|
974
|
+
* Remove a message from the dlq using it's `dlqId`
|
|
888
975
|
*/
|
|
889
|
-
async delete(
|
|
976
|
+
async delete(dlqMessageId) {
|
|
890
977
|
return await this.http.request({
|
|
891
978
|
method: "DELETE",
|
|
892
|
-
path: ["v2", "
|
|
979
|
+
path: ["v2", "dlq", dlqMessageId],
|
|
893
980
|
parseResponseAsJson: false
|
|
981
|
+
// there is no response
|
|
894
982
|
});
|
|
895
983
|
}
|
|
896
|
-
async deleteMany(messageIds) {
|
|
897
|
-
const result = await this.http.request({
|
|
898
|
-
method: "DELETE",
|
|
899
|
-
path: ["v2", "messages"],
|
|
900
|
-
headers: { "Content-Type": "application/json" },
|
|
901
|
-
body: JSON.stringify({ messageIds })
|
|
902
|
-
});
|
|
903
|
-
return result.cancelled;
|
|
904
|
-
}
|
|
905
|
-
async deleteAll() {
|
|
906
|
-
const result = await this.http.request({
|
|
907
|
-
method: "DELETE",
|
|
908
|
-
path: ["v2", "messages"]
|
|
909
|
-
});
|
|
910
|
-
return result.cancelled;
|
|
911
|
-
}
|
|
912
|
-
};
|
|
913
|
-
|
|
914
|
-
// src/client/api/base.ts
|
|
915
|
-
var BaseProvider = class {
|
|
916
|
-
baseUrl;
|
|
917
|
-
token;
|
|
918
|
-
owner;
|
|
919
|
-
constructor(baseUrl, token, owner) {
|
|
920
|
-
this.baseUrl = baseUrl;
|
|
921
|
-
this.token = token;
|
|
922
|
-
this.owner = owner;
|
|
923
|
-
}
|
|
924
|
-
getUrl() {
|
|
925
|
-
return `${this.baseUrl}/${this.getRoute().join("/")}`;
|
|
926
|
-
}
|
|
927
|
-
};
|
|
928
|
-
|
|
929
|
-
// src/client/api/llm.ts
|
|
930
|
-
var LLMProvider = class extends BaseProvider {
|
|
931
|
-
apiKind = "llm";
|
|
932
|
-
organization;
|
|
933
|
-
method = "POST";
|
|
934
|
-
constructor(baseUrl, token, owner, organization) {
|
|
935
|
-
super(baseUrl, token, owner);
|
|
936
|
-
this.organization = organization;
|
|
937
|
-
}
|
|
938
|
-
getRoute() {
|
|
939
|
-
return this.owner === "anthropic" ? ["v1", "messages"] : ["v1", "chat", "completions"];
|
|
940
|
-
}
|
|
941
|
-
getHeaders(options) {
|
|
942
|
-
if (this.owner === "upstash" && !options.analytics) {
|
|
943
|
-
return { "content-type": "application/json" };
|
|
944
|
-
}
|
|
945
|
-
const header = this.owner === "anthropic" ? "x-api-key" : "authorization";
|
|
946
|
-
const headerValue = this.owner === "anthropic" ? this.token : `Bearer ${this.token}`;
|
|
947
|
-
const headers = {
|
|
948
|
-
[header]: headerValue,
|
|
949
|
-
"content-type": "application/json"
|
|
950
|
-
};
|
|
951
|
-
if (this.owner === "openai" && this.organization) {
|
|
952
|
-
headers["OpenAI-Organization"] = this.organization;
|
|
953
|
-
}
|
|
954
|
-
if (this.owner === "anthropic") {
|
|
955
|
-
headers["anthropic-version"] = "2023-06-01";
|
|
956
|
-
}
|
|
957
|
-
return headers;
|
|
958
|
-
}
|
|
959
984
|
/**
|
|
960
|
-
*
|
|
961
|
-
*
|
|
962
|
-
* @param request
|
|
963
|
-
* @param options
|
|
985
|
+
* Remove multiple messages from the dlq using their `dlqId`s
|
|
964
986
|
*/
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
};
|
|
972
|
-
var upstash = () => {
|
|
973
|
-
return new LLMProvider("https://qstash.upstash.io/llm", "", "upstash");
|
|
974
|
-
};
|
|
975
|
-
|
|
976
|
-
// src/client/api/utils.ts
|
|
977
|
-
var getProviderInfo = (api, upstashToken) => {
|
|
978
|
-
const { name, provider, ...parameters } = api;
|
|
979
|
-
const finalProvider = provider ?? upstash();
|
|
980
|
-
if (finalProvider.owner === "upstash" && !finalProvider.token) {
|
|
981
|
-
finalProvider.token = upstashToken;
|
|
982
|
-
}
|
|
983
|
-
if (!finalProvider.baseUrl)
|
|
984
|
-
throw new TypeError("baseUrl cannot be empty or undefined!");
|
|
985
|
-
if (!finalProvider.token)
|
|
986
|
-
throw new TypeError("token cannot be empty or undefined!");
|
|
987
|
-
if (finalProvider.apiKind !== name) {
|
|
988
|
-
throw new TypeError(
|
|
989
|
-
`Unexpected api name. Expected '${finalProvider.apiKind}', received ${name}`
|
|
990
|
-
);
|
|
991
|
-
}
|
|
992
|
-
const providerInfo = {
|
|
993
|
-
url: finalProvider.getUrl(),
|
|
994
|
-
baseUrl: finalProvider.baseUrl,
|
|
995
|
-
route: finalProvider.getRoute(),
|
|
996
|
-
appendHeaders: finalProvider.getHeaders(parameters),
|
|
997
|
-
owner: finalProvider.owner,
|
|
998
|
-
method: finalProvider.method
|
|
999
|
-
};
|
|
1000
|
-
return finalProvider.onFinish(providerInfo, parameters);
|
|
1001
|
-
};
|
|
1002
|
-
var safeJoinHeaders = (headers, record) => {
|
|
1003
|
-
const joinedHeaders = new Headers(record);
|
|
1004
|
-
for (const [header, value] of headers.entries()) {
|
|
1005
|
-
joinedHeaders.set(header, value);
|
|
987
|
+
async deleteMany(request) {
|
|
988
|
+
return await this.http.request({
|
|
989
|
+
method: "DELETE",
|
|
990
|
+
path: ["v2", "dlq"],
|
|
991
|
+
headers: { "Content-Type": "application/json" },
|
|
992
|
+
body: JSON.stringify({ dlqIds: request.dlqIds })
|
|
993
|
+
});
|
|
1006
994
|
}
|
|
1007
|
-
return joinedHeaders;
|
|
1008
995
|
};
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
996
|
+
|
|
997
|
+
// src/client/http.ts
|
|
998
|
+
var HttpClient = class {
|
|
999
|
+
baseUrl;
|
|
1000
|
+
authorization;
|
|
1001
|
+
options;
|
|
1002
|
+
retry;
|
|
1003
|
+
headers;
|
|
1004
|
+
telemetryHeaders;
|
|
1005
|
+
constructor(config) {
|
|
1006
|
+
this.baseUrl = config.baseUrl.replace(/\/$/, "");
|
|
1007
|
+
this.authorization = config.authorization;
|
|
1008
|
+
this.retry = // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
1009
|
+
typeof config.retry === "boolean" && !config.retry ? {
|
|
1010
|
+
attempts: 1,
|
|
1011
|
+
backoff: () => 0
|
|
1012
|
+
} : {
|
|
1013
|
+
attempts: config.retry?.retries ?? 5,
|
|
1014
|
+
backoff: config.retry?.backoff ?? ((retryCount) => Math.exp(retryCount) * 50)
|
|
1015
|
+
};
|
|
1016
|
+
this.headers = config.headers;
|
|
1017
|
+
this.telemetryHeaders = config.telemetryHeaders;
|
|
1013
1018
|
}
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
+
async request(request) {
|
|
1020
|
+
const { response } = await this.requestWithBackoff(request);
|
|
1021
|
+
if (request.parseResponseAsJson === false) {
|
|
1022
|
+
return void 0;
|
|
1023
|
+
}
|
|
1024
|
+
return await response.json();
|
|
1025
|
+
}
|
|
1026
|
+
async *requestStream(request) {
|
|
1027
|
+
const { response } = await this.requestWithBackoff(request);
|
|
1028
|
+
if (!response.body) {
|
|
1029
|
+
throw new Error("No response body");
|
|
1030
|
+
}
|
|
1031
|
+
const body = response.body;
|
|
1032
|
+
const reader = body.getReader();
|
|
1033
|
+
const decoder = new TextDecoder();
|
|
1034
|
+
try {
|
|
1035
|
+
while (true) {
|
|
1036
|
+
const { done, value } = await reader.read();
|
|
1037
|
+
if (done) {
|
|
1038
|
+
break;
|
|
1039
|
+
}
|
|
1040
|
+
const chunkText = decoder.decode(value, { stream: true });
|
|
1041
|
+
const chunks = chunkText.split("\n").filter(Boolean);
|
|
1042
|
+
for (const chunk of chunks) {
|
|
1043
|
+
if (chunk.startsWith("data: ")) {
|
|
1044
|
+
const data = chunk.slice(6);
|
|
1045
|
+
if (data === "[DONE]") {
|
|
1046
|
+
break;
|
|
1047
|
+
}
|
|
1048
|
+
yield JSON.parse(data);
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
} finally {
|
|
1053
|
+
await reader.cancel();
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
requestWithBackoff = async (request) => {
|
|
1057
|
+
const [url, requestOptions] = this.processRequest(request);
|
|
1058
|
+
let response = void 0;
|
|
1059
|
+
let error = void 0;
|
|
1060
|
+
for (let index = 0; index <= this.retry.attempts; index++) {
|
|
1061
|
+
try {
|
|
1062
|
+
response = await fetch(url.toString(), requestOptions);
|
|
1063
|
+
break;
|
|
1064
|
+
} catch (error_) {
|
|
1065
|
+
error = error_;
|
|
1066
|
+
if (index < this.retry.attempts) {
|
|
1067
|
+
await new Promise((r) => setTimeout(r, this.retry.backoff(index)));
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
if (!response) {
|
|
1072
|
+
throw error ?? new Error("Exhausted all retries");
|
|
1019
1073
|
}
|
|
1074
|
+
await this.checkResponse(response);
|
|
1020
1075
|
return {
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
headers: safeJoinHeaders(headers, appendHeaders),
|
|
1024
|
-
...owner === "upstash" && !request.api.analytics ? { api: { name: "llm" }, url: void 0, callback } : { url, api: void 0 }
|
|
1076
|
+
response,
|
|
1077
|
+
error
|
|
1025
1078
|
};
|
|
1026
|
-
}
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
headers
|
|
1031
|
-
|
|
1032
|
-
|
|
1079
|
+
};
|
|
1080
|
+
processRequest = (request) => {
|
|
1081
|
+
const headers = new Headers(request.headers);
|
|
1082
|
+
if (!headers.has("Authorization")) {
|
|
1083
|
+
headers.set("Authorization", this.authorization);
|
|
1084
|
+
}
|
|
1085
|
+
const requestOptions = {
|
|
1086
|
+
method: request.method,
|
|
1087
|
+
headers,
|
|
1088
|
+
body: request.body,
|
|
1089
|
+
keepalive: request.keepalive
|
|
1033
1090
|
};
|
|
1091
|
+
const url = new URL([request.baseUrl ?? this.baseUrl, ...request.path].join("/"));
|
|
1092
|
+
if (request.query) {
|
|
1093
|
+
for (const [key, value] of Object.entries(request.query)) {
|
|
1094
|
+
if (value !== void 0) {
|
|
1095
|
+
url.searchParams.set(key, value.toString());
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
return [url.toString(), requestOptions];
|
|
1100
|
+
};
|
|
1101
|
+
async checkResponse(response) {
|
|
1102
|
+
if (response.status === 429) {
|
|
1103
|
+
if (response.headers.get("x-ratelimit-limit-requests")) {
|
|
1104
|
+
throw new QstashChatRatelimitError({
|
|
1105
|
+
"limit-requests": response.headers.get("x-ratelimit-limit-requests"),
|
|
1106
|
+
"limit-tokens": response.headers.get("x-ratelimit-limit-tokens"),
|
|
1107
|
+
"remaining-requests": response.headers.get("x-ratelimit-remaining-requests"),
|
|
1108
|
+
"remaining-tokens": response.headers.get("x-ratelimit-remaining-tokens"),
|
|
1109
|
+
"reset-requests": response.headers.get("x-ratelimit-reset-requests"),
|
|
1110
|
+
"reset-tokens": response.headers.get("x-ratelimit-reset-tokens")
|
|
1111
|
+
});
|
|
1112
|
+
} else if (response.headers.get("RateLimit-Limit")) {
|
|
1113
|
+
throw new QstashDailyRatelimitError({
|
|
1114
|
+
limit: response.headers.get("RateLimit-Limit"),
|
|
1115
|
+
remaining: response.headers.get("RateLimit-Remaining"),
|
|
1116
|
+
reset: response.headers.get("RateLimit-Reset")
|
|
1117
|
+
});
|
|
1118
|
+
}
|
|
1119
|
+
throw new QstashRatelimitError({
|
|
1120
|
+
limit: response.headers.get("Burst-RateLimit-Limit"),
|
|
1121
|
+
remaining: response.headers.get("Burst-RateLimit-Remaining"),
|
|
1122
|
+
reset: response.headers.get("Burst-RateLimit-Reset")
|
|
1123
|
+
});
|
|
1124
|
+
}
|
|
1125
|
+
if (response.status < 200 || response.status >= 300) {
|
|
1126
|
+
const body = await response.text();
|
|
1127
|
+
throw new QstashError(
|
|
1128
|
+
body.length > 0 ? body : `Error: status=${response.status}`,
|
|
1129
|
+
response.status
|
|
1130
|
+
);
|
|
1131
|
+
}
|
|
1034
1132
|
}
|
|
1035
1133
|
};
|
|
1036
|
-
|
|
1134
|
+
|
|
1135
|
+
// src/client/llm/providers.ts
|
|
1136
|
+
var setupAnalytics = (analytics, providerApiKey, providerBaseUrl, provider) => {
|
|
1137
|
+
if (!analytics)
|
|
1138
|
+
return {};
|
|
1037
1139
|
switch (analytics.name) {
|
|
1038
1140
|
case "helicone": {
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1141
|
+
switch (provider) {
|
|
1142
|
+
case "upstash": {
|
|
1143
|
+
return {
|
|
1144
|
+
baseURL: "https://qstash.helicone.ai/llm/v1/chat/completions",
|
|
1145
|
+
defaultHeaders: {
|
|
1146
|
+
"Helicone-Auth": `Bearer ${analytics.token}`,
|
|
1147
|
+
Authorization: `Bearer ${providerApiKey}`
|
|
1148
|
+
}
|
|
1149
|
+
};
|
|
1150
|
+
}
|
|
1151
|
+
default: {
|
|
1152
|
+
return {
|
|
1153
|
+
baseURL: "https://gateway.helicone.ai/v1/chat/completions",
|
|
1154
|
+
defaultHeaders: {
|
|
1155
|
+
"Helicone-Auth": `Bearer ${analytics.token}`,
|
|
1156
|
+
"Helicone-Target-Url": providerBaseUrl,
|
|
1157
|
+
Authorization: `Bearer ${providerApiKey}`
|
|
1158
|
+
}
|
|
1159
|
+
};
|
|
1160
|
+
}
|
|
1048
1161
|
}
|
|
1049
|
-
return providerInfo;
|
|
1050
1162
|
}
|
|
1051
1163
|
default: {
|
|
1052
|
-
throw new Error("Unknown analytics provider");
|
|
1053
|
-
}
|
|
1054
|
-
}
|
|
1055
|
-
}
|
|
1056
|
-
function updateProviderInfo(providerInfo, baseUrl, route) {
|
|
1057
|
-
providerInfo.baseUrl = baseUrl;
|
|
1058
|
-
providerInfo.route = route;
|
|
1059
|
-
providerInfo.url = `${baseUrl}/${route.join("/")}`;
|
|
1060
|
-
}
|
|
1061
|
-
|
|
1062
|
-
// src/client/utils.ts
|
|
1063
|
-
var isIgnoredHeader = (header) => {
|
|
1064
|
-
const lowerCaseHeader = header.toLowerCase();
|
|
1065
|
-
return lowerCaseHeader.startsWith("content-type") || lowerCaseHeader.startsWith("upstash-");
|
|
1066
|
-
};
|
|
1067
|
-
function prefixHeaders(headers) {
|
|
1068
|
-
const keysToBePrefixed = [...headers.keys()].filter((key) => !isIgnoredHeader(key));
|
|
1069
|
-
for (const key of keysToBePrefixed) {
|
|
1070
|
-
const value = headers.get(key);
|
|
1071
|
-
if (value !== null) {
|
|
1072
|
-
headers.set(`Upstash-Forward-${key}`, value);
|
|
1073
|
-
}
|
|
1074
|
-
headers.delete(key);
|
|
1075
|
-
}
|
|
1076
|
-
return headers;
|
|
1077
|
-
}
|
|
1078
|
-
function wrapWithGlobalHeaders(headers, globalHeaders, telemetryHeaders) {
|
|
1079
|
-
if (!globalHeaders) {
|
|
1080
|
-
return headers;
|
|
1081
|
-
}
|
|
1082
|
-
const finalHeaders = new Headers(globalHeaders);
|
|
1083
|
-
headers.forEach((value, key) => {
|
|
1084
|
-
finalHeaders.set(key, value);
|
|
1085
|
-
});
|
|
1086
|
-
telemetryHeaders?.forEach((value, key) => {
|
|
1087
|
-
if (!value)
|
|
1088
|
-
return;
|
|
1089
|
-
finalHeaders.append(key, value);
|
|
1090
|
-
});
|
|
1091
|
-
return finalHeaders;
|
|
1092
|
-
}
|
|
1093
|
-
function processHeaders(request) {
|
|
1094
|
-
const headers = prefixHeaders(new Headers(request.headers));
|
|
1095
|
-
headers.set("Upstash-Method", request.method ?? "POST");
|
|
1096
|
-
if (request.delay !== void 0) {
|
|
1097
|
-
if (typeof request.delay === "string") {
|
|
1098
|
-
headers.set("Upstash-Delay", request.delay);
|
|
1099
|
-
} else {
|
|
1100
|
-
headers.set("Upstash-Delay", `${request.delay.toFixed(0)}s`);
|
|
1101
|
-
}
|
|
1102
|
-
}
|
|
1103
|
-
if (request.notBefore !== void 0) {
|
|
1104
|
-
headers.set("Upstash-Not-Before", request.notBefore.toFixed(0));
|
|
1105
|
-
}
|
|
1106
|
-
if (request.deduplicationId !== void 0) {
|
|
1107
|
-
headers.set("Upstash-Deduplication-Id", request.deduplicationId);
|
|
1108
|
-
}
|
|
1109
|
-
if (request.contentBasedDeduplication) {
|
|
1110
|
-
headers.set("Upstash-Content-Based-Deduplication", "true");
|
|
1111
|
-
}
|
|
1112
|
-
if (request.retries !== void 0) {
|
|
1113
|
-
headers.set("Upstash-Retries", request.retries.toFixed(0));
|
|
1114
|
-
}
|
|
1115
|
-
if (request.retryDelay !== void 0) {
|
|
1116
|
-
headers.set("Upstash-Retry-Delay", request.retryDelay);
|
|
1164
|
+
throw new Error("Unknown analytics provider");
|
|
1165
|
+
}
|
|
1117
1166
|
}
|
|
1118
|
-
|
|
1119
|
-
|
|
1167
|
+
};
|
|
1168
|
+
|
|
1169
|
+
// src/client/llm/chat.ts
|
|
1170
|
+
var Chat = class _Chat {
|
|
1171
|
+
http;
|
|
1172
|
+
token;
|
|
1173
|
+
constructor(http, token) {
|
|
1174
|
+
this.http = http;
|
|
1175
|
+
this.token = token;
|
|
1120
1176
|
}
|
|
1121
|
-
|
|
1122
|
-
|
|
1177
|
+
static toChatRequest(request) {
|
|
1178
|
+
const messages = [];
|
|
1179
|
+
messages.push(
|
|
1180
|
+
{ role: "system", content: request.system },
|
|
1181
|
+
{ role: "user", content: request.user }
|
|
1182
|
+
);
|
|
1183
|
+
const chatRequest = { ...request, messages };
|
|
1184
|
+
return chatRequest;
|
|
1123
1185
|
}
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1186
|
+
/**
|
|
1187
|
+
* Calls the Upstash completions api given a ChatRequest.
|
|
1188
|
+
*
|
|
1189
|
+
* Returns a ChatCompletion or a stream of ChatCompletionChunks
|
|
1190
|
+
* if stream is enabled.
|
|
1191
|
+
*
|
|
1192
|
+
* @param request ChatRequest with messages
|
|
1193
|
+
* @returns Chat completion or stream
|
|
1194
|
+
*/
|
|
1195
|
+
create = async (request) => {
|
|
1196
|
+
if (request.provider.owner != "upstash")
|
|
1197
|
+
return this.createThirdParty(request);
|
|
1198
|
+
const body = JSON.stringify(request);
|
|
1199
|
+
let baseUrl = void 0;
|
|
1200
|
+
let headers = {
|
|
1201
|
+
"Content-Type": "application/json",
|
|
1202
|
+
Authorization: `Bearer ${this.token}`,
|
|
1203
|
+
..."stream" in request && request.stream ? {
|
|
1204
|
+
Connection: "keep-alive",
|
|
1205
|
+
Accept: "text/event-stream",
|
|
1206
|
+
"Cache-Control": "no-cache"
|
|
1207
|
+
} : {}
|
|
1208
|
+
};
|
|
1209
|
+
if (request.analytics) {
|
|
1210
|
+
const { baseURL, defaultHeaders } = setupAnalytics(
|
|
1211
|
+
{ name: "helicone", token: request.analytics.token },
|
|
1212
|
+
this.getAuthorizationToken(),
|
|
1213
|
+
request.provider.baseUrl,
|
|
1214
|
+
"upstash"
|
|
1215
|
+
);
|
|
1216
|
+
headers = { ...headers, ...defaultHeaders };
|
|
1217
|
+
baseUrl = baseURL;
|
|
1129
1218
|
}
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1219
|
+
const path = request.analytics ? [] : ["llm", "v1", "chat", "completions"];
|
|
1220
|
+
return "stream" in request && request.stream ? this.http.requestStream({
|
|
1221
|
+
path,
|
|
1222
|
+
method: "POST",
|
|
1223
|
+
headers,
|
|
1224
|
+
baseUrl,
|
|
1225
|
+
body
|
|
1226
|
+
}) : this.http.request({
|
|
1227
|
+
path,
|
|
1228
|
+
method: "POST",
|
|
1229
|
+
headers,
|
|
1230
|
+
baseUrl,
|
|
1231
|
+
body
|
|
1232
|
+
});
|
|
1233
|
+
};
|
|
1234
|
+
/**
|
|
1235
|
+
* Calls the Upstash completions api given a ChatRequest.
|
|
1236
|
+
*
|
|
1237
|
+
* Returns a ChatCompletion or a stream of ChatCompletionChunks
|
|
1238
|
+
* if stream is enabled.
|
|
1239
|
+
*
|
|
1240
|
+
* @param request ChatRequest with messages
|
|
1241
|
+
* @returns Chat completion or stream
|
|
1242
|
+
*/
|
|
1243
|
+
createThirdParty = async (request) => {
|
|
1244
|
+
const { baseUrl, token, owner, organization } = request.provider;
|
|
1245
|
+
if (owner === "upstash")
|
|
1246
|
+
throw new Error("Upstash is not 3rd party provider!");
|
|
1247
|
+
delete request.provider;
|
|
1248
|
+
delete request.system;
|
|
1249
|
+
const analytics = request.analytics;
|
|
1250
|
+
delete request.analytics;
|
|
1251
|
+
const body = JSON.stringify(request);
|
|
1252
|
+
const isAnalyticsEnabled = analytics?.name && analytics.token;
|
|
1253
|
+
const analyticsConfig = analytics?.name && analytics.token ? setupAnalytics({ name: analytics.name, token: analytics.token }, token, baseUrl, owner) : { defaultHeaders: void 0, baseURL: baseUrl };
|
|
1254
|
+
const isStream = "stream" in request && request.stream;
|
|
1255
|
+
const headers = {
|
|
1256
|
+
"Content-Type": "application/json",
|
|
1257
|
+
Authorization: `Bearer ${token}`,
|
|
1258
|
+
...organization ? {
|
|
1259
|
+
"OpenAI-Organization": organization
|
|
1260
|
+
} : {},
|
|
1261
|
+
...isStream ? {
|
|
1262
|
+
Connection: "keep-alive",
|
|
1263
|
+
Accept: "text/event-stream",
|
|
1264
|
+
"Cache-Control": "no-cache"
|
|
1265
|
+
} : {},
|
|
1266
|
+
...analyticsConfig.defaultHeaders
|
|
1267
|
+
};
|
|
1268
|
+
const response = await this.http[isStream ? "requestStream" : "request"]({
|
|
1269
|
+
path: isAnalyticsEnabled ? [] : ["v1", "chat", "completions"],
|
|
1270
|
+
method: "POST",
|
|
1271
|
+
headers,
|
|
1272
|
+
body,
|
|
1273
|
+
baseUrl: analyticsConfig.baseURL
|
|
1274
|
+
});
|
|
1275
|
+
return response;
|
|
1276
|
+
};
|
|
1277
|
+
// Helper method to get the authorization token
|
|
1278
|
+
getAuthorizationToken() {
|
|
1279
|
+
const authHeader = String(this.http.authorization);
|
|
1280
|
+
const match = /Bearer (.+)/.exec(authHeader);
|
|
1281
|
+
if (!match) {
|
|
1282
|
+
throw new Error("Invalid authorization header format");
|
|
1142
1283
|
}
|
|
1143
|
-
|
|
1144
|
-
headers.set("Upstash-Flow-Control-Value", controlValue.join(", "));
|
|
1284
|
+
return match[1];
|
|
1145
1285
|
}
|
|
1146
|
-
|
|
1147
|
-
|
|
1286
|
+
/**
|
|
1287
|
+
* Calls the Upstash completions api given a PromptRequest.
|
|
1288
|
+
*
|
|
1289
|
+
* Returns a ChatCompletion or a stream of ChatCompletionChunks
|
|
1290
|
+
* if stream is enabled.
|
|
1291
|
+
*
|
|
1292
|
+
* @param request PromptRequest with system and user messages.
|
|
1293
|
+
* Note that system parameter shouldn't be passed in the case of
|
|
1294
|
+
* mistralai/Mistral-7B-Instruct-v0.2 model.
|
|
1295
|
+
* @returns Chat completion or stream
|
|
1296
|
+
*/
|
|
1297
|
+
prompt = async (request) => {
|
|
1298
|
+
const chatRequest = _Chat.toChatRequest(request);
|
|
1299
|
+
return this.create(chatRequest);
|
|
1300
|
+
};
|
|
1301
|
+
};
|
|
1302
|
+
|
|
1303
|
+
// src/client/messages.ts
|
|
1304
|
+
var Messages = class {
|
|
1305
|
+
http;
|
|
1306
|
+
constructor(http) {
|
|
1307
|
+
this.http = http;
|
|
1148
1308
|
}
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1309
|
+
/**
|
|
1310
|
+
* Get a message
|
|
1311
|
+
*/
|
|
1312
|
+
async get(messageId) {
|
|
1313
|
+
const messagePayload = await this.http.request({
|
|
1314
|
+
method: "GET",
|
|
1315
|
+
path: ["v2", "messages", messageId]
|
|
1316
|
+
});
|
|
1317
|
+
const message = {
|
|
1318
|
+
...messagePayload,
|
|
1319
|
+
urlGroup: messagePayload.topicName,
|
|
1320
|
+
ratePerSecond: "rate" in messagePayload ? messagePayload.rate : void 0
|
|
1321
|
+
};
|
|
1322
|
+
return message;
|
|
1160
1323
|
}
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
const binString = atob(base64);
|
|
1171
|
-
const intArray = Uint8Array.from(binString, (m) => m.codePointAt(0));
|
|
1172
|
-
return new TextDecoder().decode(intArray);
|
|
1173
|
-
} catch (error) {
|
|
1174
|
-
try {
|
|
1175
|
-
const result = atob(base64);
|
|
1176
|
-
console.warn(
|
|
1177
|
-
`Upstash QStash: Failed while decoding base64 "${base64}". Decoding with atob and returning it instead. ${error}`
|
|
1178
|
-
);
|
|
1179
|
-
return result;
|
|
1180
|
-
} catch (error2) {
|
|
1181
|
-
console.warn(
|
|
1182
|
-
`Upstash QStash: Failed to decode base64 "${base64}" with atob. Returning it as it is. ${error2}`
|
|
1183
|
-
);
|
|
1184
|
-
return base64;
|
|
1185
|
-
}
|
|
1324
|
+
/**
|
|
1325
|
+
* Cancel a message
|
|
1326
|
+
*/
|
|
1327
|
+
async delete(messageId) {
|
|
1328
|
+
return await this.http.request({
|
|
1329
|
+
method: "DELETE",
|
|
1330
|
+
path: ["v2", "messages", messageId],
|
|
1331
|
+
parseResponseAsJson: false
|
|
1332
|
+
});
|
|
1186
1333
|
}
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
return
|
|
1195
|
-
|
|
1196
|
-
|
|
1334
|
+
async deleteMany(messageIds) {
|
|
1335
|
+
const result = await this.http.request({
|
|
1336
|
+
method: "DELETE",
|
|
1337
|
+
path: ["v2", "messages"],
|
|
1338
|
+
headers: { "Content-Type": "application/json" },
|
|
1339
|
+
body: JSON.stringify({ messageIds })
|
|
1340
|
+
});
|
|
1341
|
+
return result.cancelled;
|
|
1342
|
+
}
|
|
1343
|
+
async deleteAll() {
|
|
1344
|
+
const result = await this.http.request({
|
|
1345
|
+
method: "DELETE",
|
|
1346
|
+
path: ["v2", "messages"]
|
|
1347
|
+
});
|
|
1348
|
+
return result.cancelled;
|
|
1349
|
+
}
|
|
1350
|
+
};
|
|
1197
1351
|
|
|
1198
1352
|
// src/client/queue.ts
|
|
1199
1353
|
var Queue = class {
|
|
@@ -2910,19 +3064,15 @@ var Workflow = class {
|
|
|
2910
3064
|
};
|
|
2911
3065
|
|
|
2912
3066
|
// version.ts
|
|
2913
|
-
var VERSION = "v2.
|
|
3067
|
+
var VERSION = "v2.9.0-rc";
|
|
2914
3068
|
|
|
2915
3069
|
// src/client/client.ts
|
|
2916
3070
|
var Client = class {
|
|
2917
3071
|
http;
|
|
2918
3072
|
token;
|
|
2919
3073
|
constructor(config) {
|
|
2920
|
-
const environment =
|
|
2921
|
-
|
|
2922
|
-
if (baseUrl === "https://qstash.upstash.io/v2/publish") {
|
|
2923
|
-
baseUrl = "https://qstash.upstash.io";
|
|
2924
|
-
}
|
|
2925
|
-
const token = config?.token ?? environment.QSTASH_TOKEN;
|
|
3074
|
+
const environment = getSafeEnvironment();
|
|
3075
|
+
const { baseUrl, token } = getClientCredentials({ environment, config });
|
|
2926
3076
|
const enableTelemetry = environment.UPSTASH_DISABLE_TELEMETRY ? false : config?.enableTelemetry ?? true;
|
|
2927
3077
|
const isCloudflare = typeof caches !== "undefined" && "default" in caches;
|
|
2928
3078
|
const telemetryHeaders = new Headers(
|
|
@@ -2941,11 +3091,6 @@ var Client = class {
|
|
|
2941
3091
|
//@ts-expect-error caused by undici and bunjs type overlap
|
|
2942
3092
|
telemetryHeaders
|
|
2943
3093
|
});
|
|
2944
|
-
if (!token) {
|
|
2945
|
-
console.warn(
|
|
2946
|
-
"[Upstash QStash] client token is not set. Either pass a token or set QSTASH_TOKEN env variable."
|
|
2947
|
-
);
|
|
2948
|
-
}
|
|
2949
3094
|
this.token = token;
|
|
2950
3095
|
}
|
|
2951
3096
|
/**
|
|
@@ -3177,15 +3322,10 @@ var Client = class {
|
|
|
3177
3322
|
// platforms/h3.ts
|
|
3178
3323
|
var verifySignatureH3 = (handler, config) => {
|
|
3179
3324
|
const currentSigningKey = config?.currentSigningKey ?? process.env.QSTASH_CURRENT_SIGNING_KEY;
|
|
3180
|
-
if (!currentSigningKey) {
|
|
3181
|
-
throw new Error(
|
|
3182
|
-
"currentSigningKey is required, either in the config or as env variable QSTASH_CURRENT_SIGNING_KEY"
|
|
3183
|
-
);
|
|
3184
|
-
}
|
|
3185
3325
|
const nextSigningKey = config?.nextSigningKey ?? process.env.QSTASH_NEXT_SIGNING_KEY;
|
|
3186
|
-
if (!nextSigningKey) {
|
|
3326
|
+
if (!currentSigningKey && !nextSigningKey && !process.env.QSTASH_REGION) {
|
|
3187
3327
|
throw new Error(
|
|
3188
|
-
"nextSigningKey
|
|
3328
|
+
"currentSigningKey and nextSigningKey are required, either in the config or as env variables (QSTASH_CURRENT_SIGNING_KEY and QSTASH_NEXT_SIGNING_KEY)"
|
|
3189
3329
|
);
|
|
3190
3330
|
}
|
|
3191
3331
|
const receiver = new Receiver({
|
|
@@ -3200,11 +3340,13 @@ var verifySignatureH3 = (handler, config) => {
|
|
|
3200
3340
|
if (typeof signature !== "string") {
|
|
3201
3341
|
throw new TypeError("`Upstash-Signature` header is not a string");
|
|
3202
3342
|
}
|
|
3343
|
+
const upstashRegion = getHeader(event, "upstash-region");
|
|
3203
3344
|
const body = await readRawBody(event);
|
|
3204
3345
|
const isValid = await receiver.verify({
|
|
3205
3346
|
signature,
|
|
3206
3347
|
body: JSON.stringify(body),
|
|
3207
|
-
clockTolerance: config?.clockTolerance
|
|
3348
|
+
clockTolerance: config?.clockTolerance,
|
|
3349
|
+
upstashRegion: typeof upstashRegion === "string" ? upstashRegion : void 0
|
|
3208
3350
|
});
|
|
3209
3351
|
if (!isValid) {
|
|
3210
3352
|
return { status: 403, body: "invalid signature" };
|