Ctrl K
ComunidadGuías…

lib/assistant-types.ts

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
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;
}