@naturalpay/sdk 0.1.3 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +618 -146
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +253 -17
- package/dist/index.d.ts +253 -17
- package/dist/index.js +610 -147
- 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,22 +301,79 @@ 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);
|
|
309
|
+
}
|
|
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;
|
|
302
340
|
}
|
|
341
|
+
return dict;
|
|
303
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;
|
|
315
379
|
var API_KEY_PREFIX_REGEX = /^sk_ntl_(dev|sandbox|prod)_/;
|
|
@@ -354,11 +418,46 @@ function hashString(str) {
|
|
|
354
418
|
}
|
|
355
419
|
return Math.abs(hash).toString(16).slice(0, 16);
|
|
356
420
|
}
|
|
357
|
-
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 {
|
|
358
448
|
apiKey;
|
|
359
449
|
baseUrl;
|
|
360
450
|
timeout;
|
|
451
|
+
maxRetries;
|
|
361
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;
|
|
362
461
|
constructor(options = {}) {
|
|
363
462
|
this.apiKey = options.apiKey ?? process.env["NATURAL_API_KEY"] ?? "";
|
|
364
463
|
this.baseUrl = (options.baseUrl ?? process.env["NATURAL_SERVER_URL"] ?? DEFAULT_BASE_URL).replace(/\/$/, "");
|
|
@@ -367,9 +466,25 @@ var HTTPClient = class {
|
|
|
367
466
|
parseApiKeyEnv(this.apiKey);
|
|
368
467
|
}
|
|
369
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;
|
|
370
484
|
}
|
|
371
485
|
/**
|
|
372
486
|
* Get a cached JWT or exchange API key for a new one.
|
|
487
|
+
* Retries on transient failures (5xx, network) but not on 401.
|
|
373
488
|
*/
|
|
374
489
|
async getJwt() {
|
|
375
490
|
if (!this.apiKey) {
|
|
@@ -380,57 +495,90 @@ var HTTPClient = class {
|
|
|
380
495
|
if (cached && Date.now() < cached.expiresAt) {
|
|
381
496
|
return cached.token;
|
|
382
497
|
}
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
clearTimeout(timeoutId);
|
|
396
|
-
if (!response.ok) {
|
|
397
|
-
const authError = new AuthenticationError(
|
|
398
|
-
`Authentication failed (status=${response.status})`
|
|
399
|
-
);
|
|
400
|
-
logError(logger, "JWT exchange failed", {
|
|
401
|
-
error: authError,
|
|
402
|
-
statusCode: response.status,
|
|
403
|
-
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
|
|
404
510
|
});
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
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,
|
|
421
576
|
path: "/auth/api/token"
|
|
422
577
|
});
|
|
423
|
-
throw
|
|
578
|
+
throw sdkError;
|
|
424
579
|
}
|
|
425
|
-
const networkError = new NaturalError(
|
|
426
|
-
`Network error during authentication: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
427
|
-
);
|
|
428
|
-
logError(logger, "JWT exchange network error", {
|
|
429
|
-
error: networkError,
|
|
430
|
-
path: "/auth/api/token"
|
|
431
|
-
});
|
|
432
|
-
throw networkError;
|
|
433
580
|
}
|
|
581
|
+
throw new NaturalError("Unexpected retry exhaustion during JWT exchange");
|
|
434
582
|
}
|
|
435
583
|
/**
|
|
436
584
|
* Build URL with query parameters.
|
|
@@ -452,7 +600,7 @@ var HTTPClient = class {
|
|
|
452
600
|
async handleResponse(response, method, path, durationMs) {
|
|
453
601
|
if (response.status === 401) {
|
|
454
602
|
const authError = new AuthenticationError();
|
|
455
|
-
logApiCall(
|
|
603
|
+
logApiCall(logger2, method, path, {
|
|
456
604
|
statusCode: response.status,
|
|
457
605
|
durationMs,
|
|
458
606
|
error: authError
|
|
@@ -465,7 +613,7 @@ var HTTPClient = class {
|
|
|
465
613
|
"Rate limit exceeded",
|
|
466
614
|
retryAfter ? parseInt(retryAfter, 10) : void 0
|
|
467
615
|
);
|
|
468
|
-
|
|
616
|
+
logger2.warning(`Rate limited: ${method} ${path}`, {
|
|
469
617
|
method,
|
|
470
618
|
path,
|
|
471
619
|
statusCode: response.status,
|
|
@@ -476,7 +624,7 @@ var HTTPClient = class {
|
|
|
476
624
|
}
|
|
477
625
|
if (response.status >= 500) {
|
|
478
626
|
const serverError = new ServerError(`Server error: ${response.status}`);
|
|
479
|
-
logApiCall(
|
|
627
|
+
logApiCall(logger2, method, path, {
|
|
480
628
|
statusCode: response.status,
|
|
481
629
|
durationMs,
|
|
482
630
|
error: serverError
|
|
@@ -489,17 +637,18 @@ var HTTPClient = class {
|
|
|
489
637
|
data = text ? JSON.parse(text) : {};
|
|
490
638
|
} catch {
|
|
491
639
|
if (response.status >= 400) {
|
|
492
|
-
const parseError = new NaturalError(
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
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, {
|
|
496
645
|
statusCode: response.status,
|
|
497
646
|
durationMs,
|
|
498
647
|
error: parseError
|
|
499
648
|
});
|
|
500
649
|
throw parseError;
|
|
501
650
|
}
|
|
502
|
-
logApiCall(
|
|
651
|
+
logApiCall(logger2, method, path, { statusCode: response.status, durationMs });
|
|
503
652
|
return {};
|
|
504
653
|
}
|
|
505
654
|
if (response.status >= 400) {
|
|
@@ -512,69 +661,185 @@ var HTTPClient = class {
|
|
|
512
661
|
`${errorMessage} (status=${response.status})`,
|
|
513
662
|
errorCode
|
|
514
663
|
);
|
|
515
|
-
logApiCall(
|
|
664
|
+
logApiCall(logger2, method, path, {
|
|
516
665
|
statusCode: response.status,
|
|
517
666
|
durationMs,
|
|
518
667
|
error: requestError
|
|
519
668
|
});
|
|
520
669
|
throw requestError;
|
|
521
670
|
}
|
|
522
|
-
logApiCall(
|
|
671
|
+
logApiCall(logger2, method, path, { statusCode: response.status, durationMs });
|
|
523
672
|
return data;
|
|
524
673
|
}
|
|
525
674
|
/**
|
|
526
|
-
*
|
|
675
|
+
* Register agent config with the observability service.
|
|
676
|
+
*
|
|
677
|
+
* Called lazily before the first request. Errors are caught and logged,
|
|
678
|
+
* never thrown.
|
|
527
679
|
*/
|
|
528
|
-
async
|
|
529
|
-
|
|
530
|
-
const url = this.buildUrl(path, options?.params);
|
|
531
|
-
const controller = new AbortController();
|
|
532
|
-
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
533
|
-
logger.debug(`API request: ${method} ${path}`, {
|
|
534
|
-
method,
|
|
535
|
-
path,
|
|
536
|
-
hasBody: !!options?.body
|
|
537
|
-
});
|
|
538
|
-
const startTime = Date.now();
|
|
680
|
+
async registerConfig() {
|
|
681
|
+
if (!this.agentConfig) return;
|
|
539
682
|
try {
|
|
540
|
-
const
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
683
|
+
const jwt = await this.getJwt();
|
|
684
|
+
const hash = configHash(this.agentConfig);
|
|
685
|
+
const body = {
|
|
686
|
+
config_hash: hash,
|
|
687
|
+
...agentConfigToDict(this.agentConfig)
|
|
544
688
|
};
|
|
545
|
-
const
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
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);
|
|
551
721
|
}
|
|
552
|
-
const response = await fetch(url, {
|
|
553
|
-
method,
|
|
554
|
-
headers,
|
|
555
|
-
body: options?.body ? JSON.stringify(options.body) : void 0,
|
|
556
|
-
signal: controller.signal
|
|
557
|
-
});
|
|
558
|
-
clearTimeout(timeoutId);
|
|
559
|
-
const durationMs = Date.now() - startTime;
|
|
560
|
-
return this.handleResponse(response, method, path, durationMs);
|
|
561
722
|
} catch (error) {
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
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");
|
|
566
753
|
}
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
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
|
+
}
|
|
768
|
+
}
|
|
769
|
+
if (!this._configAttempted) {
|
|
770
|
+
await this._registerConfigPromise;
|
|
571
771
|
}
|
|
572
|
-
const networkError = new NaturalError(
|
|
573
|
-
`Network error: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
574
|
-
);
|
|
575
|
-
logApiCall(logger, method, path, { durationMs, error: networkError });
|
|
576
|
-
throw networkError;
|
|
577
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;
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
throw new NaturalError("Unexpected retry exhaustion");
|
|
578
843
|
}
|
|
579
844
|
async get(path, options) {
|
|
580
845
|
return this.request("GET", path, options);
|
|
@@ -597,6 +862,9 @@ var BaseResource = class {
|
|
|
597
862
|
this.http = http;
|
|
598
863
|
}
|
|
599
864
|
};
|
|
865
|
+
function sanitizeHeaderValue(value) {
|
|
866
|
+
return value.replace(/[\x00-\x1f\x7f]/g, "");
|
|
867
|
+
}
|
|
600
868
|
|
|
601
869
|
// src/resources/transactions.ts
|
|
602
870
|
function unwrapTransactionResource(resource) {
|
|
@@ -611,8 +879,9 @@ function unwrapTransactionResource(resource) {
|
|
|
611
879
|
amount: Number(attributes.amount),
|
|
612
880
|
currency: String(attributes.currency),
|
|
613
881
|
status: String(attributes.status),
|
|
882
|
+
escalationId: attributes.escalationId != null ? String(attributes.escalationId) : void 0,
|
|
883
|
+
disputeId: attributes.disputeId != null ? String(attributes.disputeId) : void 0,
|
|
614
884
|
description: attributes.description != null ? String(attributes.description) : void 0,
|
|
615
|
-
memo: attributes.memo != null ? String(attributes.memo) : void 0,
|
|
616
885
|
createdAt: String(attributes.createdAt),
|
|
617
886
|
updatedAt: attributes.updatedAt != null ? String(attributes.updatedAt) : void 0,
|
|
618
887
|
isDelegated: Boolean(attributes.isDelegated),
|
|
@@ -620,6 +889,8 @@ function unwrapTransactionResource(resource) {
|
|
|
620
889
|
customerAgentId: attributes.customerAgentId != null ? String(attributes.customerAgentId) : void 0,
|
|
621
890
|
senderName: attributes.senderName != null ? String(attributes.senderName) : void 0,
|
|
622
891
|
recipientName: attributes.recipientName != null ? String(attributes.recipientName) : void 0,
|
|
892
|
+
initiatorName: attributes.initiatorName != null ? String(attributes.initiatorName) : void 0,
|
|
893
|
+
initiatorIsAgent: attributes.initiatorIsAgent != null ? Boolean(attributes.initiatorIsAgent) : void 0,
|
|
623
894
|
transactionType: String(attributes.transactionType),
|
|
624
895
|
category: String(attributes.category),
|
|
625
896
|
direction: String(attributes.direction),
|
|
@@ -641,9 +912,15 @@ var TransactionsResource = class extends BaseResource {
|
|
|
641
912
|
*/
|
|
642
913
|
async get(transactionId, params) {
|
|
643
914
|
const headers = {};
|
|
915
|
+
if (params?.agentId) {
|
|
916
|
+
headers["X-Agent-ID"] = params.agentId;
|
|
917
|
+
}
|
|
644
918
|
if (params?.instanceId) {
|
|
645
919
|
headers["X-Instance-ID"] = params.instanceId;
|
|
646
920
|
}
|
|
921
|
+
if (params?.traceId) {
|
|
922
|
+
headers["X-Trace-ID"] = sanitizeHeaderValue(params.traceId);
|
|
923
|
+
}
|
|
647
924
|
const queryParams = {};
|
|
648
925
|
if (params?.customerPartyId) {
|
|
649
926
|
queryParams["partyId"] = params.customerPartyId;
|
|
@@ -674,6 +951,9 @@ var TransactionsResource = class extends BaseResource {
|
|
|
674
951
|
if (params?.instanceId) {
|
|
675
952
|
headers["X-Instance-ID"] = params.instanceId;
|
|
676
953
|
}
|
|
954
|
+
if (params?.traceId) {
|
|
955
|
+
headers["X-Trace-ID"] = sanitizeHeaderValue(params.traceId);
|
|
956
|
+
}
|
|
677
957
|
const queryParams = {
|
|
678
958
|
limit: params?.limit ?? 50,
|
|
679
959
|
cursor: params?.cursor,
|
|
@@ -725,8 +1005,6 @@ function unwrapPaymentResponse(response) {
|
|
|
725
1005
|
transactionType: String(attributes.transactionType ?? "payment"),
|
|
726
1006
|
category: String(attributes.category ?? "sent"),
|
|
727
1007
|
direction: String(attributes.direction ?? "OUTBOUND"),
|
|
728
|
-
// Mirror description into memo for payment convenience.
|
|
729
|
-
memo: base.description,
|
|
730
1008
|
// Payments use customerParty/counterparty relationship keys,
|
|
731
1009
|
// falling back to the generic sourceParty/destinationParty.
|
|
732
1010
|
sourcePartyId: relationships?.customerParty?.data?.id ?? base.sourcePartyId,
|
|
@@ -750,9 +1028,9 @@ var PaymentsResource = class extends BaseResource {
|
|
|
750
1028
|
amount: params.amount,
|
|
751
1029
|
currency: params.currency ?? "USD",
|
|
752
1030
|
counterparty: recipient,
|
|
753
|
-
|
|
1031
|
+
description: params.description,
|
|
1032
|
+
...params.customerPartyId != null ? { customerPartyId: params.customerPartyId } : {}
|
|
754
1033
|
};
|
|
755
|
-
attributes["description"] = params.memo;
|
|
756
1034
|
const body = { data: { attributes } };
|
|
757
1035
|
const headers = {
|
|
758
1036
|
"Idempotency-Key": params.idempotencyKey
|
|
@@ -766,6 +1044,9 @@ var PaymentsResource = class extends BaseResource {
|
|
|
766
1044
|
if (params.instanceId) {
|
|
767
1045
|
headers["X-Instance-ID"] = params.instanceId;
|
|
768
1046
|
}
|
|
1047
|
+
if (params.traceId) {
|
|
1048
|
+
headers["X-Trace-ID"] = sanitizeHeaderValue(params.traceId);
|
|
1049
|
+
}
|
|
769
1050
|
const response = await this.http.post("/payments", {
|
|
770
1051
|
body,
|
|
771
1052
|
headers
|
|
@@ -798,9 +1079,9 @@ function unwrapBalance(response) {
|
|
|
798
1079
|
const breakdown = {
|
|
799
1080
|
operatingFunded: toAmountInfo(rawBreakdown?.operatingFunded),
|
|
800
1081
|
operatingAdvanced: toAmountInfo(rawBreakdown?.operatingAdvanced),
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
1082
|
+
pendingIn: toAmountInfo(rawBreakdown?.pendingIn),
|
|
1083
|
+
pendingOut: toAmountInfo(rawBreakdown?.pendingOut),
|
|
1084
|
+
held: toAmountInfo(rawBreakdown?.held)
|
|
804
1085
|
};
|
|
805
1086
|
return {
|
|
806
1087
|
walletId: id,
|
|
@@ -848,9 +1129,15 @@ var WalletResource = class extends BaseResource {
|
|
|
848
1129
|
*/
|
|
849
1130
|
async balance(options) {
|
|
850
1131
|
const headers = {};
|
|
1132
|
+
if (options?.agentId) {
|
|
1133
|
+
headers["X-Agent-ID"] = options.agentId;
|
|
1134
|
+
}
|
|
851
1135
|
if (options?.instanceId) {
|
|
852
1136
|
headers["X-Instance-ID"] = options.instanceId;
|
|
853
1137
|
}
|
|
1138
|
+
if (options?.traceId) {
|
|
1139
|
+
headers["X-Trace-ID"] = sanitizeHeaderValue(options.traceId);
|
|
1140
|
+
}
|
|
854
1141
|
const params = {};
|
|
855
1142
|
if (options?.customerPartyId) {
|
|
856
1143
|
params["partyId"] = options.customerPartyId;
|
|
@@ -878,9 +1165,21 @@ var WalletResource = class extends BaseResource {
|
|
|
878
1165
|
};
|
|
879
1166
|
if (params.description) attributes["description"] = params.description;
|
|
880
1167
|
const body = { data: { attributes } };
|
|
1168
|
+
const headers = {
|
|
1169
|
+
"Idempotency-Key": params.idempotencyKey
|
|
1170
|
+
};
|
|
1171
|
+
if (params.agentId) {
|
|
1172
|
+
headers["X-Agent-ID"] = params.agentId;
|
|
1173
|
+
}
|
|
1174
|
+
if (params.instanceId) {
|
|
1175
|
+
headers["X-Instance-ID"] = params.instanceId;
|
|
1176
|
+
}
|
|
1177
|
+
if (params.traceId) {
|
|
1178
|
+
headers["X-Trace-ID"] = sanitizeHeaderValue(params.traceId);
|
|
1179
|
+
}
|
|
881
1180
|
const response = await this.http.post("/wallet/withdraw", {
|
|
882
1181
|
body,
|
|
883
|
-
headers
|
|
1182
|
+
headers
|
|
884
1183
|
});
|
|
885
1184
|
return unwrapWithdrawal(response);
|
|
886
1185
|
}
|
|
@@ -935,9 +1234,15 @@ var AgentsResource = class extends BaseResource {
|
|
|
935
1234
|
*/
|
|
936
1235
|
async list(params) {
|
|
937
1236
|
const headers = {};
|
|
1237
|
+
if (params?.agentId) {
|
|
1238
|
+
headers["X-Agent-ID"] = params.agentId;
|
|
1239
|
+
}
|
|
938
1240
|
if (params?.instanceId) {
|
|
939
1241
|
headers["X-Instance-ID"] = params.instanceId;
|
|
940
1242
|
}
|
|
1243
|
+
if (params?.traceId) {
|
|
1244
|
+
headers["X-Trace-ID"] = sanitizeHeaderValue(params.traceId);
|
|
1245
|
+
}
|
|
941
1246
|
const queryParams = {
|
|
942
1247
|
status: params?.status,
|
|
943
1248
|
limit: params?.limit ?? 50,
|
|
@@ -957,9 +1262,15 @@ var AgentsResource = class extends BaseResource {
|
|
|
957
1262
|
*/
|
|
958
1263
|
async get(agentId, options) {
|
|
959
1264
|
const headers = {};
|
|
1265
|
+
if (options?.agentId) {
|
|
1266
|
+
headers["X-Agent-ID"] = options.agentId;
|
|
1267
|
+
}
|
|
960
1268
|
if (options?.instanceId) {
|
|
961
1269
|
headers["X-Instance-ID"] = options.instanceId;
|
|
962
1270
|
}
|
|
1271
|
+
if (options?.traceId) {
|
|
1272
|
+
headers["X-Trace-ID"] = sanitizeHeaderValue(options.traceId);
|
|
1273
|
+
}
|
|
963
1274
|
const response = await this.http.get(`/agents/${agentId}`, {
|
|
964
1275
|
headers: Object.keys(headers).length > 0 ? headers : void 0
|
|
965
1276
|
});
|
|
@@ -986,9 +1297,15 @@ var AgentsResource = class extends BaseResource {
|
|
|
986
1297
|
if (params.idempotencyKey) {
|
|
987
1298
|
headers["Idempotency-Key"] = params.idempotencyKey;
|
|
988
1299
|
}
|
|
1300
|
+
if (params.agentId) {
|
|
1301
|
+
headers["X-Agent-ID"] = params.agentId;
|
|
1302
|
+
}
|
|
989
1303
|
if (params.instanceId) {
|
|
990
1304
|
headers["X-Instance-ID"] = params.instanceId;
|
|
991
1305
|
}
|
|
1306
|
+
if (params.traceId) {
|
|
1307
|
+
headers["X-Trace-ID"] = sanitizeHeaderValue(params.traceId);
|
|
1308
|
+
}
|
|
992
1309
|
const response = await this.http.post("/agents", {
|
|
993
1310
|
body,
|
|
994
1311
|
headers: Object.keys(headers).length > 0 ? headers : void 0
|
|
@@ -1012,9 +1329,15 @@ var AgentsResource = class extends BaseResource {
|
|
|
1012
1329
|
if (params.idempotencyKey) {
|
|
1013
1330
|
headers["Idempotency-Key"] = params.idempotencyKey;
|
|
1014
1331
|
}
|
|
1332
|
+
if (params.agentId) {
|
|
1333
|
+
headers["X-Agent-ID"] = params.agentId;
|
|
1334
|
+
}
|
|
1015
1335
|
if (params.instanceId) {
|
|
1016
1336
|
headers["X-Instance-ID"] = params.instanceId;
|
|
1017
1337
|
}
|
|
1338
|
+
if (params.traceId) {
|
|
1339
|
+
headers["X-Trace-ID"] = sanitizeHeaderValue(params.traceId);
|
|
1340
|
+
}
|
|
1018
1341
|
const response = await this.http.put(`/agents/${agentId}`, {
|
|
1019
1342
|
body,
|
|
1020
1343
|
headers: Object.keys(headers).length > 0 ? headers : void 0
|
|
@@ -1029,9 +1352,15 @@ var AgentsResource = class extends BaseResource {
|
|
|
1029
1352
|
*/
|
|
1030
1353
|
async delete(agentId, options) {
|
|
1031
1354
|
const headers = {};
|
|
1355
|
+
if (options?.agentId) {
|
|
1356
|
+
headers["X-Agent-ID"] = options.agentId;
|
|
1357
|
+
}
|
|
1032
1358
|
if (options?.instanceId) {
|
|
1033
1359
|
headers["X-Instance-ID"] = options.instanceId;
|
|
1034
1360
|
}
|
|
1361
|
+
if (options?.traceId) {
|
|
1362
|
+
headers["X-Trace-ID"] = sanitizeHeaderValue(options.traceId);
|
|
1363
|
+
}
|
|
1035
1364
|
await this.http.delete(`/agents/${agentId}`, {
|
|
1036
1365
|
headers: Object.keys(headers).length > 0 ? headers : void 0
|
|
1037
1366
|
});
|
|
@@ -1097,6 +1426,9 @@ var DelegationsResource = class extends BaseResource {
|
|
|
1097
1426
|
if (params?.instanceId) {
|
|
1098
1427
|
headers["X-Instance-ID"] = params.instanceId;
|
|
1099
1428
|
}
|
|
1429
|
+
if (params?.traceId) {
|
|
1430
|
+
headers["X-Trace-ID"] = sanitizeHeaderValue(params.traceId);
|
|
1431
|
+
}
|
|
1100
1432
|
const queryParams = {
|
|
1101
1433
|
delegationId: params?.delegationId,
|
|
1102
1434
|
agentId: params?.agentId,
|
|
@@ -1127,18 +1459,14 @@ var DelegationsResource = class extends BaseResource {
|
|
|
1127
1459
|
};
|
|
1128
1460
|
|
|
1129
1461
|
// src/resources/customers.ts
|
|
1130
|
-
var VALID_CUSTOMER_TYPES = /* @__PURE__ */ new Set(["party", "delegationInvitation"]);
|
|
1131
1462
|
var VALID_WALLET_ACCESS = /* @__PURE__ */ new Set(["granted", "denied", "noWallet"]);
|
|
1132
|
-
function isCustomerType(value) {
|
|
1133
|
-
return VALID_CUSTOMER_TYPES.has(value);
|
|
1134
|
-
}
|
|
1135
1463
|
function isWalletAccess(value) {
|
|
1136
1464
|
return typeof value === "string" && VALID_WALLET_ACCESS.has(value);
|
|
1137
1465
|
}
|
|
1138
1466
|
function unwrapCustomerResource(resource) {
|
|
1139
|
-
if (
|
|
1467
|
+
if (resource.type !== "party" || !resource.attributes) {
|
|
1140
1468
|
throw new NaturalError(
|
|
1141
|
-
`Unexpected resource format: expected type "party"
|
|
1469
|
+
`Unexpected resource format: expected type "party", got "${resource.type}"`
|
|
1142
1470
|
);
|
|
1143
1471
|
}
|
|
1144
1472
|
const { id, attributes } = resource;
|
|
@@ -1149,9 +1477,8 @@ function unwrapCustomerResource(resource) {
|
|
|
1149
1477
|
} : void 0;
|
|
1150
1478
|
return {
|
|
1151
1479
|
id,
|
|
1152
|
-
type:
|
|
1480
|
+
type: "party",
|
|
1153
1481
|
party,
|
|
1154
|
-
email: typeof attributes.email === "string" ? attributes.email : void 0,
|
|
1155
1482
|
status: typeof attributes.status === "string" ? attributes.status : "",
|
|
1156
1483
|
permissions: Array.isArray(attributes.permissions) ? attributes.permissions : [],
|
|
1157
1484
|
createdAt: typeof attributes.createdAt === "string" ? attributes.createdAt : "",
|
|
@@ -1160,6 +1487,22 @@ function unwrapCustomerResource(resource) {
|
|
|
1160
1487
|
walletAccess: isWalletAccess(attributes.walletAccess) ? attributes.walletAccess : "denied"
|
|
1161
1488
|
};
|
|
1162
1489
|
}
|
|
1490
|
+
function unwrapPendingInvitationResource(resource) {
|
|
1491
|
+
if (resource.type !== "delegationInvitation" || !resource.attributes) {
|
|
1492
|
+
throw new NaturalError(
|
|
1493
|
+
`Unexpected resource format: expected type "delegationInvitation", got "${resource.type}"`
|
|
1494
|
+
);
|
|
1495
|
+
}
|
|
1496
|
+
const { id, attributes } = resource;
|
|
1497
|
+
return {
|
|
1498
|
+
id,
|
|
1499
|
+
type: "delegationInvitation",
|
|
1500
|
+
email: typeof attributes.email === "string" ? attributes.email : void 0,
|
|
1501
|
+
status: typeof attributes.status === "string" ? attributes.status : "",
|
|
1502
|
+
permissions: Array.isArray(attributes.permissions) ? attributes.permissions : [],
|
|
1503
|
+
createdAt: typeof attributes.createdAt === "string" ? attributes.createdAt : ""
|
|
1504
|
+
};
|
|
1505
|
+
}
|
|
1163
1506
|
function unwrapCustomerList(response) {
|
|
1164
1507
|
if (!response?.data || !Array.isArray(response.data)) {
|
|
1165
1508
|
throw new NaturalError(
|
|
@@ -1173,28 +1516,59 @@ function unwrapCustomerList(response) {
|
|
|
1173
1516
|
nextCursor: pagination.nextCursor ?? null
|
|
1174
1517
|
};
|
|
1175
1518
|
}
|
|
1519
|
+
function unwrapPendingInvitationList(response) {
|
|
1520
|
+
if (!response?.data || !Array.isArray(response.data)) {
|
|
1521
|
+
throw new NaturalError(
|
|
1522
|
+
'Unexpected response format: missing "data" array in pending invitation list response'
|
|
1523
|
+
);
|
|
1524
|
+
}
|
|
1525
|
+
const pagination = response.meta?.pagination ?? {};
|
|
1526
|
+
return {
|
|
1527
|
+
items: response.data.map(unwrapPendingInvitationResource),
|
|
1528
|
+
hasMore: pagination.hasMore ?? false,
|
|
1529
|
+
nextCursor: pagination.nextCursor ?? null
|
|
1530
|
+
};
|
|
1531
|
+
}
|
|
1532
|
+
function buildRequest(params) {
|
|
1533
|
+
const headers = {};
|
|
1534
|
+
if (params?.agentId) {
|
|
1535
|
+
headers["X-Agent-ID"] = params.agentId;
|
|
1536
|
+
}
|
|
1537
|
+
if (params?.instanceId) {
|
|
1538
|
+
headers["X-Instance-ID"] = params.instanceId;
|
|
1539
|
+
}
|
|
1540
|
+
if (params?.traceId) {
|
|
1541
|
+
headers["X-Trace-ID"] = sanitizeHeaderValue(params.traceId);
|
|
1542
|
+
}
|
|
1543
|
+
const queryParams = {
|
|
1544
|
+
limit: params?.limit,
|
|
1545
|
+
cursor: params?.cursor
|
|
1546
|
+
};
|
|
1547
|
+
return {
|
|
1548
|
+
params: queryParams,
|
|
1549
|
+
headers: Object.keys(headers).length > 0 ? headers : void 0
|
|
1550
|
+
};
|
|
1551
|
+
}
|
|
1176
1552
|
var CustomersResource = class extends BaseResource {
|
|
1177
1553
|
/**
|
|
1178
|
-
* List customers who have delegated access to the partner.
|
|
1554
|
+
* List active customers who have delegated access to the partner.
|
|
1179
1555
|
*
|
|
1180
|
-
* @
|
|
1181
|
-
*
|
|
1556
|
+
* Returns only accepted delegations. Use {@link listInvitations} for pending
|
|
1557
|
+
* invitations that have not yet been accepted.
|
|
1182
1558
|
*/
|
|
1183
1559
|
async list(params) {
|
|
1184
|
-
const
|
|
1185
|
-
|
|
1186
|
-
headers["X-Instance-ID"] = params.instanceId;
|
|
1187
|
-
}
|
|
1188
|
-
const queryParams = {
|
|
1189
|
-
limit: params?.limit,
|
|
1190
|
-
cursor: params?.cursor
|
|
1191
|
-
};
|
|
1192
|
-
const response = await this.http.get("/customers", {
|
|
1193
|
-
params: queryParams,
|
|
1194
|
-
headers: Object.keys(headers).length > 0 ? headers : void 0
|
|
1195
|
-
});
|
|
1560
|
+
const request = buildRequest(params);
|
|
1561
|
+
const response = await this.http.get("/customers", request);
|
|
1196
1562
|
return unwrapCustomerList(response);
|
|
1197
1563
|
}
|
|
1564
|
+
/**
|
|
1565
|
+
* List pending customer invitations you've sent that have not yet been accepted.
|
|
1566
|
+
*/
|
|
1567
|
+
async listInvitations(params) {
|
|
1568
|
+
const request = buildRequest(params);
|
|
1569
|
+
const response = await this.http.get("/customers/invitations", request);
|
|
1570
|
+
return unwrapPendingInvitationList(response);
|
|
1571
|
+
}
|
|
1198
1572
|
};
|
|
1199
1573
|
|
|
1200
1574
|
// src/client.ts
|
|
@@ -1231,6 +1605,95 @@ var NaturalClient = class {
|
|
|
1231
1605
|
}
|
|
1232
1606
|
};
|
|
1233
1607
|
|
|
1608
|
+
// src/tool-names.ts
|
|
1609
|
+
var NATURAL_TOOL_NAMES = {
|
|
1610
|
+
CREATE_PAYMENT: "create_payment",
|
|
1611
|
+
GET_PAYMENT_STATUS: "get_payment_status",
|
|
1612
|
+
GET_ACCOUNT_BALANCE: "get_account_balance",
|
|
1613
|
+
LIST_TRANSACTIONS: "list_transactions",
|
|
1614
|
+
LIST_AGENTS: "list_agents",
|
|
1615
|
+
LIST_CUSTOMERS: "list_customers",
|
|
1616
|
+
LIST_CUSTOMER_INVITATIONS: "list_customer_invitations"
|
|
1617
|
+
};
|
|
1618
|
+
var WHSEC_PREFIX = "whsec_";
|
|
1619
|
+
var DEFAULT_TOLERANCE_SECONDS = 300;
|
|
1620
|
+
function getHeader(headers, name) {
|
|
1621
|
+
if (typeof headers.get === "function") {
|
|
1622
|
+
return headers.get(name) ?? void 0;
|
|
1623
|
+
}
|
|
1624
|
+
const lower = name.toLowerCase();
|
|
1625
|
+
for (const key of Object.keys(headers)) {
|
|
1626
|
+
if (key.toLowerCase() === lower) {
|
|
1627
|
+
return headers[key];
|
|
1628
|
+
}
|
|
1629
|
+
}
|
|
1630
|
+
return void 0;
|
|
1631
|
+
}
|
|
1632
|
+
function verifyWebhookSignature(body, headers, secret, options) {
|
|
1633
|
+
const tolerance = options?.toleranceInSeconds ?? DEFAULT_TOLERANCE_SECONDS;
|
|
1634
|
+
if (!Number.isInteger(tolerance) || tolerance <= 0) {
|
|
1635
|
+
throw new WebhookVerificationError("toleranceInSeconds must be a positive integer");
|
|
1636
|
+
}
|
|
1637
|
+
const webhookId = getHeader(headers, "webhook-id");
|
|
1638
|
+
if (!webhookId) {
|
|
1639
|
+
throw new WebhookVerificationError("webhook-id header is missing");
|
|
1640
|
+
}
|
|
1641
|
+
const timestampStr = getHeader(headers, "webhook-timestamp");
|
|
1642
|
+
if (!timestampStr) {
|
|
1643
|
+
throw new WebhookVerificationError("webhook-timestamp header is missing");
|
|
1644
|
+
}
|
|
1645
|
+
const signatureHeader = getHeader(headers, "webhook-signature");
|
|
1646
|
+
if (!signatureHeader) {
|
|
1647
|
+
throw new WebhookVerificationError("webhook-signature header is missing");
|
|
1648
|
+
}
|
|
1649
|
+
if (!/^\d+$/.test(timestampStr)) {
|
|
1650
|
+
throw new WebhookVerificationError(
|
|
1651
|
+
`Invalid webhook-timestamp: '${timestampStr}' is not a valid integer`
|
|
1652
|
+
);
|
|
1653
|
+
}
|
|
1654
|
+
const timestamp = parseInt(timestampStr, 10);
|
|
1655
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
1656
|
+
if (Math.abs(now - timestamp) > tolerance) {
|
|
1657
|
+
throw new WebhookVerificationError(
|
|
1658
|
+
timestamp > now ? "Webhook timestamp is too far in the future" : "Webhook timestamp is too old"
|
|
1659
|
+
);
|
|
1660
|
+
}
|
|
1661
|
+
let keyBytes;
|
|
1662
|
+
const base64Part = secret.startsWith(WHSEC_PREFIX) ? secret.slice(WHSEC_PREFIX.length) : secret;
|
|
1663
|
+
if (!/^[A-Za-z0-9+/]*={0,2}$/.test(base64Part) || base64Part.length === 0) {
|
|
1664
|
+
throw new WebhookVerificationError("Invalid signing secret: could not base64-decode");
|
|
1665
|
+
}
|
|
1666
|
+
try {
|
|
1667
|
+
keyBytes = Buffer.from(base64Part, "base64");
|
|
1668
|
+
if (keyBytes.length === 0) {
|
|
1669
|
+
throw new Error("empty key");
|
|
1670
|
+
}
|
|
1671
|
+
} catch {
|
|
1672
|
+
throw new WebhookVerificationError("Invalid signing secret: could not base64-decode");
|
|
1673
|
+
}
|
|
1674
|
+
const bodyStr = typeof body === "string" ? body : Buffer.from(body).toString("utf-8");
|
|
1675
|
+
const signedContent = `${webhookId}.${timestampStr}.${bodyStr}`;
|
|
1676
|
+
const expectedSig = crypto.createHmac("sha256", keyBytes).update(signedContent).digest("base64");
|
|
1677
|
+
const candidates = signatureHeader.split(" ");
|
|
1678
|
+
for (const candidate of candidates) {
|
|
1679
|
+
if (!candidate.startsWith("v1,")) continue;
|
|
1680
|
+
const candidateSig = candidate.slice(3);
|
|
1681
|
+
const expectedBuf = Buffer.from(expectedSig, "utf-8");
|
|
1682
|
+
const candidateBuf = Buffer.from(candidateSig, "utf-8");
|
|
1683
|
+
if (expectedBuf.length !== candidateBuf.length) continue;
|
|
1684
|
+
if (crypto.timingSafeEqual(expectedBuf, candidateBuf)) {
|
|
1685
|
+
try {
|
|
1686
|
+
return JSON.parse(bodyStr);
|
|
1687
|
+
} catch {
|
|
1688
|
+
throw new WebhookVerificationError("Webhook signature is valid but body is not valid JSON");
|
|
1689
|
+
}
|
|
1690
|
+
}
|
|
1691
|
+
}
|
|
1692
|
+
throw new WebhookVerificationError(
|
|
1693
|
+
"Webhook signature does not match \u2014 ensure you are using the raw request body"
|
|
1694
|
+
);
|
|
1695
|
+
}
|
|
1696
|
+
|
|
1234
1697
|
// src/types/transactions.ts
|
|
1235
1698
|
var TransactionTypeFilter = /* @__PURE__ */ ((TransactionTypeFilter2) => {
|
|
1236
1699
|
TransactionTypeFilter2["PAYMENT"] = "payment";
|
|
@@ -1242,6 +1705,7 @@ var TransactionTypeFilter = /* @__PURE__ */ ((TransactionTypeFilter2) => {
|
|
|
1242
1705
|
exports.AuthenticationError = AuthenticationError;
|
|
1243
1706
|
exports.InsufficientFundsError = InsufficientFundsError;
|
|
1244
1707
|
exports.InvalidRequestError = InvalidRequestError;
|
|
1708
|
+
exports.NATURAL_TOOL_NAMES = NATURAL_TOOL_NAMES;
|
|
1245
1709
|
exports.NaturalClient = NaturalClient;
|
|
1246
1710
|
exports.NaturalError = NaturalError;
|
|
1247
1711
|
exports.PaymentError = PaymentError;
|
|
@@ -1250,16 +1714,24 @@ exports.RecipientNotFoundError = RecipientNotFoundError;
|
|
|
1250
1714
|
exports.ServerError = ServerError;
|
|
1251
1715
|
exports.TransactionTypeFilter = TransactionTypeFilter;
|
|
1252
1716
|
exports.VERSION = VERSION;
|
|
1717
|
+
exports.WebhookVerificationError = WebhookVerificationError;
|
|
1718
|
+
exports.agentConfigToDict = agentConfigToDict;
|
|
1253
1719
|
exports.bindContext = bindContext;
|
|
1254
1720
|
exports.clearContext = clearContext;
|
|
1721
|
+
exports.configHash = configHash;
|
|
1255
1722
|
exports.configureLogging = configureLogging;
|
|
1723
|
+
exports.generateToolCallId = generateToolCallId;
|
|
1256
1724
|
exports.getContext = getContext;
|
|
1257
1725
|
exports.getLogger = getLogger;
|
|
1726
|
+
exports.getToolCallHeader = getToolCallHeader;
|
|
1258
1727
|
exports.logApiCall = logApiCall;
|
|
1259
1728
|
exports.logError = logError;
|
|
1260
1729
|
exports.logToolCall = logToolCall;
|
|
1730
|
+
exports.modelUsageToDict = modelUsageToDict;
|
|
1261
1731
|
exports.parseApiKeyEnv = parseApiKeyEnv;
|
|
1262
1732
|
exports.runWithContext = runWithContext;
|
|
1733
|
+
exports.runWithToolCall = runWithToolCall;
|
|
1263
1734
|
exports.validateBaseUrl = validateBaseUrl;
|
|
1735
|
+
exports.verifyWebhookSignature = verifyWebhookSignature;
|
|
1264
1736
|
//# sourceMappingURL=index.cjs.map
|
|
1265
1737
|
//# sourceMappingURL=index.cjs.map
|