File size: 2,637 Bytes
cd8bd0a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
import { randomBytes } from "node:crypto";
import type {
  CloudAgentTask,
  CloudAgentStatus,
  CloudAgentSource,
  CloudAgentResult,
  CloudAgentActivity,
} from "./types.ts";

export interface AgentCredentials {
  apiKey: string;
  baseUrl?: string;
}

export interface CreateTaskParams {
  prompt: string;
  source: CloudAgentSource;
  options: {
    autoCreatePr?: boolean;
    planApprovalRequired?: boolean;
    environment?: Record<string, string>;
  };
}

export interface GetStatusResult {
  status: CloudAgentStatus;
  externalId?: string;
  result?: CloudAgentResult;
  activities: CloudAgentActivity[];
  error?: string;
}

export abstract class CloudAgentBase {
  abstract readonly providerId: string;
  abstract readonly baseUrl: string;

  abstract createTask(
    params: CreateTaskParams,
    credentials: AgentCredentials
  ): Promise<CloudAgentTask>;

  abstract getStatus(externalId: string, credentials: AgentCredentials): Promise<GetStatusResult>;

  abstract approvePlan(externalId: string, credentials: AgentCredentials): Promise<void>;

  abstract sendMessage(
    externalId: string,
    message: string,
    credentials: AgentCredentials
  ): Promise<CloudAgentActivity>;

  abstract listSources(
    credentials: AgentCredentials
  ): Promise<{ name: string; url: string; branch?: string }[]>;

  protected mapStatus(status: string): CloudAgentStatus {
    const statusLower = status.toLowerCase();

    if (statusLower.includes("completed") || statusLower.includes("done")) {
      return "completed";
    }
    if (statusLower.includes("failed") || statusLower.includes("error")) {
      return "failed";
    }
    if (statusLower.includes("cancelled") || statusLower.includes("canceled")) {
      return "cancelled";
    }
    if (
      statusLower.includes("running") ||
      statusLower.includes("active") ||
      statusLower.includes("executing")
    ) {
      return "running";
    }
    if (
      statusLower.includes("pending") ||
      statusLower.includes("queued") ||
      statusLower.includes("waiting")
    ) {
      return "queued";
    }
    if (statusLower.includes("approval") || statusLower.includes("plan")) {
      return "awaiting_approval";
    }

    return "queued";
  }

  protected generateTaskId(): string {
    // Cryptographically secure suffix (not Math.random) — task IDs flow into
    // session/external identifiers, so they must be unpredictable (CodeQL js/insecure-randomness).
    return `task_${Date.now()}_${randomBytes(8).toString("hex")}`;
  }

  protected generateActivityId(): string {
    return `act_${Date.now()}_${randomBytes(8).toString("hex")}`;
  }
}