From 45c9276f2b609c9754c9443a4f671cfa8633ecea Mon Sep 17 00:00:00 2001 From: Ben Follington <5009316+bfollington@users.noreply.github.com> Date: Fri, 21 Nov 2025 11:54:52 -0800 Subject: [PATCH 1/5] Create `todo-with-suggestion.tsx` --- .beads/issues.jsonl | 4 + packages/patterns/todo-with-suggestion.tsx | 125 +++++++++++++++++++++ 2 files changed, 129 insertions(+) create mode 100644 packages/patterns/todo-with-suggestion.tsx diff --git a/.beads/issues.jsonl b/.beads/issues.jsonl index e69de29bb..c9db504a8 100644 --- a/.beads/issues.jsonl +++ b/.beads/issues.jsonl @@ -0,0 +1,4 @@ +{"id":"ct-labs-f8g","content_hash":"1b69b91461a3f1d847e8faea516eecc43f978445dd2146c1e0498cffc2466b91","title":"Integrate Suggestion pattern","description":"Add AI suggestion powered by Suggestion pattern","status":"open","priority":2,"issue_type":"task","created_at":"2025-11-21T11:25:33.60042-08:00","updated_at":"2025-11-21T11:25:33.60042-08:00","source_repo":".","dependencies":[{"issue_id":"ct-labs-f8g","depends_on_id":"ct-labs-lk6","type":"blocks","created_at":"2025-11-21T11:25:33.600965-08:00","created_by":"daemon"}]} +{"id":"ct-labs-i1z","content_hash":"e6a28322e1a86178e563e140ec0ffb70e178ce7ec7be55e94ff471cae06646cd","title":"Implement todo list base functionality","description":"Add/remove/toggle todo items with bidirectional binding","status":"open","priority":2,"issue_type":"task","created_at":"2025-11-21T11:25:33.517179-08:00","updated_at":"2025-11-21T11:25:33.517179-08:00","source_repo":".","dependencies":[{"issue_id":"ct-labs-i1z","depends_on_id":"ct-labs-lk6","type":"blocks","created_at":"2025-11-21T11:25:33.517738-08:00","created_by":"daemon"}]} +{"id":"ct-labs-l3a","content_hash":"3c78bd4e9fbc0f4a6211a62eee827e01b9b12d406c5a804aa5ec8d880cb8119e","title":"Test pattern with ct dev","description":"Verify pattern works with deno task ct dev","status":"open","priority":2,"issue_type":"task","created_at":"2025-11-21T11:25:33.682807-08:00","updated_at":"2025-11-21T11:25:33.682807-08:00","source_repo":".","dependencies":[{"issue_id":"ct-labs-l3a","depends_on_id":"ct-labs-lk6","type":"blocks","created_at":"2025-11-21T11:25:33.68338-08:00","created_by":"daemon"}]} +{"id":"ct-labs-lk6","content_hash":"99a834f63aa5afe4143a8539ad34234e981eb88826bbe12f016faa8dcd44cce4","title":"Create todo-with-suggestion pattern","description":"Create packages/patterns/todo-with-suggestion.tsx - a todo list pattern that uses the Suggestion pattern for AI-powered suggestions","status":"open","priority":1,"issue_type":"feature","created_at":"2025-11-21T11:25:24.899876-08:00","updated_at":"2025-11-21T11:25:24.899876-08:00","source_repo":"."} diff --git a/packages/patterns/todo-with-suggestion.tsx b/packages/patterns/todo-with-suggestion.tsx new file mode 100644 index 000000000..4ef5f71e8 --- /dev/null +++ b/packages/patterns/todo-with-suggestion.tsx @@ -0,0 +1,125 @@ +/// +import { Cell, Default, derive, NAME, pattern, UI } from "commontools"; +import { Suggestion } from "./suggestion.tsx"; + +interface TodoItem { + title: string; + done: Default; +} + +interface Input { + items: Cell; +} + +interface Output { + items: Cell; +} + +export default pattern(({ items }) => { + // AI suggestion based on current todos + const suggestion = Suggestion({ + situation: "Based on my todo list, suggest a helpful pattern or tool", + context: { items }, + }); + + return { + [NAME]: "Todo with Suggestions", + [UI]: ( +
+

Todo List

+ + {/* Add new item */} + { + const title = e.detail?.message?.trim(); + if (title) { + items.push({ title, done: false }); + } + }} + /> + + {/* Todo items with per-item suggestions */} +
+ {items.map((item) => { + const itemSuggestion = Suggestion({ + situation: + "Suggest a helpful action or pattern for this specific todo item", + context: { item, allItems: items }, + }); + + return ( +
+
+ + + {item.title} + + + { + const current = items.get(); + const index = current.findIndex((el) => + Cell.equals(item, el) + ); + if (index >= 0) { + items.set(current.toSpliced(index, 1)); + } + }} + > + × + +
+
+ {derive( + itemSuggestion, + (s) => s?.cell ?? ..., + )} +
+
+ ); + })} +
+ + {/* AI Suggestion */} +
+

AI Suggestion

+ {derive(suggestion, (s) => + s?.cell ?? ( + Getting suggestion... + ))} +
+
+ ), + items, + suggestion, + }; +}); From 14998414a49cb81cfb1dcf1dd0dcbf53547471bf Mon Sep 17 00:00:00 2001 From: Ben Follington <5009316+bfollington@users.noreply.github.com> Date: Fri, 21 Nov 2025 13:34:23 -0800 Subject: [PATCH 2/5] Add `write-and-run.tsx` --- packages/patterns/write-and-run.tsx | 158 ++++++++++++++++++++++++++++ 1 file changed, 158 insertions(+) create mode 100644 packages/patterns/write-and-run.tsx diff --git a/packages/patterns/write-and-run.tsx b/packages/patterns/write-and-run.tsx new file mode 100644 index 000000000..56886c959 --- /dev/null +++ b/packages/patterns/write-and-run.tsx @@ -0,0 +1,158 @@ +/// +import { + Cell, + compileAndRun, + computed, + Default, + derive, + generateText, + handler, + ifElse, + NAME, + navigateTo, + pattern, + UI, +} from "commontools"; + +// Template for the AI to reference +const TEMPLATE = `/// +import { Cell, derive, handler, NAME, pattern, UI } from "commontools"; + +interface Input { + value: Cell; +} + +const increment = handler }>( + (_, { value }) => value.set(value.get() + 1) +); + +const decrement = handler }>( + (_, { value }) => value.set(value.get() - 1) +); + +export default pattern(({ value }) => { + return { + [NAME]: derive(value, (v) => \`Counter: \${v}\`), + [UI]: ( +
+ - + {value} + + +
+ ), + value, + }; +});`; + +const SYSTEM_PROMPT = `You are a CommonTools pattern generator. Given a user request, generate a complete TypeScript pattern file. + +IMPORTANT RULES: +1. Start with: /// +2. Import from "commontools": Cell, Default, derive, handler, NAME, pattern, UI, computed, ifElse +3. Use the pattern() or pattern() API +4. For arrays that need mutation, use Cell in the interface +5. Use $checked, $value for bidirectional binding on ct-checkbox, ct-input +6. Use inline handlers for simple operations, handler() for complex ones +7. Always return [NAME] and [UI] from the pattern + +TEMPLATE FOR REFERENCE: +${TEMPLATE} + +Generate ONLY the TypeScript code, no explanations or markdown.`; + +interface Input { + prompt: Default; +} + +interface Output { + prompt: Cell; +} + +const updatePrompt = handler< + { detail: { message: string } }, + { prompt: Cell } +>((event, { prompt }) => { + const newPrompt = event.detail?.message?.trim(); + if (newPrompt) { + prompt.set(newPrompt); + } +}); + +const visit = handler }>((_, { result }) => { + return navigateTo(result); +}); + +export default pattern(({ prompt }) => { + // Step 1: Generate pattern source code from prompt + const generated = generateText({ + system: SYSTEM_PROMPT, + prompt, + model: "anthropic:claude-sonnet-4-5", + }); + + // Step 2: Compile the generated code when ready + const compileParams = derive(generated, (g) => ({ + files: [{ name: "/main.tsx", contents: g?.result ?? "" }], + main: "/main.tsx", + })); + + const compiled = compileAndRun(compileParams); + + // Compute states + const isGenerating = generated.pending; + const hasCode = computed(() => !!generated.result); + const hasError = computed(() => !!compiled.error); + const isReady = computed(() => !compiled.pending && !!compiled.result && !compiled.error); + + return { + [NAME]: "Write and Run", + [UI]: ( +
+

Write and Run

+

Describe a pattern and I'll generate, compile, and run it.

+ + + +
+ {ifElse( + isGenerating, + Generating code..., + ifElse( + hasError, +
+ Compile error: {compiled.error} +
, + ifElse( + isReady, + + Open Generated Pattern + , + Enter a prompt to generate a pattern, + ), + ), + )} +
+ + {ifElse( + hasCode, +
+

Generated Code

+ +
, + , + )} +
+ ), + prompt, + generatedCode: generated.result, + compiledCharm: compiled.result, + error: compiled.error, + }; +}); From b86b3883357d4e90d8bb0933755afa67192b5b66 Mon Sep 17 00:00:00 2001 From: Ben Follington <5009316+bfollington@users.noreply.github.com> Date: Fri, 21 Nov 2025 14:59:29 -0800 Subject: [PATCH 3/5] Working write-and-run! --- packages/patterns/write-and-run.tsx | 66 ++++++++++++++++++++++++----- 1 file changed, 56 insertions(+), 10 deletions(-) diff --git a/packages/patterns/write-and-run.tsx b/packages/patterns/write-and-run.tsx index 56886c959..402fe284f 100644 --- a/packages/patterns/write-and-run.tsx +++ b/packages/patterns/write-and-run.tsx @@ -16,10 +16,10 @@ import { // Template for the AI to reference const TEMPLATE = `/// -import { Cell, derive, handler, NAME, pattern, UI } from "commontools"; +import { Cell, derive, handler, Default, NAME, pattern, UI } from "commontools"; interface Input { - value: Cell; + value: Default; } const increment = handler }>( @@ -44,7 +44,8 @@ export default pattern(({ value }) => { }; });`; -const SYSTEM_PROMPT = `You are a CommonTools pattern generator. Given a user request, generate a complete TypeScript pattern file. +const SYSTEM_PROMPT = + `You are a CommonTools pattern generator. Given a user request, generate a complete TypeScript pattern file. IMPORTANT RULES: 1. Start with: /// @@ -90,10 +91,18 @@ export default pattern(({ prompt }) => { model: "anthropic:claude-sonnet-4-5", }); + const processedResult = computed(() => { + const result = generated?.result ?? ""; + // Remove wrapping ```typescript``` if it exists + return result.replace(/^```typescript\n?/, "").replace(/\n?```$/, ""); + }); + // Step 2: Compile the generated code when ready const compileParams = derive(generated, (g) => ({ - files: [{ name: "/main.tsx", contents: g?.result ?? "" }], - main: "/main.tsx", + files: processedResult + ? [{ name: "/main.tsx", contents: processedResult }] + : [], + main: processedResult ? "/main.tsx" : "", })); const compiled = compileAndRun(compileParams); @@ -102,21 +111,38 @@ export default pattern(({ prompt }) => { const isGenerating = generated.pending; const hasCode = computed(() => !!generated.result); const hasError = computed(() => !!compiled.error); - const isReady = computed(() => !compiled.pending && !!compiled.result && !compiled.error); + const isReady = computed(() => + !compiled.pending && !!compiled.result && !compiled.error + ); return { [NAME]: "Write and Run", [UI]: ( -
+

Write and Run

-

Describe a pattern and I'll generate, compile, and run it.

+

+ Describe a pattern and I'll generate, compile, and run it. +

-
+
{ifElse( isGenerating, Generating code..., @@ -130,12 +156,32 @@ export default pattern(({ prompt }) => { Open Generated Pattern , - Enter a prompt to generate a pattern, + + Enter a prompt to generate a pattern + , ), ), )}
+ {ifElse( + isReady, +
+

Generated Pattern

+
+ {compiled.result} +
+
, + , + )} + {ifElse( hasCode,
From 23cbe146775006dd44dc79f1aece848cd58edb81 Mon Sep 17 00:00:00 2001 From: Ben Follington <5009316+bfollington@users.noreply.github.com> Date: Fri, 21 Nov 2025 15:00:23 -0800 Subject: [PATCH 4/5] Fix lint error --- packages/patterns/write-and-run.tsx | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/packages/patterns/write-and-run.tsx b/packages/patterns/write-and-run.tsx index 402fe284f..d366625ce 100644 --- a/packages/patterns/write-and-run.tsx +++ b/packages/patterns/write-and-run.tsx @@ -98,11 +98,9 @@ export default pattern(({ prompt }) => { }); // Step 2: Compile the generated code when ready - const compileParams = derive(generated, (g) => ({ - files: processedResult - ? [{ name: "/main.tsx", contents: processedResult }] - : [], - main: processedResult ? "/main.tsx" : "", + const compileParams = derive(processedResult, (p) => ({ + files: p ? [{ name: "/main.tsx", contents: p }] : [], + main: p ? "/main.tsx" : "", })); const compiled = compileAndRun(compileParams); From dc27b775fb8a9b662d0359d435528fafec761f1c Mon Sep 17 00:00:00 2001 From: Ben Follington <5009316+bfollington@users.noreply.github.com> Date: Fri, 21 Nov 2025 15:20:43 -0800 Subject: [PATCH 5/5] Use `haiku` for suggestions --- packages/patterns/suggestion.tsx | 2 +- packages/patterns/todo-with-suggestion.tsx | 20 +------------------- packages/toolshed/routes/ai/llm/models.ts | 16 ++++++++++++++++ 3 files changed, 18 insertions(+), 20 deletions(-) diff --git a/packages/patterns/suggestion.tsx b/packages/patterns/suggestion.tsx index 792384d8d..9ca406c54 100644 --- a/packages/patterns/suggestion.tsx +++ b/packages/patterns/suggestion.tsx @@ -29,7 +29,7 @@ export const Suggestion = pattern( fetchAndRunPattern: patternTool(fetchAndRunPattern), listPatternIndex: patternTool(listPatternIndex), }, - model: "anthropic:claude-sonnet-4-5", + model: "anthropic:claude-haiku-4-5", schema: toSchema<{ cell: Cell }>(), }); diff --git a/packages/patterns/todo-with-suggestion.tsx b/packages/patterns/todo-with-suggestion.tsx index 4ef5f71e8..8e8f518ee 100644 --- a/packages/patterns/todo-with-suggestion.tsx +++ b/packages/patterns/todo-with-suggestion.tsx @@ -18,7 +18,7 @@ interface Output { export default pattern(({ items }) => { // AI suggestion based on current todos const suggestion = Suggestion({ - situation: "Based on my todo list, suggest a helpful pattern or tool", + situation: "Based on my todo list, use a pattern to help me.", context: { items }, }); @@ -42,12 +42,6 @@ export default pattern(({ items }) => { {/* Todo items with per-item suggestions */}
{items.map((item) => { - const itemSuggestion = Suggestion({ - situation: - "Suggest a helpful action or pattern for this specific todo item", - context: { item, allItems: items }, - }); - return (
(({ items }) => { ×
-
- {derive( - itemSuggestion, - (s) => s?.cell ?? ..., - )} -
); })} diff --git a/packages/toolshed/routes/ai/llm/models.ts b/packages/toolshed/routes/ai/llm/models.ts index 1c9073ad4..64b0bd215 100644 --- a/packages/toolshed/routes/ai/llm/models.ts +++ b/packages/toolshed/routes/ai/llm/models.ts @@ -210,6 +210,22 @@ if (env.CTTS_AI_LLM_ANTHROPIC_API_KEY) { }, }, }); + + addModel({ + provider: anthropicProvider, + name: "anthropic:claude-haiku-4-5", + aliases: ["haiku-4-5", "haiku-4.5"], + capabilities: { + contextWindow: 200_000, + maxOutputTokens: 8192, + images: true, + prefill: true, + systemPrompt: true, + stopSequences: true, + streaming: true, + reasoning: false, + }, + }); } if (env.CTTS_AI_LLM_GROQ_API_KEY) {