@josephyan/qingflow-cli 0.2.0-beta.70 → 0.2.0-beta.71
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.
package/README.md
CHANGED
|
@@ -3,13 +3,13 @@
|
|
|
3
3
|
Install:
|
|
4
4
|
|
|
5
5
|
```bash
|
|
6
|
-
npm install @josephyan/qingflow-cli@0.2.0-beta.
|
|
6
|
+
npm install @josephyan/qingflow-cli@0.2.0-beta.71
|
|
7
7
|
```
|
|
8
8
|
|
|
9
9
|
Run:
|
|
10
10
|
|
|
11
11
|
```bash
|
|
12
|
-
npx -y -p @josephyan/qingflow-cli@0.2.0-beta.
|
|
12
|
+
npx -y -p @josephyan/qingflow-cli@0.2.0-beta.71 qingflow
|
|
13
13
|
```
|
|
14
14
|
|
|
15
15
|
Environment:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@josephyan/qingflow-cli",
|
|
3
|
-
"version": "0.2.0-beta.
|
|
3
|
+
"version": "0.2.0-beta.71",
|
|
4
4
|
"description": "Human-friendly Qingflow command line interface for auth, record operations, import, tasks, and stable builder flows.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
package/pyproject.toml
CHANGED
|
@@ -2505,9 +2505,14 @@ class AiBuilderFacade:
|
|
|
2505
2505
|
page_size_y: int | None = None,
|
|
2506
2506
|
) -> JSONObject:
|
|
2507
2507
|
normalized_payload = deepcopy(data_payload) if isinstance(data_payload, dict) else {}
|
|
2508
|
+
warnings: list[dict[str, Any]] = []
|
|
2509
|
+
verification = {
|
|
2510
|
+
"chart_exists": True,
|
|
2511
|
+
"chart_data_loaded": True,
|
|
2512
|
+
"chart_config_loaded": True,
|
|
2513
|
+
}
|
|
2508
2514
|
try:
|
|
2509
2515
|
base = self.charts.qingbi_report_get_base(profile=profile, chart_id=chart_id).get("result") or {}
|
|
2510
|
-
config = self.charts.qingbi_report_get_config(profile=profile, chart_id=chart_id).get("result") or {}
|
|
2511
2516
|
data = self.charts.qingbi_report_get_data(
|
|
2512
2517
|
profile=profile,
|
|
2513
2518
|
chart_id=chart_id,
|
|
@@ -2534,6 +2539,36 @@ class AiBuilderFacade:
|
|
|
2534
2539
|
suggested_next_call={"tool_name": "chart_get", "arguments": {"profile": profile, "chart_id": chart_id}},
|
|
2535
2540
|
)
|
|
2536
2541
|
|
|
2542
|
+
try:
|
|
2543
|
+
config = self.charts.qingbi_report_get_config(profile=profile, chart_id=chart_id).get("result") or {}
|
|
2544
|
+
except (QingflowApiError, RuntimeError) as error:
|
|
2545
|
+
config_from_data = data.get("config") if isinstance(data, dict) else None
|
|
2546
|
+
if isinstance(config_from_data, dict):
|
|
2547
|
+
config = deepcopy(config_from_data)
|
|
2548
|
+
verification["chart_config_loaded"] = True
|
|
2549
|
+
warnings.append(
|
|
2550
|
+
_warning(
|
|
2551
|
+
"CHART_CONFIG_FALLBACK_FROM_DATA",
|
|
2552
|
+
"chart config endpoint is unavailable for this chart id; using config embedded in chart data instead",
|
|
2553
|
+
)
|
|
2554
|
+
)
|
|
2555
|
+
else:
|
|
2556
|
+
api_error = _coerce_api_error(error)
|
|
2557
|
+
return _failed_from_api_error(
|
|
2558
|
+
"CHART_GET_FAILED",
|
|
2559
|
+
api_error,
|
|
2560
|
+
normalized_args={
|
|
2561
|
+
"chart_id": chart_id,
|
|
2562
|
+
"data_payload": normalized_payload,
|
|
2563
|
+
"page_num": page_num,
|
|
2564
|
+
"page_size": page_size,
|
|
2565
|
+
"page_num_y": page_num_y,
|
|
2566
|
+
"page_size_y": page_size_y,
|
|
2567
|
+
},
|
|
2568
|
+
details={"chart_id": chart_id},
|
|
2569
|
+
suggested_next_call={"tool_name": "chart_get", "arguments": {"profile": profile, "chart_id": chart_id}},
|
|
2570
|
+
)
|
|
2571
|
+
|
|
2537
2572
|
response = ChartGetResponse(
|
|
2538
2573
|
chart_id=chart_id,
|
|
2539
2574
|
base=deepcopy(base) if isinstance(base, dict) else {},
|
|
@@ -2559,8 +2594,8 @@ class AiBuilderFacade:
|
|
|
2559
2594
|
"request_id": None,
|
|
2560
2595
|
"suggested_next_call": None,
|
|
2561
2596
|
"noop": False,
|
|
2562
|
-
"warnings":
|
|
2563
|
-
"verification":
|
|
2597
|
+
"warnings": warnings,
|
|
2598
|
+
"verification": verification,
|
|
2564
2599
|
"verified": True,
|
|
2565
2600
|
**response.model_dump(mode="json"),
|
|
2566
2601
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import json
|
|
3
4
|
from uuid import uuid4
|
|
4
5
|
|
|
5
6
|
from mcp.server.fastmcp import FastMCP
|
|
@@ -18,6 +19,37 @@ def _qingbi_base_url(base_url: str) -> str:
|
|
|
18
19
|
return normalized[:-4] if normalized.endswith("/api") else normalized
|
|
19
20
|
|
|
20
21
|
|
|
22
|
+
def _should_retry_qflow_base(error: QingflowApiError) -> bool:
|
|
23
|
+
return int(getattr(error, "backend_code", 0) or 0) == 81007
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _should_retry_asos_data(error: QingflowApiError) -> bool:
|
|
27
|
+
backend_code = int(getattr(error, "backend_code", 0) or 0)
|
|
28
|
+
http_status = getattr(error, "http_status", None)
|
|
29
|
+
return backend_code in {44011, 81007} or http_status == 404
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _coerce_tool_error(error: RuntimeError | QingflowApiError) -> QingflowApiError | None:
|
|
33
|
+
if isinstance(error, QingflowApiError):
|
|
34
|
+
return error
|
|
35
|
+
if not isinstance(error, RuntimeError):
|
|
36
|
+
return None
|
|
37
|
+
try:
|
|
38
|
+
payload = json.loads(str(error))
|
|
39
|
+
except Exception:
|
|
40
|
+
return None
|
|
41
|
+
if not isinstance(payload, dict):
|
|
42
|
+
return None
|
|
43
|
+
return QingflowApiError(
|
|
44
|
+
category=str(payload.get("category") or "runtime"),
|
|
45
|
+
message=str(payload.get("message") or str(error)),
|
|
46
|
+
backend_code=payload.get("backend_code"),
|
|
47
|
+
request_id=payload.get("request_id"),
|
|
48
|
+
http_status=payload.get("http_status"),
|
|
49
|
+
details=payload.get("details") if isinstance(payload.get("details"), dict) else None,
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
|
|
21
53
|
class QingbiReportTools(ToolBase):
|
|
22
54
|
def register(self, mcp: FastMCP) -> None:
|
|
23
55
|
@mcp.tool()
|
|
@@ -127,7 +159,13 @@ class QingbiReportTools(ToolBase):
|
|
|
127
159
|
|
|
128
160
|
def qingbi_report_get_base(self, *, profile: str, chart_id: str) -> JSONObject:
|
|
129
161
|
self._require_chart_id(chart_id)
|
|
130
|
-
|
|
162
|
+
try:
|
|
163
|
+
return self._request(profile, "GET", f"/qingbi/charts/baseinfo/{chart_id}", chart_id=chart_id)
|
|
164
|
+
except (QingflowApiError, RuntimeError) as raw_error:
|
|
165
|
+
error = _coerce_tool_error(raw_error)
|
|
166
|
+
if error is None or not _should_retry_qflow_base(error):
|
|
167
|
+
raise
|
|
168
|
+
return self._request(profile, "GET", f"/qingbi/charts/qflow/baseinfo/{chart_id}", chart_id=chart_id)
|
|
131
169
|
|
|
132
170
|
def qingbi_report_update_base(self, *, profile: str, chart_id: str, payload: JSONObject) -> JSONObject:
|
|
133
171
|
self._require_chart_id(chart_id)
|
|
@@ -166,21 +204,34 @@ class QingbiReportTools(ToolBase):
|
|
|
166
204
|
params["pageNumY"] = page_num_y
|
|
167
205
|
if page_size_y is not None:
|
|
168
206
|
params["pageSizeY"] = page_size_y
|
|
169
|
-
|
|
207
|
+
try:
|
|
208
|
+
if payload:
|
|
209
|
+
return self._request(
|
|
210
|
+
profile,
|
|
211
|
+
"POST",
|
|
212
|
+
f"/qingbi/charts/data/qflow/{chart_id}/detail",
|
|
213
|
+
chart_id=chart_id,
|
|
214
|
+
params=params,
|
|
215
|
+
json_body=payload,
|
|
216
|
+
)
|
|
170
217
|
return self._request(
|
|
171
218
|
profile,
|
|
172
|
-
"
|
|
173
|
-
f"/qingbi/charts/data/qflow/{chart_id}
|
|
219
|
+
"GET",
|
|
220
|
+
f"/qingbi/charts/data/qflow/{chart_id}",
|
|
174
221
|
chart_id=chart_id,
|
|
175
222
|
params=params,
|
|
176
|
-
json_body=payload,
|
|
177
223
|
)
|
|
224
|
+
except (QingflowApiError, RuntimeError) as raw_error:
|
|
225
|
+
error = _coerce_tool_error(raw_error)
|
|
226
|
+
if error is None or not _should_retry_asos_data(error):
|
|
227
|
+
raise
|
|
178
228
|
return self._request(
|
|
179
229
|
profile,
|
|
180
|
-
"
|
|
181
|
-
f"/qingbi/charts/data/qflow/{chart_id}",
|
|
230
|
+
"POST",
|
|
231
|
+
f"/qingbi/charts/data/qflow/{chart_id}/asos",
|
|
182
232
|
chart_id=chart_id,
|
|
183
233
|
params=params,
|
|
234
|
+
json_body=payload or {},
|
|
184
235
|
)
|
|
185
236
|
|
|
186
237
|
def qingbi_report_delete(self, *, profile: str, chart_id: str) -> JSONObject:
|