@withpica/mcp-sdk 4.0.0 → 4.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/CHANGELOG.md +47 -0
- package/dist/index.d.ts +110 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +91 -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`);
|
|
@@ -4476,6 +4549,14 @@ class StatementsResource extends BaseResource {
|
|
|
4476
4549
|
async getImport(id) {
|
|
4477
4550
|
return this.request("GET", `/admin/statements/imports/${encodeURIComponent(id)}`);
|
|
4478
4551
|
}
|
|
4552
|
+
/**
|
|
4553
|
+
* Remove an imported statement: its royalty lines and payment total go
|
|
4554
|
+
* (one transaction); an uploaded statement document it came from stays.
|
|
4555
|
+
* Refused (409 IMPORT_RUNNING) while it is still importing.
|
|
4556
|
+
*/
|
|
4557
|
+
async removeImport(importBatchId) {
|
|
4558
|
+
return this.request("DELETE", `/admin/statements/${encodeURIComponent(importBatchId)}`);
|
|
4559
|
+
}
|
|
4479
4560
|
}
|
|
4480
4561
|
class SelectionsResource extends BaseResource {
|
|
4481
4562
|
async list(params) {
|
|
@@ -4848,6 +4929,7 @@ export class PicaClient {
|
|
|
4848
4929
|
// ADR-232 — share trace aggregator + count explain registry +
|
|
4849
4930
|
// audio pipeline status read.
|
|
4850
4931
|
shareTrace;
|
|
4932
|
+
songAccess;
|
|
4851
4933
|
countExplain;
|
|
4852
4934
|
audioPipelineStatus;
|
|
4853
4935
|
publishers;
|
|
@@ -4965,6 +5047,7 @@ export class PicaClient {
|
|
|
4965
5047
|
this.recordingSplits = new RecordingSplitsResource(baseUrl, config.apiKey, debug);
|
|
4966
5048
|
this.recordingCredits = new RecordingCreditsResource(baseUrl, config.apiKey, debug);
|
|
4967
5049
|
this.shareTrace = new ShareTraceResource(baseUrl, config.apiKey, debug);
|
|
5050
|
+
this.songAccess = new SongAccessResource(baseUrl, config.apiKey, debug);
|
|
4968
5051
|
this.countExplain = new CountExplainResource(baseUrl, config.apiKey, debug);
|
|
4969
5052
|
this.audioPipelineStatus = new AudioPipelineStatusResource(baseUrl, config.apiKey, debug);
|
|
4970
5053
|
this.publishers = new PublishersResource(baseUrl, config.apiKey, debug);
|