@clipwright/core 0.1.0 → 0.3.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.
Files changed (47) hide show
  1. package/LICENSE +1 -1
  2. package/dist/__fixtures__/queue-promises.d.ts +87 -0
  3. package/dist/__fixtures__/queue-promises.js +99 -0
  4. package/dist/__fixtures__/queue-promises.js.map +1 -0
  5. package/dist/account.d.ts +49 -0
  6. package/dist/account.js +45 -0
  7. package/dist/account.js.map +1 -0
  8. package/dist/api-failure.d.ts +192 -0
  9. package/dist/api-failure.js +491 -0
  10. package/dist/api-failure.js.map +1 -0
  11. package/dist/contract-dispositions.d.ts +2 -19
  12. package/dist/contract-dispositions.js +2 -19
  13. package/dist/contract-dispositions.js.map +1 -1
  14. package/dist/errors.d.ts +148 -0
  15. package/dist/errors.js +192 -0
  16. package/dist/errors.js.map +1 -0
  17. package/dist/index.d.ts +7 -0
  18. package/dist/index.js +21 -0
  19. package/dist/index.js.map +1 -1
  20. package/dist/public-endpoints.d.ts +29 -0
  21. package/dist/public-endpoints.js +49 -0
  22. package/dist/public-endpoints.js.map +1 -0
  23. package/dist/rate-limit.d.ts +160 -0
  24. package/dist/rate-limit.js +182 -0
  25. package/dist/rate-limit.js.map +1 -0
  26. package/dist/render-backend.d.ts +44 -6
  27. package/dist/render-backend.js +24 -0
  28. package/dist/render-backend.js.map +1 -1
  29. package/dist/run-static-warnings.d.ts +5 -0
  30. package/dist/run-static-warnings.js +30 -0
  31. package/dist/run-static-warnings.js.map +1 -0
  32. package/dist/runs.d.ts +3 -0
  33. package/dist/runs.js +18 -1
  34. package/dist/runs.js.map +1 -1
  35. package/dist/skills.d.ts +27 -2
  36. package/dist/skills.js +48 -8
  37. package/dist/skills.js.map +1 -1
  38. package/dist/tts-backend.d.ts +4 -0
  39. package/dist/tts-backend.js +0 -13
  40. package/dist/tts-backend.js.map +1 -1
  41. package/dist/vendor-response.d.ts +98 -0
  42. package/dist/vendor-response.js +152 -0
  43. package/dist/vendor-response.js.map +1 -0
  44. package/dist/voices.d.ts +42 -26
  45. package/dist/voices.js +11 -8
  46. package/dist/voices.js.map +1 -1
  47. package/package.json +2 -2
package/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2026 Dimantika LLC
3
+ Copyright (c) 2026 Dimantika Sp. z o.o.
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
@@ -0,0 +1,87 @@
1
+ /**
2
+ * ОБЕЩАНИЯ ОЧЕРЕДИ, ЗАПРЕЩЁННЫЕ НА ОТКАЗЕ `account_not_admitted` (US-620).
3
+ *
4
+ * ПРАВИЛО (держится чтением; проверка ниже — лишь регрессионный сторож против
5
+ * уже случавшихся формулировок, см. «что она не гарантирует»). Из 403 известно
6
+ * ровно одно: у аккаунта нет доступа. Тем же ответом
7
+ * отвечает аккаунт, у которого доступ СНЯЛИ (третья поверхность вентиля
8
+ * построена вокруг именно этого случая — `credit-guard.ts`), и обещание скорого
9
+ * приёма было бы для него ложью в самый неподходящий момент. Поэтому ни одна
10
+ * поверхность, объясняющая этот отказ, не называет ни срока, ни номера.
11
+ *
12
+ * ПОЧЕМУ СПИСОК ОДИН НА ВСЕХ. Правило нарушалось ПЯТЬ раз подряд, и каждый раз
13
+ * чинилось по адресу находки: совет агенту, `SKILL.md`, тело ответа, снова
14
+ * совет, публичная дока. Когда сторожей стало двое, список немедленно
15
+ * разошёлся: у одного было `yet`, у другого — русские корни. Два ответа на
16
+ * вопрос «что такое это правило» — тот же дефект, который эта же приёмка дважды
17
+ * признавала блокером (два перечня писателей `admitted_at`, два текста про
18
+ * очередь). Лечение одно и то же: одна копия, остальные ссылаются.
19
+ *
20
+ * ЯЗЫКА ДВА, ПОТОМУ ЧТО КОРПУС ДВУЯЗЫЧНЫЙ. Код и агентские тексты английские,
21
+ * документация и комментарии русские. Пятое вхождение уцелело ровно потому, что
22
+ * сторож знал только английский.
23
+ *
24
+ * КОРНИ, А НЕ СЛОВОФОРМЫ: `волн` ловит «волну» и «волнами», `очеред` —
25
+ * «очередь» и «очереди».
26
+ *
27
+ * ЧТО ЭТА ПРОВЕРКА НЕ ГАРАНТИРУЕТ, И ЭТО ГЛАВНОЕ ПРО НЕЁ.
28
+ *
29
+ * Она НЕ обеспечивает инвариант «не называет срока» — обеспечить смысловой
30
+ * инвариант списком подстрок невозможно в принципе. «access will be granted
31
+ * tomorrow», «доступ появится в пятницу», «soon» пройдут: их в списке нет, и
32
+ * добавить все нельзя — язык не кончается. Обещание, написанное вовсе без имени
33
+ * отказа, тоже пройдёт: якорь его не найдёт.
34
+ *
35
+ * ЧЕГО СПИСОК НЕ ЗНАЕТ, названо поимённо, чтобы следующий автор не полагался на
36
+ * сторожа там, где тот слеп: `soon`, `shortly`, `waitlist`, `invite`, `batch`,
37
+ * «скоро», «в ближайшее время», «лист ожидания», «пригласим», «завтра». Все они
38
+ * пройдут. Добавлять их нельзя: «скоро» и `soon` встречаются в верных текстах
39
+ * повсюду, и сторож станет шумным — а шумного снимают.
40
+ *
41
+ * Что она РЕАЛЬНО делает: не даёт вернуться ШЕСТИ формулировкам, которые в этой
42
+ * стори уже возвращались — по одной на круг приёмки, в шести разных местах.
43
+ * Регрессионный сторож против известного класса, а не доказательство свойства.
44
+ * Само свойство держит чтение: правило и его довод записаны у
45
+ * `accountNotAdmittedError`, и рецензент, читающий текст отказа, проверяет
46
+ * смысл, а не подстроки. Считать иначе — значит второй раз поверить, что
47
+ * зелёный тест равен верному коду.
48
+ *
49
+ * ЧЕМ ЭТА ПРОВЕРКА ПЛАТИТ, названо прямо. Подстрочный поиск не отличает
50
+ * обещание от его отрицания: верная фраза «отказ не ставит аккаунт в очередь»
51
+ * покраснеет. Это осознанный обмен — сторож ошибается ГРОМКО и чинится
52
+ * переписыванием фразы, тогда как сторож, разбирающий отрицания, ошибётся ТИХО
53
+ * и пропустит обещание. Тексты запретов в этом проекте пишутся без запрещаемых
54
+ * слов; так уже сделано в `SKILL.md` и в `docs/03-api.md`.
55
+ *
56
+ * ЛЕЖИТ В `__fixtures__`, а не в публичном экспорте пакета: это тестовый
57
+ * материал, а не часть контракта клиентов.
58
+ */
59
+ export declare const QUEUE_PROMISES: readonly ["in waves", "in the queue", "next wave", "yet", "волн", "очеред", "пока что", "подожди"];
60
+ /**
61
+ * Предмет правила — В НОРМАЛИЗОВАННОМ ВИДЕ, а не одним написанием.
62
+ *
63
+ * Скоуп по ИМЕНИ, а не по файлу, проводит границу по смыслу: `CLAUDE.md` вправе
64
+ * описывать приём волнами как операторский процесс, а `docs/03-api.md` §7 —
65
+ * очередь рендеров при превышении потолка. Ни то, ни другое не утверждает
66
+ * ничего про этот 403.
67
+ *
68
+ * НО ИМЯ У ПРЕДМЕТА НЕ ОДНО. Кодовая база называет его четырьмя написаниями:
69
+ * `account_not_admitted` (тело и документы), `accountNotAdmittedBody`
70
+ * (сборка), `NotAdmittedFailure` (вид отказа) и `kind: "not_admitted"` (вердикт
71
+ * на четырёх поверхностях). Сторож, знавший только первое, не видел ЧЕТЫРЕХ
72
+ * файлов, объясняющих тот же отказ, — включая сообщение `issue-token.ts`,
73
+ * которое читает человек. Обе приёмки нашли это независимо, и это та же форма,
74
+ * что «список слов английский, а корпус двуязычный»: скоуп по смыслу остаётся
75
+ * скоупом по написанию, пока опознание однодиалектное.
76
+ *
77
+ * Поэтому сравнение идёт по нормализованному тексту: снимаем `_`, приводим к
78
+ * нижнему регистру, ищем подстроку. Любое БУДУЩЕЕ написание ловится само, без
79
+ * правки сторожа.
80
+ *
81
+ * ЛОЖНЫХ СРАБАТЫВАНИЙ НА СОСЕДЯХ НЕТ, и это проверено, а не предположено:
82
+ * `already_admitted` (вердикт US-602 «у рана уже есть холд») нормализуется в
83
+ * `alreadyadmitted` и подстроки `notadmitted` не содержит.
84
+ */
85
+ export declare const NOT_ADMITTED_ANCHOR = "notadmitted";
86
+ /** Снимает разницу написаний: `account_not_admitted` и `NotAdmittedFailure` — одно. */
87
+ export declare function normalizeForAnchor(text: string): string;
@@ -0,0 +1,99 @@
1
+ /**
2
+ * ОБЕЩАНИЯ ОЧЕРЕДИ, ЗАПРЕЩЁННЫЕ НА ОТКАЗЕ `account_not_admitted` (US-620).
3
+ *
4
+ * ПРАВИЛО (держится чтением; проверка ниже — лишь регрессионный сторож против
5
+ * уже случавшихся формулировок, см. «что она не гарантирует»). Из 403 известно
6
+ * ровно одно: у аккаунта нет доступа. Тем же ответом
7
+ * отвечает аккаунт, у которого доступ СНЯЛИ (третья поверхность вентиля
8
+ * построена вокруг именно этого случая — `credit-guard.ts`), и обещание скорого
9
+ * приёма было бы для него ложью в самый неподходящий момент. Поэтому ни одна
10
+ * поверхность, объясняющая этот отказ, не называет ни срока, ни номера.
11
+ *
12
+ * ПОЧЕМУ СПИСОК ОДИН НА ВСЕХ. Правило нарушалось ПЯТЬ раз подряд, и каждый раз
13
+ * чинилось по адресу находки: совет агенту, `SKILL.md`, тело ответа, снова
14
+ * совет, публичная дока. Когда сторожей стало двое, список немедленно
15
+ * разошёлся: у одного было `yet`, у другого — русские корни. Два ответа на
16
+ * вопрос «что такое это правило» — тот же дефект, который эта же приёмка дважды
17
+ * признавала блокером (два перечня писателей `admitted_at`, два текста про
18
+ * очередь). Лечение одно и то же: одна копия, остальные ссылаются.
19
+ *
20
+ * ЯЗЫКА ДВА, ПОТОМУ ЧТО КОРПУС ДВУЯЗЫЧНЫЙ. Код и агентские тексты английские,
21
+ * документация и комментарии русские. Пятое вхождение уцелело ровно потому, что
22
+ * сторож знал только английский.
23
+ *
24
+ * КОРНИ, А НЕ СЛОВОФОРМЫ: `волн` ловит «волну» и «волнами», `очеред` —
25
+ * «очередь» и «очереди».
26
+ *
27
+ * ЧТО ЭТА ПРОВЕРКА НЕ ГАРАНТИРУЕТ, И ЭТО ГЛАВНОЕ ПРО НЕЁ.
28
+ *
29
+ * Она НЕ обеспечивает инвариант «не называет срока» — обеспечить смысловой
30
+ * инвариант списком подстрок невозможно в принципе. «access will be granted
31
+ * tomorrow», «доступ появится в пятницу», «soon» пройдут: их в списке нет, и
32
+ * добавить все нельзя — язык не кончается. Обещание, написанное вовсе без имени
33
+ * отказа, тоже пройдёт: якорь его не найдёт.
34
+ *
35
+ * ЧЕГО СПИСОК НЕ ЗНАЕТ, названо поимённо, чтобы следующий автор не полагался на
36
+ * сторожа там, где тот слеп: `soon`, `shortly`, `waitlist`, `invite`, `batch`,
37
+ * «скоро», «в ближайшее время», «лист ожидания», «пригласим», «завтра». Все они
38
+ * пройдут. Добавлять их нельзя: «скоро» и `soon` встречаются в верных текстах
39
+ * повсюду, и сторож станет шумным — а шумного снимают.
40
+ *
41
+ * Что она РЕАЛЬНО делает: не даёт вернуться ШЕСТИ формулировкам, которые в этой
42
+ * стори уже возвращались — по одной на круг приёмки, в шести разных местах.
43
+ * Регрессионный сторож против известного класса, а не доказательство свойства.
44
+ * Само свойство держит чтение: правило и его довод записаны у
45
+ * `accountNotAdmittedError`, и рецензент, читающий текст отказа, проверяет
46
+ * смысл, а не подстроки. Считать иначе — значит второй раз поверить, что
47
+ * зелёный тест равен верному коду.
48
+ *
49
+ * ЧЕМ ЭТА ПРОВЕРКА ПЛАТИТ, названо прямо. Подстрочный поиск не отличает
50
+ * обещание от его отрицания: верная фраза «отказ не ставит аккаунт в очередь»
51
+ * покраснеет. Это осознанный обмен — сторож ошибается ГРОМКО и чинится
52
+ * переписыванием фразы, тогда как сторож, разбирающий отрицания, ошибётся ТИХО
53
+ * и пропустит обещание. Тексты запретов в этом проекте пишутся без запрещаемых
54
+ * слов; так уже сделано в `SKILL.md` и в `docs/03-api.md`.
55
+ *
56
+ * ЛЕЖИТ В `__fixtures__`, а не в публичном экспорте пакета: это тестовый
57
+ * материал, а не часть контракта клиентов.
58
+ */
59
+ export const QUEUE_PROMISES = [
60
+ "in waves",
61
+ "in the queue",
62
+ "next wave",
63
+ "yet",
64
+ "волн",
65
+ "очеред",
66
+ "пока что",
67
+ "подожди",
68
+ ];
69
+ /**
70
+ * Предмет правила — В НОРМАЛИЗОВАННОМ ВИДЕ, а не одним написанием.
71
+ *
72
+ * Скоуп по ИМЕНИ, а не по файлу, проводит границу по смыслу: `CLAUDE.md` вправе
73
+ * описывать приём волнами как операторский процесс, а `docs/03-api.md` §7 —
74
+ * очередь рендеров при превышении потолка. Ни то, ни другое не утверждает
75
+ * ничего про этот 403.
76
+ *
77
+ * НО ИМЯ У ПРЕДМЕТА НЕ ОДНО. Кодовая база называет его четырьмя написаниями:
78
+ * `account_not_admitted` (тело и документы), `accountNotAdmittedBody`
79
+ * (сборка), `NotAdmittedFailure` (вид отказа) и `kind: "not_admitted"` (вердикт
80
+ * на четырёх поверхностях). Сторож, знавший только первое, не видел ЧЕТЫРЕХ
81
+ * файлов, объясняющих тот же отказ, — включая сообщение `issue-token.ts`,
82
+ * которое читает человек. Обе приёмки нашли это независимо, и это та же форма,
83
+ * что «список слов английский, а корпус двуязычный»: скоуп по смыслу остаётся
84
+ * скоупом по написанию, пока опознание однодиалектное.
85
+ *
86
+ * Поэтому сравнение идёт по нормализованному тексту: снимаем `_`, приводим к
87
+ * нижнему регистру, ищем подстроку. Любое БУДУЩЕЕ написание ловится само, без
88
+ * правки сторожа.
89
+ *
90
+ * ЛОЖНЫХ СРАБАТЫВАНИЙ НА СОСЕДЯХ НЕТ, и это проверено, а не предположено:
91
+ * `already_admitted` (вердикт US-602 «у рана уже есть холд») нормализуется в
92
+ * `alreadyadmitted` и подстроки `notadmitted` не содержит.
93
+ */
94
+ export const NOT_ADMITTED_ANCHOR = "notadmitted";
95
+ /** Снимает разницу написаний: `account_not_admitted` и `NotAdmittedFailure` — одно. */
96
+ export function normalizeForAnchor(text) {
97
+ return text.replaceAll("_", "").toLowerCase();
98
+ }
99
+ //# sourceMappingURL=queue-promises.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"queue-promises.js","sourceRoot":"","sources":["../../src/__fixtures__/queue-promises.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyDG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG;IAC5B,UAAU;IACV,cAAc;IACd,WAAW;IACX,KAAK;IACL,MAAM;IACN,QAAQ;IACR,UAAU;IACV,SAAS;CACD,CAAC;AAEX;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,aAAa,CAAC;AAEjD,uFAAuF;AACvF,MAAM,UAAU,kBAAkB,CAAC,IAAY;IAC7C,OAAO,IAAI,CAAC,UAAU,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC;AAChD,CAAC"}
@@ -0,0 +1,49 @@
1
+ import { z } from "zod";
2
+ /**
3
+ * ТЕЛО `GET /v1/account` (US-602).
4
+ *
5
+ * ПУТЬ ОБЪЯВЛЕН ДАВНО, ТЕЛО — НЕТ. `docs/03-api.md` обещает этот маршрут с
6
+ * телом `{plan, credits_balance, renews_at}`. Путь берётся объявленный, тело —
7
+ * честное: `plan` и `renews_at` мертвы по ADR-012 (тарифных планов и продлений
8
+ * в продукте нет), и отдавать их означало бы поддерживать поля, за которыми
9
+ * ничего не стоит. Обещание снимается из документа отдельной стори (US-623).
10
+ *
11
+ * ЧЕТЫРЕ ЧИСЛА, А НЕ ОДНО, И КАЖДОЕ ОТВЕЧАЕТ НА СВОЙ ВОПРОС:
12
+ * - `balance_credits` — сколько денег есть;
13
+ * - `debt_credits` — сколько должен (блокирует новые раны отдельным кодом);
14
+ * - `holds_credits` — сколько заморожено под уже идущими ранами;
15
+ * - `daily_remaining_credits` — сколько ещё пропустит НАШ суточный
16
+ * предохранитель.
17
+ *
18
+ * Последнее — НЕ деньги, и потому названо отдельно, а не вычтено из баланса.
19
+ * Смешай их — и клиент с деньгами на счету, упёршийся в наш операционный
20
+ * лимит, прочтёт это как «кончились кредиты» и пойдёт покупать ещё. Ровно то
21
+ * же различение держат два разных кода отказа (`errors.ts`).
22
+ */
23
+ /**
24
+ * Начисление. `source` — z.string(), а не enum: перечень источников живёт в
25
+ * базе и пополняется (промо, партнёрка), а read-контракт обязан переживать
26
+ * новое значение у уже установленного клиента — та же асимметрия чтения и
27
+ * записи, что у стадий рана (`runRead`).
28
+ */
29
+ export declare const creditGrant: z.ZodObject<{
30
+ amount: z.ZodNumber;
31
+ source: z.ZodString;
32
+ expires_at: z.ZodNullable<z.ZodString>;
33
+ created_at: z.ZodString;
34
+ }, z.core.$strip>;
35
+ export type CreditGrant = z.infer<typeof creditGrant>;
36
+ export declare const account: z.ZodObject<{
37
+ account_id: z.ZodString;
38
+ balance_credits: z.ZodNumber;
39
+ debt_credits: z.ZodNumber;
40
+ holds_credits: z.ZodNumber;
41
+ daily_remaining_credits: z.ZodNumber;
42
+ grants: z.ZodDefault<z.ZodArray<z.ZodObject<{
43
+ amount: z.ZodNumber;
44
+ source: z.ZodString;
45
+ expires_at: z.ZodNullable<z.ZodString>;
46
+ created_at: z.ZodString;
47
+ }, z.core.$strip>>>;
48
+ }, z.core.$strip>;
49
+ export type Account = z.infer<typeof account>;
@@ -0,0 +1,45 @@
1
+ import { z } from "zod";
2
+ /**
3
+ * ТЕЛО `GET /v1/account` (US-602).
4
+ *
5
+ * ПУТЬ ОБЪЯВЛЕН ДАВНО, ТЕЛО — НЕТ. `docs/03-api.md` обещает этот маршрут с
6
+ * телом `{plan, credits_balance, renews_at}`. Путь берётся объявленный, тело —
7
+ * честное: `plan` и `renews_at` мертвы по ADR-012 (тарифных планов и продлений
8
+ * в продукте нет), и отдавать их означало бы поддерживать поля, за которыми
9
+ * ничего не стоит. Обещание снимается из документа отдельной стори (US-623).
10
+ *
11
+ * ЧЕТЫРЕ ЧИСЛА, А НЕ ОДНО, И КАЖДОЕ ОТВЕЧАЕТ НА СВОЙ ВОПРОС:
12
+ * - `balance_credits` — сколько денег есть;
13
+ * - `debt_credits` — сколько должен (блокирует новые раны отдельным кодом);
14
+ * - `holds_credits` — сколько заморожено под уже идущими ранами;
15
+ * - `daily_remaining_credits` — сколько ещё пропустит НАШ суточный
16
+ * предохранитель.
17
+ *
18
+ * Последнее — НЕ деньги, и потому названо отдельно, а не вычтено из баланса.
19
+ * Смешай их — и клиент с деньгами на счету, упёршийся в наш операционный
20
+ * лимит, прочтёт это как «кончились кредиты» и пойдёт покупать ещё. Ровно то
21
+ * же различение держат два разных кода отказа (`errors.ts`).
22
+ */
23
+ /**
24
+ * Начисление. `source` — z.string(), а не enum: перечень источников живёт в
25
+ * базе и пополняется (промо, партнёрка), а read-контракт обязан переживать
26
+ * новое значение у уже установленного клиента — та же асимметрия чтения и
27
+ * записи, что у стадий рана (`runRead`).
28
+ */
29
+ export const creditGrant = z.object({
30
+ amount: z.number().int(),
31
+ source: z.string(),
32
+ /** `null` — грант бессрочный. */
33
+ expires_at: z.string().datetime().nullable(),
34
+ created_at: z.string().datetime(),
35
+ });
36
+ export const account = z.object({
37
+ account_id: z.string(),
38
+ balance_credits: z.number().int().nonnegative(),
39
+ debt_credits: z.number().int().nonnegative(),
40
+ holds_credits: z.number().int().nonnegative(),
41
+ daily_remaining_credits: z.number().int().nonnegative(),
42
+ /** Только НЕИСТЁКШИЕ: истёкший грант в баланс не входит и в списке не лжёт. */
43
+ grants: z.array(creditGrant).default([]),
44
+ });
45
+ //# sourceMappingURL=account.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"account.js","sourceRoot":"","sources":["../src/account.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH;;;;;GAKG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,CAAC,MAAM,CAAC;IAClC,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE;IACxB,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE;IAClB,iCAAiC;IACjC,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE;IAC5C,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;CAClC,CAAC,CAAC;AAGH,MAAM,CAAC,MAAM,OAAO,GAAG,CAAC,CAAC,MAAM,CAAC;IAC9B,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE;IACtB,eAAe,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,WAAW,EAAE;IAC/C,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,WAAW,EAAE;IAC5C,aAAa,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,WAAW,EAAE;IAC7C,uBAAuB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,WAAW,EAAE;IACvD,+EAA+E;IAC/E,MAAM,EAAE,CAAC,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC;CACzC,CAAC,CAAC"}
@@ -0,0 +1,192 @@
1
+ /**
2
+ * РАЗБОР ОТКАЗА API — ОДИН НА ВСЕ КЛИЕНТСКИЕ ПОВЕРХНОСТИ (US-622).
3
+ *
4
+ * ЧТО БЫЛО. `packages/sdk` сводил ЛЮБОЙ не-2xx к плоскому `new Error(message)`:
5
+ * ни статуса, ни кода, ни `Retry-After`. Пока API отвечал одними четырёхсотыми
6
+ * «поле не то», разницы не было — отказ был один, и делать с ним было нечего,
7
+ * кроме как показать текст. Фаза 1 добавила три кода, с которыми надо
8
+ * ПОСТУПАТЬ по-разному: 429 — подождать и повторить, 402 — остановиться и
9
+ * сказать человеку числа, 5xx — повторить без паузы от сервера. Плоский `Error`
10
+ * делает все три неразличимыми, и клиент выбирает худший вариант из трёх:
11
+ * первый же 429 на поллинге убивает `makeUgc` целиком.
12
+ *
13
+ * ПОЧЕМУ РАЗБОР ЖИВЁТ ЗДЕСЬ, А НЕ В SDK. Поверхностей, читающих отказ, три —
14
+ * SDK, MCP-форматтер и CLI, — и решение «ретраить или нет» обязано быть у них
15
+ * общим. Разъедься оно, и MCP советовал бы агенту ждать там, где SDK уже сдался.
16
+ * Тот же довод, что и у самих схем (правило проекта №2): формы отказов уже
17
+ * лежат в `errors.ts` и `rate-limit.ts`, здесь — только их РАЗБОР и вывод.
18
+ *
19
+ * ЧЕГО ЭТОТ МОДУЛЬ НЕ ДЕЛАЕТ. Он не ходит в сеть и не знает про `fetch`:
20
+ * вход — статус, текст тела и заголовок. Поэтому его можно прогнать на всех
21
+ * формах ответа, включая те, которые живой сервер отдать не может, а прокси
22
+ * между ним и клиентом — вполне (HTML-страница про перегрузку).
23
+ */
24
+ /** Отказ по частоте: 429 либо наш, либо чужой (прокси, edge тарифа). */
25
+ export interface RateLimitedFailure {
26
+ kind: "rate_limited";
27
+ status: number;
28
+ message: string;
29
+ /** Через сколько секунд повторять. `undefined` — сервер не сказал. */
30
+ retryAfterSeconds: number | undefined;
31
+ /** Потолок ведра. Есть только у НАШЕГО тела; у прокси его нет. */
32
+ limit: number | undefined;
33
+ windowSeconds: number | undefined;
34
+ }
35
+ /** Кредитов меньше веса рана. Терминально: повтор не изменит баланс. */
36
+ export interface InsufficientCreditsFailure {
37
+ kind: "insufficient_credits";
38
+ status: number;
39
+ message: string;
40
+ balanceCredits: number;
41
+ requiredCredits: number;
42
+ }
43
+ /**
44
+ * Висит долг. Тоже терминально; лечится покупкой кредитов — поступившие гасят
45
+ * долг раньше, чем ложатся на баланс (clipwright#27). Отдельным от
46
+ * `insufficient_credits` отказ остаётся из-за разного положения дел: с долгом
47
+ * баланс бывает ненулевым.
48
+ */
49
+ export interface DebtOutstandingFailure {
50
+ kind: "debt_outstanding";
51
+ status: number;
52
+ message: string;
53
+ debtCredits: number;
54
+ }
55
+ /**
56
+ * Аккаунт не допущен в бету (US-620). Терминально, и лечится НЕ деньгами.
57
+ *
58
+ * Отдельный вид, а не `client_error` с кодом: у `client_error` совет агенту —
59
+ * «поправь вызов», а поправить в вызове нечего. Разница видна человеку: ему
60
+ * надо сказать «доступ выдаёт владелец», а не «ты что-то не так передал» и не
61
+ * «купи кредитов».
62
+ */
63
+ export interface NotAdmittedFailure {
64
+ kind: "not_admitted";
65
+ status: number;
66
+ message: string;
67
+ }
68
+ /** 5xx: сервер обещает, что дело в нём. Повторять можно. */
69
+ export interface ServerFailure {
70
+ kind: "server_error";
71
+ status: number;
72
+ message: string;
73
+ retryAfterSeconds: number | undefined;
74
+ }
75
+ /** Всё остальное (4xx): повтор того же запроса даст то же самое. */
76
+ export interface ClientFailure {
77
+ kind: "client_error";
78
+ status: number;
79
+ message: string;
80
+ /** Код из тела, если тело вообще было контрактным. */
81
+ code: string | undefined;
82
+ }
83
+ export type ApiFailure = RateLimitedFailure | InsufficientCreditsFailure | DebtOutstandingFailure | NotAdmittedFailure | ServerFailure | ClientFailure;
84
+ /**
85
+ * ПОВТОРЯТЬ ИЛИ НЕТ — ОДНОЙ ФУНКЦИЕЙ, а не условием на каждой поверхности.
86
+ *
87
+ * Ошибиться здесь можно в обе стороны, и цены разные. Не повторить 429 — это
88
+ * оборванный `makeUgc` на ране, за который уже заплачено. Повторить 402 — это
89
+ * агент, крутящий отказ по деньгам в цикле: баланс от повторов не растёт, а
90
+ * лимит частоты они съедают, и человек получает вместо «пополни счёт» молчание
91
+ * до самого дедлайна.
92
+ */
93
+ export declare function isRetryableFailure(failure: ApiFailure): boolean;
94
+ /**
95
+ * Пауза перед повтором, мс. `fallbackMs` — собственный шаг вызывателя (для
96
+ * поллинга это его интервал): он же и пол, потому что сервер, не назвавший
97
+ * `Retry-After`, не разрешил долбить чаще обычного.
98
+ *
99
+ * Вызывать имеет смысл только после `isRetryableFailure`; на терминальном
100
+ * отказе результат бессмыслен, и функция не притворяется, что это не так.
101
+ */
102
+ export declare function retryDelayMs(failure: ApiFailure, fallbackMs: number): number;
103
+ /**
104
+ * `Retry-After` в секундах, обе формы RFC 9110.
105
+ *
106
+ * ДВЕ ФОРМЫ — НЕ ПЕДАНТИЗМ. Дельту в секундах отдаёт наша мидлвара, а HTTP-дату
107
+ * — чужие прокси (её разрешает та же RFC, и nginx с облачными edge её ставят).
108
+ * Клиент, знающий только цифры, на дате получил бы `NaN` и либо не ждал вовсе,
109
+ * либо ждал бесконечность — обе ветки хуже, чем «подожди свой обычный шаг».
110
+ *
111
+ * `now` — параметр, а не `Date.now()` внутри: разбор даты обязан быть проверяем
112
+ * прогоном, а не зависеть от того, когда его запустили.
113
+ */
114
+ export declare function parseRetryAfterSeconds(raw: string | null | undefined, now: Date): number | undefined;
115
+ export interface ApiFailureInput {
116
+ status: number;
117
+ /**
118
+ * Сырой текст тела. Именно текст: `res.json()` на HTML бросает парсером.
119
+ *
120
+ * `undefined` — тело ПРОЧИТАТЬ НЕ УДАЛОСЬ (соединение оборвалось после
121
+ * заголовков). Это НЕ то же самое, что пустое или чужое тело, и путать их
122
+ * нельзя: по пустой строке нельзя судить, кто ответил (внешнее ревью, круг 13).
123
+ */
124
+ bodyText: string | undefined;
125
+ /** Значение заголовка `Retry-After`, как его отдал сервер. */
126
+ retryAfterHeader?: string | null | undefined;
127
+ /** Точка отсчёта для формы HTTP-date. */
128
+ now?: Date;
129
+ }
130
+ /**
131
+ * Статус + тело + заголовок → то, с чем можно ПОСТУПИТЬ.
132
+ *
133
+ * ТЕЛО УТОЧНЯЕТ СТАТУС, НО НЕ ОТМЕНЯЕТ ЕГО. Специализированная схема
134
+ * применяется, только если статус ей СООТВЕТСТВУЕТ: `rate_limited` — на 429,
135
+ * оба денежных кода — на 402.
136
+ *
137
+ * Первая редакция давала телу перевешивать статус, рассуждая так: код объявлен
138
+ * в теле, а статус по дороге переписывает кто угодно, значит 502 поверх нашего
139
+ * 402 — это всё ещё «не хватило кредитов». Внешнее ревью показало обратную
140
+ * сторону того же рассуждения, и она хуже. Кэширующий прокси, отдавший старое
141
+ * тело `debt_outstanding` с новым статусом 429, делал отказ ТЕРМИНАЛЬНЫМ: агент
142
+ * бросал бы уже оплаченный ран и требовал погасить долг, которого нет. Зеркально
143
+ * 402 с телом `rate_limited` становился РЕТРАЙНЫМ, прямо вопреки правилу «любой
144
+ * 402 останавливает». Получались и внутренне противоречивые отказы вида
145
+ * `status === 402 && retryable === true`.
146
+ *
147
+ * Сочетание статуса и кода из РАЗНЫХ ответов — это не сообщение, а его порча, и
148
+ * доверять в нём надо тому, что уцелело наверняка. Уцелел статус: он часть
149
+ * стартовой строки, его не кэшируют отдельно от тела и им управляет тот же
150
+ * узел, который решил ответить. Поэтому при расхождении класс отказа берётся из
151
+ * статуса, а текст — из тела: сказать «сервер перегружен, повтори» и оказаться
152
+ * неправым дешевле, чем бросить оплаченный ран.
153
+ *
154
+ * ЧИСЛА `retry_after_seconds` ИЗ ТЕЛА СИЛЬНЕЕ ЗАГОЛОВКА — здесь тело выигрывает
155
+ * законно, потому что спорят два поля ОДНОГО ответа, а не ответ с самим собой.
156
+ * Наша мидлвара ставит в оба места одно число, и заголовок переписывают чаще.
157
+ */
158
+ export declare function parseApiFailure(input: ApiFailureInput): ApiFailure;
159
+ /**
160
+ * ОТКАЗ, РАЗВЁРНУТЫЙ ДЛЯ АГЕНТА, — ОДНОЙ ФУНКЦИЕЙ НА ВСЕ ПОВЕРХНОСТИ (US-622).
161
+ *
162
+ * ПОЧЕМУ НЕ «ПРОСТО ТЕКСТ ОШИБКИ». Отказ по деньгам — это не сбой, а РАЗВИЛКА,
163
+ * и агент обязан её пройти, а не ретраить: 402 повторами не лечится, баланс от
164
+ * них не растёт. Значит ответ обязан нести три вещи, которых в строке нет:
165
+ * числа (сколько есть, сколько нужно), императив («останови и скажи человеку»)
166
+ * и признак, что повторять бессмысленно. Форма скопирована с `next_action` из
167
+ * ADR-009 ровно потому, что она уже работает: `make_ugc` и `get_run` говорят с
168
+ * агентом так же, и отказ, говорящий иначе, читался бы как другой продукт.
169
+ *
170
+ * ПОЧЕМУ ЗДЕСЬ, А НЕ В MCP-ФОРМАТТЕРЕ. Поверхностей три, и разъехаться им
171
+ * нельзя: MCP отдаёт этот объект агенту, CLI печатает те же числа человеку, а
172
+ * SDK держит их в типизированной ошибке. Собери форму в MCP — и CLI пришлось бы
173
+ * собирать её ВТОРОЙ раз, то есть завести второй источник правды о том, что
174
+ * значит отказ (правило проекта №2).
175
+ */
176
+ export interface AgentFailureReport {
177
+ /** Императив: повторять этот же вызов позже или прекратить. */
178
+ status: "RETRY_LATER" | "FAILED";
179
+ /** Машиночитаемая причина. Для чужих тел — `http_<status>`. */
180
+ code: string;
181
+ message: string;
182
+ /** Дубль `status` для условия в коде: строку легко сравнить неверно. */
183
+ retryable: boolean;
184
+ next_action: string;
185
+ retry_after_seconds?: number;
186
+ limit?: number;
187
+ window_seconds?: number;
188
+ balance_credits?: number;
189
+ required_credits?: number;
190
+ debt_credits?: number;
191
+ }
192
+ export declare function agentFailureReport(failure: ApiFailure): AgentFailureReport;