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