@josephyan/qingflow-cli 1.1.4 → 1.1.6
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 +7 -3
- package/docs/local-agent-install.md +57 -6
- package/entry_point.py +1 -1
- package/npm/bin/qingflow-skills.mjs +5 -0
- package/npm/bin/qingflow.mjs +1 -34
- package/npm/lib/runtime.mjs +21 -101
- package/npm/scripts/postinstall.mjs +1 -10
- package/package.json +3 -2
- package/pyproject.toml +1 -1
- package/skills/qingflow-cli/SKILL.md +58 -44
- package/skills/qingflow-cli/manifest.yaml +1 -1
- package/skills/qingflow-cli/reference/00-INDEX.md +35 -0
- package/skills/qingflow-cli/reference/builder/10-build-single-app.md +38 -0
- package/skills/qingflow-cli/reference/builder/20-build-complete-system.md +39 -0
- package/skills/qingflow-cli/reference/{QINGFLOW_CLI_SCHEMA_APPLY_FIELD_TYPES_AND_SCENARIOS.md → builder/30-schema-fields.md} +52 -10
- package/skills/qingflow-cli/reference/builder/40-layout.md +52 -0
- package/skills/qingflow-cli/reference/{QINGFLOW_CLI_BUILDER_VIEWS_WORKFLOW.md → builder/50-views.md} +39 -15
- package/skills/qingflow-cli/reference/{QINGFLOW_CLI_BUILDER_CHARTS_WORKFLOW.md → builder/60-charts.md} +36 -13
- package/skills/qingflow-cli/reference/{QINGFLOW_CLI_BUILDER_PORTAL_WORKFLOW.md → builder/70-portal.md} +36 -13
- package/skills/qingflow-cli/reference/builder/80-buttons-associated-resources.md +41 -0
- package/skills/qingflow-cli/reference/builder/90-workflow.md +34 -0
- package/skills/qingflow-cli/reference/builder/99-publish-verify.md +46 -0
- package/skills/qingflow-cli/reference/builder/README.md +41 -0
- package/skills/qingflow-cli/reference/builder/code-integrations/README.md +130 -0
- package/skills/qingflow-cli/reference/builder/code-integrations/code-block.md +66 -0
- package/skills/qingflow-cli/reference/builder/code-integrations/q-linker.md +77 -0
- package/skills/qingflow-cli/reference/{QINGFLOW_CLI_BUILDER_APP_DELIVERY_WORKFLOW.md → builder/reference/app-delivery-sop.md} +26 -16
- package/skills/qingflow-cli/reference/builder/reference/legacy-playbooks/README.md +293 -0
- package/skills/qingflow-cli/reference/builder/reference/legacy-playbooks/build-complete-system.md +809 -0
- package/skills/qingflow-cli/reference/builder/reference/legacy-playbooks/build-single-app.md +830 -0
- package/skills/qingflow-cli/reference/builder/reference/legacy-playbooks/complete-system-development-guide.md +123 -0
- package/skills/qingflow-cli/reference/builder/reference/legacy-playbooks/create-app.md +182 -0
- package/skills/qingflow-cli/reference/builder/reference/legacy-playbooks/environments.md +63 -0
- package/skills/qingflow-cli/reference/builder/reference/legacy-playbooks/flow-actors-and-permissions.md +142 -0
- package/skills/qingflow-cli/reference/builder/reference/legacy-playbooks/gotchas.md +108 -0
- package/skills/qingflow-cli/reference/builder/reference/legacy-playbooks/match-rules.md +114 -0
- package/skills/qingflow-cli/reference/builder/reference/legacy-playbooks/public-surface-sync.md +75 -0
- package/skills/qingflow-cli/reference/builder/reference/legacy-playbooks/single-app-development-guide.md +58 -0
- package/skills/qingflow-cli/reference/builder/reference/legacy-playbooks/solution-playbooks.md +52 -0
- package/skills/qingflow-cli/reference/builder/reference/legacy-playbooks/tool-selection.md +107 -0
- package/skills/qingflow-cli/reference/builder/reference/legacy-playbooks/update-flow.md +7 -0
- package/skills/qingflow-cli/reference/builder/reference/legacy-playbooks/update-layout.md +7 -0
- package/skills/qingflow-cli/reference/builder/reference/legacy-playbooks/update-schema.md +7 -0
- package/skills/qingflow-cli/reference/builder/reference/legacy-playbooks/update-views.md +7 -0
- package/skills/qingflow-cli/reference/builder/workflow/01-overview.md +45 -0
- package/skills/qingflow-cli/reference/builder/workflow/02-update-mode.md +53 -0
- package/skills/qingflow-cli/reference/builder/workflow/03-flow-patterns.md +57 -0
- package/skills/qingflow-cli/reference/builder/workflow/04-stage1-business-modeling.md +131 -0
- package/skills/qingflow-cli/reference/builder/workflow/05-stage2-members-roles.md +29 -0
- package/skills/qingflow-cli/reference/builder/workflow/06-stage3-build-spec.md +165 -0
- package/skills/qingflow-cli/reference/builder/workflow/07-stage4-validate-spec.md +33 -0
- package/skills/qingflow-cli/reference/builder/workflow/08-stage5-apply-verify.md +51 -0
- package/skills/qingflow-cli/reference/builder/workflow/09-stage6-summary.md +88 -0
- package/skills/qingflow-cli/reference/builder/workflow/10-node-config-reference.md +93 -0
- package/skills/qingflow-cli/reference/builder/workflow/11-troubleshooting.md +15 -0
- package/skills/qingflow-cli/reference/builder/workflow/README.md +88 -0
- package/skills/qingflow-cli/reference/builder/workflow/workflow-schema.json +1754 -0
- package/skills/qingflow-cli/reference/{QINGFLOW_CLI_ADMIN_CHEATSHEET.md → core/QINGFLOW_CLI_ADMIN_CHEATSHEET.md} +3 -3
- package/skills/qingflow-cli/reference/{QINGFLOW_CLI_DATA_RETRIEVAL_WORKFLOW.md → core/QINGFLOW_CLI_DATA_RETRIEVAL_WORKFLOW.md} +6 -6
- package/skills/qingflow-cli/reference/{QINGFLOW_CLI_EXPLORATION_REPORT.md → core/QINGFLOW_CLI_EXPLORATION_REPORT.md} +2 -2
- package/skills/qingflow-cli/reference/{QINGFLOW_CLI_FIELD_DATA_TYPES.md → core/QINGFLOW_CLI_FIELD_DATA_TYPES.md} +11 -11
- package/skills/qingflow-cli/reference/{QINGFLOW_CLI_MEMBER_CHEATSHEET.md → core/QINGFLOW_CLI_MEMBER_CHEATSHEET.md} +4 -4
- package/skills/qingflow-cli/reference/{QINGFLOW_CLI_ONE_SHOT_CHEATSHEET.md → core/QINGFLOW_CLI_ONE_SHOT_CHEATSHEET.md} +4 -4
- package/skills/qingflow-cli/reference/{QINGFLOW_CLI_RECORD_CREATE_WORKFLOW.md → record/QINGFLOW_CLI_RECORD_CREATE_WORKFLOW.md} +3 -3
- package/skills/qingflow-cli/reference/record/QINGFLOW_CLI_RECORD_DELETE_WORKFLOW.md +31 -0
- package/skills/qingflow-cli/reference/{QINGFLOW_CLI_RECORD_IMPORT_WORKFLOW.md → record/QINGFLOW_CLI_RECORD_IMPORT_WORKFLOW.md} +4 -4
- package/skills/qingflow-cli/reference/{QINGFLOW_CLI_RECORD_UPDATE_WORKFLOW.md → record/QINGFLOW_CLI_RECORD_UPDATE_WORKFLOW.md} +7 -7
- package/skills/qingflow-cli/reference/record/analysis/README.md +130 -0
- package/skills/qingflow-cli/reference/record/analysis/analysis-gotchas.md +91 -0
- package/skills/qingflow-cli/reference/record/analysis/analysis-patterns.md +112 -0
- package/skills/qingflow-cli/reference/record/analysis/business-context.md +74 -0
- package/skills/qingflow-cli/reference/record/analysis/confidence-reporting.md +69 -0
- package/skills/qingflow-cli/reference/record/analysis/data-access-playbook.md +106 -0
- package/skills/qingflow-cli/reference/record/analysis/pandas-recipes.md +172 -0
- package/skills/qingflow-cli/reference/record/analysis/report-format.md +76 -0
- package/skills/qingflow-cli/reference/record/insert/README.md +75 -0
- package/skills/qingflow-cli/reference/{QINGFLOW_CLI_TASK_CONTEXT_WORKFLOW.md → task/QINGFLOW_CLI_TASK_CONTEXT_WORKFLOW.md} +5 -5
- package/skills/qingflow-cli/reference/task/ops/README.md +131 -0
- package/skills/qingflow-cli/reference/task/ops/environments.md +43 -0
- package/skills/qingflow-cli/reference/task/ops/workflow-usage.md +26 -0
- package/skills/qingflow-cli/scripts/validate_system_build_summary.py +124 -0
- package/skills/qingflow-cli/scripts/workflow/diff_flow_spec.py +275 -0
- package/skills/qingflow-cli/scripts/workflow/validate_flow_spec.py +605 -0
- package/skills/qingflow-mcp-setup/SKILL.md +115 -0
- package/skills/qingflow-mcp-setup/agents/openai.yaml +4 -0
- package/skills/qingflow-mcp-setup/references/claude-desktop.md +34 -0
- package/skills/qingflow-mcp-setup/references/environments.md +62 -0
- package/skills/qingflow-mcp-setup/references/generic-stdio.md +32 -0
- package/skills/qingflow-mcp-setup/scripts/check_local_server.sh +38 -0
- package/src/qingflow_mcp/__init__.py +1 -1
- package/src/qingflow_mcp/__main__.py +6 -2
- package/src/qingflow_mcp/builder_facade/models.py +282 -102
- package/src/qingflow_mcp/builder_facade/service.py +4192 -935
- package/src/qingflow_mcp/cli/commands/builder.py +316 -298
- package/src/qingflow_mcp/cli/commands/chart.py +1 -1
- package/src/qingflow_mcp/cli/commands/common.py +12 -3
- package/src/qingflow_mcp/cli/commands/exports.py +2 -2
- package/src/qingflow_mcp/cli/commands/imports.py +3 -3
- package/src/qingflow_mcp/cli/commands/portal.py +2 -2
- package/src/qingflow_mcp/cli/commands/record.py +101 -27
- package/src/qingflow_mcp/cli/commands/task.py +28 -47
- package/src/qingflow_mcp/cli/commands/view.py +1 -1
- package/src/qingflow_mcp/cli/context.py +0 -3
- package/src/qingflow_mcp/cli/formatters.py +784 -16
- package/src/qingflow_mcp/cli/main.py +117 -33
- package/src/qingflow_mcp/errors.py +43 -2
- package/src/qingflow_mcp/public_surface.py +26 -17
- package/src/qingflow_mcp/response_trim.py +81 -17
- package/src/qingflow_mcp/server.py +14 -12
- package/src/qingflow_mcp/server_app_builder.py +65 -21
- package/src/qingflow_mcp/server_app_user.py +22 -16
- package/src/qingflow_mcp/session_store.py +11 -7
- package/src/qingflow_mcp/solution/compiler/__init__.py +3 -1
- package/src/qingflow_mcp/solution/compiler/workflow_compiler.py +173 -0
- package/src/qingflow_mcp/solution/executor.py +245 -18
- package/src/qingflow_mcp/tools/ai_builder_tools.py +1780 -406
- package/src/qingflow_mcp/tools/app_tools.py +184 -43
- package/src/qingflow_mcp/tools/approval_tools.py +197 -35
- package/src/qingflow_mcp/tools/auth_tools.py +92 -16
- package/src/qingflow_mcp/tools/code_block_tools.py +298 -40
- package/src/qingflow_mcp/tools/custom_button_tools.py +64 -10
- package/src/qingflow_mcp/tools/directory_tools.py +236 -72
- package/src/qingflow_mcp/tools/export_tools.py +244 -34
- package/src/qingflow_mcp/tools/feedback_tools.py +9 -0
- package/src/qingflow_mcp/tools/file_tools.py +9 -3
- package/src/qingflow_mcp/tools/import_tools.py +336 -49
- package/src/qingflow_mcp/tools/navigation_tools.py +91 -12
- package/src/qingflow_mcp/tools/package_tools.py +118 -6
- package/src/qingflow_mcp/tools/portal_tools.py +39 -3
- package/src/qingflow_mcp/tools/qingbi_report_tools.py +116 -7
- package/src/qingflow_mcp/tools/record_tools.py +1141 -356
- package/src/qingflow_mcp/tools/resource_read_tools.py +188 -39
- package/src/qingflow_mcp/tools/role_tools.py +80 -9
- package/src/qingflow_mcp/tools/solution_tools.py +59 -45
- package/src/qingflow_mcp/tools/task_context_tools.py +662 -158
- package/src/qingflow_mcp/tools/task_tools.py +113 -29
- package/src/qingflow_mcp/tools/view_tools.py +106 -3
- package/src/qingflow_mcp/tools/workflow_tools.py +48 -4
- package/src/qingflow_mcp/tools/workspace_tools.py +71 -3
- /package/skills/qingflow-cli/reference/{QINGFLOW_CLI_BUILDER_MATCH_RULES.md → builder/reference/match-rules.md} +0 -0
- /package/skills/qingflow-cli/reference/{QINGFLOW_CLI_BUILDER_WORKSPACE_ICONS.md → builder/reference/workspace-icons.md} +0 -0
- /package/skills/qingflow-cli/reference/{charts_remove.example.json → examples/charts/charts_remove.example.json} +0 -0
- /package/skills/qingflow-cli/reference/{charts_reorder.example.json → examples/charts/charts_reorder.example.json} +0 -0
- /package/skills/qingflow-cli/reference/{charts_upsert_bar.example.json → examples/charts/charts_upsert_bar.example.json} +0 -0
- /package/skills/qingflow-cli/reference/{charts_upsert_dashboard_starter.example.json → examples/charts/charts_upsert_dashboard_starter.example.json} +0 -0
- /package/skills/qingflow-cli/reference/{charts_upsert_minimal.example.json → examples/charts/charts_upsert_minimal.example.json} +0 -0
- /package/skills/qingflow-cli/reference/{portal_sections_all_types.example.json → examples/portal/portal_sections_all_types.example.json} +0 -0
- /package/skills/qingflow-cli/reference/{portal_sections_five_types.example.json → examples/portal/portal_sections_five_types.example.json} +0 -0
- /package/skills/qingflow-cli/reference/{portal_sections_standard_workbench.example.json → examples/portal/portal_sections_standard_workbench.example.json} +0 -0
- /package/skills/qingflow-cli/reference/{_batch_schema_complex.json → examples/schema/_batch_schema_complex.json} +0 -0
- /package/skills/qingflow-cli/reference/{_batch_schema_scalar.json → examples/schema/_batch_schema_scalar.json} +0 -0
- /package/skills/qingflow-cli/reference/{schema_add_fields_minimal.example.json → examples/schema/schema_add_fields_minimal.example.json} +0 -0
- /package/skills/qingflow-cli/reference/{schema_apply_add_fields_all_types.json → examples/schema/schema_apply_add_fields_all_types.json} +0 -0
- /package/skills/qingflow-cli/reference/{views_upsert_table_minimal.example.json → examples/views/views_upsert_table_minimal.example.json} +0 -0
|
@@ -13,7 +13,7 @@ from mcp.server.fastmcp import FastMCP
|
|
|
13
13
|
|
|
14
14
|
from ..backend_client import BackendRequestContext
|
|
15
15
|
from ..config import DEFAULT_PROFILE, DEFAULT_RECORD_LIST_TYPE
|
|
16
|
-
from ..errors import QingflowApiError
|
|
16
|
+
from ..errors import QingflowApiError, backend_code_int, is_auth_like_error, message_looks_like_invalid_token
|
|
17
17
|
from ..export_store import ExportJobStore
|
|
18
18
|
from ..json_types import JSONObject
|
|
19
19
|
from .base import ToolBase, tool_cn_name
|
|
@@ -23,6 +23,8 @@ from .record_tools import (
|
|
|
23
23
|
FormField,
|
|
24
24
|
LAYOUT_ONLY_QUE_TYPES,
|
|
25
25
|
RecordTools,
|
|
26
|
+
_build_top_level_field_index,
|
|
27
|
+
_normalize_data_list_base_info_schema,
|
|
26
28
|
_normalize_public_column_selectors,
|
|
27
29
|
)
|
|
28
30
|
|
|
@@ -59,10 +61,11 @@ class ExportTools(ToolBase):
|
|
|
59
61
|
def record_export_start(
|
|
60
62
|
profile: str = DEFAULT_PROFILE,
|
|
61
63
|
app_key: str = "",
|
|
62
|
-
view_id: str = "
|
|
64
|
+
view_id: str = "",
|
|
63
65
|
columns: list[JSONObject | int] | None = None,
|
|
64
66
|
where: list[JSONObject] | None = None,
|
|
65
67
|
order_by: list[JSONObject] | None = None,
|
|
68
|
+
record_id: str | int | None = None,
|
|
66
69
|
record_ids: list[str | int] | None = None,
|
|
67
70
|
include_workflow_log: bool = False,
|
|
68
71
|
) -> dict[str, Any]:
|
|
@@ -73,6 +76,7 @@ class ExportTools(ToolBase):
|
|
|
73
76
|
columns=columns or [],
|
|
74
77
|
where=where or [],
|
|
75
78
|
order_by=order_by or [],
|
|
79
|
+
record_id=record_id,
|
|
76
80
|
record_ids=record_ids or [],
|
|
77
81
|
include_workflow_log=include_workflow_log,
|
|
78
82
|
)
|
|
@@ -103,10 +107,11 @@ class ExportTools(ToolBase):
|
|
|
103
107
|
def record_export_direct(
|
|
104
108
|
profile: str = DEFAULT_PROFILE,
|
|
105
109
|
app_key: str = "",
|
|
106
|
-
view_id: str = "
|
|
110
|
+
view_id: str = "",
|
|
107
111
|
columns: list[JSONObject | int] | None = None,
|
|
108
112
|
where: list[JSONObject] | None = None,
|
|
109
113
|
order_by: list[JSONObject] | None = None,
|
|
114
|
+
record_id: str | int | None = None,
|
|
110
115
|
record_ids: list[str | int] | None = None,
|
|
111
116
|
include_workflow_log: bool = False,
|
|
112
117
|
download_to_path: str | None = None,
|
|
@@ -119,6 +124,7 @@ class ExportTools(ToolBase):
|
|
|
119
124
|
columns=columns or [],
|
|
120
125
|
where=where or [],
|
|
121
126
|
order_by=order_by or [],
|
|
127
|
+
record_id=record_id,
|
|
122
128
|
record_ids=record_ids or [],
|
|
123
129
|
include_workflow_log=include_workflow_log,
|
|
124
130
|
download_to_path=download_to_path,
|
|
@@ -131,25 +137,32 @@ class ExportTools(ToolBase):
|
|
|
131
137
|
*,
|
|
132
138
|
profile: str = DEFAULT_PROFILE,
|
|
133
139
|
app_key: str,
|
|
134
|
-
view_id: str = "
|
|
140
|
+
view_id: str = "",
|
|
135
141
|
columns: list[JSONObject | int] | None = None,
|
|
136
142
|
where: list[JSONObject] | None = None,
|
|
137
143
|
order_by: list[JSONObject] | None = None,
|
|
144
|
+
record_id: str | int | None = None,
|
|
138
145
|
record_ids: list[str | int] | None = None,
|
|
139
146
|
include_workflow_log: bool = False,
|
|
140
147
|
) -> dict[str, Any]:
|
|
141
148
|
normalized_app_key = str(app_key or "").strip()
|
|
142
|
-
normalized_view_id = str(view_id or "").strip()
|
|
149
|
+
normalized_view_id = str(view_id or "").strip()
|
|
143
150
|
normalized_columns = _normalize_export_columns(columns or [])
|
|
144
151
|
normalized_where = self._record_tools._normalize_record_list_where(where or [])
|
|
145
152
|
normalized_order_by = self._record_tools._normalize_record_list_order_by(order_by or [])
|
|
146
|
-
normalized_record_ids = _normalize_export_record_ids(record_ids or [])
|
|
153
|
+
normalized_record_ids = _normalize_export_record_ids(_merge_single_export_record_id(record_id, record_ids or []))
|
|
147
154
|
if not normalized_app_key:
|
|
148
155
|
return self._failed_export_result(
|
|
149
156
|
error_code="EXPORT_START_FAILED",
|
|
150
157
|
message="app_key is required",
|
|
151
158
|
extra={"view_id": normalized_view_id, "status": "failed"},
|
|
152
159
|
)
|
|
160
|
+
if not normalized_view_id:
|
|
161
|
+
return self._failed_export_result(
|
|
162
|
+
error_code="EXPORT_VIEW_REQUIRED",
|
|
163
|
+
message="view_id is required; call app_get first and pass accessible_views[].view_id or use the view_id from the frontend URL",
|
|
164
|
+
extra={"app_key": normalized_app_key, "view_id": normalized_view_id, "status": "failed"},
|
|
165
|
+
)
|
|
153
166
|
|
|
154
167
|
def runner(session_profile, context):
|
|
155
168
|
resolved_view, compatibility_warnings = self._record_tools._resolve_accessible_view_route(
|
|
@@ -160,7 +173,7 @@ class ExportTools(ToolBase):
|
|
|
160
173
|
list_type=None,
|
|
161
174
|
view_key=None,
|
|
162
175
|
view_name=None,
|
|
163
|
-
allow_default=
|
|
176
|
+
allow_default=False,
|
|
164
177
|
)
|
|
165
178
|
export_config, export_config_warnings = self._build_export_config(
|
|
166
179
|
profile=profile,
|
|
@@ -238,6 +251,8 @@ class ExportTools(ToolBase):
|
|
|
238
251
|
return {
|
|
239
252
|
"ok": True,
|
|
240
253
|
"status": "accepted",
|
|
254
|
+
"export_executed": True,
|
|
255
|
+
"safe_to_retry_export": False,
|
|
241
256
|
"app_key": normalized_app_key,
|
|
242
257
|
"view_id": resolved_view.view_id,
|
|
243
258
|
"export_handle": export_handle,
|
|
@@ -404,13 +419,17 @@ class ExportTools(ToolBase):
|
|
|
404
419
|
"verification": snapshot.get("verification") or {},
|
|
405
420
|
},
|
|
406
421
|
)
|
|
407
|
-
downloaded_files = self._download_export_files(
|
|
422
|
+
downloaded_files, download_warnings = self._download_export_files(
|
|
408
423
|
file_infos=file_infos,
|
|
409
424
|
download_to_path=download_to_path,
|
|
410
425
|
default_directory=None,
|
|
411
426
|
app_key=str(local_job.get("app_key") or ""),
|
|
412
427
|
view_id=str(local_job.get("view_id") or ""),
|
|
413
428
|
)
|
|
429
|
+
warnings = [
|
|
430
|
+
*cast(list[JSONObject], snapshot.get("warnings") or []),
|
|
431
|
+
*download_warnings,
|
|
432
|
+
]
|
|
414
433
|
return {
|
|
415
434
|
"ok": True,
|
|
416
435
|
"status": "succeeded",
|
|
@@ -429,7 +448,7 @@ class ExportTools(ToolBase):
|
|
|
429
448
|
"file_urls": snapshot.get("file_urls") or [],
|
|
430
449
|
"file_names": snapshot.get("file_names") or [],
|
|
431
450
|
"downloaded_files": downloaded_files,
|
|
432
|
-
"warnings":
|
|
451
|
+
"warnings": warnings,
|
|
433
452
|
"verification": snapshot.get("verification") or {},
|
|
434
453
|
"request_route": self.backend.describe_route(lookup_context),
|
|
435
454
|
}
|
|
@@ -449,23 +468,30 @@ class ExportTools(ToolBase):
|
|
|
449
468
|
*,
|
|
450
469
|
profile: str = DEFAULT_PROFILE,
|
|
451
470
|
app_key: str,
|
|
452
|
-
view_id: str = "
|
|
471
|
+
view_id: str = "",
|
|
453
472
|
columns: list[JSONObject | int] | None = None,
|
|
454
473
|
where: list[JSONObject] | None = None,
|
|
455
474
|
order_by: list[JSONObject] | None = None,
|
|
475
|
+
record_id: str | int | None = None,
|
|
456
476
|
record_ids: list[str | int] | None = None,
|
|
457
477
|
include_workflow_log: bool = False,
|
|
458
478
|
download_to_path: str | None = None,
|
|
459
479
|
wait_timeout_seconds: float | None = None,
|
|
460
480
|
) -> dict[str, Any]:
|
|
461
481
|
normalized_app_key = str(app_key or "").strip()
|
|
462
|
-
normalized_view_id = str(view_id or "").strip()
|
|
482
|
+
normalized_view_id = str(view_id or "").strip()
|
|
463
483
|
if not normalized_app_key:
|
|
464
484
|
return self._failed_export_result(
|
|
465
485
|
error_code="EXPORT_START_FAILED",
|
|
466
486
|
message="app_key is required",
|
|
467
487
|
extra={"status": "failed", "view_id": normalized_view_id},
|
|
468
488
|
)
|
|
489
|
+
if not normalized_view_id:
|
|
490
|
+
return self._failed_export_result(
|
|
491
|
+
error_code="EXPORT_VIEW_REQUIRED",
|
|
492
|
+
message="view_id is required; call app_get first and pass accessible_views[].view_id or use the view_id from the frontend URL",
|
|
493
|
+
extra={"status": "failed", "app_key": normalized_app_key, "view_id": normalized_view_id},
|
|
494
|
+
)
|
|
469
495
|
timeout_seconds = self._normalize_timeout_seconds(wait_timeout_seconds)
|
|
470
496
|
|
|
471
497
|
def runner(session_profile, context):
|
|
@@ -476,6 +502,7 @@ class ExportTools(ToolBase):
|
|
|
476
502
|
columns=columns or [],
|
|
477
503
|
where=where or [],
|
|
478
504
|
order_by=order_by or [],
|
|
505
|
+
record_id=record_id,
|
|
479
506
|
record_ids=record_ids or [],
|
|
480
507
|
include_workflow_log=include_workflow_log,
|
|
481
508
|
)
|
|
@@ -509,9 +536,15 @@ class ExportTools(ToolBase):
|
|
|
509
536
|
download_to_path=effective_download_path,
|
|
510
537
|
)
|
|
511
538
|
if bool(get_result.get("ok")):
|
|
539
|
+
get_result = dict(get_result)
|
|
540
|
+
get_result.setdefault("export_executed", True)
|
|
541
|
+
get_result.setdefault("safe_to_retry_export", False)
|
|
542
|
+
get_result.setdefault("export_handle", export_handle)
|
|
512
543
|
return get_result
|
|
513
544
|
return {
|
|
514
545
|
**get_result,
|
|
546
|
+
"export_executed": True,
|
|
547
|
+
"safe_to_retry_export": False,
|
|
515
548
|
"export_handle": export_handle,
|
|
516
549
|
"file_urls": snapshot.get("file_urls") or [],
|
|
517
550
|
"file_names": snapshot.get("file_names") or [],
|
|
@@ -547,8 +580,11 @@ class ExportTools(ToolBase):
|
|
|
547
580
|
}
|
|
548
581
|
if "EXPORT_HISTORY_AMBIGUOUS" in warning_codes:
|
|
549
582
|
return {
|
|
550
|
-
"ok":
|
|
583
|
+
"ok": False,
|
|
551
584
|
"status": "unknown",
|
|
585
|
+
"error_code": "EXPORT_STATUS_UNKNOWN",
|
|
586
|
+
"export_executed": True,
|
|
587
|
+
"safe_to_retry_export": False,
|
|
552
588
|
"export_handle": export_handle,
|
|
553
589
|
"app_key": str(local_job.get("app_key") or ""),
|
|
554
590
|
"view_id": str(local_job.get("view_id") or ""),
|
|
@@ -579,8 +615,11 @@ class ExportTools(ToolBase):
|
|
|
579
615
|
}
|
|
580
616
|
)
|
|
581
617
|
return {
|
|
582
|
-
"ok":
|
|
618
|
+
"ok": False,
|
|
583
619
|
"status": timeout_status,
|
|
620
|
+
"error_code": "EXPORT_WAIT_TIMEOUT",
|
|
621
|
+
"export_executed": True,
|
|
622
|
+
"safe_to_retry_export": False,
|
|
584
623
|
"export_handle": str(start_result.get("export_handle") or ""),
|
|
585
624
|
"app_key": normalized_app_key,
|
|
586
625
|
"view_id": str(start_result.get("view_id") or normalized_view_id),
|
|
@@ -704,7 +743,7 @@ class ExportTools(ToolBase):
|
|
|
704
743
|
order_by: list[JSONObject],
|
|
705
744
|
select_columns: list[JSONObject],
|
|
706
745
|
) -> list[int]:
|
|
707
|
-
browse_scope = self.
|
|
746
|
+
browse_scope = self._build_export_read_scope(
|
|
708
747
|
profile,
|
|
709
748
|
context,
|
|
710
749
|
app_key,
|
|
@@ -841,7 +880,7 @@ class ExportTools(ToolBase):
|
|
|
841
880
|
resolved_view: AccessibleViewRoute,
|
|
842
881
|
column_selectors: list[int],
|
|
843
882
|
) -> tuple[JSONObject, list[JSONObject]]: # type: ignore[no-untyped-def]
|
|
844
|
-
browse_scope = self.
|
|
883
|
+
browse_scope = self._build_export_read_scope(
|
|
845
884
|
profile,
|
|
846
885
|
context,
|
|
847
886
|
app_key,
|
|
@@ -912,6 +951,71 @@ class ExportTools(ToolBase):
|
|
|
912
951
|
)
|
|
913
952
|
return {"questionExportConfigList": question_export_config_list}, warnings
|
|
914
953
|
|
|
954
|
+
def _build_export_read_scope(
|
|
955
|
+
self,
|
|
956
|
+
profile: str,
|
|
957
|
+
context,
|
|
958
|
+
app_key: str,
|
|
959
|
+
resolved_view: AccessibleViewRoute,
|
|
960
|
+
*,
|
|
961
|
+
force_refresh: bool,
|
|
962
|
+
) -> JSONObject:
|
|
963
|
+
try:
|
|
964
|
+
scope = self._record_tools._build_browse_read_scope(
|
|
965
|
+
profile,
|
|
966
|
+
context,
|
|
967
|
+
app_key,
|
|
968
|
+
resolved_view,
|
|
969
|
+
force_refresh=force_refresh,
|
|
970
|
+
)
|
|
971
|
+
except QingflowApiError as exc:
|
|
972
|
+
if not _is_optional_export_lookup_error(exc):
|
|
973
|
+
raise
|
|
974
|
+
scope = {}
|
|
975
|
+
index = scope.get("index") if isinstance(scope, dict) else None
|
|
976
|
+
if getattr(index, "by_id", None):
|
|
977
|
+
return scope
|
|
978
|
+
if resolved_view.kind == "system" and resolved_view.list_type is not None:
|
|
979
|
+
try:
|
|
980
|
+
list_base_scope = self._build_system_export_list_base_info_scope(context, app_key)
|
|
981
|
+
except QingflowApiError as exc:
|
|
982
|
+
if not _is_optional_export_lookup_error(exc):
|
|
983
|
+
raise
|
|
984
|
+
else:
|
|
985
|
+
list_base_index = list_base_scope.get("index")
|
|
986
|
+
if getattr(list_base_index, "by_id", None):
|
|
987
|
+
return list_base_scope
|
|
988
|
+
try:
|
|
989
|
+
applicant_index = self._record_tools._get_applicant_top_level_field_index(
|
|
990
|
+
profile,
|
|
991
|
+
context,
|
|
992
|
+
app_key,
|
|
993
|
+
force_refresh=force_refresh,
|
|
994
|
+
)
|
|
995
|
+
except QingflowApiError as exc:
|
|
996
|
+
if not _is_optional_export_lookup_error(exc):
|
|
997
|
+
raise
|
|
998
|
+
applicant_index = None
|
|
999
|
+
if applicant_index is not None and applicant_index.by_id:
|
|
1000
|
+
visible_question_ids = {field.que_id for field in applicant_index.by_id.values()}
|
|
1001
|
+
return {
|
|
1002
|
+
"index": applicant_index,
|
|
1003
|
+
"writable_field_ids": set(),
|
|
1004
|
+
"visible_question_ids": visible_question_ids,
|
|
1005
|
+
}
|
|
1006
|
+
return scope or {"index": _build_top_level_field_index({}), "writable_field_ids": set(), "visible_question_ids": set()}
|
|
1007
|
+
|
|
1008
|
+
def _build_system_export_list_base_info_scope(self, context, app_key: str) -> JSONObject:
|
|
1009
|
+
payload = self.backend.request("GET", context, f"/app/{app_key}/data/listBaseInfo")
|
|
1010
|
+
schema = _normalize_data_list_base_info_schema(payload)
|
|
1011
|
+
index = _build_top_level_field_index(schema)
|
|
1012
|
+
visible_question_ids = {field.que_id for field in index.by_id.values()}
|
|
1013
|
+
return {
|
|
1014
|
+
"index": index,
|
|
1015
|
+
"writable_field_ids": set(),
|
|
1016
|
+
"visible_question_ids": visible_question_ids,
|
|
1017
|
+
}
|
|
1018
|
+
|
|
915
1019
|
def _resolve_exportable_fields(
|
|
916
1020
|
self,
|
|
917
1021
|
*,
|
|
@@ -1047,16 +1151,35 @@ class ExportTools(ToolBase):
|
|
|
1047
1151
|
) -> dict[str, Any]: # type: ignore[no-untyped-def]
|
|
1048
1152
|
app_key = str(local_job.get("app_key") or "").strip()
|
|
1049
1153
|
process_payload = self._lookup_process_details(context, app_key=app_key)
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1154
|
+
history_unavailable_warning: JSONObject | None = None
|
|
1155
|
+
try:
|
|
1156
|
+
history_page = self.backend.request(
|
|
1157
|
+
"GET",
|
|
1158
|
+
context,
|
|
1159
|
+
"/app/apply/dataExport/record",
|
|
1160
|
+
params={"appKey": app_key, "pageNum": 1, "pageSize": 100},
|
|
1161
|
+
)
|
|
1162
|
+
except QingflowApiError as exc:
|
|
1163
|
+
if not _is_optional_export_lookup_error(exc):
|
|
1164
|
+
raise
|
|
1165
|
+
history_page = {"list": []}
|
|
1166
|
+
history_unavailable_warning = {
|
|
1167
|
+
"code": "EXPORT_HISTORY_UNAVAILABLE",
|
|
1168
|
+
"message": "export history is not readable for the current user; using current process details when available.",
|
|
1169
|
+
}
|
|
1170
|
+
if exc.category:
|
|
1171
|
+
history_unavailable_warning["category"] = exc.category
|
|
1172
|
+
if exc.backend_code is not None:
|
|
1173
|
+
history_unavailable_warning["backend_code"] = exc.backend_code
|
|
1174
|
+
if exc.http_status is not None:
|
|
1175
|
+
history_unavailable_warning["http_status"] = exc.http_status
|
|
1176
|
+
if exc.request_id:
|
|
1177
|
+
history_unavailable_warning["request_id"] = exc.request_id
|
|
1056
1178
|
history_records = _extract_export_records(history_page)
|
|
1057
1179
|
matched_record, matched_by = _match_export_history_record(history_records, local_job=local_job)
|
|
1058
1180
|
if process_payload is not None:
|
|
1059
1181
|
normalized_status = _normalize_export_status(process_payload.get("processStatus") or process_payload.get("status"))
|
|
1182
|
+
process_file_infos = _normalize_export_file_infos(process_payload.get("fileUrls"))
|
|
1060
1183
|
if normalized_status in {"queued", "running"}:
|
|
1061
1184
|
return {
|
|
1062
1185
|
"status": normalized_status,
|
|
@@ -1064,22 +1187,47 @@ class ExportTools(ToolBase):
|
|
|
1064
1187
|
"num": _coerce_int(process_payload.get("num")),
|
|
1065
1188
|
"error_code": process_payload.get("errorCode"),
|
|
1066
1189
|
"audit_record_status": process_payload.get("auditRecordStatus"),
|
|
1067
|
-
"file_infos":
|
|
1068
|
-
"file_urls":
|
|
1069
|
-
"file_names":
|
|
1070
|
-
"warnings": [],
|
|
1190
|
+
"file_infos": process_file_infos,
|
|
1191
|
+
"file_urls": [item.get("url") for item in process_file_infos if isinstance(item.get("url"), str)],
|
|
1192
|
+
"file_names": [item.get("name") for item in process_file_infos if isinstance(item.get("name"), str)],
|
|
1193
|
+
"warnings": [history_unavailable_warning] if history_unavailable_warning is not None else [],
|
|
1071
1194
|
"verification": {
|
|
1072
1195
|
"current_process_visible": True,
|
|
1073
1196
|
"history_match_resolved": matched_record is not None,
|
|
1197
|
+
"history_readable": history_unavailable_warning is None,
|
|
1074
1198
|
},
|
|
1075
1199
|
"message": None,
|
|
1076
1200
|
}
|
|
1201
|
+
if normalized_status in {"succeeded", "failed"} and (process_file_infos or normalized_status == "failed"):
|
|
1202
|
+
message = "export failed" if normalized_status == "failed" else None
|
|
1203
|
+
return {
|
|
1204
|
+
"status": normalized_status,
|
|
1205
|
+
"process_status": _coerce_int(process_payload.get("processStatus") or process_payload.get("status")),
|
|
1206
|
+
"num": _coerce_int(process_payload.get("num")),
|
|
1207
|
+
"error_code": process_payload.get("errorCode"),
|
|
1208
|
+
"audit_record_status": process_payload.get("auditRecordStatus"),
|
|
1209
|
+
"file_infos": process_file_infos,
|
|
1210
|
+
"file_urls": [item.get("url") for item in process_file_infos if isinstance(item.get("url"), str)],
|
|
1211
|
+
"file_names": [item.get("name") for item in process_file_infos if isinstance(item.get("name"), str)],
|
|
1212
|
+
"warnings": [history_unavailable_warning] if history_unavailable_warning is not None else [],
|
|
1213
|
+
"verification": {
|
|
1214
|
+
"current_process_visible": True,
|
|
1215
|
+
"history_match_resolved": matched_record is not None,
|
|
1216
|
+
"history_readable": history_unavailable_warning is None,
|
|
1217
|
+
"matched_by": "current_process",
|
|
1218
|
+
},
|
|
1219
|
+
"message": message,
|
|
1220
|
+
}
|
|
1077
1221
|
if matched_record is None:
|
|
1078
1222
|
warning_code = "EXPORT_HISTORY_PENDING"
|
|
1079
1223
|
warning_message = "export has not appeared in export history yet"
|
|
1080
1224
|
if matched_by == "ambiguous":
|
|
1081
1225
|
warning_code = "EXPORT_HISTORY_AMBIGUOUS"
|
|
1082
1226
|
warning_message = "export result could not be matched uniquely in export history"
|
|
1227
|
+
warnings = [{"code": warning_code, "message": warning_message}]
|
|
1228
|
+
if history_unavailable_warning is not None:
|
|
1229
|
+
warnings = [history_unavailable_warning]
|
|
1230
|
+
warning_message = str(history_unavailable_warning["message"])
|
|
1083
1231
|
return {
|
|
1084
1232
|
"status": "unknown",
|
|
1085
1233
|
"process_status": None,
|
|
@@ -1089,10 +1237,11 @@ class ExportTools(ToolBase):
|
|
|
1089
1237
|
"file_infos": [],
|
|
1090
1238
|
"file_urls": [],
|
|
1091
1239
|
"file_names": [],
|
|
1092
|
-
"warnings":
|
|
1240
|
+
"warnings": warnings,
|
|
1093
1241
|
"verification": {
|
|
1094
1242
|
"current_process_visible": process_payload is not None,
|
|
1095
1243
|
"history_match_resolved": False,
|
|
1244
|
+
"history_readable": history_unavailable_warning is None,
|
|
1096
1245
|
},
|
|
1097
1246
|
"message": warning_message,
|
|
1098
1247
|
}
|
|
@@ -1129,7 +1278,7 @@ class ExportTools(ToolBase):
|
|
|
1129
1278
|
params={"taskType": 2},
|
|
1130
1279
|
)
|
|
1131
1280
|
except QingflowApiError as exc:
|
|
1132
|
-
if exc
|
|
1281
|
+
if _is_optional_export_lookup_error(exc):
|
|
1133
1282
|
return None
|
|
1134
1283
|
raise
|
|
1135
1284
|
if isinstance(payload, dict):
|
|
@@ -1183,9 +1332,9 @@ class ExportTools(ToolBase):
|
|
|
1183
1332
|
default_directory: str | None,
|
|
1184
1333
|
app_key: str,
|
|
1185
1334
|
view_id: str,
|
|
1186
|
-
) -> list[JSONObject]:
|
|
1335
|
+
) -> tuple[list[JSONObject], list[JSONObject]]:
|
|
1187
1336
|
if download_to_path is None and default_directory is None:
|
|
1188
|
-
return []
|
|
1337
|
+
return [], []
|
|
1189
1338
|
effective_hint = download_to_path or default_directory
|
|
1190
1339
|
assert effective_hint is not None
|
|
1191
1340
|
targets = _resolve_download_targets(
|
|
@@ -1195,13 +1344,45 @@ class ExportTools(ToolBase):
|
|
|
1195
1344
|
view_id=view_id,
|
|
1196
1345
|
)
|
|
1197
1346
|
downloaded_files: list[JSONObject] = []
|
|
1347
|
+
warnings: list[JSONObject] = []
|
|
1198
1348
|
for file_info, target in zip(file_infos, targets, strict=False):
|
|
1199
1349
|
url = str(file_info.get("url") or "").strip()
|
|
1200
1350
|
if not url:
|
|
1201
1351
|
continue
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1352
|
+
try:
|
|
1353
|
+
content = self.backend.download_binary(url)
|
|
1354
|
+
target.parent.mkdir(parents=True, exist_ok=True)
|
|
1355
|
+
target.write_bytes(content)
|
|
1356
|
+
except QingflowApiError as exc:
|
|
1357
|
+
warning: JSONObject = {
|
|
1358
|
+
"code": "EXPORT_FILE_DOWNLOAD_UNAVAILABLE",
|
|
1359
|
+
"message": "export file link is available, but local download failed; use file_urls or retry download later.",
|
|
1360
|
+
"file_name": str(file_info.get("name") or target.name),
|
|
1361
|
+
"url": url,
|
|
1362
|
+
"category": exc.category,
|
|
1363
|
+
"backend_code": exc.backend_code,
|
|
1364
|
+
"request_id": exc.request_id,
|
|
1365
|
+
"http_status": exc.http_status,
|
|
1366
|
+
}
|
|
1367
|
+
if is_auth_like_error(exc):
|
|
1368
|
+
warning["auth_like"] = True
|
|
1369
|
+
warning["error_code"] = "AUTH_REQUIRED"
|
|
1370
|
+
if exc.details:
|
|
1371
|
+
warning["details"] = exc.details
|
|
1372
|
+
warnings.append(warning)
|
|
1373
|
+
continue
|
|
1374
|
+
except OSError as exc:
|
|
1375
|
+
warnings.append(
|
|
1376
|
+
{
|
|
1377
|
+
"code": "EXPORT_FILE_WRITE_UNAVAILABLE",
|
|
1378
|
+
"message": "export file link is available, but writing the local file failed; use file_urls or retry with another download_to_path.",
|
|
1379
|
+
"file_name": str(file_info.get("name") or target.name),
|
|
1380
|
+
"url": url,
|
|
1381
|
+
"path": str(target),
|
|
1382
|
+
"error": str(exc),
|
|
1383
|
+
}
|
|
1384
|
+
)
|
|
1385
|
+
continue
|
|
1205
1386
|
downloaded_files.append(
|
|
1206
1387
|
{
|
|
1207
1388
|
"file_name": str(file_info.get("name") or target.name),
|
|
@@ -1209,7 +1390,7 @@ class ExportTools(ToolBase):
|
|
|
1209
1390
|
"url": url,
|
|
1210
1391
|
}
|
|
1211
1392
|
)
|
|
1212
|
-
return downloaded_files
|
|
1393
|
+
return downloaded_files, warnings
|
|
1213
1394
|
|
|
1214
1395
|
def _normalize_timeout_seconds(self, wait_timeout_seconds: float | None) -> float:
|
|
1215
1396
|
if wait_timeout_seconds is None:
|
|
@@ -1259,10 +1440,16 @@ class ExportTools(ToolBase):
|
|
|
1259
1440
|
payload = json.loads(str(error))
|
|
1260
1441
|
except json.JSONDecodeError:
|
|
1261
1442
|
payload = {"message": str(error)}
|
|
1443
|
+
details = payload.get("details") if isinstance(payload.get("details"), dict) else {}
|
|
1262
1444
|
response = self._failed_export_result(
|
|
1263
|
-
error_code=
|
|
1445
|
+
error_code=details.get("error_code") or _runtime_error_code(payload, default=error_code),
|
|
1264
1446
|
message=str(payload.get("message") or str(error)),
|
|
1265
1447
|
)
|
|
1448
|
+
for key in ("category", "backend_code", "request_id", "http_status"):
|
|
1449
|
+
if key in payload:
|
|
1450
|
+
response[key] = payload.get(key)
|
|
1451
|
+
if details:
|
|
1452
|
+
response["details"] = details
|
|
1266
1453
|
if extra:
|
|
1267
1454
|
response.update(extra)
|
|
1268
1455
|
return response
|
|
@@ -1279,6 +1466,23 @@ def _extract_export_records(payload: Any) -> list[JSONObject]:
|
|
|
1279
1466
|
return []
|
|
1280
1467
|
|
|
1281
1468
|
|
|
1469
|
+
def _is_optional_export_lookup_error(error: QingflowApiError) -> bool:
|
|
1470
|
+
if is_auth_like_error(error):
|
|
1471
|
+
return False
|
|
1472
|
+
backend_code = backend_code_int(error)
|
|
1473
|
+
return backend_code in {40002, 40027, 404} or error.http_status == 404
|
|
1474
|
+
|
|
1475
|
+
|
|
1476
|
+
def _runtime_error_code(payload: JSONObject, *, default: str) -> str:
|
|
1477
|
+
category = str(payload.get("category") or "").strip().lower()
|
|
1478
|
+
http_status = _coerce_int(payload.get("http_status"))
|
|
1479
|
+
if category == "auth" or http_status == 401 or message_looks_like_invalid_token(payload.get("message")):
|
|
1480
|
+
return "AUTH_REQUIRED"
|
|
1481
|
+
if category == "workspace":
|
|
1482
|
+
return "WORKSPACE_NOT_SELECTED"
|
|
1483
|
+
return default
|
|
1484
|
+
|
|
1485
|
+
|
|
1282
1486
|
def _match_export_history_record(
|
|
1283
1487
|
records: list[JSONObject],
|
|
1284
1488
|
*,
|
|
@@ -1468,6 +1672,12 @@ def _normalize_export_record_ids(record_ids: list[str | int]) -> list[int]:
|
|
|
1468
1672
|
return normalized
|
|
1469
1673
|
|
|
1470
1674
|
|
|
1675
|
+
def _merge_single_export_record_id(record_id: str | int | None, record_ids: list[str | int]) -> list[str | int]:
|
|
1676
|
+
if record_id is None or str(record_id).strip() == "":
|
|
1677
|
+
return list(record_ids)
|
|
1678
|
+
return [record_id, *record_ids]
|
|
1679
|
+
|
|
1680
|
+
|
|
1471
1681
|
def _effective_total(page: JSONObject, *, page_size: int) -> int:
|
|
1472
1682
|
rows = page.get("list")
|
|
1473
1683
|
returned_rows = len(rows) if isinstance(rows, list) else 0
|
|
@@ -146,6 +146,15 @@ class FeedbackTools:
|
|
|
146
146
|
"app_key": get_feedback_app_key(),
|
|
147
147
|
"mcp_side": self.mcp_side,
|
|
148
148
|
},
|
|
149
|
+
"submission_summary": {
|
|
150
|
+
"category": normalized_payload.get("反馈类型"),
|
|
151
|
+
"title": normalized_payload.get("title"),
|
|
152
|
+
"tool_name": normalized_payload.get("关联工具"),
|
|
153
|
+
"app_key": normalized_payload.get("关联应用"),
|
|
154
|
+
"record_id": normalized_payload.get("关联记录"),
|
|
155
|
+
"workflow_node_id": normalized_payload.get("关联节点"),
|
|
156
|
+
"impact_scope": normalized_payload.get("影响范围"),
|
|
157
|
+
},
|
|
149
158
|
"normalized_payload": normalized_payload,
|
|
150
159
|
"feedback_request_id": feedback_request_id,
|
|
151
160
|
}
|
|
@@ -12,12 +12,12 @@ from urllib.parse import quote
|
|
|
12
12
|
from mcp.server.fastmcp import FastMCP
|
|
13
13
|
|
|
14
14
|
from ..config import DEFAULT_PROFILE
|
|
15
|
-
from ..errors import QingflowApiError, raise_tool_error
|
|
15
|
+
from ..errors import QingflowApiError, backend_code_int, is_auth_like_error, raise_tool_error
|
|
16
16
|
from ..json_types import JSONObject
|
|
17
17
|
from .base import ToolBase, tool_cn_name
|
|
18
18
|
|
|
19
19
|
|
|
20
|
-
ATTACHMENT_UPLOAD_INFO_FALLBACK_CODES = {
|
|
20
|
+
ATTACHMENT_UPLOAD_INFO_FALLBACK_CODES = {40118}
|
|
21
21
|
LEGACY_OSS_FORM_REQUIRED_KEYS = ("key", "policy", "signature", "ossAccessKeyId")
|
|
22
22
|
|
|
23
23
|
|
|
@@ -190,6 +190,8 @@ class FileTools(ToolBase):
|
|
|
190
190
|
"result": result,
|
|
191
191
|
"upload_result": upload_result,
|
|
192
192
|
"download_url": download_url,
|
|
193
|
+
"write_executed": True,
|
|
194
|
+
"safe_to_retry": False,
|
|
193
195
|
"attachment_value": {
|
|
194
196
|
"value": download_url,
|
|
195
197
|
"otherInfo": file_name,
|
|
@@ -343,6 +345,8 @@ class FileTools(ToolBase):
|
|
|
343
345
|
}
|
|
344
346
|
if path_id is not None:
|
|
345
347
|
encrypted_payload["pathId"] = path_id
|
|
348
|
+
if file_related_url:
|
|
349
|
+
encrypted_payload["fileRelatedUrl"] = file_related_url
|
|
346
350
|
if bucket_type:
|
|
347
351
|
encrypted_payload["bucketType"] = bucket_type
|
|
348
352
|
result = self.backend.request("POST", context, endpoint, json_body=encrypted_payload)
|
|
@@ -357,10 +361,12 @@ class FileTools(ToolBase):
|
|
|
357
361
|
error: QingflowApiError,
|
|
358
362
|
) -> bool:
|
|
359
363
|
"""执行内部辅助逻辑。"""
|
|
364
|
+
if is_auth_like_error(error):
|
|
365
|
+
return False
|
|
360
366
|
return (
|
|
361
367
|
requested_kind == "attachment"
|
|
362
368
|
and attempted_kind == "attachment"
|
|
363
|
-
and error
|
|
369
|
+
and backend_code_int(error) in ATTACHMENT_UPLOAD_INFO_FALLBACK_CODES
|
|
364
370
|
)
|
|
365
371
|
|
|
366
372
|
def _is_legacy_oss_form_upload(self, payload: JSONObject) -> bool:
|