ghostykit 0.6.4 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,249 @@
1
+ import type { ComponentType, ReactNode } from "react";
2
+ import type { BaseEvent } from "@ag-ui/core";
3
+ import type { TextBlock } from "../core/narration";
4
+ import type { ToolVerbs } from "../core/steps";
5
+ import type { Attachment, CustomEntry, PilotState, Reference, ToolEntry, Turn } from "../core/types";
6
+ import type { UploadResult } from "./Composer";
7
+ import type { MarkdownOptions, PilotIcon } from "./context";
8
+ import type { PilotStrings } from "./strings";
9
+ import type { Pilot, TurnDoneInfo } from "./usePilot";
10
+ /** Lo que todo plugin recibe: el piloto, el estado del agente y las acciones del chat. */
11
+ export interface PluginCtx {
12
+ pilot: Pilot;
13
+ /** `pilot.state.agentState`, a la mano. */
14
+ agentState: Record<string, unknown>;
15
+ strings: PilotStrings;
16
+ send: (text: string, attachments?: Attachment[]) => void;
17
+ addReference: (ref: Reference) => void;
18
+ /** Escribe en la caja sin mandar. */
19
+ setDraft: (text: string) => void;
20
+ /**
21
+ * Agrega adjuntos a la caja, como si la persona los eligiera: `File` pasa por `upload`; un
22
+ * `Attachment` (ya subido, p. ej. de «Mis archivos») va tal cual. Se mandan con o sin texto.
23
+ */
24
+ addAttachments: (items: (File | Attachment)[]) => void;
25
+ /** Pone el foco en la caja. */
26
+ focus: () => void;
27
+ /** Los plugins montados, en orden. */
28
+ plugins: readonly PilotoPlugin[];
29
+ }
30
+ /** Un componente de plugin: recibe `ctx` y, según el punto, lo que dibuja. */
31
+ export type PluginView<P = object> = ComponentType<P & {
32
+ ctx: PluginCtx;
33
+ }>;
34
+ /** Un evento AG-UI como lo recibe `events` (los CUSTOM traen `name` y `value`). */
35
+ export type PluginEvent = BaseEvent & {
36
+ name?: string;
37
+ value?: unknown;
38
+ [k: string]: unknown;
39
+ };
40
+ /** El markdown de las respuestas, desde un plugin. */
41
+ export interface PluginMarkdown {
42
+ /** Reescribe el markdown antes de pintarlo, con el turno a la mano. Se encadena en orden. */
43
+ transform?: (md: string, turn?: Turn) => string;
44
+ /** Un clic en un enlace: `true` si el plugin lo manejó. Se pregunta en orden. */
45
+ onLinkClick?: MarkdownOptions["onLinkClick"];
46
+ /**
47
+ * Bloques con nombre del texto (```eb-file …```) que se pintan como tarjeta y salen del markdown,
48
+ * por su nombre. Uno a medio escribir llega con `complete: false`.
49
+ */
50
+ blocks?: Record<string, PluginView<{
51
+ block: TextBlock;
52
+ turn: Turn;
53
+ live: boolean;
54
+ }>>;
55
+ /**
56
+ * Cómo se pinta un elemento del markdown, por etiqueta (`a`, `code`, `img`…): p. ej. un enlace que
57
+ * es un CTA como botón. Recibe las props del elemento y `ctx`. Por etiqueta, gana el primer plugin.
58
+ */
59
+ components?: Record<string, ComponentType<Record<string, unknown> & {
60
+ ctx: PluginCtx;
61
+ children?: ReactNode;
62
+ }>>;
63
+ }
64
+ /** Un rechazo HTTP del servidor antes del stream (403 con `x-needs-login`, 429…). */
65
+ export type PluginHttpError = NonNullable<PilotState["httpError"]>;
66
+ export interface PilotoPlugin {
67
+ /** Único: aparece en `data-plugin`, en los avisos y en `pilotDevtools()`. */
68
+ name: string;
69
+ /**
70
+ * De quién son los estilos de lo que dibuja. `host` (default): de la app; las utilidades de
71
+ * ghostykit no lo tocan. `ghostykit`: usa las clases de ghostykit (para componentes de ghostykit
72
+ * como `<Subagents>` o `ModelPicker`).
73
+ */
74
+ styles?: "host" | "ghostykit";
75
+ /**
76
+ * Lo que el plugin atiende, como documentación visible. Si falta, se deriva de `events`, `state` y
77
+ * `message.custom`; si está y no coincide, en desarrollo se avisa.
78
+ */
79
+ requires?: {
80
+ events?: string[];
81
+ state?: string[];
82
+ };
83
+ /** Textos (se combinan sobre los del idioma). */
84
+ strings?: Partial<PilotStrings>;
85
+ markdown?: PluginMarkdown;
86
+ /** Variables `--piloto-*` y clases extra para la raíz (branding fino sobre la piel). */
87
+ vars?: Record<string, string>;
88
+ className?: string;
89
+ tools?: {
90
+ /** Cómo se nombra cada herramienta (`one`, `many`, `running`). */
91
+ verbs?: ToolVerbs;
92
+ /** Ícono por nombre de herramienta o por `kind`. */
93
+ icons?: Record<string, PilotIcon>;
94
+ /** Una vista propia por nombre de herramienta (p. ej. la vista previa de `create_image`), bajo los pasos. */
95
+ render?: Record<string, PluginView<{
96
+ tool: ToolEntry;
97
+ turn: Turn;
98
+ live: boolean;
99
+ }>>;
100
+ };
101
+ /** Por tipo AG-UI (`STATE_DELTA`), por nombre de CUSTOM (`ghosty.page`) o `*` para todos. */
102
+ events?: Record<string, (event: PluginEvent, ctx: PluginCtx) => void>;
103
+ /** Por llave de `agentState` (`model`, `subagents`): se llama cuando cambia. */
104
+ state?: Record<string, (value: unknown, ctx: PluginCtx) => void>;
105
+ message?: {
106
+ /** Bajo cada mensaje del usuario. */
107
+ user?: PluginView<{
108
+ turn: Turn;
109
+ }>;
110
+ /** Bajo cada respuesta. */
111
+ assistant?: PluginView<{
112
+ turn: Turn;
113
+ live: boolean;
114
+ }>;
115
+ /** Antes del texto de cada respuesta, bajo sus pasos (avance de una página, «creando imagen»). */
116
+ assistantBefore?: PluginView<{
117
+ turn: Turn;
118
+ live: boolean;
119
+ }>;
120
+ /** Una entrada de `turn.custom` por su nombre (`ghosty.delivery`, tarjetas CRM…). */
121
+ custom?: Record<string, PluginView<{
122
+ entry: CustomEntry;
123
+ turn: Turn;
124
+ live: boolean;
125
+ }>>;
126
+ /** El texto del usuario como se pinta (se encadena en orden). */
127
+ formatUserText?: (text: string) => string;
128
+ /** El texto que sale, antes de mandarlo (p. ej. anteponer lo señalado). Se encadena en orden. */
129
+ beforeSend?: (text: string, ctx: PluginCtx) => string;
130
+ /** El avatar del agente junto a cada respuesta. Gana el primer plugin. */
131
+ avatar?: PluginView<{
132
+ turn: Turn;
133
+ }>;
134
+ };
135
+ composer?: {
136
+ /** Botones en la barra del composer, entre la imagen y el clip («Mis archivos», «Señalar»). */
137
+ actions?: PluginView;
138
+ /** Botones en la barra del composer, después del clip (selector de modelo). */
139
+ actionsEnd?: PluginView;
140
+ /** Bajo la caja (chips de sugerencias en el hilo vacío). */
141
+ below?: PluginView;
142
+ /** Pegado arriba de la caja (chips, avisos, subagentes). */
143
+ above?: PluginView;
144
+ /** Adjuntos y voz. */
145
+ attachments?: {
146
+ upload?: (file: File) => Promise<UploadResult>;
147
+ transcribe?: (audio: Blob) => Promise<string>;
148
+ voiceNoteText?: (transcript: string) => string;
149
+ /** Qué se puede adjuntar (como el `accept` de un input): `"image/*"` = sólo fotos. */
150
+ accept?: string;
151
+ /** Cuántos archivos a la vez (default 8). */
152
+ maxFiles?: number;
153
+ /** Tamaño máximo por archivo, en bytes (default 25 MB). */
154
+ maxBytes?: number;
155
+ };
156
+ /**
157
+ * En lugar de la caja, cuando `when(ctx)` es verdad (p. ej. la prueba se acabó: 403 con
158
+ * `x-needs-login` → «Crea tu cuenta»). Gana el primer plugin cuyo `when` es verdad.
159
+ */
160
+ replace?: {
161
+ when: (ctx: PluginCtx) => boolean;
162
+ view: PluginView;
163
+ };
164
+ };
165
+ /** Arriba a la derecha, sobre el hilo. */
166
+ header?: PluginView;
167
+ /** Junto al chat. */
168
+ sidePanel?: PluginView;
169
+ /** Arriba de los mensajes, dentro del hilo. */
170
+ aboveThread?: PluginView;
171
+ /** El hilo vacío (en lugar del saludo). */
172
+ empty?: PluginView;
173
+ /** Lo que se ve mientras el agente no contesta (en lugar de la frase de espera). */
174
+ waiting?: PluginView<{
175
+ step: string | null;
176
+ since: number;
177
+ }>;
178
+ /** Envuelve TODO el chat (un proveedor: panel de artefactos, contexto propio). El primero va afuera. */
179
+ wrap?: ComponentType<{
180
+ ctx: PluginCtx;
181
+ children: ReactNode;
182
+ }>;
183
+ /** El servidor rechazó un mensaje antes de contestar (`ctx.pilot.state.httpError` también lo guarda). */
184
+ onHttpError?: (error: PluginHttpError, ctx: PluginCtx) => void;
185
+ /** Este cliente mandó un turno. */
186
+ onTurnStart?: (ctx: PluginCtx) => void;
187
+ /** Terminó un turno de este cliente (abrir la última entrega, re-leer datos). */
188
+ onTurnDone?: (info: TurnDoneInfo, ctx: PluginCtx) => void;
189
+ }
190
+ /** Ayuda de tipos: `definePlugin({ name: "crm", … })`. */
191
+ export declare function definePlugin<T extends PilotoPlugin>(plugin: T): T;
192
+ type Owned<T> = {
193
+ plugin: string;
194
+ value: T;
195
+ };
196
+ /** Los plugins ya combinados: lo que lee <Piloto>. */
197
+ export interface ResolvedPlugins {
198
+ list: readonly PilotoPlugin[];
199
+ strings: Partial<PilotStrings>;
200
+ verbs: ToolVerbs;
201
+ icons: Record<string, PilotIcon>;
202
+ markdown: MarkdownOptions;
203
+ formatUserText?: (text: string) => string;
204
+ vars: Record<string, string>;
205
+ className: string;
206
+ toolRenders: Record<string, Owned<NonNullable<NonNullable<PilotoPlugin["tools"]>["render"]>[string]>>;
207
+ customRenders: Record<string, Owned<NonNullable<NonNullable<PilotoPlugin["message"]>["custom"]>[string]>>;
208
+ blockRenders: Record<string, Owned<NonNullable<PluginMarkdown["blocks"]>[string]>>;
209
+ components: Record<string, Owned<NonNullable<PluginMarkdown["components"]>[string]>>;
210
+ avatar?: Owned<NonNullable<NonNullable<PilotoPlugin["message"]>["avatar"]>>;
211
+ beforeSend?: (text: string, ctx: PluginCtx) => string;
212
+ replacers: Owned<NonNullable<NonNullable<PilotoPlugin["composer"]>["replace"]>>[];
213
+ httpError: Owned<NonNullable<PilotoPlugin["onHttpError"]>>[];
214
+ attachments: NonNullable<NonNullable<PilotoPlugin["composer"]>["attachments"]>;
215
+ empty?: Owned<PluginView>;
216
+ waiting?: Owned<NonNullable<PilotoPlugin["waiting"]>>;
217
+ header: Owned<PluginView>[];
218
+ sidePanel: Owned<PluginView>[];
219
+ aboveThread: Owned<PluginView>[];
220
+ composerActions: Owned<PluginView>[];
221
+ composerAbove: Owned<PluginView>[];
222
+ composerActionsEnd: Owned<PluginView>[];
223
+ composerBelow: Owned<PluginView>[];
224
+ messageBefore: Owned<NonNullable<NonNullable<PilotoPlugin["message"]>["assistantBefore"]>>[];
225
+ wraps: Owned<NonNullable<PilotoPlugin["wrap"]>>[];
226
+ turnStart: Owned<NonNullable<PilotoPlugin["onTurnStart"]>>[];
227
+ turnDone: Owned<NonNullable<PilotoPlugin["onTurnDone"]>>[];
228
+ /** Los plugins cuyos dibujos usan los estilos de ghostykit (`styles: "ghostykit"`). */
229
+ ghostykitStyled: Set<string>;
230
+ messageUser: Owned<NonNullable<NonNullable<PilotoPlugin["message"]>["user"]>>[];
231
+ messageAssistant: Owned<NonNullable<NonNullable<PilotoPlugin["message"]>["assistant"]>>[];
232
+ events: Record<string, Owned<NonNullable<PilotoPlugin["events"]>[string]>[]>;
233
+ state: Record<string, Owned<NonNullable<PilotoPlugin["state"]>[string]>[]>;
234
+ /** Choques y declaraciones que no cuadran (se avisan en desarrollo). */
235
+ warnings: string[];
236
+ }
237
+ /** Lo que atiende un plugin: lo declarado en `requires` o, si falta, lo que se deriva de sus puntos. */
238
+ export declare function pluginRequires(p: PilotoPlugin): {
239
+ events: string[];
240
+ state: string[];
241
+ };
242
+ /** Los puntos de extensión que usa un plugin, por nombre (para `pilotDevtools()` y los avisos). */
243
+ export declare function pluginPoints(p: PilotoPlugin): string[];
244
+ export declare function resolvePlugins(list?: readonly PilotoPlugin[]): ResolvedPlugins;
245
+ /** Los CUSTOM que ghostykit atiende solo (no hace falta un plugin para ellos). */
246
+ export declare const BUILTIN_CUSTOM: Set<string>;
247
+ /** En desarrollo (lo decide el bundler de la app con `process.env.NODE_ENV`). */
248
+ export declare function isDev(): boolean;
249
+ export {};
@@ -12,6 +12,13 @@ export interface TurnDoneInfo {
12
12
  /** El run se detuvo a pedir aprobación. */
13
13
  interrupted?: boolean;
14
14
  }
15
+ /** El inicio o el fin de un turno de este cliente. */
16
+ export type TurnEvent = {
17
+ type: "start";
18
+ } | {
19
+ type: "done";
20
+ info: TurnDoneInfo;
21
+ };
15
22
  export interface UsePilotOptions {
16
23
  /** El hilo (AG-UI `threadId`). Con él, al montar se pide el hilo guardado (MESSAGES_SNAPSHOT). Sin él, hilo nuevo. */
17
24
  threadId?: string;
@@ -29,6 +36,8 @@ export interface UsePilotOptions {
29
36
  onTitle?: (title?: string) => void;
30
37
  /** Cada evento AG-UI crudo, antes del reductor (analítica, STATE_*, CUSTOM propios). */
31
38
  onEvent?: (event: BaseEvent) => void;
39
+ /** Un mensaje que sale solo en cuanto el hilo está abierto (p. ej. `?enviar=1` con la pregunta en la URL). */
40
+ initialPrompt?: string;
32
41
  /** Cada run que sale (analítica). */
33
42
  onSend?: (input: RunAgentInput) => void;
34
43
  /** El estado del agente cambió (`STATE_SNAPSHOT` / `STATE_DELTA` ya aplicado). */
@@ -82,6 +91,12 @@ export interface Pilot {
82
91
  * por fuera que algo cambió (p. ej. el bus `/me/events` avisa que un subagente terminó).
83
92
  */
84
93
  refresh: () => Promise<void>;
94
+ /** Vuelve a leer el hilo (igual que `refresh`): para una reconexión que decide la app (p. ej. su bus de eventos). */
95
+ reconnect: () => Promise<void>;
96
+ /** Escucha el inicio y el fin de cada turno de este cliente. Regresa cómo dejar de escuchar. */
97
+ onTurn: (listener: (event: TurnEvent) => void) => () => void;
98
+ /** Escucha cada evento AG-UI de este piloto (como `onEvent`, pero desde afuera). Regresa cómo dejar de escuchar. */
99
+ subscribe: (listener: (event: BaseEvent) => void) => () => void;
85
100
  /** Continúa el run que el servidor cortó (`state.cutRun`): `forwardedProps.continueRun`. */
86
101
  continueRun: () => Promise<void>;
87
102
  }