aient 1.1.51__tar.gz → 1.1.52__tar.gz

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 (79) hide show
  1. {aient-1.1.51/src/aient.egg-info → aient-1.1.52}/PKG-INFO +1 -1
  2. {aient-1.1.51 → aient-1.1.52}/setup.py +1 -1
  3. {aient-1.1.51 → aient-1.1.52}/src/aient/core/request.py +109 -50
  4. {aient-1.1.51 → aient-1.1.52}/src/aient/core/response.py +4 -0
  5. {aient-1.1.51 → aient-1.1.52}/src/aient/core/utils.py +25 -4
  6. {aient-1.1.51 → aient-1.1.52/src/aient.egg-info}/PKG-INFO +1 -1
  7. {aient-1.1.51 → aient-1.1.52}/LICENSE +0 -0
  8. {aient-1.1.51 → aient-1.1.52}/MANIFEST.in +0 -0
  9. {aient-1.1.51 → aient-1.1.52}/README.md +0 -0
  10. {aient-1.1.51 → aient-1.1.52}/setup.cfg +0 -0
  11. {aient-1.1.51 → aient-1.1.52}/src/aient/__init__.py +0 -0
  12. {aient-1.1.51 → aient-1.1.52}/src/aient/core/.git +0 -0
  13. {aient-1.1.51 → aient-1.1.52}/src/aient/core/.gitignore +0 -0
  14. {aient-1.1.51 → aient-1.1.52}/src/aient/core/__init__.py +0 -0
  15. {aient-1.1.51 → aient-1.1.52}/src/aient/core/log_config.py +0 -0
  16. {aient-1.1.51 → aient-1.1.52}/src/aient/core/models.py +0 -0
  17. {aient-1.1.51 → aient-1.1.52}/src/aient/core/test/test_base_api.py +0 -0
  18. {aient-1.1.51 → aient-1.1.52}/src/aient/core/test/test_geminimask.py +0 -0
  19. {aient-1.1.51 → aient-1.1.52}/src/aient/core/test/test_image.py +0 -0
  20. {aient-1.1.51 → aient-1.1.52}/src/aient/core/test/test_payload.py +0 -0
  21. {aient-1.1.51 → aient-1.1.52}/src/aient/models/__init__.py +0 -0
  22. {aient-1.1.51 → aient-1.1.52}/src/aient/models/audio.py +0 -0
  23. {aient-1.1.51 → aient-1.1.52}/src/aient/models/base.py +0 -0
  24. {aient-1.1.51 → aient-1.1.52}/src/aient/models/chatgpt.py +0 -0
  25. {aient-1.1.51 → aient-1.1.52}/src/aient/models/claude.py +0 -0
  26. {aient-1.1.51 → aient-1.1.52}/src/aient/models/duckduckgo.py +0 -0
  27. {aient-1.1.51 → aient-1.1.52}/src/aient/models/gemini.py +0 -0
  28. {aient-1.1.51 → aient-1.1.52}/src/aient/models/groq.py +0 -0
  29. {aient-1.1.51 → aient-1.1.52}/src/aient/models/vertex.py +0 -0
  30. {aient-1.1.51 → aient-1.1.52}/src/aient/plugins/__init__.py +0 -0
  31. {aient-1.1.51 → aient-1.1.52}/src/aient/plugins/arXiv.py +0 -0
  32. {aient-1.1.51 → aient-1.1.52}/src/aient/plugins/config.py +0 -0
  33. {aient-1.1.51 → aient-1.1.52}/src/aient/plugins/excute_command.py +0 -0
  34. {aient-1.1.51 → aient-1.1.52}/src/aient/plugins/get_time.py +0 -0
  35. {aient-1.1.51 → aient-1.1.52}/src/aient/plugins/image.py +0 -0
  36. {aient-1.1.51 → aient-1.1.52}/src/aient/plugins/list_directory.py +0 -0
  37. {aient-1.1.51 → aient-1.1.52}/src/aient/plugins/read_file.py +0 -0
  38. {aient-1.1.51 → aient-1.1.52}/src/aient/plugins/read_image.py +0 -0
  39. {aient-1.1.51 → aient-1.1.52}/src/aient/plugins/readonly.py +0 -0
  40. {aient-1.1.51 → aient-1.1.52}/src/aient/plugins/registry.py +0 -0
  41. {aient-1.1.51 → aient-1.1.52}/src/aient/plugins/run_python.py +0 -0
  42. {aient-1.1.51 → aient-1.1.52}/src/aient/plugins/websearch.py +0 -0
  43. {aient-1.1.51 → aient-1.1.52}/src/aient/plugins/write_file.py +0 -0
  44. {aient-1.1.51 → aient-1.1.52}/src/aient/utils/__init__.py +0 -0
  45. {aient-1.1.51 → aient-1.1.52}/src/aient/utils/prompt.py +0 -0
  46. {aient-1.1.51 → aient-1.1.52}/src/aient/utils/scripts.py +0 -0
  47. {aient-1.1.51 → aient-1.1.52}/src/aient.egg-info/SOURCES.txt +0 -0
  48. {aient-1.1.51 → aient-1.1.52}/src/aient.egg-info/dependency_links.txt +0 -0
  49. {aient-1.1.51 → aient-1.1.52}/src/aient.egg-info/requires.txt +0 -0
  50. {aient-1.1.51 → aient-1.1.52}/src/aient.egg-info/top_level.txt +0 -0
  51. {aient-1.1.51 → aient-1.1.52}/test/test.py +0 -0
  52. {aient-1.1.51 → aient-1.1.52}/test/test_API.py +0 -0
  53. {aient-1.1.51 → aient-1.1.52}/test/test_Deepbricks.py +0 -0
  54. {aient-1.1.51 → aient-1.1.52}/test/test_Web_crawler.py +0 -0
  55. {aient-1.1.51 → aient-1.1.52}/test/test_aiwaves.py +0 -0
  56. {aient-1.1.51 → aient-1.1.52}/test/test_aiwaves_arxiv.py +0 -0
  57. {aient-1.1.51 → aient-1.1.52}/test/test_ask_gemini.py +0 -0
  58. {aient-1.1.51 → aient-1.1.52}/test/test_class.py +0 -0
  59. {aient-1.1.51 → aient-1.1.52}/test/test_claude.py +0 -0
  60. {aient-1.1.51 → aient-1.1.52}/test/test_claude_zh_char.py +0 -0
  61. {aient-1.1.51 → aient-1.1.52}/test/test_ddg_search.py +0 -0
  62. {aient-1.1.51 → aient-1.1.52}/test/test_download_pdf.py +0 -0
  63. {aient-1.1.51 → aient-1.1.52}/test/test_gemini.py +0 -0
  64. {aient-1.1.51 → aient-1.1.52}/test/test_get_token_dict.py +0 -0
  65. {aient-1.1.51 → aient-1.1.52}/test/test_google_search.py +0 -0
  66. {aient-1.1.51 → aient-1.1.52}/test/test_jieba.py +0 -0
  67. {aient-1.1.51 → aient-1.1.52}/test/test_json.py +0 -0
  68. {aient-1.1.51 → aient-1.1.52}/test/test_logging.py +0 -0
  69. {aient-1.1.51 → aient-1.1.52}/test/test_ollama.py +0 -0
  70. {aient-1.1.51 → aient-1.1.52}/test/test_plugin.py +0 -0
  71. {aient-1.1.51 → aient-1.1.52}/test/test_py_run.py +0 -0
  72. {aient-1.1.51 → aient-1.1.52}/test/test_requests.py +0 -0
  73. {aient-1.1.51 → aient-1.1.52}/test/test_search.py +0 -0
  74. {aient-1.1.51 → aient-1.1.52}/test/test_tikitoken.py +0 -0
  75. {aient-1.1.51 → aient-1.1.52}/test/test_token.py +0 -0
  76. {aient-1.1.51 → aient-1.1.52}/test/test_url.py +0 -0
  77. {aient-1.1.51 → aient-1.1.52}/test/test_whisper.py +0 -0
  78. {aient-1.1.51 → aient-1.1.52}/test/test_wildcard.py +0 -0
  79. {aient-1.1.51 → aient-1.1.52}/test/test_yjh.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aient
3
- Version: 1.1.51
3
+ Version: 1.1.52
4
4
  Summary: Aient: The Awakening of Agent.
5
5
  Description-Content-Type: text/markdown
6
6
  License-File: LICENSE
@@ -4,7 +4,7 @@ from setuptools import setup, find_packages
4
4
 
5
5
  setup(
6
6
  name="aient",
7
- version="1.1.51",
7
+ version="1.1.52",
8
8
  description="Aient: The Awakening of Agent.",
9
9
  long_description=Path.open(Path("README.md"), encoding="utf-8").read(),
10
10
  long_description_content_type="text/markdown",
@@ -3,6 +3,8 @@ import json
3
3
  import httpx
4
4
  import base64
5
5
  import urllib.parse
6
+ from io import IOBase
7
+ from typing import Tuple
6
8
 
7
9
  from .models import RequestModel, Message
8
10
  from .utils import (
@@ -239,7 +241,6 @@ async def get_gemini_payload(request, engine, provider, api_key=None):
239
241
  ]
240
242
 
241
243
  if "gemini-2.5" in original_model:
242
- generation_config = payload.get("generationConfig", {})
243
244
  # 从请求模型名中检测思考预算设置
244
245
  m = re.match(r".*-think-(-?\d+)", request.model)
245
246
  if m:
@@ -254,7 +255,7 @@ async def get_gemini_payload(request, engine, provider, api_key=None):
254
255
  budget = 32768
255
256
  else: # 128 <= val <= 32768
256
257
  budget = val
257
-
258
+
258
259
  # gemini-2.5-flash-lite: [0] or [512, 24576]
259
260
  elif "gemini-2.5-flash-lite" in original_model:
260
261
  if val > 0 and val < 512:
@@ -270,9 +271,9 @@ async def get_gemini_payload(request, engine, provider, api_key=None):
270
271
  budget = 24576
271
272
  else: # Includes 0 and valid range, and clamps invalid negatives
272
273
  budget = val if val >= 0 else 0
273
-
274
+
274
275
  payload["generationConfig"]["thinkingConfig"] = {
275
- "includeThoughts": True,
276
+ "includeThoughts": True if budget else False,
276
277
  "thinkingBudget": budget
277
278
  }
278
279
  except ValueError:
@@ -282,11 +283,6 @@ async def get_gemini_payload(request, engine, provider, api_key=None):
282
283
  payload["generationConfig"]["thinkingConfig"] = {
283
284
  "includeThoughts": True,
284
285
  }
285
- payload["generationConfig"] = generation_config
286
-
287
- # # 检测search标签
288
- # if request.model.endswith("-search"):
289
- # payload["tools"] = [{"googleSearch": {}}]
290
286
 
291
287
  if safe_get(provider, "preferences", "post_body_parameter_overrides", default=None):
292
288
  for key, value in safe_get(provider, "preferences", "post_body_parameter_overrides", default={}).items():
@@ -372,16 +368,12 @@ async def get_vertex_gemini_payload(request, engine, provider, api_key=None):
372
368
  # search_tool = None
373
369
 
374
370
  # https://cloud.google.com/vertex-ai/generative-ai/docs/models/gemini/2-0-flash?hl=zh-cn
375
- pro_models = ["gemini-2.5", "gemini-2.0"]
371
+ pro_models = ["gemini-2.5"]
376
372
  if any(pro_model in original_model for pro_model in pro_models):
377
- location = gemini2
373
+ location = gemini2_5_pro_exp
378
374
  else:
379
375
  location = gemini1
380
376
 
381
- if "gemini-2.5-flash-lite-preview-06-17" == original_model or \
382
- "gemini-2.5-pro-preview-06-05" == original_model:
383
- location = gemini2_5_pro_exp
384
-
385
377
  if "google-vertex-ai" in provider.get("base_url", ""):
386
378
  url = provider.get("base_url").rstrip('/') + "/v1/projects/{PROJECT_ID}/locations/{LOCATION}/publishers/google/models/{MODEL_ID}:{stream}".format(
387
379
  LOCATION=await location.next(),
@@ -390,24 +382,8 @@ async def get_vertex_gemini_payload(request, engine, provider, api_key=None):
390
382
  stream=gemini_stream
391
383
  )
392
384
  elif api_key is not None and api_key[2] == ".":
393
- if provider.get("project_id") and "gemini-2.5-pro-preview-06-05" == original_model:
394
- if isinstance(provider.get("project_id"), list):
395
- api_key_index = provider.get("api").index(api_key)
396
- project_id = provider.get("project_id")[api_key_index]
397
- else:
398
- project_id = provider.get("project_id")
399
- url = f"https://aiplatform.googleapis.com/v1/projects/{project_id}/locations/global/publishers/google/models/{original_model}:{gemini_stream}?key={api_key}"
400
- else:
401
- url = f"https://aiplatform.googleapis.com/v1/publishers/google/models/{original_model}:{gemini_stream}?key={api_key}"
385
+ url = f"https://aiplatform.googleapis.com/v1/publishers/google/models/{original_model}:{gemini_stream}?key={api_key}"
402
386
  headers.pop("Authorization", None)
403
- elif "gemini-2.5-flash-lite-preview-06-17" == original_model or \
404
- "gemini-2.5-pro-preview-06-05" == original_model:
405
- url = "https://aiplatform.googleapis.com/v1/projects/{PROJECT_ID}/locations/{LOCATION}/publishers/google/models/{MODEL_ID}:{stream}".format(
406
- LOCATION=await location.next(),
407
- PROJECT_ID=project_id,
408
- MODEL_ID=original_model,
409
- stream=gemini_stream
410
- )
411
387
  else:
412
388
  url = "https://{LOCATION}-aiplatform.googleapis.com/v1/projects/{PROJECT_ID}/locations/{LOCATION}/publishers/google/models/{MODEL_ID}:{stream}".format(
413
389
  LOCATION=await location.next(),
@@ -573,7 +549,7 @@ async def get_vertex_gemini_payload(request, engine, provider, api_key=None):
573
549
  budget = 32768
574
550
  else: # 128 <= val <= 32768
575
551
  budget = val
576
-
552
+
577
553
  # gemini-2.5-flash-lite: [0] or [512, 24576]
578
554
  elif "gemini-2.5-flash-lite" in original_model:
579
555
  if val > 0 and val < 512:
@@ -589,9 +565,9 @@ async def get_vertex_gemini_payload(request, engine, provider, api_key=None):
589
565
  budget = 24576
590
566
  else: # Includes 0 and valid range, and clamps invalid negatives
591
567
  budget = val if val >= 0 else 0
592
-
568
+
593
569
  payload["generationConfig"]["thinkingConfig"] = {
594
- "includeThoughts": True,
570
+ "includeThoughts": True if budget else False,
595
571
  "thinkingBudget": budget
596
572
  }
597
573
  except ValueError:
@@ -602,9 +578,6 @@ async def get_vertex_gemini_payload(request, engine, provider, api_key=None):
602
578
  "includeThoughts": True,
603
579
  }
604
580
 
605
- # if request.model.endswith("-search"):
606
- # payload["tools"] = [search_tool]
607
-
608
581
  if safe_get(provider, "preferences", "post_body_parameter_overrides", default=None):
609
582
  for key, value in safe_get(provider, "preferences", "post_body_parameter_overrides", default={}).items():
610
583
  if key == request.model:
@@ -1822,21 +1795,98 @@ async def get_dalle_payload(request, engine, provider, api_key=None):
1822
1795
 
1823
1796
  return url, headers, payload
1824
1797
 
1798
+ async def get_upload_certificate(client: httpx.AsyncClient, api_key: str, model: str) -> dict:
1799
+ """第一步:获取文件上传凭证"""
1800
+ # print("步骤 1: 正在获取上传凭证...")
1801
+ headers = {"Authorization": f"Bearer {api_key}"}
1802
+ params = {"action": "getPolicy", "model": model}
1803
+ try:
1804
+ response = await client.get("https://dashscope.aliyuncs.com/api/v1/uploads", headers=headers, params=params)
1805
+ response.raise_for_status() # 如果请求失败则抛出异常
1806
+ cert_data = response.json()
1807
+ # print("凭证获取成功。")
1808
+ return cert_data.get("data")
1809
+ except httpx.HTTPStatusError as e:
1810
+ print(f"获取凭证失败: HTTP {e.response.status_code}")
1811
+ print(f"响应内容: {e.response.text}")
1812
+ return None
1813
+ except Exception as e:
1814
+ print(f"获取凭证时发生未知错误: {e}")
1815
+ return None
1816
+
1817
+ from mimetypes import guess_type
1818
+
1819
+ async def upload_file_to_oss(client: httpx.AsyncClient, certificate: dict, file: Tuple[str, IOBase, str]) -> str:
1820
+ """第二步:使用凭证将文件内容上传到OSS"""
1821
+ upload_host = certificate.get("upload_host")
1822
+ upload_dir = certificate.get("upload_dir")
1823
+ object_key = f"{upload_dir}/{file[0]}"
1824
+
1825
+ form_data = {
1826
+ "key": object_key,
1827
+ "policy": certificate.get("policy"),
1828
+ "OSSAccessKeyId": certificate.get("oss_access_key_id"),
1829
+ "signature": certificate.get("signature"),
1830
+ "success_action_status": "200",
1831
+ "x-oss-object-acl": certificate.get("x_oss_object_acl"),
1832
+ "x-oss-forbid-overwrite": certificate.get("x_oss_forbid_overwrite"),
1833
+ }
1834
+
1835
+ files = {"file": file}
1836
+
1837
+ try:
1838
+ response = await client.post(upload_host, data=form_data, files=files, timeout=3600)
1839
+ response.raise_for_status()
1840
+ # print("文件上传成功!")
1841
+ oss_url = f"oss://{object_key}"
1842
+ # print(f"文件OSS URL: {oss_url}")
1843
+ return oss_url
1844
+ except httpx.HTTPStatusError as e:
1845
+ print(f"上传文件失败: HTTP {e.response.status_code}")
1846
+ print(f"响应内容: {e.response.text}")
1847
+ return None
1848
+ except Exception as e:
1849
+ print(f"上传文件时发生未知错误: {e}")
1850
+ return None
1851
+
1825
1852
  async def get_whisper_payload(request, engine, provider, api_key=None):
1826
1853
  model_dict = get_model_dict(provider)
1827
1854
  original_model = model_dict[request.model]
1828
- headers = {
1829
- # "Content-Type": "multipart/form-data",
1830
- }
1855
+ headers = {}
1831
1856
  if api_key:
1832
1857
  headers['Authorization'] = f"Bearer {api_key}"
1833
1858
  url = provider['base_url']
1834
1859
  url = BaseAPI(url).audio_transcriptions
1835
1860
 
1836
- payload = {
1837
- "model": original_model,
1838
- "file": request.file,
1839
- }
1861
+ if "dashscope.aliyuncs.com" in url:
1862
+ client = httpx.AsyncClient()
1863
+ certificate = await get_upload_certificate(client, api_key, original_model)
1864
+ if not certificate:
1865
+ return
1866
+
1867
+ # 步骤 2: 上传文件
1868
+ oss_url = await upload_file_to_oss(client, certificate, request.file)
1869
+ headers = {
1870
+ "Authorization": f"Bearer {api_key}",
1871
+ "Content-Type": "application/json",
1872
+ "X-DashScope-OssResourceResolve": "enable"
1873
+ }
1874
+ payload = {
1875
+ "model": original_model,
1876
+ "input": {
1877
+ "messages": [
1878
+ {
1879
+ "role": "user",
1880
+ "content": [{"audio": oss_url}]
1881
+ }
1882
+ ]
1883
+ }
1884
+ }
1885
+ else:
1886
+ payload = {
1887
+ "model": original_model,
1888
+ "file": request.file,
1889
+ }
1840
1890
 
1841
1891
  if request.prompt:
1842
1892
  payload["prompt"] = request.prompt
@@ -1906,11 +1956,20 @@ async def get_tts_payload(request, engine, provider, api_key=None):
1906
1956
  url = provider['base_url']
1907
1957
  url = BaseAPI(url).audio_speech
1908
1958
 
1909
- payload = {
1910
- "model": original_model,
1911
- "input": request.input,
1912
- "voice": request.voice,
1913
- }
1959
+ if "api.minimaxi.com" in url:
1960
+ payload = {
1961
+ "model": original_model,
1962
+ "text": request.input,
1963
+ "voice_setting": {
1964
+ "voice_id": request.voice
1965
+ }
1966
+ }
1967
+ else:
1968
+ payload = {
1969
+ "model": original_model,
1970
+ "input": request.input,
1971
+ "voice": request.voice,
1972
+ }
1914
1973
 
1915
1974
  if request.response_format:
1916
1975
  payload["response_format"] = request.response_format
@@ -666,6 +666,10 @@ async def fetch_response(client, url, headers, payload, engine, model):
666
666
 
667
667
  yield response_json
668
668
 
669
+ elif "dashscope.aliyuncs.com" in url and "multimodal-generation" in url:
670
+ response_json = response.json()
671
+ content = safe_get(response_json, "output", "choices", 0, "message", "content", 0, default=None)
672
+ yield content
669
673
  else:
670
674
  response_json = response.json()
671
675
  yield response_json
@@ -49,10 +49,16 @@ class BaseAPI:
49
49
  self.v1_models: str = urlunparse(parsed_url[:2] + (before_v1 + "models",) + ("",) * 3)
50
50
  self.chat_url: str = urlunparse(parsed_url[:2] + (before_v1 + "chat/completions",) + ("",) * 3)
51
51
  self.image_url: str = urlunparse(parsed_url[:2] + (before_v1 + "images/generations",) + ("",) * 3)
52
- self.audio_transcriptions: str = urlunparse(parsed_url[:2] + (before_v1 + "audio/transcriptions",) + ("",) * 3)
52
+ if parsed_url.hostname == "dashscope.aliyuncs.com":
53
+ self.audio_transcriptions: str = urlunparse(parsed_url[:2] + ("/api/v1/services/aigc/multimodal-generation/generation",) + ("",) * 3)
54
+ else:
55
+ self.audio_transcriptions: str = urlunparse(parsed_url[:2] + (before_v1 + "audio/transcriptions",) + ("",) * 3)
53
56
  self.moderations: str = urlunparse(parsed_url[:2] + (before_v1 + "moderations",) + ("",) * 3)
54
57
  self.embeddings: str = urlunparse(parsed_url[:2] + (before_v1 + "embeddings",) + ("",) * 3)
55
- self.audio_speech: str = urlunparse(parsed_url[:2] + (before_v1 + "audio/speech",) + ("",) * 3)
58
+ if parsed_url.hostname == "api.minimaxi.com":
59
+ self.audio_speech: str = urlunparse(parsed_url[:2] + ("v1/t2a_v2",) + ("",) * 3)
60
+ else:
61
+ self.audio_speech: str = urlunparse(parsed_url[:2] + (before_v1 + "audio/speech",) + ("",) * 3)
56
62
 
57
63
  if parsed_url.hostname == "generativelanguage.googleapis.com":
58
64
  self.base_url = api_url
@@ -440,8 +446,23 @@ c4 = ThreadSafeCircularList(["us-east5", "us-central1", "europe-west4", "asia-so
440
446
  c3h = ThreadSafeCircularList(["us-east5", "us-central1", "europe-west1", "europe-west4"])
441
447
  gemini1 = ThreadSafeCircularList(["us-central1", "us-east4", "us-west1", "us-west4", "europe-west1", "europe-west2"])
442
448
  gemini2 = ThreadSafeCircularList(["us-central1"])
443
- gemini2_5_pro_exp = ThreadSafeCircularList(["global"])
444
-
449
+ # gemini2_5_pro_exp = ThreadSafeCircularList(["global"])
450
+ gemini2_5_pro_exp = ThreadSafeCircularList([
451
+ "us-central1",
452
+ "us-east1",
453
+ "us-east4",
454
+ "us-east5",
455
+ "us-south1",
456
+ "us-west1",
457
+ "us-west4",
458
+ "europe-central2",
459
+ "europe-north1",
460
+ "europe-southwest1",
461
+ "europe-west1",
462
+ "europe-west4",
463
+ "europe-west8",
464
+ "europe-west9"
465
+ ])
445
466
 
446
467
 
447
468
  # end_of_line = "\n\r\n"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aient
3
- Version: 1.1.51
3
+ Version: 1.1.52
4
4
  Summary: Aient: The Awakening of Agent.
5
5
  Description-Content-Type: text/markdown
6
6
  License-File: LICENSE
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes