@tangle-network/agent-integrations 0.50.0 → 0.52.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.
@@ -1008,6 +1008,24 @@ function declarativeRestConnector(spec) {
1008
1008
  async executeMutation(inv) {
1009
1009
  const op = readOperation(spec, inv.capabilityName, "mutation");
1010
1010
  const response = await executeRestRequest(spec, op.request, inv, requiredArgsOf(op.parameters));
1011
+ if (response.outcome === "conflict") {
1012
+ return {
1013
+ status: "conflict",
1014
+ alternatives: [],
1015
+ // The upstream's parsed body — the contract calls this "the current
1016
+ // authoritative state", so it must be what the provider returned,
1017
+ // not the transport's own wrapper.
1018
+ currentState: response.data,
1019
+ message: response.message || `${spec.displayName} rejected the write on a state conflict`
1020
+ };
1021
+ }
1022
+ if (response.outcome === "rate-limited") {
1023
+ return {
1024
+ status: "rate-limited",
1025
+ retryAfterMs: response.retryAfterMs ?? DEFAULT_RETRY_AFTER_MS,
1026
+ message: response.message || `${spec.displayName} throttled the write`
1027
+ };
1028
+ }
1011
1029
  return {
1012
1030
  status: "committed",
1013
1031
  data: response.data,
@@ -1087,11 +1105,12 @@ async function executeRestRequest(spec, request, inv, requiredArgs) {
1087
1105
  const awsRegion = aws ? resolveAwsRegion(aws, inv.source.metadata, placement) : void 0;
1088
1106
  let baseUrl = resolveBaseUrl(spec.baseUrl, inv.source.metadata);
1089
1107
  if (aws) baseUrl = applyRegionTemplate(baseUrl, awsRegion, aws.endpoint);
1090
- const renderedPath = interpolate(request.path, inv.args).replace(/^\/+/, "");
1108
+ const scope = renderScope(inv);
1109
+ const renderedPath = interpolate(request.path, scope).replace(/^\/+/, "");
1091
1110
  const baseWithSlash = baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
1092
1111
  const url = new URL(renderedPath, baseWithSlash);
1093
1112
  for (const [key, value] of Object.entries(request.query ?? {})) {
1094
- const rendered = renderQueryValue(value, inv.args);
1113
+ const rendered = renderQueryValue(value, scope);
1095
1114
  if (rendered !== void 0 && rendered !== "") url.searchParams.set(key, String(rendered));
1096
1115
  }
1097
1116
  const requestHeaders = request.headers ?? {};
@@ -1100,7 +1119,7 @@ async function executeRestRequest(spec, request, inv, requiredArgs) {
1100
1119
  const headers = {
1101
1120
  accept: "application/json",
1102
1121
  ...spec.defaultHeaders,
1103
- ...renderHeaders(renderableHeaders, inv.args)
1122
+ ...renderHeaders(renderableHeaders, scope)
1104
1123
  };
1105
1124
  if (hostOverride) url.host = hostOverride.replace(/\{region\}/g, awsRegion);
1106
1125
  if (inv.expectedEtag) headers["if-match"] = inv.expectedEtag;
@@ -1108,7 +1127,7 @@ async function executeRestRequest(spec, request, inv, requiredArgs) {
1108
1127
  if (sendsBody && getHeaderCI(headers, "content-type") === void 0) {
1109
1128
  headers["content-type"] = "application/json";
1110
1129
  }
1111
- const bodyString = sendsBody ? JSON.stringify(resolveBody(request.body, inv.args, requiredArgs)) : void 0;
1130
+ const bodyString = sendsBody ? JSON.stringify(resolveBody(request.body, inv.args, scope, requiredArgs)) : void 0;
1112
1131
  if (placement.kind === "aws-sigv4") {
1113
1132
  signAwsRequest(headers, url, {
1114
1133
  method: request.method,
@@ -1130,21 +1149,21 @@ async function executeRestRequest(spec, request, inv, requiredArgs) {
1130
1149
  throw new CredentialsExpired(`${spec.displayName} rejected credentials (${res.status})`, inv.source.id);
1131
1150
  }
1132
1151
  if (res.status === 409 || res.status === 412) {
1152
+ const text2 = await safeErrorText(res);
1133
1153
  return {
1134
- data: {
1135
- status: "conflict",
1136
- message: await safeErrorText(res)
1137
- },
1154
+ data: parseBodyText(text2),
1155
+ outcome: "conflict",
1156
+ message: text2,
1138
1157
  etag: res.headers.get("etag") ?? void 0
1139
1158
  };
1140
1159
  }
1141
1160
  if (res.status === 429) {
1161
+ const text2 = await safeErrorText(res);
1142
1162
  return {
1143
- data: {
1144
- status: "rate-limited",
1145
- retryAfter: res.headers.get("retry-after") ?? void 0,
1146
- message: await safeErrorText(res)
1147
- }
1163
+ data: parseBodyText(text2),
1164
+ outcome: "rate-limited",
1165
+ message: text2,
1166
+ retryAfterMs: retryAfterMsFromHeader(res.headers.get("retry-after"))
1148
1167
  };
1149
1168
  }
1150
1169
  if (request.existenceCheck && (res.status === 204 || res.status === 404)) {
@@ -1218,10 +1237,34 @@ function signAwsRequest(headers, url, opts) {
1218
1237
  headers["x-amz-date"] = signed.amzDate;
1219
1238
  headers.authorization = signed.authorization;
1220
1239
  }
1221
- function resolveBody(body, args, requiredArgs) {
1240
+ function resolveBody(body, args, scope, requiredArgs) {
1222
1241
  if (!body || body === "args") return args;
1223
- if (typeof body === "string") return renderValue(body, args, requiredArgs);
1224
- return renderObject(body, args, requiredArgs);
1242
+ if (typeof body === "string") return renderValue(body, scope, requiredArgs);
1243
+ return renderObject(body, scope, requiredArgs);
1244
+ }
1245
+ var DEFAULT_RETRY_AFTER_MS = 6e4;
1246
+ var MIN_RETRY_AFTER_MS = 1e3;
1247
+ function parseBodyText(text) {
1248
+ if (!text) return null;
1249
+ try {
1250
+ return JSON.parse(text);
1251
+ } catch {
1252
+ return { raw: text };
1253
+ }
1254
+ }
1255
+ function retryAfterMsFromHeader(raw) {
1256
+ if (typeof raw === "string" && raw.trim()) {
1257
+ const seconds = Number(raw);
1258
+ if (Number.isFinite(seconds) && seconds >= 0) {
1259
+ return Math.max(MIN_RETRY_AFTER_MS, Math.round(seconds * 1e3));
1260
+ }
1261
+ const at = Date.parse(raw);
1262
+ if (Number.isFinite(at)) return Math.max(MIN_RETRY_AFTER_MS, at - Date.now());
1263
+ }
1264
+ return DEFAULT_RETRY_AFTER_MS;
1265
+ }
1266
+ function renderScope(inv) {
1267
+ return { ...inv.args, connection: inv.source.metadata };
1225
1268
  }
1226
1269
  function renderHeaders(headers, args) {
1227
1270
  return Object.fromEntries(Object.entries(headers).map(([key, value]) => [key, interpolate(value, args)]));
@@ -18364,8 +18407,71 @@ var quickbooksConnector = declarativeRestConnector({
18364
18407
  defaultHeaders: {
18365
18408
  accept: "application/json"
18366
18409
  },
18367
- test: { method: "GET", path: "companyinfo/{realmId}", query: { minorversion: "70" } },
18410
+ // `realmId` is pinned at connect time, not passed per call, so the health
18411
+ // check reads it from connection metadata. Written as `{realmId}` it resolved
18412
+ // against the (empty) test argument map and threw before any request was
18413
+ // made — the connection test could never pass, for any connection.
18414
+ test: { method: "GET", path: "companyinfo/{connection.realmId}", query: { minorversion: "70" } },
18368
18415
  capabilities: [
18416
+ {
18417
+ // The reports namespace is NOT reachable through `entities.query` — QBO
18418
+ // exposes the accountant-grade statements only under /reports/{name}.
18419
+ // These four are the tax/bookkeeping read surface; without them a tax
18420
+ // agent can enumerate ledger rows but cannot obtain a P&L or a balance
18421
+ // sheet, which is what a return is actually built from.
18422
+ name: "reports.get",
18423
+ class: "read",
18424
+ description: "Run a QuickBooks Online report. `reportName` is the QBO report id (ProfitAndLoss, BalanceSheet, TrialBalance, GeneralLedger, CashFlow, AgedReceivables, AgedPayables).",
18425
+ parameters: {
18426
+ type: "object",
18427
+ properties: {
18428
+ reportName: {
18429
+ type: "string",
18430
+ enum: [
18431
+ "ProfitAndLoss",
18432
+ "BalanceSheet",
18433
+ "TrialBalance",
18434
+ "GeneralLedger",
18435
+ "CashFlow",
18436
+ "AgedReceivables",
18437
+ "AgedPayables"
18438
+ ]
18439
+ },
18440
+ start_date: { type: "string", description: "YYYY-MM-DD (inclusive)." },
18441
+ end_date: { type: "string", description: "YYYY-MM-DD (inclusive)." },
18442
+ accounting_method: { type: "string", enum: ["Cash", "Accrual"] },
18443
+ date_macro: { type: "string", description: 'e.g. "Last Fiscal Year" \u2014 alternative to start/end dates.' },
18444
+ summarize_column_by: { type: "string", description: "e.g. Month, Quarter, Year, Total." },
18445
+ minorversion: { type: "string" }
18446
+ },
18447
+ required: ["reportName"]
18448
+ },
18449
+ request: {
18450
+ method: "GET",
18451
+ path: "reports/{reportName}",
18452
+ query: {
18453
+ start_date: "{start_date}",
18454
+ end_date: "{end_date}",
18455
+ accounting_method: "{accounting_method}",
18456
+ date_macro: "{date_macro}",
18457
+ summarize_column_by: "{summarize_column_by}",
18458
+ minorversion: "{minorversion}"
18459
+ }
18460
+ },
18461
+ requiredScopes: ["com.intuit.quickbooks.accounting"]
18462
+ },
18463
+ {
18464
+ name: "companyinfo.get",
18465
+ class: "read",
18466
+ description: "Read the connected QuickBooks company profile (legal name, EIN/company id, fiscal year start, country) for the realm this connection was authorized against.",
18467
+ parameters: { type: "object", properties: { minorversion: { type: "string" } } },
18468
+ request: {
18469
+ method: "GET",
18470
+ path: "companyinfo/{connection.realmId}",
18471
+ query: { minorversion: "{minorversion}" }
18472
+ },
18473
+ requiredScopes: ["com.intuit.quickbooks.accounting"]
18474
+ },
18369
18475
  {
18370
18476
  name: "entities.query",
18371
18477
  class: "read",
@@ -18722,7 +18828,15 @@ var xeroConnector = declarativeRestConnector({
18722
18828
  kind: "oauth2",
18723
18829
  authorizationUrl: "https://login.xero.com/identity/connect/authorize",
18724
18830
  tokenUrl: "https://identity.xero.com/connect/token",
18725
- scopes: ["offline_access", "accounting.contacts", "accounting.transactions", "accounting.settings.read"],
18831
+ // `accounting.reports.read` is a distinct Xero scope — the reports
18832
+ // namespace is not covered by settings/transactions and 403s without it.
18833
+ scopes: [
18834
+ "offline_access",
18835
+ "accounting.contacts",
18836
+ "accounting.transactions",
18837
+ "accounting.settings.read",
18838
+ "accounting.reports.read"
18839
+ ],
18726
18840
  clientIdEnv: "XERO_OAUTH_CLIENT_ID",
18727
18841
  clientSecretEnv: "XERO_OAUTH_CLIENT_SECRET"
18728
18842
  },
@@ -18732,6 +18846,54 @@ var xeroConnector = declarativeRestConnector({
18732
18846
  defaultHeaders: { accept: "application/json" },
18733
18847
  test: { method: "GET", path: "/connections" },
18734
18848
  capabilities: [
18849
+ {
18850
+ // Every other Xero capability REQUIRES `tenantId`, and a tenant id is an
18851
+ // opaque GUID no caller can know before asking. Without this read the
18852
+ // connector is unusable by an agent: the only place the id appeared was
18853
+ // the health check, which discards its body.
18854
+ name: "tenants.list",
18855
+ class: "read",
18856
+ description: "List the Xero organizations (tenants) this connection is authorized for. Call this FIRST \u2014 every other Xero capability requires the `tenantId` returned here.",
18857
+ parameters: { type: "object", properties: {} },
18858
+ request: { method: "GET", path: "/connections" }
18859
+ },
18860
+ {
18861
+ // Xero's reporting surface, like QBO's, is a separate namespace from the
18862
+ // record endpoints. These are the statements a return or a books review
18863
+ // is actually built from.
18864
+ name: "reports.get",
18865
+ class: "read",
18866
+ description: "Run a Xero accounting report. `reportName` is the Xero report id (ProfitAndLoss, BalanceSheet, TrialBalance, BankSummary, AgedReceivablesByContact, AgedPayablesByContact).",
18867
+ parameters: {
18868
+ type: "object",
18869
+ properties: {
18870
+ tenantId: { type: "string", description: "From tenants.list." },
18871
+ reportName: {
18872
+ type: "string",
18873
+ enum: [
18874
+ "ProfitAndLoss",
18875
+ "BalanceSheet",
18876
+ "TrialBalance",
18877
+ "BankSummary",
18878
+ "AgedReceivablesByContact",
18879
+ "AgedPayablesByContact"
18880
+ ]
18881
+ },
18882
+ fromDate: { type: "string", description: "YYYY-MM-DD." },
18883
+ toDate: { type: "string", description: "YYYY-MM-DD." },
18884
+ date: { type: "string", description: "YYYY-MM-DD \u2014 point-in-time reports (BalanceSheet, TrialBalance)." },
18885
+ contactId: { type: "string", description: "Required by the aged receivables/payables reports." }
18886
+ },
18887
+ required: ["tenantId", "reportName"]
18888
+ },
18889
+ request: {
18890
+ method: "GET",
18891
+ path: "/api.xro/2.0/Reports/{reportName}",
18892
+ query: { fromDate: "{fromDate}", toDate: "{toDate}", date: "{date}", contactID: "{contactId}" },
18893
+ headers: { "xero-tenant-id": "{tenantId}" }
18894
+ },
18895
+ requiredScopes: ["accounting.reports.read"]
18896
+ },
18735
18897
  {
18736
18898
  name: "contacts.search",
18737
18899
  class: "read",
@@ -125794,4 +125956,4 @@ export {
125794
125956
  demandbaseConnector,
125795
125957
  adapters_exports
125796
125958
  };
125797
- //# sourceMappingURL=chunk-RM6JJK4Q.js.map
125959
+ //# sourceMappingURL=chunk-XEJKVCXB.js.map