coze-coding-dev-sdk 0.5.11__tar.gz → 0.5.13__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 (83) hide show
  1. {coze_coding_dev_sdk-0.5.11/coze_coding_dev_sdk.egg-info → coze_coding_dev_sdk-0.5.13}/PKG-INFO +1 -1
  2. {coze_coding_dev_sdk-0.5.11 → coze_coding_dev_sdk-0.5.13}/coze_coding_dev_sdk/cli/cli.py +2 -0
  3. coze_coding_dev_sdk-0.5.13/coze_coding_dev_sdk/cli/db.py +156 -0
  4. coze_coding_dev_sdk-0.5.13/coze_coding_dev_sdk/cli/storage.py +241 -0
  5. {coze_coding_dev_sdk-0.5.11 → coze_coding_dev_sdk-0.5.13}/coze_coding_dev_sdk/llm/client.py +0 -10
  6. {coze_coding_dev_sdk-0.5.11 → coze_coding_dev_sdk-0.5.13}/coze_coding_dev_sdk/supabase/__init__.py +20 -0
  7. {coze_coding_dev_sdk-0.5.11 → coze_coding_dev_sdk-0.5.13}/coze_coding_dev_sdk/supabase/client.py +78 -0
  8. {coze_coding_dev_sdk-0.5.11 → coze_coding_dev_sdk-0.5.13}/coze_coding_dev_sdk/supabase/models.py +80 -0
  9. {coze_coding_dev_sdk-0.5.11 → coze_coding_dev_sdk-0.5.13/coze_coding_dev_sdk.egg-info}/PKG-INFO +1 -1
  10. {coze_coding_dev_sdk-0.5.11 → coze_coding_dev_sdk-0.5.13}/coze_coding_dev_sdk.egg-info/SOURCES.txt +1 -0
  11. {coze_coding_dev_sdk-0.5.11 → coze_coding_dev_sdk-0.5.13}/pyproject.toml +1 -1
  12. coze_coding_dev_sdk-0.5.11/coze_coding_dev_sdk/cli/db.py +0 -81
  13. {coze_coding_dev_sdk-0.5.11 → coze_coding_dev_sdk-0.5.13}/CHANGELOG.md +0 -0
  14. {coze_coding_dev_sdk-0.5.11 → coze_coding_dev_sdk-0.5.13}/LICENSE +0 -0
  15. {coze_coding_dev_sdk-0.5.11 → coze_coding_dev_sdk-0.5.13}/MANIFEST.in +0 -0
  16. {coze_coding_dev_sdk-0.5.11 → coze_coding_dev_sdk-0.5.13}/README.md +0 -0
  17. {coze_coding_dev_sdk-0.5.11 → coze_coding_dev_sdk-0.5.13}/coze_coding_dev_sdk/__init__.py +0 -0
  18. {coze_coding_dev_sdk-0.5.11 → coze_coding_dev_sdk-0.5.13}/coze_coding_dev_sdk/cli/__init__.py +0 -0
  19. {coze_coding_dev_sdk-0.5.11 → coze_coding_dev_sdk-0.5.13}/coze_coding_dev_sdk/cli/chat.py +0 -0
  20. {coze_coding_dev_sdk-0.5.11 → coze_coding_dev_sdk-0.5.13}/coze_coding_dev_sdk/cli/constants.py +0 -0
  21. {coze_coding_dev_sdk-0.5.11 → coze_coding_dev_sdk-0.5.13}/coze_coding_dev_sdk/cli/document.py +0 -0
  22. {coze_coding_dev_sdk-0.5.11 → coze_coding_dev_sdk-0.5.13}/coze_coding_dev_sdk/cli/embedding.py +0 -0
  23. {coze_coding_dev_sdk-0.5.11 → coze_coding_dev_sdk-0.5.13}/coze_coding_dev_sdk/cli/image.py +0 -0
  24. {coze_coding_dev_sdk-0.5.11 → coze_coding_dev_sdk-0.5.13}/coze_coding_dev_sdk/cli/knowledge.py +0 -0
  25. {coze_coding_dev_sdk-0.5.11 → coze_coding_dev_sdk-0.5.13}/coze_coding_dev_sdk/cli/search.py +0 -0
  26. {coze_coding_dev_sdk-0.5.11 → coze_coding_dev_sdk-0.5.13}/coze_coding_dev_sdk/cli/supabase.py +0 -0
  27. {coze_coding_dev_sdk-0.5.11 → coze_coding_dev_sdk-0.5.13}/coze_coding_dev_sdk/cli/utils.py +0 -0
  28. {coze_coding_dev_sdk-0.5.11 → coze_coding_dev_sdk-0.5.13}/coze_coding_dev_sdk/cli/video.py +0 -0
  29. {coze_coding_dev_sdk-0.5.11 → coze_coding_dev_sdk-0.5.13}/coze_coding_dev_sdk/cli/video_edit.py +0 -0
  30. {coze_coding_dev_sdk-0.5.11 → coze_coding_dev_sdk-0.5.13}/coze_coding_dev_sdk/cli/voice.py +0 -0
  31. {coze_coding_dev_sdk-0.5.11 → coze_coding_dev_sdk-0.5.13}/coze_coding_dev_sdk/core/__init__.py +0 -0
  32. {coze_coding_dev_sdk-0.5.11 → coze_coding_dev_sdk-0.5.13}/coze_coding_dev_sdk/core/client.py +0 -0
  33. {coze_coding_dev_sdk-0.5.11 → coze_coding_dev_sdk-0.5.13}/coze_coding_dev_sdk/core/config.py +0 -0
  34. {coze_coding_dev_sdk-0.5.11 → coze_coding_dev_sdk-0.5.13}/coze_coding_dev_sdk/core/exceptions.py +0 -0
  35. {coze_coding_dev_sdk-0.5.11 → coze_coding_dev_sdk-0.5.13}/coze_coding_dev_sdk/database/__init__.py +0 -0
  36. {coze_coding_dev_sdk-0.5.11 → coze_coding_dev_sdk-0.5.13}/coze_coding_dev_sdk/database/client.py +0 -0
  37. {coze_coding_dev_sdk-0.5.11 → coze_coding_dev_sdk-0.5.13}/coze_coding_dev_sdk/database/migration.py +0 -0
  38. {coze_coding_dev_sdk-0.5.11 → coze_coding_dev_sdk-0.5.13}/coze_coding_dev_sdk/document/__init__.py +0 -0
  39. {coze_coding_dev_sdk-0.5.11 → coze_coding_dev_sdk-0.5.13}/coze_coding_dev_sdk/document/client.py +0 -0
  40. {coze_coding_dev_sdk-0.5.11 → coze_coding_dev_sdk-0.5.13}/coze_coding_dev_sdk/document/docx_generator.py +0 -0
  41. {coze_coding_dev_sdk-0.5.11 → coze_coding_dev_sdk-0.5.13}/coze_coding_dev_sdk/document/models.py +0 -0
  42. {coze_coding_dev_sdk-0.5.11 → coze_coding_dev_sdk-0.5.13}/coze_coding_dev_sdk/document/pdf_generator.py +0 -0
  43. {coze_coding_dev_sdk-0.5.11 → coze_coding_dev_sdk-0.5.13}/coze_coding_dev_sdk/document/pptx_generator.py +0 -0
  44. {coze_coding_dev_sdk-0.5.11 → coze_coding_dev_sdk-0.5.13}/coze_coding_dev_sdk/document/xlsx_generator.py +0 -0
  45. {coze_coding_dev_sdk-0.5.11 → coze_coding_dev_sdk-0.5.13}/coze_coding_dev_sdk/embedding/__init__.py +0 -0
  46. {coze_coding_dev_sdk-0.5.11 → coze_coding_dev_sdk-0.5.13}/coze_coding_dev_sdk/embedding/client.py +0 -0
  47. {coze_coding_dev_sdk-0.5.11 → coze_coding_dev_sdk-0.5.13}/coze_coding_dev_sdk/embedding/models.py +0 -0
  48. {coze_coding_dev_sdk-0.5.11 → coze_coding_dev_sdk-0.5.13}/coze_coding_dev_sdk/fetch/__init__.py +0 -0
  49. {coze_coding_dev_sdk-0.5.11 → coze_coding_dev_sdk-0.5.13}/coze_coding_dev_sdk/fetch/client.py +0 -0
  50. {coze_coding_dev_sdk-0.5.11 → coze_coding_dev_sdk-0.5.13}/coze_coding_dev_sdk/fetch/models.py +0 -0
  51. {coze_coding_dev_sdk-0.5.11 → coze_coding_dev_sdk-0.5.13}/coze_coding_dev_sdk/image/__init__.py +0 -0
  52. {coze_coding_dev_sdk-0.5.11 → coze_coding_dev_sdk-0.5.13}/coze_coding_dev_sdk/image/client.py +0 -0
  53. {coze_coding_dev_sdk-0.5.11 → coze_coding_dev_sdk-0.5.13}/coze_coding_dev_sdk/image/models.py +0 -0
  54. {coze_coding_dev_sdk-0.5.11 → coze_coding_dev_sdk-0.5.13}/coze_coding_dev_sdk/knowledge/__init__.py +0 -0
  55. {coze_coding_dev_sdk-0.5.11 → coze_coding_dev_sdk-0.5.13}/coze_coding_dev_sdk/knowledge/client.py +0 -0
  56. {coze_coding_dev_sdk-0.5.11 → coze_coding_dev_sdk-0.5.13}/coze_coding_dev_sdk/knowledge/models.py +0 -0
  57. {coze_coding_dev_sdk-0.5.11 → coze_coding_dev_sdk-0.5.13}/coze_coding_dev_sdk/llm/__init__.py +0 -0
  58. {coze_coding_dev_sdk-0.5.11 → coze_coding_dev_sdk-0.5.13}/coze_coding_dev_sdk/llm/models.py +0 -0
  59. {coze_coding_dev_sdk-0.5.11 → coze_coding_dev_sdk-0.5.13}/coze_coding_dev_sdk/memory/__init__.py +0 -0
  60. {coze_coding_dev_sdk-0.5.11 → coze_coding_dev_sdk-0.5.13}/coze_coding_dev_sdk/memory/client.py +0 -0
  61. {coze_coding_dev_sdk-0.5.11 → coze_coding_dev_sdk-0.5.13}/coze_coding_dev_sdk/s3/__init__.py +0 -0
  62. {coze_coding_dev_sdk-0.5.11 → coze_coding_dev_sdk-0.5.13}/coze_coding_dev_sdk/s3/client.py +0 -0
  63. {coze_coding_dev_sdk-0.5.11 → coze_coding_dev_sdk-0.5.13}/coze_coding_dev_sdk/s3/models.py +0 -0
  64. {coze_coding_dev_sdk-0.5.11 → coze_coding_dev_sdk-0.5.13}/coze_coding_dev_sdk/search/__init__.py +0 -0
  65. {coze_coding_dev_sdk-0.5.11 → coze_coding_dev_sdk-0.5.13}/coze_coding_dev_sdk/search/client.py +0 -0
  66. {coze_coding_dev_sdk-0.5.11 → coze_coding_dev_sdk-0.5.13}/coze_coding_dev_sdk/search/models.py +0 -0
  67. {coze_coding_dev_sdk-0.5.11 → coze_coding_dev_sdk-0.5.13}/coze_coding_dev_sdk/video/__init__.py +0 -0
  68. {coze_coding_dev_sdk-0.5.11 → coze_coding_dev_sdk-0.5.13}/coze_coding_dev_sdk/video/client.py +0 -0
  69. {coze_coding_dev_sdk-0.5.11 → coze_coding_dev_sdk-0.5.13}/coze_coding_dev_sdk/video/models.py +0 -0
  70. {coze_coding_dev_sdk-0.5.11 → coze_coding_dev_sdk-0.5.13}/coze_coding_dev_sdk/video_edit/__init__.py +0 -0
  71. {coze_coding_dev_sdk-0.5.11 → coze_coding_dev_sdk-0.5.13}/coze_coding_dev_sdk/video_edit/examples.py +0 -0
  72. {coze_coding_dev_sdk-0.5.11 → coze_coding_dev_sdk-0.5.13}/coze_coding_dev_sdk/video_edit/frame_extractor.py +0 -0
  73. {coze_coding_dev_sdk-0.5.11 → coze_coding_dev_sdk-0.5.13}/coze_coding_dev_sdk/video_edit/models.py +0 -0
  74. {coze_coding_dev_sdk-0.5.11 → coze_coding_dev_sdk-0.5.13}/coze_coding_dev_sdk/video_edit/video_edit.py +0 -0
  75. {coze_coding_dev_sdk-0.5.11 → coze_coding_dev_sdk-0.5.13}/coze_coding_dev_sdk/voice/__init__.py +0 -0
  76. {coze_coding_dev_sdk-0.5.11 → coze_coding_dev_sdk-0.5.13}/coze_coding_dev_sdk/voice/asr.py +0 -0
  77. {coze_coding_dev_sdk-0.5.11 → coze_coding_dev_sdk-0.5.13}/coze_coding_dev_sdk/voice/models.py +0 -0
  78. {coze_coding_dev_sdk-0.5.11 → coze_coding_dev_sdk-0.5.13}/coze_coding_dev_sdk/voice/tts.py +0 -0
  79. {coze_coding_dev_sdk-0.5.11 → coze_coding_dev_sdk-0.5.13}/coze_coding_dev_sdk.egg-info/dependency_links.txt +0 -0
  80. {coze_coding_dev_sdk-0.5.11 → coze_coding_dev_sdk-0.5.13}/coze_coding_dev_sdk.egg-info/entry_points.txt +0 -0
  81. {coze_coding_dev_sdk-0.5.11 → coze_coding_dev_sdk-0.5.13}/coze_coding_dev_sdk.egg-info/requires.txt +0 -0
  82. {coze_coding_dev_sdk-0.5.11 → coze_coding_dev_sdk-0.5.13}/coze_coding_dev_sdk.egg-info/top_level.txt +0 -0
  83. {coze_coding_dev_sdk-0.5.11 → coze_coding_dev_sdk-0.5.13}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: coze-coding-dev-sdk
3
- Version: 0.5.11
3
+ Version: 0.5.13
4
4
  Summary: Coze Coding Dev SDK - 优雅的多功能 AI SDK,支持图片生成、视频生成、语音合成、语音识别、大语言模型、联网搜索和文本/多模态 Embedding。包含命令行工具 coze-coding-ai,支持 Context 上下文追踪
5
5
  Author-email: Coze Coding Integration Team <support@coze.com>
6
6
  Maintainer-email: Coze Coding Integration Team <support@coze.com>
@@ -3,6 +3,7 @@ import click
3
3
  from .chat import chat
4
4
  from .db import db
5
5
  from .embedding import embedding
6
+ from .storage import storage
6
7
  from .image import image
7
8
  from .knowledge import knowledge
8
9
  from .search import search
@@ -32,6 +33,7 @@ main.add_command(chat)
32
33
  main.add_command(db)
33
34
  main.add_command(embedding)
34
35
  main.add_command(document)
36
+ main.add_command(storage)
35
37
 
36
38
 
37
39
  if __name__ == "__main__":
@@ -0,0 +1,156 @@
1
+ """
2
+ 数据库 CLI 命令
3
+ """
4
+
5
+ import json
6
+ import os
7
+ from dataclasses import asdict
8
+ from typing import Optional
9
+
10
+ import click
11
+ from rich.console import Console
12
+
13
+ # 默认路径
14
+ DEFAULT_MODEL_OUTPUT = "src/storage/database/shared/model.py"
15
+ DEFAULT_MODEL_IMPORT_PATH = "storage.database.shared.model"
16
+ DEFAULT_MODEL_PATH = "src"
17
+
18
+ console = Console()
19
+
20
+
21
+ def _get_workspace_path() -> str:
22
+ """获取工作目录"""
23
+ return os.getenv("WORKSPACE_PATH", os.getcwd())
24
+
25
+
26
+ @click.group()
27
+ def db():
28
+ """Database management commands."""
29
+ pass
30
+
31
+
32
+ @db.command()
33
+ @click.argument("output_path", required=False, default=None)
34
+ @click.option("--verbose", "-v", is_flag=True, help="Verbose output")
35
+ def generate_models(output_path: str, verbose: bool):
36
+ """
37
+ Generate ORM models from database.
38
+
39
+ OUTPUT_PATH: Path to output model file (default: src/storage/database/shared/model.py)
40
+ """
41
+ from coze_coding_dev_sdk.database import generate_models as _generate_models
42
+
43
+ if output_path is None:
44
+ workspace = _get_workspace_path()
45
+ output_path = os.path.join(workspace, DEFAULT_MODEL_OUTPUT)
46
+
47
+ try:
48
+ _generate_models(output_path, verbose=verbose)
49
+ click.echo(f"Models generated at {output_path}")
50
+ except Exception as e:
51
+ click.echo(f"Error: {e}", err=True)
52
+ raise SystemExit(1)
53
+
54
+
55
+ @db.command()
56
+ @click.option(
57
+ "--model-import-path",
58
+ default=DEFAULT_MODEL_IMPORT_PATH,
59
+ help=f"Model import path (default: {DEFAULT_MODEL_IMPORT_PATH})",
60
+ )
61
+ @click.option(
62
+ "--model-path",
63
+ default=None,
64
+ help=f"Path to add to sys.path for model import (default: $WORKSPACE_PATH/{DEFAULT_MODEL_PATH})",
65
+ )
66
+ @click.option("--verbose", "-v", is_flag=True, help="Verbose output")
67
+ def upgrade(model_import_path: str, model_path: str, verbose: bool):
68
+ """
69
+ Run database migrations.
70
+
71
+ Automatically generates migration and upgrades to head.
72
+ """
73
+ from coze_coding_dev_sdk.database import upgrade as _upgrade
74
+
75
+ if model_path is None:
76
+ workspace = _get_workspace_path()
77
+ model_path = os.path.join(workspace, DEFAULT_MODEL_PATH)
78
+
79
+ try:
80
+ _upgrade(
81
+ model_import_path=model_import_path,
82
+ model_path=model_path,
83
+ verbose=verbose,
84
+ )
85
+ click.echo("Database upgraded successfully")
86
+ except Exception as e:
87
+ click.echo(f"Error: {e}", err=True)
88
+ raise SystemExit(1)
89
+
90
+
91
+ @db.command()
92
+ @click.option(
93
+ "--env",
94
+ "-e",
95
+ type=click.Choice(["dev", "prod", "develop", "product"]),
96
+ default=None,
97
+ help="目标环境: dev/develop 或 prod/product,不传则使用 token 中的环境",
98
+ )
99
+ @click.option("--mock", is_flag=True, help="使用 mock 模式(测试运行,不会真正执行操作)")
100
+ @click.option(
101
+ "--header",
102
+ "-H",
103
+ multiple=True,
104
+ help="自定义 HTTP 请求头 (格式: 'Key: Value' 或 'Key=Value',可多次使用)",
105
+ )
106
+ @click.option("--verbose", "-v", is_flag=True, help="显示详细的 HTTP 请求和响应日志")
107
+ def diagnose(env: Optional[str], mock: bool, header: tuple, verbose: bool):
108
+ """获取数据库运行状态诊断报告。
109
+
110
+ 诊断内容包括:慢查询、连接状态、阻塞查询、长时间运行查询、数据库大小、表大小等。
111
+
112
+ \b
113
+ 示例:
114
+ coze-coding-ai db diagnose
115
+ coze-coding-ai db diagnose --env dev
116
+ coze-coding-ai db diagnose --env prod --verbose
117
+ """
118
+ try:
119
+ from coze_coding_utils.runtime_ctx.context import new_context
120
+
121
+ from ..core.config import Config
122
+ from ..supabase import SupabaseClient
123
+ from .constants import RUN_MODE_HEADER, RUN_MODE_TEST
124
+ from .utils import parse_headers
125
+
126
+ # normalize env aliases
127
+ env_map = {"develop": "dev", "product": "prod"}
128
+ if env in env_map:
129
+ env = env_map[env]
130
+
131
+ config = Config()
132
+ ctx = None
133
+ custom_headers = parse_headers(header) or {}
134
+
135
+ if mock:
136
+ ctx = new_context(method="db.diagnose", headers=custom_headers)
137
+ custom_headers[RUN_MODE_HEADER] = RUN_MODE_TEST
138
+ console.print("[yellow]🧪 Mock 模式已启用(测试运行)[/yellow]")
139
+
140
+ client = SupabaseClient(config, ctx=ctx, custom_headers=custom_headers, verbose=verbose)
141
+ response = client.diagnose_database(env=env)
142
+
143
+ if response.code != 0:
144
+ msg = response.msg or ""
145
+ if "record not found" in msg.lower():
146
+ env_label = env or "dev/prod"
147
+ console.print(f"[red]✗ {env_label} 数据库还未创建,请先创建数据库后再进行诊断[/red]")
148
+ else:
149
+ console.print(f"[red]Error: {msg}[/red]")
150
+ return
151
+
152
+ console.print(json.dumps(asdict(response.report) if response.report else None, indent=2, ensure_ascii=False))
153
+
154
+ except Exception as e:
155
+ console.print(f"[red]✗ Error: {str(e)}[/red]")
156
+ raise click.Abort()
@@ -0,0 +1,241 @@
1
+ """
2
+ 对象存储 CLI 命令
3
+ """
4
+
5
+ import json
6
+ import mimetypes
7
+ import sys
8
+ import tempfile
9
+ import zipfile
10
+ from concurrent.futures import ThreadPoolExecutor, as_completed
11
+ from pathlib import Path, PurePosixPath
12
+ from typing import Any, Dict, List, Optional, Tuple
13
+ from uuid import uuid4
14
+
15
+ import click
16
+
17
+
18
+ def _normalize_prefix(prefix: str) -> str:
19
+ if not prefix:
20
+ return ""
21
+ prefix = prefix.strip("/")
22
+ return prefix + "/" if prefix else ""
23
+
24
+
25
+ def _build_object_key(prefix: str, name: str, use_uuid: bool, sanitize_fn) -> str:
26
+ sanitized = sanitize_fn(name)
27
+ if use_uuid:
28
+ p = Path(sanitized)
29
+ uniq = uuid4().hex[:8]
30
+ if str(p.parent) != ".":
31
+ sanitized = str(PurePosixPath(p.parent / f"{p.stem}_{uniq}{p.suffix}"))
32
+ else:
33
+ sanitized = f"{p.stem}_{uniq}{p.suffix}"
34
+ return prefix + sanitized
35
+
36
+
37
+ def _resolve_paths(paths: Tuple[str, ...]) -> List[Tuple[Path, str]]:
38
+ """解析目录为 (绝对路径, 相对key) 列表,保留目录名"""
39
+ results: List[Tuple[Path, str]] = []
40
+ seen: set = set()
41
+ for path_str in paths:
42
+ p = Path(path_str).resolve()
43
+ if not p.exists() or not p.is_dir():
44
+ continue
45
+ for f in sorted(p.rglob("*")):
46
+ if f.is_file() and f not in seen:
47
+ seen.add(f)
48
+ rel = f.relative_to(p.parent)
49
+ results.append((f, str(PurePosixPath(rel))))
50
+ return results
51
+
52
+
53
+ def _zip_directory(dir_path: Path) -> Path:
54
+ """将目录打包为临时 zip 文件"""
55
+ zip_name = f"{dir_path.name}.zip"
56
+ tmp_dir = tempfile.mkdtemp()
57
+ zip_path = Path(tmp_dir) / zip_name
58
+ with zipfile.ZipFile(zip_path, "w", zipfile.ZIP_DEFLATED) as zf:
59
+ for f in sorted(dir_path.rglob("*")):
60
+ if f.is_file():
61
+ arcname = str(f.relative_to(dir_path.parent))
62
+ zf.write(f, arcname)
63
+ return zip_path
64
+
65
+
66
+ def _upload_file_to_s3(s3_client, bucket: str, file_path: Path, object_key: str, threshold: int) -> int:
67
+ """上传单个文件到 S3,返回字节数"""
68
+ content_type = mimetypes.guess_type(str(file_path))[0] or "application/octet-stream"
69
+ file_size = file_path.stat().st_size
70
+
71
+ if file_size <= threshold:
72
+ s3_client.put_object(
73
+ Bucket=bucket,
74
+ Key=object_key,
75
+ Body=file_path.read_bytes(),
76
+ ContentType=content_type,
77
+ )
78
+ else:
79
+ from boto3.s3.transfer import TransferConfig
80
+ config = TransferConfig(
81
+ multipart_threshold=threshold,
82
+ multipart_chunksize=threshold,
83
+ max_concurrency=1,
84
+ use_threads=False,
85
+ )
86
+ with open(file_path, "rb") as f:
87
+ s3_client.upload_fileobj(
88
+ Fileobj=f,
89
+ Bucket=bucket,
90
+ Key=object_key,
91
+ ExtraArgs={"ContentType": content_type},
92
+ Config=config,
93
+ )
94
+ return file_size
95
+
96
+
97
+ @click.group()
98
+ def storage():
99
+ """Object storage commands."""
100
+ pass
101
+
102
+
103
+ @storage.command()
104
+ @click.argument("paths", nargs=-1, required=True, type=click.Path())
105
+ @click.option("--prefix", "-p", default="", help="Target S3 key prefix (virtual directory)")
106
+ @click.option("--uuid", "use_uuid", is_flag=True, help="Append UUID suffix to filenames to avoid collisions")
107
+ @click.option("--expire", "-e", default=86400, type=int, help="Presigned URL expiration in seconds (default: 86400 = 24h)")
108
+ @click.option("--threshold", "-t", default=5 * 1024 * 1024, type=int, help="Size threshold (bytes) for streaming upload (default: 5MB)")
109
+ @click.option("--max-concurrency", "-c", default=4, type=int, help="Number of parallel upload threads (default: 4)")
110
+ def upload(paths, prefix, use_uuid, expire, threshold, max_concurrency):
111
+ """Upload files and directories to object storage.
112
+
113
+ PATHS: One or more file or directory paths to upload.
114
+ Files are uploaded directly. Directories are uploaded recursively
115
+ AND also zipped as a single archive.
116
+ Outputs JSON with name-to-URL mappings to stdout.
117
+
118
+ \b
119
+ Examples:
120
+ coze-coding-ai storage upload ./data --prefix my-project/
121
+ coze-coding-ai storage upload file1.txt dir1/ dir2/ --prefix uploads/
122
+ coze-coding-ai storage upload report.csv --prefix reports/ --expire 3600
123
+ """
124
+ from coze_coding_dev_sdk.s3 import S3SyncStorage
125
+
126
+ # 1. 分离文件和目录
127
+ files: List[Path] = []
128
+ dirs: List[Path] = []
129
+
130
+ for path_str in paths:
131
+ p = Path(path_str).resolve()
132
+ if not p.exists():
133
+ print(f"Warning: {path_str} does not exist, skipping", file=sys.stderr)
134
+ continue
135
+ if p.is_file():
136
+ files.append(p)
137
+ elif p.is_dir():
138
+ dirs.append(p)
139
+ else:
140
+ print(f"Warning: {path_str} is not a file or directory, skipping", file=sys.stderr)
141
+
142
+ if not files and not dirs:
143
+ print(json.dumps({"_error": "No files or directories found to upload."}))
144
+ raise SystemExit(1)
145
+
146
+ # 2. 规范化 prefix
147
+ prefix = _normalize_prefix(prefix)
148
+
149
+ # 3. 初始化 S3
150
+ try:
151
+ storage_client = S3SyncStorage()
152
+ target_bucket = storage_client._resolve_bucket(None)
153
+ except Exception as e:
154
+ print(json.dumps({"_error": f"S3 configuration error: {e}"}))
155
+ raise SystemExit(1)
156
+
157
+ sanitize_fn = storage_client._sanitize_file_name
158
+
159
+ # 4. 构建上传任务
160
+ single_file_tasks: List[Tuple[Path, str, str]] = []
161
+ for f in files:
162
+ key = _build_object_key(prefix, f.name, use_uuid, sanitize_fn)
163
+ single_file_tasks.append((f, key, f.name))
164
+
165
+ dir_file_tasks: List[Tuple[Path, str, str]] = []
166
+ for d in dirs:
167
+ dir_files = _resolve_paths((str(d),))
168
+ for file_path, relative_key in dir_files:
169
+ key = _build_object_key(prefix, relative_key, use_uuid, sanitize_fn)
170
+ dir_file_tasks.append((file_path, key, relative_key))
171
+
172
+ zip_tasks: List[Tuple[Path, str, str]] = []
173
+ for d in dirs:
174
+ zip_name = f"{d.name}.zip"
175
+ key = _build_object_key(prefix, zip_name, use_uuid, sanitize_fn)
176
+ zip_tasks.append((d, key, f"{d.name}/"))
177
+
178
+ all_file_tasks = single_file_tasks + dir_file_tasks
179
+
180
+ # 5. 获取 boto3 client
181
+ try:
182
+ s3_client = storage_client._get_client()
183
+ except Exception as e:
184
+ print(json.dumps({"_error": f"Failed to initialize S3 client: {e}"}))
185
+ raise SystemExit(1)
186
+
187
+ result_map: Dict[str, str] = {}
188
+ failures: List[Tuple[str, str]] = []
189
+ single_file_names = {name for _, _, name in single_file_tasks}
190
+
191
+ # 6. 并发上传文件
192
+ def _do_upload(fp: Path, key: str, name: str):
193
+ try:
194
+ _upload_file_to_s3(s3_client, target_bucket, fp, key, threshold)
195
+ if name in single_file_names:
196
+ url = storage_client.generate_presigned_url(key=key, expire_time=expire)
197
+ return (name, url, None)
198
+ return (name, None, None)
199
+ except Exception as e:
200
+ return (name, None, str(e))
201
+
202
+ if all_file_tasks:
203
+ with ThreadPoolExecutor(max_workers=max_concurrency) as executor:
204
+ futures = {
205
+ executor.submit(_do_upload, fp, key, name): name
206
+ for fp, key, name in all_file_tasks
207
+ }
208
+ for future in as_completed(futures):
209
+ name, url, error = future.result()
210
+ if error:
211
+ failures.append((name, error))
212
+ elif url:
213
+ result_map[name] = url
214
+
215
+ # 7. 打包并上传 zip
216
+ for dir_path, object_key, display_name in zip_tasks:
217
+ tmp_zip_path: Optional[Path] = None
218
+ try:
219
+ tmp_zip_path = _zip_directory(dir_path)
220
+ _upload_file_to_s3(s3_client, target_bucket, tmp_zip_path, object_key, threshold)
221
+ url = storage_client.generate_presigned_url(key=object_key, expire_time=expire)
222
+ result_map[display_name] = url
223
+ except Exception as e:
224
+ failures.append((display_name, str(e)))
225
+ finally:
226
+ if tmp_zip_path and tmp_zip_path.exists():
227
+ try:
228
+ tmp_zip_path.unlink()
229
+ tmp_zip_path.parent.rmdir()
230
+ except OSError:
231
+ pass
232
+
233
+ # 8. 输出 JSON
234
+ output: Dict[str, Any] = dict(result_map)
235
+ if failures:
236
+ output["_failures"] = [{"name": n, "error": e} for n, e in failures]
237
+
238
+ print(json.dumps(output, indent=2, ensure_ascii=False))
239
+
240
+ if failures:
241
+ raise SystemExit(1)
@@ -109,11 +109,6 @@ class LLMClient:
109
109
  - AIMessage: AI 回复,用于多轮对话
110
110
 
111
111
  model: 模型ID,默认 "doubao-seed-1-8-251228"
112
- 可选模型:
113
- - "doubao-seed-1-8-251228": 最新模型,更高性能
114
- - "doubao-seed-1-6-251015": 平衡性能
115
- - "doubao-seed-1-6-flash-250615": 快速模型
116
- - "doubao-seed-1-6-thinking-250715": 思考模型
117
112
 
118
113
  thinking: 思考模式,默认 "disabled"
119
114
  - "enabled": 启用深度思考,适合复杂推理任务
@@ -227,11 +222,6 @@ class LLMClient:
227
222
  - AIMessage: AI 回复,用于多轮对话
228
223
 
229
224
  model: 模型ID,默认 "doubao-seed-1-8-251228"
230
- 可选模型:
231
- - "doubao-seed-1-8-251228": 最新模型,更高性能
232
- - "doubao-seed-1-6-251015": 平衡性能
233
- - "doubao-seed-1-6-flash-250615": 快速模型
234
- - "doubao-seed-1-6-thinking-250715": 思考模型
235
225
 
236
226
  thinking: 思考模式,默认 "disabled"
237
227
  - "enabled": 启用深度思考,适合复杂推理任务
@@ -1,11 +1,16 @@
1
1
  from .client import SupabaseClient
2
2
  from .models import (
3
3
  AuthEmailConfig,
4
+ BlockedQueryInfo,
4
5
  Bucket,
6
+ ConnectionStateCount,
7
+ ConnectionStats,
5
8
  CreateBucketResponse,
6
9
  DeleteBucketResponse,
7
10
  DeleteEdgeFunctionResponse,
8
11
  DeployEdgeFunctionResponse,
12
+ DiagnoseDatabaseResponse,
13
+ DiagnosticReport,
9
14
  EdgeFunction,
10
15
  FunctionFile,
11
16
  GetAuthConfigResponse,
@@ -13,6 +18,11 @@ from .models import (
13
18
  GetEdgeFunctionResponse,
14
19
  ListBucketsResponse,
15
20
  ListEdgeFunctionsResponse,
21
+ LongRunningQueryInfo,
22
+ PGDiagnosticMetrics,
23
+ QueryStatsInfo,
24
+ SupabaseDiagnosticMetrics,
25
+ TableSizeInfo,
16
26
  UpdateAuthConfigResponse,
17
27
  UpdateBucketResponse,
18
28
  )
@@ -34,4 +44,14 @@ __all__ = [
34
44
  "DeleteBucketResponse",
35
45
  "GetAuthConfigResponse",
36
46
  "UpdateAuthConfigResponse",
47
+ "QueryStatsInfo",
48
+ "ConnectionStateCount",
49
+ "ConnectionStats",
50
+ "BlockedQueryInfo",
51
+ "LongRunningQueryInfo",
52
+ "TableSizeInfo",
53
+ "SupabaseDiagnosticMetrics",
54
+ "PGDiagnosticMetrics",
55
+ "DiagnosticReport",
56
+ "DiagnoseDatabaseResponse",
37
57
  ]
@@ -6,11 +6,16 @@ from ..core.client import BaseClient
6
6
  from ..core.config import Config
7
7
  from .models import (
8
8
  AuthEmailConfig,
9
+ BlockedQueryInfo,
9
10
  Bucket,
11
+ ConnectionStateCount,
12
+ ConnectionStats,
10
13
  CreateBucketResponse,
11
14
  DeleteBucketResponse,
12
15
  DeleteEdgeFunctionResponse,
13
16
  DeployEdgeFunctionResponse,
17
+ DiagnoseDatabaseResponse,
18
+ DiagnosticReport,
14
19
  EdgeFunction,
15
20
  FunctionFile,
16
21
  GetAuthConfigResponse,
@@ -18,6 +23,11 @@ from .models import (
18
23
  GetEdgeFunctionResponse,
19
24
  ListBucketsResponse,
20
25
  ListEdgeFunctionsResponse,
26
+ LongRunningQueryInfo,
27
+ PGDiagnosticMetrics,
28
+ QueryStatsInfo,
29
+ SupabaseDiagnosticMetrics,
30
+ TableSizeInfo,
21
31
  UpdateAuthConfigResponse,
22
32
  UpdateBucketResponse,
23
33
  )
@@ -334,3 +344,71 @@ class SupabaseClient(BaseClient):
334
344
  code=response.get("code", 0),
335
345
  msg=response.get("msg", ""),
336
346
  )
347
+
348
+ def diagnose_database(
349
+ self,
350
+ env: Optional[str] = None,
351
+ ) -> DiagnoseDatabaseResponse:
352
+ payload: Dict = {}
353
+ if env is not None:
354
+ payload["env"] = env
355
+
356
+ response = self._request(
357
+ method="POST",
358
+ url=f"{self.base_url}/v1/supabase/database/diagnose",
359
+ json=payload,
360
+ headers=self._get_headers(),
361
+ )
362
+
363
+ code = response.get("code", 0)
364
+ msg = response.get("msg", "")
365
+
366
+ report = None
367
+ r = response.get("report")
368
+ if r:
369
+ pg_raw = r.get("pg_metrics") or {}
370
+ pg_metrics = PGDiagnosticMetrics(
371
+ top_queries_by_total_time=[
372
+ QueryStatsInfo(**q) for q in (pg_raw.get("top_queries_by_total_time") or [])
373
+ ],
374
+ top_queries_by_frequency=[
375
+ QueryStatsInfo(**q) for q in (pg_raw.get("top_queries_by_frequency") or [])
376
+ ],
377
+ slow_queries=[
378
+ QueryStatsInfo(**q) for q in (pg_raw.get("slow_queries") or [])
379
+ ],
380
+ connection_stats=ConnectionStats(
381
+ connections_by_state=[
382
+ ConnectionStateCount(**c)
383
+ for c in (pg_raw.get("connection_stats", {}).get("connections_by_state") or [])
384
+ ],
385
+ max_connections=pg_raw.get("connection_stats", {}).get("max_connections"),
386
+ connection_usage_pct=pg_raw.get("connection_stats", {}).get("connection_usage_pct"),
387
+ total_connections=pg_raw.get("connection_stats", {}).get("total_connections"),
388
+ ) if pg_raw.get("connection_stats") else None,
389
+ blocked_queries=[
390
+ BlockedQueryInfo(**q) for q in (pg_raw.get("blocked_queries") or [])
391
+ ],
392
+ long_running_queries=[
393
+ LongRunningQueryInfo(**q) for q in (pg_raw.get("long_running_queries") or [])
394
+ ],
395
+ database_size=pg_raw.get("database_size"),
396
+ table_sizes=[
397
+ TableSizeInfo(**t) for t in (pg_raw.get("table_sizes") or [])
398
+ ],
399
+ )
400
+
401
+ supabase_metrics = None
402
+ sm = r.get("supabase_metrics")
403
+ if sm:
404
+ supabase_metrics = SupabaseDiagnosticMetrics(
405
+ rate_limited_429_count=sm.get("rate_limited_429_count"),
406
+ )
407
+
408
+ report = DiagnosticReport(
409
+ database_type=r.get("database_type", 0),
410
+ pg_metrics=pg_metrics,
411
+ supabase_metrics=supabase_metrics,
412
+ )
413
+
414
+ return DiagnoseDatabaseResponse(report=report, code=code, msg=msg)
@@ -132,3 +132,83 @@ class UpdateAuthConfigResponse:
132
132
  success: bool = False
133
133
  code: int = 0
134
134
  msg: str = ""
135
+
136
+
137
+ # Database Diagnostic
138
+
139
+
140
+ @dataclass
141
+ class QueryStatsInfo:
142
+ query: Optional[str] = None
143
+ calls: Optional[int] = None
144
+ avg_ms: Optional[float] = None
145
+ total_ms: Optional[float] = None
146
+ rows: Optional[int] = None
147
+
148
+
149
+ @dataclass
150
+ class ConnectionStateCount:
151
+ state: Optional[str] = None
152
+ count: Optional[int] = None
153
+
154
+
155
+ @dataclass
156
+ class ConnectionStats:
157
+ connections_by_state: Optional[List["ConnectionStateCount"]] = None
158
+ max_connections: Optional[int] = None
159
+ connection_usage_pct: Optional[float] = None
160
+ total_connections: Optional[int] = None
161
+
162
+
163
+ @dataclass
164
+ class BlockedQueryInfo:
165
+ blocked_pid: Optional[int] = None
166
+ blocked_query: Optional[str] = None
167
+ blocking_pid: Optional[int] = None
168
+ blocking_query: Optional[str] = None
169
+ blocked_duration: Optional[str] = None
170
+
171
+
172
+ @dataclass
173
+ class LongRunningQueryInfo:
174
+ pid: Optional[int] = None
175
+ duration: Optional[str] = None
176
+ state: Optional[str] = None
177
+ query: Optional[str] = None
178
+
179
+
180
+ @dataclass
181
+ class TableSizeInfo:
182
+ table_name: Optional[str] = None
183
+ total_size: Optional[str] = None
184
+
185
+
186
+ @dataclass
187
+ class SupabaseDiagnosticMetrics:
188
+ rate_limited_429_count: Optional[int] = None
189
+
190
+
191
+ @dataclass
192
+ class PGDiagnosticMetrics:
193
+ top_queries_by_total_time: Optional[List[QueryStatsInfo]] = None
194
+ top_queries_by_frequency: Optional[List[QueryStatsInfo]] = None
195
+ slow_queries: Optional[List[QueryStatsInfo]] = None
196
+ connection_stats: Optional[ConnectionStats] = None
197
+ blocked_queries: Optional[List[BlockedQueryInfo]] = None
198
+ long_running_queries: Optional[List[LongRunningQueryInfo]] = None
199
+ database_size: Optional[str] = None
200
+ table_sizes: Optional[List[TableSizeInfo]] = None
201
+
202
+
203
+ @dataclass
204
+ class DiagnosticReport:
205
+ database_type: int = 0
206
+ pg_metrics: Optional[PGDiagnosticMetrics] = None
207
+ supabase_metrics: Optional[SupabaseDiagnosticMetrics] = None
208
+
209
+
210
+ @dataclass
211
+ class DiagnoseDatabaseResponse:
212
+ report: Optional[DiagnosticReport] = None
213
+ code: int = 0
214
+ msg: str = ""
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: coze-coding-dev-sdk
3
- Version: 0.5.11
3
+ Version: 0.5.13
4
4
  Summary: Coze Coding Dev SDK - 优雅的多功能 AI SDK,支持图片生成、视频生成、语音合成、语音识别、大语言模型、联网搜索和文本/多模态 Embedding。包含命令行工具 coze-coding-ai,支持 Context 上下文追踪
5
5
  Author-email: Coze Coding Integration Team <support@coze.com>
6
6
  Maintainer-email: Coze Coding Integration Team <support@coze.com>
@@ -20,6 +20,7 @@ coze_coding_dev_sdk/cli/embedding.py
20
20
  coze_coding_dev_sdk/cli/image.py
21
21
  coze_coding_dev_sdk/cli/knowledge.py
22
22
  coze_coding_dev_sdk/cli/search.py
23
+ coze_coding_dev_sdk/cli/storage.py
23
24
  coze_coding_dev_sdk/cli/supabase.py
24
25
  coze_coding_dev_sdk/cli/utils.py
25
26
  coze_coding_dev_sdk/cli/video.py
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "coze-coding-dev-sdk"
7
- version = "0.5.11"
7
+ version = "0.5.13"
8
8
  description = "Coze Coding Dev SDK - 优雅的多功能 AI SDK,支持图片生成、视频生成、语音合成、语音识别、大语言模型、联网搜索和文本/多模态 Embedding。包含命令行工具 coze-coding-ai,支持 Context 上下文追踪"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -1,81 +0,0 @@
1
- """
2
- 数据库 CLI 命令
3
- """
4
-
5
- import os
6
- import click
7
-
8
- # 默认路径
9
- DEFAULT_MODEL_OUTPUT = "src/storage/database/shared/model.py"
10
- DEFAULT_MODEL_IMPORT_PATH = "storage.database.shared.model"
11
- DEFAULT_MODEL_PATH = "src"
12
-
13
-
14
- def _get_workspace_path() -> str:
15
- """获取工作目录"""
16
- return os.getenv("WORKSPACE_PATH", os.getcwd())
17
-
18
-
19
- @click.group()
20
- def db():
21
- """Database management commands."""
22
- pass
23
-
24
-
25
- @db.command()
26
- @click.argument("output_path", required=False, default=None)
27
- @click.option("--verbose", "-v", is_flag=True, help="Verbose output")
28
- def generate_models(output_path: str, verbose: bool):
29
- """
30
- Generate ORM models from database.
31
-
32
- OUTPUT_PATH: Path to output model file (default: src/storage/database/shared/model.py)
33
- """
34
- from coze_coding_dev_sdk.database import generate_models as _generate_models
35
-
36
- if output_path is None:
37
- workspace = _get_workspace_path()
38
- output_path = os.path.join(workspace, DEFAULT_MODEL_OUTPUT)
39
-
40
- try:
41
- _generate_models(output_path, verbose=verbose)
42
- click.echo(f"Models generated at {output_path}")
43
- except Exception as e:
44
- click.echo(f"Error: {e}", err=True)
45
- raise SystemExit(1)
46
-
47
-
48
- @db.command()
49
- @click.option(
50
- "--model-import-path",
51
- default=DEFAULT_MODEL_IMPORT_PATH,
52
- help=f"Model import path (default: {DEFAULT_MODEL_IMPORT_PATH})",
53
- )
54
- @click.option(
55
- "--model-path",
56
- default=None,
57
- help=f"Path to add to sys.path for model import (default: $WORKSPACE_PATH/{DEFAULT_MODEL_PATH})",
58
- )
59
- @click.option("--verbose", "-v", is_flag=True, help="Verbose output")
60
- def upgrade(model_import_path: str, model_path: str, verbose: bool):
61
- """
62
- Run database migrations.
63
-
64
- Automatically generates migration and upgrades to head.
65
- """
66
- from coze_coding_dev_sdk.database import upgrade as _upgrade
67
-
68
- if model_path is None:
69
- workspace = _get_workspace_path()
70
- model_path = os.path.join(workspace, DEFAULT_MODEL_PATH)
71
-
72
- try:
73
- _upgrade(
74
- model_import_path=model_import_path,
75
- model_path=model_path,
76
- verbose=verbose,
77
- )
78
- click.echo("Database upgraded successfully")
79
- except Exception as e:
80
- click.echo(f"Error: {e}", err=True)
81
- raise SystemExit(1)