| 1 | // Action chips: the Claude tools a helper may call, and the mapping from the |
| 2 | // model's tool_use blocks to the `actions` array shipped inside the encrypted |
| 3 | // message payload. The CLIENT is the whitelist that matters (ui.js |
| 4 | // sanitizeActions) — this file only decides what the model can *propose*. |
| 5 | // Chips never auto-run: the member taps to apply, which is why the set is |
| 6 | // small, harmless, and reversible. Dependency-free so tests can import it. |
| 7 | |
| 8 | // Keep the theme list + descriptions in step with web/static/ui.js THEMES. |
| 9 | // Exported so the deterministic command floor (commands.mjs) validates against |
| 10 | // the same lists the model tools do. |
| 11 | export const THEMES = ["litroom", "lagoon", "sunny", "plum", "linen", "classic"]; |
| 12 | export const APPEARANCES = ["light", "dark", "system"]; |
| 13 | |
| 14 | export const CHIP_TOOLS = [ |
| 15 | { |
| 16 | name: "set_theme", |
| 17 | description: |
| 18 | "Attach a one-tap button that switches the member's colour theme when THEY tap it. " + |
| 19 | "Themes: litroom (warm amber 'Lit Room'), lagoon (teal), sunny (yellow), plum (purple), " + |
| 20 | "linen (soft neutral), classic (blue). Use when someone asks to change the look/colours.", |
| 21 | input_schema: { |
| 22 | type: "object", |
| 23 | properties: { theme: { type: "string", enum: THEMES } }, |
| 24 | required: ["theme"], |
| 25 | }, |
| 26 | }, |
| 27 | { |
| 28 | name: "set_appearance", |
| 29 | description: |
| 30 | "Attach a one-tap button that switches light/dark mode when the member taps it. " + |
| 31 | "'system' follows their device. Use when someone asks for light mode, dark mode, etc.", |
| 32 | input_schema: { |
| 33 | type: "object", |
| 34 | properties: { appearance: { type: "string", enum: ["light", "dark", "system"] } }, |
| 35 | required: ["appearance"], |
| 36 | }, |
| 37 | }, |
| 38 | { |
| 39 | name: "open_theme_picker", |
| 40 | description: |
| 41 | "Attach a one-tap button that opens the theme picker, for when they want to browse the options.", |
| 42 | input_schema: { type: "object", properties: {} }, |
| 43 | }, |
| 44 | { |
| 45 | name: "open_profile", |
| 46 | description: |
| 47 | "Attach a one-tap button that opens the member's own profile editor (screen name + photo).", |
| 48 | input_schema: { type: "object", properties: {} }, |
| 49 | }, |
| 50 | ]; |
| 51 | |
| 52 | // actionsFromToolUse maps the response's tool_use blocks to payload actions. |
| 53 | // Unknown tools and malformed inputs are dropped (the client re-validates too). |
| 54 | export function actionsFromToolUse(content) { |
| 55 | const out = []; |
| 56 | for (const b of content || []) { |
| 57 | if (b.type !== "tool_use") continue; |
| 58 | const input = b.input || {}; |
| 59 | if (b.name === "set_theme" && THEMES.includes(input.theme)) out.push({ type: "set_theme", theme: input.theme }); |
| 60 | else if (b.name === "set_appearance" && APPEARANCES.includes(input.appearance)) |
| 61 | out.push({ type: "set_appearance", appearance: input.appearance }); |
| 62 | else if (b.name === "open_theme_picker") out.push({ type: "open_theme_picker" }); |
| 63 | else if (b.name === "open_profile") out.push({ type: "open_profile" }); |
| 64 | } |
| 65 | return out; |
| 66 | } |
| 67 | |