agent-builder-gateway-sdk 0.7.1__py3-none-any.whl

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.
gateway_sdk/client.py ADDED
@@ -0,0 +1,726 @@
1
+ """Gateway 客户端(内部版本)
2
+
3
+ 用于 Agent/Prefab 内部调用网关
4
+
5
+ 架构说明:
6
+ - Agent 从请求头获取 X-Internal-Token(由网关传入)
7
+ - Prefab 从请求头获取 X-Internal-Token(由 Agent 传入)
8
+ - SDK 直接传递 internal token,不做任何转换
9
+ """
10
+
11
+ import httpx
12
+ from typing import Any, Dict, List, Optional, Union, Iterator
13
+ from .models import PrefabCall, PrefabResult, BatchResult, CallStatus, StreamEvent
14
+ from .streaming import parse_sse_stream
15
+ from .exceptions import (
16
+ GatewayError,
17
+ AuthenticationError,
18
+ PrefabNotFoundError,
19
+ ValidationError,
20
+ QuotaExceededError,
21
+ ServiceUnavailableError,
22
+ MissingSecretError,
23
+ )
24
+
25
+
26
+ # Gateway 地址 (在构建时会根据环境动态替换)
27
+ DEFAULT_GATEWAY_URL = "http://agent-builder-gateway.sensedeal.vip"
28
+
29
+
30
+ class GatewayClient:
31
+ """Gateway SDK 主客户端(内部版本)"""
32
+
33
+ def __init__(
34
+ self,
35
+ internal_token: Optional[str] = None,
36
+ base_url: str = DEFAULT_GATEWAY_URL,
37
+ timeout: int = 1200,
38
+ ):
39
+ """
40
+ 初始化客户端
41
+
42
+ Args:
43
+ internal_token: 内部 token(从请求头 X-Internal-Token 获取)
44
+ 可选,如果不提供则使用白名单模式(适用于白名单环境)
45
+ base_url: Gateway 地址(默认使用测试环境)
46
+ timeout: 请求超时时间(秒),默认 1200 秒(20 分钟)
47
+
48
+ Note:
49
+ - Agent/Prefab 内部调用:传入 internal_token
50
+ - 白名单环境:无需传入 token,由 Gateway 基于 IP 白名单验证
51
+ - 外部用户请使用 from_api_key() 或外部端点 /v1/external/invoke_*
52
+ """
53
+ self.base_url = base_url.rstrip("/")
54
+ self.internal_token = internal_token
55
+ self.timeout = timeout
56
+
57
+ @classmethod
58
+ def from_api_key(cls, api_key: str, base_url: str = DEFAULT_GATEWAY_URL, timeout: int = 1200):
59
+ """
60
+ 从 API Key 创建客户端(第三方集成使用)
61
+
62
+ Args:
63
+ api_key: API Key(sk-xxx)
64
+ base_url: Gateway 地址
65
+ timeout: 请求超时时间(秒)
66
+
67
+ Returns:
68
+ GatewayClient 实例
69
+
70
+ Raises:
71
+ AuthenticationError: API Key 无效
72
+ ServiceUnavailableError: 网络请求失败
73
+
74
+ Example:
75
+ ```python
76
+ # 第三方集成
77
+ client = GatewayClient.from_api_key("sk-xxx")
78
+
79
+ # 上传输入文件
80
+ s3_url = client.upload_input_file("/tmp/video.mp4")
81
+
82
+ # 调用 Prefab
83
+ result = client.run("video-processor", "1.0.0", "extract_audio", files={"video": [s3_url]})
84
+ ```
85
+ """
86
+ from .exceptions import AuthenticationError, ServiceUnavailableError
87
+
88
+ url = f"{base_url.rstrip('/')}/v1/auth/convert_to_internal_token"
89
+ headers = {"Authorization": f"Bearer {api_key}"}
90
+
91
+ try:
92
+ with httpx.Client(timeout=timeout) as http_client:
93
+ response = http_client.post(url, headers=headers)
94
+
95
+ if response.status_code == 401:
96
+ raise AuthenticationError("Invalid or expired API Key")
97
+
98
+ response.raise_for_status()
99
+ data = response.json()
100
+ internal_token = data["internal_token"]
101
+
102
+ return cls(internal_token=internal_token, base_url=base_url, timeout=timeout)
103
+
104
+ except httpx.TimeoutException:
105
+ raise ServiceUnavailableError("请求超时")
106
+ except httpx.RequestError as e:
107
+ raise ServiceUnavailableError(f"网络请求失败: {str(e)}")
108
+
109
+ def _build_headers(self, content_type: str = "application/json") -> Dict[str, str]:
110
+ """
111
+ 构建请求头(白名单模式支持)
112
+
113
+ Args:
114
+ content_type: 内容类型
115
+
116
+ Returns:
117
+ 请求头字典
118
+ """
119
+ headers = {"Content-Type": content_type}
120
+ if self.internal_token:
121
+ headers["X-Internal-Token"] = self.internal_token
122
+ return headers
123
+
124
+ def _build_auth_headers(self) -> Dict[str, str]:
125
+ """
126
+ 构建认证请求头(仅包含认证信息,白名单模式支持)
127
+
128
+ Returns:
129
+ 请求头字典
130
+ """
131
+ headers = {}
132
+ if self.internal_token:
133
+ headers["X-Internal-Token"] = self.internal_token
134
+ return headers
135
+
136
+ def run(
137
+ self,
138
+ prefab_id: str,
139
+ version: str,
140
+ function_name: str,
141
+ parameters: Dict[str, Any],
142
+ files: Optional[Dict[str, List[str]]] = None,
143
+ stream: bool = False,
144
+ ) -> Union[PrefabResult, Iterator[StreamEvent]]:
145
+ """
146
+ 执行单个预制件
147
+
148
+ Args:
149
+ prefab_id: 预制件 ID
150
+ version: 版本号
151
+ function_name: 函数名
152
+ parameters: 参数字典
153
+ files: 文件输入(可选)
154
+ stream: 是否流式返回
155
+
156
+ Returns:
157
+ PrefabResult 或 StreamEvent 迭代器
158
+
159
+ Raises:
160
+ AuthenticationError: 认证失败
161
+ PrefabNotFoundError: 预制件不存在
162
+ ValidationError: 参数验证失败
163
+ QuotaExceededError: 配额超限
164
+ ServiceUnavailableError: 服务不可用
165
+ MissingSecretError: 缺少必需的密钥
166
+ """
167
+ call = PrefabCall(
168
+ prefab_id=prefab_id,
169
+ version=version,
170
+ function_name=function_name,
171
+ parameters=parameters,
172
+ files=files,
173
+ )
174
+
175
+ if stream:
176
+ return self._run_streaming(call)
177
+ else:
178
+ result = self.run_batch([call])
179
+ return result.results[0]
180
+
181
+ def run_batch(self, calls: List[PrefabCall]) -> BatchResult:
182
+ """
183
+ 批量执行预制件
184
+
185
+ Args:
186
+ calls: 预制件调用列表
187
+
188
+ Returns:
189
+ BatchResult
190
+
191
+ Raises:
192
+ 同 run() 方法
193
+ """
194
+ url = f"{self.base_url}/v1/internal/run"
195
+ headers = self._build_headers()
196
+
197
+ payload = {"calls": [call.to_dict() for call in calls]}
198
+
199
+ try:
200
+ with httpx.Client(timeout=self.timeout) as client:
201
+ response = client.post(url, json=payload, headers=headers)
202
+ self._handle_error_response(response)
203
+
204
+ data = response.json()
205
+ results = [
206
+ PrefabResult(
207
+ status=CallStatus(r["status"]),
208
+ output=r.get("output"),
209
+ error=r.get("error"),
210
+ job_id=data.get("job_id"),
211
+ )
212
+ for r in data["results"]
213
+ ]
214
+
215
+ return BatchResult(job_id=data["job_id"], status=data["status"], results=results)
216
+
217
+ except httpx.TimeoutException:
218
+ raise ServiceUnavailableError("请求超时")
219
+ except httpx.RequestError as e:
220
+ raise ServiceUnavailableError(f"网络请求失败: {str(e)}")
221
+
222
+ def _run_streaming(self, call: PrefabCall) -> Iterator[StreamEvent]:
223
+ """
224
+ 流式执行预制件
225
+
226
+ Args:
227
+ call: 预制件调用
228
+
229
+ Yields:
230
+ StreamEvent
231
+ """
232
+ url = f"{self.base_url}/v1/internal/run"
233
+ headers = self._build_headers()
234
+
235
+ payload = {"calls": [call.to_dict()]}
236
+
237
+ try:
238
+ with httpx.Client(timeout=self.timeout) as client:
239
+ with client.stream("POST", url, json=payload, headers=headers) as response:
240
+ self._handle_error_response(response)
241
+
242
+ # 解析 SSE 流
243
+ yield from parse_sse_stream(response.iter_bytes())
244
+
245
+ except httpx.TimeoutException:
246
+ raise ServiceUnavailableError("请求超时")
247
+ except httpx.RequestError as e:
248
+ raise ServiceUnavailableError(f"网络请求失败: {str(e)}")
249
+
250
+ def _handle_error_response(self, response: httpx.Response) -> None:
251
+ """
252
+ 处理错误响应
253
+
254
+ Args:
255
+ response: HTTP 响应
256
+
257
+ Raises:
258
+ 对应的异常
259
+ """
260
+ if response.status_code < 400:
261
+ return
262
+
263
+ try:
264
+ error_data = response.json()
265
+ detail = error_data.get("detail", "Unknown error")
266
+
267
+ # 解析错误详情
268
+ if isinstance(detail, dict):
269
+ error_code = detail.get("error_code", "UNKNOWN_ERROR")
270
+ message = detail.get("message", str(detail))
271
+ else:
272
+ error_code = "UNKNOWN_ERROR"
273
+ message = str(detail)
274
+
275
+ except Exception:
276
+ error_code = "UNKNOWN_ERROR"
277
+ # 对于流式响应,需要先读取内容
278
+ try:
279
+ error_text = response.text
280
+ except Exception:
281
+ # 如果无法读取,先读取响应再获取文本
282
+ try:
283
+ response.read()
284
+ error_text = response.text
285
+ except Exception:
286
+ error_text = "Unable to read error response"
287
+ message = f"HTTP {response.status_code}: {error_text}"
288
+
289
+ # 根据状态码和错误码抛出对应异常
290
+ if response.status_code == 401 or response.status_code == 403:
291
+ raise AuthenticationError(message)
292
+ elif response.status_code == 404:
293
+ raise PrefabNotFoundError("unknown", "unknown", message)
294
+ elif response.status_code == 422:
295
+ raise ValidationError(message)
296
+ elif response.status_code == 429:
297
+ # 配额超限
298
+ if isinstance(detail, dict):
299
+ raise QuotaExceededError(
300
+ message,
301
+ limit=detail.get("limit", 0),
302
+ used=detail.get("used", 0),
303
+ quota_type=detail.get("quota_type", "unknown"),
304
+ )
305
+ else:
306
+ raise QuotaExceededError(message, 0, 0, "unknown")
307
+ elif response.status_code == 400 and error_code == "MISSING_SECRET":
308
+ # 缺少密钥
309
+ if isinstance(detail, dict):
310
+ raise MissingSecretError(
311
+ prefab_id=detail.get("prefab_id", "unknown"),
312
+ secret_name=detail.get("secret_name", "unknown"),
313
+ instructions=detail.get("instructions"),
314
+ )
315
+ else:
316
+ raise MissingSecretError("unknown", "unknown")
317
+ elif response.status_code == 400 and error_code == "MISSING_AGENT_CONTEXT":
318
+ # 缺少 Agent 上下文(文件操作需要)
319
+ from .exceptions import AgentContextRequiredError
320
+ raise AgentContextRequiredError(message)
321
+ elif response.status_code >= 500:
322
+ raise ServiceUnavailableError(message)
323
+ else:
324
+ raise GatewayError(message, {"error_code": error_code})
325
+
326
+ # ========== 文件操作 API(新增)==========
327
+
328
+ def upload_input_file(self, file_path: str, content_type: Optional[str] = None) -> str:
329
+ """
330
+ 上传输入文件(第三方集成使用)
331
+
332
+ Args:
333
+ file_path: 本地文件路径
334
+ content_type: 内容类型(可选,如 "video/mp4")
335
+
336
+ Returns:
337
+ s3_url: S3 文件地址,可用于调用 Prefab
338
+
339
+ Raises:
340
+ GatewayError: 上传失败
341
+ AgentContextRequiredError: 不会抛出(此方法不需要 agent context)
342
+
343
+ Example:
344
+ ```python
345
+ client = GatewayClient.from_api_key("sk-xxx")
346
+
347
+ # 上传输入文件
348
+ s3_url = client.upload_input_file("/tmp/video.mp4", content_type="video/mp4")
349
+
350
+ # 调用 Prefab
351
+ result = client.run(
352
+ "video-processor",
353
+ "1.0.0",
354
+ "extract_audio",
355
+ files={"video": [s3_url]}
356
+ )
357
+ ```
358
+ """
359
+ import os
360
+ from pathlib import Path
361
+
362
+ if not os.path.exists(file_path):
363
+ raise ValueError(f"File not found: {file_path}")
364
+
365
+ filename = Path(file_path).name
366
+
367
+ # 1. 获取预签名上传 URL
368
+ url = f"{self.base_url}/internal/files/generate_upload_url"
369
+ headers = self._build_headers()
370
+ payload = {
371
+ "filename": filename,
372
+ "content_type": content_type
373
+ }
374
+
375
+ try:
376
+ with httpx.Client(timeout=self.timeout) as client:
377
+ response = client.post(url, json=payload, headers=headers)
378
+ self._handle_error_response(response)
379
+
380
+ data = response.json()
381
+ upload_url = data["upload_url"]
382
+ s3_url = data["s3_url"]
383
+
384
+ # 2. 使用预签名 URL 上传到 S3
385
+ with open(file_path, "rb") as f:
386
+ file_data = f.read()
387
+
388
+ upload_headers = {}
389
+ if content_type:
390
+ upload_headers["Content-Type"] = content_type
391
+
392
+ with httpx.Client(timeout=self.timeout * 2) as client: # 上传超时时间加倍
393
+ upload_response = client.put(upload_url, content=file_data, headers=upload_headers)
394
+ upload_response.raise_for_status()
395
+
396
+ return s3_url
397
+
398
+ except httpx.TimeoutException:
399
+ raise ServiceUnavailableError("上传超时")
400
+ except httpx.RequestError as e:
401
+ raise ServiceUnavailableError(f"上传失败: {str(e)}")
402
+
403
+ def upload_file(self, file_path: str) -> Dict[str, Any]:
404
+ """
405
+ 上传永久文件到 agent-outputs
406
+
407
+ Args:
408
+ file_path: 本地文件路径
409
+
410
+ Returns:
411
+ {
412
+ "success": bool,
413
+ "s3_url": str, # S3 地址
414
+ "filename": str,
415
+ "size": int
416
+ }
417
+
418
+ Raises:
419
+ GatewayError: 上传失败
420
+
421
+ Example:
422
+ result = client.upload_file("/tmp/result.pdf")
423
+ s3_url = result["s3_url"] # s3://bucket/agent-outputs/{user_id}/{agent_id}/...
424
+ """
425
+ import os
426
+
427
+ if not os.path.exists(file_path):
428
+ raise ValueError(f"File not found: {file_path}")
429
+
430
+ url = f"{self.base_url}/internal/files/upload"
431
+ headers = self._build_auth_headers()
432
+
433
+ try:
434
+ with open(file_path, "rb") as f:
435
+ files = {"file": (os.path.basename(file_path), f)}
436
+ with httpx.Client(timeout=self.timeout) as client:
437
+ response = client.post(url, files=files, headers=headers)
438
+ self._handle_error_response(response)
439
+ return response.json()
440
+
441
+ except httpx.TimeoutException:
442
+ raise ServiceUnavailableError("上传超时")
443
+ except httpx.RequestError as e:
444
+ raise ServiceUnavailableError(f"上传失败: {str(e)}")
445
+
446
+ def upload_temp_file(
447
+ self,
448
+ file_path: str,
449
+ ttl: int = 86400,
450
+ session_id: Optional[str] = None
451
+ ) -> Dict[str, Any]:
452
+ """
453
+ 上传临时文件到 agent-workspace(带 TTL)
454
+
455
+ Args:
456
+ file_path: 本地文件路径
457
+ ttl: 生存时间(秒),默认 86400(24 小时)
458
+ session_id: 会话 ID(可选),用于批量管理
459
+
460
+ Returns:
461
+ {
462
+ "success": bool,
463
+ "s3_url": str,
464
+ "filename": str,
465
+ "size": int
466
+ }
467
+
468
+ Raises:
469
+ GatewayError: 上传失败
470
+
471
+ Example:
472
+ # 默认 24 小时后删除
473
+ result = client.upload_temp_file("/tmp/intermediate.jpg")
474
+
475
+ # 1 小时后删除
476
+ result = client.upload_temp_file("/tmp/temp.dat", ttl=3600)
477
+
478
+ # 关联到 session
479
+ session_id = str(uuid.uuid4())
480
+ result = client.upload_temp_file("/tmp/temp.jpg", session_id=session_id)
481
+ """
482
+ import os
483
+
484
+ if not os.path.exists(file_path):
485
+ raise ValueError(f"File not found: {file_path}")
486
+
487
+ url = f"{self.base_url}/internal/files/upload_temp"
488
+ headers = self._build_auth_headers()
489
+
490
+ try:
491
+ with open(file_path, "rb") as f:
492
+ files = {"file": (os.path.basename(file_path), f)}
493
+ data = {"ttl": ttl}
494
+ if session_id:
495
+ data["session_id"] = session_id
496
+
497
+ with httpx.Client(timeout=self.timeout) as client:
498
+ response = client.post(url, files=files, data=data, headers=headers)
499
+ self._handle_error_response(response)
500
+ return response.json()
501
+
502
+ except httpx.TimeoutException:
503
+ raise ServiceUnavailableError("上传超时")
504
+ except httpx.RequestError as e:
505
+ raise ServiceUnavailableError(f"上传失败: {str(e)}")
506
+
507
+ def download_file(
508
+ self,
509
+ s3_url: str,
510
+ local_path: str,
511
+ mode: str = "presigned"
512
+ ) -> None:
513
+ """
514
+ 下载文件
515
+
516
+ Args:
517
+ s3_url: S3 文件 URL
518
+ local_path: 本地保存路径
519
+ mode: 下载模式("presigned" 推荐,"stream" 暂不支持)
520
+
521
+ Raises:
522
+ GatewayError: 下载失败
523
+
524
+ Example:
525
+ client.download_file("s3://bucket/agent-outputs/...", "/tmp/result.pdf")
526
+ """
527
+ import os
528
+
529
+ if mode != "presigned":
530
+ raise ValueError("目前仅支持 presigned 模式")
531
+
532
+ # 获取预签名 URL
533
+ presigned_url = self.get_presigned_url(s3_url)
534
+
535
+ try:
536
+ # 直接从 S3 下载
537
+ with httpx.Client(timeout=self.timeout) as client:
538
+ response = client.get(presigned_url)
539
+ response.raise_for_status()
540
+
541
+ # 保存到本地
542
+ os.makedirs(os.path.dirname(local_path), exist_ok=True)
543
+ with open(local_path, "wb") as f:
544
+ f.write(response.content)
545
+
546
+ except httpx.TimeoutException:
547
+ raise ServiceUnavailableError("下载超时")
548
+ except httpx.RequestError as e:
549
+ raise ServiceUnavailableError(f"下载失败: {str(e)}")
550
+
551
+ def get_presigned_url(
552
+ self,
553
+ s3_url: str,
554
+ expires_in: int = 3600
555
+ ) -> str:
556
+ """
557
+ 获取预签名 URL(用于直接下载)
558
+
559
+ Args:
560
+ s3_url: S3 文件 URL
561
+ expires_in: 有效期(秒),默认 3600(1 小时)
562
+
563
+ Returns:
564
+ 预签名 URL(HTTPS)
565
+
566
+ Raises:
567
+ GatewayError: 获取失败
568
+
569
+ Example:
570
+ url = client.get_presigned_url("s3://bucket/agent-outputs/...")
571
+ # 可以直接用浏览器访问这个 URL 下载文件
572
+ """
573
+ url = f"{self.base_url}/internal/files/download"
574
+ headers = self._build_auth_headers()
575
+ params = {
576
+ "s3_url": s3_url,
577
+ "mode": "presigned",
578
+ "expires_in": expires_in
579
+ }
580
+
581
+ try:
582
+ with httpx.Client(timeout=self.timeout) as client:
583
+ response = client.get(url, params=params, headers=headers)
584
+ self._handle_error_response(response)
585
+ data = response.json()
586
+ return data["presigned_url"]
587
+
588
+ except httpx.TimeoutException:
589
+ raise ServiceUnavailableError("请求超时")
590
+ except httpx.RequestError as e:
591
+ raise ServiceUnavailableError(f"请求失败: {str(e)}")
592
+
593
+ def list_files(
594
+ self,
595
+ limit: int = 100,
596
+ continuation_token: Optional[str] = None
597
+ ) -> Dict[str, Any]:
598
+ """
599
+ 列出永久文件(agent-outputs)
600
+
601
+ Args:
602
+ limit: 最大返回数量,默认 100
603
+ continuation_token: 分页 token(可选)
604
+
605
+ Returns:
606
+ {
607
+ "files": [
608
+ {
609
+ "key": str,
610
+ "size": int,
611
+ "last_modified": str,
612
+ "s3_url": str
613
+ }
614
+ ],
615
+ "next_token": str (optional)
616
+ }
617
+
618
+ Raises:
619
+ GatewayError: 获取失败
620
+
621
+ Example:
622
+ result = client.list_files(limit=50)
623
+ for file in result["files"]:
624
+ print(file["s3_url"])
625
+
626
+ # 翻页
627
+ if "next_token" in result:
628
+ next_page = client.list_files(limit=50, continuation_token=result["next_token"])
629
+ """
630
+ url = f"{self.base_url}/internal/files/list"
631
+ headers = self._build_auth_headers()
632
+ params = {"limit": limit}
633
+ if continuation_token:
634
+ params["continuation_token"] = continuation_token
635
+
636
+ try:
637
+ with httpx.Client(timeout=self.timeout) as client:
638
+ response = client.get(url, params=params, headers=headers)
639
+ self._handle_error_response(response)
640
+ return response.json()
641
+
642
+ except httpx.TimeoutException:
643
+ raise ServiceUnavailableError("请求超时")
644
+ except httpx.RequestError as e:
645
+ raise ServiceUnavailableError(f"请求失败: {str(e)}")
646
+
647
+ def list_temp_files(
648
+ self,
649
+ session_id: Optional[str] = None,
650
+ limit: int = 100,
651
+ continuation_token: Optional[str] = None
652
+ ) -> Dict[str, Any]:
653
+ """
654
+ 列出临时文件(agent-workspace)
655
+
656
+ Args:
657
+ session_id: 会话 ID(可选),不指定则列出所有临时文件
658
+ limit: 最大返回数量,默认 100
659
+ continuation_token: 分页 token(可选)
660
+
661
+ Returns:
662
+ 同 list_files()
663
+
664
+ Raises:
665
+ GatewayError: 获取失败
666
+
667
+ Example:
668
+ # 列出所有临时文件
669
+ result = client.list_temp_files()
670
+
671
+ # 列出指定 session 的临时文件
672
+ result = client.list_temp_files(session_id="abc123")
673
+ """
674
+ url = f"{self.base_url}/internal/files/list_temp"
675
+ headers = self._build_auth_headers()
676
+ params = {"limit": limit}
677
+ if session_id:
678
+ params["session_id"] = session_id
679
+ if continuation_token:
680
+ params["continuation_token"] = continuation_token
681
+
682
+ try:
683
+ with httpx.Client(timeout=self.timeout) as client:
684
+ response = client.get(url, params=params, headers=headers)
685
+ self._handle_error_response(response)
686
+ return response.json()
687
+
688
+ except httpx.TimeoutException:
689
+ raise ServiceUnavailableError("请求超时")
690
+ except httpx.RequestError as e:
691
+ raise ServiceUnavailableError(f"请求失败: {str(e)}")
692
+
693
+ def cleanup_temp_files(self, session_id: str) -> int:
694
+ """
695
+ 立即清理指定 session 的所有临时文件
696
+
697
+ Args:
698
+ session_id: 会话 ID
699
+
700
+ Returns:
701
+ 删除的文件数量
702
+
703
+ Raises:
704
+ GatewayError: 清理失败
705
+
706
+ Example:
707
+ # Agent 任务完成后立即清理
708
+ count = client.cleanup_temp_files(session_id="abc123")
709
+ print(f"Cleaned up {count} temporary files")
710
+ """
711
+ url = f"{self.base_url}/internal/files/cleanup_temp"
712
+ headers = self._build_auth_headers()
713
+ params = {"session_id": session_id}
714
+
715
+ try:
716
+ with httpx.Client(timeout=self.timeout) as client:
717
+ response = client.delete(url, params=params, headers=headers)
718
+ self._handle_error_response(response)
719
+ data = response.json()
720
+ return data["deleted_count"]
721
+
722
+ except httpx.TimeoutException:
723
+ raise ServiceUnavailableError("请求超时")
724
+ except httpx.RequestError as e:
725
+ raise ServiceUnavailableError(f"请求失败: {str(e)}")
726
+