conduyt-mcp 4.83.0 → 4.84.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/README.md +2 -0
- package/dist/client.d.ts +7 -0
- package/dist/client.js +57 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -387,6 +387,8 @@ object too large for the bound is left out and named in `_omitted`. Every HTTP r
|
|
|
387
387
|
Conduyt API 503: Contact merge in progress [Retry-After: 5s] {"code":"merge_in_progress"}
|
|
388
388
|
```
|
|
389
389
|
|
|
390
|
+
The contact merge route (`POST /contacts/merge`, no tool today) answers a retryable 503 with `Retry-After` and the code `merge_contacts_busy` (a contact stayed busy for the whole lock budget) or `merge_budget_exhausted` (the request's time budget ran out), nothing changed. Any tool that reaches it surfaces the refusal in this grammar and never retries it (one API job per key); the caller retries after the stated seconds.
|
|
391
|
+
|
|
390
392
|
## Setup
|
|
391
393
|
|
|
392
394
|
```bash
|
package/dist/client.d.ts
CHANGED
|
@@ -37,6 +37,13 @@ export interface RequestOptions {
|
|
|
37
37
|
* ALWAYS_WHOLE_KEYS; a nested object that does not fit is left out and named in `_omitted`. JSON is never cut mid-object.
|
|
38
38
|
*/
|
|
39
39
|
export declare function renderErrorDetails(body: Record<string, unknown>): string;
|
|
40
|
+
/**
|
|
41
|
+
* The ONE Retry-After parser. RFC 9110: delay-seconds (digits; a value too long for a number saturates at 2^31-1) or an
|
|
42
|
+
* HTTP-date in one of the three §5.6.7 forms, parsed explicitly and always as UTC (never the host timezone): IMF-fixdate,
|
|
43
|
+
* RFC 850 (two-digit year: more than 50 years ahead of `nowMs` means the previous century) and asctime. A date becomes the
|
|
44
|
+
* remaining seconds, floored at 0. Absent, blank or anything else (ISO timestamps, other zones, "-3") is null.
|
|
45
|
+
*/
|
|
46
|
+
export declare function parseRetryAfter(raw: string | null | undefined, nowMs?: number): number | null;
|
|
40
47
|
export declare class ConduytClient {
|
|
41
48
|
private baseUrl;
|
|
42
49
|
private apiKey;
|
package/dist/client.js
CHANGED
|
@@ -104,6 +104,58 @@ export function renderErrorDetails(body) {
|
|
|
104
104
|
out._omitted = omitted;
|
|
105
105
|
return JSON.stringify(out);
|
|
106
106
|
}
|
|
107
|
+
const RA_MAX_SECONDS = 2_147_483_647; // delay-seconds is unbounded 1*DIGIT: a longer value saturates here instead of becoming null
|
|
108
|
+
const RA_MONTHS = { Jan: 0, Feb: 1, Mar: 2, Apr: 3, May: 4, Jun: 5, Jul: 6, Aug: 7, Sep: 8, Oct: 9, Nov: 10, Dec: 11 };
|
|
109
|
+
const RA_MON = "Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec";
|
|
110
|
+
const RA_IMF = new RegExp(`^(?:Mon|Tue|Wed|Thu|Fri|Sat|Sun), (\\d{2}) (${RA_MON}) (\\d{4}) (\\d{2}):(\\d{2}):(\\d{2}) GMT$`);
|
|
111
|
+
const RA_850 = new RegExp(`^(?:Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday), (\\d{2})-(${RA_MON})-(\\d{2}) (\\d{2}):(\\d{2}):(\\d{2}) GMT$`);
|
|
112
|
+
const RA_ASC = new RegExp(`^(?:Mon|Tue|Wed|Thu|Fri|Sat|Sun) (${RA_MON}) ([ \\d]\\d) (\\d{2}):(\\d{2}):(\\d{2}) (\\d{4})$`);
|
|
113
|
+
/** A UTC timestamp for the parts, or NaN when any part is out of range (31 Feb, 25:00, ...). */
|
|
114
|
+
function raUtc(y, mo, d, h, mi, s) {
|
|
115
|
+
if (y < 1900 || h > 23 || mi > 59 || s > 60)
|
|
116
|
+
return NaN; // RFC 5322: years below 1900 are not dates
|
|
117
|
+
const t = Date.UTC(y, mo, d, h, mi, Math.min(s, 59));
|
|
118
|
+
const back = new Date(t);
|
|
119
|
+
return back.getUTCFullYear() === y && back.getUTCMonth() === mo && back.getUTCDate() === d ? t : NaN;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* The ONE Retry-After parser. RFC 9110: delay-seconds (digits; a value too long for a number saturates at 2^31-1) or an
|
|
123
|
+
* HTTP-date in one of the three §5.6.7 forms, parsed explicitly and always as UTC (never the host timezone): IMF-fixdate,
|
|
124
|
+
* RFC 850 (two-digit year: more than 50 years ahead of `nowMs` means the previous century) and asctime. A date becomes the
|
|
125
|
+
* remaining seconds, floored at 0. Absent, blank or anything else (ISO timestamps, other zones, "-3") is null.
|
|
126
|
+
*/
|
|
127
|
+
export function parseRetryAfter(raw, nowMs = Date.now()) {
|
|
128
|
+
if (raw === null || raw === undefined)
|
|
129
|
+
return null;
|
|
130
|
+
const v = raw.trim();
|
|
131
|
+
if (v === "")
|
|
132
|
+
return null;
|
|
133
|
+
if (/^\d+(\.\d+)?$/.test(v)) {
|
|
134
|
+
const n = Number(v);
|
|
135
|
+
return Number.isNaN(n) ? null : Math.min(n, RA_MAX_SECONDS);
|
|
136
|
+
}
|
|
137
|
+
let t = NaN;
|
|
138
|
+
let m;
|
|
139
|
+
if ((m = RA_IMF.exec(v)))
|
|
140
|
+
t = raUtc(Number(m[3]), RA_MONTHS[m[2]], Number(m[1]), Number(m[4]), Number(m[5]), Number(m[6]));
|
|
141
|
+
else if ((m = RA_850.exec(v))) {
|
|
142
|
+
const century = Math.floor(new Date(nowMs).getUTCFullYear() / 100) * 100;
|
|
143
|
+
const mo = RA_MONTHS[m[2]], d = Number(m[1]), h = Number(m[4]), mi = Number(m[5]), sec = Number(m[6]);
|
|
144
|
+
t = raUtc(century + Number(m[3]), mo, d, h, mi, sec);
|
|
145
|
+
// RFC 9110: a timestamp more than 50 years in the future (the same instant 50 calendar years on, UTC) is the previous century
|
|
146
|
+
// (the day is clamped to the month's last day: 29 Feb + 50 years is 28 Feb, never 1 Mar)
|
|
147
|
+
const n = new Date(nowMs);
|
|
148
|
+
const ly = n.getUTCFullYear() + 50, lm = n.getUTCMonth();
|
|
149
|
+
const limit = Date.UTC(ly, lm, Math.min(n.getUTCDate(), new Date(Date.UTC(ly, lm + 1, 0)).getUTCDate()), n.getUTCHours(), n.getUTCMinutes(), n.getUTCSeconds(), n.getUTCMilliseconds());
|
|
150
|
+
if (!Number.isNaN(t) && t > limit)
|
|
151
|
+
t = raUtc(century - 100 + Number(m[3]), mo, d, h, mi, sec);
|
|
152
|
+
}
|
|
153
|
+
else if ((m = RA_ASC.exec(v)))
|
|
154
|
+
t = raUtc(Number(m[6]), RA_MONTHS[m[1]], Number(m[2].trim()), Number(m[3]), Number(m[4]), Number(m[5]));
|
|
155
|
+
if (Number.isNaN(t))
|
|
156
|
+
return null;
|
|
157
|
+
return Math.max(0, Math.ceil((t - nowMs) / 1000));
|
|
158
|
+
}
|
|
107
159
|
export class ConduytClient {
|
|
108
160
|
baseUrl;
|
|
109
161
|
apiKey;
|
|
@@ -240,9 +292,9 @@ export class ConduytClient {
|
|
|
240
292
|
* until the rest of the text is valid JSON (a server message may itself contain a brace).
|
|
241
293
|
*/
|
|
242
294
|
httpError(res, text) {
|
|
243
|
-
const
|
|
244
|
-
const retryAfter =
|
|
245
|
-
const retryAfterOk =
|
|
295
|
+
const retryAfterParsed = parseRetryAfter(res.headers.get("retry-after"));
|
|
296
|
+
const retryAfter = retryAfterParsed ?? NaN;
|
|
297
|
+
const retryAfterOk = retryAfterParsed !== null;
|
|
246
298
|
const raTag = retryAfterOk ? ` [Retry-After: ${retryAfter}s]` : "";
|
|
247
299
|
let msg = res.statusText;
|
|
248
300
|
try {
|
|
@@ -275,8 +327,8 @@ export class ConduytClient {
|
|
|
275
327
|
});
|
|
276
328
|
}
|
|
277
329
|
retryDelayMs(res, attemptIndex) {
|
|
278
|
-
const retryAfter =
|
|
279
|
-
if (
|
|
330
|
+
const retryAfter = parseRetryAfter(res?.headers.get("retry-after") ?? null);
|
|
331
|
+
if (retryAfter !== null) {
|
|
280
332
|
return Math.min(retryAfter * 1000, MAX_RETRY_AFTER_MS);
|
|
281
333
|
}
|
|
282
334
|
return Math.min(this.retryBaseMs * 2 ** attemptIndex, MAX_RETRY_AFTER_MS);
|