@tikomni/skills 1.0.4 → 1.0.6

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": "1.0.4",
3
+ "version": "1.0.6",
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",
@@ -1586,22 +1586,8 @@ def run_u2_asr_batch_with_timeout_retry(
1586
1586
  existing = mapped_results.get(file_url)
1587
1587
  if existing is None:
1588
1588
  mapped_results[file_url] = candidate
1589
- continue
1590
-
1591
- old_score = (
1592
- 1 if existing.get("ok") else 0,
1593
- len(str(existing.get("transcript_text") or "")),
1594
- 1 if existing.get("transcription_url") else 0,
1595
- 1 if not existing.get("error_reason") else 0,
1596
- )
1597
- new_score = (
1598
- 1 if candidate.get("ok") else 0,
1599
- len(str(candidate.get("transcript_text") or "")),
1600
- 1 if candidate.get("transcription_url") else 0,
1601
- 1 if not candidate.get("error_reason") else 0,
1602
- )
1603
- if new_score > old_score:
1604
- mapped_results[file_url] = candidate
1589
+ # When the provider returns file_url, treat it as the source of truth.
1590
+ # item_index remains a fallback only for older payloads without file_url.
1605
1591
 
1606
1592
  mapped_results = hydrate_u2_batch_results_from_transcription_urls(
1607
1593
  mapped_results=mapped_results,
@@ -424,7 +424,6 @@ def _u1_fetch_one_video(
424
424
  fallback_reason="" if app_response.get("ok") else (
425
425
  "primary_timeout_retry_exhausted" if app_response.get("timeout_retry_exhausted") else "primary_non_timeout_failure"
426
426
  ),
427
- extra={"response": app_response},
428
427
  )
429
428
  )
430
429
  if app_response.get("ok"):
@@ -458,7 +457,6 @@ def _u1_fetch_one_video(
458
457
  fallback_reason="" if web_response.get("ok") else (
459
458
  "fallback_timeout_retry_exhausted" if web_response.get("timeout_retry_exhausted") else "fallback_non_timeout_failure"
460
459
  ),
461
- extra={"response": web_response},
462
460
  )
463
461
  )
464
462
  web_response["_attempts"] = attempts
@@ -891,23 +889,24 @@ def run_douyin_single_video(
891
889
  all_routes_failed=not bool(one_video_response.get("ok")),
892
890
  )
893
891
  for index, attempt in enumerate(attempts, start=1):
894
- response = attempt.get("response") if isinstance(attempt, dict) else None
892
+ if not isinstance(attempt, dict):
893
+ continue
894
+ response = attempt.get("response") if isinstance(attempt.get("response"), dict) else attempt
895
895
  endpoint = attempt.get("endpoint") if isinstance(attempt, dict) else None
896
896
  label = attempt.get("route_label") if isinstance(attempt, dict) else None
897
- if not isinstance(response, dict):
898
- if attempt.get("skipped"):
899
- trace.append(
900
- {
901
- "step": f"u1_fetch_one_video_attempt_{index}",
902
- "route_label": label,
903
- "endpoint": endpoint,
904
- "accept_reason": attempt.get("accept_reason"),
905
- "fallback_reason": attempt.get("fallback_reason"),
906
- "param_readiness": attempt.get("param_readiness"),
907
- "param_reason": attempt.get("param_reason"),
908
- "skipped": True,
909
- }
910
- )
897
+ if attempt.get("skipped"):
898
+ trace.append(
899
+ {
900
+ "step": f"u1_fetch_one_video_attempt_{index}",
901
+ "route_label": label,
902
+ "endpoint": endpoint,
903
+ "accept_reason": attempt.get("accept_reason"),
904
+ "fallback_reason": attempt.get("fallback_reason"),
905
+ "param_readiness": attempt.get("param_readiness"),
906
+ "param_reason": attempt.get("param_reason"),
907
+ "skipped": True,
908
+ }
909
+ )
911
910
  continue
912
911
  _emit_http_progress(progress, stage="single_video.fetch", response=response, route_label=str(label or "route"))
913
912
  step = "u1_fetch_one_video_effective" if index == len(attempts) else f"u1_fetch_one_video_attempt_{index}"
@@ -1125,7 +1125,6 @@ def _fetch_note_info(
1125
1125
  extra={
1126
1126
  "method": str(route["method"]).upper(),
1127
1127
  "field_completeness": response.get("_field_completeness"),
1128
- "response": response,
1129
1128
  },
1130
1129
  )
1131
1130
  )
@@ -1843,23 +1842,24 @@ def run_xiaohongshu_extract(
1843
1842
  all_routes_failed=not bool(note_response.get("ok")),
1844
1843
  )
1845
1844
  for index, attempt in enumerate(attempts, start=1):
1846
- response = attempt.get("response") if isinstance(attempt, dict) else None
1845
+ if not isinstance(attempt, dict):
1846
+ continue
1847
+ response = attempt.get("response") if isinstance(attempt.get("response"), dict) else attempt
1847
1848
  endpoint = attempt.get("endpoint") if isinstance(attempt, dict) else None
1848
1849
  label = attempt.get("route_label") if isinstance(attempt, dict) else None
1849
- if not isinstance(response, dict):
1850
- if attempt.get("skipped"):
1851
- trace.append(
1852
- {
1853
- "step": f"u1_get_note_info_attempt_{index}",
1854
- "route_label": label,
1855
- "endpoint": endpoint,
1856
- "accept_reason": attempt.get("accept_reason"),
1857
- "fallback_reason": attempt.get("fallback_reason"),
1858
- "param_readiness": attempt.get("param_readiness"),
1859
- "param_reason": attempt.get("param_reason"),
1860
- "skipped": True,
1861
- }
1862
- )
1850
+ if attempt.get("skipped"):
1851
+ trace.append(
1852
+ {
1853
+ "step": f"u1_get_note_info_attempt_{index}",
1854
+ "route_label": label,
1855
+ "endpoint": endpoint,
1856
+ "accept_reason": attempt.get("accept_reason"),
1857
+ "fallback_reason": attempt.get("fallback_reason"),
1858
+ "param_readiness": attempt.get("param_readiness"),
1859
+ "param_reason": attempt.get("param_reason"),
1860
+ "skipped": True,
1861
+ }
1862
+ )
1863
1863
  continue
1864
1864
  step = "u1_get_note_info_effective" if index == len(attempts) else f"u1_get_note_info_attempt_{index}"
1865
1865
  trace.append(
@@ -2,6 +2,7 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ import json
5
6
  import os
6
7
  import sys
7
8
  import unittest
@@ -12,11 +13,61 @@ SKILL_ROOT = Path(__file__).resolve().parents[1]
12
13
  if str(SKILL_ROOT) not in sys.path:
13
14
  sys.path.insert(0, str(SKILL_ROOT))
14
15
 
16
+ from scripts.core.asr_pipeline import run_u2_asr_batch_with_timeout_retry
15
17
  from scripts.pipelines import homepage_collectors
18
+ from scripts.pipelines import run_douyin_single_work
16
19
  from scripts.pipelines import run_xiaohongshu_single_work
17
20
 
18
21
 
19
22
  class FixedPipelineFallbackTest(unittest.TestCase):
23
+ def test_douyin_single_fetch_attempt_trace_is_json_serializable(self) -> None:
24
+ def fake_call_json_api(**_: object) -> dict:
25
+ return {
26
+ "ok": True,
27
+ "status_code": 200,
28
+ "request_id": "req-dy-single",
29
+ "error_reason": None,
30
+ "data": {"aweme_detail": {"aweme_id": "123"}},
31
+ }
32
+
33
+ with patch.object(run_douyin_single_work, "call_json_api", side_effect=fake_call_json_api):
34
+ response = run_douyin_single_work._u1_fetch_one_video(
35
+ base_url="https://api.tikomni.com",
36
+ token="test-token",
37
+ share_url="https://v.douyin.com/test-single/",
38
+ app_timeout_ms=1000,
39
+ web_timeout_ms=1000,
40
+ )
41
+
42
+ json.dumps(response, ensure_ascii=False)
43
+ self.assertNotIn("response", response["_attempts"][0])
44
+
45
+ def test_xhs_single_fetch_attempt_trace_is_json_serializable(self) -> None:
46
+ def fake_call_json_api(**_: object) -> dict:
47
+ return {
48
+ "ok": True,
49
+ "status_code": 200,
50
+ "request_id": "req-xhs-single",
51
+ "error_reason": None,
52
+ "data": {
53
+ "title": "test title",
54
+ "desc": "test content",
55
+ "video": {"master_url": "https://example.com/video.mp4"},
56
+ },
57
+ }
58
+
59
+ with patch.object(run_xiaohongshu_single_work, "call_json_api", side_effect=fake_call_json_api):
60
+ response = run_xiaohongshu_single_work._fetch_note_info(
61
+ base_url="https://api.tikomni.com",
62
+ token="test-token",
63
+ timeout_ms=1000,
64
+ source_input={"share_text": "https://xhslink.com/test-single", "note_id": "note123"},
65
+ progress=None,
66
+ )
67
+
68
+ json.dumps(response, ensure_ascii=False)
69
+ self.assertNotIn("response", response["_attempts"][0])
70
+
20
71
  def test_xhs_single_route_plan_respects_version_priority_and_cookie_gate(self) -> None:
21
72
  source_input = {
22
73
  "share_text": (
@@ -164,6 +215,71 @@ class FixedPipelineFallbackTest(unittest.TestCase):
164
215
  self.assertEqual(web_attempt.get("param_reason"), "fallback_requires_cookie")
165
216
  self.assertEqual(raw.get("error_reason"), "posts_all_routes_failed")
166
217
 
218
+ def test_u2_batch_prefers_file_url_mapping_over_item_index_fallback(self) -> None:
219
+ file_url = "https://example.com/video.mp4"
220
+ raw_task = {
221
+ "data": {
222
+ "task_status": "SUCCEEDED",
223
+ "task_metrics": {"TOTAL": 1, "SUCCEEDED": 1, "FAILED": 0},
224
+ "items": [
225
+ {
226
+ "item_index": 0,
227
+ "task_status": "SUCCEEDED",
228
+ "transcript_text": "索引映射文本。",
229
+ "transcription_url": "https://example.com/index.json",
230
+ },
231
+ {
232
+ "file_url": file_url,
233
+ "task_status": "SUCCEEDED",
234
+ "transcript_text": "file_url 映射文本。",
235
+ "transcription_url": "https://example.com/file.json",
236
+ },
237
+ ],
238
+ }
239
+ }
240
+
241
+ submit_bundle = {
242
+ "submit_response": {"ok": True, "request_id": "req-submit"},
243
+ "task_id": "task-1",
244
+ "final_submit_status": "success",
245
+ "retry_chain": [],
246
+ }
247
+ poll_result = {
248
+ "ok": True,
249
+ "task_id": "task-1",
250
+ "task_status": "SUCCEEDED",
251
+ "request_id": "req-poll",
252
+ "error_reason": "",
253
+ "raw_task": raw_task,
254
+ "task_metrics": {"TOTAL": 1, "SUCCEEDED": 1, "FAILED": 0},
255
+ "batch_progress": {"expected_total": 1, "complete": True},
256
+ "batch_complete": True,
257
+ "trace": [],
258
+ }
259
+
260
+ with patch(
261
+ "scripts.core.asr_pipeline.submit_u2_asr_batch_with_retry",
262
+ return_value=submit_bundle,
263
+ ), patch(
264
+ "scripts.core.asr_pipeline.poll_u2_task_core",
265
+ return_value=poll_result,
266
+ ):
267
+ bundle = run_u2_asr_batch_with_timeout_retry(
268
+ base_url="https://api.tikomni.com",
269
+ token="test-token",
270
+ timeout_ms=1000,
271
+ file_urls=[file_url],
272
+ submit_max_retries=0,
273
+ submit_backoff_ms=0,
274
+ poll_interval_sec=0.01,
275
+ max_polls=1,
276
+ timeout_retry_enabled=False,
277
+ timeout_retry_max_retries=0,
278
+ )
279
+
280
+ mapped_item = bundle["mapped_results"][file_url]
281
+ self.assertEqual(mapped_item["transcript_text"], "file_url 映射文本。")
282
+
167
283
 
168
284
  if __name__ == "__main__":
169
285
  unittest.main()