143 lines
5.1 KiB
Go
143 lines
5.1 KiB
Go
// Copyright 2026 Alibaba Group
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package aitable
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
|
|
apperrors "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/errors"
|
|
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/shortcut"
|
|
)
|
|
|
|
func executeRecordPrimaryDocGet(rt *shortcut.RuntimeContext) error {
|
|
baseID, tableID, requestedRecordID := rt.Str("base-id"), rt.Str("table-id"), rt.Str("record-id")
|
|
fieldsData, err := rt.CallMCPData(serverMain, "get_fields", map[string]any{"baseId": baseID, "tableId": tableID})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
fields, found := findNamedObjectList(fieldsData, "fields", "fieldList")
|
|
if !found {
|
|
return apperrors.NewAPI("get_fields response is missing the fields collection",
|
|
apperrors.WithOperation("aitable/get_fields"), apperrors.WithReason("target_invalid_response"),
|
|
apperrors.WithFailureStage("response_validation"), apperrors.WithExecutionStarted(false))
|
|
}
|
|
primaryFieldIDs := make([]string, 0, 1)
|
|
for index, field := range fields {
|
|
fieldType := stringValue(field, "type", "fieldType")
|
|
if !strings.EqualFold(fieldType, "primaryDoc") {
|
|
continue
|
|
}
|
|
fieldID := stringValue(field, "fieldId", "id")
|
|
if fieldID == "" {
|
|
return apperrors.NewAPI(fmt.Sprintf("primaryDoc field %d is missing fieldId", index),
|
|
apperrors.WithOperation("aitable/get_fields"), apperrors.WithReason("target_invalid_response"),
|
|
apperrors.WithFailureStage("response_validation"), apperrors.WithExecutionStarted(false))
|
|
}
|
|
primaryFieldIDs = append(primaryFieldIDs, fieldID)
|
|
}
|
|
|
|
recordParams := map[string]any{
|
|
"baseId": baseID, "tableId": tableID, "recordIds": []string{requestedRecordID},
|
|
}
|
|
if len(primaryFieldIDs) > 0 {
|
|
recordParams["fieldIds"] = primaryFieldIDs
|
|
}
|
|
window, err := queryRecordWindow(rt, recordParams, 1)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
records := window.Records
|
|
if len(records) == 0 {
|
|
return apperrors.NewAPI(fmt.Sprintf("record %s was not found", requestedRecordID),
|
|
apperrors.WithOperation("aitable/query_records"), apperrors.WithReason("RESOURCE_NOT_FOUND"),
|
|
apperrors.WithFailureStage("target_resolution"), apperrors.WithExecutionStarted(false))
|
|
}
|
|
if len(records) != 1 || recordID(records[0]) != requestedRecordID {
|
|
return apperrors.NewAPI("record preflight did not return the exact requested record",
|
|
apperrors.WithOperation("aitable/query_records"), apperrors.WithReason("target_invalid_response"),
|
|
apperrors.WithFailureStage("response_validation"), apperrors.WithExecutionStarted(false))
|
|
}
|
|
if len(primaryFieldIDs) == 0 {
|
|
return rt.Output(map[string]any{
|
|
"status": "no_primary_doc_field", "exists": false, "recordId": requestedRecordID, "nodeId": nil,
|
|
})
|
|
}
|
|
data, err := rt.CallMCPData(serverHelper, "get_primary_doc", map[string]any{
|
|
"baseId": baseID, "tableId": tableID, "recordId": requestedRecordID,
|
|
})
|
|
if err != nil {
|
|
if knownPrimaryDocUnassociatedError(err) {
|
|
return rt.Output(map[string]any{
|
|
"status": "unassociated", "exists": false, "recordId": requestedRecordID, "fieldIds": primaryFieldIDs, "nodeId": nil,
|
|
})
|
|
}
|
|
return err
|
|
}
|
|
nodeID := primaryDocNodeID(data)
|
|
if nodeID == "" {
|
|
if knownPrimaryDocUnassociatedData(data) {
|
|
return rt.Output(map[string]any{
|
|
"status": "unassociated", "exists": false, "recordId": requestedRecordID, "fieldIds": primaryFieldIDs, "nodeId": nil,
|
|
})
|
|
}
|
|
return apperrors.NewAPI("get_primary_doc response is missing nodeId",
|
|
apperrors.WithOperation("aitable-helper/get_primary_doc"), apperrors.WithReason("target_invalid_response"),
|
|
apperrors.WithFailureStage("response_validation"), apperrors.WithExecutionStarted(false))
|
|
}
|
|
return rt.Output(map[string]any{
|
|
"status": "associated", "exists": true, "recordId": requestedRecordID, "fieldIds": primaryFieldIDs, "nodeId": nodeID,
|
|
})
|
|
}
|
|
|
|
func knownPrimaryDocUnassociatedError(err error) bool {
|
|
message := strings.ToLower(strings.TrimSpace(err.Error()))
|
|
return strings.Contains(message, "no record") || strings.Contains(message, "unassociated")
|
|
}
|
|
|
|
func primaryDocNodeID(value any) string {
|
|
object, ok := value.(map[string]any)
|
|
if !ok {
|
|
return ""
|
|
}
|
|
for _, key := range []string{"nodeId", "dentryUuid"} {
|
|
if nodeID, ok := object[key].(string); ok && strings.TrimSpace(nodeID) != "" {
|
|
return strings.TrimSpace(nodeID)
|
|
}
|
|
}
|
|
for _, envelopeKey := range []string{"data", "result", "response"} {
|
|
if nodeID := primaryDocNodeID(object[envelopeKey]); nodeID != "" {
|
|
return nodeID
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func knownPrimaryDocUnassociatedData(value any) bool {
|
|
object, ok := value.(map[string]any)
|
|
if !ok {
|
|
return false
|
|
}
|
|
if exists, ok := object["exists"].(bool); ok && !exists {
|
|
return true
|
|
}
|
|
for _, key := range []string{"nodeId", "dentryUuid"} {
|
|
if nodeID, exists := object[key]; exists && (nodeID == nil || strings.TrimSpace(fmt.Sprint(nodeID)) == "") {
|
|
return true
|
|
}
|
|
}
|
|
if status, ok := object["status"].(string); ok {
|
|
status = strings.ToLower(strings.TrimSpace(status))
|
|
if status == "unassociated" || status == "no_record" {
|
|
return true
|
|
}
|
|
}
|
|
for _, envelopeKey := range []string{"data", "result", "response"} {
|
|
if child, ok := object[envelopeKey].(map[string]any); ok && knownPrimaryDocUnassociatedData(child) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|