1212 * the shell ever writes any. So the server parses `execute-action` against the
1313 * one grammar the proxy emits:
1414 *
15- * return await tools.<ident >("<role>")?(.<ident>) *(<JSON>)
15+ * return await tools<segment >("<role>")?<segment> *(<JSON>)
1616 *
1717 * One awaited tool call, one JSON-literal argument, nothing else — no
1818 * statements, no loops, no composition. `execute` (the model-facing codemode
1919 * tool) is untouched; this constraint is only for the app-originated channel.
2020 *
21- * The leading identifier is an INTEGRATION, not a connection: artifact paths
21+ * The leading segment is an INTEGRATION, not a connection: artifact paths
2222 * carry no tier and no connection name (see `artifact-bindings.ts`). The
2323 * optional string call right after it is the integration ROLE, which is how an
2424 * artifact using two accounts of one integration says which it means. Both are
3232
3333import { Option , Schema } from "effect" ;
3434
35- const TOOL_CALL_CODE =
36- / ^ r e t u r n a w a i t t o o l s \. ( [ A - Z a - z _ $ ] [ \w $ ] * ) (?: \( ( ( " (?: [ ^ " \\ ] | \\ .) * " ) ) \) ) ? ( (?: \. [ A - Z a - z _ $ ] [ \w $ ] * ) * ) \( ( .* ) \) ; ? $ / s;
35+ const JSON_STRING_LITERAL = String . raw `"(?:[^"\\]|\\.)*"` ;
36+ const IDENTIFIER = String . raw `[A-Za-z_$][\w$]*` ;
37+ const SLUG = String . raw `[A-Za-z_$][\w$-]*` ;
38+ const PATH_SEGMENT = String . raw `(?:\.${ IDENTIFIER } |\["${ SLUG } "\])` ;
39+ const TOOL_CALL_CODE = new RegExp (
40+ String . raw `^return await tools(${ PATH_SEGMENT } )(?:\((${ JSON_STRING_LITERAL } )\))?((?:${ PATH_SEGMENT } )*)\((.*)\);?$` ,
41+ "s" ,
42+ ) ;
43+ const PATH_SEGMENT_MATCHER = new RegExp ( String . raw `(?:\.(${ IDENTIFIER } )|\["(${ SLUG } )"\])` , "g" ) ;
3744
3845/** The proxy's argument is always `JSON.stringify` output, so anything that
3946 * does not decode is, by construction, not something the proxy emitted. */
4047const decodeArgs = Schema . decodeUnknownOption ( Schema . fromJsonString ( Schema . Unknown ) ) ;
4148const decodeRole = Schema . decodeUnknownOption ( Schema . fromJsonString ( Schema . String ) ) ;
4249
50+ const decodePath = ( serialized : string ) : readonly string [ ] =>
51+ Array . from ( serialized . matchAll ( PATH_SEGMENT_MATCHER ) , ( match ) => match [ 1 ] ?? match [ 2 ] ?? "" ) ;
52+
4353export type ParsedToolCall = {
44- /** The dotted path segments under `tools`, e.g. `["github", "issues", "create"]`.
54+ /** The path segments under `tools`, e.g. `["github", "issues", "create"]`.
4555 * The head is an integration slug (or a system-tool root); it is never a
4656 * tier or a connection name. */
4757 readonly path : readonly string [ ] ;
@@ -56,7 +66,7 @@ export type ParsedToolCall = {
5666/** The message handed back to the iframe when its code is not a tool call. */
5767export const TOOL_CALL_CONTRACT_MESSAGE = [
5868 "execute-action accepts a single tool call, not arbitrary code." ,
59- 'The only accepted form is `return await tools. <integration>("<role>")?. <path>(<json>)` —' ,
69+ 'The only accepted form is `return await tools<integration>("<role>")?<path>(<json>)` —' ,
6070 "exactly what the shell's `tools.*` proxy emits." ,
6171 "Interactive UI reaches integrations declaratively:" ,
6272 "`tools.<integration>.<tool>.queryOptions(...)` / `.infiniteQueryOptions(...)` for reads," ,
@@ -72,14 +82,14 @@ export const parseToolCallCode = (code: string): ParsedToolCall | null => {
7282 const match = TOOL_CALL_CODE . exec ( code . trim ( ) ) ;
7383 if ( ! match ) return null ;
7484
75- const [ , root , serializedRole , , dottedRest , serializedArgs ] = match ;
76- if ( root === undefined || dottedRest === undefined || serializedArgs === undefined ) return null ;
85+ const [ , root , serializedRole , serializedRest , serializedArgs ] = match ;
86+ if ( root === undefined || serializedRest === undefined || serializedArgs === undefined )
87+ return null ;
7788
7889 const args = decodeArgs ( serializedArgs ) ;
7990 if ( Option . isNone ( args ) ) return null ;
8091
81- const rest = dottedRest . length > 0 ? dottedRest . slice ( 1 ) . split ( "." ) : [ ] ;
82- const path = [ root , ...rest ] ;
92+ const path = decodePath ( `${ root } ${ serializedRest } ` ) ;
8393
8494 if ( serializedRole === undefined ) return { path, args : args . value } ;
8595
@@ -91,7 +101,11 @@ export const parseToolCallCode = (code: string): ParsedToolCall | null => {
91101 return { path, role : role . value , args : args . value } ;
92102} ;
93103
94- const TOOL_PATH_SEGMENT = / ^ [ A - Z a - z _ $ ] [ \w $ ] * $ / ;
104+ const TOOL_PATH_IDENTIFIER = / ^ [ A - Z a - z _ $ ] [ \w $ ] * $ / ;
105+ const TOOL_PATH_SEGMENT = / ^ [ A - Z a - z _ $ ] [ \w $ - ] * $ / ;
106+
107+ const formatToolPathSegment = ( segment : string ) : string =>
108+ TOOL_PATH_IDENTIFIER . test ( segment ) ? `.${ segment } ` : `[${ JSON . stringify ( segment ) } ]` ;
95109
96110/**
97111 * Build the codemode call for a RESOLVED address — the full
@@ -115,5 +129,5 @@ export const formatToolCallCode = (path: readonly string[], args: unknown): stri
115129 throw new Error ( "Invalid resolved tool path." ) ;
116130 }
117131 }
118- return `return await tools. ${ path . join ( ". " ) } (${ JSON . stringify ( args ?? { } ) } )` ;
132+ return `return await tools${ path . map ( formatToolPathSegment ) . join ( "" ) } (${ JSON . stringify ( args ?? { } ) } )` ;
119133} ;
0 commit comments