@witty-ai/skill-insight 0.5.0-beta → 0.6.0-beta

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 (223) hide show
  1. package/.env.example +5 -0
  2. package/.next/standalone/.next/BUILD_ID +1 -1
  3. package/.next/standalone/.next/app-path-routes-manifest.json +4 -0
  4. package/.next/standalone/.next/build-manifest.json +2 -2
  5. package/.next/standalone/.next/prerender-manifest.json +3 -3
  6. package/.next/standalone/.next/routes-manifest.json +24 -0
  7. package/.next/standalone/.next/server/app/_global-error.html +2 -2
  8. package/.next/standalone/.next/server/app/_global-error.rsc +1 -1
  9. package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  10. package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  11. package/.next/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  12. package/.next/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  13. package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  14. package/.next/standalone/.next/server/app/_not-found.html +1 -1
  15. package/.next/standalone/.next/server/app/_not-found.rsc +1 -1
  16. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  17. package/.next/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  18. package/.next/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  19. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  20. package/.next/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  21. package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  22. package/.next/standalone/.next/server/app/api/config/route.js +3 -3
  23. package/.next/standalone/.next/server/app/api/config/route.js.nft.json +1 -1
  24. package/.next/standalone/.next/server/app/api/data/route.js +3 -3
  25. package/.next/standalone/.next/server/app/api/data/route.js.nft.json +1 -1
  26. package/.next/standalone/.next/server/app/api/otel/v1/logs/route.js +3 -3
  27. package/.next/standalone/.next/server/app/api/otel/v1/logs/route.js.nft.json +1 -1
  28. package/.next/standalone/.next/server/app/api/otel/v1/traces/route.js +1 -1
  29. package/.next/standalone/.next/server/app/api/otel/v1/traces/route.js.nft.json +1 -1
  30. package/.next/standalone/.next/server/app/api/proxy/[taskId]/end/route.js +4 -4
  31. package/.next/standalone/.next/server/app/api/proxy/[taskId]/end/route.js.nft.json +1 -1
  32. package/.next/standalone/.next/server/app/api/rejudge/route.js +3 -3
  33. package/.next/standalone/.next/server/app/api/rejudge/route.js.nft.json +1 -1
  34. package/.next/standalone/.next/server/app/api/session/route.js +1 -1
  35. package/.next/standalone/.next/server/app/api/session/route.js.nft.json +1 -1
  36. package/.next/standalone/.next/server/app/api/setup/opencode-commands/si-optimizer/route/app-paths-manifest.json +3 -0
  37. package/.next/standalone/.next/server/app/api/setup/opencode-commands/si-optimizer/route/build-manifest.json +11 -0
  38. package/.next/standalone/.next/server/app/api/setup/opencode-commands/si-optimizer/route/server-reference-manifest.json +4 -0
  39. package/.next/standalone/.next/server/app/api/setup/opencode-commands/si-optimizer/route.js +6 -0
  40. package/.next/standalone/.next/server/app/api/setup/opencode-commands/si-optimizer/route.js.map +5 -0
  41. package/.next/standalone/.next/server/app/api/setup/opencode-commands/si-optimizer/route.js.nft.json +1 -0
  42. package/.next/standalone/.next/server/app/api/setup/opencode-commands/si-optimizer/route_client-reference-manifest.js +2 -0
  43. package/.next/standalone/.next/server/app/api/setup/opencode-tui/route/app-paths-manifest.json +3 -0
  44. package/.next/standalone/.next/server/app/api/setup/opencode-tui/route/build-manifest.json +11 -0
  45. package/.next/standalone/.next/server/app/api/setup/opencode-tui/route/server-reference-manifest.json +4 -0
  46. package/.next/standalone/.next/server/app/api/setup/opencode-tui/route.js +6 -0
  47. package/.next/standalone/.next/server/app/api/setup/opencode-tui/route.js.map +5 -0
  48. package/.next/standalone/.next/server/app/api/setup/opencode-tui/route.js.nft.json +1 -0
  49. package/.next/standalone/.next/server/app/api/setup/opencode-tui/route_client-reference-manifest.js +2 -0
  50. package/.next/standalone/.next/server/app/api/skills/[id]/versions/[version]/download/route.js.nft.json +1 -1
  51. package/.next/standalone/.next/server/app/api/skills/[id]/versions/[version]/route.js +3 -2
  52. package/.next/standalone/.next/server/app/api/skills/[id]/versions/[version]/route.js.nft.json +1 -1
  53. package/.next/standalone/.next/server/app/api/skills/automation/import/route.js.nft.json +1 -1
  54. package/.next/standalone/.next/server/app/api/skills/logs/route.js +2 -2
  55. package/.next/standalone/.next/server/app/api/skills/logs/route.js.nft.json +1 -1
  56. package/.next/standalone/.next/server/app/api/skills/route.js +3 -2
  57. package/.next/standalone/.next/server/app/api/skills/route.js.nft.json +1 -1
  58. package/.next/standalone/.next/server/app/api/skills/sync-enterprise/route/app-paths-manifest.json +3 -0
  59. package/.next/standalone/.next/server/app/api/skills/sync-enterprise/route/build-manifest.json +11 -0
  60. package/.next/standalone/.next/server/app/api/skills/sync-enterprise/route/server-reference-manifest.json +4 -0
  61. package/.next/standalone/.next/server/app/api/skills/sync-enterprise/route.js +8 -0
  62. package/.next/standalone/.next/server/app/api/skills/sync-enterprise/route.js.map +5 -0
  63. package/.next/standalone/.next/server/app/api/skills/sync-enterprise/route.js.nft.json +1 -0
  64. package/.next/standalone/.next/server/app/api/skills/sync-enterprise/route_client-reference-manifest.js +2 -0
  65. package/.next/standalone/.next/server/app/api/task-stats/route/app-paths-manifest.json +3 -0
  66. package/.next/standalone/.next/server/app/api/task-stats/route/build-manifest.json +11 -0
  67. package/.next/standalone/.next/server/app/api/task-stats/route/server-reference-manifest.json +4 -0
  68. package/.next/standalone/.next/server/app/api/task-stats/route.js +11 -0
  69. package/.next/standalone/.next/server/app/api/task-stats/route.js.map +5 -0
  70. package/.next/standalone/.next/server/app/api/task-stats/route.js.nft.json +1 -0
  71. package/.next/standalone/.next/server/app/api/task-stats/route_client-reference-manifest.js +2 -0
  72. package/.next/standalone/.next/server/app/api/upload/route.js +1 -1
  73. package/.next/standalone/.next/server/app/api/upload/route.js.nft.json +1 -1
  74. package/.next/standalone/.next/server/app/details/page/react-loadable-manifest.json +2 -2
  75. package/.next/standalone/.next/server/app/details/page_client-reference-manifest.js +1 -1
  76. package/.next/standalone/.next/server/app/details.html +1 -1
  77. package/.next/standalone/.next/server/app/details.rsc +2 -2
  78. package/.next/standalone/.next/server/app/details.segments/_full.segment.rsc +2 -2
  79. package/.next/standalone/.next/server/app/details.segments/_head.segment.rsc +1 -1
  80. package/.next/standalone/.next/server/app/details.segments/_index.segment.rsc +1 -1
  81. package/.next/standalone/.next/server/app/details.segments/_tree.segment.rsc +1 -1
  82. package/.next/standalone/.next/server/app/details.segments/details/__PAGE__.segment.rsc +2 -2
  83. package/.next/standalone/.next/server/app/details.segments/details.segment.rsc +1 -1
  84. package/.next/standalone/.next/server/app/index.html +1 -1
  85. package/.next/standalone/.next/server/app/index.rsc +2 -2
  86. package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  87. package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +2 -2
  88. package/.next/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  89. package/.next/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  90. package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  91. package/.next/standalone/.next/server/app/login.html +1 -1
  92. package/.next/standalone/.next/server/app/login.rsc +1 -1
  93. package/.next/standalone/.next/server/app/login.segments/_full.segment.rsc +1 -1
  94. package/.next/standalone/.next/server/app/login.segments/_head.segment.rsc +1 -1
  95. package/.next/standalone/.next/server/app/login.segments/_index.segment.rsc +1 -1
  96. package/.next/standalone/.next/server/app/login.segments/_tree.segment.rsc +1 -1
  97. package/.next/standalone/.next/server/app/login.segments/login/__PAGE__.segment.rsc +1 -1
  98. package/.next/standalone/.next/server/app/login.segments/login.segment.rsc +1 -1
  99. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  100. package/.next/standalone/.next/server/app/skills.html +1 -1
  101. package/.next/standalone/.next/server/app/skills.rsc +1 -1
  102. package/.next/standalone/.next/server/app/skills.segments/_full.segment.rsc +1 -1
  103. package/.next/standalone/.next/server/app/skills.segments/_head.segment.rsc +1 -1
  104. package/.next/standalone/.next/server/app/skills.segments/_index.segment.rsc +1 -1
  105. package/.next/standalone/.next/server/app/skills.segments/_tree.segment.rsc +1 -1
  106. package/.next/standalone/.next/server/app/skills.segments/skills/__PAGE__.segment.rsc +1 -1
  107. package/.next/standalone/.next/server/app/skills.segments/skills.segment.rsc +1 -1
  108. package/.next/standalone/.next/server/app-paths-manifest.json +4 -0
  109. package/.next/standalone/.next/server/chunks/[root-of-the-server]__15dbd1f2._.js +3 -0
  110. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1ce5e3b8._.js +1 -1
  111. package/.next/standalone/.next/server/chunks/[root-of-the-server]__53775b48._.js +1 -1
  112. package/.next/standalone/.next/server/chunks/[root-of-the-server]__6923eecf._.js +1 -1
  113. package/.next/standalone/.next/server/chunks/[root-of-the-server]__6d8053e2._.js +1 -1
  114. package/.next/standalone/.next/server/chunks/[root-of-the-server]__8402dfd1._.js +3 -0
  115. package/.next/standalone/.next/server/chunks/[root-of-the-server]__863cf6de._.js +1 -1
  116. package/.next/standalone/.next/server/chunks/[root-of-the-server]__89404730._.js +3 -0
  117. package/.next/standalone/.next/server/chunks/[root-of-the-server]__aa5c8858._.js +2 -2
  118. package/.next/standalone/.next/server/chunks/[root-of-the-server]__c20da96a._.js +3 -0
  119. package/.next/standalone/.next/server/chunks/[root-of-the-server]__ddf63a21._.js +3 -0
  120. package/.next/standalone/.next/server/chunks/[root-of-the-server]__f9e66e02._.js +3 -0
  121. package/.next/standalone/.next/server/chunks/_3e8b4d8c._.js +1 -1
  122. package/.next/standalone/.next/server/chunks/_41a98bd8._.js +1 -1
  123. package/.next/standalone/.next/server/chunks/_4c806e26._.js +3 -0
  124. package/.next/standalone/.next/server/chunks/_cd3d20ca._.js +1 -1
  125. package/.next/standalone/.next/server/chunks/_ddffef3e._.js +1 -1
  126. package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_setup_opencode-tui_route_actions_fc8ae29f.js +3 -0
  127. package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_skills_sync-enterprise_route_actions_0ca45899.js +3 -0
  128. package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_task-stats_route_actions_983505cd.js +3 -0
  129. package/.next/standalone/.next/server/chunks/ce889_server_app_api_setup_opencode-commands_si-optimizer_route_actions_fcde30ef.js +3 -0
  130. package/.next/standalone/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_c33286ed.js +47 -4
  131. package/.next/standalone/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_f42faeee.js +1 -1
  132. package/.next/standalone/.next/server/chunks/src_98433cb8._.js +175 -0
  133. package/.next/standalone/.next/server/chunks/src_lib_12408140._.js +1 -1
  134. package/.next/standalone/.next/server/chunks/ssr/_c8c8c083._.js +2 -2
  135. package/.next/standalone/.next/server/chunks/ssr/_fd46f439._.js +2 -1
  136. package/.next/standalone/.next/server/chunks/ssr/node_modules_dagre-d3-es_src_dagre_index_3582f3d0.js +1 -1
  137. package/.next/standalone/.next/server/chunks/ssr/node_modules_lodash-es_a1341fea._.js +1 -1
  138. package/.next/standalone/.next/server/chunks/ssr/node_modules_lodash-es_e1de6ed8._.js +1 -1
  139. package/.next/standalone/.next/server/pages/404.html +1 -1
  140. package/.next/standalone/.next/server/pages/500.html +2 -2
  141. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  142. package/.next/standalone/.next/server/server-reference-manifest.json +1 -1
  143. package/.next/standalone/.next/static/chunks/{01eddf501c574a44.js → 4ee8dc41c9f15b7b.js} +1 -1
  144. package/.next/standalone/.next/static/chunks/{737b8cff3c6a4e30.js → 9445b2873a413c58.js} +1 -1
  145. package/.next/standalone/.next/static/chunks/94dfb15df65ef720.js +2 -0
  146. package/.next/standalone/.next/static/chunks/9d1c5c3494fa53de.js +109 -0
  147. package/.next/standalone/.next/static/chunks/{e09d9ee16fe90255.js → cd0fde15dc0dfcca.js} +1 -1
  148. package/.next/standalone/.next/static/chunks/{ff6357067630b168.js → e13d208072a48316.js} +1 -1
  149. package/.next/standalone/node_modules/.prisma/client/edge.js +6 -3
  150. package/.next/standalone/node_modules/.prisma/client/index-browser.js +3 -0
  151. package/.next/standalone/node_modules/.prisma/client/index.js +6 -3
  152. package/.next/standalone/node_modules/.prisma/client/package.json +1 -1
  153. package/.next/standalone/node_modules/.prisma/client/schema.prisma +12 -9
  154. package/.next/standalone/node_modules/.prisma/client/wasm.js +3 -0
  155. package/.next/standalone/node_modules/adm-zip/util/constants.js +142 -0
  156. package/.next/standalone/node_modules/adm-zip/util/decoder.js +5 -0
  157. package/.next/standalone/node_modules/adm-zip/util/errors.js +63 -0
  158. package/.next/standalone/node_modules/adm-zip/util/fattr.js +76 -0
  159. package/.next/standalone/node_modules/adm-zip/util/index.js +5 -0
  160. package/.next/standalone/node_modules/adm-zip/util/utils.js +339 -0
  161. package/.next/standalone/package.json +6 -2
  162. package/.next/standalone/prisma/schema.prisma +3 -0
  163. package/.next/standalone/scripts/opencode_plugin.ts +23 -5
  164. package/.next/standalone/scripts/opencode_tui_plugin.tsx +308 -0
  165. package/.next/standalone/scripts/si-optimizer.md +5 -0
  166. package/.next/static/chunks/{01eddf501c574a44.js → 4ee8dc41c9f15b7b.js} +1 -1
  167. package/.next/static/chunks/{737b8cff3c6a4e30.js → 9445b2873a413c58.js} +1 -1
  168. package/.next/static/chunks/94dfb15df65ef720.js +2 -0
  169. package/.next/static/chunks/9d1c5c3494fa53de.js +109 -0
  170. package/.next/static/chunks/{e09d9ee16fe90255.js → cd0fde15dc0dfcca.js} +1 -1
  171. package/.next/static/chunks/{ff6357067630b168.js → e13d208072a48316.js} +1 -1
  172. package/package.json +6 -2
  173. package/prisma/schema.prisma +3 -0
  174. package/scripts/activate_telemetry.sh +44 -1
  175. package/scripts/opencode_plugin.ts +23 -5
  176. package/scripts/opencode_tui_plugin.tsx +308 -0
  177. package/scripts/si-optimizer.md +5 -0
  178. package/scripts/utils.js +0 -1
  179. package/.next/standalone/.next/server/chunks/[root-of-the-server]__e2f0baee._.js +0 -3
  180. package/.next/standalone/.next/server/chunks/[root-of-the-server]__fc05579d._.js +0 -3
  181. package/.next/standalone/.next/server/chunks/src_497d2ad2._.js +0 -175
  182. package/.next/standalone/.next/static/chunks/4071dbec4cf7e72e.js +0 -109
  183. package/.next/standalone/.next/static/chunks/a87b5e84254095a5.js +0 -1
  184. package/.next/standalone/LICENSE +0 -21
  185. package/.next/standalone/bin/cli.js +0 -106
  186. package/.next/standalone/custom-models.example.json +0 -21
  187. package/.next/standalone/eslint.config.mjs +0 -18
  188. package/.next/standalone/features/feature-skill-used-jump-link/design/2026-03-18-skill-used-jump-link-design.md +0 -126
  189. package/.next/standalone/features/feature-skill-used-jump-link/feature.json +0 -32
  190. package/.next/standalone/features/feature-skill-used-jump-link/issue.md +0 -32
  191. package/.next/standalone/features/feature-skill-used-jump-link/plans/2026-03-18-skill-used-jump-link.md +0 -528
  192. package/.next/standalone/next.config.ts +0 -41
  193. package/.next/standalone/scripts/activate_telemetry.sh +0 -159
  194. package/.next/standalone/scripts/create_migration_package.sh +0 -124
  195. package/.next/standalone/scripts/fix_models.js +0 -66
  196. package/.next/standalone/scripts/init_opengauss.py +0 -284
  197. package/.next/standalone/scripts/install.js +0 -273
  198. package/.next/standalone/scripts/logs.js +0 -38
  199. package/.next/standalone/scripts/otel_data/logs.jsonl +0 -12
  200. package/.next/standalone/scripts/otel_data/metrics.jsonl +0 -21
  201. package/.next/standalone/scripts/otel_data/raw_requests.jsonl +0 -8
  202. package/.next/standalone/scripts/otel_data/raw_requests.jsonl.bak +0 -6
  203. package/.next/standalone/scripts/otel_receiver.py +0 -580
  204. package/.next/standalone/scripts/postinstall.js +0 -192
  205. package/.next/standalone/scripts/publish-npm.js +0 -401
  206. package/.next/standalone/scripts/restart.js +0 -26
  207. package/.next/standalone/scripts/restart.sh +0 -138
  208. package/.next/standalone/scripts/restart_dev.sh +0 -132
  209. package/.next/standalone/scripts/start.js +0 -291
  210. package/.next/standalone/scripts/status.js +0 -41
  211. package/.next/standalone/scripts/stop.js +0 -90
  212. package/.next/standalone/scripts/sync_skills.js +0 -216
  213. package/.next/standalone/scripts/utils.js +0 -235
  214. package/.next/standalone/tests/setup_skill_optimizer.sh +0 -118
  215. package/.next/standalone/tsconfig.json +0 -34
  216. package/.next/static/chunks/4071dbec4cf7e72e.js +0 -109
  217. package/.next/static/chunks/a87b5e84254095a5.js +0 -1
  218. /package/.next/standalone/.next/static/{H581Rok68JtPV4bAVVH3l → 0uvhCJooDO_gMNlKOaHwB}/_buildManifest.js +0 -0
  219. /package/.next/standalone/.next/static/{H581Rok68JtPV4bAVVH3l → 0uvhCJooDO_gMNlKOaHwB}/_clientMiddlewareManifest.json +0 -0
  220. /package/.next/standalone/.next/static/{H581Rok68JtPV4bAVVH3l → 0uvhCJooDO_gMNlKOaHwB}/_ssgManifest.js +0 -0
  221. /package/.next/static/{H581Rok68JtPV4bAVVH3l → 0uvhCJooDO_gMNlKOaHwB}/_buildManifest.js +0 -0
  222. /package/.next/static/{H581Rok68JtPV4bAVVH3l → 0uvhCJooDO_gMNlKOaHwB}/_clientMiddlewareManifest.json +0 -0
  223. /package/.next/static/{H581Rok68JtPV4bAVVH3l → 0uvhCJooDO_gMNlKOaHwB}/_ssgManifest.js +0 -0
@@ -1,580 +0,0 @@
1
- #!/usr/bin/env python3
2
- """
3
- OTEL Receiver v3 - 使用 Flask-like 的 WSGI 方式处理 chunked body
4
-
5
- Claude Code 的 OTEL SDK 发送的请求没有 Content-Length(chunked transfer encoding),
6
- Python http.server 无法正确处理。使用 socketserver + 手动解析 chunked 编码来修复。
7
- """
8
-
9
- import gzip
10
- import json
11
- import os
12
- import re
13
- import socket
14
- import sys
15
- import threading
16
- from datetime import datetime
17
-
18
- LISTEN_PORT = 4318
19
- LOG_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "otel_data")
20
- METRICS_LOG = os.path.join(LOG_DIR, "metrics.jsonl")
21
- LOGS_LOG = os.path.join(LOG_DIR, "logs.jsonl")
22
- RAW_LOG = os.path.join(LOG_DIR, "raw_requests.jsonl")
23
-
24
- stats = {"requests": 0}
25
- lock = threading.Lock()
26
-
27
-
28
- def extract_attributes(attrs):
29
- result = {}
30
- if not isinstance(attrs, list):
31
- return result
32
- for attr in attrs:
33
- key = attr.get("key", "")
34
- value = attr.get("value", {})
35
- if isinstance(value, dict):
36
- for vtype in ["stringValue", "intValue", "doubleValue", "boolValue"]:
37
- if vtype in value:
38
- result[key] = value[vtype]
39
- break
40
- else:
41
- result[key] = value
42
- return result
43
-
44
-
45
- def write_log(filepath, record):
46
- try:
47
- with open(filepath, "a", encoding="utf-8") as f:
48
- f.write(json.dumps(record, ensure_ascii=False, default=str) + "\n")
49
- except Exception as e:
50
- print(f" ⚠️ 写入失败: {e}", flush=True)
51
-
52
-
53
- def read_chunked(conn):
54
- """手动读取 chunked transfer encoding 数据"""
55
- data = b""
56
- while True:
57
- # 读取 chunk size 行
58
- size_line = b""
59
- while not size_line.endswith(b"\r\n"):
60
- byte = conn.recv(1)
61
- if not byte:
62
- return data
63
- size_line += byte
64
-
65
- # 解析 chunk size
66
- size_str = size_line.strip().decode("ascii", errors="ignore").split(";")[0]
67
- try:
68
- chunk_size = int(size_str, 16)
69
- except ValueError:
70
- return data
71
-
72
- if chunk_size == 0:
73
- # 读取 trailing \r\n
74
- conn.recv(2)
75
- break
76
-
77
- # 读取 chunk 数据
78
- chunk = b""
79
- remaining = chunk_size
80
- while remaining > 0:
81
- part = conn.recv(min(remaining, 65536))
82
- if not part:
83
- return data
84
- chunk += part
85
- remaining -= len(part)
86
- data += chunk
87
-
88
- # 读取 chunk 后的 \r\n
89
- conn.recv(2)
90
-
91
- return data
92
-
93
-
94
- def handle_connection(conn, addr):
95
- """处理单个 HTTP 连接"""
96
- try:
97
- # 读取请求头
98
- header_data = b""
99
- while b"\r\n\r\n" not in header_data:
100
- chunk = conn.recv(4096)
101
- if not chunk:
102
- return
103
- header_data += chunk
104
-
105
- # 分离 header 和可能的 body 前段
106
- header_end = header_data.index(b"\r\n\r\n") + 4
107
- header_bytes = header_data[:header_end]
108
- body_start = header_data[header_end:]
109
-
110
- # 解析请求行和 headers
111
- header_text = header_bytes.decode("utf-8", errors="replace")
112
- lines = header_text.split("\r\n")
113
- request_line = lines[0]
114
- parts = request_line.split(" ")
115
- method = parts[0] if len(parts) > 0 else "?"
116
- path = parts[1] if len(parts) > 1 else "?"
117
-
118
- headers = {}
119
- for line in lines[1:]:
120
- if ": " in line:
121
- k, v = line.split(": ", 1)
122
- headers[k.lower()] = v
123
-
124
- with lock:
125
- stats["requests"] += 1
126
- req_num = stats["requests"]
127
-
128
- timestamp = datetime.now().isoformat()
129
- content_length = int(headers.get("content-length", -1))
130
- transfer_encoding = headers.get("transfer-encoding", "")
131
- content_type = headers.get("content-type", "unknown")
132
- content_encoding = headers.get("content-encoding", "none")
133
-
134
- # 处理 OPTIONS
135
- if method == "OPTIONS":
136
- response = "HTTP/1.1 200 OK\r\nAccess-Control-Allow-Origin: *\r\nAccess-Control-Allow-Methods: POST, GET, OPTIONS\r\nAccess-Control-Allow-Headers: *\r\nContent-Length: 0\r\n\r\n"
137
- conn.sendall(response.encode())
138
- return
139
-
140
- # 读取 body
141
- body_raw = b""
142
- if "chunked" in transfer_encoding:
143
- # chunked 编码:body_start 包含了 chunked 数据的开头
144
- # 我们需要把 body_start 和后续数据拼起来处理
145
- # 简化:用临时方式处理
146
- all_data = body_start
147
- conn.settimeout(2.0)
148
- try:
149
- while True:
150
- more = conn.recv(65536)
151
- if not more:
152
- break
153
- all_data += more
154
- except socket.timeout:
155
- pass
156
-
157
- # 解析 chunked 编码
158
- body_raw = decode_chunked(all_data)
159
-
160
- elif content_length > 0:
161
- body_raw = body_start
162
- remaining = content_length - len(body_start)
163
- while remaining > 0:
164
- chunk = conn.recv(min(remaining, 65536))
165
- if not chunk:
166
- break
167
- body_raw += chunk
168
- remaining -= len(chunk)
169
-
170
- elif content_length == 0:
171
- body_raw = b""
172
- else:
173
- # 没有 content-length 也没有 chunked,尝试读取
174
- body_raw = body_start
175
- conn.settimeout(1.0)
176
- try:
177
- while True:
178
- more = conn.recv(65536)
179
- if not more:
180
- break
181
- body_raw += more
182
- except socket.timeout:
183
- pass
184
-
185
- print(f"\n{'='*60}", flush=True)
186
- print(f"[#{req_num}] {timestamp}", flush=True)
187
- print(f" {method} {path}", flush=True)
188
- print(f" Content-Type: {content_type}", flush=True)
189
- print(f" Content-Length: {content_length}", flush=True)
190
- print(f" Transfer-Encoding: {transfer_encoding or 'none'}", flush=True)
191
- print(f" Content-Encoding: {content_encoding}", flush=True)
192
- print(f" Body size: {len(body_raw)} bytes", flush=True)
193
-
194
- # gzip 解压
195
- decoded_body = body_raw
196
- if body_raw[:2] == b"\x1f\x8b" or content_encoding == "gzip":
197
- try:
198
- decoded_body = gzip.decompress(body_raw)
199
- print(
200
- f" ✅ gzip: {len(body_raw)} → {len(decoded_body)} bytes",
201
- flush=True,
202
- )
203
- except Exception as e:
204
- print(f" ❌ gzip 失败: {e}", flush=True)
205
-
206
- # JSON 解析
207
- body = None
208
- if len(decoded_body) > 0:
209
- try:
210
- body = json.loads(decoded_body.decode("utf-8", errors="replace"))
211
- keys = (
212
- list(body.keys()) if isinstance(body, dict) else type(body).__name__
213
- )
214
- print(f" ✅ JSON 解析成功! keys: {keys}", flush=True)
215
- except Exception as e:
216
- print(f" ❌ JSON 失败: {e}", flush=True)
217
- print(f" Preview: {decoded_body[:300]}", flush=True)
218
- else:
219
- print(f" ⚠️ Body 为空", flush=True)
220
-
221
- # 记录 raw
222
- raw_record = {
223
- "req_num": req_num,
224
- "timestamp": timestamp,
225
- "path": path,
226
- "content_type": content_type,
227
- "content_length": content_length,
228
- "transfer_encoding": transfer_encoding,
229
- "body_size": len(body_raw),
230
- "decoded_size": len(decoded_body),
231
- "parsed": body is not None,
232
- }
233
- write_log(RAW_LOG, raw_record)
234
-
235
- # 处理数据
236
- if body and isinstance(body, dict):
237
- path_clean = path.rstrip("/").split("?")[0]
238
- if path_clean.endswith("/v1/metrics"):
239
- process_metrics(body, timestamp)
240
- elif path_clean.endswith("/v1/logs"):
241
- process_logs(body, timestamp)
242
- else:
243
- print(f" 📝 未知路径: {path_clean}", flush=True)
244
-
245
- # 返回 200
246
- resp_body = json.dumps({"partialSuccess": {}}).encode()
247
- response = (
248
- f"HTTP/1.1 200 OK\r\n"
249
- f"Content-Type: application/json\r\n"
250
- f"Content-Length: {len(resp_body)}\r\n"
251
- f"Connection: close\r\n"
252
- f"\r\n"
253
- )
254
- conn.sendall(response.encode() + resp_body)
255
-
256
- except Exception as e:
257
- print(f" ❌ 连接处理错误: {e}", flush=True)
258
- try:
259
- resp = "HTTP/1.1 500 Internal Server Error\r\nContent-Length: 0\r\nConnection: close\r\n\r\n"
260
- conn.sendall(resp.encode())
261
- except:
262
- pass
263
- finally:
264
- try:
265
- conn.close()
266
- except:
267
- pass
268
-
269
-
270
- def decode_chunked(data):
271
- """解码 chunked transfer encoding"""
272
- result = b""
273
- pos = 0
274
- while pos < len(data):
275
- # 找到 chunk size 行的结尾
276
- end = data.find(b"\r\n", pos)
277
- if end == -1:
278
- break
279
-
280
- size_str = data[pos:end].decode("ascii", errors="ignore").split(";")[0].strip()
281
- try:
282
- chunk_size = int(size_str, 16)
283
- except ValueError:
284
- break
285
-
286
- if chunk_size == 0:
287
- break
288
-
289
- # 提取 chunk 数据
290
- chunk_start = end + 2
291
- chunk_end = chunk_start + chunk_size
292
- if chunk_end > len(data):
293
- # 不完整的 chunk,取能取的
294
- result += data[chunk_start:]
295
- break
296
- result += data[chunk_start:chunk_end]
297
-
298
- # 跳过 chunk 后的 \r\n
299
- pos = chunk_end + 2
300
-
301
- return result
302
-
303
-
304
- def process_metrics(body, timestamp):
305
- count = 0
306
- for rm in body.get("resourceMetrics", []):
307
- resource = extract_attributes(rm.get("resource", {}).get("attributes", []))
308
- for sm in rm.get("scopeMetrics", []):
309
- for metric in sm.get("metrics", []):
310
- name = metric.get("name", "?")
311
- for dtype in ["sum", "gauge", "histogram"]:
312
- obj = metric.get(dtype)
313
- if obj:
314
- for dp in obj.get("dataPoints", []):
315
- count += 1
316
- value = (
317
- dp.get("asInt")
318
- or dp.get("asDouble")
319
- or dp.get("value", "?")
320
- )
321
- attrs = extract_attributes(dp.get("attributes", []))
322
- attrs_str = " ".join(f"{k}={v}" for k, v in attrs.items())
323
- print(f" 📊 {name} = {value} [{attrs_str}]", flush=True)
324
- write_log(
325
- METRICS_LOG,
326
- {
327
- "timestamp": timestamp,
328
- "metric_name": name,
329
- "value": value,
330
- "attributes": attrs,
331
- "resource": resource,
332
- },
333
- )
334
- print(f" → {count} metric points", flush=True)
335
-
336
-
337
- def process_logs(body, timestamp):
338
- count = 0
339
- for rl in body.get("resourceLogs", []):
340
- resource = extract_attributes(rl.get("resource", {}).get("attributes", []))
341
- for sl in rl.get("scopeLogs", []):
342
- for lr in sl.get("logRecords", []):
343
- count += 1
344
- log_body = lr.get("body", {})
345
- body_str = (
346
- log_body.get("stringValue", "")
347
- if isinstance(log_body, dict)
348
- else str(log_body)
349
- )
350
- try:
351
- body_parsed = json.loads(body_str) if body_str else None
352
- except:
353
- body_parsed = body_str
354
-
355
- attrs = extract_attributes(lr.get("attributes", []))
356
- event_name = attrs.get("event.name", "unknown")
357
-
358
- write_log(
359
- LOGS_LOG,
360
- {
361
- "timestamp": timestamp,
362
- "event_name": event_name,
363
- "severity": lr.get("severityText", ""),
364
- "attributes": attrs,
365
- "body": body_parsed,
366
- "resource": resource,
367
- },
368
- )
369
-
370
- icons = {
371
- "user_prompt": "💬",
372
- "tool_result": "🔧",
373
- "api_request": "🌐",
374
- "api_error": "❌",
375
- "tool_decision": "⚖️",
376
- }
377
- icon = icons.get(event_name, "📝")
378
-
379
- if event_name == "api_request":
380
- print(
381
- f" {icon} [{event_name}] model={attrs.get('model','?')} "
382
- f"cost=${attrs.get('cost_usd','?')} dur={attrs.get('duration_ms','?')}ms "
383
- f"in={attrs.get('input_tokens','?')} out={attrs.get('output_tokens','?')}",
384
- flush=True,
385
- )
386
- elif event_name == "tool_result":
387
- print(
388
- f" {icon} [{event_name}] tool={attrs.get('tool_name','?')} "
389
- f"success={attrs.get('success','?')} dur={attrs.get('duration_ms','?')}ms",
390
- flush=True,
391
- )
392
- elif event_name == "user_prompt":
393
- prompt = str(attrs.get("prompt", "(redacted)"))
394
- print(
395
- f" {icon} [{event_name}] len={attrs.get('prompt_length','?')} "
396
- f'"{prompt[:100]}"',
397
- flush=True,
398
- )
399
- elif event_name == "tool_decision":
400
- print(
401
- f" {icon} [{event_name}] tool={attrs.get('tool_name','?')} "
402
- f"decision={attrs.get('decision','?')}",
403
- flush=True,
404
- )
405
- else:
406
- preview = " ".join(f"{k}={v}" for k, v in list(attrs.items())[:5])
407
- print(f" {icon} [{event_name}] {preview}", flush=True)
408
- print(f" → {count} log records", flush=True)
409
-
410
-
411
- def print_summary():
412
- print(f"\n{'='*60}")
413
- print(f"📊 OTEL 数据采集摘要")
414
- print(f"{'='*60}")
415
-
416
- # Raw
417
- print(f"\n--- 请求概览 ---")
418
- if os.path.exists(RAW_LOG):
419
- raws = [json.loads(l) for l in open(RAW_LOG) if l.strip()]
420
- print(f" 总请求数: {len(raws)}")
421
- for r in raws:
422
- print(
423
- f" [{r['req_num']}] {r['path']} | body={r['body_size']}B decoded={r['decoded_size']}B parsed={r['parsed']}"
424
- )
425
- else:
426
- print(" (无)")
427
-
428
- # Metrics
429
- print(f"\n--- Metrics ---")
430
- if os.path.exists(METRICS_LOG):
431
- records = [json.loads(l) for l in open(METRICS_LOG) if l.strip()]
432
- print(f" 数据点: {len(records)}")
433
- names = {}
434
- for r in records:
435
- n = r["metric_name"]
436
- names[n] = names.get(n, 0) + 1
437
- for n, c in sorted(names.items()):
438
- print(f" {n}: {c}个点")
439
- if records and records[0].get("resource"):
440
- print(f"\n Resource:")
441
- for k, v in records[0]["resource"].items():
442
- print(f" {k}: {v}")
443
- else:
444
- print(" (无)")
445
-
446
- # Logs
447
- print(f"\n--- Events ---")
448
- if os.path.exists(LOGS_LOG):
449
- records = [json.loads(l) for l in open(LOGS_LOG) if l.strip()]
450
- print(f" 事件数: {len(records)}")
451
-
452
- types = {}
453
- models = set()
454
- tools = {}
455
- cost = 0
456
- tok_in = 0
457
- tok_out = 0
458
-
459
- for r in records:
460
- en = r["event_name"]
461
- a = r.get("attributes", {})
462
- types[en] = types.get(en, 0) + 1
463
- if a.get("model"):
464
- models.add(a["model"])
465
- if a.get("tool_name"):
466
- tools[a["tool_name"]] = tools.get(a["tool_name"], 0) + 1
467
- if en == "api_request":
468
- try:
469
- cost += float(a.get("cost_usd", 0))
470
- tok_in += int(a.get("input_tokens", 0))
471
- tok_out += int(a.get("output_tokens", 0))
472
- except:
473
- pass
474
-
475
- print(f"\n 事件类型:")
476
- for en, c in sorted(types.items(), key=lambda x: -x[1]):
477
- print(f" {en}: {c}次")
478
-
479
- print(f"\n 模型: {sorted(models) if models else '(无)'}")
480
- if tools:
481
- print(f"\n 工具:")
482
- for t, c in sorted(tools.items(), key=lambda x: -x[1])[:20]:
483
- print(f" {t}: {c}次")
484
- print(f"\n Tokens: in={tok_in:,} out={tok_out:,} cost=${cost:.6f}")
485
-
486
- # API 请求详情
487
- apis = [r for r in records if r["event_name"] == "api_request"]
488
- if apis:
489
- print(f"\n API 请求 ({len(apis)}次):")
490
- for i, r in enumerate(apis):
491
- a = r["attributes"]
492
- print(
493
- f" [{i+1}] model={a.get('model')} cost=${a.get('cost_usd','?')} "
494
- f"dur={a.get('duration_ms','?')}ms in={a.get('input_tokens','?')} "
495
- f"out={a.get('output_tokens','?')} cache_read={a.get('cache_read_tokens','?')}"
496
- )
497
-
498
- prompts = [r for r in records if r["event_name"] == "user_prompt"]
499
- if prompts:
500
- print(f"\n Prompts ({len(prompts)}条):")
501
- for i, r in enumerate(prompts):
502
- a = r["attributes"]
503
- print(
504
- f" [{i+1}] len={a.get('prompt_length','?')}: \"{str(a.get('prompt',''))[:200]}\""
505
- )
506
-
507
- # 第一条完整记录
508
- if records:
509
- print(f"\n === 第一条完整事件 ===")
510
- print(json.dumps(records[0], indent=2, ensure_ascii=False)[:2000])
511
-
512
- if records and records[0].get("resource"):
513
- print(f"\n Resource:")
514
- for k, v in records[0]["resource"].items():
515
- print(f" {k}: {v}")
516
- else:
517
- print(" (无)")
518
- print(f"\n{'='*60}")
519
-
520
-
521
- def main():
522
- os.makedirs(LOG_DIR, exist_ok=True)
523
- for f in [METRICS_LOG, LOGS_LOG, RAW_LOG]:
524
- if os.path.exists(f):
525
- try:
526
- os.rename(f, f + ".bak")
527
- except:
528
- pass
529
-
530
- print("=" * 60, flush=True)
531
- print("📡 OTEL Receiver v3 - 支持 Chunked Transfer Encoding", flush=True)
532
- print("=" * 60, flush=True)
533
- print(f"\n 监听: 0.0.0.0:{LISTEN_PORT}", flush=True)
534
- print(f" 日志: {LOG_DIR}", flush=True)
535
- print(f"\n 启动 Claude Code:", flush=True)
536
- print(f" export CLAUDE_CODE_ENABLE_TELEMETRY=1", flush=True)
537
- print(f" export OTEL_METRICS_EXPORTER=otlp", flush=True)
538
- print(f" export OTEL_LOGS_EXPORTER=otlp", flush=True)
539
- print(f" export OTEL_EXPORTER_OTLP_PROTOCOL=http/json", flush=True)
540
- print(
541
- f" export OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:{LISTEN_PORT}",
542
- flush=True,
543
- )
544
- print(f" export OTEL_LOG_USER_PROMPTS=1", flush=True)
545
- print(f" export OTEL_LOG_TOOL_DETAILS=1", flush=True)
546
- print(f" export OTEL_METRIC_EXPORT_INTERVAL=10000", flush=True)
547
- print(f" export OTEL_LOGS_EXPORT_INTERVAL=5000", flush=True)
548
- print(f" claude", flush=True)
549
- print("=" * 60, flush=True)
550
-
551
- # 创建 socket 服务器
552
- server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
553
- server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
554
- server.bind(("0.0.0.0", LISTEN_PORT))
555
- server.listen(10)
556
- server.settimeout(1.0)
557
-
558
- print(f"\n✅ 已启动,等待数据...\n", flush=True)
559
-
560
- try:
561
- while True:
562
- try:
563
- conn, addr = server.accept()
564
- t = threading.Thread(
565
- target=handle_connection, args=(conn, addr), daemon=True
566
- )
567
- t.start()
568
- except socket.timeout:
569
- continue
570
- except KeyboardInterrupt:
571
- print("\n🛑 已停止", flush=True)
572
- print_summary()
573
- server.close()
574
-
575
-
576
- if __name__ == "__main__":
577
- if len(sys.argv) > 1 and sys.argv[1] == "--summary":
578
- print_summary()
579
- else:
580
- main()