@naturalpay/sdk 0.1.2 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -231
- package/dist/index.cjs +577 -118
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +208 -5
- package/dist/index.d.ts +208 -5
- package/dist/index.js +568 -119
- package/dist/index.js.map +1 -1
- package/dist/mcp/cli.cjs +1 -1
- package/dist/mcp/cli.cjs.map +1 -1
- package/dist/mcp/cli.js +1 -1
- package/dist/mcp/cli.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var async_hooks = require('async_hooks');
|
|
4
|
+
var crypto = require('crypto');
|
|
4
5
|
|
|
5
6
|
// src/errors.ts
|
|
6
7
|
var NaturalError = class extends Error {
|
|
@@ -60,9 +61,15 @@ var ServerError = class extends NaturalError {
|
|
|
60
61
|
this.name = "ServerError";
|
|
61
62
|
}
|
|
62
63
|
};
|
|
64
|
+
var WebhookVerificationError = class extends NaturalError {
|
|
65
|
+
constructor(message) {
|
|
66
|
+
super(message, { code: "webhook_verification_failed" });
|
|
67
|
+
this.name = "WebhookVerificationError";
|
|
68
|
+
}
|
|
69
|
+
};
|
|
63
70
|
|
|
64
71
|
// src/version.ts
|
|
65
|
-
var VERSION = "0.1.
|
|
72
|
+
var VERSION = "0.1.4";
|
|
66
73
|
|
|
67
74
|
// src/logging.ts
|
|
68
75
|
var LOG_LEVEL_VALUES = {
|
|
@@ -253,7 +260,7 @@ function getLogger(name) {
|
|
|
253
260
|
}
|
|
254
261
|
return new Logger(name);
|
|
255
262
|
}
|
|
256
|
-
function logError(
|
|
263
|
+
function logError(logger3, message, options) {
|
|
257
264
|
const extra = { ...options };
|
|
258
265
|
if (options?.error) {
|
|
259
266
|
extra["errorType"] = options.error.name;
|
|
@@ -263,9 +270,9 @@ function logError(logger2, message, options) {
|
|
|
263
270
|
}
|
|
264
271
|
delete extra["error"];
|
|
265
272
|
}
|
|
266
|
-
|
|
273
|
+
logger3.error(message, extra);
|
|
267
274
|
}
|
|
268
|
-
function logApiCall(
|
|
275
|
+
function logApiCall(logger3, method, path, options) {
|
|
269
276
|
const extra = {
|
|
270
277
|
method,
|
|
271
278
|
path,
|
|
@@ -278,14 +285,14 @@ function logApiCall(logger2, method, path, options) {
|
|
|
278
285
|
extra["durationMs"] = Math.round(options.durationMs * 100) / 100;
|
|
279
286
|
}
|
|
280
287
|
if (options?.error) {
|
|
281
|
-
logError(
|
|
288
|
+
logError(logger3, `API call failed: ${method} ${path}`, extra);
|
|
282
289
|
} else if (options?.statusCode && options.statusCode >= 400) {
|
|
283
|
-
|
|
290
|
+
logger3.warning(`API call error: ${method} ${path} -> ${options.statusCode}`, extra);
|
|
284
291
|
} else {
|
|
285
|
-
|
|
292
|
+
logger3.info(`API call: ${method} ${path} -> ${options?.statusCode}`, extra);
|
|
286
293
|
}
|
|
287
294
|
}
|
|
288
|
-
function logToolCall(
|
|
295
|
+
function logToolCall(logger3, toolName, options) {
|
|
289
296
|
const extra = {
|
|
290
297
|
toolName,
|
|
291
298
|
...options
|
|
@@ -294,24 +301,114 @@ function logToolCall(logger2, toolName, options) {
|
|
|
294
301
|
extra["durationMs"] = Math.round(options.durationMs * 100) / 100;
|
|
295
302
|
}
|
|
296
303
|
if (options?.error) {
|
|
297
|
-
logError(
|
|
304
|
+
logError(logger3, `Tool call failed: ${toolName}`, extra);
|
|
298
305
|
} else if (options?.success === false) {
|
|
299
|
-
|
|
306
|
+
logger3.warning(`Tool call returned error: ${toolName}`, extra);
|
|
300
307
|
} else {
|
|
301
|
-
|
|
308
|
+
logger3.info(`Tool call: ${toolName}`, extra);
|
|
302
309
|
}
|
|
303
310
|
}
|
|
311
|
+
function configHash(cfg) {
|
|
312
|
+
const content = JSON.stringify({
|
|
313
|
+
system_prompt: cfg.systemPrompt,
|
|
314
|
+
tools_available: [...cfg.toolsAvailable].sort()
|
|
315
|
+
});
|
|
316
|
+
return crypto.createHash("sha256").update(content).digest("hex");
|
|
317
|
+
}
|
|
318
|
+
function agentConfigToDict(cfg) {
|
|
319
|
+
return {
|
|
320
|
+
system_prompt: cfg.systemPrompt,
|
|
321
|
+
tools_available: [...cfg.toolsAvailable].sort()
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
function modelUsageToDict(usage) {
|
|
325
|
+
const dict = {};
|
|
326
|
+
if (usage.model) {
|
|
327
|
+
dict.model = usage.model;
|
|
328
|
+
}
|
|
329
|
+
if (usage.provider) {
|
|
330
|
+
dict.provider = usage.provider;
|
|
331
|
+
}
|
|
332
|
+
if (usage.inputTokens != null) {
|
|
333
|
+
dict.input_tokens = usage.inputTokens;
|
|
334
|
+
}
|
|
335
|
+
if (usage.outputTokens != null) {
|
|
336
|
+
dict.output_tokens = usage.outputTokens;
|
|
337
|
+
}
|
|
338
|
+
if (usage.toolsCalled != null && usage.toolsCalled.length > 0) {
|
|
339
|
+
dict.tools_called = usage.toolsCalled;
|
|
340
|
+
}
|
|
341
|
+
return dict;
|
|
342
|
+
}
|
|
343
|
+
var logger = getLogger("tool-call-context");
|
|
304
344
|
var toolCallStorage = new async_hooks.AsyncLocalStorage();
|
|
345
|
+
function generateToolCallId() {
|
|
346
|
+
return `tc_${crypto.randomUUID()}`;
|
|
347
|
+
}
|
|
305
348
|
function getToolCallHeader() {
|
|
306
349
|
const data = toolCallStorage.getStore();
|
|
307
350
|
if (!data) return void 0;
|
|
308
|
-
|
|
351
|
+
const full = Buffer.from(JSON.stringify(data)).toString("base64");
|
|
352
|
+
if (full.length <= 16 * 1024) return full;
|
|
353
|
+
logger.warning("Tool call header exceeds 16KB, omitting arguments", {
|
|
354
|
+
toolCallId: data.tool_call_id,
|
|
355
|
+
toolName: data.tool_name,
|
|
356
|
+
fullSizeBytes: full.length
|
|
357
|
+
});
|
|
358
|
+
const slim = {
|
|
359
|
+
tool_call_id: data.tool_call_id,
|
|
360
|
+
tool_name: data.tool_name
|
|
361
|
+
};
|
|
362
|
+
return Buffer.from(JSON.stringify(slim)).toString("base64");
|
|
363
|
+
}
|
|
364
|
+
function runWithToolCall(toolCallId, name, args, fn) {
|
|
365
|
+
return toolCallStorage.run(
|
|
366
|
+
{
|
|
367
|
+
tool_call_id: toolCallId,
|
|
368
|
+
tool_name: name,
|
|
369
|
+
tool_call_arguments: JSON.stringify(args)
|
|
370
|
+
},
|
|
371
|
+
fn
|
|
372
|
+
);
|
|
309
373
|
}
|
|
310
374
|
|
|
311
375
|
// src/http.ts
|
|
312
|
-
var
|
|
376
|
+
var logger2 = getLogger("http");
|
|
313
377
|
var DEFAULT_BASE_URL = "https://api.natural.co";
|
|
314
378
|
var DEFAULT_TIMEOUT = 3e4;
|
|
379
|
+
var API_KEY_PREFIX_REGEX = /^sk_ntl_(dev|sandbox|prod)_/;
|
|
380
|
+
function parseApiKeyEnv(key) {
|
|
381
|
+
const match = API_KEY_PREFIX_REGEX.exec(key);
|
|
382
|
+
if (match) {
|
|
383
|
+
return match[1];
|
|
384
|
+
}
|
|
385
|
+
const preview = key.length > 16 ? `${key.slice(0, 16)}...` : key;
|
|
386
|
+
throw new InvalidRequestError(
|
|
387
|
+
`Invalid API key prefix. Expected a key starting with 'sk_ntl_dev_', 'sk_ntl_sandbox_', or 'sk_ntl_prod_'. Got: '${preview}'`
|
|
388
|
+
);
|
|
389
|
+
}
|
|
390
|
+
function validateBaseUrl(baseUrl) {
|
|
391
|
+
let url;
|
|
392
|
+
try {
|
|
393
|
+
url = new URL(baseUrl);
|
|
394
|
+
} catch {
|
|
395
|
+
throw new InvalidRequestError(`Invalid baseUrl: '${baseUrl}'. Must be a valid absolute URL.`);
|
|
396
|
+
}
|
|
397
|
+
if (url.protocol === "https:") {
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
400
|
+
const host = url.hostname;
|
|
401
|
+
if (host === "localhost" || host === "127.0.0.1" || host === "::1" || host === "[::1]") {
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
const allowHttp = process.env["NATURAL_ALLOW_HTTP"];
|
|
405
|
+
if (allowHttp && allowHttp !== "0" && allowHttp.toLowerCase() !== "false") {
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
throw new InvalidRequestError(
|
|
409
|
+
`baseUrl must use HTTPS (got '${baseUrl}'). To allow plaintext HTTP for development, set NATURAL_ALLOW_HTTP=1 or use a localhost host.`
|
|
410
|
+
);
|
|
411
|
+
}
|
|
315
412
|
function hashString(str) {
|
|
316
413
|
let hash = 0;
|
|
317
414
|
for (let i = 0; i < str.length; i++) {
|
|
@@ -321,82 +418,167 @@ function hashString(str) {
|
|
|
321
418
|
}
|
|
322
419
|
return Math.abs(hash).toString(16).slice(0, 16);
|
|
323
420
|
}
|
|
324
|
-
var
|
|
421
|
+
var SAFE_METHODS = /* @__PURE__ */ new Set(["GET", "HEAD"]);
|
|
422
|
+
function hasIdempotencyKey(headers) {
|
|
423
|
+
return Object.entries(headers).some(
|
|
424
|
+
([k, v]) => k.toLowerCase() === "idempotency-key" && v !== ""
|
|
425
|
+
);
|
|
426
|
+
}
|
|
427
|
+
function shouldRetry(method, headers, error, attempt, maxRetries) {
|
|
428
|
+
if (attempt >= maxRetries) return false;
|
|
429
|
+
if (error instanceof AuthenticationError) return false;
|
|
430
|
+
if (error instanceof InvalidRequestError) return false;
|
|
431
|
+
if (!(error instanceof RateLimitError) && error instanceof NaturalError && error.statusCode && error.statusCode >= 400 && error.statusCode < 500) {
|
|
432
|
+
return false;
|
|
433
|
+
}
|
|
434
|
+
const isRetryable = error instanceof ServerError || error instanceof RateLimitError || error instanceof NaturalError && !(error instanceof AuthenticationError);
|
|
435
|
+
if (!isRetryable) return false;
|
|
436
|
+
if (SAFE_METHODS.has(method.toUpperCase())) return true;
|
|
437
|
+
return hasIdempotencyKey(headers);
|
|
438
|
+
}
|
|
439
|
+
var MAX_RETRY_AFTER_MS = 6e4;
|
|
440
|
+
function calculateRetryDelay(attempt, retryAfterSeconds) {
|
|
441
|
+
if (retryAfterSeconds != null && Number.isFinite(retryAfterSeconds) && retryAfterSeconds > 0) {
|
|
442
|
+
return Math.min(retryAfterSeconds * 1e3, MAX_RETRY_AFTER_MS);
|
|
443
|
+
}
|
|
444
|
+
const jitter = 1 + Math.random() * 0.25;
|
|
445
|
+
return Math.min(500 * Math.pow(2, attempt) * jitter, 5e3);
|
|
446
|
+
}
|
|
447
|
+
var HTTPClient = class _HTTPClient {
|
|
325
448
|
apiKey;
|
|
326
449
|
baseUrl;
|
|
327
450
|
timeout;
|
|
451
|
+
maxRetries;
|
|
328
452
|
jwtCache = /* @__PURE__ */ new Map();
|
|
453
|
+
agentConfig;
|
|
454
|
+
_defaultModelUsage;
|
|
455
|
+
_configResolved = false;
|
|
456
|
+
_configAttempted = false;
|
|
457
|
+
_registerConfigPromise = null;
|
|
458
|
+
_nextRetryAt = 0;
|
|
459
|
+
_retryBackoffMs = 1e3;
|
|
460
|
+
static MAX_RETRY_BACKOFF_MS = 5 * 60 * 1e3;
|
|
329
461
|
constructor(options = {}) {
|
|
330
462
|
this.apiKey = options.apiKey ?? process.env["NATURAL_API_KEY"] ?? "";
|
|
331
463
|
this.baseUrl = (options.baseUrl ?? process.env["NATURAL_SERVER_URL"] ?? DEFAULT_BASE_URL).replace(/\/$/, "");
|
|
464
|
+
validateBaseUrl(this.baseUrl);
|
|
465
|
+
if (this.apiKey) {
|
|
466
|
+
parseApiKeyEnv(this.apiKey);
|
|
467
|
+
}
|
|
332
468
|
this.timeout = options.timeout ?? DEFAULT_TIMEOUT;
|
|
469
|
+
this.agentConfig = options.agentConfig ? {
|
|
470
|
+
systemPrompt: options.agentConfig.systemPrompt,
|
|
471
|
+
toolsAvailable: [...options.agentConfig.toolsAvailable]
|
|
472
|
+
} : void 0;
|
|
473
|
+
if (options.defaultModelUsage) {
|
|
474
|
+
this._defaultModelUsage = {
|
|
475
|
+
model: options.defaultModelUsage.model,
|
|
476
|
+
provider: options.defaultModelUsage.provider
|
|
477
|
+
};
|
|
478
|
+
}
|
|
479
|
+
const maxRetries = options.maxRetries ?? 2;
|
|
480
|
+
if (!Number.isInteger(maxRetries) || maxRetries < 0) {
|
|
481
|
+
throw new InvalidRequestError("maxRetries must be a non-negative integer");
|
|
482
|
+
}
|
|
483
|
+
this.maxRetries = maxRetries;
|
|
333
484
|
}
|
|
334
485
|
/**
|
|
335
486
|
* Get a cached JWT or exchange API key for a new one.
|
|
487
|
+
* Retries on transient failures (5xx, network) but not on 401.
|
|
336
488
|
*/
|
|
337
489
|
async getJwt() {
|
|
338
490
|
if (!this.apiKey) {
|
|
339
491
|
throw new AuthenticationError();
|
|
340
492
|
}
|
|
341
|
-
if (!this.apiKey.startsWith("sk_ntl_")) {
|
|
342
|
-
return this.apiKey;
|
|
343
|
-
}
|
|
344
493
|
const cacheKey = hashString(this.apiKey);
|
|
345
494
|
const cached = this.jwtCache.get(cacheKey);
|
|
346
495
|
if (cached && Date.now() < cached.expiresAt) {
|
|
347
496
|
return cached.token;
|
|
348
497
|
}
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
clearTimeout(timeoutId);
|
|
362
|
-
if (!response.ok) {
|
|
363
|
-
const authError = new AuthenticationError(
|
|
364
|
-
`Authentication failed (status=${response.status})`
|
|
365
|
-
);
|
|
366
|
-
logError(logger, "JWT exchange failed", {
|
|
367
|
-
error: authError,
|
|
368
|
-
statusCode: response.status,
|
|
369
|
-
path: "/auth/api/token"
|
|
498
|
+
logger2.debug("Exchanging API key for JWT", { path: "/auth/api/token" });
|
|
499
|
+
for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
|
|
500
|
+
const controller = new AbortController();
|
|
501
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
502
|
+
try {
|
|
503
|
+
const response = await fetch(`${this.baseUrl}/auth/api/token`, {
|
|
504
|
+
method: "POST",
|
|
505
|
+
headers: {
|
|
506
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
507
|
+
"Content-Type": "application/json"
|
|
508
|
+
},
|
|
509
|
+
signal: controller.signal
|
|
370
510
|
});
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
511
|
+
clearTimeout(timeoutId);
|
|
512
|
+
if (response.status === 429) {
|
|
513
|
+
const retryAfter = response.headers.get("Retry-After");
|
|
514
|
+
const rateError = new RateLimitError(
|
|
515
|
+
"JWT exchange rate limited",
|
|
516
|
+
retryAfter ? parseInt(retryAfter, 10) : void 0
|
|
517
|
+
);
|
|
518
|
+
throw rateError;
|
|
519
|
+
}
|
|
520
|
+
if (response.status >= 400 && response.status < 500) {
|
|
521
|
+
const authError = new AuthenticationError(
|
|
522
|
+
`Authentication failed (status=${response.status})`
|
|
523
|
+
);
|
|
524
|
+
logError(logger2, "JWT exchange failed", {
|
|
525
|
+
error: authError,
|
|
526
|
+
statusCode: response.status,
|
|
527
|
+
path: "/auth/api/token"
|
|
528
|
+
});
|
|
529
|
+
throw authError;
|
|
530
|
+
}
|
|
531
|
+
if (response.status >= 500) {
|
|
532
|
+
const serverError = new ServerError(`JWT exchange failed (status=${response.status})`);
|
|
533
|
+
logError(logger2, "JWT exchange server error", {
|
|
534
|
+
error: serverError,
|
|
535
|
+
statusCode: response.status,
|
|
536
|
+
path: "/auth/api/token"
|
|
537
|
+
});
|
|
538
|
+
throw serverError;
|
|
539
|
+
}
|
|
540
|
+
const data = await response.json();
|
|
541
|
+
const expiresIn = data.expiresIn ?? 900;
|
|
542
|
+
const expiresAt = Date.now() + (expiresIn - 30) * 1e3;
|
|
543
|
+
this.jwtCache.set(cacheKey, { token: data.accessToken, expiresAt });
|
|
544
|
+
return data.accessToken;
|
|
545
|
+
} catch (error) {
|
|
546
|
+
clearTimeout(timeoutId);
|
|
547
|
+
if (error instanceof AuthenticationError) {
|
|
548
|
+
throw error;
|
|
549
|
+
}
|
|
550
|
+
let sdkError;
|
|
551
|
+
if (error instanceof NaturalError) {
|
|
552
|
+
sdkError = error;
|
|
553
|
+
} else if (error instanceof Error && error.name === "AbortError") {
|
|
554
|
+
sdkError = new NaturalError("Request timed out during authentication");
|
|
555
|
+
} else {
|
|
556
|
+
sdkError = new NaturalError(
|
|
557
|
+
`Network error during authentication: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
558
|
+
);
|
|
559
|
+
}
|
|
560
|
+
if (attempt < this.maxRetries) {
|
|
561
|
+
const retryAfter = sdkError instanceof RateLimitError ? sdkError.retryAfter : void 0;
|
|
562
|
+
const delay = calculateRetryDelay(attempt, retryAfter);
|
|
563
|
+
const reason = sdkError instanceof RateLimitError ? "429 rate limited" : sdkError instanceof ServerError ? `status ${sdkError.statusCode}` : "network error";
|
|
564
|
+
logger2.warning(`Retrying JWT exchange (attempt ${attempt + 1}/${this.maxRetries})`, {
|
|
565
|
+
path: "/auth/api/token",
|
|
566
|
+
attempt: attempt + 1,
|
|
567
|
+
maxRetries: this.maxRetries,
|
|
568
|
+
delayMs: Math.round(delay),
|
|
569
|
+
reason
|
|
570
|
+
});
|
|
571
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
572
|
+
continue;
|
|
573
|
+
}
|
|
574
|
+
logError(logger2, "JWT exchange failed after retries", {
|
|
575
|
+
error: sdkError,
|
|
387
576
|
path: "/auth/api/token"
|
|
388
577
|
});
|
|
389
|
-
throw
|
|
578
|
+
throw sdkError;
|
|
390
579
|
}
|
|
391
|
-
const networkError = new NaturalError(
|
|
392
|
-
`Network error during authentication: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
393
|
-
);
|
|
394
|
-
logError(logger, "JWT exchange network error", {
|
|
395
|
-
error: networkError,
|
|
396
|
-
path: "/auth/api/token"
|
|
397
|
-
});
|
|
398
|
-
throw networkError;
|
|
399
580
|
}
|
|
581
|
+
throw new NaturalError("Unexpected retry exhaustion during JWT exchange");
|
|
400
582
|
}
|
|
401
583
|
/**
|
|
402
584
|
* Build URL with query parameters.
|
|
@@ -418,7 +600,7 @@ var HTTPClient = class {
|
|
|
418
600
|
async handleResponse(response, method, path, durationMs) {
|
|
419
601
|
if (response.status === 401) {
|
|
420
602
|
const authError = new AuthenticationError();
|
|
421
|
-
logApiCall(
|
|
603
|
+
logApiCall(logger2, method, path, {
|
|
422
604
|
statusCode: response.status,
|
|
423
605
|
durationMs,
|
|
424
606
|
error: authError
|
|
@@ -431,7 +613,7 @@ var HTTPClient = class {
|
|
|
431
613
|
"Rate limit exceeded",
|
|
432
614
|
retryAfter ? parseInt(retryAfter, 10) : void 0
|
|
433
615
|
);
|
|
434
|
-
|
|
616
|
+
logger2.warning(`Rate limited: ${method} ${path}`, {
|
|
435
617
|
method,
|
|
436
618
|
path,
|
|
437
619
|
statusCode: response.status,
|
|
@@ -442,7 +624,7 @@ var HTTPClient = class {
|
|
|
442
624
|
}
|
|
443
625
|
if (response.status >= 500) {
|
|
444
626
|
const serverError = new ServerError(`Server error: ${response.status}`);
|
|
445
|
-
logApiCall(
|
|
627
|
+
logApiCall(logger2, method, path, {
|
|
446
628
|
statusCode: response.status,
|
|
447
629
|
durationMs,
|
|
448
630
|
error: serverError
|
|
@@ -455,17 +637,18 @@ var HTTPClient = class {
|
|
|
455
637
|
data = text ? JSON.parse(text) : {};
|
|
456
638
|
} catch {
|
|
457
639
|
if (response.status >= 400) {
|
|
458
|
-
const parseError = new NaturalError(
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
640
|
+
const parseError = new NaturalError(
|
|
641
|
+
`Request failed: ${response.status} (non-JSON response)`,
|
|
642
|
+
{ statusCode: response.status, code: "parse_error" }
|
|
643
|
+
);
|
|
644
|
+
logApiCall(logger2, method, path, {
|
|
462
645
|
statusCode: response.status,
|
|
463
646
|
durationMs,
|
|
464
647
|
error: parseError
|
|
465
648
|
});
|
|
466
649
|
throw parseError;
|
|
467
650
|
}
|
|
468
|
-
logApiCall(
|
|
651
|
+
logApiCall(logger2, method, path, { statusCode: response.status, durationMs });
|
|
469
652
|
return {};
|
|
470
653
|
}
|
|
471
654
|
if (response.status >= 400) {
|
|
@@ -478,69 +661,185 @@ var HTTPClient = class {
|
|
|
478
661
|
`${errorMessage} (status=${response.status})`,
|
|
479
662
|
errorCode
|
|
480
663
|
);
|
|
481
|
-
logApiCall(
|
|
664
|
+
logApiCall(logger2, method, path, {
|
|
482
665
|
statusCode: response.status,
|
|
483
666
|
durationMs,
|
|
484
667
|
error: requestError
|
|
485
668
|
});
|
|
486
669
|
throw requestError;
|
|
487
670
|
}
|
|
488
|
-
logApiCall(
|
|
671
|
+
logApiCall(logger2, method, path, { statusCode: response.status, durationMs });
|
|
489
672
|
return data;
|
|
490
673
|
}
|
|
491
674
|
/**
|
|
492
|
-
*
|
|
675
|
+
* Register agent config with the observability service.
|
|
676
|
+
*
|
|
677
|
+
* Called lazily before the first request. Errors are caught and logged,
|
|
678
|
+
* never thrown.
|
|
493
679
|
*/
|
|
494
|
-
async
|
|
495
|
-
|
|
496
|
-
const url = this.buildUrl(path, options?.params);
|
|
497
|
-
const controller = new AbortController();
|
|
498
|
-
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
499
|
-
logger.debug(`API request: ${method} ${path}`, {
|
|
500
|
-
method,
|
|
501
|
-
path,
|
|
502
|
-
hasBody: !!options?.body
|
|
503
|
-
});
|
|
504
|
-
const startTime = Date.now();
|
|
680
|
+
async registerConfig() {
|
|
681
|
+
if (!this.agentConfig) return;
|
|
505
682
|
try {
|
|
506
|
-
const
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
683
|
+
const jwt = await this.getJwt();
|
|
684
|
+
const hash = configHash(this.agentConfig);
|
|
685
|
+
const body = {
|
|
686
|
+
config_hash: hash,
|
|
687
|
+
...agentConfigToDict(this.agentConfig)
|
|
510
688
|
};
|
|
511
|
-
const
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
689
|
+
const controller = new AbortController();
|
|
690
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
691
|
+
try {
|
|
692
|
+
const response = await fetch(`${this.baseUrl}/agents/config`, {
|
|
693
|
+
method: "POST",
|
|
694
|
+
headers: {
|
|
695
|
+
Authorization: `Bearer ${jwt}`,
|
|
696
|
+
"Content-Type": "application/json",
|
|
697
|
+
"User-Agent": `naturalpay-ts/${VERSION}`
|
|
698
|
+
},
|
|
699
|
+
body: JSON.stringify(body),
|
|
700
|
+
signal: controller.signal
|
|
701
|
+
});
|
|
702
|
+
if (response.ok) {
|
|
703
|
+
this._configResolved = true;
|
|
704
|
+
} else if (response.status === 429 || response.status === 408 || response.status >= 500) {
|
|
705
|
+
this._nextRetryAt = Date.now() + this._retryBackoffMs;
|
|
706
|
+
this._retryBackoffMs = Math.min(
|
|
707
|
+
this._retryBackoffMs * 2,
|
|
708
|
+
_HTTPClient.MAX_RETRY_BACKOFF_MS
|
|
709
|
+
);
|
|
710
|
+
logger2.warning("Agent config registration failed (transient)", {
|
|
711
|
+
statusCode: response.status
|
|
712
|
+
});
|
|
713
|
+
} else {
|
|
714
|
+
this._configResolved = true;
|
|
715
|
+
logger2.warning("Agent config registration failed (permanent)", {
|
|
716
|
+
statusCode: response.status
|
|
717
|
+
});
|
|
718
|
+
}
|
|
719
|
+
} finally {
|
|
720
|
+
clearTimeout(timeoutId);
|
|
517
721
|
}
|
|
518
|
-
const response = await fetch(url, {
|
|
519
|
-
method,
|
|
520
|
-
headers,
|
|
521
|
-
body: options?.body ? JSON.stringify(options.body) : void 0,
|
|
522
|
-
signal: controller.signal
|
|
523
|
-
});
|
|
524
|
-
clearTimeout(timeoutId);
|
|
525
|
-
const durationMs = Date.now() - startTime;
|
|
526
|
-
return this.handleResponse(response, method, path, durationMs);
|
|
527
722
|
} catch (error) {
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
723
|
+
this._nextRetryAt = Date.now() + this._retryBackoffMs;
|
|
724
|
+
this._retryBackoffMs = Math.min(this._retryBackoffMs * 2, _HTTPClient.MAX_RETRY_BACKOFF_MS);
|
|
725
|
+
logger2.warning("Failed to register agent config", {
|
|
726
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
727
|
+
});
|
|
728
|
+
} finally {
|
|
729
|
+
this._configAttempted = true;
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
/**
|
|
733
|
+
* Add agent config hash and model usage headers if configured.
|
|
734
|
+
*/
|
|
735
|
+
injectModelContextHeaders(headers, modelUsage) {
|
|
736
|
+
if (this.agentConfig) {
|
|
737
|
+
headers["X-Model-Config-Hash"] = configHash(this.agentConfig);
|
|
738
|
+
}
|
|
739
|
+
const effectiveUsage = (() => {
|
|
740
|
+
if (!modelUsage && !this._defaultModelUsage) return void 0;
|
|
741
|
+
return {
|
|
742
|
+
model: modelUsage?.model ?? this._defaultModelUsage?.model,
|
|
743
|
+
provider: modelUsage?.provider ?? this._defaultModelUsage?.provider,
|
|
744
|
+
inputTokens: modelUsage?.inputTokens,
|
|
745
|
+
outputTokens: modelUsage?.outputTokens,
|
|
746
|
+
toolsCalled: modelUsage?.toolsCalled
|
|
747
|
+
};
|
|
748
|
+
})();
|
|
749
|
+
if (effectiveUsage) {
|
|
750
|
+
const usageDict = modelUsageToDict(effectiveUsage);
|
|
751
|
+
if (Object.keys(usageDict).length > 0) {
|
|
752
|
+
headers["X-Model-Usage"] = Buffer.from(JSON.stringify(usageDict)).toString("base64");
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
/**
|
|
757
|
+
* Make an authenticated request with automatic retries.
|
|
758
|
+
*/
|
|
759
|
+
async request(method, path, options) {
|
|
760
|
+
if (this.agentConfig && !this._configResolved) {
|
|
761
|
+
if (!this._registerConfigPromise) {
|
|
762
|
+
if (!this._configAttempted || Date.now() >= this._nextRetryAt) {
|
|
763
|
+
this._registerConfigPromise = this.registerConfig().catch(() => {
|
|
764
|
+
}).finally(() => {
|
|
765
|
+
this._registerConfigPromise = null;
|
|
766
|
+
});
|
|
767
|
+
}
|
|
532
768
|
}
|
|
533
|
-
if (
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
769
|
+
if (!this._configAttempted) {
|
|
770
|
+
await this._registerConfigPromise;
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
const jwt = await this.getJwt();
|
|
774
|
+
const url = this.buildUrl(path, options?.params);
|
|
775
|
+
const mergedHeaders = {
|
|
776
|
+
Authorization: `Bearer ${jwt}`,
|
|
777
|
+
"Content-Type": "application/json",
|
|
778
|
+
"User-Agent": `naturalpay-ts/${VERSION}`
|
|
779
|
+
};
|
|
780
|
+
const toolCallHeader = getToolCallHeader();
|
|
781
|
+
if (toolCallHeader) {
|
|
782
|
+
mergedHeaders["X-Tool-Call"] = toolCallHeader;
|
|
783
|
+
}
|
|
784
|
+
this.injectModelContextHeaders(mergedHeaders, options?.modelUsage);
|
|
785
|
+
if (options?.headers) {
|
|
786
|
+
Object.assign(mergedHeaders, options.headers);
|
|
787
|
+
}
|
|
788
|
+
const bodyStr = options?.body ? JSON.stringify(options.body) : void 0;
|
|
789
|
+
for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
|
|
790
|
+
const controller = new AbortController();
|
|
791
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
792
|
+
const startTime = Date.now();
|
|
793
|
+
if (attempt === 0) {
|
|
794
|
+
logger2.debug(`API request: ${method} ${path}`, {
|
|
795
|
+
method,
|
|
796
|
+
path,
|
|
797
|
+
hasBody: !!options?.body
|
|
798
|
+
});
|
|
799
|
+
}
|
|
800
|
+
try {
|
|
801
|
+
const response = await fetch(url, {
|
|
802
|
+
method,
|
|
803
|
+
headers: mergedHeaders,
|
|
804
|
+
body: bodyStr,
|
|
805
|
+
signal: controller.signal
|
|
806
|
+
});
|
|
807
|
+
clearTimeout(timeoutId);
|
|
808
|
+
const durationMs = Date.now() - startTime;
|
|
809
|
+
return await this.handleResponse(response, method, path, durationMs);
|
|
810
|
+
} catch (error) {
|
|
811
|
+
clearTimeout(timeoutId);
|
|
812
|
+
const durationMs = Date.now() - startTime;
|
|
813
|
+
let sdkError;
|
|
814
|
+
if (error instanceof NaturalError) {
|
|
815
|
+
sdkError = error;
|
|
816
|
+
} else if (error instanceof Error && error.name === "AbortError") {
|
|
817
|
+
sdkError = new NaturalError("Request timed out");
|
|
818
|
+
logApiCall(logger2, method, path, { durationMs, error: sdkError });
|
|
819
|
+
} else {
|
|
820
|
+
sdkError = new NaturalError(
|
|
821
|
+
`Network error: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
822
|
+
);
|
|
823
|
+
logApiCall(logger2, method, path, { durationMs, error: sdkError });
|
|
824
|
+
}
|
|
825
|
+
if (shouldRetry(method, mergedHeaders, sdkError, attempt, this.maxRetries)) {
|
|
826
|
+
const retryAfter = sdkError instanceof RateLimitError ? sdkError.retryAfter : void 0;
|
|
827
|
+
const delay = calculateRetryDelay(attempt, retryAfter);
|
|
828
|
+
logger2.warning(`Retrying ${method} ${path} (attempt ${attempt + 1}/${this.maxRetries})`, {
|
|
829
|
+
method,
|
|
830
|
+
path,
|
|
831
|
+
attempt: attempt + 1,
|
|
832
|
+
maxRetries: this.maxRetries,
|
|
833
|
+
delayMs: Math.round(delay),
|
|
834
|
+
reason: sdkError instanceof ServerError ? `status ${sdkError.statusCode}` : sdkError instanceof RateLimitError ? "429 rate limited" : "network error"
|
|
835
|
+
});
|
|
836
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
837
|
+
continue;
|
|
838
|
+
}
|
|
839
|
+
throw sdkError;
|
|
537
840
|
}
|
|
538
|
-
const networkError = new NaturalError(
|
|
539
|
-
`Network error: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
540
|
-
);
|
|
541
|
-
logApiCall(logger, method, path, { durationMs, error: networkError });
|
|
542
|
-
throw networkError;
|
|
543
841
|
}
|
|
842
|
+
throw new NaturalError("Unexpected retry exhaustion");
|
|
544
843
|
}
|
|
545
844
|
async get(path, options) {
|
|
546
845
|
return this.request("GET", path, options);
|
|
@@ -563,6 +862,9 @@ var BaseResource = class {
|
|
|
563
862
|
this.http = http;
|
|
564
863
|
}
|
|
565
864
|
};
|
|
865
|
+
function sanitizeHeaderValue(value) {
|
|
866
|
+
return value.replace(/[\x00-\x1f\x7f]/g, "");
|
|
867
|
+
}
|
|
566
868
|
|
|
567
869
|
// src/resources/transactions.ts
|
|
568
870
|
function unwrapTransactionResource(resource) {
|
|
@@ -607,9 +909,15 @@ var TransactionsResource = class extends BaseResource {
|
|
|
607
909
|
*/
|
|
608
910
|
async get(transactionId, params) {
|
|
609
911
|
const headers = {};
|
|
912
|
+
if (params?.agentId) {
|
|
913
|
+
headers["X-Agent-ID"] = params.agentId;
|
|
914
|
+
}
|
|
610
915
|
if (params?.instanceId) {
|
|
611
916
|
headers["X-Instance-ID"] = params.instanceId;
|
|
612
917
|
}
|
|
918
|
+
if (params?.traceId) {
|
|
919
|
+
headers["X-Trace-ID"] = sanitizeHeaderValue(params.traceId);
|
|
920
|
+
}
|
|
613
921
|
const queryParams = {};
|
|
614
922
|
if (params?.customerPartyId) {
|
|
615
923
|
queryParams["partyId"] = params.customerPartyId;
|
|
@@ -640,6 +948,9 @@ var TransactionsResource = class extends BaseResource {
|
|
|
640
948
|
if (params?.instanceId) {
|
|
641
949
|
headers["X-Instance-ID"] = params.instanceId;
|
|
642
950
|
}
|
|
951
|
+
if (params?.traceId) {
|
|
952
|
+
headers["X-Trace-ID"] = sanitizeHeaderValue(params.traceId);
|
|
953
|
+
}
|
|
643
954
|
const queryParams = {
|
|
644
955
|
limit: params?.limit ?? 50,
|
|
645
956
|
cursor: params?.cursor,
|
|
@@ -732,6 +1043,9 @@ var PaymentsResource = class extends BaseResource {
|
|
|
732
1043
|
if (params.instanceId) {
|
|
733
1044
|
headers["X-Instance-ID"] = params.instanceId;
|
|
734
1045
|
}
|
|
1046
|
+
if (params.traceId) {
|
|
1047
|
+
headers["X-Trace-ID"] = sanitizeHeaderValue(params.traceId);
|
|
1048
|
+
}
|
|
735
1049
|
const response = await this.http.post("/payments", {
|
|
736
1050
|
body,
|
|
737
1051
|
headers
|
|
@@ -814,9 +1128,15 @@ var WalletResource = class extends BaseResource {
|
|
|
814
1128
|
*/
|
|
815
1129
|
async balance(options) {
|
|
816
1130
|
const headers = {};
|
|
1131
|
+
if (options?.agentId) {
|
|
1132
|
+
headers["X-Agent-ID"] = options.agentId;
|
|
1133
|
+
}
|
|
817
1134
|
if (options?.instanceId) {
|
|
818
1135
|
headers["X-Instance-ID"] = options.instanceId;
|
|
819
1136
|
}
|
|
1137
|
+
if (options?.traceId) {
|
|
1138
|
+
headers["X-Trace-ID"] = sanitizeHeaderValue(options.traceId);
|
|
1139
|
+
}
|
|
820
1140
|
const params = {};
|
|
821
1141
|
if (options?.customerPartyId) {
|
|
822
1142
|
params["partyId"] = options.customerPartyId;
|
|
@@ -844,9 +1164,21 @@ var WalletResource = class extends BaseResource {
|
|
|
844
1164
|
};
|
|
845
1165
|
if (params.description) attributes["description"] = params.description;
|
|
846
1166
|
const body = { data: { attributes } };
|
|
1167
|
+
const headers = {
|
|
1168
|
+
"Idempotency-Key": params.idempotencyKey
|
|
1169
|
+
};
|
|
1170
|
+
if (params.agentId) {
|
|
1171
|
+
headers["X-Agent-ID"] = params.agentId;
|
|
1172
|
+
}
|
|
1173
|
+
if (params.instanceId) {
|
|
1174
|
+
headers["X-Instance-ID"] = params.instanceId;
|
|
1175
|
+
}
|
|
1176
|
+
if (params.traceId) {
|
|
1177
|
+
headers["X-Trace-ID"] = sanitizeHeaderValue(params.traceId);
|
|
1178
|
+
}
|
|
847
1179
|
const response = await this.http.post("/wallet/withdraw", {
|
|
848
1180
|
body,
|
|
849
|
-
headers
|
|
1181
|
+
headers
|
|
850
1182
|
});
|
|
851
1183
|
return unwrapWithdrawal(response);
|
|
852
1184
|
}
|
|
@@ -901,9 +1233,15 @@ var AgentsResource = class extends BaseResource {
|
|
|
901
1233
|
*/
|
|
902
1234
|
async list(params) {
|
|
903
1235
|
const headers = {};
|
|
1236
|
+
if (params?.agentId) {
|
|
1237
|
+
headers["X-Agent-ID"] = params.agentId;
|
|
1238
|
+
}
|
|
904
1239
|
if (params?.instanceId) {
|
|
905
1240
|
headers["X-Instance-ID"] = params.instanceId;
|
|
906
1241
|
}
|
|
1242
|
+
if (params?.traceId) {
|
|
1243
|
+
headers["X-Trace-ID"] = sanitizeHeaderValue(params.traceId);
|
|
1244
|
+
}
|
|
907
1245
|
const queryParams = {
|
|
908
1246
|
status: params?.status,
|
|
909
1247
|
limit: params?.limit ?? 50,
|
|
@@ -923,9 +1261,15 @@ var AgentsResource = class extends BaseResource {
|
|
|
923
1261
|
*/
|
|
924
1262
|
async get(agentId, options) {
|
|
925
1263
|
const headers = {};
|
|
1264
|
+
if (options?.agentId) {
|
|
1265
|
+
headers["X-Agent-ID"] = options.agentId;
|
|
1266
|
+
}
|
|
926
1267
|
if (options?.instanceId) {
|
|
927
1268
|
headers["X-Instance-ID"] = options.instanceId;
|
|
928
1269
|
}
|
|
1270
|
+
if (options?.traceId) {
|
|
1271
|
+
headers["X-Trace-ID"] = sanitizeHeaderValue(options.traceId);
|
|
1272
|
+
}
|
|
929
1273
|
const response = await this.http.get(`/agents/${agentId}`, {
|
|
930
1274
|
headers: Object.keys(headers).length > 0 ? headers : void 0
|
|
931
1275
|
});
|
|
@@ -952,9 +1296,15 @@ var AgentsResource = class extends BaseResource {
|
|
|
952
1296
|
if (params.idempotencyKey) {
|
|
953
1297
|
headers["Idempotency-Key"] = params.idempotencyKey;
|
|
954
1298
|
}
|
|
1299
|
+
if (params.agentId) {
|
|
1300
|
+
headers["X-Agent-ID"] = params.agentId;
|
|
1301
|
+
}
|
|
955
1302
|
if (params.instanceId) {
|
|
956
1303
|
headers["X-Instance-ID"] = params.instanceId;
|
|
957
1304
|
}
|
|
1305
|
+
if (params.traceId) {
|
|
1306
|
+
headers["X-Trace-ID"] = sanitizeHeaderValue(params.traceId);
|
|
1307
|
+
}
|
|
958
1308
|
const response = await this.http.post("/agents", {
|
|
959
1309
|
body,
|
|
960
1310
|
headers: Object.keys(headers).length > 0 ? headers : void 0
|
|
@@ -978,9 +1328,15 @@ var AgentsResource = class extends BaseResource {
|
|
|
978
1328
|
if (params.idempotencyKey) {
|
|
979
1329
|
headers["Idempotency-Key"] = params.idempotencyKey;
|
|
980
1330
|
}
|
|
1331
|
+
if (params.agentId) {
|
|
1332
|
+
headers["X-Agent-ID"] = params.agentId;
|
|
1333
|
+
}
|
|
981
1334
|
if (params.instanceId) {
|
|
982
1335
|
headers["X-Instance-ID"] = params.instanceId;
|
|
983
1336
|
}
|
|
1337
|
+
if (params.traceId) {
|
|
1338
|
+
headers["X-Trace-ID"] = sanitizeHeaderValue(params.traceId);
|
|
1339
|
+
}
|
|
984
1340
|
const response = await this.http.put(`/agents/${agentId}`, {
|
|
985
1341
|
body,
|
|
986
1342
|
headers: Object.keys(headers).length > 0 ? headers : void 0
|
|
@@ -995,9 +1351,15 @@ var AgentsResource = class extends BaseResource {
|
|
|
995
1351
|
*/
|
|
996
1352
|
async delete(agentId, options) {
|
|
997
1353
|
const headers = {};
|
|
1354
|
+
if (options?.agentId) {
|
|
1355
|
+
headers["X-Agent-ID"] = options.agentId;
|
|
1356
|
+
}
|
|
998
1357
|
if (options?.instanceId) {
|
|
999
1358
|
headers["X-Instance-ID"] = options.instanceId;
|
|
1000
1359
|
}
|
|
1360
|
+
if (options?.traceId) {
|
|
1361
|
+
headers["X-Trace-ID"] = sanitizeHeaderValue(options.traceId);
|
|
1362
|
+
}
|
|
1001
1363
|
await this.http.delete(`/agents/${agentId}`, {
|
|
1002
1364
|
headers: Object.keys(headers).length > 0 ? headers : void 0
|
|
1003
1365
|
});
|
|
@@ -1063,6 +1425,9 @@ var DelegationsResource = class extends BaseResource {
|
|
|
1063
1425
|
if (params?.instanceId) {
|
|
1064
1426
|
headers["X-Instance-ID"] = params.instanceId;
|
|
1065
1427
|
}
|
|
1428
|
+
if (params?.traceId) {
|
|
1429
|
+
headers["X-Trace-ID"] = sanitizeHeaderValue(params.traceId);
|
|
1430
|
+
}
|
|
1066
1431
|
const queryParams = {
|
|
1067
1432
|
delegationId: params?.delegationId,
|
|
1068
1433
|
agentId: params?.agentId,
|
|
@@ -1148,9 +1513,15 @@ var CustomersResource = class extends BaseResource {
|
|
|
1148
1513
|
*/
|
|
1149
1514
|
async list(params) {
|
|
1150
1515
|
const headers = {};
|
|
1516
|
+
if (params?.agentId) {
|
|
1517
|
+
headers["X-Agent-ID"] = params.agentId;
|
|
1518
|
+
}
|
|
1151
1519
|
if (params?.instanceId) {
|
|
1152
1520
|
headers["X-Instance-ID"] = params.instanceId;
|
|
1153
1521
|
}
|
|
1522
|
+
if (params?.traceId) {
|
|
1523
|
+
headers["X-Trace-ID"] = sanitizeHeaderValue(params.traceId);
|
|
1524
|
+
}
|
|
1154
1525
|
const queryParams = {
|
|
1155
1526
|
limit: params?.limit,
|
|
1156
1527
|
cursor: params?.cursor
|
|
@@ -1196,6 +1567,84 @@ var NaturalClient = class {
|
|
|
1196
1567
|
this.customers = new CustomersResource(this.http);
|
|
1197
1568
|
}
|
|
1198
1569
|
};
|
|
1570
|
+
var WHSEC_PREFIX = "whsec_";
|
|
1571
|
+
var DEFAULT_TOLERANCE_SECONDS = 300;
|
|
1572
|
+
function getHeader(headers, name) {
|
|
1573
|
+
if (typeof headers.get === "function") {
|
|
1574
|
+
return headers.get(name) ?? void 0;
|
|
1575
|
+
}
|
|
1576
|
+
const lower = name.toLowerCase();
|
|
1577
|
+
for (const key of Object.keys(headers)) {
|
|
1578
|
+
if (key.toLowerCase() === lower) {
|
|
1579
|
+
return headers[key];
|
|
1580
|
+
}
|
|
1581
|
+
}
|
|
1582
|
+
return void 0;
|
|
1583
|
+
}
|
|
1584
|
+
function verifyWebhookSignature(body, headers, secret, options) {
|
|
1585
|
+
const tolerance = options?.toleranceInSeconds ?? DEFAULT_TOLERANCE_SECONDS;
|
|
1586
|
+
if (!Number.isInteger(tolerance) || tolerance <= 0) {
|
|
1587
|
+
throw new WebhookVerificationError("toleranceInSeconds must be a positive integer");
|
|
1588
|
+
}
|
|
1589
|
+
const webhookId = getHeader(headers, "webhook-id");
|
|
1590
|
+
if (!webhookId) {
|
|
1591
|
+
throw new WebhookVerificationError("webhook-id header is missing");
|
|
1592
|
+
}
|
|
1593
|
+
const timestampStr = getHeader(headers, "webhook-timestamp");
|
|
1594
|
+
if (!timestampStr) {
|
|
1595
|
+
throw new WebhookVerificationError("webhook-timestamp header is missing");
|
|
1596
|
+
}
|
|
1597
|
+
const signatureHeader = getHeader(headers, "webhook-signature");
|
|
1598
|
+
if (!signatureHeader) {
|
|
1599
|
+
throw new WebhookVerificationError("webhook-signature header is missing");
|
|
1600
|
+
}
|
|
1601
|
+
if (!/^\d+$/.test(timestampStr)) {
|
|
1602
|
+
throw new WebhookVerificationError(
|
|
1603
|
+
`Invalid webhook-timestamp: '${timestampStr}' is not a valid integer`
|
|
1604
|
+
);
|
|
1605
|
+
}
|
|
1606
|
+
const timestamp = parseInt(timestampStr, 10);
|
|
1607
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
1608
|
+
if (Math.abs(now - timestamp) > tolerance) {
|
|
1609
|
+
throw new WebhookVerificationError(
|
|
1610
|
+
timestamp > now ? "Webhook timestamp is too far in the future" : "Webhook timestamp is too old"
|
|
1611
|
+
);
|
|
1612
|
+
}
|
|
1613
|
+
let keyBytes;
|
|
1614
|
+
const base64Part = secret.startsWith(WHSEC_PREFIX) ? secret.slice(WHSEC_PREFIX.length) : secret;
|
|
1615
|
+
if (!/^[A-Za-z0-9+/]*={0,2}$/.test(base64Part) || base64Part.length === 0) {
|
|
1616
|
+
throw new WebhookVerificationError("Invalid signing secret: could not base64-decode");
|
|
1617
|
+
}
|
|
1618
|
+
try {
|
|
1619
|
+
keyBytes = Buffer.from(base64Part, "base64");
|
|
1620
|
+
if (keyBytes.length === 0) {
|
|
1621
|
+
throw new Error("empty key");
|
|
1622
|
+
}
|
|
1623
|
+
} catch {
|
|
1624
|
+
throw new WebhookVerificationError("Invalid signing secret: could not base64-decode");
|
|
1625
|
+
}
|
|
1626
|
+
const bodyStr = typeof body === "string" ? body : Buffer.from(body).toString("utf-8");
|
|
1627
|
+
const signedContent = `${webhookId}.${timestampStr}.${bodyStr}`;
|
|
1628
|
+
const expectedSig = crypto.createHmac("sha256", keyBytes).update(signedContent).digest("base64");
|
|
1629
|
+
const candidates = signatureHeader.split(" ");
|
|
1630
|
+
for (const candidate of candidates) {
|
|
1631
|
+
if (!candidate.startsWith("v1,")) continue;
|
|
1632
|
+
const candidateSig = candidate.slice(3);
|
|
1633
|
+
const expectedBuf = Buffer.from(expectedSig, "utf-8");
|
|
1634
|
+
const candidateBuf = Buffer.from(candidateSig, "utf-8");
|
|
1635
|
+
if (expectedBuf.length !== candidateBuf.length) continue;
|
|
1636
|
+
if (crypto.timingSafeEqual(expectedBuf, candidateBuf)) {
|
|
1637
|
+
try {
|
|
1638
|
+
return JSON.parse(bodyStr);
|
|
1639
|
+
} catch {
|
|
1640
|
+
throw new WebhookVerificationError("Webhook signature is valid but body is not valid JSON");
|
|
1641
|
+
}
|
|
1642
|
+
}
|
|
1643
|
+
}
|
|
1644
|
+
throw new WebhookVerificationError(
|
|
1645
|
+
"Webhook signature does not match \u2014 ensure you are using the raw request body"
|
|
1646
|
+
);
|
|
1647
|
+
}
|
|
1199
1648
|
|
|
1200
1649
|
// src/types/transactions.ts
|
|
1201
1650
|
var TransactionTypeFilter = /* @__PURE__ */ ((TransactionTypeFilter2) => {
|
|
@@ -1216,14 +1665,24 @@ exports.RecipientNotFoundError = RecipientNotFoundError;
|
|
|
1216
1665
|
exports.ServerError = ServerError;
|
|
1217
1666
|
exports.TransactionTypeFilter = TransactionTypeFilter;
|
|
1218
1667
|
exports.VERSION = VERSION;
|
|
1668
|
+
exports.WebhookVerificationError = WebhookVerificationError;
|
|
1669
|
+
exports.agentConfigToDict = agentConfigToDict;
|
|
1219
1670
|
exports.bindContext = bindContext;
|
|
1220
1671
|
exports.clearContext = clearContext;
|
|
1672
|
+
exports.configHash = configHash;
|
|
1221
1673
|
exports.configureLogging = configureLogging;
|
|
1674
|
+
exports.generateToolCallId = generateToolCallId;
|
|
1222
1675
|
exports.getContext = getContext;
|
|
1223
1676
|
exports.getLogger = getLogger;
|
|
1677
|
+
exports.getToolCallHeader = getToolCallHeader;
|
|
1224
1678
|
exports.logApiCall = logApiCall;
|
|
1225
1679
|
exports.logError = logError;
|
|
1226
1680
|
exports.logToolCall = logToolCall;
|
|
1681
|
+
exports.modelUsageToDict = modelUsageToDict;
|
|
1682
|
+
exports.parseApiKeyEnv = parseApiKeyEnv;
|
|
1227
1683
|
exports.runWithContext = runWithContext;
|
|
1684
|
+
exports.runWithToolCall = runWithToolCall;
|
|
1685
|
+
exports.validateBaseUrl = validateBaseUrl;
|
|
1686
|
+
exports.verifyWebhookSignature = verifyWebhookSignature;
|
|
1228
1687
|
//# sourceMappingURL=index.cjs.map
|
|
1229
1688
|
//# sourceMappingURL=index.cjs.map
|