@josephyan/qingflow-cli 0.2.0-beta.76 → 0.2.0-beta.78
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 +2 -2
- package/package.json +1 -1
- package/pyproject.toml +1 -1
- package/src/qingflow_mcp/__init__.py +1 -1
- package/src/qingflow_mcp/builder_facade/models.py +1 -1
- package/src/qingflow_mcp/builder_facade/service.py +121 -49
- package/src/qingflow_mcp/cli/commands/builder.py +4 -4
- package/src/qingflow_mcp/cli/commands/record.py +29 -2
- package/src/qingflow_mcp/cli/main.py +29 -0
- package/src/qingflow_mcp/public_surface.py +61 -52
- package/src/qingflow_mcp/server_app_builder.py +4 -4
- package/src/qingflow_mcp/tools/ai_builder_tools.py +14 -5
- package/src/qingflow_mcp/tools/record_tools.py +425 -7
|
@@ -460,6 +460,8 @@ class RecordTools(ToolBase):
|
|
|
460
460
|
app_key: str = "",
|
|
461
461
|
record_id: int | None = None,
|
|
462
462
|
fields: JSONObject | None = None,
|
|
463
|
+
items: list[JSONObject] | None = None,
|
|
464
|
+
dry_run: bool = False,
|
|
463
465
|
verify_write: bool = True,
|
|
464
466
|
output_profile: str = "normal",
|
|
465
467
|
) -> JSONObject:
|
|
@@ -467,7 +469,9 @@ class RecordTools(ToolBase):
|
|
|
467
469
|
profile=DEFAULT_PROFILE,
|
|
468
470
|
app_key=app_key,
|
|
469
471
|
record_id=record_id,
|
|
470
|
-
fields=fields
|
|
472
|
+
fields=fields,
|
|
473
|
+
items=items,
|
|
474
|
+
dry_run=dry_run,
|
|
471
475
|
verify_write=verify_write,
|
|
472
476
|
output_profile=output_profile,
|
|
473
477
|
)
|
|
@@ -1640,6 +1644,8 @@ class RecordTools(ToolBase):
|
|
|
1640
1644
|
),
|
|
1641
1645
|
}
|
|
1642
1646
|
)
|
|
1647
|
+
rows = list_data.get("rows", [])
|
|
1648
|
+
normalized_public_rows = _normalize_public_record_rows(rows if isinstance(rows, list) else [])
|
|
1643
1649
|
response: JSONObject = {
|
|
1644
1650
|
"profile": profile,
|
|
1645
1651
|
"ws_id": raw.get("ws_id"),
|
|
@@ -1650,7 +1656,7 @@ class RecordTools(ToolBase):
|
|
|
1650
1656
|
"output_profile": normalized_output_profile,
|
|
1651
1657
|
"data": {
|
|
1652
1658
|
"app_key": app_key,
|
|
1653
|
-
"items":
|
|
1659
|
+
"items": normalized_public_rows,
|
|
1654
1660
|
"pagination": {
|
|
1655
1661
|
"page": page,
|
|
1656
1662
|
"limit": limit,
|
|
@@ -1818,7 +1824,7 @@ class RecordTools(ToolBase):
|
|
|
1818
1824
|
"output_profile": normalized_output_profile,
|
|
1819
1825
|
"data": {
|
|
1820
1826
|
"app_key": app_key,
|
|
1821
|
-
"record_id": record_id,
|
|
1827
|
+
"record_id": _public_record_id_text(record_id),
|
|
1822
1828
|
"record": row,
|
|
1823
1829
|
"selection": {
|
|
1824
1830
|
"columns": [_column_selector_payload(field_id) for field_id in normalized_columns] if normalized_columns else [],
|
|
@@ -1918,21 +1924,60 @@ class RecordTools(ToolBase):
|
|
|
1918
1924
|
app_key: str,
|
|
1919
1925
|
record_id: int | None,
|
|
1920
1926
|
fields: JSONObject | None = None,
|
|
1927
|
+
items: list[JSONObject] | None = None,
|
|
1928
|
+
dry_run: bool = False,
|
|
1921
1929
|
verify_write: bool = True,
|
|
1922
1930
|
output_profile: str = "normal",
|
|
1923
1931
|
) -> JSONObject:
|
|
1924
1932
|
normalized_output_profile = self._normalize_public_output_profile(output_profile)
|
|
1925
1933
|
if not app_key:
|
|
1926
1934
|
raise_tool_error(QingflowApiError.config_error("app_key is required"))
|
|
1935
|
+
if items is not None:
|
|
1936
|
+
if dry_run not in {True, False}:
|
|
1937
|
+
raise_tool_error(QingflowApiError.config_error("dry_run must be boolean"))
|
|
1938
|
+
normalized_items = self._normalize_public_record_update_batch_items(
|
|
1939
|
+
record_id=record_id,
|
|
1940
|
+
fields=fields,
|
|
1941
|
+
items=items,
|
|
1942
|
+
)
|
|
1943
|
+
return self._record_update_public_batch(
|
|
1944
|
+
profile=profile,
|
|
1945
|
+
app_key=app_key,
|
|
1946
|
+
items=normalized_items,
|
|
1947
|
+
dry_run=dry_run,
|
|
1948
|
+
verify_write=verify_write,
|
|
1949
|
+
output_profile=normalized_output_profile,
|
|
1950
|
+
)
|
|
1951
|
+
if dry_run:
|
|
1952
|
+
raise_tool_error(QingflowApiError.config_error("dry_run currently requires items"))
|
|
1927
1953
|
if record_id is None or record_id <= 0:
|
|
1928
1954
|
raise_tool_error(QingflowApiError.config_error("record_id is required"))
|
|
1929
1955
|
if fields is not None and not isinstance(fields, dict):
|
|
1930
1956
|
raise_tool_error(QingflowApiError.config_error("fields must be an object map keyed by field title"))
|
|
1931
|
-
|
|
1957
|
+
return self._record_update_public_single(
|
|
1932
1958
|
profile=profile,
|
|
1933
1959
|
app_key=app_key,
|
|
1934
1960
|
record_id=record_id,
|
|
1935
1961
|
fields=cast(JSONObject, fields or {}),
|
|
1962
|
+
verify_write=verify_write,
|
|
1963
|
+
output_profile=normalized_output_profile,
|
|
1964
|
+
)
|
|
1965
|
+
|
|
1966
|
+
def _record_update_public_single(
|
|
1967
|
+
self,
|
|
1968
|
+
*,
|
|
1969
|
+
profile: str,
|
|
1970
|
+
app_key: str,
|
|
1971
|
+
record_id: int,
|
|
1972
|
+
fields: JSONObject,
|
|
1973
|
+
verify_write: bool,
|
|
1974
|
+
output_profile: str,
|
|
1975
|
+
) -> JSONObject:
|
|
1976
|
+
raw_preflight = self._preflight_record_update_with_auto_view(
|
|
1977
|
+
profile=profile,
|
|
1978
|
+
app_key=app_key,
|
|
1979
|
+
record_id=record_id,
|
|
1980
|
+
fields=fields,
|
|
1936
1981
|
force_refresh_form=False,
|
|
1937
1982
|
)
|
|
1938
1983
|
preflight_used_force_refresh = self._record_preflight_used_force_refresh(raw_preflight)
|
|
@@ -1950,7 +1995,7 @@ class RecordTools(ToolBase):
|
|
|
1950
1995
|
raw_preflight,
|
|
1951
1996
|
operation="update",
|
|
1952
1997
|
normalized_payload=normalized_payload,
|
|
1953
|
-
output_profile=
|
|
1998
|
+
output_profile=output_profile,
|
|
1954
1999
|
human_review=True,
|
|
1955
2000
|
target_resource={"type": "record", "app_key": app_key, "record_id": record_id, "record_ids": []},
|
|
1956
2001
|
)
|
|
@@ -1978,11 +2023,257 @@ class RecordTools(ToolBase):
|
|
|
1978
2023
|
raw_apply,
|
|
1979
2024
|
operation="update",
|
|
1980
2025
|
normalized_payload=normalized_payload,
|
|
1981
|
-
output_profile=
|
|
2026
|
+
output_profile=output_profile,
|
|
1982
2027
|
human_review=True,
|
|
1983
2028
|
preflight=raw_preflight,
|
|
1984
2029
|
)
|
|
1985
2030
|
|
|
2031
|
+
def _record_update_public_batch(
|
|
2032
|
+
self,
|
|
2033
|
+
*,
|
|
2034
|
+
profile: str,
|
|
2035
|
+
app_key: str,
|
|
2036
|
+
items: list[JSONObject],
|
|
2037
|
+
dry_run: bool,
|
|
2038
|
+
verify_write: bool,
|
|
2039
|
+
output_profile: str,
|
|
2040
|
+
) -> JSONObject:
|
|
2041
|
+
preflight_responses = [
|
|
2042
|
+
self._record_update_public_preflight_response(
|
|
2043
|
+
profile=profile,
|
|
2044
|
+
app_key=app_key,
|
|
2045
|
+
record_id=cast(int, item["record_id"]),
|
|
2046
|
+
fields=cast(JSONObject, item["fields"]),
|
|
2047
|
+
output_profile=output_profile,
|
|
2048
|
+
)
|
|
2049
|
+
for item in items
|
|
2050
|
+
]
|
|
2051
|
+
has_preflight_blockers = any(
|
|
2052
|
+
str(response.get("status") or "").lower() in {"blocked", "needs_confirmation"}
|
|
2053
|
+
for response in preflight_responses
|
|
2054
|
+
)
|
|
2055
|
+
if dry_run or has_preflight_blockers:
|
|
2056
|
+
return self._record_update_batch_response(
|
|
2057
|
+
profile=profile,
|
|
2058
|
+
app_key=app_key,
|
|
2059
|
+
responses=preflight_responses,
|
|
2060
|
+
output_profile=output_profile,
|
|
2061
|
+
dry_run=dry_run,
|
|
2062
|
+
)
|
|
2063
|
+
|
|
2064
|
+
apply_responses: list[JSONObject] = []
|
|
2065
|
+
for item in items:
|
|
2066
|
+
record_id = cast(int, item["record_id"])
|
|
2067
|
+
fields = cast(JSONObject, item["fields"])
|
|
2068
|
+
try:
|
|
2069
|
+
apply_responses.append(
|
|
2070
|
+
self._record_update_public_single(
|
|
2071
|
+
profile=profile,
|
|
2072
|
+
app_key=app_key,
|
|
2073
|
+
record_id=record_id,
|
|
2074
|
+
fields=fields,
|
|
2075
|
+
verify_write=verify_write,
|
|
2076
|
+
output_profile=output_profile,
|
|
2077
|
+
)
|
|
2078
|
+
)
|
|
2079
|
+
except (QingflowApiError, RuntimeError) as exc:
|
|
2080
|
+
apply_responses.append(
|
|
2081
|
+
self._record_write_exception_response(
|
|
2082
|
+
exc,
|
|
2083
|
+
operation="update",
|
|
2084
|
+
profile=profile,
|
|
2085
|
+
app_key=app_key,
|
|
2086
|
+
record_id=record_id,
|
|
2087
|
+
output_profile=output_profile,
|
|
2088
|
+
human_review=True,
|
|
2089
|
+
)
|
|
2090
|
+
)
|
|
2091
|
+
|
|
2092
|
+
return self._record_update_batch_response(
|
|
2093
|
+
profile=profile,
|
|
2094
|
+
app_key=app_key,
|
|
2095
|
+
responses=apply_responses,
|
|
2096
|
+
output_profile=output_profile,
|
|
2097
|
+
dry_run=False,
|
|
2098
|
+
)
|
|
2099
|
+
|
|
2100
|
+
def _record_update_public_preflight_response(
|
|
2101
|
+
self,
|
|
2102
|
+
*,
|
|
2103
|
+
profile: str,
|
|
2104
|
+
app_key: str,
|
|
2105
|
+
record_id: int,
|
|
2106
|
+
fields: JSONObject,
|
|
2107
|
+
output_profile: str,
|
|
2108
|
+
) -> JSONObject:
|
|
2109
|
+
raw_preflight = self._preflight_record_update_with_auto_view(
|
|
2110
|
+
profile=profile,
|
|
2111
|
+
app_key=app_key,
|
|
2112
|
+
record_id=record_id,
|
|
2113
|
+
fields=fields,
|
|
2114
|
+
force_refresh_form=False,
|
|
2115
|
+
)
|
|
2116
|
+
preflight_data = cast(JSONObject, raw_preflight.get("data", {}))
|
|
2117
|
+
normalized_payload = self._record_write_normalized_payload(
|
|
2118
|
+
operation="update",
|
|
2119
|
+
record_id=record_id,
|
|
2120
|
+
record_ids=[],
|
|
2121
|
+
normalized_answers=cast(list[JSONObject], preflight_data.get("normalized_answers", [])),
|
|
2122
|
+
submit_type=1,
|
|
2123
|
+
selection=cast(JSONObject | None, preflight_data.get("selection") if isinstance(preflight_data.get("selection"), dict) else None),
|
|
2124
|
+
)
|
|
2125
|
+
confirmation_requests = cast(list[JSONObject], preflight_data.get("confirmation_requests", []))
|
|
2126
|
+
if preflight_data.get("blockers") or confirmation_requests:
|
|
2127
|
+
return self._record_write_blocked_response(
|
|
2128
|
+
raw_preflight,
|
|
2129
|
+
operation="update",
|
|
2130
|
+
normalized_payload=normalized_payload,
|
|
2131
|
+
output_profile=output_profile,
|
|
2132
|
+
human_review=True,
|
|
2133
|
+
target_resource={"type": "record", "app_key": app_key, "record_id": record_id, "record_ids": []},
|
|
2134
|
+
)
|
|
2135
|
+
return self._record_write_ready_response(
|
|
2136
|
+
raw_preflight,
|
|
2137
|
+
operation="update",
|
|
2138
|
+
normalized_payload=normalized_payload,
|
|
2139
|
+
output_profile=output_profile,
|
|
2140
|
+
human_review=True,
|
|
2141
|
+
target_resource={"type": "record", "app_key": app_key, "record_id": record_id, "record_ids": []},
|
|
2142
|
+
)
|
|
2143
|
+
|
|
2144
|
+
def _normalize_public_record_update_batch_items(
|
|
2145
|
+
self,
|
|
2146
|
+
*,
|
|
2147
|
+
record_id: int | None,
|
|
2148
|
+
fields: JSONObject | None,
|
|
2149
|
+
items: list[JSONObject] | None,
|
|
2150
|
+
) -> list[JSONObject]:
|
|
2151
|
+
if record_id is not None or fields is not None:
|
|
2152
|
+
raise_tool_error(
|
|
2153
|
+
QingflowApiError.config_error("record_update batch mode does not accept record_id or fields")
|
|
2154
|
+
)
|
|
2155
|
+
if not isinstance(items, list) or not items:
|
|
2156
|
+
raise_tool_error(QingflowApiError.config_error("items must be a non-empty list"))
|
|
2157
|
+
normalized_items: list[JSONObject] = []
|
|
2158
|
+
seen_record_ids: set[int] = set()
|
|
2159
|
+
for index, item in enumerate(items):
|
|
2160
|
+
if not isinstance(item, dict):
|
|
2161
|
+
raise_tool_error(QingflowApiError.config_error(f"items[{index}] must be an object"))
|
|
2162
|
+
normalized_record_id = _coerce_count(item.get("record_id"))
|
|
2163
|
+
if normalized_record_id is None or normalized_record_id <= 0:
|
|
2164
|
+
raise_tool_error(QingflowApiError.config_error(f"items[{index}].record_id must be a positive integer"))
|
|
2165
|
+
if normalized_record_id in seen_record_ids:
|
|
2166
|
+
raise_tool_error(
|
|
2167
|
+
QingflowApiError.config_error(f"duplicate record_id in items: {normalized_record_id}")
|
|
2168
|
+
)
|
|
2169
|
+
item_fields = item.get("fields")
|
|
2170
|
+
if not isinstance(item_fields, dict):
|
|
2171
|
+
raise_tool_error(QingflowApiError.config_error(f"items[{index}].fields must be an object map keyed by field title"))
|
|
2172
|
+
seen_record_ids.add(normalized_record_id)
|
|
2173
|
+
normalized_items.append({"record_id": normalized_record_id, "fields": cast(JSONObject, item_fields)})
|
|
2174
|
+
return normalized_items
|
|
2175
|
+
|
|
2176
|
+
def _record_update_batch_response(
|
|
2177
|
+
self,
|
|
2178
|
+
*,
|
|
2179
|
+
profile: str,
|
|
2180
|
+
app_key: str,
|
|
2181
|
+
responses: list[JSONObject],
|
|
2182
|
+
output_profile: str,
|
|
2183
|
+
dry_run: bool,
|
|
2184
|
+
) -> JSONObject:
|
|
2185
|
+
summary = self._record_update_batch_summary(responses)
|
|
2186
|
+
batch_items = [self._record_update_batch_item_from_response(response, output_profile=output_profile) for response in responses]
|
|
2187
|
+
status, ok, message = self._record_update_batch_envelope_status(summary=summary, dry_run=dry_run)
|
|
2188
|
+
first_response = responses[0] if responses else {}
|
|
2189
|
+
return {
|
|
2190
|
+
"profile": first_response.get("profile", profile),
|
|
2191
|
+
"ws_id": first_response.get("ws_id"),
|
|
2192
|
+
"ok": ok,
|
|
2193
|
+
"status": status,
|
|
2194
|
+
"request_route": first_response.get("request_route"),
|
|
2195
|
+
"warnings": [],
|
|
2196
|
+
"output_profile": output_profile,
|
|
2197
|
+
"data": {
|
|
2198
|
+
"app_key": app_key,
|
|
2199
|
+
"mode": "batch",
|
|
2200
|
+
"dry_run": dry_run,
|
|
2201
|
+
"summary": summary,
|
|
2202
|
+
"items": batch_items,
|
|
2203
|
+
},
|
|
2204
|
+
"message": message,
|
|
2205
|
+
}
|
|
2206
|
+
|
|
2207
|
+
def _record_update_batch_summary(self, responses: list[JSONObject]) -> JSONObject:
|
|
2208
|
+
summary: JSONObject = {
|
|
2209
|
+
"total": len(responses),
|
|
2210
|
+
"ready_count": 0,
|
|
2211
|
+
"blocked_count": 0,
|
|
2212
|
+
"confirmation_count": 0,
|
|
2213
|
+
"applied_count": 0,
|
|
2214
|
+
"verified_count": 0,
|
|
2215
|
+
"field_level_verified_count": 0,
|
|
2216
|
+
"failed_count": 0,
|
|
2217
|
+
}
|
|
2218
|
+
for response in responses:
|
|
2219
|
+
status = str(response.get("status") or "").lower()
|
|
2220
|
+
data = cast(JSONObject, response.get("data", {}))
|
|
2221
|
+
action = cast(JSONObject, data.get("action", {}))
|
|
2222
|
+
verification = cast(JSONObject, data.get("verification", {})) if isinstance(data.get("verification"), dict) else {}
|
|
2223
|
+
executed = bool(action.get("executed"))
|
|
2224
|
+
if status == "ready":
|
|
2225
|
+
summary["ready_count"] = int(summary["ready_count"]) + 1
|
|
2226
|
+
continue
|
|
2227
|
+
if status == "blocked":
|
|
2228
|
+
summary["blocked_count"] = int(summary["blocked_count"]) + 1
|
|
2229
|
+
continue
|
|
2230
|
+
if status == "needs_confirmation":
|
|
2231
|
+
summary["confirmation_count"] = int(summary["confirmation_count"]) + 1
|
|
2232
|
+
continue
|
|
2233
|
+
if executed:
|
|
2234
|
+
summary["applied_count"] = int(summary["applied_count"]) + 1
|
|
2235
|
+
if bool(verification.get("verified")):
|
|
2236
|
+
summary["verified_count"] = int(summary["verified_count"]) + 1
|
|
2237
|
+
if bool(verification.get("field_level_verified")):
|
|
2238
|
+
summary["field_level_verified_count"] = int(summary["field_level_verified_count"]) + 1
|
|
2239
|
+
if status not in {"success"}:
|
|
2240
|
+
summary["failed_count"] = int(summary["failed_count"]) + 1
|
|
2241
|
+
return summary
|
|
2242
|
+
|
|
2243
|
+
def _record_update_batch_envelope_status(self, *, summary: JSONObject, dry_run: bool) -> tuple[str, bool, str]:
|
|
2244
|
+
if int(summary["blocked_count"]) > 0:
|
|
2245
|
+
return "blocked", False, "batch update preflight blocked"
|
|
2246
|
+
if int(summary["confirmation_count"]) > 0:
|
|
2247
|
+
return "needs_confirmation", False, "batch update requires confirmation before write"
|
|
2248
|
+
if dry_run:
|
|
2249
|
+
return "ready", True, "batch update dry run ready"
|
|
2250
|
+
if int(summary["failed_count"]) > 0:
|
|
2251
|
+
return "partial_success", False, "batch update completed with partial failures"
|
|
2252
|
+
return "success", True, "batch update completed"
|
|
2253
|
+
|
|
2254
|
+
def _record_update_batch_item_from_response(self, response: JSONObject, *, output_profile: str) -> JSONObject:
|
|
2255
|
+
data = cast(JSONObject, response.get("data", {}))
|
|
2256
|
+
item: JSONObject = {
|
|
2257
|
+
"resource": data.get("resource"),
|
|
2258
|
+
"status": response.get("status"),
|
|
2259
|
+
"verification": data.get("verification"),
|
|
2260
|
+
"field_errors": cast(list[JSONObject], data.get("field_errors", [])),
|
|
2261
|
+
"confirmation_requests": cast(list[JSONObject], data.get("confirmation_requests", [])),
|
|
2262
|
+
"resolved_fields": cast(list[JSONObject], data.get("resolved_fields", [])),
|
|
2263
|
+
}
|
|
2264
|
+
blockers = data.get("blockers")
|
|
2265
|
+
if isinstance(blockers, list) and blockers:
|
|
2266
|
+
item["blockers"] = blockers
|
|
2267
|
+
warnings = response.get("warnings")
|
|
2268
|
+
if isinstance(warnings, list) and warnings:
|
|
2269
|
+
item["warnings"] = warnings
|
|
2270
|
+
error = data.get("error")
|
|
2271
|
+
if isinstance(error, dict):
|
|
2272
|
+
item["error"] = error
|
|
2273
|
+
if output_profile == "verbose" and isinstance(data.get("debug"), dict):
|
|
2274
|
+
item["debug"] = data.get("debug")
|
|
2275
|
+
return item
|
|
2276
|
+
|
|
1986
2277
|
def _preflight_record_update_with_auto_view(
|
|
1987
2278
|
self,
|
|
1988
2279
|
*,
|
|
@@ -7944,6 +8235,48 @@ class RecordTools(ToolBase):
|
|
|
7944
8235
|
}
|
|
7945
8236
|
return response
|
|
7946
8237
|
|
|
8238
|
+
def _record_write_ready_response(
|
|
8239
|
+
self,
|
|
8240
|
+
raw_preflight: JSONObject,
|
|
8241
|
+
*,
|
|
8242
|
+
operation: str,
|
|
8243
|
+
normalized_payload: JSONObject,
|
|
8244
|
+
output_profile: str,
|
|
8245
|
+
human_review: bool,
|
|
8246
|
+
target_resource: JSONObject,
|
|
8247
|
+
) -> JSONObject:
|
|
8248
|
+
plan_data = cast(JSONObject, raw_preflight.get("data", {}))
|
|
8249
|
+
validation = cast(JSONObject, plan_data.get("validation", {}))
|
|
8250
|
+
resolved_fields = cast(list[JSONObject], plan_data.get("lookup_resolved_fields", []))
|
|
8251
|
+
warnings_payload = validation.get("warnings", [])
|
|
8252
|
+
warnings = [{"code": "PREFLIGHT_WARNING", "message": str(item)} for item in warnings_payload] if isinstance(warnings_payload, list) else []
|
|
8253
|
+
response: JSONObject = {
|
|
8254
|
+
"profile": raw_preflight.get("profile"),
|
|
8255
|
+
"ws_id": raw_preflight.get("ws_id"),
|
|
8256
|
+
"ok": True,
|
|
8257
|
+
"status": "ready",
|
|
8258
|
+
"request_route": raw_preflight.get("request_route"),
|
|
8259
|
+
"warnings": warnings,
|
|
8260
|
+
"output_profile": output_profile,
|
|
8261
|
+
"data": {
|
|
8262
|
+
"action": {"operation": operation, "executed": False},
|
|
8263
|
+
"resource": target_resource,
|
|
8264
|
+
"verification": None,
|
|
8265
|
+
"normalized_payload": normalized_payload,
|
|
8266
|
+
"blockers": [],
|
|
8267
|
+
"field_errors": [],
|
|
8268
|
+
"confirmation_requests": [],
|
|
8269
|
+
"resolved_fields": resolved_fields,
|
|
8270
|
+
"recommended_next_actions": cast(list[JSONValue], plan_data.get("recommended_next_actions", [])),
|
|
8271
|
+
"human_review": self._record_write_human_review_payload(operation, enabled=human_review),
|
|
8272
|
+
},
|
|
8273
|
+
}
|
|
8274
|
+
if output_profile == "verbose":
|
|
8275
|
+
response["data"]["debug"] = {
|
|
8276
|
+
"preflight": plan_data,
|
|
8277
|
+
}
|
|
8278
|
+
return response
|
|
8279
|
+
|
|
7947
8280
|
def _record_write_apply_response(
|
|
7948
8281
|
self,
|
|
7949
8282
|
raw_apply: JSONObject,
|
|
@@ -7994,6 +8327,67 @@ class RecordTools(ToolBase):
|
|
|
7994
8327
|
response["data"]["debug"] = debug
|
|
7995
8328
|
return response
|
|
7996
8329
|
|
|
8330
|
+
def _record_write_exception_response(
|
|
8331
|
+
self,
|
|
8332
|
+
exc: QingflowApiError | RuntimeError,
|
|
8333
|
+
*,
|
|
8334
|
+
operation: str,
|
|
8335
|
+
profile: str,
|
|
8336
|
+
app_key: str,
|
|
8337
|
+
record_id: int,
|
|
8338
|
+
output_profile: str,
|
|
8339
|
+
human_review: bool,
|
|
8340
|
+
) -> JSONObject:
|
|
8341
|
+
error_payload: JSONObject = {
|
|
8342
|
+
"error_code": "RECORD_WRITE_EXECUTION_FAILED",
|
|
8343
|
+
"message": str(exc),
|
|
8344
|
+
}
|
|
8345
|
+
request_route: JSONObject | None = None
|
|
8346
|
+
if isinstance(exc, QingflowApiError):
|
|
8347
|
+
error_payload["message"] = exc.message
|
|
8348
|
+
if exc.backend_code is not None:
|
|
8349
|
+
error_payload["backend_code"] = exc.backend_code
|
|
8350
|
+
if exc.request_id is not None:
|
|
8351
|
+
error_payload["request_id"] = exc.request_id
|
|
8352
|
+
else:
|
|
8353
|
+
try:
|
|
8354
|
+
parsed = json.loads(str(exc))
|
|
8355
|
+
except json.JSONDecodeError:
|
|
8356
|
+
parsed = None
|
|
8357
|
+
if isinstance(parsed, dict):
|
|
8358
|
+
error_payload["error_code"] = parsed.get("error_code") or cast(JSONObject, parsed.get("details", {})).get("error_code") or error_payload["error_code"]
|
|
8359
|
+
error_payload["message"] = parsed.get("message") or error_payload["message"]
|
|
8360
|
+
if parsed.get("backend_code") is not None:
|
|
8361
|
+
error_payload["backend_code"] = parsed.get("backend_code")
|
|
8362
|
+
if parsed.get("request_id") is not None:
|
|
8363
|
+
error_payload["request_id"] = parsed.get("request_id")
|
|
8364
|
+
if isinstance(parsed.get("request_route"), dict):
|
|
8365
|
+
request_route = cast(JSONObject, parsed.get("request_route"))
|
|
8366
|
+
response: JSONObject = {
|
|
8367
|
+
"profile": profile,
|
|
8368
|
+
"ws_id": None,
|
|
8369
|
+
"ok": False,
|
|
8370
|
+
"status": "failed",
|
|
8371
|
+
"request_route": request_route,
|
|
8372
|
+
"warnings": [],
|
|
8373
|
+
"output_profile": output_profile,
|
|
8374
|
+
"data": {
|
|
8375
|
+
"action": {"operation": operation, "executed": True},
|
|
8376
|
+
"resource": {"type": "record", "app_key": app_key, "record_id": record_id, "record_ids": []},
|
|
8377
|
+
"verification": None,
|
|
8378
|
+
"normalized_payload": None,
|
|
8379
|
+
"blockers": [],
|
|
8380
|
+
"field_errors": [],
|
|
8381
|
+
"confirmation_requests": [],
|
|
8382
|
+
"resolved_fields": [],
|
|
8383
|
+
"human_review": self._record_write_human_review_payload(operation, enabled=human_review),
|
|
8384
|
+
"error": error_payload,
|
|
8385
|
+
},
|
|
8386
|
+
}
|
|
8387
|
+
if output_profile == "verbose":
|
|
8388
|
+
response["data"]["debug"] = {"exception": error_payload}
|
|
8389
|
+
return response
|
|
8390
|
+
|
|
7997
8391
|
def _record_write_public_field_errors(self, plan_data: JSONObject) -> list[JSONObject]:
|
|
7998
8392
|
existing = plan_data.get("field_errors")
|
|
7999
8393
|
if isinstance(existing, list):
|
|
@@ -10221,12 +10615,36 @@ def _view_selection_supported_by_search_ids(view_selection: ViewSelection, searc
|
|
|
10221
10615
|
|
|
10222
10616
|
|
|
10223
10617
|
def _build_flat_row(answer_list: list[JSONValue], fields: list[FormField], *, apply_id: int | None) -> JSONObject:
|
|
10224
|
-
|
|
10618
|
+
public_record_id = _public_record_id_text(apply_id)
|
|
10619
|
+
row: JSONObject = {"apply_id": public_record_id, "record_id": public_record_id}
|
|
10225
10620
|
for field in fields:
|
|
10226
10621
|
row[field.que_title] = _extract_field_value(answer_list, field)
|
|
10227
10622
|
return row
|
|
10228
10623
|
|
|
10229
10624
|
|
|
10625
|
+
def _public_record_id_text(record_id: int | None) -> str | None:
|
|
10626
|
+
if record_id is None or record_id <= 0:
|
|
10627
|
+
return None
|
|
10628
|
+
return str(record_id)
|
|
10629
|
+
|
|
10630
|
+
|
|
10631
|
+
def _normalize_public_record_rows(rows: list[JSONValue]) -> list[JSONObject]:
|
|
10632
|
+
normalized_rows: list[JSONObject] = []
|
|
10633
|
+
for item in rows:
|
|
10634
|
+
if not isinstance(item, dict):
|
|
10635
|
+
continue
|
|
10636
|
+
row = dict(item)
|
|
10637
|
+
normalized_record_id = _coerce_count(row.get("apply_id"))
|
|
10638
|
+
if normalized_record_id is None:
|
|
10639
|
+
normalized_record_id = _coerce_count(row.get("record_id"))
|
|
10640
|
+
if normalized_record_id is not None and normalized_record_id > 0:
|
|
10641
|
+
public_record_id = _public_record_id_text(normalized_record_id)
|
|
10642
|
+
row["apply_id"] = public_record_id
|
|
10643
|
+
row["record_id"] = public_record_id
|
|
10644
|
+
normalized_rows.append(row)
|
|
10645
|
+
return normalized_rows
|
|
10646
|
+
|
|
10647
|
+
|
|
10230
10648
|
def _merge_answer_lists_by_field_id(existing: list[JSONValue], extra: list[JSONValue]) -> list[JSONValue]:
|
|
10231
10649
|
merged: dict[str, JSONValue] = {}
|
|
10232
10650
|
order: list[str] = []
|