@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.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);
|
|
307
|
+
}
|
|
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;
|
|
300
338
|
}
|
|
339
|
+
return dict;
|
|
301
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
|
+
}
|
|
766
|
+
}
|
|
767
|
+
if (!this._configAttempted) {
|
|
768
|
+
await this._registerConfigPromise;
|
|
569
769
|
}
|
|
570
|
-
const networkError = new NaturalError(
|
|
571
|
-
`Network error: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
572
|
-
);
|
|
573
|
-
logApiCall(logger, method, path, { durationMs, error: networkError });
|
|
574
|
-
throw networkError;
|
|
575
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;
|
|
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) {
|
|
@@ -609,8 +877,9 @@ function unwrapTransactionResource(resource) {
|
|
|
609
877
|
amount: Number(attributes.amount),
|
|
610
878
|
currency: String(attributes.currency),
|
|
611
879
|
status: String(attributes.status),
|
|
880
|
+
escalationId: attributes.escalationId != null ? String(attributes.escalationId) : void 0,
|
|
881
|
+
disputeId: attributes.disputeId != null ? String(attributes.disputeId) : void 0,
|
|
612
882
|
description: attributes.description != null ? String(attributes.description) : void 0,
|
|
613
|
-
memo: attributes.memo != null ? String(attributes.memo) : void 0,
|
|
614
883
|
createdAt: String(attributes.createdAt),
|
|
615
884
|
updatedAt: attributes.updatedAt != null ? String(attributes.updatedAt) : void 0,
|
|
616
885
|
isDelegated: Boolean(attributes.isDelegated),
|
|
@@ -618,6 +887,8 @@ function unwrapTransactionResource(resource) {
|
|
|
618
887
|
customerAgentId: attributes.customerAgentId != null ? String(attributes.customerAgentId) : void 0,
|
|
619
888
|
senderName: attributes.senderName != null ? String(attributes.senderName) : void 0,
|
|
620
889
|
recipientName: attributes.recipientName != null ? String(attributes.recipientName) : void 0,
|
|
890
|
+
initiatorName: attributes.initiatorName != null ? String(attributes.initiatorName) : void 0,
|
|
891
|
+
initiatorIsAgent: attributes.initiatorIsAgent != null ? Boolean(attributes.initiatorIsAgent) : void 0,
|
|
621
892
|
transactionType: String(attributes.transactionType),
|
|
622
893
|
category: String(attributes.category),
|
|
623
894
|
direction: String(attributes.direction),
|
|
@@ -639,9 +910,15 @@ var TransactionsResource = class extends BaseResource {
|
|
|
639
910
|
*/
|
|
640
911
|
async get(transactionId, params) {
|
|
641
912
|
const headers = {};
|
|
913
|
+
if (params?.agentId) {
|
|
914
|
+
headers["X-Agent-ID"] = params.agentId;
|
|
915
|
+
}
|
|
642
916
|
if (params?.instanceId) {
|
|
643
917
|
headers["X-Instance-ID"] = params.instanceId;
|
|
644
918
|
}
|
|
919
|
+
if (params?.traceId) {
|
|
920
|
+
headers["X-Trace-ID"] = sanitizeHeaderValue(params.traceId);
|
|
921
|
+
}
|
|
645
922
|
const queryParams = {};
|
|
646
923
|
if (params?.customerPartyId) {
|
|
647
924
|
queryParams["partyId"] = params.customerPartyId;
|
|
@@ -672,6 +949,9 @@ var TransactionsResource = class extends BaseResource {
|
|
|
672
949
|
if (params?.instanceId) {
|
|
673
950
|
headers["X-Instance-ID"] = params.instanceId;
|
|
674
951
|
}
|
|
952
|
+
if (params?.traceId) {
|
|
953
|
+
headers["X-Trace-ID"] = sanitizeHeaderValue(params.traceId);
|
|
954
|
+
}
|
|
675
955
|
const queryParams = {
|
|
676
956
|
limit: params?.limit ?? 50,
|
|
677
957
|
cursor: params?.cursor,
|
|
@@ -723,8 +1003,6 @@ function unwrapPaymentResponse(response) {
|
|
|
723
1003
|
transactionType: String(attributes.transactionType ?? "payment"),
|
|
724
1004
|
category: String(attributes.category ?? "sent"),
|
|
725
1005
|
direction: String(attributes.direction ?? "OUTBOUND"),
|
|
726
|
-
// Mirror description into memo for payment convenience.
|
|
727
|
-
memo: base.description,
|
|
728
1006
|
// Payments use customerParty/counterparty relationship keys,
|
|
729
1007
|
// falling back to the generic sourceParty/destinationParty.
|
|
730
1008
|
sourcePartyId: relationships?.customerParty?.data?.id ?? base.sourcePartyId,
|
|
@@ -748,9 +1026,9 @@ var PaymentsResource = class extends BaseResource {
|
|
|
748
1026
|
amount: params.amount,
|
|
749
1027
|
currency: params.currency ?? "USD",
|
|
750
1028
|
counterparty: recipient,
|
|
751
|
-
|
|
1029
|
+
description: params.description,
|
|
1030
|
+
...params.customerPartyId != null ? { customerPartyId: params.customerPartyId } : {}
|
|
752
1031
|
};
|
|
753
|
-
attributes["description"] = params.memo;
|
|
754
1032
|
const body = { data: { attributes } };
|
|
755
1033
|
const headers = {
|
|
756
1034
|
"Idempotency-Key": params.idempotencyKey
|
|
@@ -764,6 +1042,9 @@ var PaymentsResource = class extends BaseResource {
|
|
|
764
1042
|
if (params.instanceId) {
|
|
765
1043
|
headers["X-Instance-ID"] = params.instanceId;
|
|
766
1044
|
}
|
|
1045
|
+
if (params.traceId) {
|
|
1046
|
+
headers["X-Trace-ID"] = sanitizeHeaderValue(params.traceId);
|
|
1047
|
+
}
|
|
767
1048
|
const response = await this.http.post("/payments", {
|
|
768
1049
|
body,
|
|
769
1050
|
headers
|
|
@@ -796,9 +1077,9 @@ function unwrapBalance(response) {
|
|
|
796
1077
|
const breakdown = {
|
|
797
1078
|
operatingFunded: toAmountInfo(rawBreakdown?.operatingFunded),
|
|
798
1079
|
operatingAdvanced: toAmountInfo(rawBreakdown?.operatingAdvanced),
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
1080
|
+
pendingIn: toAmountInfo(rawBreakdown?.pendingIn),
|
|
1081
|
+
pendingOut: toAmountInfo(rawBreakdown?.pendingOut),
|
|
1082
|
+
held: toAmountInfo(rawBreakdown?.held)
|
|
802
1083
|
};
|
|
803
1084
|
return {
|
|
804
1085
|
walletId: id,
|
|
@@ -846,9 +1127,15 @@ var WalletResource = class extends BaseResource {
|
|
|
846
1127
|
*/
|
|
847
1128
|
async balance(options) {
|
|
848
1129
|
const headers = {};
|
|
1130
|
+
if (options?.agentId) {
|
|
1131
|
+
headers["X-Agent-ID"] = options.agentId;
|
|
1132
|
+
}
|
|
849
1133
|
if (options?.instanceId) {
|
|
850
1134
|
headers["X-Instance-ID"] = options.instanceId;
|
|
851
1135
|
}
|
|
1136
|
+
if (options?.traceId) {
|
|
1137
|
+
headers["X-Trace-ID"] = sanitizeHeaderValue(options.traceId);
|
|
1138
|
+
}
|
|
852
1139
|
const params = {};
|
|
853
1140
|
if (options?.customerPartyId) {
|
|
854
1141
|
params["partyId"] = options.customerPartyId;
|
|
@@ -876,9 +1163,21 @@ var WalletResource = class extends BaseResource {
|
|
|
876
1163
|
};
|
|
877
1164
|
if (params.description) attributes["description"] = params.description;
|
|
878
1165
|
const body = { data: { attributes } };
|
|
1166
|
+
const headers = {
|
|
1167
|
+
"Idempotency-Key": params.idempotencyKey
|
|
1168
|
+
};
|
|
1169
|
+
if (params.agentId) {
|
|
1170
|
+
headers["X-Agent-ID"] = params.agentId;
|
|
1171
|
+
}
|
|
1172
|
+
if (params.instanceId) {
|
|
1173
|
+
headers["X-Instance-ID"] = params.instanceId;
|
|
1174
|
+
}
|
|
1175
|
+
if (params.traceId) {
|
|
1176
|
+
headers["X-Trace-ID"] = sanitizeHeaderValue(params.traceId);
|
|
1177
|
+
}
|
|
879
1178
|
const response = await this.http.post("/wallet/withdraw", {
|
|
880
1179
|
body,
|
|
881
|
-
headers
|
|
1180
|
+
headers
|
|
882
1181
|
});
|
|
883
1182
|
return unwrapWithdrawal(response);
|
|
884
1183
|
}
|
|
@@ -933,9 +1232,15 @@ var AgentsResource = class extends BaseResource {
|
|
|
933
1232
|
*/
|
|
934
1233
|
async list(params) {
|
|
935
1234
|
const headers = {};
|
|
1235
|
+
if (params?.agentId) {
|
|
1236
|
+
headers["X-Agent-ID"] = params.agentId;
|
|
1237
|
+
}
|
|
936
1238
|
if (params?.instanceId) {
|
|
937
1239
|
headers["X-Instance-ID"] = params.instanceId;
|
|
938
1240
|
}
|
|
1241
|
+
if (params?.traceId) {
|
|
1242
|
+
headers["X-Trace-ID"] = sanitizeHeaderValue(params.traceId);
|
|
1243
|
+
}
|
|
939
1244
|
const queryParams = {
|
|
940
1245
|
status: params?.status,
|
|
941
1246
|
limit: params?.limit ?? 50,
|
|
@@ -955,9 +1260,15 @@ var AgentsResource = class extends BaseResource {
|
|
|
955
1260
|
*/
|
|
956
1261
|
async get(agentId, options) {
|
|
957
1262
|
const headers = {};
|
|
1263
|
+
if (options?.agentId) {
|
|
1264
|
+
headers["X-Agent-ID"] = options.agentId;
|
|
1265
|
+
}
|
|
958
1266
|
if (options?.instanceId) {
|
|
959
1267
|
headers["X-Instance-ID"] = options.instanceId;
|
|
960
1268
|
}
|
|
1269
|
+
if (options?.traceId) {
|
|
1270
|
+
headers["X-Trace-ID"] = sanitizeHeaderValue(options.traceId);
|
|
1271
|
+
}
|
|
961
1272
|
const response = await this.http.get(`/agents/${agentId}`, {
|
|
962
1273
|
headers: Object.keys(headers).length > 0 ? headers : void 0
|
|
963
1274
|
});
|
|
@@ -984,9 +1295,15 @@ var AgentsResource = class extends BaseResource {
|
|
|
984
1295
|
if (params.idempotencyKey) {
|
|
985
1296
|
headers["Idempotency-Key"] = params.idempotencyKey;
|
|
986
1297
|
}
|
|
1298
|
+
if (params.agentId) {
|
|
1299
|
+
headers["X-Agent-ID"] = params.agentId;
|
|
1300
|
+
}
|
|
987
1301
|
if (params.instanceId) {
|
|
988
1302
|
headers["X-Instance-ID"] = params.instanceId;
|
|
989
1303
|
}
|
|
1304
|
+
if (params.traceId) {
|
|
1305
|
+
headers["X-Trace-ID"] = sanitizeHeaderValue(params.traceId);
|
|
1306
|
+
}
|
|
990
1307
|
const response = await this.http.post("/agents", {
|
|
991
1308
|
body,
|
|
992
1309
|
headers: Object.keys(headers).length > 0 ? headers : void 0
|
|
@@ -1010,9 +1327,15 @@ var AgentsResource = class extends BaseResource {
|
|
|
1010
1327
|
if (params.idempotencyKey) {
|
|
1011
1328
|
headers["Idempotency-Key"] = params.idempotencyKey;
|
|
1012
1329
|
}
|
|
1330
|
+
if (params.agentId) {
|
|
1331
|
+
headers["X-Agent-ID"] = params.agentId;
|
|
1332
|
+
}
|
|
1013
1333
|
if (params.instanceId) {
|
|
1014
1334
|
headers["X-Instance-ID"] = params.instanceId;
|
|
1015
1335
|
}
|
|
1336
|
+
if (params.traceId) {
|
|
1337
|
+
headers["X-Trace-ID"] = sanitizeHeaderValue(params.traceId);
|
|
1338
|
+
}
|
|
1016
1339
|
const response = await this.http.put(`/agents/${agentId}`, {
|
|
1017
1340
|
body,
|
|
1018
1341
|
headers: Object.keys(headers).length > 0 ? headers : void 0
|
|
@@ -1027,9 +1350,15 @@ var AgentsResource = class extends BaseResource {
|
|
|
1027
1350
|
*/
|
|
1028
1351
|
async delete(agentId, options) {
|
|
1029
1352
|
const headers = {};
|
|
1353
|
+
if (options?.agentId) {
|
|
1354
|
+
headers["X-Agent-ID"] = options.agentId;
|
|
1355
|
+
}
|
|
1030
1356
|
if (options?.instanceId) {
|
|
1031
1357
|
headers["X-Instance-ID"] = options.instanceId;
|
|
1032
1358
|
}
|
|
1359
|
+
if (options?.traceId) {
|
|
1360
|
+
headers["X-Trace-ID"] = sanitizeHeaderValue(options.traceId);
|
|
1361
|
+
}
|
|
1033
1362
|
await this.http.delete(`/agents/${agentId}`, {
|
|
1034
1363
|
headers: Object.keys(headers).length > 0 ? headers : void 0
|
|
1035
1364
|
});
|
|
@@ -1095,6 +1424,9 @@ var DelegationsResource = class extends BaseResource {
|
|
|
1095
1424
|
if (params?.instanceId) {
|
|
1096
1425
|
headers["X-Instance-ID"] = params.instanceId;
|
|
1097
1426
|
}
|
|
1427
|
+
if (params?.traceId) {
|
|
1428
|
+
headers["X-Trace-ID"] = sanitizeHeaderValue(params.traceId);
|
|
1429
|
+
}
|
|
1098
1430
|
const queryParams = {
|
|
1099
1431
|
delegationId: params?.delegationId,
|
|
1100
1432
|
agentId: params?.agentId,
|
|
@@ -1125,18 +1457,14 @@ var DelegationsResource = class extends BaseResource {
|
|
|
1125
1457
|
};
|
|
1126
1458
|
|
|
1127
1459
|
// src/resources/customers.ts
|
|
1128
|
-
var VALID_CUSTOMER_TYPES = /* @__PURE__ */ new Set(["party", "delegationInvitation"]);
|
|
1129
1460
|
var VALID_WALLET_ACCESS = /* @__PURE__ */ new Set(["granted", "denied", "noWallet"]);
|
|
1130
|
-
function isCustomerType(value) {
|
|
1131
|
-
return VALID_CUSTOMER_TYPES.has(value);
|
|
1132
|
-
}
|
|
1133
1461
|
function isWalletAccess(value) {
|
|
1134
1462
|
return typeof value === "string" && VALID_WALLET_ACCESS.has(value);
|
|
1135
1463
|
}
|
|
1136
1464
|
function unwrapCustomerResource(resource) {
|
|
1137
|
-
if (
|
|
1465
|
+
if (resource.type !== "party" || !resource.attributes) {
|
|
1138
1466
|
throw new NaturalError(
|
|
1139
|
-
`Unexpected resource format: expected type "party"
|
|
1467
|
+
`Unexpected resource format: expected type "party", got "${resource.type}"`
|
|
1140
1468
|
);
|
|
1141
1469
|
}
|
|
1142
1470
|
const { id, attributes } = resource;
|
|
@@ -1147,9 +1475,8 @@ function unwrapCustomerResource(resource) {
|
|
|
1147
1475
|
} : void 0;
|
|
1148
1476
|
return {
|
|
1149
1477
|
id,
|
|
1150
|
-
type:
|
|
1478
|
+
type: "party",
|
|
1151
1479
|
party,
|
|
1152
|
-
email: typeof attributes.email === "string" ? attributes.email : void 0,
|
|
1153
1480
|
status: typeof attributes.status === "string" ? attributes.status : "",
|
|
1154
1481
|
permissions: Array.isArray(attributes.permissions) ? attributes.permissions : [],
|
|
1155
1482
|
createdAt: typeof attributes.createdAt === "string" ? attributes.createdAt : "",
|
|
@@ -1158,6 +1485,22 @@ function unwrapCustomerResource(resource) {
|
|
|
1158
1485
|
walletAccess: isWalletAccess(attributes.walletAccess) ? attributes.walletAccess : "denied"
|
|
1159
1486
|
};
|
|
1160
1487
|
}
|
|
1488
|
+
function unwrapPendingInvitationResource(resource) {
|
|
1489
|
+
if (resource.type !== "delegationInvitation" || !resource.attributes) {
|
|
1490
|
+
throw new NaturalError(
|
|
1491
|
+
`Unexpected resource format: expected type "delegationInvitation", got "${resource.type}"`
|
|
1492
|
+
);
|
|
1493
|
+
}
|
|
1494
|
+
const { id, attributes } = resource;
|
|
1495
|
+
return {
|
|
1496
|
+
id,
|
|
1497
|
+
type: "delegationInvitation",
|
|
1498
|
+
email: typeof attributes.email === "string" ? attributes.email : void 0,
|
|
1499
|
+
status: typeof attributes.status === "string" ? attributes.status : "",
|
|
1500
|
+
permissions: Array.isArray(attributes.permissions) ? attributes.permissions : [],
|
|
1501
|
+
createdAt: typeof attributes.createdAt === "string" ? attributes.createdAt : ""
|
|
1502
|
+
};
|
|
1503
|
+
}
|
|
1161
1504
|
function unwrapCustomerList(response) {
|
|
1162
1505
|
if (!response?.data || !Array.isArray(response.data)) {
|
|
1163
1506
|
throw new NaturalError(
|
|
@@ -1171,28 +1514,59 @@ function unwrapCustomerList(response) {
|
|
|
1171
1514
|
nextCursor: pagination.nextCursor ?? null
|
|
1172
1515
|
};
|
|
1173
1516
|
}
|
|
1517
|
+
function unwrapPendingInvitationList(response) {
|
|
1518
|
+
if (!response?.data || !Array.isArray(response.data)) {
|
|
1519
|
+
throw new NaturalError(
|
|
1520
|
+
'Unexpected response format: missing "data" array in pending invitation list response'
|
|
1521
|
+
);
|
|
1522
|
+
}
|
|
1523
|
+
const pagination = response.meta?.pagination ?? {};
|
|
1524
|
+
return {
|
|
1525
|
+
items: response.data.map(unwrapPendingInvitationResource),
|
|
1526
|
+
hasMore: pagination.hasMore ?? false,
|
|
1527
|
+
nextCursor: pagination.nextCursor ?? null
|
|
1528
|
+
};
|
|
1529
|
+
}
|
|
1530
|
+
function buildRequest(params) {
|
|
1531
|
+
const headers = {};
|
|
1532
|
+
if (params?.agentId) {
|
|
1533
|
+
headers["X-Agent-ID"] = params.agentId;
|
|
1534
|
+
}
|
|
1535
|
+
if (params?.instanceId) {
|
|
1536
|
+
headers["X-Instance-ID"] = params.instanceId;
|
|
1537
|
+
}
|
|
1538
|
+
if (params?.traceId) {
|
|
1539
|
+
headers["X-Trace-ID"] = sanitizeHeaderValue(params.traceId);
|
|
1540
|
+
}
|
|
1541
|
+
const queryParams = {
|
|
1542
|
+
limit: params?.limit,
|
|
1543
|
+
cursor: params?.cursor
|
|
1544
|
+
};
|
|
1545
|
+
return {
|
|
1546
|
+
params: queryParams,
|
|
1547
|
+
headers: Object.keys(headers).length > 0 ? headers : void 0
|
|
1548
|
+
};
|
|
1549
|
+
}
|
|
1174
1550
|
var CustomersResource = class extends BaseResource {
|
|
1175
1551
|
/**
|
|
1176
|
-
* List customers who have delegated access to the partner.
|
|
1552
|
+
* List active customers who have delegated access to the partner.
|
|
1177
1553
|
*
|
|
1178
|
-
* @
|
|
1179
|
-
*
|
|
1554
|
+
* Returns only accepted delegations. Use {@link listInvitations} for pending
|
|
1555
|
+
* invitations that have not yet been accepted.
|
|
1180
1556
|
*/
|
|
1181
1557
|
async list(params) {
|
|
1182
|
-
const
|
|
1183
|
-
|
|
1184
|
-
headers["X-Instance-ID"] = params.instanceId;
|
|
1185
|
-
}
|
|
1186
|
-
const queryParams = {
|
|
1187
|
-
limit: params?.limit,
|
|
1188
|
-
cursor: params?.cursor
|
|
1189
|
-
};
|
|
1190
|
-
const response = await this.http.get("/customers", {
|
|
1191
|
-
params: queryParams,
|
|
1192
|
-
headers: Object.keys(headers).length > 0 ? headers : void 0
|
|
1193
|
-
});
|
|
1558
|
+
const request = buildRequest(params);
|
|
1559
|
+
const response = await this.http.get("/customers", request);
|
|
1194
1560
|
return unwrapCustomerList(response);
|
|
1195
1561
|
}
|
|
1562
|
+
/**
|
|
1563
|
+
* List pending customer invitations you've sent that have not yet been accepted.
|
|
1564
|
+
*/
|
|
1565
|
+
async listInvitations(params) {
|
|
1566
|
+
const request = buildRequest(params);
|
|
1567
|
+
const response = await this.http.get("/customers/invitations", request);
|
|
1568
|
+
return unwrapPendingInvitationList(response);
|
|
1569
|
+
}
|
|
1196
1570
|
};
|
|
1197
1571
|
|
|
1198
1572
|
// src/client.ts
|
|
@@ -1229,6 +1603,95 @@ var NaturalClient = class {
|
|
|
1229
1603
|
}
|
|
1230
1604
|
};
|
|
1231
1605
|
|
|
1606
|
+
// src/tool-names.ts
|
|
1607
|
+
var NATURAL_TOOL_NAMES = {
|
|
1608
|
+
CREATE_PAYMENT: "create_payment",
|
|
1609
|
+
GET_PAYMENT_STATUS: "get_payment_status",
|
|
1610
|
+
GET_ACCOUNT_BALANCE: "get_account_balance",
|
|
1611
|
+
LIST_TRANSACTIONS: "list_transactions",
|
|
1612
|
+
LIST_AGENTS: "list_agents",
|
|
1613
|
+
LIST_CUSTOMERS: "list_customers",
|
|
1614
|
+
LIST_CUSTOMER_INVITATIONS: "list_customer_invitations"
|
|
1615
|
+
};
|
|
1616
|
+
var WHSEC_PREFIX = "whsec_";
|
|
1617
|
+
var DEFAULT_TOLERANCE_SECONDS = 300;
|
|
1618
|
+
function getHeader(headers, name) {
|
|
1619
|
+
if (typeof headers.get === "function") {
|
|
1620
|
+
return headers.get(name) ?? void 0;
|
|
1621
|
+
}
|
|
1622
|
+
const lower = name.toLowerCase();
|
|
1623
|
+
for (const key of Object.keys(headers)) {
|
|
1624
|
+
if (key.toLowerCase() === lower) {
|
|
1625
|
+
return headers[key];
|
|
1626
|
+
}
|
|
1627
|
+
}
|
|
1628
|
+
return void 0;
|
|
1629
|
+
}
|
|
1630
|
+
function verifyWebhookSignature(body, headers, secret, options) {
|
|
1631
|
+
const tolerance = options?.toleranceInSeconds ?? DEFAULT_TOLERANCE_SECONDS;
|
|
1632
|
+
if (!Number.isInteger(tolerance) || tolerance <= 0) {
|
|
1633
|
+
throw new WebhookVerificationError("toleranceInSeconds must be a positive integer");
|
|
1634
|
+
}
|
|
1635
|
+
const webhookId = getHeader(headers, "webhook-id");
|
|
1636
|
+
if (!webhookId) {
|
|
1637
|
+
throw new WebhookVerificationError("webhook-id header is missing");
|
|
1638
|
+
}
|
|
1639
|
+
const timestampStr = getHeader(headers, "webhook-timestamp");
|
|
1640
|
+
if (!timestampStr) {
|
|
1641
|
+
throw new WebhookVerificationError("webhook-timestamp header is missing");
|
|
1642
|
+
}
|
|
1643
|
+
const signatureHeader = getHeader(headers, "webhook-signature");
|
|
1644
|
+
if (!signatureHeader) {
|
|
1645
|
+
throw new WebhookVerificationError("webhook-signature header is missing");
|
|
1646
|
+
}
|
|
1647
|
+
if (!/^\d+$/.test(timestampStr)) {
|
|
1648
|
+
throw new WebhookVerificationError(
|
|
1649
|
+
`Invalid webhook-timestamp: '${timestampStr}' is not a valid integer`
|
|
1650
|
+
);
|
|
1651
|
+
}
|
|
1652
|
+
const timestamp = parseInt(timestampStr, 10);
|
|
1653
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
1654
|
+
if (Math.abs(now - timestamp) > tolerance) {
|
|
1655
|
+
throw new WebhookVerificationError(
|
|
1656
|
+
timestamp > now ? "Webhook timestamp is too far in the future" : "Webhook timestamp is too old"
|
|
1657
|
+
);
|
|
1658
|
+
}
|
|
1659
|
+
let keyBytes;
|
|
1660
|
+
const base64Part = secret.startsWith(WHSEC_PREFIX) ? secret.slice(WHSEC_PREFIX.length) : secret;
|
|
1661
|
+
if (!/^[A-Za-z0-9+/]*={0,2}$/.test(base64Part) || base64Part.length === 0) {
|
|
1662
|
+
throw new WebhookVerificationError("Invalid signing secret: could not base64-decode");
|
|
1663
|
+
}
|
|
1664
|
+
try {
|
|
1665
|
+
keyBytes = Buffer.from(base64Part, "base64");
|
|
1666
|
+
if (keyBytes.length === 0) {
|
|
1667
|
+
throw new Error("empty key");
|
|
1668
|
+
}
|
|
1669
|
+
} catch {
|
|
1670
|
+
throw new WebhookVerificationError("Invalid signing secret: could not base64-decode");
|
|
1671
|
+
}
|
|
1672
|
+
const bodyStr = typeof body === "string" ? body : Buffer.from(body).toString("utf-8");
|
|
1673
|
+
const signedContent = `${webhookId}.${timestampStr}.${bodyStr}`;
|
|
1674
|
+
const expectedSig = createHmac("sha256", keyBytes).update(signedContent).digest("base64");
|
|
1675
|
+
const candidates = signatureHeader.split(" ");
|
|
1676
|
+
for (const candidate of candidates) {
|
|
1677
|
+
if (!candidate.startsWith("v1,")) continue;
|
|
1678
|
+
const candidateSig = candidate.slice(3);
|
|
1679
|
+
const expectedBuf = Buffer.from(expectedSig, "utf-8");
|
|
1680
|
+
const candidateBuf = Buffer.from(candidateSig, "utf-8");
|
|
1681
|
+
if (expectedBuf.length !== candidateBuf.length) continue;
|
|
1682
|
+
if (timingSafeEqual(expectedBuf, candidateBuf)) {
|
|
1683
|
+
try {
|
|
1684
|
+
return JSON.parse(bodyStr);
|
|
1685
|
+
} catch {
|
|
1686
|
+
throw new WebhookVerificationError("Webhook signature is valid but body is not valid JSON");
|
|
1687
|
+
}
|
|
1688
|
+
}
|
|
1689
|
+
}
|
|
1690
|
+
throw new WebhookVerificationError(
|
|
1691
|
+
"Webhook signature does not match \u2014 ensure you are using the raw request body"
|
|
1692
|
+
);
|
|
1693
|
+
}
|
|
1694
|
+
|
|
1232
1695
|
// src/types/transactions.ts
|
|
1233
1696
|
var TransactionTypeFilter = /* @__PURE__ */ ((TransactionTypeFilter2) => {
|
|
1234
1697
|
TransactionTypeFilter2["PAYMENT"] = "payment";
|
|
@@ -1237,6 +1700,6 @@ var TransactionTypeFilter = /* @__PURE__ */ ((TransactionTypeFilter2) => {
|
|
|
1237
1700
|
return TransactionTypeFilter2;
|
|
1238
1701
|
})(TransactionTypeFilter || {});
|
|
1239
1702
|
|
|
1240
|
-
export { AuthenticationError, InsufficientFundsError, InvalidRequestError, NaturalClient, NaturalError, PaymentError, RateLimitError, RecipientNotFoundError, ServerError, TransactionTypeFilter, VERSION, bindContext, clearContext, configureLogging, getContext, getLogger, logApiCall, logError, logToolCall, parseApiKeyEnv, runWithContext, validateBaseUrl };
|
|
1703
|
+
export { AuthenticationError, InsufficientFundsError, InvalidRequestError, NATURAL_TOOL_NAMES, 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
1704
|
//# sourceMappingURL=index.js.map
|
|
1242
1705
|
//# sourceMappingURL=index.js.map
|