@tikomni/skills 0.1.8 → 0.1.9
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tikomni/skills",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.9",
|
|
4
4
|
"description": "TikOmni skill installer CLI for structured social media crawling in Codex, Claude Code, and OpenClaw",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://github.com/mark-ly-wang/TikOmni-Skills#readme",
|
|
@@ -257,6 +257,13 @@ def _resolve_timeout_retry_backoff_ms() -> int:
|
|
|
257
257
|
return max(0, min(backoff, 5000))
|
|
258
258
|
|
|
259
259
|
|
|
260
|
+
def resolve_timeout_retry_policy() -> Dict[str, int]:
|
|
261
|
+
return {
|
|
262
|
+
"max_retries": _resolve_timeout_retry_max(),
|
|
263
|
+
"backoff_ms": _resolve_timeout_retry_backoff_ms(),
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
|
|
260
267
|
def _wait_rate_limit_slot(qps: float) -> int:
|
|
261
268
|
global _NEXT_ALLOWED_TS
|
|
262
269
|
interval_sec = 1.0 / max(qps, 0.1)
|
|
@@ -5,18 +5,26 @@ from __future__ import annotations
|
|
|
5
5
|
|
|
6
6
|
import mimetypes
|
|
7
7
|
import os
|
|
8
|
+
import socket
|
|
8
9
|
import tempfile
|
|
10
|
+
import time
|
|
9
11
|
import urllib.error
|
|
10
12
|
import urllib.parse
|
|
11
13
|
import urllib.request
|
|
12
14
|
from pathlib import Path
|
|
13
|
-
from typing import Any, Dict, Optional
|
|
15
|
+
from typing import Any, Dict, List, Optional
|
|
14
16
|
|
|
15
|
-
from scripts.core.tikomni_common import
|
|
17
|
+
from scripts.core.tikomni_common import (
|
|
18
|
+
DEFAULT_USER_AGENT,
|
|
19
|
+
call_json_api,
|
|
20
|
+
normalize_text,
|
|
21
|
+
resolve_timeout_retry_policy,
|
|
22
|
+
)
|
|
16
23
|
|
|
17
24
|
DEFAULT_U3_PROVIDER = "oss"
|
|
18
25
|
DEFAULT_CONTENT_TYPE = "video/mp4"
|
|
19
26
|
DOWNLOAD_CHUNK_SIZE = 1024 * 1024
|
|
27
|
+
TIMEOUT_LIKE_HTTP_STATUS_CODES = {408, 429, 502, 503, 504}
|
|
20
28
|
|
|
21
29
|
|
|
22
30
|
def _safe_name_from_url(source_url: str) -> str:
|
|
@@ -135,6 +143,16 @@ def create_u3_upload(
|
|
|
135
143
|
)
|
|
136
144
|
|
|
137
145
|
|
|
146
|
+
def _is_timeout_like_upload_error(status_code: Optional[int], error_reason: Optional[str]) -> bool:
|
|
147
|
+
if isinstance(status_code, (int, float)) and int(status_code) in TIMEOUT_LIKE_HTTP_STATUS_CODES:
|
|
148
|
+
return True
|
|
149
|
+
|
|
150
|
+
reason = str(error_reason or "").strip().lower()
|
|
151
|
+
if not reason:
|
|
152
|
+
return False
|
|
153
|
+
return any(token in reason for token in ("timeout", "timed out", "deadline exceeded"))
|
|
154
|
+
|
|
155
|
+
|
|
138
156
|
def upload_file_to_presigned_url(
|
|
139
157
|
*,
|
|
140
158
|
upload_url: str,
|
|
@@ -147,35 +165,130 @@ def upload_file_to_presigned_url(
|
|
|
147
165
|
try:
|
|
148
166
|
with open(file_path, "rb") as handle:
|
|
149
167
|
data = handle.read()
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
"
|
|
153
|
-
"
|
|
168
|
+
except Exception as error:
|
|
169
|
+
return {
|
|
170
|
+
"ok": False,
|
|
171
|
+
"status_code": None,
|
|
172
|
+
"error_reason": f"u3_upload_failed:{normalize_text(error)}",
|
|
173
|
+
"retry_attempt": 0,
|
|
174
|
+
"timeout_retry_max": 0,
|
|
175
|
+
"timeout_retry_exhausted": False,
|
|
176
|
+
"retry_chain": [],
|
|
154
177
|
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
headers=
|
|
166
|
-
|
|
178
|
+
|
|
179
|
+
headers = {
|
|
180
|
+
"Content-Type": content_type or DEFAULT_CONTENT_TYPE,
|
|
181
|
+
"User-Agent": os.getenv("TIKOMNI_HTTP_USER_AGENT", DEFAULT_USER_AGENT),
|
|
182
|
+
}
|
|
183
|
+
if isinstance(upload_headers, dict):
|
|
184
|
+
for key, value in upload_headers.items():
|
|
185
|
+
header_key = str(key).strip()
|
|
186
|
+
if not header_key:
|
|
187
|
+
continue
|
|
188
|
+
headers[header_key] = str(value)
|
|
189
|
+
|
|
190
|
+
retry_policy = resolve_timeout_retry_policy()
|
|
191
|
+
timeout_retry_max = int(retry_policy.get("max_retries", 0) or 0)
|
|
192
|
+
retry_backoff_ms = int(retry_policy.get("backoff_ms", 0) or 0)
|
|
193
|
+
max_attempts = 1 + timeout_retry_max
|
|
194
|
+
retry_chain: List[Dict[str, Any]] = []
|
|
195
|
+
last_result: Dict[str, Any] = {
|
|
196
|
+
"ok": False,
|
|
197
|
+
"status_code": None,
|
|
198
|
+
"error_reason": "u3_upload_failed:unknown",
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
for attempt in range(1, max_attempts + 1):
|
|
202
|
+
if attempt > 1 and retry_backoff_ms > 0:
|
|
203
|
+
sleep_ms = retry_backoff_ms * (2 ** (attempt - 2))
|
|
204
|
+
time.sleep(sleep_ms / 1000.0)
|
|
205
|
+
|
|
206
|
+
try:
|
|
207
|
+
request = urllib.request.Request(
|
|
208
|
+
upload_url,
|
|
209
|
+
data=data,
|
|
210
|
+
headers=headers,
|
|
211
|
+
method=(upload_method or "PUT").upper(),
|
|
212
|
+
)
|
|
213
|
+
with urllib.request.urlopen(request, timeout=max(timeout_ms / 1000.0, 1.0)) as response:
|
|
214
|
+
status_code = response.getcode()
|
|
215
|
+
result: Dict[str, Any] = {
|
|
216
|
+
"ok": 200 <= int(status_code) < 300,
|
|
217
|
+
"status_code": status_code,
|
|
218
|
+
"error_reason": None if 200 <= int(status_code) < 300 else f"u3_upload_http_{status_code}",
|
|
219
|
+
}
|
|
220
|
+
except urllib.error.HTTPError as error:
|
|
221
|
+
result = {
|
|
222
|
+
"ok": False,
|
|
223
|
+
"status_code": error.code,
|
|
224
|
+
"error_reason": f"u3_upload_http_{error.code}",
|
|
225
|
+
}
|
|
226
|
+
except urllib.error.URLError as error:
|
|
227
|
+
reason_obj = getattr(error, "reason", error)
|
|
228
|
+
reason_text = normalize_text(reason_obj)
|
|
229
|
+
result = {
|
|
230
|
+
"ok": False,
|
|
231
|
+
"status_code": None,
|
|
232
|
+
"error_reason": f"u3_upload_failed:{reason_text or 'network_error'}",
|
|
233
|
+
"_timeout_like": isinstance(reason_obj, socket.timeout)
|
|
234
|
+
or _is_timeout_like_upload_error(status_code=None, error_reason=reason_text),
|
|
235
|
+
}
|
|
236
|
+
except (TimeoutError, socket.timeout) as error:
|
|
237
|
+
result = {
|
|
238
|
+
"ok": False,
|
|
239
|
+
"status_code": None,
|
|
240
|
+
"error_reason": f"u3_upload_failed:{normalize_text(error) or 'timeout'}",
|
|
241
|
+
"_timeout_like": True,
|
|
242
|
+
}
|
|
243
|
+
except Exception as error:
|
|
244
|
+
reason_text = normalize_text(error)
|
|
245
|
+
result = {
|
|
246
|
+
"ok": False,
|
|
247
|
+
"status_code": None,
|
|
248
|
+
"error_reason": f"u3_upload_failed:{reason_text or 'unknown'}",
|
|
249
|
+
"_timeout_like": _is_timeout_like_upload_error(status_code=None, error_reason=reason_text),
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
if result.get("ok"):
|
|
253
|
+
result["retry_attempt"] = max(0, attempt - 1)
|
|
254
|
+
result["timeout_retry_max"] = timeout_retry_max
|
|
255
|
+
result["timeout_retry_exhausted"] = False
|
|
256
|
+
result["retry_chain"] = retry_chain
|
|
257
|
+
return result
|
|
258
|
+
|
|
259
|
+
timeout_like = bool(
|
|
260
|
+
result.pop(
|
|
261
|
+
"_timeout_like",
|
|
262
|
+
_is_timeout_like_upload_error(
|
|
263
|
+
status_code=result.get("status_code"),
|
|
264
|
+
error_reason=result.get("error_reason"),
|
|
265
|
+
),
|
|
266
|
+
)
|
|
167
267
|
)
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
"
|
|
172
|
-
"
|
|
173
|
-
"
|
|
268
|
+
retry_chain.append(
|
|
269
|
+
{
|
|
270
|
+
"attempt": attempt,
|
|
271
|
+
"status_code": result.get("status_code"),
|
|
272
|
+
"error_reason": result.get("error_reason"),
|
|
273
|
+
"timeout_like": timeout_like,
|
|
174
274
|
}
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
275
|
+
)
|
|
276
|
+
last_result = dict(result)
|
|
277
|
+
|
|
278
|
+
if timeout_like and attempt < max_attempts:
|
|
279
|
+
continue
|
|
280
|
+
|
|
281
|
+
last_result["retry_attempt"] = max(0, attempt - 1)
|
|
282
|
+
last_result["timeout_retry_max"] = timeout_retry_max
|
|
283
|
+
last_result["timeout_retry_exhausted"] = bool(timeout_like and attempt >= max_attempts)
|
|
284
|
+
last_result["retry_chain"] = retry_chain
|
|
285
|
+
return last_result
|
|
286
|
+
|
|
287
|
+
last_result["retry_attempt"] = timeout_retry_max
|
|
288
|
+
last_result["timeout_retry_max"] = timeout_retry_max
|
|
289
|
+
last_result["timeout_retry_exhausted"] = True
|
|
290
|
+
last_result["retry_chain"] = retry_chain
|
|
291
|
+
return last_result
|
|
179
292
|
|
|
180
293
|
|
|
181
294
|
def complete_u3_upload(
|
|
@@ -284,6 +397,11 @@ def run_u3_public_url_fallback(
|
|
|
284
397
|
"ok": bool(upload_response.get("ok")),
|
|
285
398
|
"status_code": upload_response.get("status_code"),
|
|
286
399
|
"error_reason": upload_response.get("error_reason"),
|
|
400
|
+
"retry_attempt": upload_response.get("retry_attempt", 0),
|
|
401
|
+
"retry_count": len(upload_response.get("retry_chain") or []),
|
|
402
|
+
"timeout_retry_max": upload_response.get("timeout_retry_max", 0),
|
|
403
|
+
"timeout_retry_exhausted": bool(upload_response.get("timeout_retry_exhausted")),
|
|
404
|
+
"retry_chain": upload_response.get("retry_chain") or [],
|
|
287
405
|
}
|
|
288
406
|
)
|
|
289
407
|
if not upload_response.get("ok"):
|