lib/assistant-types.ts
Rama main · réplica de lectura del repositorio original.
import type { ReasoningLevel } from "./agent-reasoning";
import type { ObjectiveStatus } from "./objective-validation";
import type { ResourceInput } from "./types";
import type { IssueStatus } from "./issue-constants";
import type { SelfHostingHelpContext } from "./self-hosting-help-context";
// ── Numo (AI assistant) shared types ─────────────────────────────────
export type ConversationStatus = "idle" | "generating" | "error";
export type NumoTurnStatus =
| "queued"
| "running"
| "waiting_input"
| "stopping"
| "stopped"
| "retryable"
| "reconciling"
| "completed"
| "failed";
export interface NumoTurnActivity {
id: string;
seq: number;
type: string;
payload: Record<string, unknown>;
created_at: string;
}
export interface Conversation {
id: string;
project_id: string | null;
user_id: string;
title: string | null;
status: ConversationStatus;
error_message: string | null;
created_at: string;
updated_at: string;
/** Joined project name, present on list responses in global mode. */
project?: { name: string } | null;
}
export interface NumoConversation extends Omit<Conversation, "user_id"> {
user_id: string | null;
source: "assistant";
/** The assistant conversation row behind the common Numo identity. */
legacy_id: string;
archived_at: string | null;
pinned_at: string | null;
last_read_at: string | null;
/** Time of Numo's latest answer; null while the conversation has none. */
last_answer_at: string | null;
/** Explicit conversation choice; null preserves the account default. */
/** Provider of the conversation (MIN-826); null follows Numo's default provider. */
provider?: string | null;
model?: string | null;
/** Explicit conversation choice; null preserves the compatible legacy default. */
reasoning_level?: ReasoningLevel | null;
}
export interface NumoMessage extends AssistantMessage {
source: "assistant";
kind: "message" | "action";
turn_id: string | null;
}
export interface NumoConversationDetail {
conversation: NumoConversation;
messages: NumoMessage[];
actions: NumoMessage[];
turns: Array<{
id: string; conversation_id: string; run_id: string;
status: NumoTurnStatus; model: string | null;
reasoning_level: string | null; initiated_by: string | null;
cost_usd: number; outcome: string | null; error_message: string | null;
started_at: string | null; completed_at: string | null;
created_at: string; updated_at: string;
}>;
/** Trigger provenance when this conversation is a routine occurrence. */
routine_occurrence?: {
id: string;
routine_id: string;
origin: "scheduled" | "manual" | "event" | "database";
event_id?: number | null;
subject_id?: string | null;
subject_type?: "issue" | "page" | "feedback" | null;
scheduled_for: string | null;
created_at: string;
} | null;
}
export interface NumoConversationPatch {
title?: string | null;
pinned?: boolean;
archived?: boolean;
read?: boolean;
/** Empty or null follows the active assistant provider default. */
/** Provider of the conversation (MIN-826); null follows Numo's default provider. */
provider?: string | null;
model?: string | null;
/** Null follows the instance assistant reasoning default. */
reasoningLevel?: ReasoningLevel | null;
}
export interface AssistantToolCall {
id: string;
type: "function";
function: {
name: string;
arguments: string;
};
/**
* Provider data echoed back with the call on the next round: Gemini 3 puts
* its `thought_signature` here and rejects a later round without it.
*/
extra_content?: unknown;
}
export interface AssistantMessage {
id: string;
conversation_id: string;
role: "user" | "assistant" | "system" | "tool";
content: string | null;
tool_calls: AssistantToolCall[] | null;
tool_call_id: string | null;
tool_name: string | null;
/** On user messages, `metadata.attachments` carries the files sent with the
message (AttachmentInput[] shape) — chat files have no DB row. */
metadata: Record<string, unknown>;
/**
* The page context the message was sent with (open issue, board onglet…),
* persisted so the chat can render a context badge above the user bubble.
* Only set on user messages sent from a contextual surface; null otherwise.
*/
context?: AssistantPageContext | null;
created_at: string;
}
/**
* A hand-chosen context element (@ button on the composer): a ticket,
* a project or a team member. The label comes from the client — it's only there
* so Numo can name the thing without re-resolving its id.
*/
export interface AssistantPinnedContext {
/** Validated project provenance of the referenced resource. */
projectId?: string;
kind: "issue" | "project" | "member" | "objective" | "page";
id: string;
/** Examples such as “MIN-42”, “minddy”, and “Clément Guérin” — what the pill displays. */
label: string;
/** Secondary detail: ticket title, member email. */
detail?: string;
/** Members: the portrait seed (public.user_avatars), which is NOT
still the user_id — inferring it would show another face. */
avatarSeed?: string;
/** Objectives: their color — this is what their target wears, here like
everywhere else in the application. */
color?: string | null;
/** Objectives: their status, shown instead of the target (MIN-785). */
objectiveStatus?: ObjectiveStatus;
/** Wiki pages: their emoji, when they have one. */
icon?: string | null;
}
/**
* A “/” command chosen from the composer slash menu. The id is canonical
* (the label is localized - "/create issue" / "/create ticket"):
* it is he who travels in the request and persists on `metadata.command`
* of the user message, where the server unfolds it into instructions for Numo.
*/
export type AssistantCommandId = "create-issue";
/**
* An "@" mention written IN the message (team member, project, ticket
* or goal), resolved at the time of typing. Persisted on
* `metadata.mentions` of the user message: it is used to return the pill
* to the bubble, and to tell Numo who/what this name designates exactly.
*/
export interface AssistantMention {
projectId?: string;
type: "member" | "project" | "issue" | "objective" | "page";
id: string;
/** The text written after the “@” in the message. */
label: string;
/** Zero-based occurrence of this exact token label in a persisted prompt. */
occurrence?: number;
/** Members: the portrait seed — see AssistantPinnedContext.avatarSeed. */
avatarSeed?: string;
/** Objectives: their color; see AssistantPinnedContext.color. */
color?: string | null;
/** Wiki pages: their emoji (MIN-273). */
icon?: string | null;
/** Issues: workflow state captured for immediate rendering; live sources
win. Objectives: the issue state their status borrows (MIN-785). */
status?: IssueStatus;
}
/** A repository skill explicitly attached to one Numo message. */
export interface AssistantSkillSelection {
projectId?: string;
/** Repository-relative SKILL.md entrypoint; it is the stable selection id. */
path: string;
name: string;
description: string;
}
/**
* Structured "what the user is currently looking at" context, attached to a
* chat request so Numo can resolve deictic references ("this ticket", "this
* view") to a concrete issue/board without guessing. Derived ambiently from
* the page the user is on. Plain opens with no surface leave it undefined.
* Client-set, server-validated.
*/
export interface AssistantPageContext {
/** Public guide context; this surface only permits documentation help. */
documentation?: { articleId: string | null; locale: string; selfHosting?: SelfHostingHelpContext };
projectId?: string;
/** The account-level inbox is the current ambient surface. */
inbox?: true;
/** The settings surface the assistant was opened from. */
settings?: "account" | "project";
/** The admin dashboard tab the assistant was opened from (MIN-829). */
admin?: "accounts" | "costs" | "models" | "instance";
/**
* Context PINED by hand from the composer (@ button), as opposed to
* from the rest of this object, inferred from the page. It survives navigation: it's
* the user who chose it, not the page that published it.
*/
pinned?: AssistantPinnedContext[];
/** Legacy (pre views-v2): the board tab the message was sent from. No longer
populated — kept so old persisted messages still render their badge. */
onglet?: "my" | "all";
/** The issue open in the side panel (or selected in triage), when any. */
issueId?: string;
/** Issues selected for a bulk assistant request. */
issueIds?: string[];
/** Project provenance aligned with issueIds, resolved by the server. */
issueProjectIds?: string[];
issueIdentifiers?: string[];
issueTitles?: string[];
/** Human identifier ("MIND-42") — used for the context badge. */
issueIdentifier?: string;
issueTitle?: string;
/** The objective whose filtered board is displayed, when any. */
objectiveId?: string;
objectiveName?: string;
/** Its color — what its target wears on the context pill. */
objectiveColor?: string | null;
/** Its status, whose glyph the context pill shows (MIN-785). */
objectiveStatus?: ObjectiveStatus;
/** The feedback post open in the team dashboard, when any (MIN-52). */
feedbackId?: string;
feedbackTitle?: string;
/** The routine open in the Agents page's Routines tab, when any (MIN-185). */
routineId?: string;
routineTitle?: string;
/** Canonical Minddy pull-request row. Unlike `prRunId`, this also exists for
human pull requests and survives worker changes. */
pullRequestId?: string;
prNumber?: number;
prState?: string;
/** Selected head/base refs retained with the voluntary request. */
prHeadRef?: string;
prBaseRef?: string;
/** Canonical agent run id backing the PR — what read_pull_request resolves. */
prRunId?: string;
/** Historical worker explicitly selected for continuation through Numo. */
codeWorkerRunId?: string;
/** The saved kanban view currently selected on the board, when any. */
viewId?: string;
viewName?: string;
/** The cycle displayed in cycle mode (MIN-32), when any. */
cycleId?: string;
/** Human date-range label ("6–19 juil") — used for the context badge. */
cycleLabel?: string;
/**
* The opened wiki PAGE (MIN-273). The title travels with the id so that the
* pill says it without rereading the page, and so that Numo can name it before
* its first call of tool.
*/
pageId?: string;
pageTitle?: string;
pageIcon?: string | null;
/**
* Client-only hint that the open page is a database, so the empty composer
* offers database suggestions. The chat route never forwards it: the server
* derives `pageIsDatabase` on its own.
*/
pageDatabase?: true;
/**
* Set by the server, never read from the request: the open page is a
* database, so "a view" means one of its saved views, not a board view.
*/
pageIsDatabase?: boolean;
/**
* A passage the user selected in an editor and handed to Numo ("Ask Numo"
* in the selection toolbar). It belongs to the message being written: the
* composer drops it once that message is sent. Normalized and capped by
* `normalizeSelectionText` on both sides (lib/assistant-selection.ts).
*/
selection?: AssistantSelectionContext;
/**
* Records the user selected together on a list surface (MIN-873), all of
* one kind. Tickets keep their own `issueIds` lists.
*/
selectedItems?: AssistantSelectedItem[];
}
/**
* One record of a group selected on a list surface other than an issue board
* (MIN-873): feedback posts, routines, objectives or wiki pages. The label is
* the record's title, so Numo can name it before its first tool call.
*/
export interface AssistantSelectedItem {
kind: AssistantSelectedItemKind;
id: string;
label: string;
}
export type AssistantSelectedItemKind = "feedback" | "routine" | "objective" | "page";
/** The quoted passage of `AssistantPageContext.selection`. */
export interface AssistantSelectionContext {
text: string;
}
/** Where a voluntary request entered Numo. This is product context, not an
* execution choice: repository work is still delegated by Numo itself. */
export type NumoIntentSource =
| "home"
| "issue"
| "objective"
| "feedback"
| "page"
| "scratchpad"
| "code_worker"
| "pull_request"
| "routine"
| "bulk"
/** Background Numo (MIN-929): a wake on workspace events, never a client gesture. */
| "background"
/** A reminder the user asked Numo for (MIN-923) whose condition was met. */
| "reminder";
/** The distinction the originating action promised to preserve. */
export type NumoIntentAction =
| "discuss"
| "implement"
| "plan"
| "verify"
| "custom"
| "review"
| "fix"
| "promote";
/**
* The single client-side entry contract for a voluntary Numo request.
*
* Every source supplies the original wording plus structured source context.
* Model selection, Minddy tools and code-worker delegation remain decisions of
* the common conversation runtime rather than fields on this payload.
*/
export interface NumoIntent {
source: NumoIntentSource;
action: NumoIntentAction;
projectId: string | null;
/** Omit when the gesture only opens the shared composer with source context. */
prompt?: string;
pageContext?: AssistantPageContext;
mentions?: AssistantMention[];
command?: AssistantCommandId;
attachments?: ResourceInput[];
}
// Request body for chat endpoint
export interface AssistantChatRequest {
/** Idempotency key for one user intent. Reuse it when the same POST is retried. */
requestId?: string;
/** Identity reserved by the browser before the first response headers arrive. */
newConversationId?: string;
conversationId?: string;
projectId?: string;
message: string;
/** Voluntary entry provenance, persisted with the user message. */
intent?: Pick<NumoIntent, "source" | "action">;
/**
* The version of Numo's Home summary this first message answers (MIN-933):
* the new conversation opens with it as Numo's first message.
*/
homeBriefId?: string;
/** Exact pending delegated-worker question answered by this card submission. */
workerInput?: {
parentTurnId: string;
runId: string;
questionId: string;
};
/** Model selected for this conversation; omitted/null follows its default. */
/** Conversation provider (MIN-826): `minddy` or a credential provider; null follows the Numo default. */
provider?: string | null;
model?: string | null;
/** Reasoning selected for this conversation; omitted/null follows its default. */
reasoningLevel?: ReasoningLevel | null;
/**
* What the user is currently viewing (open issue, board onglet, objective).
* Injected into the system prompt so Numo can resolve "ce ticket" precisely.
* Carried on the messages of the originating session.
*/
pageContext?: AssistantPageContext;
/**
* Files already uploaded to the `attachments` bucket under `chat/{uid}/…`
* (AttachmentInput[] shape) — validated server-side against that prefix and
* persisted on the user message's metadata.
*/
attachments?: Array<{
storage_path: string;
file_name: string;
mime_type: string;
size_bytes: number;
}>;
/**
* The "@" written in the message (members, projects), resolved on the client side.
* Persisted on the message metadata and given to Numo in the form of a
* resolution line name → id.
*/
mentions?: AssistantMention[];
/**
* The “/” command placed at the top of the message, when there is one. Validated
* server, persisted on the message metadata and unfolded as a block
* of instructions attached to this message (same mechanics as the mentions).
*/
command?: AssistantCommandId;
/** Repository entrypoints selected through the slash or add menu. */
skillPaths?: string[];
skills?: AssistantSkillSelection[];
/**
* The browser's IANA zone (MIN-185). Without it, "creates a routine every
* Mondays at 1 p.m." would go into UTC without anyone knowing — and would find out weeks later, when the routine runs. This is
* data that only the client knows: the server cannot guess it.
*/
timezone?: string;
}