@@ -15,7 +15,7 @@ import { StepVariableType } from '../analysis/types.js';
1515import { buildParameters , buildChoiceRule } from './expressionMapper.js' ;
1616import { type PathDialect , JSON_PATH_DIALECT } from './pathDialect.js' ;
1717import { STEP_ERROR_NAMES } from '../../runtime/index.js' ;
18- import { SDK_PARAM_SHAPE , SDK_RESOURCE_INJECT } from '../../runtime/services/metadata.js' ;
18+ import { SDK_PARAM_SHAPE , SDK_RESOURCE_INJECT , SERVICE_SDK_IDS } from '../../runtime/services/metadata.js' ;
1919import type {
2020 State ,
2121 StateMachineDefinition ,
@@ -313,7 +313,7 @@ function processAwaitAssignment(
313313
314314 if ( ! ts . isCallExpression ( callExpr ) ) return null ;
315315
316- const serviceCall = extractServiceCall ( ctx , callExpr ) ;
316+ const serviceCall = extractServiceCall ( ctx , callExpr ) ?? extractStepsAwsSdk ( ctx , callExpr ) ;
317317 if ( ! serviceCall ) return null ;
318318
319319 // Get the variable name for ResultPath
@@ -346,9 +346,9 @@ function processAwaitAssignment(
346346 ...( varName ? ctx . dialect . emitResultAssignment ( varName ) : ctx . dialect . emitResultDiscard ( ) ) ,
347347 ...( serviceCall . retry && { Retry : serviceCall . retry } ) ,
348348 ...( serviceCall . timeoutSeconds != null && { TimeoutSeconds : serviceCall . timeoutSeconds } ) ,
349- ...( serviceCall . timeoutSecondsPath && { TimeoutSecondsPath : serviceCall . timeoutSecondsPath } ) ,
349+ ...( serviceCall . timeoutSecondsPath && ctx . dialect . emitDynamicTimeout ( 'TimeoutSeconds' , serviceCall . timeoutSecondsPath ) ) ,
350350 ...( serviceCall . heartbeatSeconds != null && { HeartbeatSeconds : serviceCall . heartbeatSeconds } ) ,
351- ...( serviceCall . heartbeatSecondsPath && { HeartbeatSecondsPath : serviceCall . heartbeatSecondsPath } ) ,
351+ ...( serviceCall . heartbeatSecondsPath && ctx . dialect . emitDynamicTimeout ( 'HeartbeatSeconds' , serviceCall . heartbeatSecondsPath ) ) ,
352352 ...buildCatchRules ( ctx ) ,
353353 } ;
354354
@@ -372,7 +372,7 @@ function processAwaitFireAndForget(
372372
373373 if ( ! ts . isCallExpression ( callExpr ) ) return null ;
374374
375- const serviceCall = extractServiceCall ( ctx , callExpr ) ;
375+ const serviceCall = extractServiceCall ( ctx , callExpr ) ?? extractStepsAwsSdk ( ctx , callExpr ) ;
376376 if ( ! serviceCall ) return null ;
377377
378378 const stateName = generateStateName (
@@ -387,9 +387,9 @@ function processAwaitFireAndForget(
387387 ...ctx . dialect . emitResultDiscard ( ) ,
388388 ...( serviceCall . retry && { Retry : serviceCall . retry } ) ,
389389 ...( serviceCall . timeoutSeconds != null && { TimeoutSeconds : serviceCall . timeoutSeconds } ) ,
390- ...( serviceCall . timeoutSecondsPath && { TimeoutSecondsPath : serviceCall . timeoutSecondsPath } ) ,
390+ ...( serviceCall . timeoutSecondsPath && ctx . dialect . emitDynamicTimeout ( 'TimeoutSeconds' , serviceCall . timeoutSecondsPath ) ) ,
391391 ...( serviceCall . heartbeatSeconds != null && { HeartbeatSeconds : serviceCall . heartbeatSeconds } ) ,
392- ...( serviceCall . heartbeatSecondsPath && { HeartbeatSecondsPath : serviceCall . heartbeatSecondsPath } ) ,
392+ ...( serviceCall . heartbeatSecondsPath && ctx . dialect . emitDynamicTimeout ( 'HeartbeatSeconds' , serviceCall . heartbeatSecondsPath ) ) ,
393393 ...buildCatchRules ( ctx ) ,
394394 } ;
395395
@@ -405,7 +405,7 @@ function processAwaitReassignment(
405405 const callExpr = awaitExpr . expression ;
406406 if ( ! ts . isCallExpression ( callExpr ) ) return null ;
407407
408- const serviceCall = extractServiceCall ( ctx , callExpr ) ;
408+ const serviceCall = extractServiceCall ( ctx , callExpr ) ?? extractStepsAwsSdk ( ctx , callExpr ) ;
409409 if ( ! serviceCall ) return null ;
410410
411411 // Resolve the LHS identifier to get its variable name for result assignment
@@ -435,9 +435,9 @@ function processAwaitReassignment(
435435 ...resultAssignment ,
436436 ...( serviceCall . retry && { Retry : serviceCall . retry } ) ,
437437 ...( serviceCall . timeoutSeconds != null && { TimeoutSeconds : serviceCall . timeoutSeconds } ) ,
438- ...( serviceCall . timeoutSecondsPath && { TimeoutSecondsPath : serviceCall . timeoutSecondsPath } ) ,
438+ ...( serviceCall . timeoutSecondsPath && ctx . dialect . emitDynamicTimeout ( 'TimeoutSeconds' , serviceCall . timeoutSecondsPath ) ) ,
439439 ...( serviceCall . heartbeatSeconds != null && { HeartbeatSeconds : serviceCall . heartbeatSeconds } ) ,
440- ...( serviceCall . heartbeatSecondsPath && { HeartbeatSecondsPath : serviceCall . heartbeatSecondsPath } ) ,
440+ ...( serviceCall . heartbeatSecondsPath && ctx . dialect . emitDynamicTimeout ( 'HeartbeatSeconds' , serviceCall . heartbeatSecondsPath ) ) ,
441441 ...buildCatchRules ( ctx ) ,
442442 } ;
443443
@@ -473,21 +473,21 @@ function processStepsDelay(
473473 if ( resolved . kind === 'literal' && typeof resolved . value === 'number' ) {
474474 ( waitState as any ) . Seconds = resolved . value ;
475475 } else if ( resolved . kind === 'jsonpath' ) {
476- ( waitState as any ) . SecondsPath = resolved . path ;
476+ Object . assign ( waitState , ctx . dialect . emitDynamicWaitField ( 'Seconds' , resolved . path ! ) ) ;
477477 }
478478 } else if ( key === 'timestamp' ) {
479479 if ( resolved . kind === 'literal' && typeof resolved . value === 'string' ) {
480480 ( waitState as any ) . Timestamp = resolved . value ;
481481 } else if ( resolved . kind === 'jsonpath' ) {
482- ( waitState as any ) . TimestampPath = resolved . path ;
482+ Object . assign ( waitState , ctx . dialect . emitDynamicWaitField ( 'Timestamp' , resolved . path ! ) ) ;
483483 }
484484 } else if ( key === 'secondsPath' ) {
485485 if ( resolved . kind === 'literal' && typeof resolved . value === 'string' ) {
486- ( waitState as any ) . SecondsPath = resolved . value ;
486+ Object . assign ( waitState , ctx . dialect . emitDynamicWaitField ( 'Seconds' , resolved . value as string ) ) ;
487487 }
488488 } else if ( key === 'timestampPath' ) {
489489 if ( resolved . kind === 'literal' && typeof resolved . value === 'string' ) {
490- ( waitState as any ) . TimestampPath = resolved . value ;
490+ Object . assign ( waitState , ctx . dialect . emitDynamicWaitField ( 'Timestamp' , resolved . value as string ) ) ;
491491 }
492492 }
493493 }
@@ -1093,7 +1093,7 @@ function processBranchExpression(
10931093
10941094 if ( ! ts . isCallExpression ( callExpr ) ) return null ;
10951095
1096- const serviceCall = extractServiceCall ( ctx , callExpr as ts . CallExpression ) ;
1096+ const serviceCall = extractServiceCall ( ctx , callExpr as ts . CallExpression ) ?? extractStepsAwsSdk ( ctx , callExpr as ts . CallExpression ) ;
10971097 if ( ! serviceCall ) return null ;
10981098
10991099 const stateName = generateStateName (
@@ -1107,9 +1107,9 @@ function processBranchExpression(
11071107 ...( serviceCall . parameters && ctx . dialect . emitParameters ( serviceCall . parameters ) ) ,
11081108 ...( serviceCall . retry && { Retry : serviceCall . retry } ) ,
11091109 ...( serviceCall . timeoutSeconds != null && { TimeoutSeconds : serviceCall . timeoutSeconds } ) ,
1110- ...( serviceCall . timeoutSecondsPath && { TimeoutSecondsPath : serviceCall . timeoutSecondsPath } ) ,
1110+ ...( serviceCall . timeoutSecondsPath && ctx . dialect . emitDynamicTimeout ( 'TimeoutSeconds' , serviceCall . timeoutSecondsPath ) ) ,
11111111 ...( serviceCall . heartbeatSeconds != null && { HeartbeatSeconds : serviceCall . heartbeatSeconds } ) ,
1112- ...( serviceCall . heartbeatSecondsPath && { HeartbeatSecondsPath : serviceCall . heartbeatSecondsPath } ) ,
1112+ ...( serviceCall . heartbeatSecondsPath && ctx . dialect . emitDynamicTimeout ( 'HeartbeatSeconds' , serviceCall . heartbeatSecondsPath ) ) ,
11131113 End : true ,
11141114 } ;
11151115
@@ -1132,7 +1132,7 @@ function processReturnTerminator(
11321132
11331133 // return await svc.call(...) → Task state with End: true
11341134 if ( ts . isAwaitExpression ( expression ) && ts . isCallExpression ( expression . expression ) ) {
1135- const serviceCall = extractServiceCall ( ctx , expression . expression ) ;
1135+ const serviceCall = extractServiceCall ( ctx , expression . expression ) ?? extractStepsAwsSdk ( ctx , expression . expression ) ;
11361136 if ( serviceCall ) {
11371137 const stateName = generateStateName (
11381138 `Invoke_${ serviceCall . serviceVarName } ` ,
@@ -1144,9 +1144,9 @@ function processReturnTerminator(
11441144 ...( serviceCall . parameters && ctx . dialect . emitParameters ( serviceCall . parameters ) ) ,
11451145 ...( serviceCall . retry && { Retry : serviceCall . retry } ) ,
11461146 ...( serviceCall . timeoutSeconds != null && { TimeoutSeconds : serviceCall . timeoutSeconds } ) ,
1147- ...( serviceCall . timeoutSecondsPath && { TimeoutSecondsPath : serviceCall . timeoutSecondsPath } ) ,
1147+ ...( serviceCall . timeoutSecondsPath && ctx . dialect . emitDynamicTimeout ( 'TimeoutSeconds' , serviceCall . timeoutSecondsPath ) ) ,
11481148 ...( serviceCall . heartbeatSeconds != null && { HeartbeatSeconds : serviceCall . heartbeatSeconds } ) ,
1149- ...( serviceCall . heartbeatSecondsPath && { HeartbeatSecondsPath : serviceCall . heartbeatSecondsPath } ) ,
1149+ ...( serviceCall . heartbeatSecondsPath && ctx . dialect . emitDynamicTimeout ( 'HeartbeatSeconds' , serviceCall . heartbeatSecondsPath ) ) ,
11501150 ...buildCatchRules ( ctx ) ,
11511151 End : true ,
11521152 } ;
@@ -1170,7 +1170,7 @@ function processReturnTerminator(
11701170 const stateName = generateStateName ( 'Return_Result' , ctx . usedNames ) ;
11711171 const passState : PassState = {
11721172 Type : 'Pass' ,
1173- ...ctx . dialect . emitParameters ( params ) ,
1173+ ...ctx . dialect . emitPassOutput ( params ) ,
11741174 End : true ,
11751175 } ;
11761176
@@ -1228,7 +1228,7 @@ function processReturnTerminator(
12281228 const stateName = generateStateName ( 'Return_Result' , ctx . usedNames ) ;
12291229 const passState : PassState = {
12301230 Type : 'Pass' ,
1231- ...ctx . dialect . emitParameters ( ctx . dialect . wrapIntrinsicResult ( 'result' , resolved . path ! ) ) ,
1231+ ...ctx . dialect . emitPassOutput ( ctx . dialect . wrapIntrinsicResult ( 'result' , resolved . path ! ) ) ,
12321232 End : true ,
12331233 } ;
12341234 ctx . states . set ( stateName , passState ) ;
@@ -1292,7 +1292,7 @@ function processThrowTerminator(
12921292 if ( resolved . kind === 'literal' && typeof resolved . value === 'string' ) {
12931293 failState = { Type : 'Fail' , Error : errorName , Cause : resolved . value } ;
12941294 } else if ( resolved . kind === 'jsonpath' ) {
1295- failState = { Type : 'Fail' , Error : errorName , CausePath : resolved . path } ;
1295+ failState = { Type : 'Fail' , Error : errorName , ... ctx . dialect . emitDynamicFailCause ( resolved . path ! ) } ;
12961296 } else {
12971297 failState = { Type : 'Fail' , Error : errorName } ;
12981298 }
@@ -1428,19 +1428,92 @@ function extractServiceCall(
14281428 } ;
14291429}
14301430
1431+ // ---------------------------------------------------------------------------
1432+ // Steps.awsSdk() → Task state (generic AWS SDK escape hatch)
1433+ // ---------------------------------------------------------------------------
1434+
1435+ /**
1436+ * Recognize `Steps.awsSdk(service, action, params, ?options)` call sites
1437+ * and extract them as a service call that compiles to:
1438+ * Resource: "arn:aws:states:::aws-sdk:{sdkId}:{action}"
1439+ */
1440+ function extractStepsAwsSdk (
1441+ ctx : BuildContext ,
1442+ callExpr : ts . CallExpression ,
1443+ ) : ExtractedServiceCall | null {
1444+ // Check callee is Steps.awsSdk
1445+ if ( ! ts . isPropertyAccessExpression ( callExpr . expression ) ) return null ;
1446+ const propAccess = callExpr . expression ;
1447+ if ( ! ts . isIdentifier ( propAccess . expression ) || propAccess . expression . text !== 'Steps' ) return null ;
1448+ if ( propAccess . name . text !== 'awsSdk' ) return null ;
1449+
1450+ // First arg: service name (must be string literal)
1451+ const serviceArg = callExpr . arguments [ 0 ] ;
1452+ if ( ! serviceArg || ! ts . isStringLiteral ( serviceArg ) ) {
1453+ ctx . compilerContext . addError (
1454+ callExpr ,
1455+ 'Steps.awsSdk() requires a string literal service name as the first argument' ,
1456+ ErrorCodes . Gen . AwsSdkRequiresLiteral . code ,
1457+ ) ;
1458+ return null ;
1459+ }
1460+ const serviceName = serviceArg . text ;
1461+
1462+ // Second arg: action name (must be string literal)
1463+ const actionArg = callExpr . arguments [ 1 ] ;
1464+ if ( ! actionArg || ! ts . isStringLiteral ( actionArg ) ) {
1465+ ctx . compilerContext . addError (
1466+ callExpr ,
1467+ 'Steps.awsSdk() requires a string literal action name as the second argument' ,
1468+ ErrorCodes . Gen . AwsSdkRequiresLiteral . code ,
1469+ ) ;
1470+ return null ;
1471+ }
1472+ const actionName = actionArg . text ;
1473+
1474+ // Look up SDK ID for service name (PascalCase → lowercase SDK ID)
1475+ const sdkId = SERVICE_SDK_IDS [ serviceName ] ?? serviceName . toLowerCase ( ) ;
1476+ const resource = `arn:aws:states:::aws-sdk:${ sdkId } :${ actionName } ` ;
1477+
1478+ // Third arg: parameters (optional object literal)
1479+ let parameters : Record < string , unknown > | undefined ;
1480+ const paramsArg = callExpr . arguments [ 2 ] ;
1481+ if ( paramsArg && ts . isObjectLiteralExpression ( paramsArg ) ) {
1482+ parameters = buildParameters (
1483+ ctx . compilerContext ,
1484+ paramsArg ,
1485+ ctx . variables . toResolution ( ) ,
1486+ ctx . dialect ,
1487+ ) ;
1488+ }
1489+
1490+ // Fourth arg: task options (retry, timeout, heartbeat)
1491+ const taskOptions = extractTaskOptions ( ctx , callExpr , 3 ) ;
1492+
1493+ return {
1494+ serviceVarName : `${ serviceName } _${ actionName } ` ,
1495+ resource,
1496+ parameters,
1497+ methodInfo : { integration : 'sdk' , hasOutput : true , methodName : actionName } as ServiceMethodInfo ,
1498+ ...taskOptions ,
1499+ } ;
1500+ }
1501+
14311502// ---------------------------------------------------------------------------
14321503// Task options extraction (retry, timeout, heartbeat)
14331504// ---------------------------------------------------------------------------
14341505
14351506/**
1436- * Extract task-level options from the 2nd argument of a service call.
1507+ * Extract task-level options from the specified argument of a service call.
14371508 * Supports: retry, timeoutSeconds, heartbeatSeconds.
1509+ * @param optionsArgIndex — index of the options argument (default 1 for service.call(input, opts))
14381510 */
14391511function extractTaskOptions (
14401512 ctx : BuildContext ,
14411513 callExpr : ts . CallExpression ,
1514+ optionsArgIndex : number = 1 ,
14421515) : Pick < ExtractedServiceCall , 'retry' | 'timeoutSeconds' | 'timeoutSecondsPath' | 'heartbeatSeconds' | 'heartbeatSecondsPath' > {
1443- const optionsArg = callExpr . arguments [ 1 ] ;
1516+ const optionsArg = callExpr . arguments [ optionsArgIndex ] ;
14441517 if ( ! optionsArg || ! ts . isObjectLiteralExpression ( optionsArg ) ) return { } ;
14451518
14461519 const result : {
@@ -1774,7 +1847,7 @@ function buildTernaryBranchPass(
17741847 } else if ( resolved . kind === 'jsonpath' && resolved . path ) {
17751848 Object . assign ( passState , ctx . dialect . emitReturnPath ( resolved . path ) ) ;
17761849 } else if ( resolved . kind === 'intrinsic' && resolved . path ) {
1777- Object . assign ( passState , ctx . dialect . emitParameters ( ctx . dialect . wrapIntrinsicResult ( 'value' , resolved . path ! ) ) ) ;
1850+ Object . assign ( passState , ctx . dialect . emitPassOutput ( ctx . dialect . wrapIntrinsicResult ( 'value' , resolved . path ! ) ) ) ;
17781851 }
17791852
17801853 if ( nextState ) {
0 commit comments