eve
Version:
Filesystem-first framework for durable backend AI agents that run anywhere.
142 lines (141 loc) • 5.9 kB
TypeScript
/**
* Slack HITL widget rendering + click-decode helpers.
*
* Wire format: select-style HITL widgets mint `action_id =
* HITL_ACTION_PREFIX + requestId`. Button widgets add a stable suffix so
* every button in the same Slack actions block has a unique `action_id`.
* The decoder requires that suffix for button payloads.
*
* Buttons surface the selected option on `action.value`; radio and
* static selects surface it on `selected_option.value`. The decoder
* picks whichever is set so the renderer can pick a widget kind on
* UX grounds without changing the read path.
*/
import type { InputRequest } from "#runtime/input/types.js";
/**
* Wire-format prefix every framework HITL widget mints onto its
* `action_id`. Exposed so end-user adapters that render their own
* interactive widgets can avoid collisions.
*/
export declare const HITL_ACTION_PREFIX = "eve_input:";
/**
* `action_id` prefix for the "Type your answer" button that opens a
* freeform-answer modal. Splitting the prefix from {@link HITL_ACTION_PREFIX}
* lets the route handler differentiate "this click is a final answer"
* (resolve via `inputResponses`) from "this click needs a modal first"
* (call `views.open`, then resolve on `view_submission`).
*/
export declare const HITL_FREEFORM_ACTION_PREFIX = "eve_input_freeform:";
/**
* `view.callback_id` carried on the freeform-answer modal. Used to
* route the inbound `view_submission` back to this channel.
*/
export declare const HITL_FREEFORM_MODAL_CALLBACK_ID = "eve_input_freeform_submit";
/**
* `block_id` of the modal's text-input block — the route reads the
* submitted text out of `view.state.values[block_id][action_id]`.
*/
export declare const HITL_FREEFORM_MODAL_BLOCK_ID = "eve_freeform_block";
/**
* `action_id` of the text input inside the freeform answer modal.
*/
export declare const HITL_FREEFORM_MODAL_ACTION_ID = "eve_freeform_text";
/**
* Subset of one Slack interactivity action the HITL decoder reads.
* Mirrors the relevant fields of `SlackInteractionAction`.
*/
interface SlackHitlAction {
readonly actionId: string;
/** `value` field on Slack `button` payloads. */
readonly value?: string;
/** `selected_option.value` field on radio / static-select payloads. */
readonly selectedOptionValue?: string;
}
/**
* Resolved HITL response derived from one Slack interactivity action.
* Matches the `InputResponse` contract minus `text` — freeform answers
* come back through a different interaction path.
*/
interface DerivedHitlResponse {
readonly requestId: string;
readonly optionId: string;
}
/**
* Decodes one Slack interactivity action into an HITL response, or
* returns `null` when the action does not match an HITL widget the
* framework rendered.
*/
export declare function deriveHitlResponse(action: SlackHitlAction): DerivedHitlResponse | null;
/**
* Returns `true` when the action id was minted by an HITL widget the
* framework rendered. Used by the channel route to split inbound
* clicks into the HITL path vs. the user-owned `onInteraction` path.
*/
export declare function isHitlAction(actionId: string): boolean;
/**
* Renders one `InputRequest` as Block Kit blocks:
*
* - `display === "select"` with ≤ {@link RADIO_SELECT_OPTION_LIMIT}
* options → `radio_buttons`. Single-click answer, options stay
* visible.
* - `display === "select"` with more options → `static_select`
* dropdown so the picker stays scrollable.
* - Anything else with options → buttons. Best for visually distinct
* choices (approve / deny / cancel).
* - No options (or `allowFreeform: true`) → a single "Type your answer"
* button that opens a Slack modal with a plain_text_input. The modal
* submission comes back as a `view_submission` webhook the channel
* resolves into an {@link InputResponse} carrying `text`.
*
* Always emits at least the prompt section.
*/
export declare function renderInputRequestBlocks(request: InputRequest): unknown[];
/**
* Metadata round-tripped on the freeform-answer modal's
* `private_metadata` field. Threaded from the button click that opens
* the modal to the `view_submission` that closes it so the route can
* deliver the answer back to the right session.
*/
export interface HitlFreeformModalMetadata {
readonly continuationToken: string;
readonly channelId: string;
readonly threadTs: string;
readonly messageTs: string;
readonly requestId: string;
}
/**
* Builds the `views.open` payload for the freeform-answer modal. The
* triggering `prompt` is preserved as a header section so the user can
* re-read what they're answering inside the modal.
*
* Title is auto-truncated to the Slack modal-title limit.
*/
export declare function buildFreeformModalView(input: {
readonly metadata: HitlFreeformModalMetadata;
readonly prompt?: string;
}): Record<string, unknown>;
/**
* True when an `action_id` was minted by the framework's freeform-answer
* button (the click that opens a modal — not the final answer).
*/
export declare function isFreeformAction(actionId: string): boolean;
/**
* Extracts the requestId from a freeform-answer button's `action_id`.
*/
export declare function freeformRequestIdFromActionId(actionId: string): string | undefined;
/**
* Renders the "answered" replacement blocks for a previously-posted
* HITL card. Preserves the original prompt block (so context stays
* visible), appends a confirmation line naming the chosen answer, and
* attributes the click to the user when their id is known.
*
* Slack's `chat.update` replaces every block in one shot, so the caller
* passes the full list to `blocks` and the rendered fallback text to
* `text`.
*/
export declare function buildAnsweredBlocks(input: {
readonly promptBlock: unknown;
readonly answerLabel: string;
readonly userId?: string;
}): unknown[];
export {};