@upstash/qstash 2.8.4 → 2.9.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/{chunk-EZZS7N6P.mjs → chunk-H5OAU75L.mjs} +6 -9
- package/{chunk-5PQP3YLP.mjs → chunk-M7SEEFAC.mjs} +1 -1
- package/{chunk-RQPZUJXG.mjs → chunk-QYBCXZKB.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/solidjs.js
CHANGED
|
@@ -38,120 +38,154 @@ module.exports = __toCommonJS(solidjs_exports);
|
|
|
38
38
|
// src/receiver.ts
|
|
39
39
|
var jose = __toESM(require("jose"));
|
|
40
40
|
var import_crypto_js = __toESM(require("crypto-js"));
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
41
|
+
|
|
42
|
+
// src/client/api/base.ts
|
|
43
|
+
var BaseProvider = class {
|
|
44
|
+
baseUrl;
|
|
45
|
+
token;
|
|
46
|
+
owner;
|
|
47
|
+
constructor(baseUrl, token, owner) {
|
|
48
|
+
this.baseUrl = baseUrl;
|
|
49
|
+
this.token = token;
|
|
50
|
+
this.owner = owner;
|
|
51
|
+
}
|
|
52
|
+
getUrl() {
|
|
53
|
+
return `${this.baseUrl}/${this.getRoute().join("/")}`;
|
|
45
54
|
}
|
|
46
55
|
};
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
56
|
+
|
|
57
|
+
// src/client/api/llm.ts
|
|
58
|
+
var LLMProvider = class extends BaseProvider {
|
|
59
|
+
apiKind = "llm";
|
|
60
|
+
organization;
|
|
61
|
+
method = "POST";
|
|
62
|
+
constructor(baseUrl, token, owner, organization) {
|
|
63
|
+
super(baseUrl, token, owner);
|
|
64
|
+
this.organization = organization;
|
|
53
65
|
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
*
|
|
61
|
-
* If that fails, the signature is invalid and a `SignatureError` is thrown.
|
|
62
|
-
*/
|
|
63
|
-
async verify(request) {
|
|
64
|
-
let payload;
|
|
65
|
-
try {
|
|
66
|
-
payload = await this.verifyWithKey(this.currentSigningKey, request);
|
|
67
|
-
} catch {
|
|
68
|
-
payload = await this.verifyWithKey(this.nextSigningKey, request);
|
|
66
|
+
getRoute() {
|
|
67
|
+
return this.owner === "anthropic" ? ["v1", "messages"] : ["v1", "chat", "completions"];
|
|
68
|
+
}
|
|
69
|
+
getHeaders(options) {
|
|
70
|
+
if (this.owner === "upstash" && !options.analytics) {
|
|
71
|
+
return { "content-type": "application/json" };
|
|
69
72
|
}
|
|
70
|
-
this.
|
|
71
|
-
|
|
73
|
+
const header = this.owner === "anthropic" ? "x-api-key" : "authorization";
|
|
74
|
+
const headerValue = this.owner === "anthropic" ? this.token : `Bearer ${this.token}`;
|
|
75
|
+
const headers = {
|
|
76
|
+
[header]: headerValue,
|
|
77
|
+
"content-type": "application/json"
|
|
78
|
+
};
|
|
79
|
+
if (this.owner === "openai" && this.organization) {
|
|
80
|
+
headers["OpenAI-Organization"] = this.organization;
|
|
81
|
+
}
|
|
82
|
+
if (this.owner === "anthropic") {
|
|
83
|
+
headers["anthropic-version"] = "2023-06-01";
|
|
84
|
+
}
|
|
85
|
+
return headers;
|
|
72
86
|
}
|
|
73
87
|
/**
|
|
74
|
-
*
|
|
88
|
+
* Checks if callback exists and adds analytics in place if it's set.
|
|
89
|
+
*
|
|
90
|
+
* @param request
|
|
91
|
+
* @param options
|
|
75
92
|
*/
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
clockTolerance: request.clockTolerance
|
|
80
|
-
}).catch((error) => {
|
|
81
|
-
throw new SignatureError(error.message);
|
|
82
|
-
});
|
|
83
|
-
return jwt.payload;
|
|
84
|
-
}
|
|
85
|
-
verifyBodyAndUrl(payload, request) {
|
|
86
|
-
const p = payload;
|
|
87
|
-
if (request.url !== void 0 && p.sub !== request.url) {
|
|
88
|
-
throw new SignatureError(`invalid subject: ${p.sub}, want: ${request.url}`);
|
|
89
|
-
}
|
|
90
|
-
const bodyHash = import_crypto_js.default.SHA256(request.body).toString(import_crypto_js.default.enc.Base64url);
|
|
91
|
-
const padding = new RegExp(/=+$/);
|
|
92
|
-
if (p.body.replace(padding, "") !== bodyHash.replace(padding, "")) {
|
|
93
|
-
throw new SignatureError(`body hash does not match, want: ${p.body}, got: ${bodyHash}`);
|
|
93
|
+
onFinish(providerInfo, options) {
|
|
94
|
+
if (options.analytics) {
|
|
95
|
+
return updateWithAnalytics(providerInfo, options.analytics);
|
|
94
96
|
}
|
|
97
|
+
return providerInfo;
|
|
95
98
|
}
|
|
96
99
|
};
|
|
100
|
+
var upstash = () => {
|
|
101
|
+
return new LLMProvider("https://qstash.upstash.io/llm", "", "upstash");
|
|
102
|
+
};
|
|
97
103
|
|
|
98
|
-
// src/client/
|
|
99
|
-
var
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
104
|
+
// src/client/api/utils.ts
|
|
105
|
+
var getProviderInfo = (api, upstashToken) => {
|
|
106
|
+
const { name, provider, ...parameters } = api;
|
|
107
|
+
const finalProvider = provider ?? upstash();
|
|
108
|
+
if (finalProvider.owner === "upstash" && !finalProvider.token) {
|
|
109
|
+
finalProvider.token = upstashToken;
|
|
103
110
|
}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
+
if (!finalProvider.baseUrl)
|
|
112
|
+
throw new TypeError("baseUrl cannot be empty or undefined!");
|
|
113
|
+
if (!finalProvider.token)
|
|
114
|
+
throw new TypeError("token cannot be empty or undefined!");
|
|
115
|
+
if (finalProvider.apiKind !== name) {
|
|
116
|
+
throw new TypeError(
|
|
117
|
+
`Unexpected api name. Expected '${finalProvider.apiKind}', received ${name}`
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
const providerInfo = {
|
|
121
|
+
url: finalProvider.getUrl(),
|
|
122
|
+
baseUrl: finalProvider.baseUrl,
|
|
123
|
+
route: finalProvider.getRoute(),
|
|
124
|
+
appendHeaders: finalProvider.getHeaders(parameters),
|
|
125
|
+
owner: finalProvider.owner,
|
|
126
|
+
method: finalProvider.method
|
|
127
|
+
};
|
|
128
|
+
return finalProvider.onFinish(providerInfo, parameters);
|
|
129
|
+
};
|
|
130
|
+
var safeJoinHeaders = (headers, record) => {
|
|
131
|
+
const joinedHeaders = new Headers(record);
|
|
132
|
+
for (const [header, value] of headers.entries()) {
|
|
133
|
+
joinedHeaders.set(header, value);
|
|
134
|
+
}
|
|
135
|
+
return joinedHeaders;
|
|
136
|
+
};
|
|
137
|
+
var processApi = (request, headers, upstashToken) => {
|
|
138
|
+
if (!request.api) {
|
|
139
|
+
request.headers = headers;
|
|
140
|
+
return request;
|
|
141
|
+
}
|
|
142
|
+
const { url, appendHeaders, owner, method } = getProviderInfo(request.api, upstashToken);
|
|
143
|
+
if (request.api.name === "llm") {
|
|
144
|
+
const callback = request.callback;
|
|
145
|
+
if (!callback) {
|
|
146
|
+
throw new TypeError("Callback cannot be undefined when using LLM api.");
|
|
147
|
+
}
|
|
148
|
+
return {
|
|
149
|
+
...request,
|
|
150
|
+
method: request.method ?? method,
|
|
151
|
+
headers: safeJoinHeaders(headers, appendHeaders),
|
|
152
|
+
...owner === "upstash" && !request.api.analytics ? { api: { name: "llm" }, url: void 0, callback } : { url, api: void 0 }
|
|
111
153
|
};
|
|
112
|
-
|
|
113
|
-
method: "GET",
|
|
114
|
-
path: ["v2", "dlq"],
|
|
115
|
-
query: {
|
|
116
|
-
cursor: options?.cursor,
|
|
117
|
-
count: options?.count,
|
|
118
|
-
...filterPayload
|
|
119
|
-
}
|
|
120
|
-
});
|
|
154
|
+
} else {
|
|
121
155
|
return {
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
};
|
|
128
|
-
}),
|
|
129
|
-
cursor: messagesPayload.cursor
|
|
156
|
+
...request,
|
|
157
|
+
method: request.method ?? method,
|
|
158
|
+
headers: safeJoinHeaders(headers, appendHeaders),
|
|
159
|
+
url,
|
|
160
|
+
api: void 0
|
|
130
161
|
};
|
|
131
162
|
}
|
|
132
|
-
/**
|
|
133
|
-
* Remove a message from the dlq using it's `dlqId`
|
|
134
|
-
*/
|
|
135
|
-
async delete(dlqMessageId) {
|
|
136
|
-
return await this.http.request({
|
|
137
|
-
method: "DELETE",
|
|
138
|
-
path: ["v2", "dlq", dlqMessageId],
|
|
139
|
-
parseResponseAsJson: false
|
|
140
|
-
// there is no response
|
|
141
|
-
});
|
|
142
|
-
}
|
|
143
|
-
/**
|
|
144
|
-
* Remove multiple messages from the dlq using their `dlqId`s
|
|
145
|
-
*/
|
|
146
|
-
async deleteMany(request) {
|
|
147
|
-
return await this.http.request({
|
|
148
|
-
method: "DELETE",
|
|
149
|
-
path: ["v2", "dlq"],
|
|
150
|
-
headers: { "Content-Type": "application/json" },
|
|
151
|
-
body: JSON.stringify({ dlqIds: request.dlqIds })
|
|
152
|
-
});
|
|
153
|
-
}
|
|
154
163
|
};
|
|
164
|
+
function updateWithAnalytics(providerInfo, analytics) {
|
|
165
|
+
switch (analytics.name) {
|
|
166
|
+
case "helicone": {
|
|
167
|
+
providerInfo.appendHeaders["Helicone-Auth"] = `Bearer ${analytics.token}`;
|
|
168
|
+
if (providerInfo.owner === "upstash") {
|
|
169
|
+
updateProviderInfo(providerInfo, "https://qstash.helicone.ai", [
|
|
170
|
+
"llm",
|
|
171
|
+
...providerInfo.route
|
|
172
|
+
]);
|
|
173
|
+
} else {
|
|
174
|
+
providerInfo.appendHeaders["Helicone-Target-Url"] = providerInfo.baseUrl;
|
|
175
|
+
updateProviderInfo(providerInfo, "https://gateway.helicone.ai", providerInfo.route);
|
|
176
|
+
}
|
|
177
|
+
return providerInfo;
|
|
178
|
+
}
|
|
179
|
+
default: {
|
|
180
|
+
throw new Error("Unknown analytics provider");
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
function updateProviderInfo(providerInfo, baseUrl, route) {
|
|
185
|
+
providerInfo.baseUrl = baseUrl;
|
|
186
|
+
providerInfo.route = route;
|
|
187
|
+
providerInfo.url = `${baseUrl}/${route.join("/")}`;
|
|
188
|
+
}
|
|
155
189
|
|
|
156
190
|
// src/client/error.ts
|
|
157
191
|
var RATELIMIT_STATUS = 429;
|
|
@@ -233,644 +267,764 @@ var formatWorkflowError = (error) => {
|
|
|
233
267
|
};
|
|
234
268
|
};
|
|
235
269
|
|
|
236
|
-
// src/client/
|
|
237
|
-
var
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
headers;
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
this.retry = // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
248
|
-
typeof config.retry === "boolean" && !config.retry ? {
|
|
249
|
-
attempts: 1,
|
|
250
|
-
backoff: () => 0
|
|
251
|
-
} : {
|
|
252
|
-
attempts: config.retry?.retries ?? 5,
|
|
253
|
-
backoff: config.retry?.backoff ?? ((retryCount) => Math.exp(retryCount) * 50)
|
|
254
|
-
};
|
|
255
|
-
this.headers = config.headers;
|
|
256
|
-
this.telemetryHeaders = config.telemetryHeaders;
|
|
257
|
-
}
|
|
258
|
-
async request(request) {
|
|
259
|
-
const { response } = await this.requestWithBackoff(request);
|
|
260
|
-
if (request.parseResponseAsJson === false) {
|
|
261
|
-
return void 0;
|
|
262
|
-
}
|
|
263
|
-
return await response.json();
|
|
264
|
-
}
|
|
265
|
-
async *requestStream(request) {
|
|
266
|
-
const { response } = await this.requestWithBackoff(request);
|
|
267
|
-
if (!response.body) {
|
|
268
|
-
throw new Error("No response body");
|
|
269
|
-
}
|
|
270
|
-
const body = response.body;
|
|
271
|
-
const reader = body.getReader();
|
|
272
|
-
const decoder = new TextDecoder();
|
|
273
|
-
try {
|
|
274
|
-
while (true) {
|
|
275
|
-
const { done, value } = await reader.read();
|
|
276
|
-
if (done) {
|
|
277
|
-
break;
|
|
278
|
-
}
|
|
279
|
-
const chunkText = decoder.decode(value, { stream: true });
|
|
280
|
-
const chunks = chunkText.split("\n").filter(Boolean);
|
|
281
|
-
for (const chunk of chunks) {
|
|
282
|
-
if (chunk.startsWith("data: ")) {
|
|
283
|
-
const data = chunk.slice(6);
|
|
284
|
-
if (data === "[DONE]") {
|
|
285
|
-
break;
|
|
286
|
-
}
|
|
287
|
-
yield JSON.parse(data);
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
} finally {
|
|
292
|
-
await reader.cancel();
|
|
270
|
+
// src/client/utils.ts
|
|
271
|
+
var isIgnoredHeader = (header) => {
|
|
272
|
+
const lowerCaseHeader = header.toLowerCase();
|
|
273
|
+
return lowerCaseHeader.startsWith("content-type") || lowerCaseHeader.startsWith("upstash-");
|
|
274
|
+
};
|
|
275
|
+
function prefixHeaders(headers) {
|
|
276
|
+
const keysToBePrefixed = [...headers.keys()].filter((key) => !isIgnoredHeader(key));
|
|
277
|
+
for (const key of keysToBePrefixed) {
|
|
278
|
+
const value = headers.get(key);
|
|
279
|
+
if (value !== null) {
|
|
280
|
+
headers.set(`Upstash-Forward-${key}`, value);
|
|
293
281
|
}
|
|
282
|
+
headers.delete(key);
|
|
294
283
|
}
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
const headers = new Headers(request.headers);
|
|
321
|
-
if (!headers.has("Authorization")) {
|
|
322
|
-
headers.set("Authorization", this.authorization);
|
|
284
|
+
return headers;
|
|
285
|
+
}
|
|
286
|
+
function wrapWithGlobalHeaders(headers, globalHeaders, telemetryHeaders) {
|
|
287
|
+
if (!globalHeaders) {
|
|
288
|
+
return headers;
|
|
289
|
+
}
|
|
290
|
+
const finalHeaders = new Headers(globalHeaders);
|
|
291
|
+
headers.forEach((value, key) => {
|
|
292
|
+
finalHeaders.set(key, value);
|
|
293
|
+
});
|
|
294
|
+
telemetryHeaders?.forEach((value, key) => {
|
|
295
|
+
if (!value)
|
|
296
|
+
return;
|
|
297
|
+
finalHeaders.append(key, value);
|
|
298
|
+
});
|
|
299
|
+
return finalHeaders;
|
|
300
|
+
}
|
|
301
|
+
function processHeaders(request) {
|
|
302
|
+
const headers = prefixHeaders(new Headers(request.headers));
|
|
303
|
+
headers.set("Upstash-Method", request.method ?? "POST");
|
|
304
|
+
if (request.delay !== void 0) {
|
|
305
|
+
if (typeof request.delay === "string") {
|
|
306
|
+
headers.set("Upstash-Delay", request.delay);
|
|
307
|
+
} else {
|
|
308
|
+
headers.set("Upstash-Delay", `${request.delay.toFixed(0)}s`);
|
|
323
309
|
}
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
310
|
+
}
|
|
311
|
+
if (request.notBefore !== void 0) {
|
|
312
|
+
headers.set("Upstash-Not-Before", request.notBefore.toFixed(0));
|
|
313
|
+
}
|
|
314
|
+
if (request.deduplicationId !== void 0) {
|
|
315
|
+
headers.set("Upstash-Deduplication-Id", request.deduplicationId);
|
|
316
|
+
}
|
|
317
|
+
if (request.contentBasedDeduplication) {
|
|
318
|
+
headers.set("Upstash-Content-Based-Deduplication", "true");
|
|
319
|
+
}
|
|
320
|
+
if (request.retries !== void 0) {
|
|
321
|
+
headers.set("Upstash-Retries", request.retries.toFixed(0));
|
|
322
|
+
}
|
|
323
|
+
if (request.retryDelay !== void 0) {
|
|
324
|
+
headers.set("Upstash-Retry-Delay", request.retryDelay);
|
|
325
|
+
}
|
|
326
|
+
if (request.callback !== void 0) {
|
|
327
|
+
headers.set("Upstash-Callback", request.callback);
|
|
328
|
+
}
|
|
329
|
+
if (request.failureCallback !== void 0) {
|
|
330
|
+
headers.set("Upstash-Failure-Callback", request.failureCallback);
|
|
331
|
+
}
|
|
332
|
+
if (request.timeout !== void 0) {
|
|
333
|
+
if (typeof request.timeout === "string") {
|
|
334
|
+
headers.set("Upstash-Timeout", request.timeout);
|
|
335
|
+
} else {
|
|
336
|
+
headers.set("Upstash-Timeout", `${request.timeout}s`);
|
|
337
337
|
}
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
});
|
|
351
|
-
} else if (response.headers.get("RateLimit-Limit")) {
|
|
352
|
-
throw new QstashDailyRatelimitError({
|
|
353
|
-
limit: response.headers.get("RateLimit-Limit"),
|
|
354
|
-
remaining: response.headers.get("RateLimit-Remaining"),
|
|
355
|
-
reset: response.headers.get("RateLimit-Reset")
|
|
356
|
-
});
|
|
357
|
-
}
|
|
358
|
-
throw new QstashRatelimitError({
|
|
359
|
-
limit: response.headers.get("Burst-RateLimit-Limit"),
|
|
360
|
-
remaining: response.headers.get("Burst-RateLimit-Remaining"),
|
|
361
|
-
reset: response.headers.get("Burst-RateLimit-Reset")
|
|
362
|
-
});
|
|
338
|
+
}
|
|
339
|
+
if (request.flowControl?.key) {
|
|
340
|
+
const parallelism = request.flowControl.parallelism?.toString();
|
|
341
|
+
const rate = (request.flowControl.rate ?? request.flowControl.ratePerSecond)?.toString();
|
|
342
|
+
const period = typeof request.flowControl.period === "number" ? `${request.flowControl.period}s` : request.flowControl.period;
|
|
343
|
+
const controlValue = [
|
|
344
|
+
parallelism ? `parallelism=${parallelism}` : void 0,
|
|
345
|
+
rate ? `rate=${rate}` : void 0,
|
|
346
|
+
period ? `period=${period}` : void 0
|
|
347
|
+
].filter(Boolean);
|
|
348
|
+
if (controlValue.length === 0) {
|
|
349
|
+
throw new QstashError("Provide at least one of parallelism or ratePerSecond for flowControl");
|
|
363
350
|
}
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
351
|
+
headers.set("Upstash-Flow-Control-Key", request.flowControl.key);
|
|
352
|
+
headers.set("Upstash-Flow-Control-Value", controlValue.join(", "));
|
|
353
|
+
}
|
|
354
|
+
if (request.label !== void 0) {
|
|
355
|
+
headers.set("Upstash-Label", request.label);
|
|
356
|
+
}
|
|
357
|
+
return headers;
|
|
358
|
+
}
|
|
359
|
+
function getRequestPath(request) {
|
|
360
|
+
const nonApiPath = request.url ?? request.urlGroup ?? request.topic;
|
|
361
|
+
if (nonApiPath)
|
|
362
|
+
return nonApiPath;
|
|
363
|
+
if (request.api?.name === "llm")
|
|
364
|
+
return `api/llm`;
|
|
365
|
+
if (request.api?.name === "email") {
|
|
366
|
+
const providerInfo = getProviderInfo(request.api, "not-needed");
|
|
367
|
+
return providerInfo.baseUrl;
|
|
368
|
+
}
|
|
369
|
+
throw new QstashError(`Failed to infer request path for ${JSON.stringify(request)}`);
|
|
370
|
+
}
|
|
371
|
+
var NANOID_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_";
|
|
372
|
+
var NANOID_LENGTH = 21;
|
|
373
|
+
function nanoid() {
|
|
374
|
+
return [...crypto.getRandomValues(new Uint8Array(NANOID_LENGTH))].map((x) => NANOID_CHARS[x % NANOID_CHARS.length]).join("");
|
|
375
|
+
}
|
|
376
|
+
function decodeBase64(base64) {
|
|
377
|
+
try {
|
|
378
|
+
const binString = atob(base64);
|
|
379
|
+
const intArray = Uint8Array.from(binString, (m) => m.codePointAt(0));
|
|
380
|
+
return new TextDecoder().decode(intArray);
|
|
381
|
+
} catch (error) {
|
|
382
|
+
try {
|
|
383
|
+
const result = atob(base64);
|
|
384
|
+
console.warn(
|
|
385
|
+
`Upstash QStash: Failed while decoding base64 "${base64}". Decoding with atob and returning it instead. ${error}`
|
|
369
386
|
);
|
|
387
|
+
return result;
|
|
388
|
+
} catch (error2) {
|
|
389
|
+
console.warn(
|
|
390
|
+
`Upstash QStash: Failed to decode base64 "${base64}" with atob. Returning it as it is. ${error2}`
|
|
391
|
+
);
|
|
392
|
+
return base64;
|
|
370
393
|
}
|
|
371
394
|
}
|
|
395
|
+
}
|
|
396
|
+
function getRuntime() {
|
|
397
|
+
if (typeof process === "object" && typeof process.versions == "object" && process.versions.bun)
|
|
398
|
+
return `bun@${process.versions.bun}`;
|
|
399
|
+
if (typeof EdgeRuntime === "string")
|
|
400
|
+
return "edge-light";
|
|
401
|
+
else if (typeof process === "object" && typeof process.version === "string")
|
|
402
|
+
return `node@${process.version}`;
|
|
403
|
+
return "";
|
|
404
|
+
}
|
|
405
|
+
function getSafeEnvironment() {
|
|
406
|
+
return typeof process === "undefined" ? {} : process.env;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// src/client/multi-region/utils.ts
|
|
410
|
+
var VALID_REGIONS = ["EU_CENTRAL_1", "US_EAST_1"];
|
|
411
|
+
var DEFAULT_QSTASH_URL = "https://qstash.upstash.io";
|
|
412
|
+
var getRegionFromEnvironment = (environment) => {
|
|
413
|
+
const region = environment.QSTASH_REGION;
|
|
414
|
+
return normalizeRegionHeader(region);
|
|
372
415
|
};
|
|
416
|
+
function readEnvironmentVariables(environmentVariables, environment, region) {
|
|
417
|
+
const result = {};
|
|
418
|
+
for (const variable of environmentVariables) {
|
|
419
|
+
const key = region ? `${region}_${variable}` : variable;
|
|
420
|
+
result[variable] = environment[key];
|
|
421
|
+
}
|
|
422
|
+
return result;
|
|
423
|
+
}
|
|
424
|
+
function readClientEnvironmentVariables(environment, region) {
|
|
425
|
+
return readEnvironmentVariables(["QSTASH_URL", "QSTASH_TOKEN"], environment, region);
|
|
426
|
+
}
|
|
427
|
+
function readReceiverEnvironmentVariables(environment, region) {
|
|
428
|
+
return readEnvironmentVariables(
|
|
429
|
+
["QSTASH_CURRENT_SIGNING_KEY", "QSTASH_NEXT_SIGNING_KEY"],
|
|
430
|
+
environment,
|
|
431
|
+
region
|
|
432
|
+
);
|
|
433
|
+
}
|
|
434
|
+
function normalizeRegionHeader(region) {
|
|
435
|
+
if (!region) {
|
|
436
|
+
return void 0;
|
|
437
|
+
}
|
|
438
|
+
region = region.replaceAll("-", "_").toUpperCase();
|
|
439
|
+
if (VALID_REGIONS.includes(region)) {
|
|
440
|
+
return region;
|
|
441
|
+
}
|
|
442
|
+
console.warn(
|
|
443
|
+
`[Upstash QStash] Invalid UPSTASH_REGION header value: "${region}". Expected one of: ${VALID_REGIONS.join(
|
|
444
|
+
", "
|
|
445
|
+
)}.`
|
|
446
|
+
);
|
|
447
|
+
return void 0;
|
|
448
|
+
}
|
|
373
449
|
|
|
374
|
-
// src/client/
|
|
375
|
-
var
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
450
|
+
// src/client/multi-region/incoming.ts
|
|
451
|
+
var getReceiverSigningKeys = ({
|
|
452
|
+
environment,
|
|
453
|
+
regionFromHeader,
|
|
454
|
+
config
|
|
455
|
+
}) => {
|
|
456
|
+
if (config?.currentSigningKey && config.nextSigningKey) {
|
|
457
|
+
return {
|
|
458
|
+
currentSigningKey: config.currentSigningKey,
|
|
459
|
+
nextSigningKey: config.nextSigningKey
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
const regionEnvironment = getRegionFromEnvironment(environment);
|
|
463
|
+
if (regionEnvironment) {
|
|
464
|
+
const regionHeader = normalizeRegionHeader(regionFromHeader);
|
|
465
|
+
if (regionHeader) {
|
|
466
|
+
const regionCreds = readReceiverEnvironmentVariables(environment, regionHeader);
|
|
467
|
+
if (regionCreds.QSTASH_CURRENT_SIGNING_KEY && regionCreds.QSTASH_NEXT_SIGNING_KEY) {
|
|
468
|
+
return {
|
|
469
|
+
currentSigningKey: regionCreds.QSTASH_CURRENT_SIGNING_KEY,
|
|
470
|
+
nextSigningKey: regionCreds.QSTASH_NEXT_SIGNING_KEY,
|
|
471
|
+
region: regionHeader
|
|
472
|
+
};
|
|
473
|
+
} else {
|
|
474
|
+
console.warn(
|
|
475
|
+
`[Upstash QStash] Signing keys not found for region "${regionHeader}". Falling back to default signing keys.`
|
|
476
|
+
);
|
|
400
477
|
}
|
|
478
|
+
} else {
|
|
479
|
+
console.warn(
|
|
480
|
+
`[Upstash QStash] Invalid UPSTASH_REGION header value: "${regionFromHeader}". Expected one of: EU-CENTRAL-1, US-EAST-1. Falling back to default signing keys.`
|
|
481
|
+
);
|
|
401
482
|
}
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
483
|
+
}
|
|
484
|
+
const defaultCreds = readReceiverEnvironmentVariables(environment);
|
|
485
|
+
if (defaultCreds.QSTASH_CURRENT_SIGNING_KEY && defaultCreds.QSTASH_NEXT_SIGNING_KEY) {
|
|
486
|
+
return {
|
|
487
|
+
currentSigningKey: defaultCreds.QSTASH_CURRENT_SIGNING_KEY,
|
|
488
|
+
nextSigningKey: defaultCreds.QSTASH_NEXT_SIGNING_KEY
|
|
489
|
+
};
|
|
405
490
|
}
|
|
406
491
|
};
|
|
407
492
|
|
|
408
|
-
// src/client/
|
|
409
|
-
var
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
);
|
|
422
|
-
const chatRequest = { ...request, messages };
|
|
423
|
-
return chatRequest;
|
|
424
|
-
}
|
|
425
|
-
/**
|
|
426
|
-
* Calls the Upstash completions api given a ChatRequest.
|
|
427
|
-
*
|
|
428
|
-
* Returns a ChatCompletion or a stream of ChatCompletionChunks
|
|
429
|
-
* if stream is enabled.
|
|
430
|
-
*
|
|
431
|
-
* @param request ChatRequest with messages
|
|
432
|
-
* @returns Chat completion or stream
|
|
433
|
-
*/
|
|
434
|
-
create = async (request) => {
|
|
435
|
-
if (request.provider.owner != "upstash")
|
|
436
|
-
return this.createThirdParty(request);
|
|
437
|
-
const body = JSON.stringify(request);
|
|
438
|
-
let baseUrl = void 0;
|
|
439
|
-
let headers = {
|
|
440
|
-
"Content-Type": "application/json",
|
|
441
|
-
Authorization: `Bearer ${this.token}`,
|
|
442
|
-
..."stream" in request && request.stream ? {
|
|
443
|
-
Connection: "keep-alive",
|
|
444
|
-
Accept: "text/event-stream",
|
|
445
|
-
"Cache-Control": "no-cache"
|
|
446
|
-
} : {}
|
|
493
|
+
// src/client/multi-region/outgoing.ts
|
|
494
|
+
var getClientCredentials = (clientCredentialConfig) => {
|
|
495
|
+
const credentials = resolveCredentials(clientCredentialConfig);
|
|
496
|
+
return verifyCredentials(credentials);
|
|
497
|
+
};
|
|
498
|
+
var resolveCredentials = ({
|
|
499
|
+
environment,
|
|
500
|
+
config
|
|
501
|
+
}) => {
|
|
502
|
+
if (config?.baseUrl && config.token) {
|
|
503
|
+
return {
|
|
504
|
+
baseUrl: config.baseUrl,
|
|
505
|
+
token: config.token
|
|
447
506
|
};
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
507
|
+
}
|
|
508
|
+
const region = getRegionFromEnvironment(environment);
|
|
509
|
+
if (region) {
|
|
510
|
+
const regionCreds = readClientEnvironmentVariables(environment, region);
|
|
511
|
+
if (regionCreds.QSTASH_URL && regionCreds.QSTASH_TOKEN) {
|
|
512
|
+
return {
|
|
513
|
+
baseUrl: regionCreds.QSTASH_URL,
|
|
514
|
+
token: regionCreds.QSTASH_TOKEN,
|
|
515
|
+
region
|
|
516
|
+
};
|
|
517
|
+
} else {
|
|
518
|
+
console.warn(
|
|
519
|
+
`[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.`
|
|
454
520
|
);
|
|
455
|
-
headers = { ...headers, ...defaultHeaders };
|
|
456
|
-
baseUrl = baseURL;
|
|
457
521
|
}
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
baseUrl,
|
|
464
|
-
body
|
|
465
|
-
}) : this.http.request({
|
|
466
|
-
path,
|
|
467
|
-
method: "POST",
|
|
468
|
-
headers,
|
|
469
|
-
baseUrl,
|
|
470
|
-
body
|
|
471
|
-
});
|
|
522
|
+
}
|
|
523
|
+
const defaultCreds = readClientEnvironmentVariables(environment);
|
|
524
|
+
return {
|
|
525
|
+
baseUrl: config?.baseUrl ?? defaultCreds.QSTASH_URL ?? DEFAULT_QSTASH_URL,
|
|
526
|
+
token: config?.token ?? defaultCreds.QSTASH_TOKEN ?? ""
|
|
472
527
|
};
|
|
528
|
+
};
|
|
529
|
+
var verifyCredentials = (credentials) => {
|
|
530
|
+
const token = credentials.token;
|
|
531
|
+
let baseUrl = credentials.baseUrl;
|
|
532
|
+
baseUrl = baseUrl.replace(/\/$/, "");
|
|
533
|
+
if (baseUrl === "https://qstash.upstash.io/v2/publish") {
|
|
534
|
+
baseUrl = DEFAULT_QSTASH_URL;
|
|
535
|
+
}
|
|
536
|
+
if (!token) {
|
|
537
|
+
console.warn(
|
|
538
|
+
"[Upstash QStash] client token is not set. Either pass a token or set QSTASH_TOKEN env variable."
|
|
539
|
+
);
|
|
540
|
+
}
|
|
541
|
+
return { baseUrl, token };
|
|
542
|
+
};
|
|
543
|
+
|
|
544
|
+
// src/receiver.ts
|
|
545
|
+
var SignatureError = class extends Error {
|
|
546
|
+
constructor(message) {
|
|
547
|
+
super(message);
|
|
548
|
+
this.name = "SignatureError";
|
|
549
|
+
}
|
|
550
|
+
};
|
|
551
|
+
var Receiver = class {
|
|
552
|
+
currentSigningKey;
|
|
553
|
+
nextSigningKey;
|
|
554
|
+
constructor(config) {
|
|
555
|
+
this.currentSigningKey = config?.currentSigningKey;
|
|
556
|
+
this.nextSigningKey = config?.nextSigningKey;
|
|
557
|
+
}
|
|
473
558
|
/**
|
|
474
|
-
*
|
|
559
|
+
* Verify the signature of a request.
|
|
475
560
|
*
|
|
476
|
-
*
|
|
477
|
-
*
|
|
561
|
+
* Tries to verify the signature with the current signing key.
|
|
562
|
+
* If that fails, maybe because you have rotated the keys recently, it will
|
|
563
|
+
* try to verify the signature with the next signing key.
|
|
478
564
|
*
|
|
479
|
-
*
|
|
480
|
-
* @returns Chat completion or stream
|
|
565
|
+
* If that fails, the signature is invalid and a `SignatureError` is thrown.
|
|
481
566
|
*/
|
|
482
|
-
|
|
483
|
-
const
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
const isAnalyticsEnabled = analytics?.name && analytics.token;
|
|
492
|
-
const analyticsConfig = analytics?.name && analytics.token ? setupAnalytics({ name: analytics.name, token: analytics.token }, token, baseUrl, owner) : { defaultHeaders: void 0, baseURL: baseUrl };
|
|
493
|
-
const isStream = "stream" in request && request.stream;
|
|
494
|
-
const headers = {
|
|
495
|
-
"Content-Type": "application/json",
|
|
496
|
-
Authorization: `Bearer ${token}`,
|
|
497
|
-
...organization ? {
|
|
498
|
-
"OpenAI-Organization": organization
|
|
499
|
-
} : {},
|
|
500
|
-
...isStream ? {
|
|
501
|
-
Connection: "keep-alive",
|
|
502
|
-
Accept: "text/event-stream",
|
|
503
|
-
"Cache-Control": "no-cache"
|
|
504
|
-
} : {},
|
|
505
|
-
...analyticsConfig.defaultHeaders
|
|
506
|
-
};
|
|
507
|
-
const response = await this.http[isStream ? "requestStream" : "request"]({
|
|
508
|
-
path: isAnalyticsEnabled ? [] : ["v1", "chat", "completions"],
|
|
509
|
-
method: "POST",
|
|
510
|
-
headers,
|
|
511
|
-
body,
|
|
512
|
-
baseUrl: analyticsConfig.baseURL
|
|
567
|
+
async verify(request) {
|
|
568
|
+
const environment = getSafeEnvironment();
|
|
569
|
+
const signingKeys = getReceiverSigningKeys({
|
|
570
|
+
environment,
|
|
571
|
+
regionFromHeader: request.upstashRegion,
|
|
572
|
+
config: {
|
|
573
|
+
currentSigningKey: this.currentSigningKey,
|
|
574
|
+
nextSigningKey: this.nextSigningKey
|
|
575
|
+
}
|
|
513
576
|
});
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
const authHeader = String(this.http.authorization);
|
|
519
|
-
const match = /Bearer (.+)/.exec(authHeader);
|
|
520
|
-
if (!match) {
|
|
521
|
-
throw new Error("Invalid authorization header format");
|
|
577
|
+
if (!signingKeys) {
|
|
578
|
+
throw new Error(
|
|
579
|
+
"[Upstash QStash] No signing keys available for verification. See the warning above for more details."
|
|
580
|
+
);
|
|
522
581
|
}
|
|
523
|
-
|
|
582
|
+
let payload;
|
|
583
|
+
try {
|
|
584
|
+
payload = await this.verifyWithKey(signingKeys.currentSigningKey, request);
|
|
585
|
+
} catch {
|
|
586
|
+
payload = await this.verifyWithKey(signingKeys.nextSigningKey, request);
|
|
587
|
+
}
|
|
588
|
+
this.verifyBodyAndUrl(payload, request);
|
|
589
|
+
return true;
|
|
524
590
|
}
|
|
525
591
|
/**
|
|
526
|
-
*
|
|
527
|
-
*
|
|
528
|
-
* Returns a ChatCompletion or a stream of ChatCompletionChunks
|
|
529
|
-
* if stream is enabled.
|
|
530
|
-
*
|
|
531
|
-
* @param request PromptRequest with system and user messages.
|
|
532
|
-
* Note that system parameter shouldn't be passed in the case of
|
|
533
|
-
* mistralai/Mistral-7B-Instruct-v0.2 model.
|
|
534
|
-
* @returns Chat completion or stream
|
|
592
|
+
* Verify signature with a specific signing key
|
|
535
593
|
*/
|
|
536
|
-
|
|
537
|
-
const
|
|
538
|
-
|
|
539
|
-
|
|
594
|
+
async verifyWithKey(key, request) {
|
|
595
|
+
const jwt = await jose.jwtVerify(request.signature, new TextEncoder().encode(key), {
|
|
596
|
+
issuer: "Upstash",
|
|
597
|
+
clockTolerance: request.clockTolerance
|
|
598
|
+
}).catch((error) => {
|
|
599
|
+
throw new SignatureError(error.message);
|
|
600
|
+
});
|
|
601
|
+
return jwt.payload;
|
|
602
|
+
}
|
|
603
|
+
verifyBodyAndUrl(payload, request) {
|
|
604
|
+
const p = payload;
|
|
605
|
+
if (request.url !== void 0 && p.sub !== request.url) {
|
|
606
|
+
throw new SignatureError(`invalid subject: ${p.sub}, want: ${request.url}`);
|
|
607
|
+
}
|
|
608
|
+
const bodyHash = import_crypto_js.default.SHA256(request.body).toString(import_crypto_js.default.enc.Base64url);
|
|
609
|
+
const padding = new RegExp(/=+$/);
|
|
610
|
+
if (p.body.replace(padding, "") !== bodyHash.replace(padding, "")) {
|
|
611
|
+
throw new SignatureError(`body hash does not match, want: ${p.body}, got: ${bodyHash}`);
|
|
612
|
+
}
|
|
613
|
+
}
|
|
540
614
|
};
|
|
541
615
|
|
|
542
|
-
// src/client/
|
|
543
|
-
var
|
|
616
|
+
// src/client/dlq.ts
|
|
617
|
+
var DLQ = class {
|
|
544
618
|
http;
|
|
545
619
|
constructor(http) {
|
|
546
620
|
this.http = http;
|
|
547
621
|
}
|
|
548
622
|
/**
|
|
549
|
-
*
|
|
623
|
+
* List messages in the dlq
|
|
550
624
|
*/
|
|
551
|
-
async
|
|
552
|
-
const
|
|
625
|
+
async listMessages(options) {
|
|
626
|
+
const filterPayload = {
|
|
627
|
+
...options?.filter,
|
|
628
|
+
topicName: options?.filter?.urlGroup
|
|
629
|
+
};
|
|
630
|
+
const messagesPayload = await this.http.request({
|
|
553
631
|
method: "GET",
|
|
554
|
-
path: ["v2", "
|
|
632
|
+
path: ["v2", "dlq"],
|
|
633
|
+
query: {
|
|
634
|
+
cursor: options?.cursor,
|
|
635
|
+
count: options?.count,
|
|
636
|
+
...filterPayload
|
|
637
|
+
}
|
|
555
638
|
});
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
639
|
+
return {
|
|
640
|
+
messages: messagesPayload.messages.map((message) => {
|
|
641
|
+
return {
|
|
642
|
+
...message,
|
|
643
|
+
urlGroup: message.topicName,
|
|
644
|
+
ratePerSecond: "rate" in message ? message.rate : void 0
|
|
645
|
+
};
|
|
646
|
+
}),
|
|
647
|
+
cursor: messagesPayload.cursor
|
|
560
648
|
};
|
|
561
|
-
return message;
|
|
562
649
|
}
|
|
563
650
|
/**
|
|
564
|
-
*
|
|
651
|
+
* Remove a message from the dlq using it's `dlqId`
|
|
565
652
|
*/
|
|
566
|
-
async delete(
|
|
653
|
+
async delete(dlqMessageId) {
|
|
567
654
|
return await this.http.request({
|
|
568
655
|
method: "DELETE",
|
|
569
|
-
path: ["v2", "
|
|
656
|
+
path: ["v2", "dlq", dlqMessageId],
|
|
570
657
|
parseResponseAsJson: false
|
|
658
|
+
// there is no response
|
|
571
659
|
});
|
|
572
660
|
}
|
|
573
|
-
async deleteMany(messageIds) {
|
|
574
|
-
const result = await this.http.request({
|
|
575
|
-
method: "DELETE",
|
|
576
|
-
path: ["v2", "messages"],
|
|
577
|
-
headers: { "Content-Type": "application/json" },
|
|
578
|
-
body: JSON.stringify({ messageIds })
|
|
579
|
-
});
|
|
580
|
-
return result.cancelled;
|
|
581
|
-
}
|
|
582
|
-
async deleteAll() {
|
|
583
|
-
const result = await this.http.request({
|
|
584
|
-
method: "DELETE",
|
|
585
|
-
path: ["v2", "messages"]
|
|
586
|
-
});
|
|
587
|
-
return result.cancelled;
|
|
588
|
-
}
|
|
589
|
-
};
|
|
590
|
-
|
|
591
|
-
// src/client/api/base.ts
|
|
592
|
-
var BaseProvider = class {
|
|
593
|
-
baseUrl;
|
|
594
|
-
token;
|
|
595
|
-
owner;
|
|
596
|
-
constructor(baseUrl, token, owner) {
|
|
597
|
-
this.baseUrl = baseUrl;
|
|
598
|
-
this.token = token;
|
|
599
|
-
this.owner = owner;
|
|
600
|
-
}
|
|
601
|
-
getUrl() {
|
|
602
|
-
return `${this.baseUrl}/${this.getRoute().join("/")}`;
|
|
603
|
-
}
|
|
604
|
-
};
|
|
605
|
-
|
|
606
|
-
// src/client/api/llm.ts
|
|
607
|
-
var LLMProvider = class extends BaseProvider {
|
|
608
|
-
apiKind = "llm";
|
|
609
|
-
organization;
|
|
610
|
-
method = "POST";
|
|
611
|
-
constructor(baseUrl, token, owner, organization) {
|
|
612
|
-
super(baseUrl, token, owner);
|
|
613
|
-
this.organization = organization;
|
|
614
|
-
}
|
|
615
|
-
getRoute() {
|
|
616
|
-
return this.owner === "anthropic" ? ["v1", "messages"] : ["v1", "chat", "completions"];
|
|
617
|
-
}
|
|
618
|
-
getHeaders(options) {
|
|
619
|
-
if (this.owner === "upstash" && !options.analytics) {
|
|
620
|
-
return { "content-type": "application/json" };
|
|
621
|
-
}
|
|
622
|
-
const header = this.owner === "anthropic" ? "x-api-key" : "authorization";
|
|
623
|
-
const headerValue = this.owner === "anthropic" ? this.token : `Bearer ${this.token}`;
|
|
624
|
-
const headers = {
|
|
625
|
-
[header]: headerValue,
|
|
626
|
-
"content-type": "application/json"
|
|
627
|
-
};
|
|
628
|
-
if (this.owner === "openai" && this.organization) {
|
|
629
|
-
headers["OpenAI-Organization"] = this.organization;
|
|
630
|
-
}
|
|
631
|
-
if (this.owner === "anthropic") {
|
|
632
|
-
headers["anthropic-version"] = "2023-06-01";
|
|
633
|
-
}
|
|
634
|
-
return headers;
|
|
635
|
-
}
|
|
636
661
|
/**
|
|
637
|
-
*
|
|
638
|
-
*
|
|
639
|
-
* @param request
|
|
640
|
-
* @param options
|
|
662
|
+
* Remove multiple messages from the dlq using their `dlqId`s
|
|
641
663
|
*/
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
};
|
|
649
|
-
var upstash = () => {
|
|
650
|
-
return new LLMProvider("https://qstash.upstash.io/llm", "", "upstash");
|
|
651
|
-
};
|
|
652
|
-
|
|
653
|
-
// src/client/api/utils.ts
|
|
654
|
-
var getProviderInfo = (api, upstashToken) => {
|
|
655
|
-
const { name, provider, ...parameters } = api;
|
|
656
|
-
const finalProvider = provider ?? upstash();
|
|
657
|
-
if (finalProvider.owner === "upstash" && !finalProvider.token) {
|
|
658
|
-
finalProvider.token = upstashToken;
|
|
659
|
-
}
|
|
660
|
-
if (!finalProvider.baseUrl)
|
|
661
|
-
throw new TypeError("baseUrl cannot be empty or undefined!");
|
|
662
|
-
if (!finalProvider.token)
|
|
663
|
-
throw new TypeError("token cannot be empty or undefined!");
|
|
664
|
-
if (finalProvider.apiKind !== name) {
|
|
665
|
-
throw new TypeError(
|
|
666
|
-
`Unexpected api name. Expected '${finalProvider.apiKind}', received ${name}`
|
|
667
|
-
);
|
|
668
|
-
}
|
|
669
|
-
const providerInfo = {
|
|
670
|
-
url: finalProvider.getUrl(),
|
|
671
|
-
baseUrl: finalProvider.baseUrl,
|
|
672
|
-
route: finalProvider.getRoute(),
|
|
673
|
-
appendHeaders: finalProvider.getHeaders(parameters),
|
|
674
|
-
owner: finalProvider.owner,
|
|
675
|
-
method: finalProvider.method
|
|
676
|
-
};
|
|
677
|
-
return finalProvider.onFinish(providerInfo, parameters);
|
|
678
|
-
};
|
|
679
|
-
var safeJoinHeaders = (headers, record) => {
|
|
680
|
-
const joinedHeaders = new Headers(record);
|
|
681
|
-
for (const [header, value] of headers.entries()) {
|
|
682
|
-
joinedHeaders.set(header, value);
|
|
664
|
+
async deleteMany(request) {
|
|
665
|
+
return await this.http.request({
|
|
666
|
+
method: "DELETE",
|
|
667
|
+
path: ["v2", "dlq"],
|
|
668
|
+
headers: { "Content-Type": "application/json" },
|
|
669
|
+
body: JSON.stringify({ dlqIds: request.dlqIds })
|
|
670
|
+
});
|
|
683
671
|
}
|
|
684
|
-
return joinedHeaders;
|
|
685
672
|
};
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
673
|
+
|
|
674
|
+
// src/client/http.ts
|
|
675
|
+
var HttpClient = class {
|
|
676
|
+
baseUrl;
|
|
677
|
+
authorization;
|
|
678
|
+
options;
|
|
679
|
+
retry;
|
|
680
|
+
headers;
|
|
681
|
+
telemetryHeaders;
|
|
682
|
+
constructor(config) {
|
|
683
|
+
this.baseUrl = config.baseUrl.replace(/\/$/, "");
|
|
684
|
+
this.authorization = config.authorization;
|
|
685
|
+
this.retry = // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
686
|
+
typeof config.retry === "boolean" && !config.retry ? {
|
|
687
|
+
attempts: 1,
|
|
688
|
+
backoff: () => 0
|
|
689
|
+
} : {
|
|
690
|
+
attempts: config.retry?.retries ?? 5,
|
|
691
|
+
backoff: config.retry?.backoff ?? ((retryCount) => Math.exp(retryCount) * 50)
|
|
692
|
+
};
|
|
693
|
+
this.headers = config.headers;
|
|
694
|
+
this.telemetryHeaders = config.telemetryHeaders;
|
|
690
695
|
}
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
+
async request(request) {
|
|
697
|
+
const { response } = await this.requestWithBackoff(request);
|
|
698
|
+
if (request.parseResponseAsJson === false) {
|
|
699
|
+
return void 0;
|
|
700
|
+
}
|
|
701
|
+
return await response.json();
|
|
702
|
+
}
|
|
703
|
+
async *requestStream(request) {
|
|
704
|
+
const { response } = await this.requestWithBackoff(request);
|
|
705
|
+
if (!response.body) {
|
|
706
|
+
throw new Error("No response body");
|
|
707
|
+
}
|
|
708
|
+
const body = response.body;
|
|
709
|
+
const reader = body.getReader();
|
|
710
|
+
const decoder = new TextDecoder();
|
|
711
|
+
try {
|
|
712
|
+
while (true) {
|
|
713
|
+
const { done, value } = await reader.read();
|
|
714
|
+
if (done) {
|
|
715
|
+
break;
|
|
716
|
+
}
|
|
717
|
+
const chunkText = decoder.decode(value, { stream: true });
|
|
718
|
+
const chunks = chunkText.split("\n").filter(Boolean);
|
|
719
|
+
for (const chunk of chunks) {
|
|
720
|
+
if (chunk.startsWith("data: ")) {
|
|
721
|
+
const data = chunk.slice(6);
|
|
722
|
+
if (data === "[DONE]") {
|
|
723
|
+
break;
|
|
724
|
+
}
|
|
725
|
+
yield JSON.parse(data);
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
} finally {
|
|
730
|
+
await reader.cancel();
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
requestWithBackoff = async (request) => {
|
|
734
|
+
const [url, requestOptions] = this.processRequest(request);
|
|
735
|
+
let response = void 0;
|
|
736
|
+
let error = void 0;
|
|
737
|
+
for (let index = 0; index <= this.retry.attempts; index++) {
|
|
738
|
+
try {
|
|
739
|
+
response = await fetch(url.toString(), requestOptions);
|
|
740
|
+
break;
|
|
741
|
+
} catch (error_) {
|
|
742
|
+
error = error_;
|
|
743
|
+
if (index < this.retry.attempts) {
|
|
744
|
+
await new Promise((r) => setTimeout(r, this.retry.backoff(index)));
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
if (!response) {
|
|
749
|
+
throw error ?? new Error("Exhausted all retries");
|
|
696
750
|
}
|
|
751
|
+
await this.checkResponse(response);
|
|
697
752
|
return {
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
headers: safeJoinHeaders(headers, appendHeaders),
|
|
701
|
-
...owner === "upstash" && !request.api.analytics ? { api: { name: "llm" }, url: void 0, callback } : { url, api: void 0 }
|
|
753
|
+
response,
|
|
754
|
+
error
|
|
702
755
|
};
|
|
703
|
-
}
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
headers
|
|
708
|
-
|
|
709
|
-
|
|
756
|
+
};
|
|
757
|
+
processRequest = (request) => {
|
|
758
|
+
const headers = new Headers(request.headers);
|
|
759
|
+
if (!headers.has("Authorization")) {
|
|
760
|
+
headers.set("Authorization", this.authorization);
|
|
761
|
+
}
|
|
762
|
+
const requestOptions = {
|
|
763
|
+
method: request.method,
|
|
764
|
+
headers,
|
|
765
|
+
body: request.body,
|
|
766
|
+
keepalive: request.keepalive
|
|
710
767
|
};
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
if (providerInfo.owner === "upstash") {
|
|
718
|
-
updateProviderInfo(providerInfo, "https://qstash.helicone.ai", [
|
|
719
|
-
"llm",
|
|
720
|
-
...providerInfo.route
|
|
721
|
-
]);
|
|
722
|
-
} else {
|
|
723
|
-
providerInfo.appendHeaders["Helicone-Target-Url"] = providerInfo.baseUrl;
|
|
724
|
-
updateProviderInfo(providerInfo, "https://gateway.helicone.ai", providerInfo.route);
|
|
768
|
+
const url = new URL([request.baseUrl ?? this.baseUrl, ...request.path].join("/"));
|
|
769
|
+
if (request.query) {
|
|
770
|
+
for (const [key, value] of Object.entries(request.query)) {
|
|
771
|
+
if (value !== void 0) {
|
|
772
|
+
url.searchParams.set(key, value.toString());
|
|
773
|
+
}
|
|
725
774
|
}
|
|
726
|
-
return providerInfo;
|
|
727
775
|
}
|
|
728
|
-
|
|
729
|
-
|
|
776
|
+
return [url.toString(), requestOptions];
|
|
777
|
+
};
|
|
778
|
+
async checkResponse(response) {
|
|
779
|
+
if (response.status === 429) {
|
|
780
|
+
if (response.headers.get("x-ratelimit-limit-requests")) {
|
|
781
|
+
throw new QstashChatRatelimitError({
|
|
782
|
+
"limit-requests": response.headers.get("x-ratelimit-limit-requests"),
|
|
783
|
+
"limit-tokens": response.headers.get("x-ratelimit-limit-tokens"),
|
|
784
|
+
"remaining-requests": response.headers.get("x-ratelimit-remaining-requests"),
|
|
785
|
+
"remaining-tokens": response.headers.get("x-ratelimit-remaining-tokens"),
|
|
786
|
+
"reset-requests": response.headers.get("x-ratelimit-reset-requests"),
|
|
787
|
+
"reset-tokens": response.headers.get("x-ratelimit-reset-tokens")
|
|
788
|
+
});
|
|
789
|
+
} else if (response.headers.get("RateLimit-Limit")) {
|
|
790
|
+
throw new QstashDailyRatelimitError({
|
|
791
|
+
limit: response.headers.get("RateLimit-Limit"),
|
|
792
|
+
remaining: response.headers.get("RateLimit-Remaining"),
|
|
793
|
+
reset: response.headers.get("RateLimit-Reset")
|
|
794
|
+
});
|
|
795
|
+
}
|
|
796
|
+
throw new QstashRatelimitError({
|
|
797
|
+
limit: response.headers.get("Burst-RateLimit-Limit"),
|
|
798
|
+
remaining: response.headers.get("Burst-RateLimit-Remaining"),
|
|
799
|
+
reset: response.headers.get("Burst-RateLimit-Reset")
|
|
800
|
+
});
|
|
730
801
|
}
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
}
|
|
738
|
-
|
|
739
|
-
// src/client/utils.ts
|
|
740
|
-
var isIgnoredHeader = (header) => {
|
|
741
|
-
const lowerCaseHeader = header.toLowerCase();
|
|
742
|
-
return lowerCaseHeader.startsWith("content-type") || lowerCaseHeader.startsWith("upstash-");
|
|
743
|
-
};
|
|
744
|
-
function prefixHeaders(headers) {
|
|
745
|
-
const keysToBePrefixed = [...headers.keys()].filter((key) => !isIgnoredHeader(key));
|
|
746
|
-
for (const key of keysToBePrefixed) {
|
|
747
|
-
const value = headers.get(key);
|
|
748
|
-
if (value !== null) {
|
|
749
|
-
headers.set(`Upstash-Forward-${key}`, value);
|
|
802
|
+
if (response.status < 200 || response.status >= 300) {
|
|
803
|
+
const body = await response.text();
|
|
804
|
+
throw new QstashError(
|
|
805
|
+
body.length > 0 ? body : `Error: status=${response.status}`,
|
|
806
|
+
response.status
|
|
807
|
+
);
|
|
750
808
|
}
|
|
751
|
-
headers.delete(key);
|
|
752
|
-
}
|
|
753
|
-
return headers;
|
|
754
|
-
}
|
|
755
|
-
function wrapWithGlobalHeaders(headers, globalHeaders, telemetryHeaders) {
|
|
756
|
-
if (!globalHeaders) {
|
|
757
|
-
return headers;
|
|
758
809
|
}
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
810
|
+
};
|
|
811
|
+
|
|
812
|
+
// src/client/llm/providers.ts
|
|
813
|
+
var setupAnalytics = (analytics, providerApiKey, providerBaseUrl, provider) => {
|
|
814
|
+
if (!analytics)
|
|
815
|
+
return {};
|
|
816
|
+
switch (analytics.name) {
|
|
817
|
+
case "helicone": {
|
|
818
|
+
switch (provider) {
|
|
819
|
+
case "upstash": {
|
|
820
|
+
return {
|
|
821
|
+
baseURL: "https://qstash.helicone.ai/llm/v1/chat/completions",
|
|
822
|
+
defaultHeaders: {
|
|
823
|
+
"Helicone-Auth": `Bearer ${analytics.token}`,
|
|
824
|
+
Authorization: `Bearer ${providerApiKey}`
|
|
825
|
+
}
|
|
826
|
+
};
|
|
827
|
+
}
|
|
828
|
+
default: {
|
|
829
|
+
return {
|
|
830
|
+
baseURL: "https://gateway.helicone.ai/v1/chat/completions",
|
|
831
|
+
defaultHeaders: {
|
|
832
|
+
"Helicone-Auth": `Bearer ${analytics.token}`,
|
|
833
|
+
"Helicone-Target-Url": providerBaseUrl,
|
|
834
|
+
Authorization: `Bearer ${providerApiKey}`
|
|
835
|
+
}
|
|
836
|
+
};
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
default: {
|
|
841
|
+
throw new Error("Unknown analytics provider");
|
|
778
842
|
}
|
|
779
843
|
}
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
if (request.retries !== void 0) {
|
|
790
|
-
headers.set("Upstash-Retries", request.retries.toFixed(0));
|
|
791
|
-
}
|
|
792
|
-
if (request.retryDelay !== void 0) {
|
|
793
|
-
headers.set("Upstash-Retry-Delay", request.retryDelay);
|
|
794
|
-
}
|
|
795
|
-
if (request.callback !== void 0) {
|
|
796
|
-
headers.set("Upstash-Callback", request.callback);
|
|
844
|
+
};
|
|
845
|
+
|
|
846
|
+
// src/client/llm/chat.ts
|
|
847
|
+
var Chat = class _Chat {
|
|
848
|
+
http;
|
|
849
|
+
token;
|
|
850
|
+
constructor(http, token) {
|
|
851
|
+
this.http = http;
|
|
852
|
+
this.token = token;
|
|
797
853
|
}
|
|
798
|
-
|
|
799
|
-
|
|
854
|
+
static toChatRequest(request) {
|
|
855
|
+
const messages = [];
|
|
856
|
+
messages.push(
|
|
857
|
+
{ role: "system", content: request.system },
|
|
858
|
+
{ role: "user", content: request.user }
|
|
859
|
+
);
|
|
860
|
+
const chatRequest = { ...request, messages };
|
|
861
|
+
return chatRequest;
|
|
800
862
|
}
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
863
|
+
/**
|
|
864
|
+
* Calls the Upstash completions api given a ChatRequest.
|
|
865
|
+
*
|
|
866
|
+
* Returns a ChatCompletion or a stream of ChatCompletionChunks
|
|
867
|
+
* if stream is enabled.
|
|
868
|
+
*
|
|
869
|
+
* @param request ChatRequest with messages
|
|
870
|
+
* @returns Chat completion or stream
|
|
871
|
+
*/
|
|
872
|
+
create = async (request) => {
|
|
873
|
+
if (request.provider.owner != "upstash")
|
|
874
|
+
return this.createThirdParty(request);
|
|
875
|
+
const body = JSON.stringify(request);
|
|
876
|
+
let baseUrl = void 0;
|
|
877
|
+
let headers = {
|
|
878
|
+
"Content-Type": "application/json",
|
|
879
|
+
Authorization: `Bearer ${this.token}`,
|
|
880
|
+
..."stream" in request && request.stream ? {
|
|
881
|
+
Connection: "keep-alive",
|
|
882
|
+
Accept: "text/event-stream",
|
|
883
|
+
"Cache-Control": "no-cache"
|
|
884
|
+
} : {}
|
|
885
|
+
};
|
|
886
|
+
if (request.analytics) {
|
|
887
|
+
const { baseURL, defaultHeaders } = setupAnalytics(
|
|
888
|
+
{ name: "helicone", token: request.analytics.token },
|
|
889
|
+
this.getAuthorizationToken(),
|
|
890
|
+
request.provider.baseUrl,
|
|
891
|
+
"upstash"
|
|
892
|
+
);
|
|
893
|
+
headers = { ...headers, ...defaultHeaders };
|
|
894
|
+
baseUrl = baseURL;
|
|
806
895
|
}
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
896
|
+
const path = request.analytics ? [] : ["llm", "v1", "chat", "completions"];
|
|
897
|
+
return "stream" in request && request.stream ? this.http.requestStream({
|
|
898
|
+
path,
|
|
899
|
+
method: "POST",
|
|
900
|
+
headers,
|
|
901
|
+
baseUrl,
|
|
902
|
+
body
|
|
903
|
+
}) : this.http.request({
|
|
904
|
+
path,
|
|
905
|
+
method: "POST",
|
|
906
|
+
headers,
|
|
907
|
+
baseUrl,
|
|
908
|
+
body
|
|
909
|
+
});
|
|
910
|
+
};
|
|
911
|
+
/**
|
|
912
|
+
* Calls the Upstash completions api given a ChatRequest.
|
|
913
|
+
*
|
|
914
|
+
* Returns a ChatCompletion or a stream of ChatCompletionChunks
|
|
915
|
+
* if stream is enabled.
|
|
916
|
+
*
|
|
917
|
+
* @param request ChatRequest with messages
|
|
918
|
+
* @returns Chat completion or stream
|
|
919
|
+
*/
|
|
920
|
+
createThirdParty = async (request) => {
|
|
921
|
+
const { baseUrl, token, owner, organization } = request.provider;
|
|
922
|
+
if (owner === "upstash")
|
|
923
|
+
throw new Error("Upstash is not 3rd party provider!");
|
|
924
|
+
delete request.provider;
|
|
925
|
+
delete request.system;
|
|
926
|
+
const analytics = request.analytics;
|
|
927
|
+
delete request.analytics;
|
|
928
|
+
const body = JSON.stringify(request);
|
|
929
|
+
const isAnalyticsEnabled = analytics?.name && analytics.token;
|
|
930
|
+
const analyticsConfig = analytics?.name && analytics.token ? setupAnalytics({ name: analytics.name, token: analytics.token }, token, baseUrl, owner) : { defaultHeaders: void 0, baseURL: baseUrl };
|
|
931
|
+
const isStream = "stream" in request && request.stream;
|
|
932
|
+
const headers = {
|
|
933
|
+
"Content-Type": "application/json",
|
|
934
|
+
Authorization: `Bearer ${token}`,
|
|
935
|
+
...organization ? {
|
|
936
|
+
"OpenAI-Organization": organization
|
|
937
|
+
} : {},
|
|
938
|
+
...isStream ? {
|
|
939
|
+
Connection: "keep-alive",
|
|
940
|
+
Accept: "text/event-stream",
|
|
941
|
+
"Cache-Control": "no-cache"
|
|
942
|
+
} : {},
|
|
943
|
+
...analyticsConfig.defaultHeaders
|
|
944
|
+
};
|
|
945
|
+
const response = await this.http[isStream ? "requestStream" : "request"]({
|
|
946
|
+
path: isAnalyticsEnabled ? [] : ["v1", "chat", "completions"],
|
|
947
|
+
method: "POST",
|
|
948
|
+
headers,
|
|
949
|
+
body,
|
|
950
|
+
baseUrl: analyticsConfig.baseURL
|
|
951
|
+
});
|
|
952
|
+
return response;
|
|
953
|
+
};
|
|
954
|
+
// Helper method to get the authorization token
|
|
955
|
+
getAuthorizationToken() {
|
|
956
|
+
const authHeader = String(this.http.authorization);
|
|
957
|
+
const match = /Bearer (.+)/.exec(authHeader);
|
|
958
|
+
if (!match) {
|
|
959
|
+
throw new Error("Invalid authorization header format");
|
|
819
960
|
}
|
|
820
|
-
|
|
821
|
-
headers.set("Upstash-Flow-Control-Value", controlValue.join(", "));
|
|
961
|
+
return match[1];
|
|
822
962
|
}
|
|
823
|
-
|
|
824
|
-
|
|
963
|
+
/**
|
|
964
|
+
* Calls the Upstash completions api given a PromptRequest.
|
|
965
|
+
*
|
|
966
|
+
* Returns a ChatCompletion or a stream of ChatCompletionChunks
|
|
967
|
+
* if stream is enabled.
|
|
968
|
+
*
|
|
969
|
+
* @param request PromptRequest with system and user messages.
|
|
970
|
+
* Note that system parameter shouldn't be passed in the case of
|
|
971
|
+
* mistralai/Mistral-7B-Instruct-v0.2 model.
|
|
972
|
+
* @returns Chat completion or stream
|
|
973
|
+
*/
|
|
974
|
+
prompt = async (request) => {
|
|
975
|
+
const chatRequest = _Chat.toChatRequest(request);
|
|
976
|
+
return this.create(chatRequest);
|
|
977
|
+
};
|
|
978
|
+
};
|
|
979
|
+
|
|
980
|
+
// src/client/messages.ts
|
|
981
|
+
var Messages = class {
|
|
982
|
+
http;
|
|
983
|
+
constructor(http) {
|
|
984
|
+
this.http = http;
|
|
825
985
|
}
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
986
|
+
/**
|
|
987
|
+
* Get a message
|
|
988
|
+
*/
|
|
989
|
+
async get(messageId) {
|
|
990
|
+
const messagePayload = await this.http.request({
|
|
991
|
+
method: "GET",
|
|
992
|
+
path: ["v2", "messages", messageId]
|
|
993
|
+
});
|
|
994
|
+
const message = {
|
|
995
|
+
...messagePayload,
|
|
996
|
+
urlGroup: messagePayload.topicName,
|
|
997
|
+
ratePerSecond: "rate" in messagePayload ? messagePayload.rate : void 0
|
|
998
|
+
};
|
|
999
|
+
return message;
|
|
837
1000
|
}
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
const binString = atob(base64);
|
|
848
|
-
const intArray = Uint8Array.from(binString, (m) => m.codePointAt(0));
|
|
849
|
-
return new TextDecoder().decode(intArray);
|
|
850
|
-
} catch (error) {
|
|
851
|
-
try {
|
|
852
|
-
const result = atob(base64);
|
|
853
|
-
console.warn(
|
|
854
|
-
`Upstash QStash: Failed while decoding base64 "${base64}". Decoding with atob and returning it instead. ${error}`
|
|
855
|
-
);
|
|
856
|
-
return result;
|
|
857
|
-
} catch (error2) {
|
|
858
|
-
console.warn(
|
|
859
|
-
`Upstash QStash: Failed to decode base64 "${base64}" with atob. Returning it as it is. ${error2}`
|
|
860
|
-
);
|
|
861
|
-
return base64;
|
|
862
|
-
}
|
|
1001
|
+
/**
|
|
1002
|
+
* Cancel a message
|
|
1003
|
+
*/
|
|
1004
|
+
async delete(messageId) {
|
|
1005
|
+
return await this.http.request({
|
|
1006
|
+
method: "DELETE",
|
|
1007
|
+
path: ["v2", "messages", messageId],
|
|
1008
|
+
parseResponseAsJson: false
|
|
1009
|
+
});
|
|
863
1010
|
}
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
return
|
|
872
|
-
|
|
873
|
-
|
|
1011
|
+
async deleteMany(messageIds) {
|
|
1012
|
+
const result = await this.http.request({
|
|
1013
|
+
method: "DELETE",
|
|
1014
|
+
path: ["v2", "messages"],
|
|
1015
|
+
headers: { "Content-Type": "application/json" },
|
|
1016
|
+
body: JSON.stringify({ messageIds })
|
|
1017
|
+
});
|
|
1018
|
+
return result.cancelled;
|
|
1019
|
+
}
|
|
1020
|
+
async deleteAll() {
|
|
1021
|
+
const result = await this.http.request({
|
|
1022
|
+
method: "DELETE",
|
|
1023
|
+
path: ["v2", "messages"]
|
|
1024
|
+
});
|
|
1025
|
+
return result.cancelled;
|
|
1026
|
+
}
|
|
1027
|
+
};
|
|
874
1028
|
|
|
875
1029
|
// src/client/queue.ts
|
|
876
1030
|
var Queue = class {
|
|
@@ -2587,19 +2741,15 @@ var Workflow = class {
|
|
|
2587
2741
|
};
|
|
2588
2742
|
|
|
2589
2743
|
// version.ts
|
|
2590
|
-
var VERSION = "v2.
|
|
2744
|
+
var VERSION = "v2.9.0";
|
|
2591
2745
|
|
|
2592
2746
|
// src/client/client.ts
|
|
2593
2747
|
var Client = class {
|
|
2594
2748
|
http;
|
|
2595
2749
|
token;
|
|
2596
2750
|
constructor(config) {
|
|
2597
|
-
const environment =
|
|
2598
|
-
|
|
2599
|
-
if (baseUrl === "https://qstash.upstash.io/v2/publish") {
|
|
2600
|
-
baseUrl = "https://qstash.upstash.io";
|
|
2601
|
-
}
|
|
2602
|
-
const token = config?.token ?? environment.QSTASH_TOKEN;
|
|
2751
|
+
const environment = getSafeEnvironment();
|
|
2752
|
+
const { baseUrl, token } = getClientCredentials({ environment, config });
|
|
2603
2753
|
const enableTelemetry = environment.UPSTASH_DISABLE_TELEMETRY ? false : config?.enableTelemetry ?? true;
|
|
2604
2754
|
const isCloudflare = typeof caches !== "undefined" && "default" in caches;
|
|
2605
2755
|
const telemetryHeaders = new Headers(
|
|
@@ -2618,11 +2768,6 @@ var Client = class {
|
|
|
2618
2768
|
//@ts-expect-error caused by undici and bunjs type overlap
|
|
2619
2769
|
telemetryHeaders
|
|
2620
2770
|
});
|
|
2621
|
-
if (!token) {
|
|
2622
|
-
console.warn(
|
|
2623
|
-
"[Upstash QStash] client token is not set. Either pass a token or set QSTASH_TOKEN env variable."
|
|
2624
|
-
);
|
|
2625
|
-
}
|
|
2626
2771
|
this.token = token;
|
|
2627
2772
|
}
|
|
2628
2773
|
/**
|
|
@@ -2854,12 +2999,11 @@ var Client = class {
|
|
|
2854
2999
|
// platforms/solidjs.ts
|
|
2855
3000
|
var verifySignatureSolidjs = (handler, config) => {
|
|
2856
3001
|
const currentSigningKey = config?.currentSigningKey ?? process.env.QSTASH_CURRENT_SIGNING_KEY;
|
|
2857
|
-
if (!currentSigningKey) {
|
|
2858
|
-
throw new Error("currentSigningKey is required, either in the config or from the env");
|
|
2859
|
-
}
|
|
2860
3002
|
const nextSigningKey = config?.nextSigningKey ?? process.env.QSTASH_NEXT_SIGNING_KEY;
|
|
2861
|
-
if (!nextSigningKey) {
|
|
2862
|
-
throw new Error(
|
|
3003
|
+
if (!currentSigningKey && !nextSigningKey && !process.env.QSTASH_REGION) {
|
|
3004
|
+
throw new Error(
|
|
3005
|
+
"currentSigningKey and nextSigningKey are required, either in the config or as env variables (QSTASH_CURRENT_SIGNING_KEY and QSTASH_NEXT_SIGNING_KEY)"
|
|
3006
|
+
);
|
|
2863
3007
|
}
|
|
2864
3008
|
const receiver = new Receiver({
|
|
2865
3009
|
currentSigningKey,
|
|
@@ -2873,12 +3017,14 @@ var verifySignatureSolidjs = (handler, config) => {
|
|
|
2873
3017
|
if (typeof signature !== "string") {
|
|
2874
3018
|
throw new TypeError("`Upstash-Signature` header is not a string");
|
|
2875
3019
|
}
|
|
3020
|
+
const upstashRegion = event.request.headers.get("upstash-region");
|
|
2876
3021
|
const cloneRequest = event.request.clone();
|
|
2877
3022
|
const body = await cloneRequest.text();
|
|
2878
3023
|
const isValid = await receiver.verify({
|
|
2879
3024
|
signature,
|
|
2880
3025
|
body,
|
|
2881
|
-
clockTolerance: config?.clockTolerance
|
|
3026
|
+
clockTolerance: config?.clockTolerance,
|
|
3027
|
+
upstashRegion: upstashRegion ?? void 0
|
|
2882
3028
|
});
|
|
2883
3029
|
if (!isValid) {
|
|
2884
3030
|
return new Response("invalid signature", { status: 403 });
|