@josephyan/qingflow-cli 0.2.0-beta.58 → 0.2.0-beta.60
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 +3 -2
- package/docs/local-agent-install.md +9 -0
- package/npm/bin/qingflow.mjs +1 -1
- package/npm/lib/runtime.mjs +156 -21
- package/package.json +1 -1
- package/pyproject.toml +1 -1
- package/src/qingflow_mcp/builder_facade/service.py +670 -191
- package/src/qingflow_mcp/cli/commands/app.py +16 -16
- package/src/qingflow_mcp/cli/commands/auth.py +19 -16
- package/src/qingflow_mcp/cli/commands/builder.py +124 -162
- package/src/qingflow_mcp/cli/commands/common.py +21 -95
- package/src/qingflow_mcp/cli/commands/imports.py +42 -34
- package/src/qingflow_mcp/cli/commands/record.py +131 -133
- package/src/qingflow_mcp/cli/commands/task.py +43 -44
- package/src/qingflow_mcp/cli/commands/workspace.py +10 -10
- package/src/qingflow_mcp/cli/context.py +35 -32
- package/src/qingflow_mcp/cli/formatters.py +124 -121
- package/src/qingflow_mcp/cli/main.py +52 -17
- package/src/qingflow_mcp/server_app_builder.py +122 -190
- package/src/qingflow_mcp/server_app_user.py +63 -662
- package/src/qingflow_mcp/solution/executor.py +63 -4
- package/src/qingflow_mcp/tools/solution_tools.py +115 -3
- package/src/qingflow_mcp/ops/__init__.py +0 -3
- package/src/qingflow_mcp/ops/apps.py +0 -64
- package/src/qingflow_mcp/ops/auth.py +0 -121
- package/src/qingflow_mcp/ops/base.py +0 -290
- package/src/qingflow_mcp/ops/builder.py +0 -357
- package/src/qingflow_mcp/ops/context.py +0 -120
- package/src/qingflow_mcp/ops/directory.py +0 -171
- package/src/qingflow_mcp/ops/feedback.py +0 -49
- package/src/qingflow_mcp/ops/files.py +0 -78
- package/src/qingflow_mcp/ops/imports.py +0 -140
- package/src/qingflow_mcp/ops/records.py +0 -415
- package/src/qingflow_mcp/ops/tasks.py +0 -171
- package/src/qingflow_mcp/ops/workspace.py +0 -76
|
@@ -236,8 +236,14 @@ class SolutionExecutor:
|
|
|
236
236
|
existing_role_id = existing.get("role_id")
|
|
237
237
|
if existing_role_id:
|
|
238
238
|
return
|
|
239
|
-
|
|
240
|
-
|
|
239
|
+
try:
|
|
240
|
+
page = self.role_tools.role_search(profile=profile, keyword=role.name, page_num=1, page_size=50).get("page") or {}
|
|
241
|
+
role_list = page.get("list") if isinstance(page, dict) else []
|
|
242
|
+
except Exception as exc: # noqa: BLE001
|
|
243
|
+
api_error = _coerce_qingflow_error(exc)
|
|
244
|
+
if api_error is None or not _is_permission_restricted_error(api_error):
|
|
245
|
+
raise
|
|
246
|
+
role_list = []
|
|
241
247
|
matched_role = next(
|
|
242
248
|
(
|
|
243
249
|
item
|
|
@@ -312,7 +318,18 @@ class SolutionExecutor:
|
|
|
312
318
|
if not isinstance(app_key, str) or not app_key:
|
|
313
319
|
raise ValueError(f"missing app_key for package attach on entity '{entity.entity_id}'")
|
|
314
320
|
|
|
315
|
-
|
|
321
|
+
try:
|
|
322
|
+
package_detail = self.package_tools.package_get(profile=profile, tag_id=tag_id, include_raw=True)
|
|
323
|
+
except Exception as exc: # noqa: BLE001
|
|
324
|
+
api_error = _coerce_qingflow_error(exc)
|
|
325
|
+
if api_error is None or not _is_permission_restricted_error(api_error):
|
|
326
|
+
raise
|
|
327
|
+
raise _required_state_read_blocked_error(
|
|
328
|
+
resource="package_attach",
|
|
329
|
+
message=f"package attach requires readable package state before sorting items for tag '{tag_id}'",
|
|
330
|
+
error=api_error,
|
|
331
|
+
details={"tag_id": tag_id, "app_key": app_key},
|
|
332
|
+
) from exc
|
|
316
333
|
package_result = package_detail.get("result") if isinstance(package_detail.get("result"), dict) else {}
|
|
317
334
|
tag_items = [deepcopy(item) for item in package_result.get("tagItems", []) if isinstance(item, dict)]
|
|
318
335
|
if any(_package_item_app_key(item) == app_key for item in tag_items):
|
|
@@ -675,7 +692,18 @@ class SolutionExecutor:
|
|
|
675
692
|
if dash_key:
|
|
676
693
|
store.set_artifact("portal", "dash_key", dash_key)
|
|
677
694
|
if dash_key:
|
|
678
|
-
|
|
695
|
+
try:
|
|
696
|
+
base_payload = self.portal_tools.portal_get(profile=profile, dash_key=dash_key, being_draft=True).get("result") or {}
|
|
697
|
+
except Exception as exc: # noqa: BLE001
|
|
698
|
+
api_error = _coerce_qingflow_error(exc)
|
|
699
|
+
if api_error is None or not _is_permission_restricted_error(api_error):
|
|
700
|
+
raise
|
|
701
|
+
raise _required_state_read_blocked_error(
|
|
702
|
+
resource="portal",
|
|
703
|
+
message=f"portal update requires readable draft state for dash '{dash_key}'",
|
|
704
|
+
error=api_error,
|
|
705
|
+
details={"dash_key": dash_key},
|
|
706
|
+
) from exc
|
|
679
707
|
update_payload = self._resolve_portal_payload(compiled.portal_plan["update_payload"], store, base_payload=base_payload)
|
|
680
708
|
self.portal_tools.portal_update(profile=profile, dash_key=dash_key, payload=update_payload)
|
|
681
709
|
self._refresh_portal_artifact(profile=profile, store=store, being_draft=True, artifact_key="draft_result")
|
|
@@ -2123,6 +2151,37 @@ def _coerce_qingflow_error(error: Exception) -> QingflowApiError | None:
|
|
|
2123
2151
|
)
|
|
2124
2152
|
|
|
2125
2153
|
|
|
2154
|
+
def _is_permission_restricted_error(error: QingflowApiError) -> bool:
|
|
2155
|
+
return error.backend_code in {40002, 40027}
|
|
2156
|
+
|
|
2157
|
+
|
|
2158
|
+
def _required_state_read_blocked_error(
|
|
2159
|
+
*,
|
|
2160
|
+
resource: str,
|
|
2161
|
+
message: str,
|
|
2162
|
+
error: QingflowApiError,
|
|
2163
|
+
details: dict[str, Any] | None = None,
|
|
2164
|
+
) -> QingflowApiError:
|
|
2165
|
+
merged_details = deepcopy(details) if isinstance(details, dict) else {}
|
|
2166
|
+
merged_details["state_read_blocked"] = {
|
|
2167
|
+
"resource": resource,
|
|
2168
|
+
"transport_error": {
|
|
2169
|
+
"http_status": error.http_status,
|
|
2170
|
+
"backend_code": error.backend_code,
|
|
2171
|
+
"category": error.category,
|
|
2172
|
+
"request_id": error.request_id,
|
|
2173
|
+
},
|
|
2174
|
+
}
|
|
2175
|
+
return QingflowApiError(
|
|
2176
|
+
category=error.category,
|
|
2177
|
+
message=message,
|
|
2178
|
+
backend_code=error.backend_code,
|
|
2179
|
+
request_id=error.request_id,
|
|
2180
|
+
http_status=error.http_status,
|
|
2181
|
+
details=merged_details,
|
|
2182
|
+
)
|
|
2183
|
+
|
|
2184
|
+
|
|
2126
2185
|
def _portal_component_position(
|
|
2127
2186
|
source_type: Any,
|
|
2128
2187
|
*,
|
|
@@ -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,35 @@ 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
|
-
|
|
1855
|
+
try:
|
|
1856
|
+
result = packages.package_get(profile=profile, tag_id=package_tag_id, include_raw=False)
|
|
1857
|
+
except (QingflowApiError, RuntimeError) as exc:
|
|
1858
|
+
error = _coerce_solution_api_error(exc)
|
|
1859
|
+
if error.backend_code in {40002, 40027}:
|
|
1860
|
+
return {
|
|
1861
|
+
"status": "resolved",
|
|
1862
|
+
"matched_via": "tag_id",
|
|
1863
|
+
"tag_id": package_tag_id,
|
|
1864
|
+
"tag_name": None,
|
|
1865
|
+
"candidates": [],
|
|
1866
|
+
"metadata_unverified": True,
|
|
1867
|
+
"lookup_permission_blocked": {
|
|
1868
|
+
"scope": "package",
|
|
1869
|
+
"target": {"tag_id": package_tag_id},
|
|
1870
|
+
"transport_error": {
|
|
1871
|
+
"http_status": error.http_status,
|
|
1872
|
+
"backend_code": error.backend_code,
|
|
1873
|
+
"category": error.category,
|
|
1874
|
+
"request_id": error.request_id,
|
|
1875
|
+
},
|
|
1876
|
+
},
|
|
1877
|
+
}
|
|
1878
|
+
return _builder_package_resolution_failed(
|
|
1879
|
+
package_name=normalized_name,
|
|
1880
|
+
package_tag_id=package_tag_id,
|
|
1881
|
+
error=error,
|
|
1882
|
+
retried=False,
|
|
1883
|
+
)
|
|
1854
1884
|
summary = result.get("result") if isinstance(result.get("result"), dict) else {}
|
|
1855
1885
|
return {
|
|
1856
1886
|
"status": "resolved",
|
|
@@ -1869,12 +1899,28 @@ class SolutionTools(ToolBase):
|
|
|
1869
1899
|
"source_shape": None,
|
|
1870
1900
|
"retried": False,
|
|
1871
1901
|
}
|
|
1872
|
-
|
|
1902
|
+
try:
|
|
1903
|
+
result = packages.package_list(profile=profile, trial_status="all", include_raw=False)
|
|
1904
|
+
except (QingflowApiError, RuntimeError) as exc:
|
|
1905
|
+
return _builder_package_resolution_failed(
|
|
1906
|
+
package_name=normalized_name,
|
|
1907
|
+
package_tag_id=package_tag_id,
|
|
1908
|
+
error=_coerce_solution_api_error(exc),
|
|
1909
|
+
retried=False,
|
|
1910
|
+
)
|
|
1873
1911
|
items = result.get("items") if isinstance(result.get("items"), list) else []
|
|
1874
1912
|
source_shape = result.get("source_shape")
|
|
1875
1913
|
retried = False
|
|
1876
1914
|
if not items:
|
|
1877
|
-
|
|
1915
|
+
try:
|
|
1916
|
+
retry_result = packages.package_list(profile=profile, trial_status="all", include_raw=False)
|
|
1917
|
+
except (QingflowApiError, RuntimeError) as exc:
|
|
1918
|
+
return _builder_package_resolution_failed(
|
|
1919
|
+
package_name=normalized_name,
|
|
1920
|
+
package_tag_id=package_tag_id,
|
|
1921
|
+
error=_coerce_solution_api_error(exc),
|
|
1922
|
+
retried=True,
|
|
1923
|
+
)
|
|
1878
1924
|
retry_items = retry_result.get("items") if isinstance(retry_result.get("items"), list) else []
|
|
1879
1925
|
if retry_items:
|
|
1880
1926
|
items = retry_items
|
|
@@ -2982,6 +3028,72 @@ def _solution_error_fields(
|
|
|
2982
3028
|
}
|
|
2983
3029
|
|
|
2984
3030
|
|
|
3031
|
+
def _builder_package_resolution_failed(
|
|
3032
|
+
*,
|
|
3033
|
+
package_name: str,
|
|
3034
|
+
package_tag_id: int,
|
|
3035
|
+
error: QingflowApiError,
|
|
3036
|
+
retried: bool,
|
|
3037
|
+
) -> dict[str, Any]:
|
|
3038
|
+
if package_tag_id > 0:
|
|
3039
|
+
detail = f"failed to resolve package_tag_id '{package_tag_id}': {error.message}"
|
|
3040
|
+
elif error.backend_code in {40002, 40027}:
|
|
3041
|
+
detail = (
|
|
3042
|
+
f"failed to resolve package '{package_name}' because package listing is permission-restricted; "
|
|
3043
|
+
"provide package_tag_id explicitly or use an account that can list packages"
|
|
3044
|
+
)
|
|
3045
|
+
else:
|
|
3046
|
+
detail = f"failed to resolve package '{package_name}': {error.message}"
|
|
3047
|
+
error_fields = _solution_error_fields(category="config", detail=detail, suggested_next_call=None, stage="app")
|
|
3048
|
+
error_fields["error_code"] = "PACKAGE_RESOLVE_FAILED"
|
|
3049
|
+
return {
|
|
3050
|
+
"status": "failed",
|
|
3051
|
+
"response": {
|
|
3052
|
+
"status": "failed",
|
|
3053
|
+
"mode": "plan",
|
|
3054
|
+
"stage": "app",
|
|
3055
|
+
"errors": [{"category": "config", "detail": detail}],
|
|
3056
|
+
"package_resolution": {
|
|
3057
|
+
"status": "failed",
|
|
3058
|
+
"requested_name": package_name or None,
|
|
3059
|
+
"requested_tag_id": package_tag_id or None,
|
|
3060
|
+
"resolution_policy": "exact_name_only" if package_name else "tag_id_only",
|
|
3061
|
+
"candidates": [],
|
|
3062
|
+
"source_shape": None,
|
|
3063
|
+
"retried": retried,
|
|
3064
|
+
"transport_error": {
|
|
3065
|
+
"http_status": error.http_status,
|
|
3066
|
+
"backend_code": error.backend_code,
|
|
3067
|
+
"category": error.category,
|
|
3068
|
+
"request_id": error.request_id,
|
|
3069
|
+
},
|
|
3070
|
+
},
|
|
3071
|
+
**error_fields,
|
|
3072
|
+
},
|
|
3073
|
+
}
|
|
3074
|
+
|
|
3075
|
+
|
|
3076
|
+
def _coerce_solution_api_error(error: Exception) -> QingflowApiError:
|
|
3077
|
+
if isinstance(error, QingflowApiError):
|
|
3078
|
+
return error
|
|
3079
|
+
if isinstance(error, RuntimeError):
|
|
3080
|
+
try:
|
|
3081
|
+
payload = json.loads(str(error))
|
|
3082
|
+
except json.JSONDecodeError:
|
|
3083
|
+
payload = None
|
|
3084
|
+
if isinstance(payload, dict) and payload.get("category") and payload.get("message"):
|
|
3085
|
+
details = payload.get("details")
|
|
3086
|
+
return QingflowApiError(
|
|
3087
|
+
category=str(payload.get("category")),
|
|
3088
|
+
message=str(payload.get("message")),
|
|
3089
|
+
backend_code=payload.get("backend_code"),
|
|
3090
|
+
request_id=payload.get("request_id"),
|
|
3091
|
+
http_status=payload.get("http_status"),
|
|
3092
|
+
details=details if isinstance(details, dict) else None,
|
|
3093
|
+
)
|
|
3094
|
+
return QingflowApiError(category="runtime", message=str(error))
|
|
3095
|
+
|
|
3096
|
+
|
|
2985
3097
|
def _coerce_count(value: Any) -> int | None:
|
|
2986
3098
|
if isinstance(value, bool) or value is None:
|
|
2987
3099
|
return None
|
|
@@ -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
|
-
}
|