@josephyan/qingflow-app-builder-mcp 1.1.19 → 1.1.21
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-app-builder-mcp@1.1.
|
|
6
|
+
npm install @josephyan/qingflow-app-builder-mcp@1.1.21
|
|
7
7
|
```
|
|
8
8
|
|
|
9
9
|
Run:
|
|
10
10
|
|
|
11
11
|
```bash
|
|
12
|
-
npx -y -p @josephyan/qingflow-app-builder-mcp@1.1.
|
|
12
|
+
npx -y -p @josephyan/qingflow-app-builder-mcp@1.1.21 qingflow-app-builder-mcp
|
|
13
13
|
```
|
|
14
14
|
|
|
15
15
|
Environment:
|
package/package.json
CHANGED
package/pyproject.toml
CHANGED
|
@@ -9,6 +9,7 @@ import random
|
|
|
9
9
|
import re
|
|
10
10
|
import string
|
|
11
11
|
import tempfile
|
|
12
|
+
import time
|
|
12
13
|
from typing import Any, NoReturn, cast
|
|
13
14
|
from urllib.parse import quote_plus, unquote_plus
|
|
14
15
|
from uuid import uuid4
|
|
@@ -8585,7 +8586,17 @@ class AiBuilderFacade:
|
|
|
8585
8586
|
add_fields: list[FieldPatch],
|
|
8586
8587
|
update_fields: list[FieldUpdatePatch],
|
|
8587
8588
|
remove_fields: list[FieldRemovePatch],
|
|
8589
|
+
inline_form_layout_sections: list[LayoutSectionPatch] | None = None,
|
|
8588
8590
|
) -> JSONObject:
|
|
8591
|
+
apply_started_at = time.perf_counter()
|
|
8592
|
+
schema_write_ms: int | None = None
|
|
8593
|
+
inline_layout_ms: int | None = None
|
|
8594
|
+
publish_ms: int | None = None
|
|
8595
|
+
readback_ms: int | None = None
|
|
8596
|
+
requested_inline_layout_sections = [
|
|
8597
|
+
section.model_dump(mode="json", exclude_none=True)
|
|
8598
|
+
for section in (inline_form_layout_sections or [])
|
|
8599
|
+
]
|
|
8589
8600
|
normalized_args = {
|
|
8590
8601
|
"app_key": app_key,
|
|
8591
8602
|
"package_tag_id": package_tag_id,
|
|
@@ -8599,6 +8610,8 @@ class AiBuilderFacade:
|
|
|
8599
8610
|
"update_fields": [patch.model_dump(mode="json") for patch in update_fields],
|
|
8600
8611
|
"remove_fields": [patch.model_dump(mode="json") for patch in remove_fields],
|
|
8601
8612
|
}
|
|
8613
|
+
if requested_inline_layout_sections:
|
|
8614
|
+
normalized_args["inline_form_layout_sections"] = requested_inline_layout_sections
|
|
8602
8615
|
try:
|
|
8603
8616
|
desired_auth = (
|
|
8604
8617
|
self._compile_visibility_to_member_auth(profile=profile, visibility=visibility)
|
|
@@ -8614,7 +8627,8 @@ class AiBuilderFacade:
|
|
|
8614
8627
|
suggested_next_call=None,
|
|
8615
8628
|
)
|
|
8616
8629
|
permission_outcomes: list[PermissionCheckOutcome] = []
|
|
8617
|
-
|
|
8630
|
+
requested_inline_layout = bool(requested_inline_layout_sections)
|
|
8631
|
+
requested_field_changes = bool(add_fields or update_fields or remove_fields or requested_inline_layout)
|
|
8618
8632
|
resolved: JSONObject
|
|
8619
8633
|
if app_key:
|
|
8620
8634
|
resolved = self.app_resolve(profile=profile, app_key=app_key)
|
|
@@ -8950,6 +8964,59 @@ class AiBuilderFacade:
|
|
|
8950
8964
|
suggested_next_call={"tool_name": "app_get_fields", "arguments": {"profile": profile, "app_key": target.app_key}},
|
|
8951
8965
|
)
|
|
8952
8966
|
|
|
8967
|
+
inline_form_layout_target: dict[str, Any] | None = None
|
|
8968
|
+
inline_form_layout_auto_added_fields: list[str] = []
|
|
8969
|
+
inline_form_layout_changed = False
|
|
8970
|
+
if requested_inline_layout:
|
|
8971
|
+
inline_layout_started_at = time.perf_counter()
|
|
8972
|
+
resolved_inline_sections, missing_selectors = _resolve_layout_sections_to_names(
|
|
8973
|
+
requested_inline_layout_sections,
|
|
8974
|
+
current_fields,
|
|
8975
|
+
)
|
|
8976
|
+
normalized_args["inline_form_layout_sections"] = resolved_inline_sections
|
|
8977
|
+
if missing_selectors:
|
|
8978
|
+
return _failed(
|
|
8979
|
+
"UNKNOWN_LAYOUT_FIELD",
|
|
8980
|
+
"inline form layout references unknown field selectors",
|
|
8981
|
+
normalized_args=normalized_args,
|
|
8982
|
+
details={"unknown_selectors": missing_selectors},
|
|
8983
|
+
missing_fields=[str(item) for item in missing_selectors],
|
|
8984
|
+
suggested_next_call={"tool_name": "app_get_fields", "arguments": {"profile": profile, "app_key": target.app_key}},
|
|
8985
|
+
)
|
|
8986
|
+
fields_by_name = {field["name"]: field for field in current_fields}
|
|
8987
|
+
seen_layout_fields: list[str] = []
|
|
8988
|
+
for section in resolved_inline_sections:
|
|
8989
|
+
for row in section.get("rows", []):
|
|
8990
|
+
for field_name in row:
|
|
8991
|
+
if field_name not in fields_by_name:
|
|
8992
|
+
return _failed(
|
|
8993
|
+
"UNKNOWN_LAYOUT_FIELD",
|
|
8994
|
+
f"inline form layout references unknown field '{field_name}'",
|
|
8995
|
+
normalized_args=normalized_args,
|
|
8996
|
+
details={"field_name": field_name},
|
|
8997
|
+
suggested_next_call={"tool_name": "app_get_fields", "arguments": {"profile": profile, "app_key": target.app_key}},
|
|
8998
|
+
)
|
|
8999
|
+
if field_name in seen_layout_fields:
|
|
9000
|
+
return _failed(
|
|
9001
|
+
"DUPLICATE_LAYOUT_FIELD",
|
|
9002
|
+
f"inline form layout references field '{field_name}' more than once",
|
|
9003
|
+
normalized_args=normalized_args,
|
|
9004
|
+
details={"field_name": field_name},
|
|
9005
|
+
suggested_next_call={"tool_name": "app_schema_apply", "arguments": {"profile": profile, **normalized_args}},
|
|
9006
|
+
)
|
|
9007
|
+
seen_layout_fields.append(field_name)
|
|
9008
|
+
merged_inline_layout = _merge_layout(
|
|
9009
|
+
current_layout=layout,
|
|
9010
|
+
requested_sections=resolved_inline_sections,
|
|
9011
|
+
all_field_names=[field["name"] for field in current_fields],
|
|
9012
|
+
)
|
|
9013
|
+
inline_form_layout_auto_added_fields = list(merged_inline_layout.get("auto_added_fields") or [])
|
|
9014
|
+
inline_form_layout_target = cast(dict[str, Any], merged_inline_layout["layout"])
|
|
9015
|
+
inline_form_layout_changed = not _layouts_equal(layout, inline_form_layout_target)
|
|
9016
|
+
if inline_form_layout_changed:
|
|
9017
|
+
layout = inline_form_layout_target
|
|
9018
|
+
inline_layout_ms = _duration_ms(inline_layout_started_at)
|
|
9019
|
+
|
|
8953
9020
|
schema_write_requested = bool(
|
|
8954
9021
|
added
|
|
8955
9022
|
or updated
|
|
@@ -8957,6 +9024,7 @@ class AiBuilderFacade:
|
|
|
8957
9024
|
or normalized_code_block_fields
|
|
8958
9025
|
or data_display_selection.has_any
|
|
8959
9026
|
or bool(resolved.get("created"))
|
|
9027
|
+
or inline_form_layout_changed
|
|
8960
9028
|
)
|
|
8961
9029
|
if schema_write_requested:
|
|
8962
9030
|
try:
|
|
@@ -9006,7 +9074,8 @@ class AiBuilderFacade:
|
|
|
9006
9074
|
else []
|
|
9007
9075
|
)
|
|
9008
9076
|
|
|
9009
|
-
if not
|
|
9077
|
+
if not schema_write_requested:
|
|
9078
|
+
readback_started_at = time.perf_counter()
|
|
9010
9079
|
try:
|
|
9011
9080
|
base_info = self.apps.app_get_base(profile=profile, app_key=target.app_key, include_raw=True).get("result") or {}
|
|
9012
9081
|
except (QingflowApiError, RuntimeError) as error:
|
|
@@ -9042,6 +9111,7 @@ class AiBuilderFacade:
|
|
|
9042
9111
|
expected_app_icon = str(visual_result.get("app_icon") or "").strip() or None
|
|
9043
9112
|
app_icon_verified = True if expected_app_icon is None else actual_app_icon == expected_app_icon
|
|
9044
9113
|
app_base_verified = actual_app_name == effective_app_name and app_icon_verified
|
|
9114
|
+
readback_ms = _duration_ms(readback_started_at)
|
|
9045
9115
|
verified = app_base_verified and relation_target_metadata_verified
|
|
9046
9116
|
response = {
|
|
9047
9117
|
"status": "success" if verified else "partial_success",
|
|
@@ -9079,11 +9149,34 @@ class AiBuilderFacade:
|
|
|
9079
9149
|
"package_attached": package_attached,
|
|
9080
9150
|
}
|
|
9081
9151
|
response["details"]["relation_field_count"] = relation_field_count
|
|
9152
|
+
if requested_inline_layout:
|
|
9153
|
+
response["inline_form_layout"] = {
|
|
9154
|
+
"requested": True,
|
|
9155
|
+
"section_count": len(requested_inline_layout_sections),
|
|
9156
|
+
"app_key": target.app_key,
|
|
9157
|
+
"applied": True,
|
|
9158
|
+
"applied_in_schema": True,
|
|
9159
|
+
"layout_status": "success",
|
|
9160
|
+
"message": "layout already matched requested state",
|
|
9161
|
+
"auto_added_fields": inline_form_layout_auto_added_fields,
|
|
9162
|
+
}
|
|
9163
|
+
response["verification"]["inline_form_layout_verified"] = True
|
|
9082
9164
|
if normalized_code_block_fields:
|
|
9083
9165
|
response["normalized_code_block_output_assignment"] = True
|
|
9084
9166
|
response["normalized_code_block_fields"] = normalized_code_block_fields
|
|
9085
9167
|
response = _apply_permission_outcomes(response, relation_permission_outcome)
|
|
9086
|
-
|
|
9168
|
+
publish_started_at = time.perf_counter()
|
|
9169
|
+
response = self._append_publish_result(profile=profile, app_key=target.app_key, publish=publish, response=response)
|
|
9170
|
+
publish_ms = _duration_ms(publish_started_at)
|
|
9171
|
+
_merge_duration_breakdown(
|
|
9172
|
+
response,
|
|
9173
|
+
schema_write_ms=0,
|
|
9174
|
+
inline_layout_ms=inline_layout_ms,
|
|
9175
|
+
publish_ms=publish_ms,
|
|
9176
|
+
readback_ms=readback_ms,
|
|
9177
|
+
total_ms=_duration_ms(apply_started_at),
|
|
9178
|
+
)
|
|
9179
|
+
return finalize(response)
|
|
9087
9180
|
|
|
9088
9181
|
payload = (
|
|
9089
9182
|
_build_form_payload_from_fields(
|
|
@@ -9109,7 +9202,9 @@ class AiBuilderFacade:
|
|
|
9109
9202
|
current_schema=schema_result,
|
|
9110
9203
|
)
|
|
9111
9204
|
try:
|
|
9205
|
+
schema_write_started_at = time.perf_counter()
|
|
9112
9206
|
self.apps.app_update_form_schema(profile=profile, app_key=target.app_key, payload=payload)
|
|
9207
|
+
schema_write_ms = _duration_ms(schema_write_started_at)
|
|
9113
9208
|
except (QingflowApiError, RuntimeError) as error:
|
|
9114
9209
|
api_error = _coerce_api_error(error)
|
|
9115
9210
|
if backend_code_int(api_error) == 49614:
|
|
@@ -9271,7 +9366,9 @@ class AiBuilderFacade:
|
|
|
9271
9366
|
current_schema=rebound_schema,
|
|
9272
9367
|
)
|
|
9273
9368
|
try:
|
|
9369
|
+
schema_write_started_at = time.perf_counter()
|
|
9274
9370
|
self.apps.app_update_form_schema(profile=profile, app_key=target.app_key, payload=rebound_payload)
|
|
9371
|
+
schema_write_ms = (schema_write_ms or 0) + _duration_ms(schema_write_started_at)
|
|
9275
9372
|
except (QingflowApiError, RuntimeError) as error:
|
|
9276
9373
|
api_error = _coerce_api_error(error)
|
|
9277
9374
|
return _failed_from_api_error(
|
|
@@ -9334,6 +9431,22 @@ class AiBuilderFacade:
|
|
|
9334
9431
|
"package_attached": None,
|
|
9335
9432
|
}
|
|
9336
9433
|
response["details"]["relation_field_count"] = relation_field_count
|
|
9434
|
+
if requested_inline_layout:
|
|
9435
|
+
response["inline_form_layout"] = {
|
|
9436
|
+
"requested": True,
|
|
9437
|
+
"section_count": len(requested_inline_layout_sections),
|
|
9438
|
+
"app_key": target.app_key,
|
|
9439
|
+
"applied": True,
|
|
9440
|
+
"applied_in_schema": True,
|
|
9441
|
+
"layout_status": "success",
|
|
9442
|
+
"message": (
|
|
9443
|
+
"applied inline form layout inside schema write"
|
|
9444
|
+
if inline_form_layout_changed
|
|
9445
|
+
else "layout already matched requested state"
|
|
9446
|
+
),
|
|
9447
|
+
"auto_added_fields": inline_form_layout_auto_added_fields,
|
|
9448
|
+
}
|
|
9449
|
+
response["verification"]["inline_form_layout_verified"] = False
|
|
9337
9450
|
if normalized_code_block_fields:
|
|
9338
9451
|
response["normalized_code_block_output_assignment"] = True
|
|
9339
9452
|
response["normalized_code_block_fields"] = normalized_code_block_fields
|
|
@@ -9342,11 +9455,14 @@ class AiBuilderFacade:
|
|
|
9342
9455
|
if schema_readback_delayed_error is not None:
|
|
9343
9456
|
response["details"]["schema_readback_delayed_error"] = schema_readback_delayed_error
|
|
9344
9457
|
response = _apply_permission_outcomes(response, relation_permission_outcome)
|
|
9458
|
+
publish_started_at = time.perf_counter()
|
|
9345
9459
|
response = self._append_publish_result(profile=profile, app_key=target.app_key, publish=publish, response=response)
|
|
9460
|
+
publish_ms = _duration_ms(publish_started_at)
|
|
9346
9461
|
verification_ok = False
|
|
9347
9462
|
tag_ids_after: list[int] = []
|
|
9348
9463
|
package_attached: bool | None = None
|
|
9349
9464
|
verification_error: QingflowApiError | None = None
|
|
9465
|
+
readback_started_at = time.perf_counter()
|
|
9350
9466
|
try:
|
|
9351
9467
|
verified = self.app_read(profile=profile, app_key=target.app_key, include_raw=False)
|
|
9352
9468
|
verified_field_names = {field["name"] for field in verified["schema"]["fields"]}
|
|
@@ -9380,6 +9496,14 @@ class AiBuilderFacade:
|
|
|
9380
9496
|
response["verification"].update(data_display_verification)
|
|
9381
9497
|
if data_display_verification.get("data_display_config_verified") is False:
|
|
9382
9498
|
verification_ok = False
|
|
9499
|
+
if requested_inline_layout and inline_form_layout_target is not None:
|
|
9500
|
+
verified_layout = verified.get("layout") if isinstance(verified.get("layout"), dict) else {}
|
|
9501
|
+
inline_layout_verified = _layouts_equal(verified_layout, inline_form_layout_target) or _layouts_semantically_equal(
|
|
9502
|
+
verified_layout,
|
|
9503
|
+
inline_form_layout_target,
|
|
9504
|
+
)
|
|
9505
|
+
response["verification"]["inline_form_layout_verified"] = inline_layout_verified
|
|
9506
|
+
verification_ok = verification_ok and inline_layout_verified
|
|
9383
9507
|
if relation_degraded_expectations:
|
|
9384
9508
|
relation_verified_fields = cast(list[dict[str, Any]], verified["schema"]["fields"])
|
|
9385
9509
|
try:
|
|
@@ -9412,6 +9536,7 @@ class AiBuilderFacade:
|
|
|
9412
9536
|
tag_ids_after = []
|
|
9413
9537
|
package_attached = None if package_tag_id is None else False
|
|
9414
9538
|
app_base_verified = False
|
|
9539
|
+
readback_ms = _duration_ms(readback_started_at)
|
|
9415
9540
|
response["verification"]["fields_verified"] = verification_ok
|
|
9416
9541
|
response["verification"]["package_attached"] = package_attached
|
|
9417
9542
|
response["verification"]["app_visuals_verified"] = app_base_verified
|
|
@@ -9460,6 +9585,14 @@ class AiBuilderFacade:
|
|
|
9460
9585
|
"message": verification_error.message,
|
|
9461
9586
|
**_transport_error_payload(verification_error),
|
|
9462
9587
|
}
|
|
9588
|
+
_merge_duration_breakdown(
|
|
9589
|
+
response,
|
|
9590
|
+
schema_write_ms=schema_write_ms,
|
|
9591
|
+
inline_layout_ms=inline_layout_ms,
|
|
9592
|
+
publish_ms=publish_ms,
|
|
9593
|
+
readback_ms=readback_ms,
|
|
9594
|
+
total_ms=_duration_ms(apply_started_at),
|
|
9595
|
+
)
|
|
9463
9596
|
return finalize(response)
|
|
9464
9597
|
|
|
9465
9598
|
def app_layout_apply(
|
|
@@ -21636,6 +21769,21 @@ def _layouts_semantically_equal(left: dict[str, Any], right: dict[str, Any]) ->
|
|
|
21636
21769
|
return _layout_field_sequence(left) == _layout_field_sequence(right)
|
|
21637
21770
|
|
|
21638
21771
|
|
|
21772
|
+
def _duration_ms(started_at: float) -> int:
|
|
21773
|
+
return max(0, int((time.perf_counter() - started_at) * 1000))
|
|
21774
|
+
|
|
21775
|
+
|
|
21776
|
+
def _merge_duration_breakdown(response: JSONObject, **items: int | None) -> None:
|
|
21777
|
+
breakdown = response.get("duration_breakdown_ms")
|
|
21778
|
+
if not isinstance(breakdown, dict):
|
|
21779
|
+
breakdown = {}
|
|
21780
|
+
response["duration_breakdown_ms"] = breakdown
|
|
21781
|
+
for key, value in items.items():
|
|
21782
|
+
if value is None:
|
|
21783
|
+
continue
|
|
21784
|
+
breakdown[key] = int(value)
|
|
21785
|
+
|
|
21786
|
+
|
|
21639
21787
|
def _find_field_section_id(layout: dict[str, Any], field_name: str) -> str | None:
|
|
21640
21788
|
for section in layout.get("sections", []) or []:
|
|
21641
21789
|
for row in section.get("rows", []) or []:
|
|
@@ -107,6 +107,26 @@ def _payload_bool(payload: JSONObject, *keys: str, default: bool = True) -> bool
|
|
|
107
107
|
return bool(value)
|
|
108
108
|
|
|
109
109
|
|
|
110
|
+
def _elapsed_ms(started_at: float) -> int:
|
|
111
|
+
return max(0, int((time.perf_counter() - started_at) * 1000))
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def _merge_duration_breakdown(payload: JSONObject, **items: int | None) -> None:
|
|
115
|
+
breakdown = payload.get("duration_breakdown_ms")
|
|
116
|
+
if not isinstance(breakdown, dict):
|
|
117
|
+
breakdown = {}
|
|
118
|
+
payload["duration_breakdown_ms"] = breakdown
|
|
119
|
+
for key, value in items.items():
|
|
120
|
+
if value is None:
|
|
121
|
+
continue
|
|
122
|
+
breakdown[key] = int(value)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def _inline_form_layout_was_handled(payload: JSONObject) -> bool:
|
|
126
|
+
inline_layout = payload.get("inline_form_layout")
|
|
127
|
+
return isinstance(inline_layout, dict) and bool(inline_layout.get("requested"))
|
|
128
|
+
|
|
129
|
+
|
|
110
130
|
PUBLIC_STABLE_FLOW_NODE_TYPES = ["start", "approve", "fill", "copy", "webhook", "end"]
|
|
111
131
|
BUILDER_APPLY_SCHEMA_VERSION = "builder.apply.v1"
|
|
112
132
|
BUILDER_APPLY_TOOL_NAMES = {
|
|
@@ -2359,6 +2379,7 @@ class AiBuilderTools(ToolBase):
|
|
|
2359
2379
|
continue
|
|
2360
2380
|
|
|
2361
2381
|
initial_add_fields, deferred_add_fields = _split_multi_app_initial_add_fields(item, is_new_app=not bool(app_key))
|
|
2382
|
+
inline_layout_sections = list(item.get("_inline_form_layout_sections") or [])
|
|
2362
2383
|
item["_deferred_add_fields"] = deferred_add_fields
|
|
2363
2384
|
shell = self._app_schema_apply_once(
|
|
2364
2385
|
profile=profile,
|
|
@@ -2374,6 +2395,7 @@ class AiBuilderTools(ToolBase):
|
|
|
2374
2395
|
add_fields=initial_add_fields,
|
|
2375
2396
|
update_fields=[],
|
|
2376
2397
|
remove_fields=[],
|
|
2398
|
+
inline_form_layout_sections=inline_layout_sections if not deferred_add_fields else None,
|
|
2377
2399
|
)
|
|
2378
2400
|
public_shell = _publicize_package_fields(shell)
|
|
2379
2401
|
resolved_key = str(public_shell.get("app_key") or "").strip()
|
|
@@ -2471,7 +2493,7 @@ class AiBuilderTools(ToolBase):
|
|
|
2471
2493
|
if bool(existing.get("created")) and not deferred_add_fields and not update_fields and not remove_fields:
|
|
2472
2494
|
shell_result = existing.get("shell_result") if isinstance(existing.get("shell_result"), dict) else {}
|
|
2473
2495
|
layout_sections = list(compiled_item.get("_inline_form_layout_sections") or [])
|
|
2474
|
-
if layout_sections:
|
|
2496
|
+
if layout_sections and not _inline_form_layout_was_handled(shell_result):
|
|
2475
2497
|
shell_result = self._apply_inline_form_layout_after_schema(
|
|
2476
2498
|
profile=profile,
|
|
2477
2499
|
schema_result=shell_result,
|
|
@@ -2522,10 +2544,11 @@ class AiBuilderTools(ToolBase):
|
|
|
2522
2544
|
add_fields=deferred_add_fields,
|
|
2523
2545
|
update_fields=update_fields,
|
|
2524
2546
|
remove_fields=remove_fields,
|
|
2547
|
+
inline_form_layout_sections=list(compiled_item.get("_inline_form_layout_sections") or []),
|
|
2525
2548
|
)
|
|
2526
2549
|
public_result = _publicize_package_fields(field_result)
|
|
2527
2550
|
layout_sections = list(compiled_item.get("_inline_form_layout_sections") or [])
|
|
2528
|
-
if layout_sections:
|
|
2551
|
+
if layout_sections and not _inline_form_layout_was_handled(public_result):
|
|
2529
2552
|
public_result = self._apply_inline_form_layout_after_schema(
|
|
2530
2553
|
profile=profile,
|
|
2531
2554
|
schema_result=public_result,
|
|
@@ -2632,6 +2655,7 @@ class AiBuilderTools(ToolBase):
|
|
|
2632
2655
|
inline_form_layout_sections: list[JSONObject] | None = None,
|
|
2633
2656
|
) -> JSONObject:
|
|
2634
2657
|
"""执行内部辅助逻辑。"""
|
|
2658
|
+
started_at = time.perf_counter()
|
|
2635
2659
|
effective_app_name = app_name or app_title
|
|
2636
2660
|
system_field_failure = _reserved_system_field_name_failure(
|
|
2637
2661
|
tool_name="app_schema_apply",
|
|
@@ -2686,6 +2710,10 @@ class AiBuilderTools(ToolBase):
|
|
|
2686
2710
|
parsed_add = [FieldPatch.model_validate(item) for item in plan_args.get("add_fields") or []]
|
|
2687
2711
|
parsed_update = [FieldUpdatePatch.model_validate(item) for item in plan_args.get("update_fields") or []]
|
|
2688
2712
|
parsed_remove = [FieldRemovePatch.model_validate(item) for item in plan_args.get("remove_fields") or []]
|
|
2713
|
+
parsed_inline_layout = [
|
|
2714
|
+
LayoutSectionPatch.model_validate(item)
|
|
2715
|
+
for item in (inline_form_layout_sections or [])
|
|
2716
|
+
]
|
|
2689
2717
|
except ValidationError as exc:
|
|
2690
2718
|
return _validation_failure(
|
|
2691
2719
|
str(exc),
|
|
@@ -2721,6 +2749,16 @@ class AiBuilderTools(ToolBase):
|
|
|
2721
2749
|
"add_fields": [patch.model_dump(mode="json") for patch in parsed_add],
|
|
2722
2750
|
"update_fields": [patch.model_dump(mode="json") for patch in parsed_update],
|
|
2723
2751
|
"remove_fields": [patch.model_dump(mode="json") for patch in parsed_remove],
|
|
2752
|
+
**(
|
|
2753
|
+
{
|
|
2754
|
+
"inline_form_layout_sections": [
|
|
2755
|
+
section.model_dump(mode="json", exclude_none=True)
|
|
2756
|
+
for section in parsed_inline_layout
|
|
2757
|
+
]
|
|
2758
|
+
}
|
|
2759
|
+
if parsed_inline_layout
|
|
2760
|
+
else {}
|
|
2761
|
+
),
|
|
2724
2762
|
}
|
|
2725
2763
|
result = _safe_tool_call(
|
|
2726
2764
|
lambda: self._facade.app_schema_apply(
|
|
@@ -2736,13 +2774,15 @@ class AiBuilderTools(ToolBase):
|
|
|
2736
2774
|
add_fields=parsed_add,
|
|
2737
2775
|
update_fields=parsed_update,
|
|
2738
2776
|
remove_fields=parsed_remove,
|
|
2777
|
+
inline_form_layout_sections=parsed_inline_layout,
|
|
2739
2778
|
),
|
|
2740
2779
|
error_code="SCHEMA_APPLY_FAILED",
|
|
2741
2780
|
normalized_args=normalized_args,
|
|
2742
2781
|
suggested_next_call={"tool_name": "app_schema_apply", "arguments": {"profile": profile, **normalized_args}},
|
|
2743
2782
|
)
|
|
2744
2783
|
public_result = _publicize_package_fields(result)
|
|
2745
|
-
if inline_form_layout_sections:
|
|
2784
|
+
if inline_form_layout_sections and not _inline_form_layout_was_handled(public_result):
|
|
2785
|
+
layout_started_at = time.perf_counter()
|
|
2746
2786
|
public_result = self._apply_inline_form_layout_after_schema(
|
|
2747
2787
|
profile=profile,
|
|
2748
2788
|
schema_result=public_result,
|
|
@@ -2750,6 +2790,11 @@ class AiBuilderTools(ToolBase):
|
|
|
2750
2790
|
publish=publish,
|
|
2751
2791
|
sections=inline_form_layout_sections,
|
|
2752
2792
|
)
|
|
2793
|
+
_merge_duration_breakdown(
|
|
2794
|
+
public_result,
|
|
2795
|
+
inline_layout_ms=_elapsed_ms(layout_started_at),
|
|
2796
|
+
)
|
|
2797
|
+
_merge_duration_breakdown(public_result, total_ms=_elapsed_ms(started_at))
|
|
2753
2798
|
return public_result
|
|
2754
2799
|
|
|
2755
2800
|
def _apply_inline_form_layout_after_schema(
|