@@ -29,97 +29,81 @@ import {
2929 jsmGetSlaTool ,
3030 jsmGetTransitionsTool ,
3131} from '@/tools/jsm'
32- import type { ToolConfig } from '@/tools/types'
32+ import type { ToolConfig , ToolResponse } from '@/tools/types'
3333
3434const DOMAIN = 'example.atlassian.net'
3535/** Injected by the executor from the OAuth credential before the tool's `body` runs. */
3636const ACCESS_TOKEN = 'token-123'
3737
3838interface PaginatedCase {
3939 operation : string
40- tool : ToolConfig < any , any >
40+ toolId : string
41+ /** The tool's own `request.body`, captured at its concrete param type by `paginatedCase`. */
42+ buildBody : ( params : Record < string , unknown > ) => Record < string , unknown >
4143 schema : z . ZodType
4244 extraInputs : Record < string , string >
4345}
4446
47+ /**
48+ * Captures each tool at its own generic so an incompatible tool/contract pairing is still a type
49+ * error at the call site, rather than being erased by a widened `ToolConfig` in the table type.
50+ */
51+ function paginatedCase < P , R extends ToolResponse > (
52+ operation : string ,
53+ tool : ToolConfig < P , R > ,
54+ schema : z . ZodType ,
55+ extraInputs : Record < string , string > = { }
56+ ) : PaginatedCase {
57+ return {
58+ operation,
59+ toolId : tool . id ,
60+ buildBody : ( params ) => {
61+ const bodyFn = tool . request . body
62+ if ( ! bodyFn ) throw new Error ( `${ tool . id } is missing request.body` )
63+ return bodyFn ( params as P ) as Record < string , unknown >
64+ } ,
65+ schema,
66+ extraInputs,
67+ }
68+ }
69+
4570/**
4671 * Every paginated JSM operation, wired to the tool it resolves to and the contract its route
4772 * parses the body with. This walks the real chain — block `tools.config.params` → the tool's
4873 * `request.body` → the route contract — which is exactly where `jsm_get_comments` broke: the
4974 * tools declare `start`/`limit` as `type: 'number'` while the contract demanded strings.
5075 */
5176const PAGINATED_CASES : PaginatedCase [ ] = [
52- {
53- operation : 'get_service_desks' ,
54- tool : jsmGetServiceDesksTool ,
55- schema : jsmServiceDesksBodySchema ,
56- extraInputs : { } ,
57- } ,
58- {
59- operation : 'get_request_types' ,
60- tool : jsmGetRequestTypesTool ,
61- schema : jsmRequestTypesToolBodySchema ,
62- extraInputs : { serviceDeskId : '1' } ,
63- } ,
64- {
65- operation : 'get_requests' ,
66- tool : jsmGetRequestsTool ,
67- schema : jsmRequestsBodySchema ,
68- extraInputs : { } ,
69- } ,
70- {
71- operation : 'get_comments' ,
72- tool : jsmGetCommentsTool ,
73- schema : jsmCommentsBodySchema ,
74- extraInputs : { issueIdOrKey : 'SD-123' } ,
75- } ,
76- {
77- operation : 'get_customers' ,
78- tool : jsmGetCustomersTool ,
79- schema : jsmCustomersBodySchema ,
80- extraInputs : { serviceDeskId : '1' } ,
81- } ,
82- {
83- operation : 'get_organizations' ,
84- tool : jsmGetOrganizationsTool ,
85- schema : jsmServiceDeskScopedBodySchema ,
86- extraInputs : { serviceDeskId : '1' } ,
87- } ,
88- {
89- operation : 'get_queues' ,
90- tool : jsmGetQueuesTool ,
91- schema : jsmQueuesBodySchema ,
92- extraInputs : { serviceDeskId : '1' } ,
93- } ,
94- {
95- operation : 'get_sla' ,
96- tool : jsmGetSlaTool ,
97- schema : jsmIssuePaginationBodySchema ,
98- extraInputs : { issueIdOrKey : 'SD-123' } ,
99- } ,
100- {
101- operation : 'get_transitions' ,
102- tool : jsmGetTransitionsTool ,
103- schema : jsmIssuePaginationBodySchema ,
104- extraInputs : { issueIdOrKey : 'SD-123' } ,
105- } ,
106- {
107- operation : 'get_participants' ,
108- tool : jsmGetParticipantsTool ,
109- schema : jsmParticipantsBodySchema ,
110- extraInputs : { issueIdOrKey : 'SD-123' } ,
111- } ,
112- {
113- operation : 'get_approvals' ,
114- tool : jsmGetApprovalsTool ,
115- schema : jsmApprovalsBodySchema ,
116- extraInputs : { issueIdOrKey : 'SD-123' } ,
117- } ,
77+ paginatedCase ( 'get_service_desks' , jsmGetServiceDesksTool , jsmServiceDesksBodySchema ) ,
78+ paginatedCase ( 'get_request_types' , jsmGetRequestTypesTool , jsmRequestTypesToolBodySchema , {
79+ serviceDeskId : '1' ,
80+ } ) ,
81+ paginatedCase ( 'get_requests' , jsmGetRequestsTool , jsmRequestsBodySchema ) ,
82+ paginatedCase ( 'get_comments' , jsmGetCommentsTool , jsmCommentsBodySchema , {
83+ issueIdOrKey : 'SD-123' ,
84+ } ) ,
85+ paginatedCase ( 'get_customers' , jsmGetCustomersTool , jsmCustomersBodySchema , {
86+ serviceDeskId : '1' ,
87+ } ) ,
88+ paginatedCase ( 'get_organizations' , jsmGetOrganizationsTool , jsmServiceDeskScopedBodySchema , {
89+ serviceDeskId : '1' ,
90+ } ) ,
91+ paginatedCase ( 'get_queues' , jsmGetQueuesTool , jsmQueuesBodySchema , { serviceDeskId : '1' } ) ,
92+ paginatedCase ( 'get_sla' , jsmGetSlaTool , jsmIssuePaginationBodySchema , { issueIdOrKey : 'SD-123' } ) ,
93+ paginatedCase ( 'get_transitions' , jsmGetTransitionsTool , jsmIssuePaginationBodySchema , {
94+ issueIdOrKey : 'SD-123' ,
95+ } ) ,
96+ paginatedCase ( 'get_participants' , jsmGetParticipantsTool , jsmParticipantsBodySchema , {
97+ issueIdOrKey : 'SD-123' ,
98+ } ) ,
99+ paginatedCase ( 'get_approvals' , jsmGetApprovalsTool , jsmApprovalsBodySchema , {
100+ issueIdOrKey : 'SD-123' ,
101+ } ) ,
118102]
119103
120104/** Run a set of block inputs through `tools.config.params`, then through the tool's request body. */
121105function buildRequestBody (
122- { operation, tool , extraInputs } : PaginatedCase ,
106+ { operation, buildBody , extraInputs } : PaginatedCase ,
123107 pagination : Record < string , string >
124108) {
125109 const paramsFn = JiraServiceManagementBlock . tools . config ?. params
@@ -133,22 +117,16 @@ function buildRequestBody(
133117 ...pagination ,
134118 } )
135119
136- const bodyFn = tool . request . body
137- if ( ! bodyFn ) throw new Error ( `${ tool . id } is missing request.body` )
138-
139- return bodyFn ( { ...toolParams , accessToken : ACCESS_TOKEN , domain : DOMAIN } ) as Record <
140- string ,
141- unknown
142- >
120+ return buildBody ( { ...toolParams , accessToken : ACCESS_TOKEN , domain : DOMAIN } )
143121}
144122
145123describe . each ( PAGINATED_CASES . map ( ( testCase ) => [ testCase . operation , testCase ] as const ) ) (
146124 'JiraServiceManagementBlock %s' ,
147125 ( _operation , testCase ) => {
148126 it ( 'resolves to the expected tool' , ( ) => {
149127 const toolFn = JiraServiceManagementBlock . tools . config ?. tool
150- expect ( toolFn ?.( { operation : testCase . operation } ) ) . toBe ( testCase . tool . id )
151- expect ( JiraServiceManagementBlock . tools . access ) . toContain ( testCase . tool . id )
128+ expect ( toolFn ?.( { operation : testCase . operation } ) ) . toBe ( testCase . toolId )
129+ expect ( JiraServiceManagementBlock . tools . access ) . toContain ( testCase . toolId )
152130 } )
153131
154132 it ( 'sends a body its route contract accepts when pagination is filled in' , ( ) => {
0 commit comments