@@ -9,6 +9,44 @@ import { getTrigger } from '@/triggers'
99/** Reviewers can be named individually or by team slug; either identifies the request. */
1010const REVIEWER_FIELD = [ 'reviewers' , 'team_reviewers' ] as const
1111
12+ /**
13+ * Block subBlock ids that differ from the tool param they feed, each scoped to
14+ * the operations whose tool declares that target. `sort` has two sources and
15+ * `title`/`description`/`state` share their names with fields on other
16+ * operations, so the scoping is what keeps them from colliding.
17+ *
18+ * `toBoolean` marks a dropdown feeding a boolean tool param: a dropdown stores
19+ * its option id, so the value arrives as the string 'true'/'false' and the
20+ * generic handler only JSON-parses `json`/`array` inputs.
21+ */
22+ const GITHUB_PARAM_ALIASES : ReadonlyArray < {
23+ from : string
24+ to : string
25+ operations : readonly string [ ]
26+ toBoolean ?: true
27+ } > = [
28+ {
29+ from : 'reaction_content' ,
30+ to : 'content' ,
31+ operations : [ 'github_create_issue_reaction' , 'github_create_comment_reaction' ] ,
32+ } ,
33+ {
34+ from : 'milestone_title' ,
35+ to : 'title' ,
36+ operations : [ 'github_create_milestone' , 'github_update_milestone' ] ,
37+ } ,
38+ {
39+ from : 'milestone_description' ,
40+ to : 'description' ,
41+ operations : [ 'github_create_milestone' , 'github_update_milestone' ] ,
42+ } ,
43+ { from : 'milestone_state' , to : 'state' , operations : [ 'github_list_milestones' ] } ,
44+ { from : 'milestone_sort' , to : 'sort' , operations : [ 'github_list_milestones' ] } ,
45+ { from : 'fork_name' , to : 'name' , operations : [ 'github_fork_repo' ] } ,
46+ { from : 'fork_sort' , to : 'sort' , operations : [ 'github_list_forks' ] } ,
47+ { from : 'gist_public' , to : 'public' , operations : [ 'github_create_gist' ] , toBoolean : true } ,
48+ ]
49+
1250export const GitHubBlock : BlockConfig < GitHubResponse > = {
1351 type : 'github' ,
1452 name : 'GitHub (Legacy)' ,
@@ -2284,53 +2322,39 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`,
22842322 *
22852323 * A tool param is populated only when a subBlock's `id` equals it — the
22862324 * serializer keys values by subBlock id, and nothing else renames them.
2287- * Each field below renders, accepts input, and then arrives under a name
2288- * its tool never reads.
2325+ * Each aliased field below renders, accepts input, and then arrives under
2326+ * a name its tool never reads.
2327+ *
2328+ * Every alias is scoped to the operations whose tool actually declares
2329+ * the target param, and that scoping is load-bearing. Seven of these
2330+ * sources are `mode: 'advanced'`, and `shouldSerializeSubBlock`
2331+ * (`serializer/index.ts:91-93`) serializes a non-empty advanced field
2332+ * WITHOUT evaluating its condition. So a `milestone_title` left over from
2333+ * an earlier operation is still in `params` after the user switches to,
2334+ * say, Update PR — and an unscoped alias would rewrite it to `title` and
2335+ * clobber the PR's own title with stale milestone data.
22892336 *
2290- * Every assignment is guarded, and that is load-bearing rather than
2291- * defensive. `generic-handler.ts` merges `{ ...inputs, ...params(inputs) }`
2292- * and `providers/utils.ts` installs this same function as the provider
2293- * `paramsTransform`, spreading its result over the model's tool-call
2294- * arguments. An unconditional write would therefore clobber a
2295- * model-supplied `content`/`title`/`sort` with `undefined` on the agent
2296- * tool-calling path — which is the one path these fields work on today.
2337+ * Presence is tested rather than truthiness so that a deliberate `false`
2338+ * or `'false'` is not mistaken for an unset field; only nullish and empty
2339+ * defer to the tool's own default.
22972340 *
2298- * `sort` has two sources and `title`/`description`/`state` share their
2299- * names with fields on other operations. That is safe only because each
2300- * source subBlock's `condition` binds it to a single operation, so at
2301- * most one source of a given target is ever present in `params`.
2341+ * `generic-handler.ts` merges `{ ...inputs, ...params(inputs) }` and
2342+ * `providers/utils.ts` installs this as the provider `paramsTransform`,
2343+ * spreading over the model's tool-call arguments — so emitting a key the
2344+ * block did not supply would clobber a model-supplied value on the agent
2345+ * path, which is the one path these fields work on today.
23022346 */
23032347 params : ( params ) => {
23042348 const result : Record < string , unknown > = { }
2349+ const operation = typeof params . operation === 'string' ? params . operation : ''
23052350
2306- if ( params . reaction_content ) result . content = params . reaction_content
2307- if ( params . milestone_title ) result . title = params . milestone_title
2308- if ( params . milestone_description ) result . description = params . milestone_description
2309- if ( params . milestone_state ) result . state = params . milestone_state
2310- if ( params . milestone_sort ) result . sort = params . milestone_sort
2311- if ( params . fork_name ) result . name = params . fork_name
2312- if ( params . fork_sort ) result . sort = params . fork_sort
2351+ const isSet = ( value : unknown ) => value !== undefined && value !== null && value !== ''
23132352
2314- /**
2315- * A dropdown stores its option id, so this arrives as the string
2316- * 'true'/'false' while the tool declares `public` as a boolean. The
2317- * generic handler only JSON-parses `json`/`array` inputs, so nothing
2318- * else coerces it.
2319- *
2320- * Presence is tested rather than truthiness, because boolean `false` is
2321- * a real selection: the block declares this input as `boolean`, so a
2322- * writer following that schema stores `false` rather than `'false'`.
2323- * Under a truthy check the two disagree — `'false'` would force the
2324- * gist secret while `false` was dropped, letting a model-supplied
2325- * `public: true` through on the agent path. Only an unset field
2326- * (nullish or empty) defers to the tool's own default.
2327- */
2328- if (
2329- params . gist_public !== undefined &&
2330- params . gist_public !== null &&
2331- params . gist_public !== ''
2332- ) {
2333- result . public = params . gist_public === true || params . gist_public === 'true'
2353+ for ( const alias of GITHUB_PARAM_ALIASES ) {
2354+ if ( ! alias . operations . includes ( operation ) ) continue
2355+ const value = params [ alias . from ]
2356+ if ( ! isSet ( value ) ) continue
2357+ result [ alias . to ] = alias . toBoolean ? value === true || value === 'true' : value
23342358 }
23352359
23362360 return result
0 commit comments