Cogover Learning

API reference @cogover/sdk: Custom Module Action

Trang này thuộc API reference của @cogover/sdk.

Custom Module Action

Custom Module Action là một thao tác ngắn, chạy đồng bộ của project mà Process hoặc AI Agent có thể gọi. Process Builder đưa mỗi action mở cho "process" thành một node Custom Module Action, và AI Agent Builder đưa mỗi action mở cho "agent" thành một tool. Khai báo từng action bằng defineAction và liệt kê kết quả trong named export actions của project. Default export vẫn là tuỳ chọn với project chỉ khai báo action, trigger và job.

Ví dụ này giả định workspace.d.ts đã khai báo Object lead và các field dùng bên dưới.

import { defineAction, s } from "@cogover/sdk";
 
export const actions = [
  defineAction({
    key: "score_lead",
    label: "Score lead",
    description: "Scores a lead from its recent activities and returns a tier.",
    exposeTo: ["process", "agent"],
    effect: "read",
    input: s.object({
      leadId: s.recordId("lead").describe("ID of the lead to score"),
      strict: s.boolean().optional(),
    }),
    output: s.object({
      found: s.boolean(),
      score: s.number(),
      tier: s.enum(["A", "B", "C"]),
    }),
    timeoutMs: 5000,
    async handler({ data, invocation, log }, input) {
      const lead = await data.object("lead").records.get(input.leadId, { fields: ["rating"] });
      if (!lead) return { found: false, score: 0, tier: "C" };
      const score = Number(lead.fields.rating ?? 0) * (input.strict ? 8 : 10);
      log.info("Lead scored", { leadId: lead.id, source: invocation.source.type });
      return { found: true, score, tier: score >= 80 ? "A" : score >= 50 ? "B" : "C" };
    },
  }),
];

Cogover đọc khai báo action khi một version của project được publish và cung cấp các action của version đang active: node Process và tool của AI Agent tham chiếu action bằng slug của project và key của action, và luôn chạy version đang active tại thời điểm gọi. Version có khai báo không hợp lệ sẽ publish thất bại với ACTION_MANIFEST_INVALID. Deactivate project sẽ gỡ các action của nó khỏi cả hai builder. Một project khai báo tối đa 50 action.

Label, mô tả và schema của action hiển thị với mọi thành viên đang hoạt động của Workspace: không đặt secret trong đó.

defineAction(config): ActionDefinition

function defineAction<
  TInput extends ObjectSchema<ObjectShape, false>,
  TOutput extends ObjectSchema<ObjectShape, false>,
  TSchema extends object = EffectiveWorkspaceObjects,
>(config: ActionConfig<TInput, TOutput, TSchema>): ActionDefinition;
 
type ActionExposure = "process" | "agent";
type ActionEffect = "read" | "write";
 
interface ActionConfig<
  TInput extends ObjectSchema<ObjectShape, false> = ObjectSchema<ObjectShape, false>,
  TOutput extends ObjectSchema<ObjectShape, false> = ObjectSchema<ObjectShape, false>,
  TSchema extends object = EffectiveWorkspaceObjects,
> {
  readonly key: string;
  readonly label: string;
  readonly description?: string;
  readonly exposeTo: readonly ActionExposure[];
  readonly effect: ActionEffect;
  readonly input: TInput;
  readonly output: TOutput;
  readonly timeoutMs?: number;
  readonly handler: ActionHandler<TInput, TOutput, TSchema>;
}

defineAction kiểm tra cấu hình ngay và ném ValidationError khi có vi phạm, khi có key cấu hình lạ hoặc khi handler không phải function; khi key đã hợp lệ, details.actionKey và details.option cho biết action và tuỳ chọn bị sai. Hàm trả về một ActionDefinition đã freeze. Kiểu của tham số input và kiểu trả về của handler được suy ra từ schema input và output.

Tuỳ chọn Bắt buộc Quy tắc và ý nghĩa
key Có Định danh action trong project: một chữ thường, theo sau tối đa 63 chữ thường, chữ số hoặc _. Duy nhất trong project. Giữ ổn định giữa các version: node Process và tool của agent dùng key cũ sẽ không tìm thấy action nữa.
label Có Tên hiển thị trong Process Builder và AI Agent Builder, từ 1 đến 100 ký tự sau khi trim.
description Với agent Tối đa 1000 ký tự sau khi trim. Bắt buộc khi exposeTo có "agent": AI Agent đọc mô tả này để quyết định khi nào gọi tool.
exposeTo Có Danh sách khác rỗng gồm "process" và "agent", không trùng: nơi action được dùng.
effect Có "read": action chạy chỉ đọc, mọi thao tác ghi đều bị từ chối bằng PermissionDeniedError. "write": action được ghi; tool của AI Agent cho action này mặc định cần người phê duyệt.
input Có Schema của input, tạo bằng s.object.
output Có Schema của output, tạo bằng s.object.
timeoutMs Không Số nguyên từ 1000 đến 8000; mặc định 8000. Giới hạn thời gian của một lần gọi.
handler Có (context, input) => output, đồng bộ hoặc bất đồng bộ.

Schema builder s

s tạo schema vừa là kiểu TypeScript vừa là JSON Schema. Action dùng nó để khai báo input và output, và agents.start dùng nó cho resultSchema (xem Process và AI Agent).

const s: {
  string(options?: { minLength?: number; maxLength?: number }): Schema<string>;
  enum<const Values extends readonly string[]>(values: Values): Schema<Values[number]>;
  number(options?: { minimum?: number; maximum?: number }): Schema<number>;
  integer(options?: { minimum?: number; maximum?: number }): Schema<number>;
  boolean(): Schema<boolean>;
  date(): Schema<string>;
  dateTime(): Schema<number>;
  recordId(objectSlug: string): Schema<CogoverRecordId>;
  array<Item extends Schema<unknown, false>>(item: Item, options?: { maxItems?: number }): Schema<InferSchema<Item>[]>;
  object<Shape extends ObjectShape>(shape: Shape): ObjectSchema<Shape>;
};
 
interface Schema<T = unknown, Optional extends boolean = false> {
  optional(): Schema<T, true>;
  describe(text: string): Schema<T, Optional>;
  toJSON(): JsonSchema;
}
 
interface ObjectSchema<Shape extends ObjectShape = ObjectShape, Optional extends boolean = false>
  extends Schema</* kiểu object của Shape */, Optional> {
  optional(): ObjectSchema<Shape, true>;
  describe(text: string): ObjectSchema<Shape, Optional>;
}
 
type ObjectShape = { readonly [property: string]: Schema<unknown, boolean> };
type InferSchema<T> = T extends Schema<infer Value, boolean> ? Value : never;
 
interface JsonSchema {
  readonly [keyword: string]: null | boolean | number | string | readonly unknown[] | JsonSchema;
}
Builder Giá trị JSON Schema Kiểu dữ liệu Process
s.string({ minLength?, maxLength? }) string {"type": "string"} Text
s.enum(["A", "B"]) một trong các chuỗi đã liệt kê {"type": "string", "enum": [...]} Text
s.number({ minimum?, maximum? }) number hữu hạn {"type": "number"} Number
s.integer({ minimum?, maximum? }) số nguyên an toàn {"type": "integer"} Number
s.boolean() boolean {"type": "boolean"} Boolean
s.date() chuỗi YYYY-MM-DD {"type": "string", "format": "date"} Date
s.dateTime() Unix millisecond, như field date_time {"type": "integer", "format": "x-epoch-ms"} Date time
s.recordId("lead") ID của một record thuộc Object, kiểu CogoverRecordId {"type": "string", "x-cogover-object": "lead"} Text
s.array(item, { maxItems? }) danh sách giá trị item {"type": "array", "items": {...}} danh sách theo kiểu của item
s.object({ ... }) object có đúng các property này {"type": "object", "properties": {...}, "required": [...], "additionalProperties": false} Record, mỗi property là một field con
  • Mọi node đều bất biến: .optional() và .describe(text) trả về node mới. .optional() cho phép bỏ property khỏi object chứa nó (không có trong required); .describe(text) thêm description từ 1 đến 500 ký tự, lưu sau khi trim, được Process Builder hiển thị và AI Agent đọc.
  • s.string: maxLength từ 1 đến 100000, mặc định 10000; minLength từ 0 đến maxLength. Không có option pattern: kiểm tra định dạng, ví dụ mã hay địa chỉ email, trong handler và ném ValidationError khi không khớp.
  • s.enum: từ 1 đến 100 chuỗi khác nhau, mỗi chuỗi từ 1 đến 200 ký tự.
  • s.number và s.integer: minimum và maximum là số hữu hạn (số nguyên an toàn với s.integer), và minimum không lớn hơn maximum.
  • s.recordId: slug của Object bắt đầu bằng chữ cái hoặc _ và có tối đa 128 chữ cái, chữ số hoặc _. Giá trị có kiểu CogoverRecordId, nên handler truyền thẳng vào records.get hoặc records.update mà không cần ép kiểu, và property output tạo bằng nó nhận id của record mà records API trả về. Lúc chạy nó chỉ là chuỗi: Cogover không kiểm tra record có tồn tại.
  • s.array: item không được optional; maxItems từ 1 đến 10000, mặc định 1000.
  • s.object: tối đa 50 property, mỗi tên có dạng như leadId: một chữ cái, theo sau tối đa 63 chữ cái, chữ số hoặc _. Property giữ thứ tự khai báo.
  • input và output của action, và resultSchema của agent, là một s.object không optional, lồng object và array tối đa 3 cấp (object gốc là cấp thứ nhất). JSON Schema của input và output của một action cộng lại, và schema kết quả của agent, tối đa 16384 byte UTF-8 khi mã hoá JSON.
  • Chỉ node do s tạo mới là schema: object JSON Schema viết tay bị từ chối bằng ValidationError. toJSON(), và do đó JSON.stringify(node), trả về JSON Schema của node.

Schema là node schema mô tả giá trị của một kiểu TypeScript, ObjectSchema là node tạo bằng s.object, và ObjectShape là bảng ánh xạ tên property sang node mà s.object nhận. InferSchema cho kiểu TypeScript của một node, và JsonSchema là JSON Schema dưới dạng dữ liệu JSON thuần:

const leadInput = s.object({ leadId: s.recordId("lead"), note: s.string().optional() });
type LeadInput = InferSchema<typeof leadInput>; // { leadId: string; note?: string }

ActionDefinition và ActionManifest

interface ActionManifest {
  readonly key: string;
  readonly label: string;
  readonly description: string;
  readonly exposeTo: readonly ActionExposure[];
  readonly effect: ActionEffect;
  readonly timeoutMs: number;
  readonly inputSchema: JsonSchema;
  readonly outputSchema: JsonSchema;
}
 
interface ActionDefinition {
  readonly key: string;
  readonly config: ActionManifest;
  readonly __cogoverActionHandler: (inputJson: string) => Promise<string>;
}

config là cấu hình đã chuẩn hoá dưới dạng JSON thuần đã deep-freeze, đã áp mọi giá trị mặc định: description là "" khi không khai báo, timeoutMs là 8000 nếu không đặt, label và description đã trim. Đây là nội dung Process Builder và AI Agent Builder hiển thị. Nó tách khỏi object đã truyền cho defineAction. __cogoverActionHandler dành riêng cho Cogover; code của project không gọi nó.

Context của action handler

type ActionHandler<TInput, TOutput, TSchema extends object = EffectiveWorkspaceObjects> = (
  context: ActionContext<TSchema>,
  input: InferSchema<TInput>,
) => InferSchema<TOutput> | Promise<InferSchema<TOutput>>;
 
interface ActionContext<TSchema extends object = EffectiveWorkspaceObjects> {
  readonly action: ActionInfo;
  readonly invocation: ActionInvocationContext;
  readonly data: DataApi<TSchema>;
  readonly schema: SchemaApi<TSchema>;
  readonly log: ScriptLogger;
  readonly state: ProjectState;
  readonly locks: DistributedLocks;
  readonly push: PushApi<TSchema>;
  readonly notifications: NotificationsApi;
  readonly email: EmailApi<TSchema>;
  readonly jobs: JobsApi;
  readonly secrets: SecretsApi;
  readonly crypto: CryptoApi;
  readonly org: OrgApi;
  readonly limits: LimitsApi;
  readonly processes: ProcessesApi;
  readonly agents: AgentsApi;
}
 
interface ActionInfo {
  readonly key: string;
  readonly runId: string;
  readonly effect: ActionEffect;
}
 
type ActionInvocationContext = (UserInvocationContext | SystemInvocationContext) & {
  readonly executionIdentity: "user" | "system";
  readonly source: ActionSource;
};
 
type ActionSource = ProcessActionSource | AgentActionSource;
 
interface ProcessActionSource {
  readonly type: "process";
  readonly processId: string;
  readonly processInfoId: string;
  readonly instanceId: string;
  readonly nodeId: string;
  readonly runAs: ProcessActionRunAs | null;
}
 
type ProcessActionRunAs = "PROCESS_STARTER" | "PERSONNEL" | "SYSTEM";
 
interface AgentActionSource {
  readonly type: "agent";
  readonly agentId: string;
  readonly sessionId: string | null;
  readonly runId: string | null;
  readonly interactive: boolean;
  readonly initiatorPersonnelId: string | null;
}

data, schema, log, state, locks, push, notifications, email, jobs, secrets, crypto, org và limits là chính các API mà script nhận được, còn processes và agents được mô tả ở Process và AI Agent. Action context không có input, request hay response: input là tham số thứ hai của handler.

  • action.key và action.effect lặp lại cấu hình. action.runId định danh lần gọi và giữ nguyên khi bên gọi gửi lại chính lần gọi đó (ví dụ node Process thử lại sau lỗi mạng); lần chạy mới của node hoặc lần gọi tool mới của agent có runId mới. Dùng nó làm idempotency key cho lời gọi ra ngoài và cho jobs.enqueue hoặc processes.start.
  • invocation là context invocation của người dùng hoặc system (xem Danh tính invocation và workspace hiện tại) cộng thêm executionIdentity, lặp lại invocation.identity, và source.
  • invocation.source cho biết bên gọi. ProcessActionSource cho biết Process (processId), định nghĩa Process (processInfoId), lượt chạy (instanceId), node BPMN (nodeId) và runAs, giá trị ProcessActionRunAs mà node dùng để chọn danh tính chạy action: "PROCESS_STARTER" (người khởi tạo Process), "PERSONNEL" (nhân sự do node chỉ định, có thể lấy từ dữ liệu của Process) hoặc "SYSTEM"; là null khi bên gọi không cho biết. AgentActionSource cho biết agent, phiên chat và lượt chạy của nó (mỗi giá trị là null khi agent không được khởi chạy theo cách đó), interactive, bằng true khi có người đang chat với agent, và initiatorPersonnelId, ID nhân sự của người đang chat với agent hoặc người khởi chạy lượt chạy agent (null khi không có).
  • Action do AI Agent gọi chạy bằng danh tính của agent, có thể khác người đang chat: mọi người dùng agent đều có quyền truy cập của danh tính đó. Khi action chỉ được làm những gì người đó được phép, hãy kiểm tra initiatorPersonnelId trong handler (ví dụ bằng data.asUser, identity policy phải cho phép) và từ chối nếu không. Với runAs: "PERSONNEL", Process có thể đã lấy nhân sự từ dữ liệu do người dùng hoặc hệ thống bên ngoài cung cấp: đừng coi đó là bằng chứng ai đã yêu cầu.

Input và output

  • Cogover kiểm tra input theo schema input trước khi handler chạy. Input không hợp lệ thì handler không chạy: bên gọi nhận INPUT_INVALID kèm đường dẫn JSON Pointer và thông báo cho từng lỗi (tối đa 20). Vì vậy handler luôn nhận đúng các property đã khai báo, với giá trị đúng kiểu.
  • Handler trả về output. Cogover kiểm tra output theo schema output sau khi handler trả về: giá trị không phải dữ liệu JSON, hoặc không khớp schema, được báo cho bên gọi là OUTPUT_INVALID, và các thao tác ghi handler đã làm không được hoàn tác. Hãy trả về một object; undefined không phải output hợp lệ.
  • Node Process hoặc AI Agent chỉ nhận mã lỗi và một thông báo an toàn, cố định của lỗi mà handler ném (xem bên dưới), không bao giờ nhận thông báo riêng của handler. Khi bên gọi cần biết vì sao một trường hợp dự kiến thất bại, hãy mô tả nó trong output, như found trong ví dụ trên, thay vì ném lỗi.

Quy tắc thực thi action

  • Danh tính. Node Process chạy action dưới danh tính người đã khởi chạy Process, một nhân sự được chọn, hoặc không có người dùng, theo lựa chọn trên node. AI Agent chạy action dưới danh tính người mà agent đang thay mặt. Khi có người dùng, invocation.user mô tả người đó, data.object() dùng quyền của họ, và Cogover kiểm tra trước rằng người đó vẫn là thành viên đang hoạt động của Workspace. Khi không có người dùng, invocation.identity là "system", và lời gọi bị từ chối trước khi chạy trừ khi identity policy đã duyệt của version đang active đặt allowInternalSystem: true. data.asUser() và data.asSystem() tuân theo identity policy như thông thường. Khi version đang active chưa có identity policy được duyệt, action chạy giống route HTTP: lời gọi có người dùng chạy với quyền riêng của người đó và không có quyền nào của policy (data.asSystem(), data.asUser(), email.send(), fetch() tới port khác, processes.start() và agents.start() bị từ chối), còn lời gọi không có người dùng bị từ chối trước khi chạy.
  • Effect. Action "read" được đọc record, schema và state, dùng org, email.senders và crypto trừ khi dùng key { secret }, gửi fetch với method GET hoặc HEAD không kèm credential, và gọi processes.get và agents.get. Mọi lời gọi khác, như ghi record, locks, push, notifications.send, email.send, jobs.enqueue, secrets.get, processes.start hoặc agents.start, ném PermissionDeniedError với thông báo This action is declared read-only.
  • Giới hạn. Action có ngân sách của một HTTP route (xem Giới hạn của một lần thực thi) và giới hạn thời gian riêng là timeoutMs, tính từ lúc handler bắt đầu. Vượt thời gian sẽ kết thúc lần gọi với TIMEOUT; handler không bắt được lỗi này.
  • Lời gọi lặp lại. Node Process và AI Agent gửi lại lời gọi với cùng runId khi không nhận được câu trả lời. Khi đó, trong 7 ngày, Cogover trả kết quả đã lưu của lần gọi đã xong mà không chạy lại handler, và từ chối cùng runId với input khác. Nếu Cogover bị gián đoạn trong lúc handler chạy, lần thử lại sau có thể chạy lại handler, nên hãy viết action có ghi dữ liệu theo kiểu idempotent, ví dụ dùng action.runId.
  • Vòng lặp luôn kết thúc. Mỗi lời gọi mang ngân sách tự động hoá của lượt chạy Process hoặc agent đã gọi nó, và thao tác ghi record của action dùng ngân sách này giống thao tác ghi của trigger. Lời gọi đã hết ngân sách bị từ chối với MAX_HOP_EXCEEDED trước khi chạy.

Cách Process và AI Agent dùng action

  • Process. Node Custom Module Action liệt kê các action mở cho "process". Node điền từng property cấp một của input từ dữ liệu của Process; property mà node để trống sẽ không được gửi, nên hãy khai báo nó bằng .optional() trừ khi luôn cần. Các node phía sau đọc output của node: status (COMPLETED hoặc FAILED), result (output của action, mỗi property của output là một field con), errorCode, errorMessage, runId và durationMs. Lời gọi thất bại sẽ dừng lượt chạy Process trừ khi node được đặt tiếp tục khi lỗi.
  • AI Agent. Tool thuộc nhóm Custom Module gọi một action mở cho "agent". Agent thấy mô tả và schema input của action, kể cả các đoạn describe, và tự sinh input; Cogover kiểm tra input đó như mọi input khác. Agent nhận output dưới dạng JSON, hoặc mã lỗi và thông báo. Tool cho action "write" mặc định cần người phê duyệt; cấu hình của agent có thể đổi điều này.

Lỗi bên gọi nhận được

Lời gọi đã chạy báo COMPLETED kèm output, hoặc FAILED với một trong các mã sau:

Mã Ý nghĩa
INPUT_INVALID Input không khớp schema input; handler không chạy. details.errors liệt kê path và message của từng lỗi.
OUTPUT_INVALID Handler trả về giá trị không phải dữ liệu JSON hoặc không khớp schema output.
SCRIPT_ERROR Handler ném lỗi SDK. details.scriptErrorCode là mã của lỗi đó, như VALIDATION_ERROR hoặc NOT_FOUND, kèm cùng thông báo an toàn mà HTTP route trả cho mã đó. Mọi lỗi khác được báo với thông báo The action handler failed.
TIMEOUT Lời gọi vượt timeoutMs; details.timeoutMs là giới hạn đã áp dụng.
RATE_LIMITED Lời gọi vượt một ngân sách của lần thực thi; details.budget mô tả ngân sách đó.
SCRIPT_ERROR với details.reason là "RESOURCE_LIMIT_EXCEEDED" Handler dùng hết một giới hạn tài nguyên của sandbox: số câu lệnh, bộ nhớ hoặc stack.
PERMISSION_DENIED Handler để lọt PermissionDeniedError, ví dụ khi ghi trong action "read". Trong lúc activate version mới, lời gọi có thể thất bại trong chốc lát với details.reason là "PROJECT_POLICY_NOT_APPROVED" trước khi chạy; lần gửi lại của bên gọi sẽ chạy.
INTERNAL_ERROR Lỗi khác.

Lời gọi cũng có thể bị từ chối trước khi action chạy: không tìm thấy action trong version đang active hoặc action không mở cho bên gọi đó, người mà action sẽ chạy dưới danh tính không còn là thành viên đang hoạt động, lời gọi không có người dùng không được identity policy cho phép, ngân sách tự động hoá đã hết, cùng action.runId đã được dùng với input khác hoặc bởi bên gọi hay danh tính khác, hoặc Cogover tạm thời không sẵn sàng (bên gọi sẽ thử lại).

© 2026 Cogover LLC