@withpica/mcp-sdk 4.0.0 → 4.1.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/CHANGELOG.md +38 -0
- package/dist/index.d.ts +101 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +83 -8
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -147,6 +147,35 @@ export class EntityNotFoundError extends ApiError {
|
|
|
147
147
|
function isRetryableStatus(status) {
|
|
148
148
|
return status === 429 || status === 502 || status === 503 || status === 504;
|
|
149
149
|
}
|
|
150
|
+
/**
|
|
151
|
+
* A write (POST, PATCH) is resent only when the server said it did nothing:
|
|
152
|
+
* 429 and 503 are refusals before the route runs. A timeout, a dropped
|
|
153
|
+
* connection, a 502 or a 504 can each follow a write that DID happen, and
|
|
154
|
+
* resending it repeats the write. On prod 2026-10-29 a 94 s works create
|
|
155
|
+
* timed out at 30 s; the resend found the song the first attempt had just
|
|
156
|
+
* made and answered WORK_ALREADY_EXISTS naming it, and the agent then made by
|
|
157
|
+
* hand the recording the first attempt was still about to make (ops 198deea1).
|
|
158
|
+
* GET, PUT and DELETE are idempotent and keep every retry.
|
|
159
|
+
*/
|
|
160
|
+
function isWriteMethod(method) {
|
|
161
|
+
const m = (method ?? "GET").toUpperCase();
|
|
162
|
+
return m === "POST" || m === "PATCH";
|
|
163
|
+
}
|
|
164
|
+
function isRefusedBeforeRunning(status) {
|
|
165
|
+
return status === 429 || status === 503;
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* A write whose outcome is unknown: it timed out, lost its connection, or got
|
|
169
|
+
* a 502/504, and was not resent because it may have been applied. Look before
|
|
170
|
+
* trying again.
|
|
171
|
+
*/
|
|
172
|
+
export class WriteOutcomeUnknownError extends ApiError {
|
|
173
|
+
code = "WRITE_OUTCOME_UNKNOWN";
|
|
174
|
+
constructor(method, path, cause, status = 0) {
|
|
175
|
+
super(`${method} ${path} did not answer (${cause}). It may still have been applied, so it was not sent again: check whether it took effect (for example, query for what it creates) before retrying.`, status, false);
|
|
176
|
+
this.name = "WriteOutcomeUnknownError";
|
|
177
|
+
}
|
|
178
|
+
}
|
|
150
179
|
function getTimeoutForPath(path) {
|
|
151
180
|
return LONG_TIMEOUT_PATTERNS.some((p) => path.includes(p))
|
|
152
181
|
? LONG_TIMEOUT_MS
|
|
@@ -255,6 +284,15 @@ class BaseResource {
|
|
|
255
284
|
}
|
|
256
285
|
async fetchWithRetry(url, init, timeoutMs) {
|
|
257
286
|
let lastError;
|
|
287
|
+
const write = isWriteMethod(init.method);
|
|
288
|
+
const pathOf = () => {
|
|
289
|
+
try {
|
|
290
|
+
return new URL(url).pathname;
|
|
291
|
+
}
|
|
292
|
+
catch {
|
|
293
|
+
return url;
|
|
294
|
+
}
|
|
295
|
+
};
|
|
258
296
|
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
|
259
297
|
try {
|
|
260
298
|
const response = await this.fetchWithTimeout(url, init, timeoutMs);
|
|
@@ -266,6 +304,12 @@ class BaseResource {
|
|
|
266
304
|
if (response.status === 401 || response.status === 403) {
|
|
267
305
|
throw new ApiError(`Authentication failed: ${response.status} ${errorText}`, response.status, false);
|
|
268
306
|
}
|
|
307
|
+
// A write that may have run is never resent (isWriteMethod).
|
|
308
|
+
if (write &&
|
|
309
|
+
isRetryableStatus(response.status) &&
|
|
310
|
+
!isRefusedBeforeRunning(response.status)) {
|
|
311
|
+
throw new WriteOutcomeUnknownError(init.method ?? "POST", pathOf(), `${response.status} ${errorText.slice(0, 120)}`, response.status);
|
|
312
|
+
}
|
|
269
313
|
// Retryable errors — retry with backoff
|
|
270
314
|
if (isRetryableStatus(response.status) && attempt < MAX_RETRIES) {
|
|
271
315
|
const retryAfter = response.headers.get("retry-after");
|
|
@@ -291,6 +335,10 @@ class BaseResource {
|
|
|
291
335
|
catch (err) {
|
|
292
336
|
if (err instanceof ApiError && !err.retryable)
|
|
293
337
|
throw err;
|
|
338
|
+
// Timed out or lost the connection: a write may have run.
|
|
339
|
+
if (write && !(err instanceof CatalogueNotHonouredError)) {
|
|
340
|
+
throw new WriteOutcomeUnknownError(init.method ?? "POST", pathOf(), err.message);
|
|
341
|
+
}
|
|
294
342
|
lastError = err;
|
|
295
343
|
if (attempt < MAX_RETRIES) {
|
|
296
344
|
const delayMs = RETRY_BASE_MS * Math.pow(2, attempt);
|
|
@@ -1143,6 +1191,8 @@ class AudioFilesResource extends BaseResource {
|
|
|
1143
1191
|
queryParams.set("limit", String(params.limit));
|
|
1144
1192
|
if (params?.offset)
|
|
1145
1193
|
queryParams.set("offset", String(params.offset));
|
|
1194
|
+
if (params?.cursor)
|
|
1195
|
+
queryParams.set("cursor", params.cursor);
|
|
1146
1196
|
const query = queryParams.toString();
|
|
1147
1197
|
return this.request("GET", `/admin/audio-files${query ? `?${query}` : ""}`);
|
|
1148
1198
|
}
|
|
@@ -3504,20 +3554,30 @@ class UsersResource extends BaseResource {
|
|
|
3504
3554
|
async getMyProfile() {
|
|
3505
3555
|
return this.request("GET", "/admin/users/profile");
|
|
3506
3556
|
}
|
|
3557
|
+
/**
|
|
3558
|
+
* Write your bio: the biography on your own person record (one bio per
|
|
3559
|
+
* person). Refused 409 NO_LINKED_PERSON when your account has no person.
|
|
3560
|
+
*/
|
|
3507
3561
|
async setBio(bio) {
|
|
3508
|
-
return this.request("POST", "/admin/users/bio", {
|
|
3509
|
-
|
|
3510
|
-
|
|
3562
|
+
return this.request("POST", "/admin/users/bio", { bio });
|
|
3563
|
+
}
|
|
3564
|
+
/**
|
|
3565
|
+
* Settle a waiting `bio_choice`: "current" keeps your bio, "earlier" uses
|
|
3566
|
+
* the earlier one. The text not kept goes to `set_aside_bios`, never
|
|
3567
|
+
* deleted. Refused 409 NO_CHOICE_WAITING when nothing is waiting.
|
|
3568
|
+
*/
|
|
3569
|
+
async chooseBio(keep) {
|
|
3570
|
+
return this.request("POST", "/admin/users/bio", { choose: keep });
|
|
3511
3571
|
}
|
|
3512
3572
|
async setAvatarUrl(avatarUrl) {
|
|
3513
3573
|
return this.request("POST", "/admin/users/avatar", { avatar_url: avatarUrl });
|
|
3514
3574
|
}
|
|
3515
3575
|
/**
|
|
3516
|
-
*
|
|
3517
|
-
*
|
|
3518
|
-
*
|
|
3519
|
-
*
|
|
3520
|
-
*
|
|
3576
|
+
* @deprecated Retired: there is one bio now. Your profile bio IS the
|
|
3577
|
+
* biography on your own person record, so there is nothing to import, and
|
|
3578
|
+
* the server answers 410 RETIRED. Read the bio with `getMyProfile` and
|
|
3579
|
+
* change it with `setBio`. Kept so an existing caller gets that message
|
|
3580
|
+
* rather than a missing method; removed in the next major.
|
|
3521
3581
|
*/
|
|
3522
3582
|
async importBioFromPerson(params) {
|
|
3523
3583
|
return this.request("POST", "/admin/users/import-bio-from-person", params ?? {});
|
|
@@ -3746,6 +3806,19 @@ class ShareTraceResource extends BaseResource {
|
|
|
3746
3806
|
return this.request("GET", `/admin/share-trace?${params.toString()}`);
|
|
3747
3807
|
}
|
|
3748
3808
|
}
|
|
3809
|
+
class SongAccessResource extends BaseResource {
|
|
3810
|
+
/** GET /admin/works/{id}/access — who can see one of your songs. */
|
|
3811
|
+
async list(workId) {
|
|
3812
|
+
return this.request("GET", `/admin/works/${encodeURIComponent(workId)}/access`);
|
|
3813
|
+
}
|
|
3814
|
+
/**
|
|
3815
|
+
* POST /admin/works/{id}/access — end one line. A removal that took
|
|
3816
|
+
* nothing off throws (500 ACCESS_NOT_REMOVED).
|
|
3817
|
+
*/
|
|
3818
|
+
async remove(workId, key) {
|
|
3819
|
+
return this.request("POST", `/admin/works/${encodeURIComponent(workId)}/access`, { key });
|
|
3820
|
+
}
|
|
3821
|
+
}
|
|
3749
3822
|
class RecordingSplitsResource extends BaseResource {
|
|
3750
3823
|
async list(recordingId) {
|
|
3751
3824
|
return this.request("GET", `/admin/recordings/${recordingId}/splits`);
|
|
@@ -4848,6 +4921,7 @@ export class PicaClient {
|
|
|
4848
4921
|
// ADR-232 — share trace aggregator + count explain registry +
|
|
4849
4922
|
// audio pipeline status read.
|
|
4850
4923
|
shareTrace;
|
|
4924
|
+
songAccess;
|
|
4851
4925
|
countExplain;
|
|
4852
4926
|
audioPipelineStatus;
|
|
4853
4927
|
publishers;
|
|
@@ -4965,6 +5039,7 @@ export class PicaClient {
|
|
|
4965
5039
|
this.recordingSplits = new RecordingSplitsResource(baseUrl, config.apiKey, debug);
|
|
4966
5040
|
this.recordingCredits = new RecordingCreditsResource(baseUrl, config.apiKey, debug);
|
|
4967
5041
|
this.shareTrace = new ShareTraceResource(baseUrl, config.apiKey, debug);
|
|
5042
|
+
this.songAccess = new SongAccessResource(baseUrl, config.apiKey, debug);
|
|
4968
5043
|
this.countExplain = new CountExplainResource(baseUrl, config.apiKey, debug);
|
|
4969
5044
|
this.audioPipelineStatus = new AudioPipelineStatusResource(baseUrl, config.apiKey, debug);
|
|
4970
5045
|
this.publishers = new PublishersResource(baseUrl, config.apiKey, debug);
|