240 lines
7.3 KiB
Go
240 lines
7.3 KiB
Go
package tools
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"isphere-ai-bridge/internal/helperclient"
|
|
)
|
|
|
|
const (
|
|
uiaSendMessageOp = "uia_send_message"
|
|
EnvSendConnectorMode = "ISPHERE_SEND_CONNECTOR_MODE"
|
|
EnvSendUIAHelperPath = "ISPHERE_SEND_UIA_HELPER_PATH"
|
|
EnvSendUIAHwnd = "ISPHERE_SEND_UIA_HWND"
|
|
EnvSendUIAEditorAutomationID = "ISPHERE_SEND_UIA_EDITOR_AUTOMATION_ID"
|
|
EnvSendUIButtonAutomationID = "ISPHERE_SEND_UIA_BUTTON_AUTOMATION_ID"
|
|
EnvSendUIATimeoutSeconds = "ISPHERE_SEND_UIA_TIMEOUT_SECONDS"
|
|
)
|
|
|
|
type UiaSendMessageAdapterConfig struct {
|
|
HelperPath string
|
|
TimeoutSeconds int
|
|
Hwnd string
|
|
SendEditorAutomationID string
|
|
SendButtonAutomationID string
|
|
Mode string
|
|
}
|
|
|
|
type UiaHelperCaller interface {
|
|
CallUiaHelper(ctx context.Context, op string, args map[string]any) (uiaHelperResponse, error)
|
|
}
|
|
|
|
type uiaHelperResponse struct {
|
|
OK bool
|
|
Data map[string]any
|
|
ErrCode string
|
|
ErrText string
|
|
}
|
|
|
|
type helperClientUiaCaller struct {
|
|
client helperclient.Client
|
|
}
|
|
|
|
type uiaSendMessageConnector struct {
|
|
config UiaSendMessageAdapterConfig
|
|
caller UiaHelperCaller
|
|
}
|
|
|
|
func NewSendMessageConnectorFromEnv() SendMessageConnector {
|
|
mode := strings.TrimSpace(strings.ToLower(os.Getenv(EnvSendConnectorMode)))
|
|
if mode != "uia_rpa" && mode != "uia-rpa" {
|
|
return nil
|
|
}
|
|
timeoutSeconds := 10
|
|
if raw := strings.TrimSpace(os.Getenv(EnvSendUIATimeoutSeconds)); raw != "" {
|
|
if parsed, err := strconv.Atoi(raw); err == nil && parsed > 0 {
|
|
timeoutSeconds = parsed
|
|
}
|
|
}
|
|
return NewUiaSendMessageConnector(UiaSendMessageAdapterConfig{
|
|
Mode: "enabled",
|
|
HelperPath: strings.TrimSpace(os.Getenv(EnvSendUIAHelperPath)),
|
|
TimeoutSeconds: timeoutSeconds,
|
|
Hwnd: strings.TrimSpace(os.Getenv(EnvSendUIAHwnd)),
|
|
SendEditorAutomationID: strings.TrimSpace(os.Getenv(EnvSendUIAEditorAutomationID)),
|
|
SendButtonAutomationID: strings.TrimSpace(os.Getenv(EnvSendUIButtonAutomationID)),
|
|
}, nil)
|
|
}
|
|
|
|
func NewUiaSendMessageConnector(config UiaSendMessageAdapterConfig, caller UiaHelperCaller) SendMessageConnector {
|
|
config.Mode = strings.TrimSpace(strings.ToLower(config.Mode))
|
|
if config.Mode == "" {
|
|
config.Mode = "disabled"
|
|
}
|
|
if config.SendEditorAutomationID == "" {
|
|
config.SendEditorAutomationID = "rtbSendMessage"
|
|
}
|
|
if config.SendButtonAutomationID == "" {
|
|
config.SendButtonAutomationID = "btnSend"
|
|
}
|
|
if caller == nil {
|
|
timeout := 10 * time.Second
|
|
if config.TimeoutSeconds > 0 {
|
|
timeout = time.Duration(config.TimeoutSeconds) * time.Second
|
|
}
|
|
caller = helperClientUiaCaller{client: helperclient.Client{HelperPath: config.HelperPath, Timeout: timeout}}
|
|
}
|
|
return uiaSendMessageConnector{config: config, caller: caller}
|
|
}
|
|
|
|
func (c uiaSendMessageConnector) ExecuteSendMessage(ctx context.Context, request SendMessageConnectorRequest) (SendMessageConnectorResult, error) {
|
|
if c.config.Mode != "enabled" && c.config.Mode != "uia_rpa" {
|
|
result := SendMessageConnectorResult{
|
|
Accepted: false,
|
|
Status: "blocked",
|
|
ErrorCode: "uia_rpa_mode_blocked",
|
|
ErrorMessage: "A-route UIA/RPA send adapter is disabled",
|
|
ConnectorMode: "uia-rpa-disabled",
|
|
}
|
|
return result, fmt.Errorf(result.ErrorMessage)
|
|
}
|
|
if err := validateUiaSendMessageConfig(c.config); err != nil {
|
|
result := SendMessageConnectorResult{
|
|
Accepted: false,
|
|
Status: "blocked",
|
|
ErrorCode: "uia_rpa_config_missing",
|
|
ErrorMessage: err.Error(),
|
|
ConnectorMode: "uia-rpa",
|
|
}
|
|
return result, err
|
|
}
|
|
if err := validateUiaSendMessageRequest(request); err != nil {
|
|
return SendMessageConnectorResult{
|
|
Accepted: false,
|
|
Status: "failed",
|
|
ErrorCode: "uia_rpa_invalid_request",
|
|
ErrorMessage: err.Error(),
|
|
ConnectorMode: "uia-rpa",
|
|
}, nil
|
|
}
|
|
|
|
args := map[string]any{
|
|
"hwnd": c.config.Hwnd,
|
|
"send_editor_automation_id": c.config.SendEditorAutomationID,
|
|
"send_button_automation_id": c.config.SendButtonAutomationID,
|
|
"target_ref": request.TargetRef,
|
|
"content_text": request.ContentText,
|
|
"content_sha256": request.ContentSHA256,
|
|
"idempotency_key_sha256": request.IdempotencyKeySHA256,
|
|
}
|
|
response, err := c.caller.CallUiaHelper(ctx, uiaSendMessageOp, args)
|
|
if err != nil {
|
|
return SendMessageConnectorResult{
|
|
Accepted: false,
|
|
Status: "failed",
|
|
ErrorCode: "uia_rpa_helper_error",
|
|
ErrorMessage: err.Error(),
|
|
ConnectorMode: "uia-rpa",
|
|
}, err
|
|
}
|
|
if !response.OK {
|
|
code := strings.TrimSpace(response.ErrCode)
|
|
if code == "" {
|
|
code = "uia_rpa_helper_rejected"
|
|
}
|
|
message := strings.TrimSpace(response.ErrText)
|
|
if message == "" {
|
|
message = "UIA helper rejected send action"
|
|
}
|
|
return SendMessageConnectorResult{
|
|
Accepted: false,
|
|
Status: "failed",
|
|
ErrorCode: code,
|
|
ErrorMessage: message,
|
|
ConnectorMode: "uia-rpa",
|
|
}, nil
|
|
}
|
|
ackRef, _ := response.Data["ack_ref"].(string)
|
|
if strings.TrimSpace(ackRef) == "" {
|
|
ackRef = "uia:" + c.config.Hwnd + ":" + c.config.SendButtonAutomationID
|
|
}
|
|
return SendMessageConnectorResult{
|
|
Accepted: true,
|
|
Status: "accepted",
|
|
AckRef: ackRef,
|
|
ConnectorMode: "uia-rpa",
|
|
ProductionEnabled: true,
|
|
SideEffects: map[string]any{
|
|
"sent_message": true,
|
|
"typed_text": true,
|
|
"clicked_ui": true,
|
|
"uploaded_file": false,
|
|
"sent_file": false,
|
|
"captured_network": false,
|
|
"attached_hook": false,
|
|
"modified_client_data": false,
|
|
},
|
|
}, nil
|
|
}
|
|
|
|
func validateUiaSendMessageConfig(config UiaSendMessageAdapterConfig) error {
|
|
if strings.TrimSpace(config.Hwnd) == "" {
|
|
return fmt.Errorf("ISPHERE_SEND_UIA_HWND is required for A-route UIA send")
|
|
}
|
|
if strings.TrimSpace(config.SendEditorAutomationID) == "" {
|
|
return fmt.Errorf("send editor automation id is required")
|
|
}
|
|
if strings.TrimSpace(config.SendButtonAutomationID) == "" {
|
|
return fmt.Errorf("send button automation id is required")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func validateUiaSendMessageRequest(request SendMessageConnectorRequest) error {
|
|
if strings.TrimSpace(request.TargetRef) == "" {
|
|
return fmt.Errorf("target_ref is required")
|
|
}
|
|
if strings.TrimSpace(request.ContentText) == "" {
|
|
return fmt.Errorf("content_text is required")
|
|
}
|
|
if strings.TrimSpace(request.ContentSHA256) == "" {
|
|
return fmt.Errorf("content_sha256 is required")
|
|
}
|
|
if strings.TrimSpace(request.IdempotencyKeySHA256) == "" {
|
|
return fmt.Errorf("idempotency_key_sha256 is required")
|
|
}
|
|
if request.ExecutionMode != sendMessageProductionMode {
|
|
return fmt.Errorf("execution_mode must be production")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c helperClientUiaCaller) CallUiaHelper(ctx context.Context, op string, args map[string]any) (uiaHelperResponse, error) {
|
|
response, err := c.client.Call(ctx, op, args)
|
|
if err != nil {
|
|
return uiaHelperResponse{}, err
|
|
}
|
|
out := uiaHelperResponse{OK: response != nil && response.OK}
|
|
if response != nil {
|
|
out.Data = decodeRawData(response.Data)
|
|
if response.Error != nil {
|
|
out.ErrCode = response.Error.Code
|
|
out.ErrText = response.Error.Message
|
|
}
|
|
}
|
|
if out.Data == nil {
|
|
out.Data = map[string]any{}
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
func uiaHelperResponseFromRaw(data map[string]json.RawMessage, ok bool) uiaHelperResponse {
|
|
return uiaHelperResponse{OK: ok, Data: decodeRawData(data)}
|
|
}
|