@nyte-ai/plugin 0.0.25-nightly.20261101.43 → 0.0.27
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/THIRD-PARTY-NOTICES.md +65 -0
- package/dist/chunks/{bash-description-myvnmtt2.js → bash-description-03bm07ft.js} +2 -8
- package/dist/chunks/{bash-description-6mm9fym2.js → bash-description-0gnxcq7q.js} +31 -11
- package/dist/chunks/bash-description-0vfhp1qd.js +54 -0
- package/dist/chunks/{bash-description-g7gfy7nw.js → bash-description-4kqkzdkp.js} +3 -5
- package/dist/chunks/{bash-description-axnga0jz.js → bash-description-a99pev5h.js} +2 -2
- package/dist/chunks/{bash-description-bkhr9zrz.js → bash-description-s6v2m3ch.js} +28 -10
- package/dist/chunks/{bash-description-qwyy182r.js → bash-description-spc5av5d.js} +1 -7
- package/dist/chunks/bash-description-t3hbavcc.js +12 -0
- package/dist/src/acp/connection.js +1 -1
- package/dist/src/acp/index.js +2 -2
- package/dist/src/acp/transcript.js +1 -1
- package/dist/src/approvals/approvals.js +6 -3
- package/dist/src/approvals/schema.js +1 -1
- package/dist/src/browser-client.d.ts +135 -0
- package/dist/src/browser-client.js +374 -0
- package/dist/src/canvas/canvas-client.d.ts +71 -0
- package/dist/src/canvas/canvas-client.js +70 -0
- package/dist/src/canvas/canvas-compile.d.ts +0 -34
- package/dist/src/canvas/canvas-libraries.d.ts +0 -6
- package/dist/src/canvas/canvas-preview.d.ts +0 -65
- package/dist/src/canvas/canvas-protocol.d.ts +5 -80
- package/dist/src/canvas/canvas.d.ts +0 -25
- package/dist/src/canvas/schema.d.ts +0 -37
- package/dist/src/client/notifications.js +2 -1
- package/dist/src/client/presentation.d.ts +3 -23
- package/dist/src/client/presentation.js +7 -3
- package/dist/src/client/transcript.js +18 -18
- package/dist/src/client/user-messages.d.ts +41 -0
- package/dist/src/client/user-messages.js +10 -0
- package/dist/src/desktop-settings/desktop-settings.d.ts +31 -0
- package/dist/src/desktop-settings/desktop-settings.js +136 -0
- package/dist/src/desktop-settings/schema.d.ts +42 -0
- package/dist/src/desktop-settings/schema.js +14 -0
- package/dist/src/question/client/questions-tui.js +6 -2
- package/dist/src/question/client/questions.js +47 -46
- package/dist/src/question/question.d.ts +37 -6
- package/dist/src/question/question.js +69 -17
- package/dist/src/question/schema.d.ts +73 -11
- package/dist/src/question/schema.js +9 -5
- package/dist/src/rename.d.ts +1 -1
- package/dist/src/subagents.js +11 -23
- package/dist/src/web/fetch.js +1 -1
- package/dist/src/web/index.js +1 -1
- package/package.json +30 -14
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
// src/browser-client.ts
|
|
2
|
+
import { onlyNyteAppHas } from "@nyte-ai/core";
|
|
3
|
+
import { asId, compile, idSchema, isJson, SdkError } from "@nyte-ai/schema";
|
|
4
|
+
import { Type } from "typebox";
|
|
5
|
+
import browserTools from "@nyte-ai/plugin/core/browser-tools";
|
|
6
|
+
var BROWSER_ACTION = {
|
|
7
|
+
name: "browser",
|
|
8
|
+
summary: "Drives the page a chat's agent holds in this app's browser.",
|
|
9
|
+
input: { type: "object" }
|
|
10
|
+
};
|
|
11
|
+
var PERFORM_MS = 30000;
|
|
12
|
+
var SessionField = idSchema();
|
|
13
|
+
var ref = Type.String();
|
|
14
|
+
var OwnerType = Type.Union([
|
|
15
|
+
Type.Object({ kind: Type.Literal("home") }),
|
|
16
|
+
Type.Object({ kind: Type.Literal("project"), path: Type.String() })
|
|
17
|
+
]);
|
|
18
|
+
var WaitType = Type.Union([
|
|
19
|
+
Type.Object({ until: Type.Literal("load") }),
|
|
20
|
+
Type.Object({ until: Type.Literal("text"), text: Type.String() }),
|
|
21
|
+
Type.Object({ until: Type.Literal("gone"), text: Type.String() }),
|
|
22
|
+
Type.Object({ until: Type.Literal("time"), seconds: Type.Number() })
|
|
23
|
+
]);
|
|
24
|
+
var BrowserCallType = Type.Union([
|
|
25
|
+
Type.Object({
|
|
26
|
+
method: Type.Literal("open"),
|
|
27
|
+
session: SessionField,
|
|
28
|
+
owner: OwnerType,
|
|
29
|
+
url: Type.String()
|
|
30
|
+
}),
|
|
31
|
+
Type.Object({ method: Type.Literal("snapshot"), session: SessionField, ref: Type.Optional(ref) }),
|
|
32
|
+
Type.Object({
|
|
33
|
+
method: Type.Literal("click"),
|
|
34
|
+
session: SessionField,
|
|
35
|
+
ref,
|
|
36
|
+
expect: Type.String(),
|
|
37
|
+
button: Type.Optional(Type.Enum(["left", "right", "middle"])),
|
|
38
|
+
double: Type.Optional(Type.Boolean())
|
|
39
|
+
}),
|
|
40
|
+
Type.Object({
|
|
41
|
+
method: Type.Literal("type"),
|
|
42
|
+
session: SessionField,
|
|
43
|
+
ref,
|
|
44
|
+
text: Type.String(),
|
|
45
|
+
expect: Type.String(),
|
|
46
|
+
clear: Type.Optional(Type.Boolean()),
|
|
47
|
+
submit: Type.Optional(Type.Boolean())
|
|
48
|
+
}),
|
|
49
|
+
Type.Object({
|
|
50
|
+
method: Type.Literal("press"),
|
|
51
|
+
session: SessionField,
|
|
52
|
+
key: Type.String(),
|
|
53
|
+
ref: Type.Optional(ref)
|
|
54
|
+
}),
|
|
55
|
+
Type.Object({
|
|
56
|
+
method: Type.Literal("scroll"),
|
|
57
|
+
session: SessionField,
|
|
58
|
+
direction: Type.Enum(["up", "down", "top", "bottom"]),
|
|
59
|
+
ref: Type.Optional(ref),
|
|
60
|
+
pages: Type.Optional(Type.Number())
|
|
61
|
+
}),
|
|
62
|
+
Type.Object({ method: Type.Literal("wait"), session: SessionField, wait: WaitType }),
|
|
63
|
+
Type.Object({
|
|
64
|
+
method: Type.Literal("console"),
|
|
65
|
+
session: SessionField,
|
|
66
|
+
limit: Type.Integer({ minimum: 1 }),
|
|
67
|
+
clear: Type.Optional(Type.Boolean())
|
|
68
|
+
}),
|
|
69
|
+
Type.Object({
|
|
70
|
+
method: Type.Literal("evaluate"),
|
|
71
|
+
session: SessionField,
|
|
72
|
+
expression: Type.String(),
|
|
73
|
+
ref: Type.Optional(ref)
|
|
74
|
+
}),
|
|
75
|
+
Type.Object({ method: Type.Literal("capture"), session: SessionField }),
|
|
76
|
+
Type.Object({ method: Type.Literal("release"), session: SessionField })
|
|
77
|
+
]);
|
|
78
|
+
var PageStateType = Type.Object({
|
|
79
|
+
url: Type.String(),
|
|
80
|
+
title: Type.String(),
|
|
81
|
+
loading: Type.Boolean(),
|
|
82
|
+
snapshot: Type.Number(),
|
|
83
|
+
nodes: Type.Array(Type.Object({
|
|
84
|
+
ref: Type.String(),
|
|
85
|
+
role: Type.String(),
|
|
86
|
+
name: Type.String(),
|
|
87
|
+
editable: Type.Optional(Type.Boolean())
|
|
88
|
+
}))
|
|
89
|
+
});
|
|
90
|
+
var FailureType = Type.Object({
|
|
91
|
+
kind: Type.Enum([
|
|
92
|
+
"stale_document",
|
|
93
|
+
"unknown_ref",
|
|
94
|
+
"detached",
|
|
95
|
+
"not_visible",
|
|
96
|
+
"occluded",
|
|
97
|
+
"no_window",
|
|
98
|
+
"closed",
|
|
99
|
+
"crashed"
|
|
100
|
+
])
|
|
101
|
+
});
|
|
102
|
+
var ActionAnswerType = Type.Union([
|
|
103
|
+
Type.Object({ kind: Type.Literal("ok"), state: PageStateType }),
|
|
104
|
+
Type.Object({ kind: Type.Literal("failed"), failure: FailureType })
|
|
105
|
+
]);
|
|
106
|
+
var EvaluateAnswerType = Type.Union([
|
|
107
|
+
Type.Object({ kind: Type.Literal("value"), json: Type.String() }),
|
|
108
|
+
Type.Object({ kind: Type.Literal("threw"), message: Type.String() })
|
|
109
|
+
]);
|
|
110
|
+
var ConsoleAnswerType = Type.Array(Type.Object({
|
|
111
|
+
level: Type.Enum(["error", "warning", "info", "debug"]),
|
|
112
|
+
message: Type.String(),
|
|
113
|
+
at: Type.Number()
|
|
114
|
+
}));
|
|
115
|
+
var CaptureAnswerType = Type.Union([Type.Null(), Type.Object({ png: Type.String() })]);
|
|
116
|
+
var answers = {
|
|
117
|
+
action: compile(ActionAnswerType),
|
|
118
|
+
evaluate: compile(EvaluateAnswerType),
|
|
119
|
+
console: compile(ConsoleAnswerType),
|
|
120
|
+
capture: compile(CaptureAnswerType)
|
|
121
|
+
};
|
|
122
|
+
var UNICODE_FORGERY = /[\u2028\u2029\u202a-\u202e]/g;
|
|
123
|
+
var CONTROL_CHARACTERS = /[\x00-\x1f\x7f-\x9f]/g;
|
|
124
|
+
function sanitizeLine(text) {
|
|
125
|
+
return text.replace(CONTROL_CHARACTERS, "").replace(UNICODE_FORGERY, "");
|
|
126
|
+
}
|
|
127
|
+
function sessionSurfaceId(sessionId) {
|
|
128
|
+
return `stage:session:${encodeURIComponent(sessionId)}`;
|
|
129
|
+
}
|
|
130
|
+
function clientBrowserPlugins(actions, home) {
|
|
131
|
+
return [
|
|
132
|
+
browserTools,
|
|
133
|
+
{
|
|
134
|
+
id: "client-browser",
|
|
135
|
+
activate(api) {
|
|
136
|
+
const owner = api.env.cwd === home ? { kind: "home" } : { kind: "project", path: api.env.cwd };
|
|
137
|
+
const browser = sessionBrowser(actions, api.session.sessionId, owner);
|
|
138
|
+
api.wrapEnv((inner) => ({
|
|
139
|
+
identity: inner.identity,
|
|
140
|
+
cwd: inner.cwd,
|
|
141
|
+
files: inner.files,
|
|
142
|
+
exec: (command, options) => inner.exec(command, options),
|
|
143
|
+
close: () => inner.close(),
|
|
144
|
+
browser
|
|
145
|
+
}));
|
|
146
|
+
api.signal.addEventListener("abort", () => {
|
|
147
|
+
if (actions.offered(BROWSER_ACTION.name))
|
|
148
|
+
ask(actions, { method: "release", session: api.session.sessionId }).catch(() => {
|
|
149
|
+
return;
|
|
150
|
+
});
|
|
151
|
+
}, { once: true });
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
];
|
|
155
|
+
}
|
|
156
|
+
function sessionBrowser(actions, session, owner) {
|
|
157
|
+
const id = asId(sessionSurfaceId(session));
|
|
158
|
+
let state = null;
|
|
159
|
+
const act = async (call) => decode(answers.action, await ask(actions, call));
|
|
160
|
+
const settle = (answer) => {
|
|
161
|
+
if (answer.kind === "failed")
|
|
162
|
+
return failureOutcome(answer.failure, state);
|
|
163
|
+
state = answer.state;
|
|
164
|
+
return { kind: "applied", snapshot: toPageSnapshot(answer.state) };
|
|
165
|
+
};
|
|
166
|
+
const expect = (target) => state?.nodes.find((node) => node.ref === target)?.name ?? "";
|
|
167
|
+
const script = async (expression) => {
|
|
168
|
+
const ran = decode(answers.evaluate, await ask(actions, { method: "evaluate", session, expression }));
|
|
169
|
+
if (ran.kind === "threw")
|
|
170
|
+
return { kind: "refused", reason: "policy" };
|
|
171
|
+
return settle(await act({ method: "wait", session, wait: { until: "load" } }));
|
|
172
|
+
};
|
|
173
|
+
const view = () => ({
|
|
174
|
+
id,
|
|
175
|
+
url: sanitizeLine(state?.url ?? ""),
|
|
176
|
+
title: sanitizeLine(state?.title ?? ""),
|
|
177
|
+
loading: state?.loading ?? false,
|
|
178
|
+
revision: asId(String(state?.snapshot ?? 0)),
|
|
179
|
+
canGoBack: false,
|
|
180
|
+
canGoForward: false
|
|
181
|
+
});
|
|
182
|
+
const pages = () => {
|
|
183
|
+
offeredOrThrow(actions);
|
|
184
|
+
return state === null ? [] : [{ id, url: state.url, title: state.title, holders: 1 }];
|
|
185
|
+
};
|
|
186
|
+
const page = {
|
|
187
|
+
id,
|
|
188
|
+
view: async () => view(),
|
|
189
|
+
async* watch() {
|
|
190
|
+
yield view();
|
|
191
|
+
},
|
|
192
|
+
async snapshot(input) {
|
|
193
|
+
const answer = await act({ method: "snapshot", session, ref: input?.ref });
|
|
194
|
+
if (answer.kind === "failed")
|
|
195
|
+
throw new SdkError("unavailable", `The page cannot be read: ${answer.failure.kind}.`);
|
|
196
|
+
state = answer.state;
|
|
197
|
+
return toPageSnapshot(answer.state);
|
|
198
|
+
},
|
|
199
|
+
navigate: async (url) => settle(await act({ method: "open", session, owner, url })),
|
|
200
|
+
back: () => script("history.back()"),
|
|
201
|
+
forward: () => script("history.forward()"),
|
|
202
|
+
reload: () => script("location.reload()"),
|
|
203
|
+
stopLoading: () => script("window.stop()"),
|
|
204
|
+
click: async (input) => settle(await act({
|
|
205
|
+
method: "click",
|
|
206
|
+
session,
|
|
207
|
+
ref: input.ref,
|
|
208
|
+
expect: expect(input.ref),
|
|
209
|
+
button: input.button,
|
|
210
|
+
double: input.double
|
|
211
|
+
})),
|
|
212
|
+
type: async (input) => settle(await act({
|
|
213
|
+
method: "type",
|
|
214
|
+
session,
|
|
215
|
+
ref: input.ref,
|
|
216
|
+
text: input.text,
|
|
217
|
+
clear: input.clear,
|
|
218
|
+
submit: input.submit,
|
|
219
|
+
expect: expect(input.ref)
|
|
220
|
+
})),
|
|
221
|
+
press: async (input) => settle(await act({ method: "press", session, key: input.key, ref: input.ref })),
|
|
222
|
+
scroll: async (input) => settle(await act({
|
|
223
|
+
method: "scroll",
|
|
224
|
+
session,
|
|
225
|
+
direction: input.direction,
|
|
226
|
+
ref: input.ref,
|
|
227
|
+
pages: input.pages
|
|
228
|
+
})),
|
|
229
|
+
async wait(condition) {
|
|
230
|
+
if (condition.kind === "loaded")
|
|
231
|
+
return settle(await act({ method: "wait", session, wait: { until: "load" } }));
|
|
232
|
+
if (condition.kind === "delay")
|
|
233
|
+
return settle(await act({
|
|
234
|
+
method: "wait",
|
|
235
|
+
session,
|
|
236
|
+
wait: { until: "time", seconds: Math.max(0.1, condition.ms / 1000) }
|
|
237
|
+
}));
|
|
238
|
+
return settle(await act({
|
|
239
|
+
method: "wait",
|
|
240
|
+
session,
|
|
241
|
+
wait: { until: condition.present ? "text" : "gone", text: condition.text }
|
|
242
|
+
}));
|
|
243
|
+
},
|
|
244
|
+
async console(input) {
|
|
245
|
+
const entries = decode(answers.console, await ask(actions, {
|
|
246
|
+
method: "console",
|
|
247
|
+
session,
|
|
248
|
+
limit: input?.limit ?? 50,
|
|
249
|
+
clear: input?.clear
|
|
250
|
+
}));
|
|
251
|
+
return entries.map((entry) => ({
|
|
252
|
+
level: entry.level === "warning" ? "warn" : entry.level === "debug" ? "log" : entry.level,
|
|
253
|
+
text: entry.message,
|
|
254
|
+
at: entry.at
|
|
255
|
+
}));
|
|
256
|
+
},
|
|
257
|
+
async capture() {
|
|
258
|
+
const captured = decode(answers.capture, await ask(actions, { method: "capture", session }));
|
|
259
|
+
if (captured === null)
|
|
260
|
+
throw new SdkError("unavailable", "The page could not be captured.");
|
|
261
|
+
return pngReader(Uint8Array.fromBase64(captured.png));
|
|
262
|
+
},
|
|
263
|
+
async evaluate(expression, options) {
|
|
264
|
+
const result = decode(answers.evaluate, await ask(actions, { method: "evaluate", session, expression, ref: options?.ref }));
|
|
265
|
+
if (result.kind === "threw")
|
|
266
|
+
return result;
|
|
267
|
+
const value = JSON.parse(result.json);
|
|
268
|
+
return { kind: "value", value: isJson(value) ? value : result.json };
|
|
269
|
+
},
|
|
270
|
+
close: async () => {
|
|
271
|
+
return;
|
|
272
|
+
},
|
|
273
|
+
[Symbol.asyncDispose]: async () => {
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
};
|
|
277
|
+
return {
|
|
278
|
+
async open(input) {
|
|
279
|
+
const outcome = settle(await act({ method: "open", session, owner, url: input.url }));
|
|
280
|
+
if (outcome.kind !== "applied")
|
|
281
|
+
throw new SdkError("unavailable", `The page did not open: ${outcome.kind}.`);
|
|
282
|
+
return page;
|
|
283
|
+
},
|
|
284
|
+
async adopt(pageId) {
|
|
285
|
+
offeredOrThrow(actions);
|
|
286
|
+
if (pageId !== id || state === null)
|
|
287
|
+
throw new SdkError("unknown", "No page has this id.");
|
|
288
|
+
return page;
|
|
289
|
+
},
|
|
290
|
+
list: async () => pages(),
|
|
291
|
+
async* watch() {
|
|
292
|
+
yield pages();
|
|
293
|
+
}
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
async function ask(actions, call) {
|
|
297
|
+
offeredOrThrow(actions);
|
|
298
|
+
const input = JSON.parse(JSON.stringify(call));
|
|
299
|
+
if (!isJson(input))
|
|
300
|
+
throw new SdkError("internal", "A browser call did not encode as JSON.");
|
|
301
|
+
return actions.perform(BROWSER_ACTION.name, input, PERFORM_MS).catch((error) => {
|
|
302
|
+
if (error instanceof SdkError && error.code === "not_supported")
|
|
303
|
+
throw unavailable();
|
|
304
|
+
throw error;
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
function offeredOrThrow(actions) {
|
|
308
|
+
if (!actions.offered(BROWSER_ACTION.name))
|
|
309
|
+
throw unavailable();
|
|
310
|
+
}
|
|
311
|
+
function unavailable() {
|
|
312
|
+
return new SdkError("unavailable", onlyNyteAppHas("The browser", [browserTools.id]));
|
|
313
|
+
}
|
|
314
|
+
function decode(checker, value) {
|
|
315
|
+
if (!checker.Check(value))
|
|
316
|
+
throw new SdkError("invalid", "The app answered a browser call with something unreadable.");
|
|
317
|
+
return value;
|
|
318
|
+
}
|
|
319
|
+
function failureOutcome(failure, state) {
|
|
320
|
+
switch (failure.kind) {
|
|
321
|
+
case "stale_document":
|
|
322
|
+
return { kind: "stale", revision: asId(String(state?.snapshot ?? 0)) };
|
|
323
|
+
case "unknown_ref":
|
|
324
|
+
case "detached":
|
|
325
|
+
return { kind: "refused", reason: "unknown-ref" };
|
|
326
|
+
case "occluded":
|
|
327
|
+
return { kind: "refused", reason: "occluded" };
|
|
328
|
+
case "not_visible":
|
|
329
|
+
case "no_window":
|
|
330
|
+
return { kind: "refused", reason: "not-visible" };
|
|
331
|
+
case "closed":
|
|
332
|
+
return { kind: "closed", reason: "released" };
|
|
333
|
+
case "crashed":
|
|
334
|
+
return { kind: "closed", reason: "crashed" };
|
|
335
|
+
default: {
|
|
336
|
+
const _exhaustive = failure.kind;
|
|
337
|
+
return _exhaustive;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
function toPageSnapshot(state) {
|
|
342
|
+
return {
|
|
343
|
+
revision: asId(String(state.snapshot)),
|
|
344
|
+
nodes: state.nodes.map((node) => ({
|
|
345
|
+
ref: node.ref,
|
|
346
|
+
role: sanitizeLine(node.role),
|
|
347
|
+
name: sanitizeLine(node.name),
|
|
348
|
+
editable: node.editable === true,
|
|
349
|
+
depth: 0
|
|
350
|
+
}))
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
function pngReader(bytes) {
|
|
354
|
+
return {
|
|
355
|
+
size: bytes.byteLength,
|
|
356
|
+
mime: "image/png",
|
|
357
|
+
async* chunks() {
|
|
358
|
+
yield bytes;
|
|
359
|
+
},
|
|
360
|
+
close: async () => {
|
|
361
|
+
return;
|
|
362
|
+
},
|
|
363
|
+
[Symbol.asyncDispose]: async () => {
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
export {
|
|
369
|
+
BROWSER_ACTION,
|
|
370
|
+
BrowserCallType,
|
|
371
|
+
clientBrowserPlugins,
|
|
372
|
+
sanitizeLine,
|
|
373
|
+
sessionSurfaceId
|
|
374
|
+
};
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { Type } from "typebox";
|
|
2
|
+
import type { Static } from "typebox";
|
|
3
|
+
import type { ClientActions } from "../browser-client.ts";
|
|
4
|
+
import type { CanvasPreviewer } from "./canvas-preview.ts";
|
|
5
|
+
/**
|
|
6
|
+
* The action an app offers to render a canvas preview in its own renderer;
|
|
7
|
+
* its input is a `CanvasPreviewRequest`, its answer a
|
|
8
|
+
* `CanvasPreviewAnswer`.
|
|
9
|
+
*
|
|
10
|
+
* @category plugins
|
|
11
|
+
* @since 0.1.0
|
|
12
|
+
*/
|
|
13
|
+
export declare const CANVAS_PREVIEW_ACTION: {
|
|
14
|
+
readonly name: "canvas.preview";
|
|
15
|
+
readonly summary: "Renders a canvas off-screen in this app and reports what it saw.";
|
|
16
|
+
readonly input: {
|
|
17
|
+
readonly type: "object";
|
|
18
|
+
};
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* What the host sends the preview action.
|
|
22
|
+
*
|
|
23
|
+
* @category plugins
|
|
24
|
+
* @since 0.1.0
|
|
25
|
+
*/
|
|
26
|
+
export declare const CanvasPreviewCallType: Type.TObject<{
|
|
27
|
+
module: Type.TObject<{
|
|
28
|
+
runtime: Type.TNumber;
|
|
29
|
+
code: Type.TString;
|
|
30
|
+
title: Type.TString;
|
|
31
|
+
}>;
|
|
32
|
+
theme: Type.TEnum<["light", "dark"]>;
|
|
33
|
+
}>;
|
|
34
|
+
/**
|
|
35
|
+
* What the preview action answers: the outcome, its PNG as base64.
|
|
36
|
+
*
|
|
37
|
+
* @category plugins
|
|
38
|
+
* @since 0.1.0
|
|
39
|
+
*/
|
|
40
|
+
export declare const CanvasPreviewAnswerType: Type.TUnion<[Type.TObject<{
|
|
41
|
+
kind: Type.TLiteral<"rendered">;
|
|
42
|
+
report: Type.TObject<{
|
|
43
|
+
png: Type.TString;
|
|
44
|
+
heights: Type.TObject<{
|
|
45
|
+
chat: Type.TNumber;
|
|
46
|
+
phone: Type.TNumber;
|
|
47
|
+
}>;
|
|
48
|
+
errors: Type.TArray<Type.TObject<{
|
|
49
|
+
phase: Type.TEnum<["load", "render"]>;
|
|
50
|
+
message: Type.TString;
|
|
51
|
+
stack: Type.TUnion<[Type.TString, Type.TNull]>;
|
|
52
|
+
}>>;
|
|
53
|
+
console: Type.TArray<Type.TObject<{
|
|
54
|
+
level: Type.TString;
|
|
55
|
+
text: Type.TString;
|
|
56
|
+
}>>;
|
|
57
|
+
}>;
|
|
58
|
+
}>, Type.TObject<{
|
|
59
|
+
kind: Type.TLiteral<"unavailable">;
|
|
60
|
+
reason: Type.TString;
|
|
61
|
+
}>]>;
|
|
62
|
+
export type CanvasPreviewAnswer = Static<typeof CanvasPreviewAnswerType>;
|
|
63
|
+
/**
|
|
64
|
+
* Returns a previewer that renders in the connected app that offers
|
|
65
|
+
* `CANVAS_PREVIEW_ACTION`, and in `fallback` while none does. The app
|
|
66
|
+
* answers within 30 seconds, the longest a client action waits.
|
|
67
|
+
*
|
|
68
|
+
* @category plugins
|
|
69
|
+
* @since 0.1.0
|
|
70
|
+
*/
|
|
71
|
+
export declare function clientCanvasPreview(actions: ClientActions, fallback: CanvasPreviewer): CanvasPreviewer;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// src/canvas/canvas-client.ts
|
|
2
|
+
import { compile, SdkError } from "@nyte-ai/schema";
|
|
3
|
+
import { Type } from "typebox";
|
|
4
|
+
var CANVAS_PREVIEW_ACTION = {
|
|
5
|
+
name: "canvas.preview",
|
|
6
|
+
summary: "Renders a canvas off-screen in this app and reports what it saw.",
|
|
7
|
+
input: { type: "object" }
|
|
8
|
+
};
|
|
9
|
+
var PERFORM_MS = 30000;
|
|
10
|
+
var CanvasPreviewCallType = Type.Object({
|
|
11
|
+
module: Type.Object({ runtime: Type.Number(), code: Type.String(), title: Type.String() }),
|
|
12
|
+
theme: Type.Enum(["light", "dark"])
|
|
13
|
+
});
|
|
14
|
+
var CanvasPreviewAnswerType = Type.Union([
|
|
15
|
+
Type.Object({
|
|
16
|
+
kind: Type.Literal("rendered"),
|
|
17
|
+
report: Type.Object({
|
|
18
|
+
png: Type.String(),
|
|
19
|
+
heights: Type.Object({ chat: Type.Number(), phone: Type.Number() }),
|
|
20
|
+
errors: Type.Array(Type.Object({
|
|
21
|
+
phase: Type.Enum(["load", "render"]),
|
|
22
|
+
message: Type.String(),
|
|
23
|
+
stack: Type.Union([Type.String(), Type.Null()])
|
|
24
|
+
})),
|
|
25
|
+
console: Type.Array(Type.Object({ level: Type.String(), text: Type.String() }))
|
|
26
|
+
})
|
|
27
|
+
}),
|
|
28
|
+
Type.Object({ kind: Type.Literal("unavailable"), reason: Type.String() })
|
|
29
|
+
]);
|
|
30
|
+
var previewAnswers = compile(CanvasPreviewAnswerType);
|
|
31
|
+
function clientCanvasPreview(actions, fallback) {
|
|
32
|
+
return async (request, signal) => {
|
|
33
|
+
if (!actions.offered(CANVAS_PREVIEW_ACTION.name))
|
|
34
|
+
return fallback(request, signal);
|
|
35
|
+
const answered = await actions.perform(CANVAS_PREVIEW_ACTION.name, previewInput(request), PERFORM_MS).catch((error) => {
|
|
36
|
+
if (error instanceof SdkError && error.code === "not_supported")
|
|
37
|
+
return null;
|
|
38
|
+
throw error;
|
|
39
|
+
});
|
|
40
|
+
if (answered === null)
|
|
41
|
+
return fallback(request, signal);
|
|
42
|
+
if (!previewAnswers.Check(answered))
|
|
43
|
+
throw new SdkError("invalid", "The app answered a canvas preview with something unreadable.");
|
|
44
|
+
return readAnswer(answered);
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
function previewInput(request) {
|
|
48
|
+
return {
|
|
49
|
+
module: {
|
|
50
|
+
runtime: request.module.runtime,
|
|
51
|
+
code: request.module.code,
|
|
52
|
+
title: request.module.title
|
|
53
|
+
},
|
|
54
|
+
theme: request.theme
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
function readAnswer(answer) {
|
|
58
|
+
if (answer.kind === "unavailable")
|
|
59
|
+
return answer;
|
|
60
|
+
return {
|
|
61
|
+
kind: "rendered",
|
|
62
|
+
report: { ...answer.report, png: Uint8Array.fromBase64(answer.report.png) }
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
export {
|
|
66
|
+
CANVAS_PREVIEW_ACTION,
|
|
67
|
+
CanvasPreviewAnswerType,
|
|
68
|
+
CanvasPreviewCallType,
|
|
69
|
+
clientCanvasPreview
|
|
70
|
+
};
|
|
@@ -1,10 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Returns what the compiler answers for one canvas: the ESM module to ship,
|
|
3
|
-
* with the runtime it targets, or the diagnostics the model fixes.
|
|
4
|
-
*
|
|
5
|
-
* @category plugins
|
|
6
|
-
* @since 0.1.0
|
|
7
|
-
*/
|
|
8
1
|
export type CompileOutcome = {
|
|
9
2
|
readonly kind: "compiled";
|
|
10
3
|
readonly code: string;
|
|
@@ -13,41 +6,14 @@ export type CompileOutcome = {
|
|
|
13
6
|
readonly kind: "failed";
|
|
14
7
|
readonly errors: readonly CompileError[];
|
|
15
8
|
};
|
|
16
|
-
/**
|
|
17
|
-
* Returns one diagnostic, positioned in a source file when the compiler
|
|
18
|
-
* knows where; `file` is null for one about the canvas as a whole.
|
|
19
|
-
*
|
|
20
|
-
* @category plugins
|
|
21
|
-
* @since 0.1.0
|
|
22
|
-
*/
|
|
23
9
|
export type CompileError = {
|
|
24
10
|
readonly file: string | null;
|
|
25
11
|
readonly line: number | null;
|
|
26
12
|
readonly column: number | null;
|
|
27
13
|
readonly text: string;
|
|
28
14
|
};
|
|
29
|
-
/**
|
|
30
|
-
* Returns a canvas's other files, keyed by their path from `main.tsx`'s
|
|
31
|
-
* folder, such as `chart.tsx` or `lib/scale.ts`.
|
|
32
|
-
*
|
|
33
|
-
* @category plugins
|
|
34
|
-
* @since 0.1.0
|
|
35
|
-
*/
|
|
36
15
|
export type CanvasFiles = Readonly<Record<string, string>>;
|
|
37
|
-
/**
|
|
38
|
-
* The relative specifiers a file imports at run time; type-only imports are stripped first.
|
|
39
|
-
*
|
|
40
|
-
* @category plugins
|
|
41
|
-
*/
|
|
42
16
|
export declare function localImports(file: string, source: string): Promise<readonly string[]>;
|
|
43
|
-
/**
|
|
44
|
-
* Compiles a canvas, `source` as `main.tsx` (or `options.entry`) plus the
|
|
45
|
-
* `files` it imports, to the one module the shell imports. The caps default
|
|
46
|
-
* to the contract's; a test narrows them.
|
|
47
|
-
*
|
|
48
|
-
* @category plugins
|
|
49
|
-
* @since 0.1.0
|
|
50
|
-
*/
|
|
51
17
|
export declare function compileCanvas(source: string, files?: CanvasFiles, options?: {
|
|
52
18
|
readonly source?: number;
|
|
53
19
|
readonly code?: number;
|
|
@@ -1,9 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The libraries a canvas may import, each pinned in packages/canvas and
|
|
3
|
-
* served by the shell as its own chunk, with every name its module exports.
|
|
4
|
-
* Written by `packages/canvas/scripts/library-exports.mjs`; a canvas test
|
|
5
|
-
* checks it against the installed packages.
|
|
6
|
-
*/
|
|
7
1
|
export declare const CANVAS_LIBRARIES: {
|
|
8
2
|
readonly three: {
|
|
9
3
|
readonly version: "0.186.1";
|
|
@@ -1,50 +1,21 @@
|
|
|
1
1
|
import type { CanvasModule } from "./canvas-protocol.ts";
|
|
2
|
-
/** The widths a preview measures at, in CSS pixels: the desktop chat column (its 840 px measure less the gutters) and a phone's. */
|
|
3
2
|
export declare const CANVAS_PREVIEW_WIDTHS: {
|
|
4
3
|
readonly chat: 808;
|
|
5
4
|
readonly phone: 358;
|
|
6
5
|
};
|
|
7
|
-
/**
|
|
8
|
-
* Returns what a previewer renders: the compiled module and the theme to
|
|
9
|
-
* show it in.
|
|
10
|
-
*
|
|
11
|
-
* @category plugins
|
|
12
|
-
* @since 0.1.0
|
|
13
|
-
*/
|
|
14
6
|
export type CanvasPreviewRequest = {
|
|
15
7
|
readonly module: CanvasModule;
|
|
16
8
|
readonly theme: "light" | "dark";
|
|
17
9
|
};
|
|
18
|
-
/**
|
|
19
|
-
* Returns one error the shell reported while the preview ran, its message
|
|
20
|
-
* ending in the canvas file and line when the stack named one.
|
|
21
|
-
*
|
|
22
|
-
* @category plugins
|
|
23
|
-
* @since 0.1.0
|
|
24
|
-
*/
|
|
25
10
|
export type CanvasPreviewError = {
|
|
26
11
|
readonly phase: "load" | "render";
|
|
27
12
|
readonly message: string;
|
|
28
13
|
readonly stack: string | null;
|
|
29
14
|
};
|
|
30
|
-
/**
|
|
31
|
-
* Returns one console call the canvas made while the preview ran.
|
|
32
|
-
*
|
|
33
|
-
* @category plugins
|
|
34
|
-
* @since 0.1.0
|
|
35
|
-
*/
|
|
36
15
|
export type CanvasPreviewLog = {
|
|
37
16
|
readonly level: string;
|
|
38
17
|
readonly text: string;
|
|
39
18
|
};
|
|
40
|
-
/**
|
|
41
|
-
* Returns what a preview saw: a PNG of the canvas at the chat column's
|
|
42
|
-
* width, its height there and at a phone's (`CANVAS_PREVIEW_WIDTHS`), its
|
|
43
|
-
* errors and its console.
|
|
44
|
-
*
|
|
45
|
-
* @category plugins
|
|
46
|
-
* @since 0.1.0
|
|
47
|
-
*/
|
|
48
19
|
export type CanvasPreviewReport = {
|
|
49
20
|
readonly png: Uint8Array;
|
|
50
21
|
readonly heights: {
|
|
@@ -54,13 +25,6 @@ export type CanvasPreviewReport = {
|
|
|
54
25
|
readonly errors: readonly CanvasPreviewError[];
|
|
55
26
|
readonly console: readonly CanvasPreviewLog[];
|
|
56
27
|
};
|
|
57
|
-
/**
|
|
58
|
-
* Returns a previewer's answer: the report, or why this host cannot render
|
|
59
|
-
* a preview (no renderer installed, the renderer failed to start).
|
|
60
|
-
*
|
|
61
|
-
* @category plugins
|
|
62
|
-
* @since 0.1.0
|
|
63
|
-
*/
|
|
64
28
|
export type CanvasPreviewOutcome = {
|
|
65
29
|
readonly kind: "rendered";
|
|
66
30
|
readonly report: CanvasPreviewReport;
|
|
@@ -68,40 +32,11 @@ export type CanvasPreviewOutcome = {
|
|
|
68
32
|
readonly kind: "unavailable";
|
|
69
33
|
readonly reason: string;
|
|
70
34
|
};
|
|
71
|
-
/**
|
|
72
|
-
* Renders a compiled canvas off-screen in the real shell. A host passes one
|
|
73
|
-
* to `canvasPlugin` to give the model `canvas_preview`, which gives each
|
|
74
|
-
* preview 45 seconds. When `signal` aborts (past that, or the turn
|
|
75
|
-
* stopped), the previewer tears its renderer down, so a canvas that hangs
|
|
76
|
-
* its page holds up no later preview.
|
|
77
|
-
*
|
|
78
|
-
* @category plugins
|
|
79
|
-
* @since 0.1.0
|
|
80
|
-
*/
|
|
81
35
|
export type CanvasPreviewer = (request: CanvasPreviewRequest, signal: AbortSignal) => Promise<CanvasPreviewOutcome>;
|
|
82
|
-
/**
|
|
83
|
-
* Returns the page a previewer loaded the shell in, top-level, for
|
|
84
|
-
* `runCanvasPreview` to drive: `evaluate` runs an expression in the page
|
|
85
|
-
* and resolves with its JSON-safe value (awaiting a promise), `resize` sets
|
|
86
|
-
* the viewport in CSS pixels, `capture` takes a PNG of the viewport.
|
|
87
|
-
*
|
|
88
|
-
* @category plugins
|
|
89
|
-
* @since 0.1.0
|
|
90
|
-
*/
|
|
91
36
|
export type CanvasPreviewPage = {
|
|
92
37
|
evaluate(expression: string): Promise<unknown>;
|
|
93
38
|
resize(width: number, height: number): Promise<void>;
|
|
94
39
|
capture(): Promise<Uint8Array>;
|
|
95
40
|
};
|
|
96
|
-
/**
|
|
97
|
-
* Runs a preview on a page that has loaded the shell: installs the harness
|
|
98
|
-
* (retrying while the shell's script has not run yet), settles at the chat
|
|
99
|
-
* column's width and captures it there, then settles again at a phone's
|
|
100
|
-
* width for its height.
|
|
101
|
-
*
|
|
102
|
-
* @category plugins
|
|
103
|
-
* @since 0.1.0
|
|
104
|
-
*/
|
|
105
41
|
export declare function runCanvasPreview(page: CanvasPreviewPage, request: CanvasPreviewRequest): Promise<CanvasPreviewReport>;
|
|
106
|
-
/** Returns the preview as the model reads it beside the screenshot: heights, errors, console. */
|
|
107
42
|
export declare function describeCanvasPreview(report: CanvasPreviewReport): string;
|