@josephyan/qingflow-cli 0.2.0-beta.988 → 0.2.0-beta.990

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.
@@ -13,6 +13,7 @@ from mcp.server.fastmcp import FastMCP
13
13
 
14
14
  from ..config import DEFAULT_PROFILE, DEFAULT_RECORD_LIST_TYPE
15
15
  from ..errors import QingflowApiError, raise_tool_error
16
+ from ..id_utils import normalize_positive_id_int, stringify_backend_id
16
17
  from ..json_types import JSONObject, JSONScalar, JSONValue
17
18
  from ..list_type_labels import (
18
19
  SYSTEM_VIEW_DEFINITIONS,
@@ -284,7 +285,7 @@ class RecordTools(ToolBase):
284
285
  profile: str = DEFAULT_PROFILE,
285
286
  app_key: str = "",
286
287
  field_id: int = 0,
287
- record_id: int | None = None,
288
+ record_id: str | None = None,
288
289
  workflow_node_id: int | None = None,
289
290
  fields: JSONObject | None = None,
290
291
  keyword: str = "",
@@ -315,7 +316,7 @@ class RecordTools(ToolBase):
315
316
  profile: str = DEFAULT_PROFILE,
316
317
  app_key: str = "",
317
318
  field_id: int = 0,
318
- record_id: int | None = None,
319
+ record_id: str | None = None,
319
320
  workflow_node_id: int | None = None,
320
321
  fields: JSONObject | None = None,
321
322
  keyword: str = "",
@@ -407,7 +408,7 @@ class RecordTools(ToolBase):
407
408
  def record_get(
408
409
  profile: str = DEFAULT_PROFILE,
409
410
  app_key: str = "",
410
- record_id: int = 0,
411
+ record_id: str = "",
411
412
  columns: list[JSONObject | int] | None = None,
412
413
  view_id: str | None = None,
413
414
  workflow_node_id: int | None = None,
@@ -439,7 +440,7 @@ class RecordTools(ToolBase):
439
440
  @mcp.tool()
440
441
  def record_update_schema_get(
441
442
  app_key: str = "",
442
- record_id: int = 0,
443
+ record_id: str = "",
443
444
  output_profile: str = "normal",
444
445
  ) -> JSONObject:
445
446
  return self.record_update_schema_get_public(
@@ -479,7 +480,7 @@ class RecordTools(ToolBase):
479
480
  )
480
481
  def record_update(
481
482
  app_key: str = "",
482
- record_id: int | None = None,
483
+ record_id: str | None = None,
483
484
  fields: JSONObject | None = None,
484
485
  items: list[JSONObject] | None = None,
485
486
  dry_run: bool = False,
@@ -505,8 +506,8 @@ class RecordTools(ToolBase):
505
506
  )
506
507
  def record_delete(
507
508
  app_key: str = "",
508
- record_id: int | None = None,
509
- record_ids: list[int] | None = None,
509
+ record_id: str | None = None,
510
+ record_ids: list[str] | None = None,
510
511
  output_profile: str = "normal",
511
512
  ) -> JSONObject:
512
513
  return self.record_delete_public(
@@ -784,14 +785,13 @@ class RecordTools(ToolBase):
784
785
  *,
785
786
  profile: str = DEFAULT_PROFILE,
786
787
  app_key: str,
787
- record_id: int,
788
+ record_id: Any,
788
789
  output_profile: str = "normal",
789
790
  ) -> JSONObject:
790
791
  """执行记录相关逻辑。"""
791
792
  if not app_key:
792
793
  raise_tool_error(QingflowApiError.config_error("app_key is required"))
793
- if record_id <= 0:
794
- raise_tool_error(QingflowApiError.config_error("record_id is required"))
794
+ record_id_int = normalize_positive_id_int(record_id, field_name="record_id")
795
795
  normalized_output_profile = self._normalize_public_output_profile(output_profile)
796
796
 
797
797
  def runner(session_profile, context):
@@ -815,7 +815,7 @@ class RecordTools(ToolBase):
815
815
  probes = self._probe_candidate_record_contexts(
816
816
  context,
817
817
  app_key=app_key,
818
- apply_id=record_id,
818
+ apply_id=record_id_int,
819
819
  candidate_routes=candidate_routes,
820
820
  )
821
821
  probe_summary: list[JSONObject] = []
@@ -906,7 +906,7 @@ class RecordTools(ToolBase):
906
906
  ws_id=ws_id,
907
907
  request_route=request_route,
908
908
  app_key=app_key,
909
- record_id=record_id,
909
+ record_id=record_id_int,
910
910
  blockers=blockers,
911
911
  warnings=warnings,
912
912
  recommended_next_actions=recommended_next_actions,
@@ -949,7 +949,7 @@ class RecordTools(ToolBase):
949
949
  ws_id=ws_id,
950
950
  request_route=request_route,
951
951
  app_key=app_key,
952
- record_id=record_id,
952
+ record_id=record_id_int,
953
953
  blockers=["NO_WRITABLE_FIELDS_FOR_RECORD"],
954
954
  warnings=[item["message"] for item in warnings if isinstance(item.get("message"), str)],
955
955
  recommended_next_actions=[
@@ -969,7 +969,7 @@ class RecordTools(ToolBase):
969
969
  "request_route": request_route,
970
970
  "warnings": warnings,
971
971
  "app_key": app_key,
972
- "record_id": record_id,
972
+ "record_id": stringify_backend_id(record_id_int),
973
973
  "schema_scope": "update_ready",
974
974
  "writable_fields": writable_fields,
975
975
  "payload_template": {
@@ -1009,7 +1009,7 @@ class RecordTools(ToolBase):
1009
1009
  "request_route": request_route,
1010
1010
  "warnings": [{"code": "PREFLIGHT_WARNING", "message": item} for item in warnings],
1011
1011
  "app_key": app_key,
1012
- "record_id": record_id,
1012
+ "record_id": stringify_backend_id(record_id),
1013
1013
  "schema_scope": "update_ready",
1014
1014
  "blockers": blockers,
1015
1015
  "writable_fields": [],
@@ -1281,7 +1281,7 @@ class RecordTools(ToolBase):
1281
1281
  profile: str,
1282
1282
  app_key: str,
1283
1283
  field_id: int,
1284
- record_id: int | None = None,
1284
+ record_id: Any | None = None,
1285
1285
  workflow_node_id: int | None = None,
1286
1286
  fields: JSONObject | None = None,
1287
1287
  keyword: str,
@@ -1297,6 +1297,12 @@ class RecordTools(ToolBase):
1297
1297
  raise_tool_error(QingflowApiError.config_error("page_num must be positive"))
1298
1298
  if page_size <= 0:
1299
1299
  raise_tool_error(QingflowApiError.config_error("page_size must be positive"))
1300
+ record_id_int = (
1301
+ normalize_positive_id_int(record_id, field_name="record_id")
1302
+ if record_id is not None
1303
+ else None
1304
+ )
1305
+ record_id_text = stringify_backend_id(record_id_int) if record_id_int is not None else None
1300
1306
 
1301
1307
  def runner(session_profile, context):
1302
1308
  index = self._get_field_index(profile, context, app_key, force_refresh=False)
@@ -1316,7 +1322,7 @@ class RecordTools(ToolBase):
1316
1322
  )
1317
1323
  normalized_fields = fields if isinstance(fields, dict) else {}
1318
1324
  runtime_lookup = self._candidate_lookup_uses_runtime_scope(
1319
- record_id=record_id,
1325
+ record_id=record_id_int,
1320
1326
  workflow_node_id=workflow_node_id,
1321
1327
  fields=normalized_fields,
1322
1328
  )
@@ -1327,7 +1333,7 @@ class RecordTools(ToolBase):
1327
1333
  profile,
1328
1334
  context,
1329
1335
  app_key=app_key,
1330
- record_id=record_id,
1336
+ record_id=record_id_int,
1331
1337
  workflow_node_id=workflow_node_id,
1332
1338
  fields=normalized_fields,
1333
1339
  )
@@ -1366,7 +1372,7 @@ class RecordTools(ToolBase):
1366
1372
  "app_key": app_key,
1367
1373
  "field_id": field.que_id,
1368
1374
  "field_title": field.que_title,
1369
- "record_id": record_id,
1375
+ "record_id": record_id_text,
1370
1376
  "workflow_node_id": workflow_node_id,
1371
1377
  "fields_present": bool(normalized_fields),
1372
1378
  "keyword": keyword,
@@ -1385,7 +1391,7 @@ class RecordTools(ToolBase):
1385
1391
  profile: str,
1386
1392
  app_key: str,
1387
1393
  field_id: int,
1388
- record_id: int | None = None,
1394
+ record_id: Any | None = None,
1389
1395
  workflow_node_id: int | None = None,
1390
1396
  fields: JSONObject | None = None,
1391
1397
  keyword: str,
@@ -1401,6 +1407,12 @@ class RecordTools(ToolBase):
1401
1407
  raise_tool_error(QingflowApiError.config_error("page_num must be positive"))
1402
1408
  if page_size <= 0:
1403
1409
  raise_tool_error(QingflowApiError.config_error("page_size must be positive"))
1410
+ record_id_int = (
1411
+ normalize_positive_id_int(record_id, field_name="record_id")
1412
+ if record_id is not None
1413
+ else None
1414
+ )
1415
+ record_id_text = stringify_backend_id(record_id_int) if record_id_int is not None else None
1404
1416
 
1405
1417
  def runner(session_profile, context):
1406
1418
  index = self._get_field_index(profile, context, app_key, force_refresh=False)
@@ -1420,7 +1432,7 @@ class RecordTools(ToolBase):
1420
1432
  )
1421
1433
  normalized_fields = fields if isinstance(fields, dict) else {}
1422
1434
  runtime_lookup = self._candidate_lookup_uses_runtime_scope(
1423
- record_id=record_id,
1435
+ record_id=record_id_int,
1424
1436
  workflow_node_id=workflow_node_id,
1425
1437
  fields=normalized_fields,
1426
1438
  )
@@ -1431,7 +1443,7 @@ class RecordTools(ToolBase):
1431
1443
  profile,
1432
1444
  context,
1433
1445
  app_key=app_key,
1434
- record_id=record_id,
1446
+ record_id=record_id_int,
1435
1447
  workflow_node_id=workflow_node_id,
1436
1448
  fields=normalized_fields,
1437
1449
  )
@@ -1487,7 +1499,7 @@ class RecordTools(ToolBase):
1487
1499
  "app_key": app_key,
1488
1500
  "field_id": field.que_id,
1489
1501
  "field_title": field.que_title,
1490
- "record_id": record_id,
1502
+ "record_id": record_id_text,
1491
1503
  "workflow_node_id": workflow_node_id,
1492
1504
  "fields_present": bool(normalized_fields),
1493
1505
  "keyword": keyword,
@@ -1753,7 +1765,7 @@ class RecordTools(ToolBase):
1753
1765
  *,
1754
1766
  profile: str,
1755
1767
  app_key: str,
1756
- record_id: int,
1768
+ record_id: Any,
1757
1769
  columns: list[JSONObject | int],
1758
1770
  view_id: str | None = None,
1759
1771
  workflow_node_id: int | None = None,
@@ -1761,8 +1773,7 @@ class RecordTools(ToolBase):
1761
1773
  ) -> JSONObject:
1762
1774
  """执行记录相关逻辑。"""
1763
1775
  normalized_output_profile = self._normalize_public_output_profile(output_profile, allow_normalized=True)
1764
- if record_id <= 0:
1765
- raise_tool_error(QingflowApiError.config_error("record_id must be positive"))
1776
+ record_id_int = normalize_positive_id_int(record_id, field_name="record_id")
1766
1777
  normalized_columns = _normalize_public_column_selectors(columns)
1767
1778
 
1768
1779
  def runner(session_profile, context):
@@ -1791,7 +1802,7 @@ class RecordTools(ToolBase):
1791
1802
  result = self.backend.request(
1792
1803
  "GET",
1793
1804
  context,
1794
- f"/view/{resolved_view.view_selection.view_key}/apply/{record_id}",
1805
+ f"/view/{resolved_view.view_selection.view_key}/apply/{record_id_int}",
1795
1806
  )
1796
1807
  used_list_type = None
1797
1808
  else:
@@ -1808,7 +1819,7 @@ class RecordTools(ToolBase):
1808
1819
  result = self.backend.request(
1809
1820
  "GET",
1810
1821
  context,
1811
- f"/app/{app_key}/apply/{record_id}",
1822
+ f"/app/{app_key}/apply/{record_id_int}",
1812
1823
  params={"role": 1, "listType": lt},
1813
1824
  )
1814
1825
  used_list_type = lt
@@ -1823,7 +1834,7 @@ class RecordTools(ToolBase):
1823
1834
  raise last_error
1824
1835
  raise_tool_error(QingflowApiError.config_error("record_get failed: no accessible listType"))
1825
1836
  answer_list = result.get("answers") if isinstance(result, dict) and isinstance(result.get("answers"), list) else []
1826
- row = _build_flat_row(cast(list[JSONValue], answer_list), selected_fields, apply_id=record_id)
1837
+ row = _build_flat_row(cast(list[JSONValue], answer_list), selected_fields, apply_id=record_id_int)
1827
1838
  normalized_record, normalized_ambiguous_fields = _build_normalized_row_from_answers(
1828
1839
  cast(list[JSONValue], answer_list),
1829
1840
  selected_fields,
@@ -1851,7 +1862,7 @@ class RecordTools(ToolBase):
1851
1862
  if normalized_columns
1852
1863
  else list(index.by_id.values())
1853
1864
  )
1854
- row = _build_flat_row(cast(list[JSONValue], answer_list), selected_fields, apply_id=record_id)
1865
+ row = _build_flat_row(cast(list[JSONValue], answer_list), selected_fields, apply_id=record_id_int)
1855
1866
  normalized_record, normalized_ambiguous_fields = _build_normalized_row_from_answers(
1856
1867
  cast(list[JSONValue], answer_list),
1857
1868
  selected_fields,
@@ -1874,7 +1885,7 @@ class RecordTools(ToolBase):
1874
1885
  "output_profile": normalized_output_profile,
1875
1886
  "data": {
1876
1887
  "app_key": app_key,
1877
- "record_id": _public_record_id_text(record_id),
1888
+ "record_id": _public_record_id_text(record_id_int),
1878
1889
  "record": row,
1879
1890
  "selection": {
1880
1891
  "columns": [_column_selector_payload(field_id) for field_id in normalized_columns] if normalized_columns else [],
@@ -1975,7 +1986,7 @@ class RecordTools(ToolBase):
1975
1986
  *,
1976
1987
  profile: str = DEFAULT_PROFILE,
1977
1988
  app_key: str,
1978
- record_id: int | None,
1989
+ record_id: Any | None,
1979
1990
  fields: JSONObject | None = None,
1980
1991
  items: list[JSONObject] | None = None,
1981
1992
  dry_run: bool = False,
@@ -2004,14 +2015,15 @@ class RecordTools(ToolBase):
2004
2015
  )
2005
2016
  if dry_run:
2006
2017
  raise_tool_error(QingflowApiError.config_error("dry_run currently requires items"))
2007
- if record_id is None or record_id <= 0:
2018
+ if record_id is None:
2008
2019
  raise_tool_error(QingflowApiError.config_error("record_id is required"))
2020
+ record_id_int = normalize_positive_id_int(record_id, field_name="record_id")
2009
2021
  if fields is not None and not isinstance(fields, dict):
2010
2022
  raise_tool_error(QingflowApiError.config_error("fields must be an object map keyed by field title"))
2011
2023
  return self._record_update_public_single(
2012
2024
  profile=profile,
2013
2025
  app_key=app_key,
2014
- record_id=record_id,
2026
+ record_id=record_id_int,
2015
2027
  fields=cast(JSONObject, fields or {}),
2016
2028
  verify_write=verify_write,
2017
2029
  output_profile=normalized_output_profile,
@@ -2201,7 +2213,7 @@ class RecordTools(ToolBase):
2201
2213
  def _normalize_public_record_update_batch_items(
2202
2214
  self,
2203
2215
  *,
2204
- record_id: int | None,
2216
+ record_id: Any | None,
2205
2217
  fields: JSONObject | None,
2206
2218
  items: list[JSONObject] | None,
2207
2219
  ) -> list[JSONObject]:
@@ -2217,9 +2229,10 @@ class RecordTools(ToolBase):
2217
2229
  for index, item in enumerate(items):
2218
2230
  if not isinstance(item, dict):
2219
2231
  raise_tool_error(QingflowApiError.config_error(f"items[{index}] must be an object"))
2220
- normalized_record_id = _coerce_count(item.get("record_id"))
2221
- if normalized_record_id is None or normalized_record_id <= 0:
2222
- raise_tool_error(QingflowApiError.config_error(f"items[{index}].record_id must be a positive integer"))
2232
+ normalized_record_id = normalize_positive_id_int(
2233
+ item.get("record_id"),
2234
+ field_name=f"items[{index}].record_id",
2235
+ )
2223
2236
  if normalized_record_id in seen_record_ids:
2224
2237
  raise_tool_error(
2225
2238
  QingflowApiError.config_error(f"duplicate record_id in items: {normalized_record_id}")
@@ -3069,22 +3082,26 @@ class RecordTools(ToolBase):
3069
3082
  *,
3070
3083
  profile: str = DEFAULT_PROFILE,
3071
3084
  app_key: str,
3072
- record_id: int | None = None,
3073
- record_ids: list[int] | None = None,
3085
+ record_id: Any | None = None,
3086
+ record_ids: list[Any] | None = None,
3074
3087
  output_profile: str = "normal",
3075
3088
  ) -> JSONObject:
3076
3089
  """执行记录相关逻辑。"""
3077
3090
  normalized_output_profile = self._normalize_public_output_profile(output_profile)
3078
3091
  if not app_key:
3079
3092
  raise_tool_error(QingflowApiError.config_error("app_key is required"))
3080
- normalized_record_ids = [item for item in (record_ids or []) if isinstance(item, int) and item > 0]
3081
- delete_ids = normalized_record_ids or ([record_id] if record_id is not None and record_id > 0 else [])
3093
+ normalized_record_ids: list[int] = []
3094
+ for index, item in enumerate(record_ids or []):
3095
+ normalized_record_ids.append(normalize_positive_id_int(item, field_name=f"record_ids[{index}]"))
3096
+ delete_ids = normalized_record_ids
3097
+ if not delete_ids and record_id is not None:
3098
+ delete_ids = [normalize_positive_id_int(record_id, field_name="record_id")]
3082
3099
  if not delete_ids:
3083
3100
  raise_tool_error(QingflowApiError.config_error("record_id or record_ids is required"))
3084
3101
  normalized_payload = {
3085
3102
  "operation": "delete",
3086
- "record_id": record_id,
3087
- "record_ids": delete_ids,
3103
+ "record_id": stringify_backend_id(record_id) if record_id is not None else None,
3104
+ "record_ids": [stringify_backend_id(item) for item in delete_ids],
3088
3105
  "answers": [],
3089
3106
  "submit_type": 1,
3090
3107
  }
@@ -8527,8 +8544,8 @@ class RecordTools(ToolBase):
8527
8544
  """执行内部辅助逻辑。"""
8528
8545
  payload: JSONObject = {
8529
8546
  "operation": operation,
8530
- "record_id": record_id,
8531
- "record_ids": record_ids,
8547
+ "record_id": stringify_backend_id(record_id) if record_id is not None else None,
8548
+ "record_ids": [stringify_backend_id(item) for item in record_ids],
8532
8549
  "answers": normalized_answers,
8533
8550
  "submit_type": submit_type,
8534
8551
  }
@@ -8727,7 +8744,7 @@ class RecordTools(ToolBase):
8727
8744
  "output_profile": output_profile,
8728
8745
  "data": {
8729
8746
  "action": {"operation": operation, "executed": True},
8730
- "resource": raw_apply.get("resource"),
8747
+ "resource": _public_record_resource(raw_apply.get("resource")),
8731
8748
  "verification": raw_apply.get("verification"),
8732
8749
  "normalized_payload": normalized_payload,
8733
8750
  "blockers": [],
@@ -10072,8 +10089,9 @@ class RecordTools(ToolBase):
10072
10089
  """执行内部辅助逻辑。"""
10073
10090
  if not app_key:
10074
10091
  raise_tool_error(QingflowApiError.config_error("app_key is required"))
10075
- normalized_apply_id = _coerce_count(apply_id)
10076
- if normalized_apply_id is None or normalized_apply_id <= 0:
10092
+ try:
10093
+ normalized_apply_id = normalize_positive_id_int(apply_id, field_name="apply_id")
10094
+ except QingflowApiError:
10077
10095
  raise_tool_error(QingflowApiError.config_error("apply_id must be positive"))
10078
10096
  return normalized_apply_id
10079
10097
 
@@ -11084,10 +11102,14 @@ def _build_flat_row(answer_list: list[JSONValue], fields: list[FormField], *, ap
11084
11102
  return row
11085
11103
 
11086
11104
 
11087
- def _public_record_id_text(record_id: int | None) -> str | None:
11088
- if record_id is None or record_id <= 0:
11105
+ def _public_record_id_text(record_id: Any) -> str | None:
11106
+ if record_id is None or isinstance(record_id, bool):
11089
11107
  return None
11090
- return str(record_id)
11108
+ if isinstance(record_id, int) and record_id <= 0:
11109
+ return None
11110
+ if isinstance(record_id, str) and (not record_id.strip() or not record_id.strip().isdecimal()):
11111
+ return None
11112
+ return stringify_backend_id(record_id)
11091
11113
 
11092
11114
 
11093
11115
  def _normalize_public_record_rows(rows: list[JSONValue]) -> list[JSONObject]:
@@ -11640,10 +11662,29 @@ def _field_mapping_entry(role: str, field: FormField | None, *, requested: str)
11640
11662
  }
11641
11663
 
11642
11664
 
11643
- def _record_resource_payload(record_id: int | None) -> JSONObject | None:
11644
- if record_id is None or record_id <= 0:
11665
+ def _record_resource_payload(record_id: Any) -> JSONObject | None:
11666
+ public_record_id = _public_record_id_text(record_id)
11667
+ if public_record_id is None:
11645
11668
  return None
11646
- return {"type": "record", "record_id": record_id, "apply_id": record_id}
11669
+ return {"type": "record", "record_id": public_record_id, "apply_id": public_record_id}
11670
+
11671
+
11672
+ def _public_record_resource(resource: Any) -> Any:
11673
+ if not isinstance(resource, dict) or resource.get("type") != "record":
11674
+ return resource
11675
+ payload = dict(resource)
11676
+ if "record_id" in payload:
11677
+ payload["record_id"] = _public_record_id_text(payload.get("record_id"))
11678
+ if "apply_id" in payload:
11679
+ payload["apply_id"] = _public_record_id_text(payload.get("apply_id"))
11680
+ record_ids = payload.get("record_ids")
11681
+ if isinstance(record_ids, list):
11682
+ payload["record_ids"] = [
11683
+ stringify_backend_id(item)
11684
+ for item in record_ids
11685
+ if stringify_backend_id(item) is not None
11686
+ ]
11687
+ return payload
11647
11688
 
11648
11689
 
11649
11690
  def _query_id() -> str: