deepflow-openagent-cli 0.1.6__tar.gz → 0.1.7__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.
- {deepflow_openagent_cli-0.1.6 → deepflow_openagent_cli-0.1.7}/PKG-INFO +28 -3
- {deepflow_openagent_cli-0.1.6 → deepflow_openagent_cli-0.1.7}/README.md +27 -2
- {deepflow_openagent_cli-0.1.6 → deepflow_openagent_cli-0.1.7}/pyproject.toml +1 -1
- {deepflow_openagent_cli-0.1.6 → deepflow_openagent_cli-0.1.7}/src/deepflow_openagent_cli/__init__.py +1 -1
- {deepflow_openagent_cli-0.1.6 → deepflow_openagent_cli-0.1.7}/src/deepflow_openagent_cli/cli.py +68 -0
- {deepflow_openagent_cli-0.1.6 → deepflow_openagent_cli-0.1.7}/src/deepflow_openagent_cli.egg-info/PKG-INFO +28 -3
- {deepflow_openagent_cli-0.1.6 → deepflow_openagent_cli-0.1.7}/tests/test_cloudpc_cli.py +80 -0
- {deepflow_openagent_cli-0.1.6 → deepflow_openagent_cli-0.1.7}/setup.cfg +0 -0
- {deepflow_openagent_cli-0.1.6 → deepflow_openagent_cli-0.1.7}/src/deepflow_openagent_cli/__main__.py +0 -0
- {deepflow_openagent_cli-0.1.6 → deepflow_openagent_cli-0.1.7}/src/deepflow_openagent_cli.egg-info/SOURCES.txt +0 -0
- {deepflow_openagent_cli-0.1.6 → deepflow_openagent_cli-0.1.7}/src/deepflow_openagent_cli.egg-info/dependency_links.txt +0 -0
- {deepflow_openagent_cli-0.1.6 → deepflow_openagent_cli-0.1.7}/src/deepflow_openagent_cli.egg-info/entry_points.txt +0 -0
- {deepflow_openagent_cli-0.1.6 → deepflow_openagent_cli-0.1.7}/src/deepflow_openagent_cli.egg-info/requires.txt +0 -0
- {deepflow_openagent_cli-0.1.6 → deepflow_openagent_cli-0.1.7}/src/deepflow_openagent_cli.egg-info/top_level.txt +0 -0
- {deepflow_openagent_cli-0.1.6 → deepflow_openagent_cli-0.1.7}/tests/test_cli.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: deepflow-openagent-cli
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.7
|
|
4
4
|
Summary: Command-line client for OpenAgent OpenAPI (agents, conversations, SSE chat, tools, channels, knowledge bases).
|
|
5
5
|
Author: DeepFlow AI
|
|
6
6
|
Project-URL: Repository, https://github.com/DeepFlowAI/OpenAgent
|
|
@@ -158,6 +158,30 @@ selected version does not exist; use `--from base` for a fresh environment.
|
|
|
158
158
|
Version ID is different from the `x.y.z` version label. First save automatically
|
|
159
159
|
publishes; later saves require a separate `versions publish` command.
|
|
160
160
|
|
|
161
|
+
Binary uploads (including PDF) use the same sandbox upload writer as the workbench:
|
|
162
|
+
|
|
163
|
+
```bash
|
|
164
|
+
deepflow_openagent_cli cloudpc files upload 123 --file ./report.pdf
|
|
165
|
+
# After retry exhaustion, reuse BOTH printed IDs and the same local file:
|
|
166
|
+
deepflow_openagent_cli cloudpc files upload 123 --file ./report.pdf \
|
|
167
|
+
--upload-id UPLOAD_ID --sandbox-id SANDBOX_ID
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Uploads accept 1 byte–50 MiB and create `/workspace/files/uploads` automatically.
|
|
171
|
+
The returned path is `/workspace/files/uploads/<upload_id>-<original_filename>`;
|
|
172
|
+
use that path in subsequent OCR or chat commands. The ID prefix prevents duplicate
|
|
173
|
+
files after a lost response. Reusing the same ID/name with different bytes is
|
|
174
|
+
rejected. The sandbox ID pins retries to the original development environment.
|
|
175
|
+
This admin/config API accepts binary content; channel visitor format restrictions
|
|
176
|
+
still apply only to visitor uploads. Uploading does not automatically start OCR.
|
|
177
|
+
|
|
178
|
+
Uploads and `dev exec` submission/status/stop retry connection failures and HTTP
|
|
179
|
+
408/429/502/503/504 up to four times (1/2/4/8 second delays). Execution retries keep
|
|
180
|
+
the same execution ID and output cursor. Upload retries resend the whole binary
|
|
181
|
+
body; chunk resume is not implemented. Other mutating commands are not retried.
|
|
182
|
+
Requires updated CLI, API server and Cloud PC control plane; no VM image rebuild
|
|
183
|
+
is needed for the upload writer.
|
|
184
|
+
|
|
161
185
|
Additional operations:
|
|
162
186
|
|
|
163
187
|
```bash
|
|
@@ -176,8 +200,9 @@ Config update replaces configuration: provide `model_name`, `thinking_enabled`
|
|
|
176
200
|
and the complete `env_vars` list; omitted thinking/env fields reset to defaults.
|
|
177
201
|
Config queries include environment variable values. File mutation and snapshot
|
|
178
202
|
operations share the per-agent management lock; direct writes by the sandbox's
|
|
179
|
-
Agent or terminal are outside that lock.
|
|
180
|
-
timeout and
|
|
203
|
+
Agent or terminal are outside that lock. Upload and execution requests have a
|
|
204
|
+
120-second per-attempt timeout and the retries described above. Other Cloud PC
|
|
205
|
+
HTTP requests have a 900-second timeout and are not automatically retried. After a timeout, inspect sandbox or
|
|
181
206
|
version state before retrying a mutation. Conversation logs and chat-control
|
|
182
207
|
routes still require an administrator session.
|
|
183
208
|
|
|
@@ -137,6 +137,30 @@ selected version does not exist; use `--from base` for a fresh environment.
|
|
|
137
137
|
Version ID is different from the `x.y.z` version label. First save automatically
|
|
138
138
|
publishes; later saves require a separate `versions publish` command.
|
|
139
139
|
|
|
140
|
+
Binary uploads (including PDF) use the same sandbox upload writer as the workbench:
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
deepflow_openagent_cli cloudpc files upload 123 --file ./report.pdf
|
|
144
|
+
# After retry exhaustion, reuse BOTH printed IDs and the same local file:
|
|
145
|
+
deepflow_openagent_cli cloudpc files upload 123 --file ./report.pdf \
|
|
146
|
+
--upload-id UPLOAD_ID --sandbox-id SANDBOX_ID
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Uploads accept 1 byte–50 MiB and create `/workspace/files/uploads` automatically.
|
|
150
|
+
The returned path is `/workspace/files/uploads/<upload_id>-<original_filename>`;
|
|
151
|
+
use that path in subsequent OCR or chat commands. The ID prefix prevents duplicate
|
|
152
|
+
files after a lost response. Reusing the same ID/name with different bytes is
|
|
153
|
+
rejected. The sandbox ID pins retries to the original development environment.
|
|
154
|
+
This admin/config API accepts binary content; channel visitor format restrictions
|
|
155
|
+
still apply only to visitor uploads. Uploading does not automatically start OCR.
|
|
156
|
+
|
|
157
|
+
Uploads and `dev exec` submission/status/stop retry connection failures and HTTP
|
|
158
|
+
408/429/502/503/504 up to four times (1/2/4/8 second delays). Execution retries keep
|
|
159
|
+
the same execution ID and output cursor. Upload retries resend the whole binary
|
|
160
|
+
body; chunk resume is not implemented. Other mutating commands are not retried.
|
|
161
|
+
Requires updated CLI, API server and Cloud PC control plane; no VM image rebuild
|
|
162
|
+
is needed for the upload writer.
|
|
163
|
+
|
|
140
164
|
Additional operations:
|
|
141
165
|
|
|
142
166
|
```bash
|
|
@@ -155,8 +179,9 @@ Config update replaces configuration: provide `model_name`, `thinking_enabled`
|
|
|
155
179
|
and the complete `env_vars` list; omitted thinking/env fields reset to defaults.
|
|
156
180
|
Config queries include environment variable values. File mutation and snapshot
|
|
157
181
|
operations share the per-agent management lock; direct writes by the sandbox's
|
|
158
|
-
Agent or terminal are outside that lock.
|
|
159
|
-
timeout and
|
|
182
|
+
Agent or terminal are outside that lock. Upload and execution requests have a
|
|
183
|
+
120-second per-attempt timeout and the retries described above. Other Cloud PC
|
|
184
|
+
HTTP requests have a 900-second timeout and are not automatically retried. After a timeout, inspect sandbox or
|
|
160
185
|
version state before retrying a mutation. Conversation logs and chat-control
|
|
161
186
|
routes still require an administrator session.
|
|
162
187
|
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "deepflow-openagent-cli"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.7"
|
|
8
8
|
description = "Command-line client for OpenAgent OpenAPI (agents, conversations, SSE chat, tools, channels, knowledge bases)."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.11"
|
{deepflow_openagent_cli-0.1.6 → deepflow_openagent_cli-0.1.7}/src/deepflow_openagent_cli/cli.py
RENAMED
|
@@ -8,6 +8,7 @@ Run with:
|
|
|
8
8
|
from __future__ import annotations
|
|
9
9
|
|
|
10
10
|
import argparse
|
|
11
|
+
from http.client import HTTPException
|
|
11
12
|
import json
|
|
12
13
|
import mimetypes
|
|
13
14
|
import os
|
|
@@ -129,6 +130,18 @@ def _param(name: str, type_: str, required: bool, description: str) -> ParamDoc:
|
|
|
129
130
|
|
|
130
131
|
|
|
131
132
|
ENDPOINTS: tuple[EndpointDoc, ...] = (
|
|
133
|
+
EndpointDoc(
|
|
134
|
+
key="cloudpc.files.upload", method="POST",
|
|
135
|
+
path="/api/v1/cloudpc/agents/{agent_id}/dev-sandbox/files/upload", scope="config",
|
|
136
|
+
summary="Upload binary content (1 byte–50 MiB) into /workspace/files/uploads; safe to retry with the same upload ID and sandbox ID.",
|
|
137
|
+
path_params=[_param("agent_id", "integer", True, "Cloud PC Agent ID.")],
|
|
138
|
+
query_params=[_param("filename", "string", True, "Original basename."),
|
|
139
|
+
_param("upload_id", "string", True, "32 lowercase hex characters; prefixes the saved filename."),
|
|
140
|
+
_param("sandbox_id", "integer", True, "Pinned development sandbox ID from dev get.")],
|
|
141
|
+
response="Saved filename, absolute path and size.",
|
|
142
|
+
example="deepflow_openagent_cli cloudpc files upload 1 --file ./report.pdf",
|
|
143
|
+
),
|
|
144
|
+
|
|
132
145
|
EndpointDoc(
|
|
133
146
|
key="cloudpc.dev.exec", method="POST", path="/api/v1/cloudpc/agents/{agent_id}/dev-sandbox/executions", scope="config",
|
|
134
147
|
summary='Start an idempotent shell execution. Output remains in the sandbox runtime until restart or eviction (32 retained).',
|
|
@@ -1254,6 +1267,38 @@ class OpenApiClient:
|
|
|
1254
1267
|
except (URLError, OSError, UnicodeError, ValueError) as exc:
|
|
1255
1268
|
raise CliError(f"Chat stream interrupted: {exc}") from exc
|
|
1256
1269
|
|
|
1270
|
+
def _read_retryable(self, request: Request) -> bytes:
|
|
1271
|
+
"""Only for operations whose server contract makes replay safe."""
|
|
1272
|
+
for attempt in range(5):
|
|
1273
|
+
try:
|
|
1274
|
+
with urlopen(request, timeout=120) as response:
|
|
1275
|
+
return response.read()
|
|
1276
|
+
except HTTPError as exc:
|
|
1277
|
+
if exc.code not in {408, 429, 502, 503, 504} or attempt == 4:
|
|
1278
|
+
raise CliError(f"HTTP {exc.code}: {exc.read().decode('utf-8', errors='replace')}") from exc
|
|
1279
|
+
except (URLError, OSError, HTTPException) as exc:
|
|
1280
|
+
if attempt == 4:
|
|
1281
|
+
raise CliError(f"Connection failed after 5 attempts: {exc}") from exc
|
|
1282
|
+
print(f"Connection interrupted; retry {attempt + 1}/4…", file=sys.stderr)
|
|
1283
|
+
time.sleep(2 ** attempt)
|
|
1284
|
+
|
|
1285
|
+
def upload(self, agent_id: int, file: Path, upload_id: str, sandbox_id: int) -> bytes:
|
|
1286
|
+
if not self.api_key:
|
|
1287
|
+
raise CliError(f"Missing API key. Set {ENV_API_KEY} or pass --api-key.")
|
|
1288
|
+
try:
|
|
1289
|
+
with file.open("rb") as source:
|
|
1290
|
+
content = source.read(50 * 1024 * 1024 + 1)
|
|
1291
|
+
except OSError as exc:
|
|
1292
|
+
raise CliError(f"Cannot read upload: {exc}") from exc
|
|
1293
|
+
if not 0 < len(content) <= 50 * 1024 * 1024:
|
|
1294
|
+
raise CliError("Upload must be between 1 byte and 50 MiB.")
|
|
1295
|
+
url = self._build_url(get_endpoint("cloudpc.files.upload"), {"agent_id": agent_id},
|
|
1296
|
+
{"filename": file.name, "upload_id": upload_id, "sandbox_id": sandbox_id})
|
|
1297
|
+
request = Request(url, data=content, method="POST", headers={
|
|
1298
|
+
"Authorization": f"Bearer {self.api_key}", "Content-Type": "application/octet-stream",
|
|
1299
|
+
})
|
|
1300
|
+
return self._read_retryable(request)
|
|
1301
|
+
|
|
1257
1302
|
def request(
|
|
1258
1303
|
self,
|
|
1259
1304
|
endpoint_key: str,
|
|
@@ -1262,6 +1307,7 @@ class OpenApiClient:
|
|
|
1262
1307
|
body: dict[str, Any] | None = None,
|
|
1263
1308
|
*,
|
|
1264
1309
|
stream: bool = False,
|
|
1310
|
+
retry: bool = False,
|
|
1265
1311
|
) -> bytes | None:
|
|
1266
1312
|
endpoint = get_endpoint(endpoint_key)
|
|
1267
1313
|
if endpoint.scope and not self.api_key:
|
|
@@ -1277,6 +1323,8 @@ class OpenApiClient:
|
|
|
1277
1323
|
headers["Content-Type"] = "application/json"
|
|
1278
1324
|
|
|
1279
1325
|
request = Request(url, data=data, headers=headers, method=endpoint.method)
|
|
1326
|
+
if not stream and (retry or endpoint_key in {"cloudpc.dev.exec", "cloudpc.dev.exec_status", "cloudpc.dev.exec_cancel"}):
|
|
1327
|
+
return self._read_retryable(request)
|
|
1280
1328
|
try:
|
|
1281
1329
|
options = {"timeout": 900} if endpoint_key.startswith("cloudpc.") else {}
|
|
1282
1330
|
with urlopen(request, **options) as response: # noqa: S310 - CLI calls user-provided API host.
|
|
@@ -1919,6 +1967,21 @@ def handle_cloudpc(args: argparse.Namespace) -> int:
|
|
|
1919
1967
|
raise CliError("Version ID must be positive.")
|
|
1920
1968
|
body = {"from_version_id": version_id}
|
|
1921
1969
|
elif group == "files":
|
|
1970
|
+
if action == "upload":
|
|
1971
|
+
if bool(args.upload_id) != (args.sandbox_id is not None):
|
|
1972
|
+
raise CliError("Use --upload-id and --sandbox-id together when resuming an upload.")
|
|
1973
|
+
client = client_from_args(args)
|
|
1974
|
+
upload_id = args.upload_id or uuid4().hex
|
|
1975
|
+
sandbox_id = args.sandbox_id
|
|
1976
|
+
if sandbox_id is None:
|
|
1977
|
+
dev = json.loads(client.request("cloudpc.dev.get", path_params, retry=True))
|
|
1978
|
+
sandbox_id = dev.get("sandbox_id")
|
|
1979
|
+
if sandbox_id is None:
|
|
1980
|
+
raise CliError("No upload-capable development sandbox; create/resume it and update the server.")
|
|
1981
|
+
print(f"upload_id={upload_id} sandbox_id={sandbox_id}; reuse both IDs to retry this upload.", file=sys.stderr)
|
|
1982
|
+
data = client.upload(args.agent_id, Path(args.file).expanduser(), upload_id, sandbox_id)
|
|
1983
|
+
print_output(data, pretty=args.pretty, raw=args.raw)
|
|
1984
|
+
return 0
|
|
1922
1985
|
if action in {"read", "delete"}:
|
|
1923
1986
|
query = {"path": args.path}
|
|
1924
1987
|
elif action == "write":
|
|
@@ -2129,6 +2192,11 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
2129
2192
|
elif action == "rename":
|
|
2130
2193
|
command.add_argument("--src", required=True)
|
|
2131
2194
|
command.add_argument("--dst", required=True)
|
|
2195
|
+
upload = cloudpc_files.add_parser("upload", help="Upload binary files to /workspace/files/uploads (max 50 MiB).")
|
|
2196
|
+
upload.add_argument("agent_id", type=int)
|
|
2197
|
+
upload.add_argument("--file", required=True)
|
|
2198
|
+
upload.add_argument("--upload-id", help="Reuse the printed ID when retrying an interrupted upload.")
|
|
2199
|
+
upload.add_argument("--sandbox-id", type=int, help="Reuse the printed sandbox ID on manual retry.")
|
|
2132
2200
|
cloudpc_versions = cloudpc_sub.add_parser("versions").add_subparsers(dest="cloudpc_action", required=True)
|
|
2133
2201
|
for action in ("list", "save", "publish"):
|
|
2134
2202
|
command = cloudpc_versions.add_parser(action)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: deepflow-openagent-cli
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.7
|
|
4
4
|
Summary: Command-line client for OpenAgent OpenAPI (agents, conversations, SSE chat, tools, channels, knowledge bases).
|
|
5
5
|
Author: DeepFlow AI
|
|
6
6
|
Project-URL: Repository, https://github.com/DeepFlowAI/OpenAgent
|
|
@@ -158,6 +158,30 @@ selected version does not exist; use `--from base` for a fresh environment.
|
|
|
158
158
|
Version ID is different from the `x.y.z` version label. First save automatically
|
|
159
159
|
publishes; later saves require a separate `versions publish` command.
|
|
160
160
|
|
|
161
|
+
Binary uploads (including PDF) use the same sandbox upload writer as the workbench:
|
|
162
|
+
|
|
163
|
+
```bash
|
|
164
|
+
deepflow_openagent_cli cloudpc files upload 123 --file ./report.pdf
|
|
165
|
+
# After retry exhaustion, reuse BOTH printed IDs and the same local file:
|
|
166
|
+
deepflow_openagent_cli cloudpc files upload 123 --file ./report.pdf \
|
|
167
|
+
--upload-id UPLOAD_ID --sandbox-id SANDBOX_ID
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Uploads accept 1 byte–50 MiB and create `/workspace/files/uploads` automatically.
|
|
171
|
+
The returned path is `/workspace/files/uploads/<upload_id>-<original_filename>`;
|
|
172
|
+
use that path in subsequent OCR or chat commands. The ID prefix prevents duplicate
|
|
173
|
+
files after a lost response. Reusing the same ID/name with different bytes is
|
|
174
|
+
rejected. The sandbox ID pins retries to the original development environment.
|
|
175
|
+
This admin/config API accepts binary content; channel visitor format restrictions
|
|
176
|
+
still apply only to visitor uploads. Uploading does not automatically start OCR.
|
|
177
|
+
|
|
178
|
+
Uploads and `dev exec` submission/status/stop retry connection failures and HTTP
|
|
179
|
+
408/429/502/503/504 up to four times (1/2/4/8 second delays). Execution retries keep
|
|
180
|
+
the same execution ID and output cursor. Upload retries resend the whole binary
|
|
181
|
+
body; chunk resume is not implemented. Other mutating commands are not retried.
|
|
182
|
+
Requires updated CLI, API server and Cloud PC control plane; no VM image rebuild
|
|
183
|
+
is needed for the upload writer.
|
|
184
|
+
|
|
161
185
|
Additional operations:
|
|
162
186
|
|
|
163
187
|
```bash
|
|
@@ -176,8 +200,9 @@ Config update replaces configuration: provide `model_name`, `thinking_enabled`
|
|
|
176
200
|
and the complete `env_vars` list; omitted thinking/env fields reset to defaults.
|
|
177
201
|
Config queries include environment variable values. File mutation and snapshot
|
|
178
202
|
operations share the per-agent management lock; direct writes by the sandbox's
|
|
179
|
-
Agent or terminal are outside that lock.
|
|
180
|
-
timeout and
|
|
203
|
+
Agent or terminal are outside that lock. Upload and execution requests have a
|
|
204
|
+
120-second per-attempt timeout and the retries described above. Other Cloud PC
|
|
205
|
+
HTTP requests have a 900-second timeout and are not automatically retried. After a timeout, inspect sandbox or
|
|
181
206
|
version state before retrying a mutation. Conversation logs and chat-control
|
|
182
207
|
routes still require an administrator session.
|
|
183
208
|
|
|
@@ -254,3 +254,83 @@ def test_dev_chat_requires_message_or_existing_round(cli, transport):
|
|
|
254
254
|
assert run(cli, 'cloudpc', 'dev', 'chat', '7') == 1
|
|
255
255
|
assert run(cli, 'cloudpc', 'dev', 'chat', '7', '--attach') == 1
|
|
256
256
|
assert transport[0] == []
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def test_binary_upload_replays_exact_body_after_ssl_eof(cli, monkeypatch, tmp_path, capsys):
|
|
260
|
+
import ssl
|
|
261
|
+
source = tmp_path / '报告.pdf'
|
|
262
|
+
content = b'%PDF-1.7\n\x00\xff' + b'x' * (2 * 1024 * 1024)
|
|
263
|
+
source.write_bytes(content)
|
|
264
|
+
calls = []
|
|
265
|
+
def send(request, **kwargs):
|
|
266
|
+
calls.append(request)
|
|
267
|
+
if request.method == 'GET':
|
|
268
|
+
return io.BytesIO(b'{"sandbox_id":42,"exists":true}')
|
|
269
|
+
if len(calls) == 2:
|
|
270
|
+
raise ssl.SSLEOFError('UNEXPECTED_EOF_WHILE_READING')
|
|
271
|
+
return io.BytesIO(b'{"path":"/workspace/files/uploads/saved.pdf"}')
|
|
272
|
+
monkeypatch.setattr(cli, 'urlopen', send)
|
|
273
|
+
monkeypatch.setattr(cli.time, 'sleep', lambda _: None)
|
|
274
|
+
assert run(cli, 'cloudpc', 'files', 'upload', '7', '--file', str(source)) == 0
|
|
275
|
+
assert calls[1].data == calls[2].data == content
|
|
276
|
+
assert calls[1].full_url == calls[2].full_url
|
|
277
|
+
assert 'sandbox_id=42' in calls[1].full_url
|
|
278
|
+
assert 'upload_id=' in capsys.readouterr().err
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
def test_exec_retries_same_submission_and_cursor(cli, monkeypatch, capsys):
|
|
282
|
+
from http.client import IncompleteRead
|
|
283
|
+
calls = []
|
|
284
|
+
def send(request, **kwargs):
|
|
285
|
+
calls.append(request)
|
|
286
|
+
if len(calls) in {1, 3}:
|
|
287
|
+
raise IncompleteRead(b'partial')
|
|
288
|
+
if request.method == 'POST':
|
|
289
|
+
return io.BytesIO(b'{}')
|
|
290
|
+
return io.BytesIO(b'{"events":[{"event":{"type":"stdout","text":"once"}}],"cursor":1,"state":"exited","exit_code":0}')
|
|
291
|
+
monkeypatch.setattr(cli, 'urlopen', send)
|
|
292
|
+
monkeypatch.setattr(cli.time, 'sleep', lambda _: None)
|
|
293
|
+
assert run(cli, 'cloudpc', 'dev', 'exec', '7', '--command', 'echo once', '--execution-id', 'job') == 0
|
|
294
|
+
assert calls[0].data == calls[1].data
|
|
295
|
+
assert calls[2].full_url == calls[3].full_url
|
|
296
|
+
assert capsys.readouterr().out == 'once'
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
@pytest.mark.parametrize('code', [400, 401, 403, 409, 413])
|
|
300
|
+
def test_upload_permanent_errors_not_retried(cli, monkeypatch, tmp_path, code):
|
|
301
|
+
file = tmp_path / 'x.pdf'
|
|
302
|
+
file.write_bytes(b'\xff\x00')
|
|
303
|
+
calls = []
|
|
304
|
+
def send(request, **kwargs):
|
|
305
|
+
calls.append(request)
|
|
306
|
+
raise HTTPError(request.full_url, code, 'rejected', {}, io.BytesIO(b'rejected'))
|
|
307
|
+
monkeypatch.setattr(cli, 'urlopen', send)
|
|
308
|
+
assert run(cli, 'cloudpc', 'files', 'upload', '7', '--file', str(file), '--sandbox-id', '42', '--upload-id', 'a' * 32) == 1
|
|
309
|
+
assert len(calls) == 1
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
def test_exec_retry_exhaustion_preserves_attach_hint(cli, monkeypatch, capsys):
|
|
313
|
+
calls = []
|
|
314
|
+
def send(request, **kwargs):
|
|
315
|
+
calls.append(request)
|
|
316
|
+
raise ConnectionResetError('reset')
|
|
317
|
+
monkeypatch.setattr(cli, 'urlopen', send)
|
|
318
|
+
monkeypatch.setattr(cli.time, 'sleep', lambda _: None)
|
|
319
|
+
assert run(cli, 'cloudpc', 'dev', 'exec-attach', '7', 'job', '--after', '8') == 1
|
|
320
|
+
assert len(calls) == 5
|
|
321
|
+
assert all('after=8' in request.full_url for request in calls)
|
|
322
|
+
assert 'exec-attach 7 job --after 8' in capsys.readouterr().err
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
@pytest.mark.parametrize('size', [0, 50 * 1024 * 1024 + 1])
|
|
326
|
+
def test_upload_size_rejected_before_send(cli, transport, tmp_path, size):
|
|
327
|
+
source = tmp_path / 'x.pdf'
|
|
328
|
+
with source.open('wb') as file:
|
|
329
|
+
file.truncate(size)
|
|
330
|
+
assert run(cli, 'cloudpc', 'files', 'upload', '7', '--file', str(source), '--upload-id', 'a' * 32, '--sandbox-id', '42') == 1
|
|
331
|
+
assert transport[0] == []
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
def test_upload_manual_retry_requires_both_ids(cli, transport):
|
|
335
|
+
assert run(cli, 'cloudpc', 'files', 'upload', '7', '--file', 'x.pdf', '--upload-id', 'a' * 32) == 1
|
|
336
|
+
assert transport[0] == []
|
|
File without changes
|
{deepflow_openagent_cli-0.1.6 → deepflow_openagent_cli-0.1.7}/src/deepflow_openagent_cli/__main__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|