ghostykit 0.7.0 → 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.
- package/README.md +10 -4
- package/dist/chunks/{chunk-FWBUIGTC.js → chunk-7M2B7VKH.js} +1 -1
- package/dist/chunks/{chunk-TMEHQZWL.js → chunk-KG23PQI2.js} +5 -2
- package/dist/chunks/{chunk-ZIUOXG7B.js → chunk-PNU3LKWG.js} +49 -8
- package/dist/core/index.js +1 -1
- package/dist/core/types.d.ts +7 -0
- package/dist/plugins/index.js +1 -1
- package/dist/react/Bubble.d.ts +5 -1
- package/dist/react/Composer.d.ts +5 -1
- package/dist/react/Piloto.d.ts +5 -0
- package/dist/react/context.d.ts +2 -0
- package/dist/react/index.js +69 -21
- package/dist/react/plugins.d.ts +38 -1
- package/dist/transport/index.d.ts +3 -1
- package/dist/transport/index.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -106,7 +106,8 @@ ref.current?.send("Why did this fail?"); // ask on the user'
|
|
|
106
106
|
```
|
|
107
107
|
|
|
108
108
|
Other props: `features` (`attachments`, `retry`, `copy`, `usage`, `replyOptions`, `duration`,
|
|
109
|
-
`steer`
|
|
109
|
+
`steer`, `narration`: `false` keeps the text written before each tool as part of the answer instead of
|
|
110
|
+
checked progress lines), `references` / `onReferencesChange`, `placeholder`, `avatar`, `initialTurns`, `loadOlder`
|
|
110
111
|
(earlier pages of a long thread), `forwardedProps` (extra keys for every run, e.g.
|
|
111
112
|
`{permissions: "ask"}`), and the callbacks `onTurnStart`, `onTitle`, `onState`, `onEvent` (every raw
|
|
112
113
|
AG-UI event), `onSend`. The agent's shared state (`STATE_SNAPSHOT` + `STATE_DELTA`) is
|
|
@@ -152,6 +153,7 @@ const PLUGINS = [crm, modelPicker({ onChange: (model) => api.setModel(model) }),
|
|
|
152
153
|
| `strings` | Texts over the locale's. | `{ stop: "Para" }` |
|
|
153
154
|
| `markdown.transform` | Rewrites the answer's markdown, with the turn at hand (chained). | `(md, turn) => md.replace(/#(\d+)/g, "[#$1](#folio-$1)")` |
|
|
154
155
|
| `markdown.onLinkClick` | A click on a link; `true` if handled (asked in order). | open in-app |
|
|
156
|
+
| `markdown.components` | How a markdown element is drawn, by tag (gets its props and `ctx`): e.g. a link that is a CTA as a button. | `{ a: ({ href, children, ctx }) => … }` |
|
|
155
157
|
| `markdown.blocks` | Fenced blocks by name (```` ```eb-file ````) taken out of the text and drawn as cards under the answer; one still being written comes with `complete: false`. | `{ "eb-file": FileCard }` |
|
|
156
158
|
| `vars`, `className` | Branding on the root: `--piloto-*` tokens and classes. | `{ "--piloto-radius-bubble": "6px" }` |
|
|
157
159
|
| `tools.verbs` | How each tool is named in the steps. | `{ run_cron: { one, many, running } }` |
|
|
@@ -163,17 +165,21 @@ const PLUGINS = [crm, modelPicker({ onChange: (model) => api.setModel(model) }),
|
|
|
163
165
|
| `message.assistantBefore` | Before each answer's text, under its steps. | live page progress, "Creating image…" |
|
|
164
166
|
| `message.user` / `message.assistant` | Extras under each user message / answer. | `({ turn, live }) => …` |
|
|
165
167
|
| `message.formatUserText` | How the user's text is shown (chained). | strip app plumbing |
|
|
168
|
+
| `message.beforeSend` | Rewrites the text that goes out (chained). | prepend what was pointed at |
|
|
169
|
+
| `message.avatar` | The agent's avatar next to each answer. | `() => <img src={logo} />` |
|
|
166
170
|
| `composer.actions` | Buttons in the composer bar, between the image button and the clip. | "My files", "Point" |
|
|
167
171
|
| `composer.actionsEnd` | Buttons after the clip. | model picker |
|
|
168
172
|
| `composer.above` | Above the message box. | `<Subagents pilot={ctx.pilot} />` |
|
|
169
173
|
| `composer.below` | Under the message box. | suggestion chips |
|
|
170
|
-
| `composer.attachments` | `upload`, `transcribe`, `voiceNoteText`. | `{ upload:
|
|
174
|
+
| `composer.attachments` | `upload`, `transcribe`, `voiceNoteText`, and the limits `accept`, `maxFiles`, `maxBytes`. A voice note keeps its audio attached when there is `upload`. | `{ upload, accept: "image/*", maxFiles: 4, maxBytes: 4_000_000 }` |
|
|
175
|
+
| `composer.replace` | `{ when(ctx), view }`: drawn instead of the message box while `when` is true. | trial over (403 + `x-needs-login`) → "Create your account" |
|
|
171
176
|
| `header` | Top right, over the thread. | share button |
|
|
172
177
|
| `sidePanel` | Next to the chat. | live page, subagents |
|
|
173
178
|
| `aboveThread` | Above the messages. | a banner |
|
|
174
179
|
| `empty` | The empty thread (instead of the greeting). | onboarding |
|
|
175
180
|
| `waiting` | While the agent hasn't answered (instead of the waiting phrase). Gets `step`, `since`. | "Creating your image…" |
|
|
176
181
|
| `wrap` | A provider around the whole chat (`{ctx, children}`); the first plugin is outermost. | artifacts panel |
|
|
182
|
+
| `onHttpError` | The server refused a message before answering (`{status, code, message, headers}`; also in `pilot.state.httpError` until the next send). | 429 → upgrade banner |
|
|
177
183
|
| `onTurnStart` / `onTurnDone` | This client started / finished a turn. | open the last delivery |
|
|
178
184
|
|
|
179
185
|
Every view gets `ctx`: `{pilot, agentState, strings, send, addReference, addAttachments, setDraft, focus, plugins}`.
|
|
@@ -186,9 +192,9 @@ in the URL).
|
|
|
186
192
|
|
|
187
193
|
**Order and precedence**, in the order of the `plugins` list:
|
|
188
194
|
|
|
189
|
-
- **Maps** (`strings`, `tools.verbs`, `tools.icons`, `vars`) are merged, and later plugins win. `markdown` and `
|
|
195
|
+
- **Maps** (`strings`, `tools.verbs`, `tools.icons`, `vars`) are merged, and later plugins win. `markdown.transform`, `formatUserText` and `beforeSend` are chained.
|
|
190
196
|
- **`events` and `state`** call every handler, in order.
|
|
191
|
-
- **Keyed or single views** (`tools.render`, `message.custom`, `markdown.blocks`, `empty`, `waiting`, `composer.attachments`): the first plugin that declares one wins. A clash is a development warning.
|
|
197
|
+
- **Keyed or single views** (`tools.render`, `message.custom`, `markdown.blocks`, `markdown.components`, `message.avatar`, `empty`, `waiting`, `composer.attachments`, `composer.replace`): the first plugin that declares one wins. A clash is a development warning.
|
|
192
198
|
- **Lists** (`header`, `sidePanel`, `aboveThread`, `composer.actions`/`actionsEnd`/`above`/`below`, `message.user`/`assistant`/`assistantBefore`, `wrap`, `onTurnStart`/`onTurnDone`) all apply, in order.
|
|
193
199
|
|
|
194
200
|
In development, ghostykit also warns once about a `CUSTOM` event that nothing handles.
|
|
@@ -439,7 +439,7 @@ function finish(s0, e, now, failed = false) {
|
|
|
439
439
|
}
|
|
440
440
|
function beginSend(prev, text, attachments = [], now = Date.now()) {
|
|
441
441
|
const turns = text || attachments.length ? [...prev.turns, { role: "user", text, ...attachments.length ? { attachments } : {} }] : prev.turns;
|
|
442
|
-
return { ...prev, turns, busy: true, error: null, notice: null, cutRun: null, turnStartedAt: now, effects: [{ type: "turn-start" }] };
|
|
442
|
+
return { ...prev, turns, busy: true, error: null, notice: null, cutRun: null, httpError: null, turnStartedAt: now, effects: [{ type: "turn-start" }] };
|
|
443
443
|
}
|
|
444
444
|
function beginSteer(prev, text) {
|
|
445
445
|
return { ...prev, turns: [...prev.turns, { role: "user", text }], notice: null, openText: null, effects: [] };
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
// src/transport/index.ts
|
|
2
2
|
var PilotHttpError = class extends Error {
|
|
3
|
-
constructor(message, status, code, retryAfter) {
|
|
3
|
+
constructor(message, status, code, retryAfter, headers) {
|
|
4
4
|
super(message);
|
|
5
5
|
this.name = "PilotHttpError";
|
|
6
6
|
this.status = status;
|
|
7
7
|
this.code = code;
|
|
8
8
|
this.retryAfter = retryAfter;
|
|
9
|
+
this.headers = headers;
|
|
9
10
|
}
|
|
10
11
|
};
|
|
11
12
|
async function readSse(body, onMessage) {
|
|
@@ -62,7 +63,9 @@ function createAgUiTransport(opts) {
|
|
|
62
63
|
}
|
|
63
64
|
if (!res.ok) {
|
|
64
65
|
const json = await res.json().catch(() => null);
|
|
65
|
-
|
|
66
|
+
const headers = {};
|
|
67
|
+
res.headers?.forEach?.((v, k) => void (headers[k.toLowerCase()] = v));
|
|
68
|
+
throw new PilotHttpError(json?.message ?? json?.error ?? `Error ${res.status}`, res.status, json?.error, retryAfter, headers);
|
|
66
69
|
}
|
|
67
70
|
if (!res.body) return;
|
|
68
71
|
const serverDate = Date.parse(res.headers.get("date") ?? "");
|
|
@@ -942,8 +942,25 @@ var __iconData37 = {
|
|
|
942
942
|
__iconData37.node;
|
|
943
943
|
var UsersRound = createLucideIcon(__iconData37);
|
|
944
944
|
|
|
945
|
-
// ../../node_modules/lucide-react/dist/esm/icons/
|
|
945
|
+
// ../../node_modules/lucide-react/dist/esm/icons/wrench.mjs
|
|
946
946
|
var __iconData38 = {
|
|
947
|
+
name: "wrench",
|
|
948
|
+
size: 24,
|
|
949
|
+
node: [
|
|
950
|
+
[
|
|
951
|
+
"path",
|
|
952
|
+
{
|
|
953
|
+
d: "M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.106-3.105c.32-.322.863-.22.983.218a6 6 0 0 1-8.259 7.057l-7.91 7.91a1 1 0 0 1-2.999-3l7.91-7.91a6 6 0 0 1 7.057-8.259c.438.12.54.662.219.984z",
|
|
954
|
+
key: "1ngwbx"
|
|
955
|
+
}
|
|
956
|
+
]
|
|
957
|
+
]
|
|
958
|
+
};
|
|
959
|
+
__iconData38.node;
|
|
960
|
+
var Wrench = createLucideIcon(__iconData38);
|
|
961
|
+
|
|
962
|
+
// ../../node_modules/lucide-react/dist/esm/icons/x.mjs
|
|
963
|
+
var __iconData39 = {
|
|
947
964
|
name: "x",
|
|
948
965
|
size: 24,
|
|
949
966
|
node: [
|
|
@@ -951,8 +968,8 @@ var __iconData38 = {
|
|
|
951
968
|
["path", { d: "m6 6 12 12", key: "d8bk6v" }]
|
|
952
969
|
]
|
|
953
970
|
};
|
|
954
|
-
|
|
955
|
-
var X = createLucideIcon(
|
|
971
|
+
__iconData39.node;
|
|
972
|
+
var X = createLucideIcon(__iconData39);
|
|
956
973
|
|
|
957
974
|
// src/react/Chips.tsx
|
|
958
975
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
@@ -1033,6 +1050,9 @@ function mimeOf(file) {
|
|
|
1033
1050
|
const ext = (file.name.match(/\.([A-Za-z0-9]+)$/)?.[1] || "").toLowerCase();
|
|
1034
1051
|
return BY_EXTENSION[ext] ?? "application/octet-stream";
|
|
1035
1052
|
}
|
|
1053
|
+
function accepts(accept, name, mime) {
|
|
1054
|
+
return accept.split(",").map((x) => x.trim().toLowerCase()).filter(Boolean).some((rule) => rule.startsWith(".") ? name.toLowerCase().endsWith(rule) : rule.endsWith("/*") ? mime.startsWith(rule.slice(0, -1)) : mime === rule);
|
|
1055
|
+
}
|
|
1036
1056
|
var isText = (mime) => mime.startsWith("text/") || mime === "application/json" || mime.endsWith("+json") || mime.endsWith("+xml");
|
|
1037
1057
|
function shortSize(bytes) {
|
|
1038
1058
|
if (bytes < 1024) return `${bytes} B`;
|
|
@@ -1073,6 +1093,7 @@ function Composer({
|
|
|
1073
1093
|
extras,
|
|
1074
1094
|
extrasEnd,
|
|
1075
1095
|
handleRef,
|
|
1096
|
+
accept,
|
|
1076
1097
|
maxFiles = 8,
|
|
1077
1098
|
maxFileMb = 25
|
|
1078
1099
|
}) {
|
|
@@ -1129,6 +1150,10 @@ function Composer({
|
|
|
1129
1150
|
rejected.push(t.tooBig(file.name, maxFileMb));
|
|
1130
1151
|
continue;
|
|
1131
1152
|
}
|
|
1153
|
+
if (accept && !accepts(accept, file.name, mime)) {
|
|
1154
|
+
rejected.push(t.cantUpload(file.name));
|
|
1155
|
+
continue;
|
|
1156
|
+
}
|
|
1132
1157
|
if (next.length >= maxFiles) {
|
|
1133
1158
|
rejected.push(t.tooMany(maxFiles));
|
|
1134
1159
|
break;
|
|
@@ -1150,7 +1175,7 @@ function Composer({
|
|
|
1150
1175
|
setNotice(rejected.length ? rejected.join(" \xB7 ") : null);
|
|
1151
1176
|
ref.current?.focus({ preventScroll: true });
|
|
1152
1177
|
},
|
|
1153
|
-
[busy, canSeeImages, upload, maxFiles, maxFileMb, t]
|
|
1178
|
+
[busy, canSeeImages, upload, maxFiles, maxFileMb, accept, t]
|
|
1154
1179
|
);
|
|
1155
1180
|
useEffect(() => {
|
|
1156
1181
|
if (!withFiles) return;
|
|
@@ -1434,7 +1459,7 @@ function Composer({
|
|
|
1434
1459
|
/* @__PURE__ */ jsxs2("div", { className: "flex items-center justify-between gap-2", children: [
|
|
1435
1460
|
/* @__PURE__ */ jsxs2("div", { className: "@container flex min-w-0 flex-1 items-center gap-1.5 overflow-hidden", children: [
|
|
1436
1461
|
withFiles && /* @__PURE__ */ jsxs2(Fragment2, { children: [
|
|
1437
|
-
/* @__PURE__ */ jsx2(
|
|
1462
|
+
(!accept || accepts(accept, "x.png", "image/png")) && /* @__PURE__ */ jsx2(
|
|
1438
1463
|
"button",
|
|
1439
1464
|
{
|
|
1440
1465
|
type: "button",
|
|
@@ -1451,13 +1476,13 @@ function Composer({
|
|
|
1451
1476
|
}
|
|
1452
1477
|
),
|
|
1453
1478
|
extras,
|
|
1454
|
-
/* @__PURE__ */ jsx2(
|
|
1479
|
+
(!accept || !/^\s*image\/\*\s*$/.test(accept)) && /* @__PURE__ */ jsx2(
|
|
1455
1480
|
"button",
|
|
1456
1481
|
{
|
|
1457
1482
|
type: "button",
|
|
1458
1483
|
onClick: () => {
|
|
1459
1484
|
if (!fileRef.current) return;
|
|
1460
|
-
fileRef.current.accept = "";
|
|
1485
|
+
fileRef.current.accept = accept ?? "";
|
|
1461
1486
|
fileRef.current.click();
|
|
1462
1487
|
},
|
|
1463
1488
|
disabled: busy || files.length >= maxFiles,
|
|
@@ -1583,6 +1608,11 @@ function pluginPoints(p) {
|
|
|
1583
1608
|
add(p.message?.assistant, "message.assistant");
|
|
1584
1609
|
for (const k of Object.keys(p.message?.custom ?? {})) out.push(`message.custom[${k}]`);
|
|
1585
1610
|
add(p.message?.formatUserText, "message.formatUserText");
|
|
1611
|
+
add(p.message?.beforeSend, "message.beforeSend");
|
|
1612
|
+
add(p.message?.avatar, "message.avatar");
|
|
1613
|
+
for (const k of Object.keys(p.markdown?.components ?? {})) out.push(`markdown.components[${k}]`);
|
|
1614
|
+
add(p.composer?.replace, "composer.replace");
|
|
1615
|
+
add(p.onHttpError, "onHttpError");
|
|
1586
1616
|
add(p.composer?.actions, "composer.actions");
|
|
1587
1617
|
add(p.composer?.actionsEnd, "composer.actionsEnd");
|
|
1588
1618
|
add(p.composer?.above, "composer.above");
|
|
@@ -1611,6 +1641,9 @@ function resolvePlugins(list = []) {
|
|
|
1611
1641
|
toolRenders: {},
|
|
1612
1642
|
customRenders: {},
|
|
1613
1643
|
blockRenders: {},
|
|
1644
|
+
components: {},
|
|
1645
|
+
replacers: [],
|
|
1646
|
+
httpError: [],
|
|
1614
1647
|
attachments: {},
|
|
1615
1648
|
header: [],
|
|
1616
1649
|
sidePanel: [],
|
|
@@ -1633,6 +1666,7 @@ function resolvePlugins(list = []) {
|
|
|
1633
1666
|
const transforms = [];
|
|
1634
1667
|
const linkHandlers = [];
|
|
1635
1668
|
const formatters = [];
|
|
1669
|
+
const beforeSends = [];
|
|
1636
1670
|
const names = /* @__PURE__ */ new Set();
|
|
1637
1671
|
const first = (slot, key, plugin, value, point) => {
|
|
1638
1672
|
if (slot[key]) r.warnings.push(`\xAB${plugin}\xBB y \xAB${slot[key].plugin}\xBB dibujan ${point}[${key}]: gana \xAB${slot[key].plugin}\xBB (va antes).`);
|
|
@@ -1654,13 +1688,18 @@ function resolvePlugins(list = []) {
|
|
|
1654
1688
|
for (const [k, v] of Object.entries(p.message?.custom ?? {})) first(r.customRenders, k, p.name, v, "message.custom");
|
|
1655
1689
|
for (const [k, v] of Object.entries(p.markdown?.blocks ?? {})) first(r.blockRenders, k, p.name, v, "markdown.blocks");
|
|
1656
1690
|
if (p.styles === "ghostykit") r.ghostykitStyled.add(p.name);
|
|
1691
|
+
for (const [k, v] of Object.entries(p.markdown?.components ?? {})) first(r.components, k, p.name, v, "markdown.components");
|
|
1692
|
+
if (p.message?.avatar) r.avatar ? r.warnings.push(`\xAB${p.name}\xBB tambi\xE9n da message.avatar: gana \xAB${r.avatar.plugin}\xBB.`) : r.avatar = own(p.message.avatar);
|
|
1693
|
+
if (p.message?.beforeSend) beforeSends.push(p.message.beforeSend);
|
|
1694
|
+
if (p.composer?.replace) r.replacers.push(own(p.composer.replace));
|
|
1695
|
+
if (p.onHttpError) r.httpError.push(own(p.onHttpError));
|
|
1657
1696
|
if (p.composer?.actionsEnd) r.composerActionsEnd.push(own(p.composer.actionsEnd));
|
|
1658
1697
|
if (p.composer?.below) r.composerBelow.push(own(p.composer.below));
|
|
1659
1698
|
if (p.message?.assistantBefore) r.messageBefore.push(own(p.message.assistantBefore));
|
|
1660
1699
|
if (p.wrap) r.wraps.push(own(p.wrap));
|
|
1661
1700
|
if (p.onTurnStart) r.turnStart.push(own(p.onTurnStart));
|
|
1662
1701
|
if (p.onTurnDone) r.turnDone.push(own(p.onTurnDone));
|
|
1663
|
-
for (const key of ["upload", "transcribe", "voiceNoteText"]) {
|
|
1702
|
+
for (const key of ["upload", "transcribe", "voiceNoteText", "accept", "maxFiles", "maxBytes"]) {
|
|
1664
1703
|
const v = p.composer?.attachments?.[key];
|
|
1665
1704
|
if (!v) continue;
|
|
1666
1705
|
if (r.attachments[key]) r.warnings.push(`\xAB${p.name}\xBB tambi\xE9n da composer.attachments.${key}: gana el primero.`);
|
|
@@ -1685,6 +1724,7 @@ function resolvePlugins(list = []) {
|
|
|
1685
1724
|
}
|
|
1686
1725
|
if (transforms.length) r.markdown.transform = (md, turn) => transforms.reduce((acc, f) => f(acc, turn), md);
|
|
1687
1726
|
if (linkHandlers.length) r.markdown.onLinkClick = (href, ev) => linkHandlers.some((f) => f(href, ev));
|
|
1727
|
+
if (beforeSends.length) r.beforeSend = (t, ctx) => beforeSends.reduce((acc, f) => f(acc, ctx), t);
|
|
1688
1728
|
if (formatters.length) r.formatUserText = (t) => formatters.reduce((acc, f) => f(acc), t);
|
|
1689
1729
|
return r;
|
|
1690
1730
|
}
|
|
@@ -1736,6 +1776,7 @@ export {
|
|
|
1736
1776
|
Send,
|
|
1737
1777
|
ShieldAlert,
|
|
1738
1778
|
UsersRound,
|
|
1779
|
+
Wrench,
|
|
1739
1780
|
X,
|
|
1740
1781
|
STRINGS_ES,
|
|
1741
1782
|
STRINGS_EN,
|
package/dist/core/index.js
CHANGED
package/dist/core/types.d.ts
CHANGED
|
@@ -159,6 +159,13 @@ export interface PilotState {
|
|
|
159
159
|
/** Cuándo empezó el turno que ESTE cliente encargó (0 = ninguno). */
|
|
160
160
|
turnStartedAt: number;
|
|
161
161
|
runId: string | null;
|
|
162
|
+
/** El último rechazo HTTP del servidor (antes del stream): para avisos propios (p. ej. 403 + `x-needs-login`). Se limpia al mandar. */
|
|
163
|
+
httpError?: {
|
|
164
|
+
status: number;
|
|
165
|
+
code?: string;
|
|
166
|
+
message: string;
|
|
167
|
+
headers?: Record<string, string>;
|
|
168
|
+
} | null;
|
|
162
169
|
/** El estado compartido del agente (`STATE_SNAPSHOT`, con `STATE_DELTA` aplicado encima). */
|
|
163
170
|
agentState: Record<string, unknown>;
|
|
164
171
|
/** El paso con nombre en curso (`STEP_STARTED`), p. ej. `waking`. */
|
package/dist/plugins/index.js
CHANGED
package/dist/react/Bubble.d.ts
CHANGED
|
@@ -40,10 +40,14 @@ export interface AgentBubbleProps {
|
|
|
40
40
|
renderTools?: (turn: Turn, live: boolean) => ReactNode;
|
|
41
41
|
/** Antes del texto de la respuesta, bajo sus pasos. */
|
|
42
42
|
renderBefore?: (turn: Turn, live: boolean) => ReactNode;
|
|
43
|
+
/** `false`: el texto antes de cada herramienta NO se vuelve paso con palomita (todo es respuesta). */
|
|
44
|
+
narration?: boolean;
|
|
45
|
+
/** El avatar del agente a la izquierda de la respuesta. */
|
|
46
|
+
avatar?: ReactNode;
|
|
43
47
|
/** Bloques con nombre (```eb-file```) que salen del texto y se pintan como tarjetas bajo la respuesta. */
|
|
44
48
|
blocks?: {
|
|
45
49
|
langs: string[];
|
|
46
50
|
render: (blocks: TextBlock[], turn: Turn, live: boolean) => ReactNode;
|
|
47
51
|
};
|
|
48
52
|
}
|
|
49
|
-
export declare function AgentBubble(
|
|
53
|
+
export declare function AgentBubble(props: AgentBubbleProps): import("react").JSX.Element;
|
package/dist/react/Composer.d.ts
CHANGED
|
@@ -16,6 +16,8 @@ export interface ComposerHandle {
|
|
|
16
16
|
}
|
|
17
17
|
/** El tipo del archivo, deducido del nombre cuando el navegador no lo dice (pasa más de lo que parece). */
|
|
18
18
|
export declare function mimeOf(file: File): string;
|
|
19
|
+
/** ¿Cabe en un `accept` («image/*,.pdf,application/json»)? */
|
|
20
|
+
export declare function accepts(accept: string, name: string, mime: string): boolean;
|
|
19
21
|
export interface ComposerProps {
|
|
20
22
|
onSubmit: (text: string, attachments: Attachment[]) => void;
|
|
21
23
|
onStop?: () => void;
|
|
@@ -46,10 +48,12 @@ export interface ComposerProps {
|
|
|
46
48
|
extrasEnd?: ReactNode;
|
|
47
49
|
/** Para agregar adjuntos desde afuera. */
|
|
48
50
|
handleRef?: Ref<ComposerHandle>;
|
|
51
|
+
/** Qué se puede adjuntar (como el `accept` de un input). Default: todo. */
|
|
52
|
+
accept?: string;
|
|
49
53
|
maxFiles?: number;
|
|
50
54
|
maxFileMb?: number;
|
|
51
55
|
}
|
|
52
|
-
export declare function Composer({ onSubmit, onStop, busy, canSteer, placeholder, initialValue, autoFocus, canSeeImages, attachments: withFiles, upload, transcribe, voiceNoteText, models, currentModel, onModelChange, references, onRemoveReference, onActivateReference, extras, extrasEnd, handleRef, maxFiles, maxFileMb, }: ComposerProps): import("react").JSX.Element;
|
|
56
|
+
export declare function Composer({ onSubmit, onStop, busy, canSteer, placeholder, initialValue, autoFocus, canSeeImages, attachments: withFiles, upload, transcribe, voiceNoteText, models, currentModel, onModelChange, references, onRemoveReference, onActivateReference, extras, extrasEnd, handleRef, accept, maxFiles, maxFileMb, }: ComposerProps): import("react").JSX.Element;
|
|
53
57
|
/**
|
|
54
58
|
* El selector de modelo. PORTADO de Ghosty (`ModelPicker.tsx`): dropdown discreto hacia arriba, sin
|
|
55
59
|
* Radix; cierra con clic fuera y con Escape.
|
package/dist/react/Piloto.d.ts
CHANGED
|
@@ -19,6 +19,11 @@ export interface PilotoFeatures {
|
|
|
19
19
|
duration?: boolean;
|
|
20
20
|
/** Mandar con el run vivo (steer). Default: lo que diga el agente en `state.caps.steer`. */
|
|
21
21
|
steer?: boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Lo que el agente dice ANTES de cada herramienta como pasos con palomita («Reviso tus crons.»).
|
|
24
|
+
* Default: sí. `false`: todo el texto es respuesta (los pasos siguen en su caja).
|
|
25
|
+
*/
|
|
26
|
+
narration?: boolean;
|
|
22
27
|
}
|
|
23
28
|
export interface PilotoProps extends UsePilotOptions {
|
|
24
29
|
transport: PilotTransport | null;
|
package/dist/react/context.d.ts
CHANGED
|
@@ -11,6 +11,8 @@ export type PilotTheme = "light" | "dark" | "auto";
|
|
|
11
11
|
export interface MarkdownOptions {
|
|
12
12
|
/** Reescribe el markdown antes de pintarlo (p. ej. `#12` → `[#12](#folio-12)`). */
|
|
13
13
|
transform?: (md: string, turn?: Turn) => string;
|
|
14
|
+
/** Cómo se pinta un elemento del markdown, por etiqueta (`a`, `code`…). */
|
|
15
|
+
components?: Record<string, ComponentType<any>>;
|
|
14
16
|
/** Un clic en un enlace. Devuelve `true` si la app lo manejó (no se abre el aviso de salida). */
|
|
15
17
|
onLinkClick?: (href: string, event: MouseEvent) => boolean;
|
|
16
18
|
}
|
package/dist/react/index.js
CHANGED
|
@@ -24,12 +24,12 @@ import {
|
|
|
24
24
|
subagentsOf,
|
|
25
25
|
takeQueued,
|
|
26
26
|
toPermission
|
|
27
|
-
} from "../chunks/chunk-
|
|
27
|
+
} from "../chunks/chunk-7M2B7VKH.js";
|
|
28
28
|
import {
|
|
29
29
|
PilotHttpError,
|
|
30
30
|
createAgUiTransport,
|
|
31
31
|
readSse
|
|
32
|
-
} from "../chunks/chunk-
|
|
32
|
+
} from "../chunks/chunk-KG23PQI2.js";
|
|
33
33
|
import {
|
|
34
34
|
Activity,
|
|
35
35
|
AppWindow,
|
|
@@ -70,6 +70,7 @@ import {
|
|
|
70
70
|
Suggestions,
|
|
71
71
|
TurnContext,
|
|
72
72
|
UsersRound,
|
|
73
|
+
Wrench,
|
|
73
74
|
X,
|
|
74
75
|
definePlugin,
|
|
75
76
|
isDev,
|
|
@@ -79,7 +80,7 @@ import {
|
|
|
79
80
|
resolvePlugins,
|
|
80
81
|
rootProps,
|
|
81
82
|
usePiloto
|
|
82
|
-
} from "../chunks/chunk-
|
|
83
|
+
} from "../chunks/chunk-PNU3LKWG.js";
|
|
83
84
|
import {
|
|
84
85
|
TOOL_VERBS_EN,
|
|
85
86
|
TOOL_VERBS_ES,
|
|
@@ -441,7 +442,7 @@ function Markdown({ children, animate = false, className }) {
|
|
|
441
442
|
{
|
|
442
443
|
onClickCapture: onClick,
|
|
443
444
|
className: `ghosty-md min-w-0 max-w-full break-words ${className ?? "text-text-primary"} [&_pre]:max-w-full [&_pre]:overflow-x-auto [&_table]:block [&_table]:max-w-full [&_table]:overflow-x-auto`,
|
|
444
|
-
children: animate ? /* @__PURE__ */ jsx3(ChunksContext.Provider, { value: { registry, part: 0 }, children: /* @__PURE__ */ jsx3(Streamdown, { BlockComponent: ChunkedBlock, linkSafety, children: text }) }) : /* @__PURE__ */ jsx3(Streamdown, { linkSafety, children: text })
|
|
445
|
+
children: animate ? /* @__PURE__ */ jsx3(ChunksContext.Provider, { value: { registry, part: 0 }, children: /* @__PURE__ */ jsx3(Streamdown, { BlockComponent: ChunkedBlock, linkSafety, components: markdown.components, children: text }) }) : /* @__PURE__ */ jsx3(Streamdown, { linkSafety, components: markdown.components, children: text })
|
|
445
446
|
}
|
|
446
447
|
)
|
|
447
448
|
);
|
|
@@ -544,7 +545,7 @@ var DEFAULT_TOOL_ICONS = {
|
|
|
544
545
|
};
|
|
545
546
|
function iconFor(t, icons) {
|
|
546
547
|
for (const name of toolNames(t)) if (icons[name]) return icons[name];
|
|
547
|
-
return t.kind && (icons[t.kind] ?? DEFAULT_TOOL_ICONS[t.kind]) ||
|
|
548
|
+
return t.kind && (icons[t.kind] ?? DEFAULT_TOOL_ICONS[t.kind]) || icons["*"] || Wrench;
|
|
548
549
|
}
|
|
549
550
|
var VISIBLE = 5;
|
|
550
551
|
function AgentSteps({ tools, live }) {
|
|
@@ -777,14 +778,22 @@ function TurnFooter({ text, seconds, onRetry, showCopy = true, showDuration = tr
|
|
|
777
778
|
] })
|
|
778
779
|
] });
|
|
779
780
|
}
|
|
780
|
-
function AgentBubble(
|
|
781
|
+
function AgentBubble(props) {
|
|
782
|
+
const { turn, avatar } = props;
|
|
783
|
+
const body = /* @__PURE__ */ jsx6(AgentBody, { ...props, showUsage: props.showUsage ?? true, showCopy: props.showCopy ?? true, showDuration: props.showDuration ?? true });
|
|
784
|
+
return /* @__PURE__ */ jsx6(TurnContext.Provider, { value: turn, children: avatar ? /* @__PURE__ */ jsxs4("div", { className: "flex items-start gap-3", children: [
|
|
785
|
+
/* @__PURE__ */ jsx6("div", { className: "mt-0.5 shrink-0", children: avatar }),
|
|
786
|
+
body
|
|
787
|
+
] }) : body });
|
|
788
|
+
}
|
|
789
|
+
function AgentBody({ turn, live, onRetry, onOption, showUsage, showCopy, showDuration, renderDeliveries, renderTools, renderBefore, blocks, narration = true }) {
|
|
781
790
|
const { strings: t } = usePiloto();
|
|
782
791
|
const extracted = blocks ? extractBlocks(turn.text ?? "", blocks.langs, turn.cuts) : { text: turn.text ?? "", cuts: turn.cuts, blocks: [] };
|
|
783
792
|
const closed = !live && !turn.tools?.some((x) => x.status === "pending");
|
|
784
|
-
const narrated = splitNarration(extracted.text, extracted.cuts, closed);
|
|
793
|
+
const narrated = narration ? splitNarration(extracted.text, extracted.cuts, closed) : { steps: [], rest: extracted.text };
|
|
785
794
|
const visible = narrated.rest;
|
|
786
795
|
const options = onOption && !live && visible ? extractReplyOptions(visible) : null;
|
|
787
|
-
return /* @__PURE__ */
|
|
796
|
+
return /* @__PURE__ */ jsxs4("div", { className: "gc-msg-agent min-w-0 max-w-[90%]", children: [
|
|
788
797
|
turn.thought && /* @__PURE__ */ jsxs4("details", { className: "mb-3 text-xs text-text-secondary", children: [
|
|
789
798
|
/* @__PURE__ */ jsx6("summary", { className: "cursor-pointer select-none", children: t.thinking }),
|
|
790
799
|
/* @__PURE__ */ jsx6("p", { className: "mt-2 whitespace-pre-wrap border-l-2 border-border-secondary pl-3", children: turn.thought })
|
|
@@ -805,7 +814,7 @@ function AgentBubble({ turn, live, onRetry, onOption, showUsage = true, showCopy
|
|
|
805
814
|
turn.decisions?.length ? /* @__PURE__ */ jsx6(DecisionLines, { decisions: turn.decisions }) : null,
|
|
806
815
|
showUsage && turn.usage && /* @__PURE__ */ jsx6(UsageStats, { ...turn.usage }),
|
|
807
816
|
turn.text && !live && /* @__PURE__ */ jsx6(TurnFooter, { text: turn.text, seconds: turn.seconds, onRetry, showCopy, showDuration })
|
|
808
|
-
] })
|
|
817
|
+
] });
|
|
809
818
|
}
|
|
810
819
|
|
|
811
820
|
// src/react/Status.tsx
|
|
@@ -1002,7 +1011,8 @@ function usePilot(transport, opts = {}) {
|
|
|
1002
1011
|
commit({ ...ref.current, notice: err instanceof Error && err.message ? err.message : null, effects: [] });
|
|
1003
1012
|
return runNow({ messages: [] }, true);
|
|
1004
1013
|
}
|
|
1005
|
-
|
|
1014
|
+
const failed = sendFailed(ref.current, err instanceof Error && err.message ? err.message : "No se pudo hablar con el agente.");
|
|
1015
|
+
commit(err instanceof PilotHttpError ? { ...failed, httpError: { status: err.status, ...err.code ? { code: err.code } : {}, message: err.message, ...err.headers ? { headers: err.headers } : {} } } : failed);
|
|
1006
1016
|
return;
|
|
1007
1017
|
}
|
|
1008
1018
|
if (c.signal.aborted) return;
|
|
@@ -1189,6 +1199,18 @@ var Piloto = forwardRef(function Piloto2(props, handle) {
|
|
|
1189
1199
|
useEffect5(() => {
|
|
1190
1200
|
if (isDev()) for (const w of plugins.warnings) warnOnce(w);
|
|
1191
1201
|
}, [plugins]);
|
|
1202
|
+
const ctxForComponents = useRef4(null);
|
|
1203
|
+
const boundComponents = useMemo3(() => {
|
|
1204
|
+
const entries = Object.entries(plugins.components);
|
|
1205
|
+
if (!entries.length) return void 0;
|
|
1206
|
+
return Object.fromEntries(
|
|
1207
|
+
entries.map(([tag, { plugin, value: C }]) => [
|
|
1208
|
+
tag,
|
|
1209
|
+
// Como toda vista de plugin: marcada con su nombre y, si es de la app, aislada de las utilidades.
|
|
1210
|
+
(p) => /* @__PURE__ */ jsx8("span", { ...pluginAttrs(plugin, plugins.ghostykitStyled), style: { display: "contents" }, children: /* @__PURE__ */ jsx8(C, { ...p, ctx: ctxForComponents.current }) })
|
|
1211
|
+
])
|
|
1212
|
+
);
|
|
1213
|
+
}, [plugins]);
|
|
1192
1214
|
const strings = useMemo3(() => ({ ...locale === "en" ? STRINGS_EN : STRINGS_ES, ...plugins.strings }), [locale, plugins]);
|
|
1193
1215
|
const config = useMemo3(
|
|
1194
1216
|
() => ({
|
|
@@ -1198,9 +1220,9 @@ var Piloto = forwardRef(function Piloto2(props, handle) {
|
|
|
1198
1220
|
formatUserText: plugins.formatUserText ? (x) => plugins.formatUserText(cleanUserText(x)) : cleanUserText,
|
|
1199
1221
|
theme,
|
|
1200
1222
|
skin: props.skin ?? "ghosty",
|
|
1201
|
-
markdown: plugins.markdown
|
|
1223
|
+
markdown: { ...plugins.markdown, ...boundComponents ? { components: boundComponents } : {} }
|
|
1202
1224
|
}),
|
|
1203
|
-
[strings, locale, plugins, theme, props.skin]
|
|
1225
|
+
[strings, locale, plugins, theme, props.skin, boundComponents]
|
|
1204
1226
|
);
|
|
1205
1227
|
const t = strings;
|
|
1206
1228
|
const [ownRefs, setOwnRefs] = useState8([]);
|
|
@@ -1235,12 +1257,16 @@ var Piloto = forwardRef(function Piloto2(props, handle) {
|
|
|
1235
1257
|
const send = useCallback2(
|
|
1236
1258
|
(text, attachments = []) => {
|
|
1237
1259
|
const current = refsRef.current;
|
|
1238
|
-
const
|
|
1260
|
+
const before = pluginsRef.current.beforeSend;
|
|
1261
|
+
const full = applyReferences(before && ctxRef.current ? before(text, ctxRef.current) : text, current);
|
|
1239
1262
|
if (current.length) setRefs([]);
|
|
1240
1263
|
void pilot.send(full, attachments, current.length ? { references: current } : {});
|
|
1241
1264
|
},
|
|
1242
1265
|
[pilot, setRefs]
|
|
1243
1266
|
);
|
|
1267
|
+
const pluginsRef = useRef4(plugins);
|
|
1268
|
+
pluginsRef.current = plugins;
|
|
1269
|
+
const ctxRef = useRef4(null);
|
|
1244
1270
|
const composerHandle = useRef4(null);
|
|
1245
1271
|
const focus = useCallback2(() => composerBox.current?.querySelector("textarea")?.focus({ preventScroll: true }), []);
|
|
1246
1272
|
const addReference = useCallback2(
|
|
@@ -1256,10 +1282,9 @@ var Piloto = forwardRef(function Piloto2(props, handle) {
|
|
|
1256
1282
|
const actions = { pilot, send, addReference, addAttachments, setDraft, focus };
|
|
1257
1283
|
useImperativeHandle(handle, () => actions, [pilot, send, addReference, addAttachments, setDraft, focus]);
|
|
1258
1284
|
const ctx = { ...actions, agentState: state.agentState, strings, plugins: plugins.list };
|
|
1259
|
-
|
|
1285
|
+
ctxForComponents.current = ctx;
|
|
1286
|
+
const mark = (plugin) => pluginAttrs(plugin, plugins.ghostykitStyled);
|
|
1260
1287
|
ctxRef.current = ctx;
|
|
1261
|
-
const pluginsRef = useRef4(plugins);
|
|
1262
|
-
pluginsRef.current = plugins;
|
|
1263
1288
|
const unattended = useRef4(/* @__PURE__ */ new Set());
|
|
1264
1289
|
useEffect5(
|
|
1265
1290
|
() => pilot.subscribe?.((event) => {
|
|
@@ -1315,6 +1340,17 @@ var Piloto = forwardRef(function Piloto2(props, handle) {
|
|
|
1315
1340
|
}),
|
|
1316
1341
|
[pilot.onTurn]
|
|
1317
1342
|
);
|
|
1343
|
+
useEffect5(() => {
|
|
1344
|
+
const err = state.httpError;
|
|
1345
|
+
if (!err) return;
|
|
1346
|
+
for (const h of pluginsRef.current.httpError) {
|
|
1347
|
+
try {
|
|
1348
|
+
h.value(err, ctxRef.current);
|
|
1349
|
+
} catch (e) {
|
|
1350
|
+
console.error(`[ghostykit] el plugin \xAB${h.plugin}\xBB fall\xF3 con onHttpError`, e);
|
|
1351
|
+
}
|
|
1352
|
+
}
|
|
1353
|
+
}, [state.httpError]);
|
|
1318
1354
|
const [held, setHeld] = useState8({});
|
|
1319
1355
|
const heldRef = useRef4(held);
|
|
1320
1356
|
heldRef.current = held;
|
|
@@ -1428,12 +1464,20 @@ var Piloto = forwardRef(function Piloto2(props, handle) {
|
|
|
1428
1464
|
const toolRunning = busy && last?.role === "assistant" && (last.tools ?? []).some(isRunning);
|
|
1429
1465
|
const empty = turns.length === 0 && !busy;
|
|
1430
1466
|
const placeholder = props.placeholder ?? (props.agentName ? t.placeholder(props.agentName) : t.placeholderDefault);
|
|
1431
|
-
const { upload, transcribe, voiceNoteText } = plugins.attachments;
|
|
1467
|
+
const { upload, transcribe, voiceNoteText, accept, maxFiles, maxBytes } = plugins.attachments;
|
|
1432
1468
|
const showFiles = features.attachments ?? Boolean(upload);
|
|
1433
1469
|
const pointedRefs = refs.filter((r) => r.prompt);
|
|
1434
1470
|
const plainRefs = refs.filter((r) => !r.prompt);
|
|
1435
1471
|
const removeRef = (id) => setRefs(refsRef.current.filter((r) => r.id !== id));
|
|
1436
|
-
const
|
|
1472
|
+
const replacer = plugins.replacers.find((r) => {
|
|
1473
|
+
try {
|
|
1474
|
+
return r.value.when(ctx);
|
|
1475
|
+
} catch {
|
|
1476
|
+
return false;
|
|
1477
|
+
}
|
|
1478
|
+
});
|
|
1479
|
+
const Replacement = replacer?.value.view;
|
|
1480
|
+
const composer = Replacement ? /* @__PURE__ */ jsx8("div", { ...mark(replacer.plugin), children: /* @__PURE__ */ jsx8(Replacement, { ctx }) }) : /* @__PURE__ */ jsxs6("div", { ref: composerBox, children: [
|
|
1437
1481
|
/* @__PURE__ */ jsx8(ReferenceChips, { references: pointedRefs, onRemove: removeRef, onActivate: props.onReferenceClick }),
|
|
1438
1482
|
/* @__PURE__ */ jsx8(
|
|
1439
1483
|
Composer,
|
|
@@ -1453,7 +1497,10 @@ var Piloto = forwardRef(function Piloto2(props, handle) {
|
|
|
1453
1497
|
onActivateReference: props.onReferenceClick,
|
|
1454
1498
|
extras: plugins.composerActions.length ? /* @__PURE__ */ jsx8(Views, { items: plugins.composerActions, ctx, styled: plugins.ghostykitStyled, inline: true }) : void 0,
|
|
1455
1499
|
extrasEnd: plugins.composerActionsEnd.length ? /* @__PURE__ */ jsx8(Views, { items: plugins.composerActionsEnd, ctx, styled: plugins.ghostykitStyled, inline: true }) : void 0,
|
|
1456
|
-
handleRef: composerHandle
|
|
1500
|
+
handleRef: composerHandle,
|
|
1501
|
+
accept,
|
|
1502
|
+
...maxFiles ? { maxFiles } : {},
|
|
1503
|
+
...maxBytes ? { maxFileMb: maxBytes / (1024 * 1024) } : {}
|
|
1457
1504
|
},
|
|
1458
1505
|
draft.n
|
|
1459
1506
|
)
|
|
@@ -1465,7 +1512,6 @@ var Piloto = forwardRef(function Piloto2(props, handle) {
|
|
|
1465
1512
|
const aboveComposer = plugins.composerAbove.length ? /* @__PURE__ */ jsx8(Views, { items: plugins.composerAbove, ctx, styled: plugins.ghostykitStyled }) : null;
|
|
1466
1513
|
const belowComposer = plugins.composerBelow.length ? /* @__PURE__ */ jsx8(Views, { items: plugins.composerBelow, ctx, styled: plugins.ghostykitStyled }) : null;
|
|
1467
1514
|
const header = plugins.header.length ? /* @__PURE__ */ jsx8(Views, { items: plugins.header, ctx, styled: plugins.ghostykitStyled, inline: true }) : null;
|
|
1468
|
-
const mark = (plugin) => pluginAttrs(plugin, plugins.ghostykitStyled);
|
|
1469
1515
|
const Empty = plugins.empty?.value;
|
|
1470
1516
|
const Waiting = plugins.waiting?.value;
|
|
1471
1517
|
const renderDeliveries = (turn, live) => {
|
|
@@ -1585,7 +1631,9 @@ var Piloto = forwardRef(function Piloto2(props, handle) {
|
|
|
1585
1631
|
renderDeliveries,
|
|
1586
1632
|
renderTools,
|
|
1587
1633
|
renderBefore,
|
|
1588
|
-
blocks
|
|
1634
|
+
blocks,
|
|
1635
|
+
narration: features.narration !== false,
|
|
1636
|
+
avatar: plugins.avatar ? /* @__PURE__ */ jsx8("span", { ...mark(plugins.avatar.plugin), children: /* @__PURE__ */ jsx8(plugins.avatar.value, { ctx, turn }) }) : void 0
|
|
1589
1637
|
}
|
|
1590
1638
|
),
|
|
1591
1639
|
turn.role === "assistant" && /* @__PURE__ */ jsx8(TurnNotes, { turn })
|
package/dist/react/plugins.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import type { ComponentType, ReactNode } from "react";
|
|
|
2
2
|
import type { BaseEvent } from "@ag-ui/core";
|
|
3
3
|
import type { TextBlock } from "../core/narration";
|
|
4
4
|
import type { ToolVerbs } from "../core/steps";
|
|
5
|
-
import type { Attachment, CustomEntry, Reference, ToolEntry, Turn } from "../core/types";
|
|
5
|
+
import type { Attachment, CustomEntry, PilotState, Reference, ToolEntry, Turn } from "../core/types";
|
|
6
6
|
import type { UploadResult } from "./Composer";
|
|
7
7
|
import type { MarkdownOptions, PilotIcon } from "./context";
|
|
8
8
|
import type { PilotStrings } from "./strings";
|
|
@@ -52,7 +52,17 @@ export interface PluginMarkdown {
|
|
|
52
52
|
turn: Turn;
|
|
53
53
|
live: boolean;
|
|
54
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
|
+
}>>;
|
|
55
63
|
}
|
|
64
|
+
/** Un rechazo HTTP del servidor antes del stream (403 con `x-needs-login`, 429…). */
|
|
65
|
+
export type PluginHttpError = NonNullable<PilotState["httpError"]>;
|
|
56
66
|
export interface PilotoPlugin {
|
|
57
67
|
/** Único: aparece en `data-plugin`, en los avisos y en `pilotDevtools()`. */
|
|
58
68
|
name: string;
|
|
@@ -115,6 +125,12 @@ export interface PilotoPlugin {
|
|
|
115
125
|
}>>;
|
|
116
126
|
/** El texto del usuario como se pinta (se encadena en orden). */
|
|
117
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
|
+
}>;
|
|
118
134
|
};
|
|
119
135
|
composer?: {
|
|
120
136
|
/** Botones en la barra del composer, entre la imagen y el clip («Mis archivos», «Señalar»). */
|
|
@@ -130,6 +146,20 @@ export interface PilotoPlugin {
|
|
|
130
146
|
upload?: (file: File) => Promise<UploadResult>;
|
|
131
147
|
transcribe?: (audio: Blob) => Promise<string>;
|
|
132
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;
|
|
133
163
|
};
|
|
134
164
|
};
|
|
135
165
|
/** Arriba a la derecha, sobre el hilo. */
|
|
@@ -150,6 +180,8 @@ export interface PilotoPlugin {
|
|
|
150
180
|
ctx: PluginCtx;
|
|
151
181
|
children: ReactNode;
|
|
152
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;
|
|
153
185
|
/** Este cliente mandó un turno. */
|
|
154
186
|
onTurnStart?: (ctx: PluginCtx) => void;
|
|
155
187
|
/** Terminó un turno de este cliente (abrir la última entrega, re-leer datos). */
|
|
@@ -174,6 +206,11 @@ export interface ResolvedPlugins {
|
|
|
174
206
|
toolRenders: Record<string, Owned<NonNullable<NonNullable<PilotoPlugin["tools"]>["render"]>[string]>>;
|
|
175
207
|
customRenders: Record<string, Owned<NonNullable<NonNullable<PilotoPlugin["message"]>["custom"]>[string]>>;
|
|
176
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"]>>[];
|
|
177
214
|
attachments: NonNullable<NonNullable<PilotoPlugin["composer"]>["attachments"]>;
|
|
178
215
|
empty?: Owned<PluginView>;
|
|
179
216
|
waiting?: Owned<NonNullable<PilotoPlugin["waiting"]>>;
|
|
@@ -13,7 +13,9 @@ export declare class PilotHttpError extends Error {
|
|
|
13
13
|
code?: string;
|
|
14
14
|
/** Segundos de `Retry-After` (429/503). */
|
|
15
15
|
retryAfter?: number;
|
|
16
|
-
|
|
16
|
+
/** Las cabeceras de la respuesta (minúsculas), p. ej. `x-needs-login`. */
|
|
17
|
+
headers?: Record<string, string>;
|
|
18
|
+
constructor(message: string, status: number, code?: string, retryAfter?: number, headers?: Record<string, string>);
|
|
17
19
|
}
|
|
18
20
|
export interface AgUiTransportOptions {
|
|
19
21
|
/** El endpoint del agente: `POST <url>`. */
|
package/dist/transport/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ghostykit",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.1",
|
|
4
4
|
"description": "Ghosty Autopilot \u2014 the autopilot chat for your React app, on AG-UI: works with LangGraph, Mastra, CrewAI or any AG-UI agent. Streaming text, agent steps, human approval cards (interrupt/resume), attachments and voice.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/core/index.js",
|