@josephyan/qingflow-cli 0.2.0-beta.57 → 0.2.0-beta.59

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 (34) hide show
  1. package/README.md +3 -2
  2. package/docs/local-agent-install.md +9 -0
  3. package/npm/bin/qingflow.mjs +1 -1
  4. package/npm/lib/runtime.mjs +156 -21
  5. package/package.json +1 -1
  6. package/pyproject.toml +1 -1
  7. package/src/qingflow_mcp/builder_facade/service.py +137 -5
  8. package/src/qingflow_mcp/cli/commands/app.py +16 -16
  9. package/src/qingflow_mcp/cli/commands/auth.py +19 -16
  10. package/src/qingflow_mcp/cli/commands/builder.py +124 -162
  11. package/src/qingflow_mcp/cli/commands/common.py +21 -95
  12. package/src/qingflow_mcp/cli/commands/imports.py +42 -34
  13. package/src/qingflow_mcp/cli/commands/record.py +131 -133
  14. package/src/qingflow_mcp/cli/commands/task.py +43 -44
  15. package/src/qingflow_mcp/cli/commands/workspace.py +10 -10
  16. package/src/qingflow_mcp/cli/context.py +35 -32
  17. package/src/qingflow_mcp/cli/formatters.py +124 -121
  18. package/src/qingflow_mcp/cli/main.py +52 -17
  19. package/src/qingflow_mcp/server_app_builder.py +122 -190
  20. package/src/qingflow_mcp/server_app_user.py +63 -662
  21. package/src/qingflow_mcp/tools/solution_tools.py +95 -3
  22. package/src/qingflow_mcp/ops/__init__.py +0 -3
  23. package/src/qingflow_mcp/ops/apps.py +0 -64
  24. package/src/qingflow_mcp/ops/auth.py +0 -121
  25. package/src/qingflow_mcp/ops/base.py +0 -290
  26. package/src/qingflow_mcp/ops/builder.py +0 -323
  27. package/src/qingflow_mcp/ops/context.py +0 -120
  28. package/src/qingflow_mcp/ops/directory.py +0 -171
  29. package/src/qingflow_mcp/ops/feedback.py +0 -49
  30. package/src/qingflow_mcp/ops/files.py +0 -78
  31. package/src/qingflow_mcp/ops/imports.py +0 -140
  32. package/src/qingflow_mcp/ops/records.py +0 -415
  33. package/src/qingflow_mcp/ops/tasks.py +0 -171
  34. package/src/qingflow_mcp/ops/workspace.py +0 -76
@@ -1,6 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from copy import deepcopy
4
+ import json
4
5
  from typing import Any
5
6
  from uuid import uuid4
6
7
 
@@ -8,6 +9,7 @@ from mcp.server.fastmcp import FastMCP
8
9
  from pydantic import BaseModel, ValidationError
9
10
 
10
11
  from ..config import DEFAULT_PROFILE
12
+ from ..errors import QingflowApiError
11
13
  from ..list_type_labels import get_record_list_type_label
12
14
  from ..solution.build_assembly_store import BuildAssemblyStore, default_manifest
13
15
  from ..solution.compiler import CompiledSolution, ExecutionPlan, ExecutionStep, build_execution_plan, compile_solution
@@ -1850,7 +1852,15 @@ class SolutionTools(ToolBase):
1850
1852
  packages = PackageTools(self.sessions, self.backend)
1851
1853
  normalized_name = package_name.strip()
1852
1854
  if package_tag_id > 0:
1853
- result = packages.package_get(profile=profile, tag_id=package_tag_id, include_raw=False)
1855
+ try:
1856
+ result = packages.package_get(profile=profile, tag_id=package_tag_id, include_raw=False)
1857
+ except (QingflowApiError, RuntimeError) as exc:
1858
+ return _builder_package_resolution_failed(
1859
+ package_name=normalized_name,
1860
+ package_tag_id=package_tag_id,
1861
+ error=_coerce_solution_api_error(exc),
1862
+ retried=False,
1863
+ )
1854
1864
  summary = result.get("result") if isinstance(result.get("result"), dict) else {}
1855
1865
  return {
1856
1866
  "status": "resolved",
@@ -1869,12 +1879,28 @@ class SolutionTools(ToolBase):
1869
1879
  "source_shape": None,
1870
1880
  "retried": False,
1871
1881
  }
1872
- result = packages.package_list(profile=profile, trial_status="all", include_raw=False)
1882
+ try:
1883
+ result = packages.package_list(profile=profile, trial_status="all", include_raw=False)
1884
+ except (QingflowApiError, RuntimeError) as exc:
1885
+ return _builder_package_resolution_failed(
1886
+ package_name=normalized_name,
1887
+ package_tag_id=package_tag_id,
1888
+ error=_coerce_solution_api_error(exc),
1889
+ retried=False,
1890
+ )
1873
1891
  items = result.get("items") if isinstance(result.get("items"), list) else []
1874
1892
  source_shape = result.get("source_shape")
1875
1893
  retried = False
1876
1894
  if not items:
1877
- retry_result = packages.package_list(profile=profile, trial_status="all", include_raw=False)
1895
+ try:
1896
+ retry_result = packages.package_list(profile=profile, trial_status="all", include_raw=False)
1897
+ except (QingflowApiError, RuntimeError) as exc:
1898
+ return _builder_package_resolution_failed(
1899
+ package_name=normalized_name,
1900
+ package_tag_id=package_tag_id,
1901
+ error=_coerce_solution_api_error(exc),
1902
+ retried=True,
1903
+ )
1878
1904
  retry_items = retry_result.get("items") if isinstance(retry_result.get("items"), list) else []
1879
1905
  if retry_items:
1880
1906
  items = retry_items
@@ -2982,6 +3008,72 @@ def _solution_error_fields(
2982
3008
  }
2983
3009
 
2984
3010
 
3011
+ def _builder_package_resolution_failed(
3012
+ *,
3013
+ package_name: str,
3014
+ package_tag_id: int,
3015
+ error: QingflowApiError,
3016
+ retried: bool,
3017
+ ) -> dict[str, Any]:
3018
+ if package_tag_id > 0:
3019
+ detail = f"failed to resolve package_tag_id '{package_tag_id}': {error.message}"
3020
+ elif error.backend_code in {40002, 40027}:
3021
+ detail = (
3022
+ f"failed to resolve package '{package_name}' because package listing is permission-restricted; "
3023
+ "provide package_tag_id explicitly or use an account that can list packages"
3024
+ )
3025
+ else:
3026
+ detail = f"failed to resolve package '{package_name}': {error.message}"
3027
+ error_fields = _solution_error_fields(category="config", detail=detail, suggested_next_call=None, stage="app")
3028
+ error_fields["error_code"] = "PACKAGE_RESOLVE_FAILED"
3029
+ return {
3030
+ "status": "failed",
3031
+ "response": {
3032
+ "status": "failed",
3033
+ "mode": "plan",
3034
+ "stage": "app",
3035
+ "errors": [{"category": "config", "detail": detail}],
3036
+ "package_resolution": {
3037
+ "status": "failed",
3038
+ "requested_name": package_name or None,
3039
+ "requested_tag_id": package_tag_id or None,
3040
+ "resolution_policy": "exact_name_only" if package_name else "tag_id_only",
3041
+ "candidates": [],
3042
+ "source_shape": None,
3043
+ "retried": retried,
3044
+ "transport_error": {
3045
+ "http_status": error.http_status,
3046
+ "backend_code": error.backend_code,
3047
+ "category": error.category,
3048
+ "request_id": error.request_id,
3049
+ },
3050
+ },
3051
+ **error_fields,
3052
+ },
3053
+ }
3054
+
3055
+
3056
+ def _coerce_solution_api_error(error: Exception) -> QingflowApiError:
3057
+ if isinstance(error, QingflowApiError):
3058
+ return error
3059
+ if isinstance(error, RuntimeError):
3060
+ try:
3061
+ payload = json.loads(str(error))
3062
+ except json.JSONDecodeError:
3063
+ payload = None
3064
+ if isinstance(payload, dict) and payload.get("category") and payload.get("message"):
3065
+ details = payload.get("details")
3066
+ return QingflowApiError(
3067
+ category=str(payload.get("category")),
3068
+ message=str(payload.get("message")),
3069
+ backend_code=payload.get("backend_code"),
3070
+ request_id=payload.get("request_id"),
3071
+ http_status=payload.get("http_status"),
3072
+ details=details if isinstance(details, dict) else None,
3073
+ )
3074
+ return QingflowApiError(category="runtime", message=str(error))
3075
+
3076
+
2985
3077
  def _coerce_count(value: Any) -> int | None:
2986
3078
  if isinstance(value, bool) or value is None:
2987
3079
  return None
@@ -1,3 +0,0 @@
1
- from .context import OperationsRuntime, build_operations_runtime
2
-
3
- __all__ = ["OperationsRuntime", "build_operations_runtime"]
@@ -1,64 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from typing import Any
4
-
5
- from .base import normalize_exception, success_result
6
-
7
-
8
- class AppOperations:
9
- def __init__(self, tools: Any) -> None:
10
- self._tools = tools
11
-
12
- def list(self, *, profile: str) -> dict:
13
- try:
14
- raw = self._tools.app.app_list(profile=profile)
15
- except Exception as error: # noqa: BLE001
16
- return normalize_exception(error)
17
- items = raw.get("items") if isinstance(raw.get("items"), list) else []
18
- return success_result(
19
- "APPS_LISTED",
20
- "已读取应用列表",
21
- data={"items": items, "count": raw.get("count", len(items))},
22
- meta={"profile": profile, "workspace_id": raw.get("ws_id")},
23
- legacy=raw,
24
- )
25
-
26
- def find(self, *, profile: str, keyword: str, page_num: int, page_size: int) -> dict:
27
- try:
28
- raw = self._tools.app.app_search(profile=profile, keyword=keyword, page_num=page_num, page_size=page_size)
29
- except Exception as error: # noqa: BLE001
30
- return normalize_exception(error)
31
- return success_result(
32
- "APPS_FOUND",
33
- "已完成应用搜索",
34
- data={
35
- "items": raw.get("items") if isinstance(raw.get("items"), list) else [],
36
- "total": raw.get("total"),
37
- "keyword": keyword,
38
- "page_num": page_num,
39
- "page_size": page_size,
40
- },
41
- meta={"profile": profile, "workspace_id": raw.get("ws_id")},
42
- legacy=raw,
43
- )
44
-
45
- def show(self, *, profile: str, app_key: str) -> dict:
46
- try:
47
- raw = self._tools.app.app_get(profile=profile, app_key=app_key)
48
- except Exception as error: # noqa: BLE001
49
- return normalize_exception(error)
50
- data = raw.get("data") if isinstance(raw.get("data"), dict) else {}
51
- return success_result(
52
- "APP_SHOWN",
53
- "已读取应用信息",
54
- data={
55
- "app_key": data.get("app_key", app_key),
56
- "app_name": data.get("app_name"),
57
- "can_create": data.get("can_create"),
58
- "import_capability": data.get("import_capability"),
59
- "accessible_views": data.get("accessible_views") if isinstance(data.get("accessible_views"), list) else [],
60
- },
61
- warnings=raw.get("warnings") if isinstance(raw.get("warnings"), list) else [],
62
- meta={"profile": profile, "workspace_id": raw.get("ws_id"), "request_route": raw.get("request_route")},
63
- legacy=raw,
64
- )
@@ -1,121 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from typing import Any
4
-
5
- from .base import normalize_exception, success_result
6
-
7
-
8
- class AuthOperations:
9
- def __init__(self, tools: Any) -> None:
10
- self._tools = tools
11
-
12
- def login(
13
- self,
14
- *,
15
- profile: str,
16
- base_url: str | None,
17
- qf_version: str | None,
18
- email: str,
19
- password: str,
20
- persist: bool,
21
- ) -> dict:
22
- try:
23
- raw = self._tools.auth.auth_login(
24
- profile=profile,
25
- base_url=base_url,
26
- qf_version=qf_version,
27
- email=email,
28
- password=password,
29
- persist=persist,
30
- )
31
- except Exception as error: # noqa: BLE001
32
- return normalize_exception(error)
33
- return success_result(
34
- "AUTHENTICATED",
35
- "登录成功",
36
- data=_auth_payload(raw),
37
- meta={"profile": profile},
38
- legacy=raw,
39
- )
40
-
41
- def use_token(
42
- self,
43
- *,
44
- profile: str,
45
- base_url: str | None,
46
- qf_version: str | None,
47
- token: str,
48
- ws_id: int | None,
49
- persist: bool,
50
- ) -> dict:
51
- try:
52
- raw = self._tools.auth.auth_use_token(
53
- profile=profile,
54
- base_url=base_url,
55
- qf_version=qf_version,
56
- token=token,
57
- ws_id=ws_id,
58
- persist=persist,
59
- )
60
- except Exception as error: # noqa: BLE001
61
- return normalize_exception(error)
62
- return success_result(
63
- "TOKEN_ACCEPTED",
64
- "已接入会话令牌",
65
- data=_auth_payload(raw),
66
- meta={"profile": profile},
67
- legacy=raw,
68
- )
69
-
70
- def me(self, *, profile: str) -> dict:
71
- try:
72
- raw = self._tools.auth.auth_whoami(profile=profile)
73
- except Exception as error: # noqa: BLE001
74
- return normalize_exception(error)
75
- return success_result(
76
- "SESSION_READY",
77
- "当前会话已就绪",
78
- data=_auth_payload(raw),
79
- meta={"profile": profile},
80
- legacy=raw,
81
- )
82
-
83
- def logout(self, *, profile: str, forget_persisted: bool) -> dict:
84
- try:
85
- raw = self._tools.auth.auth_logout(profile=profile, forget_persisted=forget_persisted)
86
- except Exception as error: # noqa: BLE001
87
- return normalize_exception(error)
88
- return success_result(
89
- "SESSION_CLEARED",
90
- "已退出当前会话",
91
- data={
92
- "profile": raw.get("profile", profile),
93
- "logged_out": bool(raw.get("logged_out", True)),
94
- "forgot_persisted": bool(raw.get("forgot_persisted", forget_persisted)),
95
- },
96
- meta={"profile": profile},
97
- legacy=raw,
98
- )
99
-
100
-
101
- def _auth_payload(raw: dict) -> dict:
102
- return {
103
- "profile": raw.get("profile"),
104
- "base_url": raw.get("base_url"),
105
- "qf_version": raw.get("qf_version"),
106
- "qf_version_source": raw.get("qf_version_source"),
107
- "user": {
108
- "uid": raw.get("uid"),
109
- "email": raw.get("email"),
110
- "nick_name": raw.get("nick_name"),
111
- },
112
- "workspace": {
113
- "ws_id": raw.get("selected_ws_id"),
114
- "name": raw.get("selected_ws_name"),
115
- },
116
- "suggested_workspace": {
117
- "ws_id": raw.get("suggested_ws_id"),
118
- "name": raw.get("suggested_ws_name"),
119
- },
120
- "persisted": bool(raw.get("persisted")),
121
- }
@@ -1,290 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import json
4
- from typing import Any
5
-
6
- from ..errors import QingflowApiError
7
- from ..json_types import JSONObject
8
-
9
-
10
- _COMMON_KEYS = {
11
- "ok",
12
- "status",
13
- "message",
14
- "error_code",
15
- "details",
16
- "warnings",
17
- "verification",
18
- "profile",
19
- "ws_id",
20
- "request_id",
21
- "request_route",
22
- "output_profile",
23
- "recoverable",
24
- "missing_fields",
25
- "allowed_values",
26
- "suggested_next_call",
27
- "noop",
28
- "compact",
29
- "summary",
30
- }
31
-
32
- _INTERNAL_LEGACY_KEY = "__legacy__"
33
-
34
-
35
- def success_result(
36
- code: str,
37
- message: str,
38
- *,
39
- data: Any = None,
40
- warnings: list[Any] | None = None,
41
- meta: JSONObject | None = None,
42
- legacy: Any = None,
43
- ) -> JSONObject:
44
- payload: JSONObject = {
45
- "ok": True,
46
- "code": code,
47
- "message": message,
48
- "data": data if data is not None else {},
49
- "warnings": list(warnings or []),
50
- "meta": dict(meta or {}),
51
- }
52
- if legacy is not None:
53
- payload[_INTERNAL_LEGACY_KEY] = legacy
54
- return payload
55
-
56
-
57
- def error_result(
58
- code: str,
59
- message: str,
60
- *,
61
- details: Any = None,
62
- suggested_next: Any = None,
63
- meta: JSONObject | None = None,
64
- legacy: Any = None,
65
- ) -> JSONObject:
66
- payload: JSONObject = {
67
- "ok": False,
68
- "code": code,
69
- "message": message,
70
- "details": details if details is not None else {},
71
- "meta": dict(meta or {}),
72
- }
73
- if suggested_next is not None:
74
- payload["suggested_next"] = suggested_next
75
- if legacy is not None:
76
- payload[_INTERNAL_LEGACY_KEY] = legacy
77
- return payload
78
-
79
-
80
- def build_meta(raw: Any, *, extra: JSONObject | None = None) -> JSONObject:
81
- meta: JSONObject = {}
82
- if isinstance(raw, dict):
83
- if raw.get("profile") is not None:
84
- meta["profile"] = raw.get("profile")
85
- if raw.get("ws_id") is not None:
86
- meta["workspace_id"] = raw.get("ws_id")
87
- if raw.get("request_id") is not None:
88
- meta["request_id"] = raw.get("request_id")
89
- if isinstance(raw.get("request_route"), dict):
90
- meta["request_route"] = raw.get("request_route")
91
- if isinstance(raw.get("verification"), dict) and raw.get("verification"):
92
- meta["verification"] = raw.get("verification")
93
- if extra:
94
- meta.update(extra)
95
- return meta
96
-
97
-
98
- def collect_warnings(raw: Any) -> list[Any]:
99
- warnings: list[Any] = []
100
- if isinstance(raw, dict):
101
- if isinstance(raw.get("warnings"), list):
102
- warnings.extend(raw["warnings"])
103
- status = str(raw.get("status") or "").lower()
104
- if status == "partial_success":
105
- warnings.append(
106
- {
107
- "code": "PARTIAL_SUCCESS",
108
- "message": str(raw.get("message") or "operation completed with warnings"),
109
- }
110
- )
111
- return warnings
112
-
113
-
114
- def tool_payload(raw: Any) -> Any:
115
- if not isinstance(raw, dict):
116
- return raw
117
- if "data" in raw:
118
- return raw.get("data")
119
- if "items" in raw:
120
- return {
121
- "items": raw.get("items") if isinstance(raw.get("items"), list) else [],
122
- "count": raw.get("count"),
123
- }
124
- if "page" in raw:
125
- page = raw.get("page")
126
- if isinstance(page, dict):
127
- return {
128
- "items": page.get("list") if isinstance(page.get("list"), list) else [],
129
- "page": page,
130
- }
131
- if "result" in raw:
132
- return raw.get("result")
133
- return {key: value for key, value in raw.items() if key not in _COMMON_KEYS}
134
-
135
-
136
- def is_failed_tool_result(raw: Any) -> bool:
137
- if not isinstance(raw, dict):
138
- return False
139
- if raw.get("ok") is False:
140
- return True
141
- status = str(raw.get("status") or "").lower()
142
- return status in {"failed", "blocked"}
143
-
144
-
145
- def tool_result_to_cli(
146
- raw: Any,
147
- *,
148
- code: str,
149
- message: str,
150
- data: Any = None,
151
- meta: JSONObject | None = None,
152
- ) -> JSONObject:
153
- if is_failed_tool_result(raw):
154
- payload = normalize_tool_error(raw)
155
- payload["meta"] = build_meta(raw, extra=meta)
156
- return payload
157
- return success_result(
158
- code,
159
- message,
160
- data=tool_payload(raw) if data is None else data,
161
- warnings=collect_warnings(raw),
162
- meta=build_meta(raw, extra=meta),
163
- )
164
-
165
-
166
- def normalize_tool_error(raw: Any) -> JSONObject:
167
- if isinstance(raw, dict):
168
- details = raw.get("details")
169
- if not isinstance(details, dict):
170
- details = {}
171
- if raw.get("backend_code") is not None:
172
- details.setdefault("backend_code", raw.get("backend_code"))
173
- if raw.get("request_id") is not None:
174
- details.setdefault("request_id", raw.get("request_id"))
175
- if raw.get("verification") not in (None, {}):
176
- details.setdefault("verification", raw.get("verification"))
177
- return error_result(
178
- str(raw.get("error_code") or raw.get("code") or raw.get("category") or "OPERATION_FAILED"),
179
- str(raw.get("message") or "operation failed"),
180
- details=details,
181
- suggested_next=raw.get("suggested_next_call"),
182
- legacy=raw,
183
- )
184
- return error_result("OPERATION_FAILED", str(raw))
185
-
186
-
187
- def coerce_runtime_error(exc: RuntimeError) -> QingflowApiError:
188
- raw = str(exc)
189
- try:
190
- payload = json.loads(raw)
191
- except json.JSONDecodeError:
192
- return QingflowApiError(category="runtime", message=raw)
193
- if not isinstance(payload, dict):
194
- return QingflowApiError(category="runtime", message=raw)
195
- return QingflowApiError(
196
- category=str(payload.get("category") or "runtime"),
197
- message=str(payload.get("message") or raw),
198
- backend_code=payload.get("backend_code"),
199
- request_id=payload.get("request_id"),
200
- details=payload.get("details") if isinstance(payload.get("details"), dict) else None,
201
- )
202
-
203
-
204
- def normalize_exception(error: Exception) -> JSONObject:
205
- if isinstance(error, RuntimeError):
206
- api_error = coerce_runtime_error(error)
207
- elif isinstance(error, QingflowApiError):
208
- api_error = error
209
- else:
210
- return error_result("RUNTIME_ERROR", str(error))
211
-
212
- details = dict(api_error.details or {})
213
- if api_error.backend_code is not None:
214
- details.setdefault("backend_code", api_error.backend_code)
215
- if api_error.request_id is not None:
216
- details.setdefault("request_id", api_error.request_id)
217
- code = str(details.pop("error_code", None) or api_error.category or "OPERATION_FAILED").upper()
218
- if code == "CONFIG":
219
- code = "INVALID_ARGUMENT"
220
- elif code == "AUTH":
221
- code = "AUTH_REQUIRED"
222
- elif code == "WORKSPACE":
223
- code = "WORKSPACE_NOT_SELECTED"
224
- legacy_payload = None
225
- if isinstance(error, RuntimeError):
226
- raw = str(error)
227
- try:
228
- parsed = json.loads(raw)
229
- except json.JSONDecodeError:
230
- parsed = None
231
- if isinstance(parsed, dict):
232
- legacy_payload = parsed
233
- return error_result(code, api_error.message, details=details, legacy=legacy_payload)
234
-
235
-
236
- def public_result(payload: Any) -> Any:
237
- if isinstance(payload, dict):
238
- return {
239
- key: public_result(value)
240
- for key, value in payload.items()
241
- if key != _INTERNAL_LEGACY_KEY
242
- }
243
- if isinstance(payload, list):
244
- return [public_result(item) for item in payload]
245
- return payload
246
-
247
-
248
- def mcp_result_from_operation(result: Any) -> Any:
249
- if not isinstance(result, dict):
250
- return result
251
- legacy = result.get(_INTERNAL_LEGACY_KEY)
252
- if isinstance(legacy, dict):
253
- return legacy
254
- meta = result.get("meta") if isinstance(result.get("meta"), dict) else {}
255
- if result.get("ok") is False:
256
- payload: JSONObject = {
257
- "ok": False,
258
- "status": "failed",
259
- "error_code": result.get("code") or "OPERATION_FAILED",
260
- "message": result.get("message") or "operation failed",
261
- "details": result.get("details") if isinstance(result.get("details"), dict) else {},
262
- "warnings": result.get("warnings") if isinstance(result.get("warnings"), list) else [],
263
- }
264
- if isinstance(meta.get("request_route"), dict):
265
- payload["request_route"] = meta["request_route"]
266
- if isinstance(meta.get("verification"), dict):
267
- payload["verification"] = meta["verification"]
268
- if meta.get("profile") is not None:
269
- payload["profile"] = meta.get("profile")
270
- if meta.get("workspace_id") is not None:
271
- payload["ws_id"] = meta.get("workspace_id")
272
- if result.get("suggested_next") is not None:
273
- payload["suggested_next_call"] = result.get("suggested_next")
274
- return payload
275
- payload = {
276
- "ok": True,
277
- "status": "success",
278
- "message": result.get("message") or "ok",
279
- "data": result.get("data"),
280
- "warnings": result.get("warnings") if isinstance(result.get("warnings"), list) else [],
281
- }
282
- if isinstance(meta.get("request_route"), dict):
283
- payload["request_route"] = meta["request_route"]
284
- if isinstance(meta.get("verification"), dict):
285
- payload["verification"] = meta["verification"]
286
- if meta.get("profile") is not None:
287
- payload["profile"] = meta.get("profile")
288
- if meta.get("workspace_id") is not None:
289
- payload["ws_id"] = meta.get("workspace_id")
290
- return payload