@parlor/agent-ui 0.0.0-stage → 0.2.0

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.
Files changed (111) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +10 -2
  3. package/dist/client/agent-ui-client.d.ts +60 -0
  4. package/dist/client/agent-ui-client.d.ts.map +1 -0
  5. package/dist/client/agent-ui-client.js +407 -0
  6. package/dist/client/agent-ui-client.js.map +1 -0
  7. package/dist/client/confirmations.d.ts +34 -0
  8. package/dist/client/confirmations.d.ts.map +1 -0
  9. package/dist/client/confirmations.js +95 -0
  10. package/dist/client/confirmations.js.map +1 -0
  11. package/dist/client/environment.d.ts +37 -0
  12. package/dist/client/environment.d.ts.map +1 -0
  13. package/dist/client/environment.js +79 -0
  14. package/dist/client/environment.js.map +1 -0
  15. package/dist/client/highlight.d.ts +12 -0
  16. package/dist/client/highlight.d.ts.map +1 -0
  17. package/dist/client/highlight.js +98 -0
  18. package/dist/client/highlight.js.map +1 -0
  19. package/dist/client/index.d.ts +7 -0
  20. package/dist/client/index.d.ts.map +1 -0
  21. package/dist/client/index.js +7 -0
  22. package/dist/client/index.js.map +1 -0
  23. package/dist/client/json-equal.d.ts +2 -0
  24. package/dist/client/json-equal.d.ts.map +1 -0
  25. package/dist/client/json-equal.js +20 -0
  26. package/dist/client/json-equal.js.map +1 -0
  27. package/dist/client/registry.d.ts +54 -0
  28. package/dist/client/registry.d.ts.map +1 -0
  29. package/dist/client/registry.js +280 -0
  30. package/dist/client/registry.js.map +1 -0
  31. package/dist/client/screen.d.ts +20 -0
  32. package/dist/client/screen.d.ts.map +1 -0
  33. package/dist/client/screen.js +196 -0
  34. package/dist/client/screen.js.map +1 -0
  35. package/dist/client/write.d.ts +13 -0
  36. package/dist/client/write.d.ts.map +1 -0
  37. package/dist/client/write.js +101 -0
  38. package/dist/client/write.js.map +1 -0
  39. package/dist/index.d.ts +5 -0
  40. package/dist/index.d.ts.map +1 -0
  41. package/dist/index.js +5 -0
  42. package/dist/index.js.map +1 -0
  43. package/dist/manifest.d.ts +44 -0
  44. package/dist/manifest.d.ts.map +1 -0
  45. package/dist/manifest.js +23 -0
  46. package/dist/manifest.js.map +1 -0
  47. package/dist/pages.d.ts +11 -0
  48. package/dist/pages.d.ts.map +1 -0
  49. package/dist/pages.js +42 -0
  50. package/dist/pages.js.map +1 -0
  51. package/dist/protocol.d.ts +206 -0
  52. package/dist/protocol.d.ts.map +1 -0
  53. package/dist/protocol.js +152 -0
  54. package/dist/protocol.js.map +1 -0
  55. package/dist/server/agent-ui.d.ts +36 -0
  56. package/dist/server/agent-ui.d.ts.map +1 -0
  57. package/dist/server/agent-ui.js +89 -0
  58. package/dist/server/agent-ui.js.map +1 -0
  59. package/dist/server/audience.d.ts +9 -0
  60. package/dist/server/audience.d.ts.map +1 -0
  61. package/dist/server/audience.js +15 -0
  62. package/dist/server/audience.js.map +1 -0
  63. package/dist/server/broker.d.ts +29 -0
  64. package/dist/server/broker.d.ts.map +1 -0
  65. package/dist/server/broker.js +152 -0
  66. package/dist/server/broker.js.map +1 -0
  67. package/dist/server/confirmations.d.ts +40 -0
  68. package/dist/server/confirmations.d.ts.map +1 -0
  69. package/dist/server/confirmations.js +230 -0
  70. package/dist/server/confirmations.js.map +1 -0
  71. package/dist/server/directory.d.ts +41 -0
  72. package/dist/server/directory.d.ts.map +1 -0
  73. package/dist/server/directory.js +193 -0
  74. package/dist/server/directory.js.map +1 -0
  75. package/dist/server/index.d.ts +10 -0
  76. package/dist/server/index.d.ts.map +1 -0
  77. package/dist/server/index.js +10 -0
  78. package/dist/server/index.js.map +1 -0
  79. package/dist/server/manifest-source.d.ts +10 -0
  80. package/dist/server/manifest-source.d.ts.map +1 -0
  81. package/dist/server/manifest-source.js +36 -0
  82. package/dist/server/manifest-source.js.map +1 -0
  83. package/dist/server/routes.d.ts +11 -0
  84. package/dist/server/routes.d.ts.map +1 -0
  85. package/dist/server/routes.js +38 -0
  86. package/dist/server/routes.js.map +1 -0
  87. package/dist/server/schemas.d.ts +69 -0
  88. package/dist/server/schemas.d.ts.map +1 -0
  89. package/dist/server/schemas.js +78 -0
  90. package/dist/server/schemas.js.map +1 -0
  91. package/dist/server/tools.d.ts +49 -0
  92. package/dist/server/tools.d.ts.map +1 -0
  93. package/dist/server/tools.js +312 -0
  94. package/dist/server/tools.js.map +1 -0
  95. package/dist/view.d.ts +54 -0
  96. package/dist/view.d.ts.map +1 -0
  97. package/dist/view.js +15 -0
  98. package/dist/view.js.map +1 -0
  99. package/dist/vite/index.d.ts +3 -0
  100. package/dist/vite/index.d.ts.map +1 -0
  101. package/dist/vite/index.js +3 -0
  102. package/dist/vite/index.js.map +1 -0
  103. package/dist/vite/manifest.d.ts +5 -0
  104. package/dist/vite/manifest.d.ts.map +1 -0
  105. package/dist/vite/manifest.js +107 -0
  106. package/dist/vite/manifest.js.map +1 -0
  107. package/dist/vite/plugin.d.ts +16 -0
  108. package/dist/vite/plugin.d.ts.map +1 -0
  109. package/dist/vite/plugin.js +192 -0
  110. package/dist/vite/plugin.js.map +1 -0
  111. package/package.json +65 -3
@@ -0,0 +1,11 @@
1
+ import type { H3 } from "h3";
2
+ import type { AgentUi } from "./agent-ui.js";
3
+ export declare const MAX_CLIENT_MESSAGE_BYTES: number;
4
+ export interface AgentUiRouteOptions {
5
+ readonly basePath: string;
6
+ readonly agentUi: AgentUi;
7
+ readonly audience: (request: Request) => string | Promise<string>;
8
+ readonly clientId: (request: Request) => string | undefined;
9
+ }
10
+ export declare function registerAgentUiRoutes(app: H3, options: AgentUiRouteOptions): void;
11
+ //# sourceMappingURL=routes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../src/server/routes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,IAAI,CAAC;AAE7B,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAG7C,eAAO,MAAM,wBAAwB,QAAa,CAAC;AAEnD,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,QAAQ,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAClE,QAAQ,CAAC,QAAQ,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,MAAM,GAAG,SAAS,CAAC;CAC7D;AAID,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,EAAE,EAAE,OAAO,EAAE,mBAAmB,GAAG,IAAI,CA6BjF"}
@@ -0,0 +1,38 @@
1
+ import { parseClientMessage } from "./schemas.js";
2
+ export const MAX_CLIENT_MESSAGE_BYTES = 256 * 1024;
3
+ const status = (code) => new Response(null, { status: code });
4
+ export function registerAgentUiRoutes(app, options) {
5
+ const { basePath, agentUi, audience, clientId } = options;
6
+ app.post(`${basePath}/ui`, async (event) => {
7
+ const request = event.req;
8
+ if (Number(request.headers.get("content-length") ?? 0) > MAX_CLIENT_MESSAGE_BYTES) {
9
+ return status(413);
10
+ }
11
+ const body = await request.text();
12
+ if (new TextEncoder().encode(body).byteLength > MAX_CLIENT_MESSAGE_BYTES) {
13
+ return status(413);
14
+ }
15
+ const id = clientId(request);
16
+ if (!id)
17
+ return status(400);
18
+ let json;
19
+ try {
20
+ json = JSON.parse(body);
21
+ }
22
+ catch {
23
+ return status(400);
24
+ }
25
+ const message = parseClientMessage(json);
26
+ if (!message)
27
+ return status(400);
28
+ const receipt = agentUi.receive(id, await audience(request), message);
29
+ if (!receipt.accepted)
30
+ return status(404);
31
+ if (receipt.next === undefined)
32
+ return status(204);
33
+ return new Response(receipt.next, {
34
+ headers: { "content-type": "application/json" },
35
+ });
36
+ });
37
+ }
38
+ //# sourceMappingURL=routes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routes.js","sourceRoot":"","sources":["../../src/server/routes.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAElD,MAAM,CAAC,MAAM,wBAAwB,GAAG,GAAG,GAAG,IAAI,CAAC;AASnD,MAAM,MAAM,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC,IAAI,QAAQ,CAAC,IAAI,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;AAEtE,MAAM,UAAU,qBAAqB,CAAC,GAAO,EAAE,OAA4B;IACzE,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,GAAG,OAAO,CAAC;IAE1D,GAAG,CAAC,IAAI,CAAC,GAAG,QAAQ,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE;QACzC,MAAM,OAAO,GAAG,KAAK,CAAC,GAAG,CAAC;QAC1B,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,IAAI,CAAC,CAAC,GAAG,wBAAwB,EAAE,CAAC;YAClF,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC;QACrB,CAAC;QACD,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,IAAI,EAAE,CAAC;QAClC,IAAI,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,UAAU,GAAG,wBAAwB,EAAE,CAAC;YACzE,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC;QACrB,CAAC;QACD,MAAM,EAAE,GAAG,QAAQ,CAAC,OAAO,CAAC,CAAC;QAC7B,IAAI,CAAC,EAAE;YAAE,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC;QAC5B,IAAI,IAAa,CAAC;QAClB,IAAI,CAAC;YACH,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAC1B,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC;QACrB,CAAC;QACD,MAAM,OAAO,GAAG,kBAAkB,CAAC,IAAI,CAAC,CAAC;QACzC,IAAI,CAAC,OAAO;YAAE,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC;QACjC,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,EAAE,EAAE,MAAM,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC,CAAC;QACtE,IAAI,CAAC,OAAO,CAAC,QAAQ;YAAE,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC;QAC1C,IAAI,OAAO,CAAC,IAAI,KAAK,SAAS;YAAE,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC;QACnD,OAAO,IAAI,QAAQ,CAAC,OAAO,CAAC,IAAI,EAAE;YAChC,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;SAChD,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC","sourcesContent":["import type { H3 } from \"h3\";\n\nimport type { AgentUi } from \"./agent-ui.js\";\nimport { parseClientMessage } from \"./schemas.js\";\n\nexport const MAX_CLIENT_MESSAGE_BYTES = 256 * 1024;\n\nexport interface AgentUiRouteOptions {\n readonly basePath: string;\n readonly agentUi: AgentUi;\n readonly audience: (request: Request) => string | Promise<string>;\n readonly clientId: (request: Request) => string | undefined;\n}\n\nconst status = (code: number) => new Response(null, { status: code });\n\nexport function registerAgentUiRoutes(app: H3, options: AgentUiRouteOptions): void {\n const { basePath, agentUi, audience, clientId } = options;\n\n app.post(`${basePath}/ui`, async (event) => {\n const request = event.req;\n if (Number(request.headers.get(\"content-length\") ?? 0) > MAX_CLIENT_MESSAGE_BYTES) {\n return status(413);\n }\n const body = await request.text();\n if (new TextEncoder().encode(body).byteLength > MAX_CLIENT_MESSAGE_BYTES) {\n return status(413);\n }\n const id = clientId(request);\n if (!id) return status(400);\n let json: unknown;\n try {\n json = JSON.parse(body);\n } catch {\n return status(400);\n }\n const message = parseClientMessage(json);\n if (!message) return status(400);\n const receipt = agentUi.receive(id, await audience(request), message);\n if (!receipt.accepted) return status(404);\n if (receipt.next === undefined) return status(204);\n return new Response(receipt.next, {\n headers: { \"content-type\": \"application/json\" },\n });\n });\n}\n"]}
@@ -0,0 +1,69 @@
1
+ import { z } from "zod";
2
+ import { type ClientMessage } from "../protocol.js";
3
+ export declare const uiOpInputs: {
4
+ screen: z.ZodObject<{}, z.core.$strip>;
5
+ navigate: z.ZodObject<{
6
+ to: z.ZodString;
7
+ params: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
8
+ search: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
9
+ replace: z.ZodOptional<z.ZodBoolean>;
10
+ }, z.core.$strip>;
11
+ set: z.ZodObject<{
12
+ view: z.ZodString;
13
+ key: z.ZodOptional<z.ZodString>;
14
+ value: z.ZodRecord<z.ZodString, z.ZodUnknown>;
15
+ }, z.core.$strip>;
16
+ highlight: z.ZodObject<{
17
+ view: z.ZodString;
18
+ key: z.ZodOptional<z.ZodString>;
19
+ field: z.ZodOptional<z.ZodString>;
20
+ }, z.core.$strip>;
21
+ };
22
+ export declare const clientMessageSchema: z.ZodUnion<readonly [z.ZodObject<{
23
+ type: z.ZodLiteral<"reply">;
24
+ id: z.ZodString;
25
+ ok: z.ZodLiteral<true>;
26
+ result: z.ZodUnknown;
27
+ }, z.core.$strip>, z.ZodObject<{
28
+ type: z.ZodLiteral<"reply">;
29
+ id: z.ZodString;
30
+ ok: z.ZodLiteral<false>;
31
+ error: z.ZodObject<{
32
+ code: z.ZodEnum<{
33
+ ambiguous_client: "ambiguous_client";
34
+ busy: "busy";
35
+ internal: "internal";
36
+ invalid_value: "invalid_value";
37
+ not_mounted: "not_mounted";
38
+ not_writable: "not_writable";
39
+ outcome_unknown: "outcome_unknown";
40
+ timeout: "timeout";
41
+ unknown_client: "unknown_client";
42
+ unknown_route: "unknown_route";
43
+ unsupported_op: "unsupported_op";
44
+ user_editing: "user_editing";
45
+ }>;
46
+ message: z.ZodString;
47
+ issues: z.ZodOptional<z.ZodArray<z.ZodObject<{
48
+ path: z.ZodString;
49
+ message: z.ZodString;
50
+ }, z.core.$strip>>>;
51
+ }, z.core.$strip>;
52
+ }, z.core.$strip>, z.ZodObject<{
53
+ type: z.ZodLiteral<"presence">;
54
+ focused: z.ZodBoolean;
55
+ visible: z.ZodBoolean;
56
+ platform: z.ZodString;
57
+ buildId: z.ZodOptional<z.ZodString>;
58
+ ops: z.ZodOptional<z.ZodArray<z.ZodString>>;
59
+ canConfirm: z.ZodOptional<z.ZodBoolean>;
60
+ watching: z.ZodCatch<z.ZodOptional<z.ZodPipe<z.ZodArray<z.ZodUnknown>, z.ZodTransform<string[], unknown[]>>>>;
61
+ }, z.core.$strip>, z.ZodObject<{
62
+ type: z.ZodLiteral<"leave">;
63
+ }, z.core.$strip>, z.ZodObject<{
64
+ type: z.ZodLiteral<"answer">;
65
+ id: z.ZodString;
66
+ approve: z.ZodBoolean;
67
+ }, z.core.$strip>]>;
68
+ export declare function parseClientMessage(body: unknown): ClientMessage | undefined;
69
+ //# sourceMappingURL=schemas.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schemas.d.ts","sourceRoot":"","sources":["../../src/server/schemas.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,KAAK,aAAa,EAAuC,MAAM,gBAAgB,CAAC;AAEzF,eAAO,MAAM,UAAU;IACrB,MAAM;IACN,QAAQ;;;;;;IAeR,GAAG;;;;;IAOH,SAAS;;;;;CAQV,CAAC;AAkBF,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;mBAyB9B,CAAC;AAEH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,OAAO,GAAG,aAAa,GAAG,SAAS,CAG3E"}
@@ -0,0 +1,78 @@
1
+ import { z } from "zod";
2
+ import { MAX_WATCHED_THREADS, UI_ERROR_CODES } from "../protocol.js";
3
+ export const uiOpInputs = {
4
+ screen: z.object({}),
5
+ navigate: z.object({
6
+ to: z.string(),
7
+ params: z
8
+ .record(z.string(), z.string())
9
+ .optional()
10
+ .describe('Values for the $params in the path, e.g. { "taskId": "..." }.'),
11
+ search: z
12
+ .record(z.string(), z.unknown())
13
+ .optional()
14
+ .describe("Search params for the page, as its entry in the list names them."),
15
+ replace: z
16
+ .boolean()
17
+ .optional()
18
+ .describe("Replace the current history entry instead of pushing one."),
19
+ }),
20
+ set: z.object({
21
+ view: z.string().describe("A writable view's name, as listed."),
22
+ key: z.string().optional().describe("The item's key, for a keyed view."),
23
+ value: z
24
+ .record(z.string(), z.unknown())
25
+ .describe("The fields to write; others keep their current value."),
26
+ }),
27
+ highlight: z.object({
28
+ view: z.string().describe("A view's name, as ui_screen reports it."),
29
+ key: z.string().optional().describe("The item's key, for a keyed view."),
30
+ field: z
31
+ .string()
32
+ .optional()
33
+ .describe("A field inside the view to focus, by its input name."),
34
+ }),
35
+ };
36
+ const errorSchema = z.object({
37
+ code: z.enum(UI_ERROR_CODES),
38
+ message: z.string(),
39
+ issues: z.array(z.object({ path: z.string(), message: z.string() })).optional(),
40
+ });
41
+ const watchingSchema = z
42
+ .array(z.unknown())
43
+ .transform((ids) => ids
44
+ .filter((id) => typeof id === "string" && id !== "")
45
+ .slice(0, MAX_WATCHED_THREADS))
46
+ .optional()
47
+ .catch(undefined);
48
+ export const clientMessageSchema = z.union([
49
+ z.object({
50
+ type: z.literal("reply"),
51
+ id: z.string().min(1),
52
+ ok: z.literal(true),
53
+ result: z.unknown(),
54
+ }),
55
+ z.object({
56
+ type: z.literal("reply"),
57
+ id: z.string().min(1),
58
+ ok: z.literal(false),
59
+ error: errorSchema,
60
+ }),
61
+ z.object({
62
+ type: z.literal("presence"),
63
+ focused: z.boolean(),
64
+ visible: z.boolean(),
65
+ platform: z.string(),
66
+ buildId: z.string().optional(),
67
+ ops: z.array(z.string()).optional(),
68
+ canConfirm: z.boolean().optional(),
69
+ watching: watchingSchema,
70
+ }),
71
+ z.object({ type: z.literal("leave") }),
72
+ z.object({ type: z.literal("answer"), id: z.string().min(1), approve: z.boolean() }),
73
+ ]);
74
+ export function parseClientMessage(body) {
75
+ const parsed = clientMessageSchema.safeParse(body);
76
+ return parsed.success ? parsed.data : undefined;
77
+ }
78
+ //# sourceMappingURL=schemas.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schemas.js","sourceRoot":"","sources":["../../src/server/schemas.ts"],"names":[],"mappings":"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","sourcesContent":["import { z } from \"zod\";\n\nimport { type ClientMessage, MAX_WATCHED_THREADS, UI_ERROR_CODES } from \"../protocol.js\";\n\nexport const uiOpInputs = {\n screen: z.object({}),\n navigate: z.object({\n to: z.string(),\n params: z\n .record(z.string(), z.string())\n .optional()\n .describe('Values for the $params in the path, e.g. { \"taskId\": \"...\" }.'),\n search: z\n .record(z.string(), z.unknown())\n .optional()\n .describe(\"Search params for the page, as its entry in the list names them.\"),\n replace: z\n .boolean()\n .optional()\n .describe(\"Replace the current history entry instead of pushing one.\"),\n }),\n set: z.object({\n view: z.string().describe(\"A writable view's name, as listed.\"),\n key: z.string().optional().describe(\"The item's key, for a keyed view.\"),\n value: z\n .record(z.string(), z.unknown())\n .describe(\"The fields to write; others keep their current value.\"),\n }),\n highlight: z.object({\n view: z.string().describe(\"A view's name, as ui_screen reports it.\"),\n key: z.string().optional().describe(\"The item's key, for a keyed view.\"),\n field: z\n .string()\n .optional()\n .describe(\"A field inside the view to focus, by its input name.\"),\n }),\n};\n\nconst errorSchema = z.object({\n code: z.enum(UI_ERROR_CODES),\n message: z.string(),\n issues: z.array(z.object({ path: z.string(), message: z.string() })).optional(),\n});\n\nconst watchingSchema = z\n .array(z.unknown())\n .transform((ids) =>\n ids\n .filter((id): id is string => typeof id === \"string\" && id !== \"\")\n .slice(0, MAX_WATCHED_THREADS),\n )\n .optional()\n .catch(undefined);\n\nexport const clientMessageSchema = z.union([\n z.object({\n type: z.literal(\"reply\"),\n id: z.string().min(1),\n ok: z.literal(true),\n result: z.unknown(),\n }),\n z.object({\n type: z.literal(\"reply\"),\n id: z.string().min(1),\n ok: z.literal(false),\n error: errorSchema,\n }),\n z.object({\n type: z.literal(\"presence\"),\n focused: z.boolean(),\n visible: z.boolean(),\n platform: z.string(),\n buildId: z.string().optional(),\n ops: z.array(z.string()).optional(),\n canConfirm: z.boolean().optional(),\n watching: watchingSchema,\n }),\n z.object({ type: z.literal(\"leave\") }),\n z.object({ type: z.literal(\"answer\"), id: z.string().min(1), approve: z.boolean() }),\n]);\n\nexport function parseClientMessage(body: unknown): ClientMessage | undefined {\n const parsed = clientMessageSchema.safeParse(body);\n return parsed.success ? parsed.data : undefined;\n}\n"]}
@@ -0,0 +1,49 @@
1
+ import { z } from "zod";
2
+ import { type UiManifest } from "../manifest.js";
3
+ import { type AgentOrigin, type ClientSummary, type ScreenSnapshot, type UiOp, type UiOpResults } from "../protocol.js";
4
+ import { type UiBroker } from "./broker.js";
5
+ import type { ClientDirectory } from "./directory.js";
6
+ export type UiToolName = `ui_${UiOp}`;
7
+ export declare const UI_TOOLS: readonly UiToolName[];
8
+ export declare const UI_MANIFEST_RESOURCE = "parlor://manifest";
9
+ export interface AgentToolContext {
10
+ readonly scope: string;
11
+ readonly clientId?: string | undefined;
12
+ readonly origin: AgentOrigin;
13
+ readonly signal?: AbortSignal | undefined;
14
+ readonly webOrigin?: string | undefined;
15
+ }
16
+ export interface AgentTool<I extends z.ZodObject = z.ZodObject> {
17
+ readonly name: UiToolName;
18
+ readonly description: string;
19
+ readonly input: I;
20
+ readonly readOnly: boolean;
21
+ run(input: z.output<I>, ctx: AgentToolContext): Promise<unknown>;
22
+ }
23
+ export interface ScreenToolResult extends ScreenSnapshot {
24
+ readonly client: ClientSummary;
25
+ readonly otherClients: readonly ClientSummary[];
26
+ readonly tiedWith?: readonly string[] | undefined;
27
+ }
28
+ export interface NavigateToolResult {
29
+ readonly client: ClientSummary;
30
+ readonly screen: ScreenSnapshot;
31
+ }
32
+ export type SetToolResult = UiOpResults["set"] & {
33
+ readonly client: ClientSummary;
34
+ };
35
+ export type HighlightToolResult = UiOpResults["highlight"] & {
36
+ readonly client: ClientSummary;
37
+ };
38
+ export interface UnreachableResult {
39
+ readonly reachable: false;
40
+ readonly reason: "no_client";
41
+ readonly href?: string | undefined;
42
+ }
43
+ export interface UiToolDeps {
44
+ readonly directory: ClientDirectory;
45
+ readonly broker: UiBroker;
46
+ }
47
+ export declare function createUiTools(deps: UiToolDeps): (manifest: UiManifest | undefined) => readonly AgentTool[];
48
+ export declare function uiInstructions(): string;
49
+ //# sourceMappingURL=tools.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../../src/server/tools.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAIL,KAAK,UAAU,EAChB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,KAAK,WAAW,EAChB,KAAK,aAAa,EAClB,KAAK,cAAc,EAEnB,KAAK,IAAI,EACT,KAAK,WAAW,EAEjB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,KAAK,QAAQ,EAAa,MAAM,aAAa,CAAC;AACvD,OAAO,KAAK,EAAE,eAAe,EAAa,MAAM,gBAAgB,CAAC;AAGjE,MAAM,MAAM,UAAU,GAAG,MAAM,IAAI,EAAE,CAAC;AAItC,eAAO,MAAM,QAAQ,EAAE,SAAS,UAAU,EAAyB,CAAC;AAEpE,eAAO,MAAM,oBAAoB,sBAAsB,CAAC;AAExD,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACvC,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;IAC7B,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,SAAS,CAAC;IAC1C,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACzC;AAED,MAAM,WAAW,SAAS,CAAC,CAAC,SAAS,CAAC,CAAC,SAAS,GAAG,CAAC,CAAC,SAAS;IAC5D,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAC1B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC;IAClB,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,GAAG,CAAC,KAAK,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,gBAAgB,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CAClE;AAED,MAAM,WAAW,gBAAiB,SAAQ,cAAc;IACtD,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC;IAC/B,QAAQ,CAAC,YAAY,EAAE,SAAS,aAAa,EAAE,CAAC;IAChD,QAAQ,CAAC,QAAQ,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,CAAC;CACnD;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC;IAC/B,QAAQ,CAAC,MAAM,EAAE,cAAc,CAAC;CACjC;AAED,MAAM,MAAM,aAAa,GAAG,WAAW,CAAC,KAAK,CAAC,GAAG;IAAE,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAA;CAAE,CAAC;AAEpF,MAAM,MAAM,mBAAmB,GAAG,WAAW,CAAC,WAAW,CAAC,GAAG;IAC3D,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC;CAChC,CAAC;AAEF,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,SAAS,EAAE,KAAK,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;IAC7B,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACpC;AAED,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,SAAS,EAAE,eAAe,CAAC;IACpC,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC;CAC3B;AA4JD,wBAAgB,aAAa,CAC3B,IAAI,EAAE,UAAU,GACf,CAAC,QAAQ,EAAE,UAAU,GAAG,SAAS,KAAK,SAAS,SAAS,EAAE,CAmM5D;AAED,wBAAgB,cAAc,IAAI,MAAM,CAavC"}
@@ -0,0 +1,312 @@
1
+ import { z } from "zod";
2
+ import { routeHref, } from "../manifest.js";
3
+ import { UI_OPS, uiOpReadOnly, } from "../protocol.js";
4
+ import { UiFailure } from "./broker.js";
5
+ import { uiOpInputs } from "./schemas.js";
6
+ const toolName = (op) => `ui_${op}`;
7
+ export const UI_TOOLS = UI_OPS.map(toolName);
8
+ export const UI_MANIFEST_RESOURCE = "parlor://manifest";
9
+ const clientIdInput = z
10
+ .string()
11
+ .optional()
12
+ .describe("Target a specific app client by id, as listed in client or otherClients of a ui_screen result. Omit to use the one the user is looking at.");
13
+ const UNREACHABLE = { reachable: false, reason: "no_client" };
14
+ function sentence(text) {
15
+ const trimmed = text.trim();
16
+ return /[.!?]$/.test(trimmed) ? trimmed : `${trimmed}.`;
17
+ }
18
+ function viewCatalog(manifest) {
19
+ if (!manifest || manifest.views.length === 0)
20
+ return "";
21
+ const lines = manifest.views.map((view) => `- ${view.name}${view.keyed ? " (keyed)" : ""}: ${sentence(view.description)}`);
22
+ return `\n\nViews this app can show:\n${lines.join("\n")}`;
23
+ }
24
+ function typeOf(schema) {
25
+ if (!schema)
26
+ return "value";
27
+ if (Array.isArray(schema.enum))
28
+ return schema.enum.map((value) => JSON.stringify(value)).join(" | ");
29
+ if ("const" in schema)
30
+ return JSON.stringify(schema.const);
31
+ const union = (schema.anyOf ?? schema.oneOf);
32
+ if (Array.isArray(union))
33
+ return union.map(typeOf).join(" | ");
34
+ if (schema.type === "array")
35
+ return `${typeOf(schema.items)}[]`;
36
+ return typeof schema.type === "string" ? schema.type : "value";
37
+ }
38
+ /** `filter?: "all" | "done", page?: number`: what a page's search params take. */
39
+ function searchOf(search) {
40
+ const properties = search?.properties;
41
+ if (!properties || Object.keys(properties).length === 0)
42
+ return undefined;
43
+ const required = new Set(Array.isArray(search?.required) ? search.required : []);
44
+ return Object.entries(properties)
45
+ .map(([name, schema]) => `${name}${required.has(name) ? "" : "?"}: ${typeOf(schema)}`)
46
+ .join(", ");
47
+ }
48
+ function routeCatalog(manifest) {
49
+ if (!manifest || manifest.routes.length === 0)
50
+ return "";
51
+ const lines = manifest.routes.map((route) => {
52
+ const parts = [`- ${route.path}`];
53
+ if (route.description)
54
+ parts.push(` - ${sentence(route.description)}`);
55
+ const search = searchOf(route.search);
56
+ if (search)
57
+ parts.push(` Search: ${search}.`);
58
+ if (route.views.length > 0)
59
+ parts.push(` Views: ${route.views.join(", ")}.`);
60
+ return parts.join("");
61
+ });
62
+ return `\n\nPages:\n${lines.join("\n")}`;
63
+ }
64
+ function screenDescription(manifest) {
65
+ return ([
66
+ "Read what the user currently sees in the app: the route, the actions feeding the screen (name and input, never data), the views on screen with their values, and attention.",
67
+ "route is the page the url matched, with notFound: false, its path pattern, params, search and url; when the url matches no page and the app shows its not-found screen, route is notFound: true with only the url.",
68
+ "attention holds focus (the focused element), selection (the text the user selected and the view holding it; null unless the selection lies inside one view), lastInteracted (the view the user last clicked or focused), lastEntered (the view the user last scrolled into view; null until the user scrolls, since items already on screen when the page renders never count), and focal (the view nearest the middle of the viewport).",
69
+ "Keyed views list only the items inside the viewport, in screen order, plus an offscreen count.",
70
+ "The result names the app client it came from and every other open client.",
71
+ "Returns reachable: false when the app is not open anywhere.",
72
+ ].join(" ") + viewCatalog(manifest));
73
+ }
74
+ function navigateDescription(manifest) {
75
+ return ([
76
+ "Take the user's app to a page, filling $params in the path from params.",
77
+ "Waits for the page's data to load (up to 1.5 s) and returns the new screen in ui_screen's shape, so its route shows the page the app landed on.",
78
+ "Returns reachable: false with an href to hand the user when the app is not open.",
79
+ ].join(" ") + routeCatalog(manifest));
80
+ }
81
+ function writableCatalog(manifest) {
82
+ const lines = (manifest?.views ?? [])
83
+ .filter((view) => view.writable.length > 0)
84
+ .map((view) => `- ${view.name}${view.keyed ? " (keyed)" : ""}: ${sentence(view.description)} Fields: ${view.writable.join(", ")}.`);
85
+ return lines.length === 0 ? "" : `\n\nForms you can fill:\n${lines.join("\n")}`;
86
+ }
87
+ function setDescription(manifest) {
88
+ return ([
89
+ "Fill fields of a form the user sees, as a proposal: the user reviews it and submits it themselves. Call the matching action instead only when the user explicitly asks you to submit.",
90
+ "value is merged into the view's current value field by field; fields you omit keep what the user typed.",
91
+ "Returns the view's value read back from the screen and agentFields, the fields that still hold what you wrote; a field missing from agentFields was changed by the user.",
92
+ "Refuses with not_mounted, not_writable, user_editing (the user is typing in that field: wait or ask), invalid_value (issues name the fields), or ambiguous_client.",
93
+ "Returns reachable: false when the app is not open.",
94
+ ].join(" ") + writableCatalog(manifest));
95
+ }
96
+ function highlightDescription() {
97
+ return [
98
+ "Point at something on the user's screen: scrolls a view (a keyed item by key) or one field inside it into the middle of the screen, focuses the field (not on a touch screen, where that opens the keyboard), and marks it for two seconds.",
99
+ "Use it to show the user what you mean or to ask which one they meant.",
100
+ "Returns shown, the visible fraction after scrolling, or not_mounted when it is not on screen, or reachable: false when the app is not open.",
101
+ "Refuses with user_editing when the user is typing in another field: wait or ask, and it moves nothing.",
102
+ ].join(" ");
103
+ }
104
+ function routeEnum(manifest) {
105
+ const paths = manifest?.routes.map((route) => route.path) ?? [];
106
+ const [first, ...rest] = paths;
107
+ return first === undefined ? z.string() : z.enum([first, ...rest]);
108
+ }
109
+ function defineTool(tool) {
110
+ return tool;
111
+ }
112
+ function withClients(directory, scope, selection, snapshot) {
113
+ const chosen = selection.target.client;
114
+ return {
115
+ client: directory.summary(chosen),
116
+ otherClients: directory.summaries(scope).filter((c) => c.id !== chosen.id),
117
+ ...(selection.tied.length > 0 ? { tiedWith: selection.tied.map((c) => c.id) } : {}),
118
+ ...snapshot,
119
+ };
120
+ }
121
+ function missingParams(route, params) {
122
+ const missing = route.params.filter((name) => !params?.[name]);
123
+ if (missing.length === 0)
124
+ return;
125
+ throw new UiFailure({
126
+ code: "unknown_route",
127
+ message: `${route.path} needs params: ${missing.join(", ")}.`,
128
+ issues: missing.map((name) => ({ path: `params.${name}`, message: "Required" })),
129
+ });
130
+ }
131
+ function hrefFor(href, webOrigin) {
132
+ return webOrigin ? new URL(href, webOrigin).href : href;
133
+ }
134
+ export function createUiTools(deps) {
135
+ const { directory, broker } = deps;
136
+ const refuseTie = (selection) => {
137
+ if (selection.tied.length === 0)
138
+ return;
139
+ const candidates = [selection.target.client, ...selection.tied];
140
+ throw new UiFailure({
141
+ code: "ambiguous_client",
142
+ message: "The user has several app clients open and none stands out. Ask which one, then retry with clientId.",
143
+ issues: candidates.map((client) => {
144
+ const summary = directory.summary(client);
145
+ return {
146
+ path: "clientId",
147
+ message: `${summary.id} (${summary.platform}, ${summary.focused ? "focused" : "not focused"}, ${summary.visible ? "visible" : "hidden"}, seen ${summary.seenAgoMs} ms ago)`,
148
+ };
149
+ }),
150
+ });
151
+ };
152
+ const requireOp = (selection, op) => {
153
+ const { client } = selection.target;
154
+ if (client.ops.includes(op))
155
+ return;
156
+ throw new UiFailure({
157
+ code: "unsupported_op",
158
+ message: `The app client ${client.id} (${client.platform}${client.buildId ? `, build ${client.buildId}` : ""}) is too old for ui_${op}. Ask the user to update the app; ui_screen and ui_navigate still work.`,
159
+ });
160
+ };
161
+ const select = (ctx, clientId) => {
162
+ if (ctx.clientId !== undefined)
163
+ return selectBound(ctx, ctx.clientId, clientId);
164
+ if (clientId !== undefined && !directory.connected(ctx.scope, clientId)) {
165
+ const connected = directory.summaries(ctx.scope).map((client) => client.id);
166
+ throw new UiFailure({
167
+ code: "unknown_client",
168
+ message: `No app client ${clientId} is connected. Connected clients: ${connected.join(", ") || "none"}.`,
169
+ issues: connected.map((id) => ({ path: "clientId", message: id })),
170
+ });
171
+ }
172
+ return directory.select(ctx.scope, clientId);
173
+ };
174
+ const selectBound = (ctx, bound, requested) => {
175
+ if (requested !== undefined && requested !== bound) {
176
+ throw new UiFailure({
177
+ code: "unknown_client",
178
+ message: `This chat acts on the app client its prompt came from, not on ${requested}.`,
179
+ });
180
+ }
181
+ if (!directory.connected(ctx.scope, bound)) {
182
+ throw new UiFailure({
183
+ code: "unknown_client",
184
+ message: "The app client this chat was sent from is no longer connected.",
185
+ });
186
+ }
187
+ return directory.select(ctx.scope, bound);
188
+ };
189
+ const build = (manifest) => {
190
+ const screen = defineTool({
191
+ name: "ui_screen",
192
+ description: screenDescription(manifest),
193
+ input: uiOpInputs.screen.extend({ clientId: clientIdInput }),
194
+ readOnly: uiOpReadOnly.screen,
195
+ async run({ clientId }, ctx) {
196
+ const selection = select(ctx, clientId);
197
+ if (!selection)
198
+ return UNREACHABLE;
199
+ const snapshot = await broker.request(selection.target, "screen", {}, ctx.origin, ctx.signal);
200
+ return withClients(directory, ctx.scope, selection, snapshot);
201
+ },
202
+ });
203
+ const navigate = defineTool({
204
+ name: "ui_navigate",
205
+ description: navigateDescription(manifest),
206
+ input: uiOpInputs.navigate.extend({
207
+ to: routeEnum(manifest).describe("The page's route path, exactly as listed."),
208
+ clientId: clientIdInput,
209
+ }),
210
+ readOnly: uiOpReadOnly.navigate,
211
+ async run({ to, params, search, replace, clientId }, ctx) {
212
+ const selection = select(ctx, clientId);
213
+ const route = manifest?.routes.find((entry) => entry.path === to);
214
+ if (manifest && manifest.routes.length > 0 && !route) {
215
+ throw new UiFailure({ code: "unknown_route", message: `No page at ${to}.` });
216
+ }
217
+ if (route)
218
+ missingParams(route, params);
219
+ if (!selection) {
220
+ const href = routeHref(route ?? { path: to, params: [], views: [] }, params, search);
221
+ return { ...UNREACHABLE, href: hrefFor(href, ctx.webOrigin) };
222
+ }
223
+ refuseTie(selection);
224
+ const input = {
225
+ to,
226
+ ...(params ? { params } : {}),
227
+ ...(search ? { search } : {}),
228
+ ...(replace === undefined ? {} : { replace }),
229
+ };
230
+ const result = await broker.request(selection.target, "navigate", input, ctx.origin, ctx.signal);
231
+ const navigated = {
232
+ client: directory.summary(selection.target.client),
233
+ screen: result.screen,
234
+ };
235
+ return navigated;
236
+ },
237
+ });
238
+ const set = defineTool({
239
+ name: toolName("set"),
240
+ description: setDescription(manifest),
241
+ input: uiOpInputs.set.extend({ clientId: clientIdInput }),
242
+ readOnly: uiOpReadOnly.set,
243
+ async run({ view, key, value, clientId }, ctx) {
244
+ const selection = select(ctx, clientId);
245
+ if (!selection)
246
+ return UNREACHABLE;
247
+ refuseTie(selection);
248
+ requireOp(selection, "set");
249
+ const result = await broker.request(selection.target, "set", { view, ...(key === undefined ? {} : { key }), value }, ctx.origin, ctx.signal);
250
+ const written = {
251
+ client: directory.summary(selection.target.client),
252
+ ...result,
253
+ };
254
+ return written;
255
+ },
256
+ });
257
+ const highlight = defineTool({
258
+ name: toolName("highlight"),
259
+ description: highlightDescription(),
260
+ input: uiOpInputs.highlight.extend({ clientId: clientIdInput }),
261
+ readOnly: uiOpReadOnly.highlight,
262
+ async run({ view, key, field, clientId }, ctx) {
263
+ const selection = select(ctx, clientId);
264
+ if (!selection)
265
+ return UNREACHABLE;
266
+ refuseTie(selection);
267
+ requireOp(selection, "highlight");
268
+ const result = await broker.request(selection.target, "highlight", {
269
+ view,
270
+ ...(key === undefined ? {} : { key }),
271
+ ...(field === undefined ? {} : { field }),
272
+ }, ctx.origin, ctx.signal);
273
+ const pointed = {
274
+ client: directory.summary(selection.target.client),
275
+ ...result,
276
+ };
277
+ return pointed;
278
+ },
279
+ });
280
+ const byOp = {
281
+ screen,
282
+ navigate,
283
+ set,
284
+ highlight,
285
+ };
286
+ return UI_OPS.map((op) => byOp[op]);
287
+ };
288
+ let cachedFor;
289
+ let cached = build(undefined);
290
+ return (manifest) => {
291
+ if (manifest !== cachedFor) {
292
+ cached = build(manifest);
293
+ cachedFor = manifest;
294
+ }
295
+ return cached;
296
+ };
297
+ }
298
+ export function uiInstructions() {
299
+ return [
300
+ "This app's screen is visible to you through the ui_ tools, next to the app's actions.",
301
+ 'Call ui_screen to see what the user is looking at before acting on words like "this", "that" or "here".',
302
+ "Resolve them from attention in this order: focus, selection, lastInteracted, focal.",
303
+ "A keyed view's key is the entity id that the app's actions take, so pass it straight to the action.",
304
+ "Do the work through actions; the screen updates by itself.",
305
+ "To show the user a page, call ui_navigate with a path from its list.",
306
+ "You fill forms and the user submits them: use ui_set to fill a form, and call the action that submits it only when the user explicitly asks you to.",
307
+ "Some actions ask the user to approve them in the app before they run: call them as usual and wait. If one fails with declined, do not retry unless the user asks again; if it fails with confirmation_unavailable, ask the user to do it in the app themselves.",
308
+ "Use ui_highlight to point at what you mean, or at candidates when you need the user to pick one.",
309
+ "If a ui_ tool returns reachable: false, the app is not open: tell the user, and for navigation hand them the href.",
310
+ ].join("\n");
311
+ }
312
+ //# sourceMappingURL=tools.js.map