@stelfield/cli 0.0.0-stage → 0.12.1

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.
@@ -0,0 +1,84 @@
1
+ // v0.20.0 M6: client-side pre-check of the publish input limits.
2
+ //
3
+ // What this is for (plain words): the platform publishes its current limits at GET /api/v1/tasks/input-limits
4
+ // (title length, description length, attachment count and total size). Before sending a request the CLI checks
5
+ // the same numbers so the agent hears "too long" immediately. The numbers are NEVER written into the CLI:
6
+ // they are read from the platform on every call, so changing a limit on the server takes effect at once.
7
+ // The wording and the field names are the same as the backend 422 `input_limit_exceeded`, because the
8
+ // backend remains the final judge (it also guards the web page and old CLI versions).
9
+
10
+ const MB = 1024 * 1024;
11
+
12
+ function isNum(v) {
13
+ return typeof v === 'number' && Number.isFinite(v);
14
+ }
15
+
16
+ // Same counting as the backend: characters (code points) after trimming spaces at both ends.
17
+ function charCount(text) {
18
+ return [...String(text).trim()].length;
19
+ }
20
+
21
+ // 2 decimals, no trailing ".0" (matches the backend text: 3 / 1.01).
22
+ function fmtMb(value) {
23
+ return String(Number(value.toFixed(2)));
24
+ }
25
+
26
+ /**
27
+ * @param {object|null} limits response of getInputLimits (null = unknown, skip)
28
+ * @param {{title?: string, description?: string}} input
29
+ * @returns {null | {error, message, field, limit, current}}
30
+ */
31
+ export function checkTaskText(limits, input) {
32
+ if (!limits) return null;
33
+ const rules = [
34
+ ['title', limits.title_max_chars, '任务标题'],
35
+ ['description', limits.desc_max_chars, '任务描述'],
36
+ ];
37
+ for (const [field, limit, label] of rules) {
38
+ if (input[field] == null || !isNum(limit)) continue;
39
+ const size = charCount(input[field]);
40
+ if (size > limit) {
41
+ return {
42
+ error: 'input_limit_exceeded',
43
+ message: `${label}最多 ${limit} 个字(当前 ${size} 个字)`,
44
+ field, limit, current: size,
45
+ };
46
+ }
47
+ }
48
+ return null;
49
+ }
50
+
51
+ /**
52
+ * @param {object|null} limits response of getInputLimits
53
+ * @param {{count: number, totalBytes: number, sizeBytes: number}} usage what the task has now + the new file
54
+ */
55
+ export function checkAttachment(limits, usage) {
56
+ if (!limits) return null;
57
+ const maxCount = limits.attach_max_count;
58
+ const maxMb = limits.attach_max_total_mb;
59
+ if (isNum(maxCount) && usage.count + 1 > maxCount) {
60
+ return {
61
+ error: 'input_limit_exceeded',
62
+ message: `单个任务最多 ${maxCount} 个附件(当前已有 ${usage.count} 个)`,
63
+ field: 'attach_count', limit: maxCount, current: usage.count + 1,
64
+ };
65
+ }
66
+ if (isNum(maxMb) && usage.totalBytes + usage.sizeBytes > maxMb * MB) {
67
+ const afterMb = Math.ceil(((usage.totalBytes + usage.sizeBytes) / MB) * 100) / 100;
68
+ return {
69
+ error: 'input_limit_exceeded',
70
+ message: `附件总大小最多 ${maxMb} MB(加上这个文件会到 ${fmtMb(afterMb)} MB)`,
71
+ field: 'attach_total_mb', limit: maxMb, current: afterMb,
72
+ };
73
+ }
74
+ return null;
75
+ }
76
+
77
+ /** Build an Error shaped like an API refusal (status 422, body = the backend error body). */
78
+ export function limitError(refusal) {
79
+ const e = new Error(`请求失败(422):${refusal.message}`);
80
+ e.status = 422;
81
+ e.body = refusal;
82
+ e.retryAfter = null;
83
+ return e;
84
+ }