owenkaplinsky commited on
Commit
6eec7f7
·
1 Parent(s): fc2c1ea

Fixes; prompt improvements

Browse files
project/blocks.txt CHANGED
@@ -1,60 +1,60 @@
1
  # AI
2
- llm_call(inputs(MODEL: "gpt-3.5-turbo-0125/gpt-4o-2024-08-06/gpt-5-mini-2025-08-07/gpt-5-2025-08-07/gpt-4o-search-preview-2025-03-11", PROMPT: value))
3
 
4
  # JSON and API
5
- in_json(inputs(NAME: value, JSON: value)) // NAME is the value you want to extract from the JSON
6
- make_json(inputs(KEYN: value, FIELDN: value)) // N starts at 0; you can make as many N as you want. Each key goes with one field
7
- call_api(inputs(METHOD: "GET/POST/PUT/DELETE", URL: value, HEADERS: value)) // HEADERS is optional
8
 
9
  # Logic
10
- controls_if(inputs(IF: value, IFELSEN0: value, ELSE)) // IF is REQUIRED (the condition). IFELSEN0, IFELSEN1, etc are OPTIONAL (additional else-if conditions). ELSE is OPTIONAL (no value needed, just include the word). DO NOT use input_name with controls_if creation; specify all conditions in the inputs. After creating, use input_name to place statements: "DO0" (IF then-statements), "DO1" (first ELSE-IF then-statements), "ELSE" (final else statements)
11
- logic_negate(inputs())
12
- logic_boolean(inputs(BOOL: "TRUE/FALSE"))
13
- logic_null(inputs())
14
- logic_compare(inputs(OP: "EQ/NEQ/LT/LTE/GT/GTE", A: value, B: value))
15
- logic_operation(inputs(OP: "AND/OR", A: value, B: value))
16
 
17
  # Loops
18
- controls_repeat_ext(inputs(TIMES: value))
19
- controls_whileUntil(inputs(MODE: "WHILE/UNTIL", BOOL: value))
20
- controls_for(inputs(VAR: value, FROM: value, TO: value, BY: value)) // VAR is a variable ID. BY is the increment amount
21
- controls_forEach(inputs(VAR: value), LIST: value) // VAR is a variable ID
22
- controls_flow_statements(inputs(FLOW: "CONTINUE/BREAK")) // Must go **inside** of a loop
23
 
24
  # Math
25
- math_number(inputs(NUM: value))
26
- math_arithmetic(inputs(OP: "ADD/MINUS/MULTIPLY/DIVIDE/POWER", A: value, B: value)) // A and B are required. They must be blocks, so you need to add math_number or an alternative
27
- math_single(inputs(OP: "ROOT/ABS/NEG/LN/LOG10/EXP/POW10", NUM: value)) // EXP is e^num
28
- math_number_property(inputs(PROPERTY: "EVEN/ODD/PRIME/WHOLE/POSITIVE/NEGATIVE", NUMBER_TO_CHECK: value)) // Boolean output
29
- math_round(inputs(OP: "ROUND/ROUNDUP/ROUNDDOWN", NUM: value))
30
- math_modulo(inputs(DIVIDEND: value, DIVISOR: value))
31
- math_constrain(VALUE: value, LOW: value, HIGH: value)
32
- math_random_int(inputs(FROM: value, TO: value))
33
 
34
  # Text
35
- text(inputs(TEXT: value))
36
- text_length(VALUE: value)
37
- text_join(inputs(ADDN: value)) // N starts at 0; you can make as many N as you want
38
- text_isEmpty(inputs(VALUE: value)) // Boolean output
39
- text_changeCase(inputs(CASE: "UPPERCASE/LOWERCASE/TITLECASE", TEXT: value))
40
- text_getSubstring(inputs(WHERE1: "FROM_START/FROM_END/FIRST", WHERE2: "FROM_START/FROM_END/FIRST", STRING: value, AT1: value, AT2: value)) // For AT1 and AT2, only use them if the associated WHEREN is not "FROM_START"
41
- text_reverse(inputs(TEXT: value))
42
- text_count(inputs(SUB: value, TEXT: value)) // Integer output
43
- text_replace(inputs(FROM: value, TO: value, TEXT: value))
44
- text_trim(inputs(MODE: "BOTH/LEFT/RIGHT", TEXT: value)) // Trim spaces from text
45
 
46
  # Lists
47
- lists_length(inputs(VALUE: value))
48
- lists_isEmpty(inputs(VALUE: value)) // Boolean output
49
- lists_indexOf(inputs(END: "FIRST/LAST", VALUE: value, FIND: value))
50
- lists_reverse(inputs(LIST: value))
51
- lists_create_with(inputs(ADDN: value)) // N starts at 0; you can make as many N as you want
52
- lists_sort(inputs(TYPE: "NUMERIC/TEXT/IGNORE_CASE", DIRECTION: "1/-1")) // For direction, 1 is ascending, -1 is descending
53
 
54
  # Variables
55
- variables_get(inputs(VAR: value)) // VAR is a variable ID
56
- variables_set(inputs(VAR: value, VALUE: value)) // VAR is a variable ID
57
- math_change(inputs(VAR: value, DELTA: value)) // VAR is a variable ID. To use this, you MUST have used `variables_set` before this
58
 
59
  # MCP Block inputs
60
- input_reference_NAME(inputs()) // Replace "NAME" with the name of the MCP block input. Any inputs for the one MCP block can have their blocks made with this. You may ONLY use this if the MCP has an input with this name. If needed you must create inputs and outputs first for the MCP block
 
1
  # AI
2
+ VALUE: llm_call(inputs(MODEL: "gpt-3.5-turbo-0125/gpt-4o-2024-08-06/gpt-5-mini-2025-08-07/gpt-5-2025-08-07/gpt-4o-search-preview-2025-03-11", PROMPT: value))
3
 
4
  # JSON and API
5
+ VALUE: in_json(inputs(NAME: value, JSON: value)) // NAME is the value you want to extract from the JSON
6
+ VALUE: make_json(inputs(KEYN: value, FIELDN: value)) // N starts at 0; you can make as many N as you want. Each key goes with one field
7
+ VALUE: call_api(inputs(METHOD: "GET/POST/PUT/DELETE", URL: value, HEADERS: value)) // HEADERS is optional
8
 
9
  # Logic
10
+ STATEMENT: controls_if(inputs(IF: value, IFELSEN0: value, ELSE)) // IF is REQUIRED (the condition). IFELSEN0, IFELSEN1, etc are OPTIONAL (additional else-if conditions). ELSE is OPTIONAL (no value needed, just include the word). DO NOT use input_name with controls_if creation; specify all conditions in the inputs. After creating, use input_name to place statements: "DO0" (IF then-statements), "DO1" (first ELSE-IF then-statements), "ELSE" (final else statements)
11
+ VALUE: logic_negate(inputs())
12
+ VALUE: logic_boolean(inputs(BOOL: "TRUE/FALSE"))
13
+ VALUE: logic_null(inputs())
14
+ VALUE: logic_compare(inputs(OP: "EQ/NEQ/LT/LTE/GT/GTE", A: value, B: value))
15
+ VALUE: logic_operation(inputs(OP: "AND/OR", A: value, B: value))
16
 
17
  # Loops
18
+ STATEMENT: controls_repeat_ext(inputs(TIMES: value))
19
+ STATEMENT: controls_whileUntil(inputs(MODE: "WHILE/UNTIL", BOOL: value))
20
+ STATEMENT: controls_for(inputs(VAR: value, FROM: value, TO: value, BY: value)) // VAR is a variable ID. BY is the increment amount
21
+ STATEMENT: controls_forEach(inputs(VAR: value), LIST: value) // VAR is a variable ID
22
+ STATEMENT: controls_flow_statements(inputs(FLOW: "CONTINUE/BREAK")) // Must go **inside** of a loop
23
 
24
  # Math
25
+ VALUE: math_number(inputs(NUM: value))
26
+ VALUE: math_arithmetic(inputs(OP: "ADD/MINUS/MULTIPLY/DIVIDE/POWER", A: value, B: value)) // A and B are required. They must be blocks, so you need to add math_number or an alternative
27
+ VALUE: math_single(inputs(OP: "ROOT/ABS/NEG/LN/LOG10/EXP/POW10", NUM: value)) // EXP is e^num
28
+ VALUE: math_number_property(inputs(PROPERTY: "EVEN/ODD/PRIME/WHOLE/POSITIVE/NEGATIVE", NUMBER_TO_CHECK: value)) // Boolean output
29
+ VALUE: math_round(inputs(OP: "ROUND/ROUNDUP/ROUNDDOWN", NUM: value))
30
+ VALUE: math_modulo(inputs(DIVIDEND: value, DIVISOR: value))
31
+ VALUE: math_constrain(VALUE: value, LOW: value, HIGH: value)
32
+ VALUE: math_random_int(inputs(FROM: value, TO: value))
33
 
34
  # Text
35
+ VALUE: text(inputs(TEXT: value))
36
+ VALUE: text_length(VALUE: value)
37
+ VALUE: text_join(inputs(ADDN: value)) // N starts at 0; you can make as many N as you want
38
+ VALUE: text_isEmpty(inputs(VALUE: value)) // Boolean output
39
+ VALUE: text_changeCase(inputs(CASE: "UPPERCASE/LOWERCASE/TITLECASE", TEXT: value))
40
+ VALUE: text_getSubstring(inputs(WHERE1: "FROM_START/FROM_END/FIRST", WHERE2: "FROM_START/FROM_END/FIRST", STRING: value, AT1: value, AT2: value)) // For AT1 and AT2, only use them if the associated WHEREN is not "FROM_START"
41
+ VALUE: text_reverse(inputs(TEXT: value))
42
+ VALUE: text_count(inputs(SUB: value, TEXT: value)) // Integer output
43
+ VALUE: text_replace(inputs(FROM: value, TO: value, TEXT: value))
44
+ VALUE: text_trim(inputs(MODE: "BOTH/LEFT/RIGHT", TEXT: value)) // Trim spaces from text
45
 
46
  # Lists
47
+ VALUE: lists_length(inputs(VALUE: value))
48
+ VALUE: lists_isEmpty(inputs(VALUE: value)) // Boolean output
49
+ VALUE: lists_indexOf(inputs(END: "FIRST/LAST", VALUE: value, FIND: value))
50
+ VALUE: lists_reverse(inputs(LIST: value))
51
+ VALUE: lists_create_with(inputs(ADDN: value)) // N starts at 0; you can make as many N as you want
52
+ VALUE: lists_sort(inputs(TYPE: "NUMERIC/TEXT/IGNORE_CASE", DIRECTION: "1/-1")) // For direction, 1 is ascending, -1 is descending
53
 
54
  # Variables
55
+ VALUE: variables_get(inputs(VAR: value)) // VAR is a variable ID
56
+ VALUE: variables_set(inputs(VAR: value, VALUE: value)) // VAR is a variable ID
57
+ VALUE: math_change(inputs(VAR: value, DELTA: value)) // VAR is a variable ID. To use this, you MUST have used `variables_set` before this
58
 
59
  # MCP Block inputs
60
+ VALUE: input_reference_NAME(inputs()) // Replace "NAME" with the name of the MCP block input. Any inputs for the one MCP block can have their blocks made with this. You may ONLY use this if the MCP has an input with this name. If needed you must create inputs and outputs first for the MCP block
project/chat.py CHANGED
@@ -548,13 +548,13 @@ def create_gradio_interface():
548
  SYSTEM_PROMPT = f"""You are an AI assistant that helps users build **MCP servers** using Blockly blocks.
549
 
550
  You'll receive the workspace state in this format:
551
- `blockId | block_name(inputs(input_name: value))`
552
 
553
- Block ID parsing: Block IDs are everything before ` | ` (space-pipe-space). IDs are always complex/long strings.
554
- Example: `?fHZRh^|us|9bECO![$= | text(inputs(TEXT: "hello"))`, ID is `?fHZRh^|us|9bECO![$=`
555
 
556
  Special cases:
557
- - `create_mcp` and `func_def` use `blockId | block_name(inputs(input_name: type), outputs(output_name: value))`
558
  - Indentation or nesting shows logic hierarchy (like loops or conditionals).
559
 
560
  ---
@@ -579,11 +579,11 @@ def create_gradio_interface():
579
  ---
580
 
581
  ### Deleting Blocks
582
- - Each block starts with its ID, like `blockId | block_name(...)`.
583
  - To delete a block, specify its block ID.
584
  - Any block except the main `create_mcp` block can be deleted.
585
 
586
- `blockId | code`
587
 
588
  Use the exact ID from the workspace.
589
 
@@ -597,16 +597,9 @@ def create_gradio_interface():
597
  You cannot create a `create_mcp` block, but you may edit its inputs using the `edit_mcp` tool.
598
 
599
  ### Block Types: Statement vs Value
600
- **Statement blocks** are containers that hold other blocks inside them. They can have blocks above or below them:
601
- - Loops (repeat, while, for)
602
- - Conditionals (if/else)
603
- - Any block that wraps other blocks
604
 
605
- **Value blocks** produce a value and plug into another block's input:
606
- - Math blocks (math_number, math_arithmetic)
607
- - Text blocks (text, text_join)
608
- - Logic blocks (logic_compare, logic_operation)
609
- - Any block that outputs a result for something else to use
610
 
611
  ### How to Place Blocks
612
 
@@ -636,21 +629,20 @@ def create_gradio_interface():
636
  - `input_name: "DO1"`: first ELSE-IF branch
637
  - `input_name: "ELSE"`: ELSE branch
638
 
 
 
639
  **Placement types** - use `blockID` and `type` parameters:
640
 
641
  - `type: "under"` - For statement blocks inside containers. Create the container first, then create statement blocks using the container's ID.
642
  Example: Create a loop first, then use `blockID: loopID, type: "under"` to place code inside it.
643
- Also for stackable blocks: create one, get its ID, then create the next one with the previous block's ID and `type: "under"`.
644
- **Optional: use `input_name` to specify which statement input to place the block in (e.g., "DO0", "DO1", "ELSE" for IF blocks).**
645
- Example: `create_block(text_append(...), blockID: ifBlockID, type: "under", input_name: "DO0")` places the block in the first THEN branch of an IF block.
646
- **CRITICAL: When using `type: "under"`, `input_name` must ONLY contain "DO0", "DO1", "ELSE", or other statement input names. NEVER use R0, R1, R2, etc. with `type: "under"`.**
647
 
648
- - `type: "input"` - ONLY for value blocks placed in MCP output slots. Provide `input_name` with the output slot name (R0, R1, R2, etc).
649
  Example: `text(inputs(TEXT: "hello"))` with `type: "input", input_name: "R0"` places the text block in the MCP's first output slot.
650
- **CRITICAL:** NEVER use `type: "input"` and `input_name` for standalone value expressions that aren't being assigned to an MCP output. Only use these parameters
651
- if the workspace shows the create_mcp block has explicit outputs defined (you will see `outputs(...)` in the state). If you cannot see outputs defined in the MCP
652
- block, do NOT use `type: "input"` at all.
653
- **CRITICAL: NEVER combine `type: "under"` with `input_name: "R0"`, `"R1"`, `"R2"`, etc. These R-numbered slots are only for `type: "input"` when placing into MCP outputs.**
654
 
655
  **Value block nesting** - For value blocks inside other blocks: nest them directly in the create_block command (do not use `blockID` or `type`).
656
  Example: `math_arithmetic(inputs(A: math_number(inputs(NUM: 5)), B: math_number(inputs(NUM: 3))))`
@@ -671,6 +663,10 @@ def create_gradio_interface():
671
  - WRONG: `math_single(inputs(OP: ROOT, NUM: value)`
672
  - CORRECT: `math_single(inputs(OP: "ROOT", NUM: value)`
673
 
 
 
 
 
674
  For value inputs, never use raw values. Always wrap in a block:
675
  - WRONG: `math_arithmetic(inputs(A: 5, B: "hi"))`
676
  - RIGHT: `math_arithmetic(inputs(A: math_number(inputs(NUM: 5)), B: text(inputs(TEXT: "hi"))))`
@@ -682,7 +678,7 @@ def create_gradio_interface():
682
  ### Variables
683
  Variables appear as:
684
 
685
- `varId | varName`
686
 
687
  ---
688
 
@@ -721,6 +717,29 @@ def create_gradio_interface():
721
 
722
  ---
723
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
724
  Tool responses appear under the assistant role, but they are not part of your own words. Never treat tool output as
725
  something you said, and never fabricate or echo fake tool outputs.
726
 
@@ -738,20 +757,27 @@ def create_gradio_interface():
738
  This is for *your own* understanding: work out the logic *before* translating to blocks.
739
  Example: `for item in items: result = process(item); output = combine(result)`
740
 
741
- 3. **Create a build order**: which blocks are top-level, which nest inside others, which are value expressions that must be built in one call.
 
742
 
743
- 4. **Check the create_mcp block state:** Before using `type: "input"` and `input_name: "R0"`, verify that the create_mcp block has outputs defined in the workspace state. If you do not see `outputs(...)` in the create_mcp block, do NOT use these parameters.
 
 
 
 
 
744
 
745
- 5. **Understand MCP block placement:** When placing blocks under the MCP block itself (as opposed to inside its outputs):
746
- - Use `type: "under"` with the MCP's `blockID`
747
- - Do NOT use `input_name: "R0"`, `"R1"`, etc. (those are only for output slots)
748
- - The MCP block is a statement container, not an output slot
749
- - If you're defining what the MCP returns, you must either create output blocks inside the MCP, or edit the MCP block to add outputs first
750
 
751
- 6. Perform the actions in order without asking for approval or asking to wait for intermediate results.
 
752
 
753
- """
754
 
 
755
  tools = [
756
  {
757
  "type": "function",
@@ -775,13 +801,18 @@ def create_gradio_interface():
775
  "parameters": {
776
  "type": "object",
777
  "properties": {
 
 
 
 
 
778
  "command": {
779
  "type": "string",
780
  "description": "The create block command using the custom DSL format.",
781
  },
782
  "blockID": {
783
  "type": "string",
784
- "description": "The ID of the target block for placement.",
785
  },
786
  "type": {
787
  "type": "string",
 
548
  SYSTEM_PROMPT = f"""You are an AI assistant that helps users build **MCP servers** using Blockly blocks.
549
 
550
  You'll receive the workspace state in this format:
551
+ `blockId block_name(inputs(input_name: value))`
552
 
553
+ Block ID parsing: Block IDs are everything before ` ` (space-arrow-space). IDs are always complex/long strings.
554
+ Example: `?fHZRh^|us|9bECO![$= text(inputs(TEXT: "hello"))`, ID is `?fHZRh^|us|9bECO![$=`
555
 
556
  Special cases:
557
+ - `create_mcp` and `func_def` use `blockId block_name(inputs(input_name: type), outputs(output_name: value))`
558
  - Indentation or nesting shows logic hierarchy (like loops or conditionals).
559
 
560
  ---
 
579
  ---
580
 
581
  ### Deleting Blocks
582
+ - Each block starts with its ID, like `blockId block_name(...)`.
583
  - To delete a block, specify its block ID.
584
  - Any block except the main `create_mcp` block can be deleted.
585
 
586
+ `blockId code`
587
 
588
  Use the exact ID from the workspace.
589
 
 
597
  You cannot create a `create_mcp` block, but you may edit its inputs using the `edit_mcp` tool.
598
 
599
  ### Block Types: Statement vs Value
600
+ **Statement blocks** (loops, conditionals, any container) hold other blocks inside them and require sequential creation to get the container's ID first.
 
 
 
601
 
602
+ **Value blocks** (math, text, logic, comparison) produce values that plug into inputs and must be built entirely in one nested create_block call.
 
 
 
 
603
 
604
  ### How to Place Blocks
605
 
 
629
  - `input_name: "DO1"`: first ELSE-IF branch
630
  - `input_name: "ELSE"`: ELSE branch
631
 
632
+ YOU CANNOT EDIT THE IF BLOCK LATER. IF YOU WILL NEED TO HAVE AN ELSE OR IFELSE LATER, YOU MUST CREATE IT WITH ALL BRANCHES FROM THE START.
633
+
634
  **Placement types** - use `blockID` and `type` parameters:
635
 
636
  - `type: "under"` - For statement blocks inside containers. Create the container first, then create statement blocks using the container's ID.
637
  Example: Create a loop first, then use `blockID: loopID, type: "under"` to place code inside it.
638
+ Optional: use `input_name` for statement input names only (e.g., "DO0", "DO1", "ELSE" for IF blocks).
639
+ Example: `create_block(text_append(...), blockID: ifBlockID, type: "under", input_name: "DO0")` places the block in the IF branch.
 
 
640
 
641
+ - `type: "input"` - ONLY for value blocks placed in MCP output slots (R0, R1, R2, etc).
642
  Example: `text(inputs(TEXT: "hello"))` with `type: "input", input_name: "R0"` places the text block in the MCP's first output slot.
643
+ Requirement: The create_mcp block must have explicit outputs defined (you will see `outputs(...)` in the workspace state). Do not use this if outputs are not visible.
644
+
645
+ Key rule: Statement input names (DO0, DO1, ELSE) are for `type: "under"`. Output slot names (R0, R1, R2) are for `type: "input"`. Never mix them.
 
646
 
647
  **Value block nesting** - For value blocks inside other blocks: nest them directly in the create_block command (do not use `blockID` or `type`).
648
  Example: `math_arithmetic(inputs(A: math_number(inputs(NUM: 5)), B: math_number(inputs(NUM: 3))))`
 
663
  - WRONG: `math_single(inputs(OP: ROOT, NUM: value)`
664
  - CORRECT: `math_single(inputs(OP: "ROOT", NUM: value)`
665
 
666
+ **Variable IDs must always be quoted with double quotes**, even if they look like identifiers. Variable IDs often contain special characters like parentheses, pipes, and semicolons. ALWAYS quote the variable ID:
667
+ - WRONG: variables_set(inputs(VAR: ..., VALUE: ...))
668
+ - CORRECT: variables_set(inputs(VAR: "...", VALUE: ...))
669
+
670
  For value inputs, never use raw values. Always wrap in a block:
671
  - WRONG: `math_arithmetic(inputs(A: 5, B: "hi"))`
672
  - RIGHT: `math_arithmetic(inputs(A: math_number(inputs(NUM: 5)), B: text(inputs(TEXT: "hi"))))`
 
678
  ### Variables
679
  Variables appear as:
680
 
681
+ `varId varName`
682
 
683
  ---
684
 
 
717
 
718
  ---
719
 
720
+ ## EVERY VALUE MUST HAVE A DESTINATION
721
+
722
+ Before constructing ANY expression block (text_join, math operations, etc.), identify where it goes:
723
+ - Assigned to a variable (via set_var block)
724
+ - Passed as input to another block (nested in the create_block call)
725
+ - Placed in an MCP output slot (using type: "input" and input_name: "R#")
726
+
727
+ Do NOT create orphaned expression blocks with no destination. They serve no purpose.
728
+
729
+ Always build the container/assignment block FIRST, then construct the value expression INSIDE it, both in a single call.
730
+
731
+ ### No Early Returns in Conditionals
732
+
733
+ Blockly does not support early returns from within conditional branches. You MUST use a variable to store the result and return that variable in the MCP output:
734
+
735
+ 1. Create a variable
736
+ 2. In each branch, use a `set_var` block to assign the value to that variable. The VALUE input of set_var must contain your text_join or expression.
737
+ 3. Return the variable as an MCP output
738
+
739
+ Always collect results in a variable first; never create expression blocks that exist outside a set_var or MCP output.
740
+
741
+ ---
742
+
743
  Tool responses appear under the assistant role, but they are not part of your own words. Never treat tool output as
744
  something you said, and never fabricate or echo fake tool outputs.
745
 
 
757
  This is for *your own* understanding: work out the logic *before* translating to blocks.
758
  Example: `for item in items: result = process(item); output = combine(result)`
759
 
760
+ 3. **Create a detailed, step-by-step to-do list**:
761
+ List every action you will take, in exact order, with no omissions. Build from the outside in: create outer containers first, then add inner blocks inside them.
762
 
763
+ For every step, explicitly specify:
764
+ - What action you will perform
765
+ - Which block you are acting on
766
+ - Which input or output slot you are using
767
+ - All parameters involved (including type and input_name)
768
+ - Every variable used and where it is used
769
 
770
+ For every IF statement you create, you must explicitly state:
771
+ - The number of IFELSE branches (0, 1, 2, etc.)
772
+ - The number of ELSE branches (0 or 1)
773
+ - The exact condition that causes each branch to activate
 
774
 
775
+ THESE DECLARATIONS ARE REQUIRED EVERY TIME AN IF STATEMENT IS MENTIONED, AND YOU MUST ALWAYS PROVIDE EXACTLY THREE INTEGERS WITH NO EXCEPTIONS OR SUBSTITUTIONS. FAILURE TO DO SO IMMEDIATELY INVALIDATES THE RESPONSE IN ITS ENTIRETY.
776
+ YOU MUST HAVE EXPLICITLY SAID THESE THREE VALUES NO MATTER WHAT. THIS IS NON-NEGOTIABLE. THIS IS A HARD REQUIREMENT. ALWAYS SAY THIS, EVERY SINGLE TIME, NO MATTER WHAT.
777
 
778
+ 4. **Check the create_mcp block state:** Before using `type: "input"` and `input_name: "R0"`, verify that the create_mcp block has outputs defined in the workspace state. If you do not see `outputs(...)` in the create_mcp block, do NOT use these parameters.
779
 
780
+ 5. Perform the actions in order without asking for approval or asking to wait for intermediate results."""
781
  tools = [
782
  {
783
  "type": "function",
 
801
  "parameters": {
802
  "type": "object",
803
  "properties": {
804
+ # Throwaway parameter to get the agent to think about IF rules.
805
+ "if_notes": {
806
+ "type": "string",
807
+ "description": "If you are going to make an if statement, YOU ARE REQUIRED TO USE THIS. Write the amount if IF/IFELSE/ELSE you will need.",
808
+ },
809
  "command": {
810
  "type": "string",
811
  "description": "The create block command using the custom DSL format.",
812
  },
813
  "blockID": {
814
  "type": "string",
815
+ "description": "The ID of the target block for placement. Do not use this if your goal is to place a block detached in the workspace.",
816
  },
817
  "type": {
818
  "type": "string",
project/src/generators/chat.js CHANGED
@@ -70,8 +70,8 @@ forBlock['create_mcp'] = function (block, generator) {
70
  let body = generator.statementToCode(block, 'BODY');
71
 
72
  // Construct the create_mcp call with inputs and outputs
73
- // Include block ID for deletion tracking with pipe separator
74
- let code = `${block.id} | create_mcp(inputs(${inputParams.join(', ')}), outputs(${outputParams.join(', ')}))`
75
 
76
  // Add the function body
77
  if (body) {
@@ -120,8 +120,8 @@ forBlock['func_def'] = function (block, generator) {
120
  let body = generator.statementToCode(block, 'BODY');
121
 
122
  // Construct the func_def call with inputs and outputs
123
- // Include block ID for deletion tracking with pipe separator
124
- let code = `${block.id} | ${name}(inputs(${inputParams.join(', ')}), outputs(${outputParams.join(', ')}))`
125
 
126
  // Add the function body
127
  if (body) {
@@ -257,7 +257,7 @@ chatGenerator.blockToCode = function (block, opt_thisOnly) {
257
  }
258
 
259
  // Generate the controls_if call with conditions
260
- let code = `${block.id} | ${blockType}(inputs(${inputs.join(', ')}))`;
261
 
262
  // Now get all the statement blocks with proper formatting
263
  // DO0, DO1, etc. are indented under the if
@@ -326,8 +326,8 @@ chatGenerator.blockToCode = function (block, opt_thisOnly) {
326
  }
327
  }
328
 
329
- // Generate the standard format: name(inputs(...)) with block ID and pipe separator
330
- const code = `${block.id} | ${blockType}(inputs(${inputs.join(', ')}))`;
331
 
332
  // Handle statement inputs (for blocks that have a body)
333
  let statements = '';
@@ -359,7 +359,7 @@ chatGenerator.blockToCode = function (block, opt_thisOnly) {
359
  return [valueCode, this.ORDER_ATOMIC];
360
  } else {
361
  // When standalone (not connected), include the ID
362
- const standaloneCode = `${block.id} | ${blockType}(inputs(${inputs.join(', ')}))`;
363
  // For standalone value blocks, we need to return them as statement-like
364
  // but still maintain the value block return format for Blockly
365
  return [standaloneCode, this.ORDER_ATOMIC];
 
70
  let body = generator.statementToCode(block, 'BODY');
71
 
72
  // Construct the create_mcp call with inputs and outputs
73
+ // Include block ID for deletion tracking with arrow separator
74
+ let code = `${block.id} create_mcp(inputs(${inputParams.join(', ')}), outputs(${outputParams.join(', ')}))`
75
 
76
  // Add the function body
77
  if (body) {
 
120
  let body = generator.statementToCode(block, 'BODY');
121
 
122
  // Construct the func_def call with inputs and outputs
123
+ // Include block ID for deletion tracking with arrow separator
124
+ let code = `${block.id} ${name}(inputs(${inputParams.join(', ')}), outputs(${outputParams.join(', ')}))`
125
 
126
  // Add the function body
127
  if (body) {
 
257
  }
258
 
259
  // Generate the controls_if call with conditions
260
+ let code = `${block.id} ${blockType}(inputs(${inputs.join(', ')}))`;
261
 
262
  // Now get all the statement blocks with proper formatting
263
  // DO0, DO1, etc. are indented under the if
 
326
  }
327
  }
328
 
329
+ // Generate the standard format: name(inputs(...)) with block ID and arrow separator
330
+ const code = `${block.id} ${blockType}(inputs(${inputs.join(', ')}))`;
331
 
332
  // Handle statement inputs (for blocks that have a body)
333
  let statements = '';
 
359
  return [valueCode, this.ORDER_ATOMIC];
360
  } else {
361
  // When standalone (not connected), include the ID
362
+ const standaloneCode = `${block.id} ${blockType}(inputs(${inputs.join(', ')}))`;
363
  // For standalone value blocks, we need to return them as statement-like
364
  // but still maintain the value block return format for Blockly
365
  return [standaloneCode, this.ORDER_ATOMIC];
project/src/index.js CHANGED
@@ -397,7 +397,7 @@ const setupUnifiedStream = () => {
397
 
398
  try {
399
  // Parse and create blocks recursively
400
- function parseAndCreateBlock(spec, shouldPosition = false) {
401
  // Match block_name(inputs(...)) with proper parenthesis matching
402
  const blockMatch = spec.match(/^(\w+)\s*\((.*)$/s);
403
 
@@ -503,6 +503,93 @@ const setupUnifiedStream = () => {
503
 
504
  console.log('[SSE CREATE] inputsContent to parse:', inputsContent);
505
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
506
  // Create the block
507
  const newBlock = ws.newBlock(blockType);
508
 
@@ -573,6 +660,28 @@ const setupUnifiedStream = () => {
573
  }
574
  }
575
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
576
  } else if (blockType === 'controls_if') {
577
  // Special handling for if/else blocks
578
  // IF is required, IFELSEN0, IFELSEN1, etc are optional, ELSE is optional
@@ -723,6 +832,40 @@ const setupUnifiedStream = () => {
723
  }
724
  }
725
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
726
  // Only position the top-level block
727
  if (shouldPosition) {
728
  // Find a good position that doesn't overlap existing blocks
@@ -849,7 +992,7 @@ const setupUnifiedStream = () => {
849
  }
850
 
851
  // Create the block and all its nested children
852
- const newBlock = parseAndCreateBlock(data.block_spec, true);
853
 
854
  if (newBlock) {
855
  blockId = newBlock.id;
@@ -1131,7 +1274,7 @@ const updateCode = () => {
1131
  }
1132
 
1133
  const vars = ws.getVariableMap().getAllVariables();
1134
- globalVarString = vars.map(v => `${v.id} | ${v.name}`).join("\n");
1135
 
1136
  const codeEl = document.querySelector('#generatedCode code');
1137
 
 
397
 
398
  try {
399
  // Parse and create blocks recursively
400
+ function parseAndCreateBlock(spec, shouldPosition = false, placementType = null, placementBlockID = null) {
401
  // Match block_name(inputs(...)) with proper parenthesis matching
402
  const blockMatch = spec.match(/^(\w+)\s*\((.*)$/s);
403
 
 
503
 
504
  console.log('[SSE CREATE] inputsContent to parse:', inputsContent);
505
 
506
+ // VALIDATION: Check for malformed input - if inputsContent contains unmatched parentheses
507
+ // If there are missing closing parentheses (1-2), automatically add them
508
+ let validateParenCount = 0;
509
+ let validateQuotes = false;
510
+ let validateQuoteChar = '';
511
+ for (let i = 0; i < inputsContent.length; i++) {
512
+ const char = inputsContent[i];
513
+ if ((char === '"' || char === "'") && (i === 0 || inputsContent[i - 1] !== '\\')) {
514
+ if (!validateQuotes) {
515
+ validateQuotes = true;
516
+ validateQuoteChar = char;
517
+ } else if (char === validateQuoteChar) {
518
+ validateQuotes = false;
519
+ }
520
+ }
521
+ if (!validateQuotes) {
522
+ if (char === '(') validateParenCount++;
523
+ else if (char === ')') validateParenCount--;
524
+ }
525
+ }
526
+
527
+ // If parentheses are unbalanced, try to fix by adding closing parentheses
528
+ if (validateParenCount > 0) {
529
+ if (validateParenCount <= 2) {
530
+ console.log(`[SSE CREATE] Auto-fixing ${validateParenCount} missing closing parentheses in block '${blockType}'`);
531
+
532
+ // Smart insertion: find the best place to insert closing parentheses
533
+ // Look for the last comma at depth 0 (which separates key-value pairs)
534
+ let bestInsertPos = inputsContent.length; // Default to end
535
+ let depth = 0;
536
+ let inQuotes = false;
537
+ let quoteChar = '';
538
+
539
+ for (let i = inputsContent.length - 1; i >= 0; i--) {
540
+ const char = inputsContent[i];
541
+
542
+ // Handle quotes (from right to left)
543
+ if ((char === '"' || char === "'") && (i === 0 || inputsContent[i - 1] !== '\\')) {
544
+ if (!inQuotes) {
545
+ inQuotes = true;
546
+ quoteChar = char;
547
+ } else if (char === quoteChar) {
548
+ inQuotes = false;
549
+ }
550
+ }
551
+
552
+ // Handle parentheses (from right to left, so reverse the logic)
553
+ if (!inQuotes) {
554
+ if (char === ')') depth++;
555
+ else if (char === '(') depth--;
556
+ }
557
+
558
+ // Found the last comma at depth 0 - this separates the last nested block from following keys
559
+ if (char === ',' && depth === 0 && !inQuotes) {
560
+ bestInsertPos = i;
561
+ break;
562
+ }
563
+ }
564
+
565
+ // Insert the closing parentheses at the best position
566
+ if (bestInsertPos < inputsContent.length && inputsContent[bestInsertPos] === ',') {
567
+ // Insert right before the comma
568
+ inputsContent = inputsContent.slice(0, bestInsertPos) + ')'.repeat(validateParenCount) + inputsContent.slice(bestInsertPos);
569
+ } else {
570
+ // Fallback: insert at end
571
+ inputsContent += ')'.repeat(validateParenCount);
572
+ }
573
+ } else {
574
+ throw new Error(`Malformed block specification for '${blockType}': too many unmatched opening parentheses (${validateParenCount}). This usually means a nested block specification is malformed. Received: ${inputsContent}`);
575
+ }
576
+ } else if (validateParenCount < 0) {
577
+ throw new Error(`Malformed block specification for '${blockType}': more closing parentheses than opening. Received: ${inputsContent}`);
578
+ }
579
+
580
+ // VALIDATION: Check if trying to place a value block under a statement block
581
+ // Value blocks have an output connection but no previous connection
582
+ if (placementType === 'under') {
583
+ // Check if this block type is a value block by temporarily creating it
584
+ const testBlock = ws.newBlock(blockType);
585
+ const isValueBlock = testBlock.outputConnection && !testBlock.previousConnection;
586
+ testBlock.dispose(true); // Remove the test block
587
+
588
+ if (isValueBlock) {
589
+ throw new Error(`Cannot place value block '${blockType}' under a statement block. Value blocks must be nested inside inputs of other blocks or placed in MCP outputs using type: "input".`);
590
+ }
591
+ }
592
+
593
  // Create the block
594
  const newBlock = ws.newBlock(blockType);
595
 
 
660
  }
661
  }
662
  }
663
+ } else if (blockType === 'text_join') {
664
+ // Special handling for text_join block (and similar blocks with ADD0, ADD1, ADD2...)
665
+ // Count ADD entries to determine how many items we need
666
+ let addCount = 0;
667
+ const addValues = {};
668
+
669
+ for (const [key, value] of Object.entries(inputs)) {
670
+ const addMatch = key.match(/^ADD(\d+)$/);
671
+ if (addMatch) {
672
+ const index = parseInt(addMatch[1]);
673
+ addCount = Math.max(addCount, index + 1);
674
+ addValues[index] = value;
675
+ }
676
+ }
677
+
678
+ console.log('[SSE CREATE] text_join detected with', addCount, 'items');
679
+
680
+ // Store pending text_join state to apply after initSvg()
681
+ if (addCount > 0) {
682
+ newBlock.pendingAddCount_ = addCount;
683
+ newBlock.pendingAddValues_ = addValues;
684
+ }
685
  } else if (blockType === 'controls_if') {
686
  // Special handling for if/else blocks
687
  // IF is required, IFELSEN0, IFELSEN1, etc are optional, ELSE is optional
 
832
  }
833
  }
834
 
835
+ // Apply pending text_join mutations (must be after initSvg)
836
+ if (newBlock.type === 'text_join' && newBlock.pendingAddCount_ && newBlock.pendingAddCount_ > 0) {
837
+ console.log('[SSE CREATE] Applying text_join mutation with', newBlock.pendingAddCount_, 'items');
838
+
839
+ const addCount = newBlock.pendingAddCount_;
840
+ const addValues = newBlock.pendingAddValues_;
841
+
842
+ // Use loadExtraState if available to set the item count
843
+ if (typeof newBlock.loadExtraState === 'function') {
844
+ newBlock.loadExtraState({ itemCount: addCount });
845
+ } else {
846
+ // Fallback: set internal state
847
+ newBlock.itemCount_ = addCount;
848
+ }
849
+
850
+ // Now connect the ADD values
851
+ for (let i = 0; i < addCount; i++) {
852
+ const value = addValues[i];
853
+ if (value && typeof value === 'string' && value.match(/^\w+\s*\(inputs\(/)) {
854
+ // This is a nested block, create it recursively
855
+ const childBlock = parseAndCreateBlock(value);
856
+
857
+ // Connect the child block to the ADD input
858
+ const input = newBlock.getInput('ADD' + i);
859
+ if (input && input.connection && childBlock.outputConnection) {
860
+ childBlock.outputConnection.connect(input.connection);
861
+ console.log('[SSE CREATE] Connected ADD' + i + ' input');
862
+ } else {
863
+ console.warn('[SSE CREATE] Could not connect ADD' + i + ' input');
864
+ }
865
+ }
866
+ }
867
+ }
868
+
869
  // Only position the top-level block
870
  if (shouldPosition) {
871
  // Find a good position that doesn't overlap existing blocks
 
992
  }
993
 
994
  // Create the block and all its nested children
995
+ const newBlock = parseAndCreateBlock(data.block_spec, true, data.placement_type, data.blockID);
996
 
997
  if (newBlock) {
998
  blockId = newBlock.id;
 
1274
  }
1275
 
1276
  const vars = ws.getVariableMap().getAllVariables();
1277
+ globalVarString = vars.map(v => `${v.id} ${v.name}`).join("\n");
1278
 
1279
  const codeEl = document.querySelector('#generatedCode code');
1280